line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
|
3
|
37
|
|
|
37
|
|
293
|
use strict; |
|
37
|
|
|
|
|
136
|
|
|
37
|
|
|
|
|
1217
|
|
4
|
37
|
|
|
37
|
|
248
|
use warnings; |
|
37
|
|
|
|
|
104
|
|
|
37
|
|
|
|
|
1597
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package WWW::Shopify::Liquid::Parser; |
7
|
37
|
|
|
37
|
|
266
|
use base 'WWW::Shopify::Liquid::Pipeline'; |
|
37
|
|
|
|
|
98
|
|
|
37
|
|
|
|
|
14386
|
|
8
|
37
|
|
|
37
|
|
319
|
use Module::Find; |
|
37
|
|
|
|
|
104
|
|
|
37
|
|
|
|
|
3118
|
|
9
|
37
|
|
|
37
|
|
295
|
use List::MoreUtils qw(firstidx part); |
|
37
|
|
|
|
|
92
|
|
|
37
|
|
|
|
|
316
|
|
10
|
37
|
|
|
37
|
|
32368
|
use List::Util qw(first); |
|
37
|
|
|
|
|
106
|
|
|
37
|
|
|
|
|
2563
|
|
11
|
37
|
|
|
37
|
|
14094
|
use WWW::Shopify::Liquid::Exception; |
|
37
|
|
|
|
|
144
|
|
|
37
|
|
|
|
|
49624
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
useall WWW::Shopify::Liquid::Operator; |
14
|
|
|
|
|
|
|
useall WWW::Shopify::Liquid::Tag; |
15
|
|
|
|
|
|
|
useall WWW::Shopify::Liquid::Filter; |
16
|
|
|
|
|
|
|
|
17
|
85
|
|
|
85
|
0
|
1050
|
sub new { return bless { |
18
|
|
|
|
|
|
|
# Internal |
19
|
|
|
|
|
|
|
order_of_operations => [], |
20
|
|
|
|
|
|
|
operators => {}, |
21
|
|
|
|
|
|
|
enclosing_tags => {}, |
22
|
|
|
|
|
|
|
free_tags => {}, |
23
|
|
|
|
|
|
|
filters => {}, |
24
|
|
|
|
|
|
|
inner_tags => {}, |
25
|
|
|
|
|
|
|
security => WWW::Shopify::Liquid::Security->new, |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# Determines whether or not we should error on finding a filter we don't recognize. Can be useful for parsing, without actually rendering ot have this on. |
28
|
|
|
|
|
|
|
# No tag version, because we need to know whether a tag is free or enclosing to parse correctly. |
29
|
|
|
|
|
|
|
accept_unknown_filters => 0, |
30
|
|
|
|
|
|
|
# Determines whether we clear the custom tag/filter cache after each parse. For security purposes, should be true by default, |
31
|
|
|
|
|
|
|
# or custom tags should be disabled entirely. |
32
|
|
|
|
|
|
|
transient_custom_operations => 1, |
33
|
|
|
|
|
|
|
custom_tags => {}, |
34
|
|
|
|
|
|
|
custom_filters => {}, |
35
|
|
|
|
|
|
|
transient_elements => [] |
36
|
|
|
|
|
|
|
}, $_[0]; } |
37
|
17
|
100
|
|
17
|
0
|
102
|
sub accept_unknown_filters { $_[0]->{accept_unknown_filters} = $_[1] if defined $_[1]; return $_[0]->{accept_unknown_filters}; } |
|
17
|
|
|
|
|
97
|
|
38
|
0
|
0
|
|
0
|
0
|
0
|
sub accept_method_calls { $_[0]->{accept_method_calls} = $_[1] if defined $_[1]; return $_[0]->{accept_method_calls}; } |
|
0
|
|
|
|
|
0
|
|
39
|
11
|
100
|
|
11
|
0
|
33
|
sub transient_custom_operations { $_[0]->{transient_custom_operations} = $_[1] if defined $_[1]; return $_[0]->{transient_custom_operations}; } |
|
11
|
|
|
|
|
30
|
|
40
|
15
|
|
|
15
|
0
|
50
|
sub custom_tags { return $_[0]->{custom_tags}; } |
41
|
2
|
|
|
2
|
0
|
8
|
sub custom_filters { return $_[0]->{custom_filters}; } |
42
|
1308
|
|
|
1308
|
0
|
7024
|
sub operator { return $_[0]->{operators}->{$_[1]}; } |
43
|
3136
|
|
|
3136
|
0
|
13237
|
sub operators { return $_[0]->{operators}; } |
44
|
1151
|
|
|
1151
|
0
|
1901
|
sub order_of_operations { return @{$_[0]->{order_of_operations}}; } |
|
1151
|
|
|
|
|
4104
|
|
45
|
1211
|
|
|
1211
|
0
|
5243
|
sub free_tags { return $_[0]->{free_tags}; } |
46
|
3392
|
|
|
3392
|
0
|
12201
|
sub enclosing_tags { return $_[0]->{enclosing_tags}; } |
47
|
601
|
|
|
601
|
0
|
2553
|
sub inner_tags { return $_[0]->{inner_tags}; } |
48
|
5229
|
|
|
5229
|
0
|
35421
|
sub filters { return $_[0]->{filters}; } |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub register_tag { |
51
|
1801
|
|
|
1801
|
0
|
4493
|
my ($self, $tag) = @_; |
52
|
1801
|
|
|
|
|
6682
|
$self->SUPER::register_tag($tag); |
53
|
1801
|
100
|
|
|
|
10703
|
$self->free_tags->{$tag->name} = $tag if $tag->is_free; |
54
|
1801
|
100
|
|
|
|
6399
|
if ($tag->is_enclosing) { |
55
|
945
|
|
|
|
|
2259
|
$self->enclosing_tags->{$tag->name} = $tag; |
56
|
945
|
|
|
|
|
4623
|
foreach my $inner_tag ($tag->inner_tags) { |
57
|
595
|
|
|
|
|
1410
|
$self->inner_tags->{$inner_tag} = 1; |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub register_operator { |
63
|
2380
|
|
|
2380
|
0
|
7936
|
$_[0]->SUPER::register_operator($_[1]); |
64
|
2380
|
|
|
|
|
10485
|
$_[0]->operators->{$_} = $_[1] for($_[1]->symbol); |
65
|
2380
|
|
|
|
|
5179
|
my $ooo = $_[0]->{order_of_operations}; |
66
|
2380
|
|
|
10710
|
|
13760
|
my $element = first { $_->[0]->priority == $_[1]->priority } @$ooo; |
|
10710
|
|
|
|
|
38044
|
|
67
|
2380
|
100
|
|
|
|
9577
|
if ($element) { |
68
|
1530
|
|
|
|
|
4146
|
push(@$element, $_[1]); |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
else { |
71
|
850
|
|
|
|
|
2789
|
push(@$ooo, [$_[1]]); |
72
|
|
|
|
|
|
|
} |
73
|
2380
|
|
|
|
|
9332
|
$_[0]->{order_of_operations} = [sort { $b->[0]->priority <=> $a->[0]->priority } @$ooo]; |
|
32215
|
|
|
|
|
79270
|
|
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
sub register_filter { |
76
|
5226
|
|
|
5226
|
0
|
16147
|
$_[0]->SUPER::register_filter($_[1]); |
77
|
5226
|
|
|
|
|
11232
|
$_[0]->filters->{$_[1]->name} = $_[1]; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub deregister_tag { |
81
|
3
|
|
|
3
|
0
|
5
|
my ($self, $tag) = @_; |
82
|
3
|
|
|
|
|
12
|
$self->SUPER::deregister_tag($tag); |
83
|
3
|
100
|
|
|
|
7
|
if ($tag->is_enclosing) { |
84
|
1
|
50
|
|
|
|
4
|
delete $self->enclosing_tags->{$tag->name} if $self->enclosing_tags->{$tag->name}; |
85
|
1
|
|
|
|
|
5
|
foreach my $inner_tag ($tag->inner_tags) { |
86
|
0
|
0
|
|
|
|
0
|
delete $self->inner_tags->{$inner_tag} if $self->inner_tags->{$inner_tag}; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
} else { |
89
|
2
|
50
|
|
|
|
6
|
delete $self->free_tags->{$tag->name} if $self->free_tags->{$tag->name}; |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
sub deregister_filter { |
93
|
1
|
|
|
1
|
0
|
4
|
my ($self, $filter) = @_; |
94
|
1
|
|
|
|
|
7
|
$self->SUPER::deregister_filter($filter); |
95
|
1
|
50
|
|
|
|
3
|
delete $self->filters->{$filter->name} if $self->filters->{$filter->name}; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub parse_filter_tokens { |
100
|
144
|
|
|
144
|
0
|
500
|
my ($self, $initial, @tokens) = @_; |
101
|
144
|
|
|
|
|
340
|
my $filter = shift(@tokens); |
102
|
144
|
50
|
|
|
|
952
|
my $filter_name = $filter->isa('WWW::Shopify::Liquid::Token::Operator') ? $filter->{core} : $filter->{core}->[0]->{core}; |
103
|
|
|
|
|
|
|
# TODO God, this is stupid, but temporary patch. |
104
|
144
|
|
|
|
|
308
|
my $filter_package; |
105
|
144
|
100
|
|
|
|
588
|
if ($filter_name =~ m/::/) { |
106
|
15
|
|
|
|
|
53
|
$filter_package = $filter_name; |
107
|
15
|
|
|
|
|
36
|
eval { $filter_package->name }; |
|
15
|
|
|
|
|
142
|
|
108
|
15
|
50
|
|
|
|
61
|
if ($@) { |
109
|
0
|
0
|
|
|
|
0
|
die new WWW::Shopify::Liquid::Exception::Parser::UnknownFilter($filter) if !$self->accept_unknown_filters; |
110
|
0
|
|
|
|
|
0
|
$filter_package = 'WWW::Shopify::Liquid::Filter::Unknown'; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
} else { |
113
|
129
|
100
|
|
|
|
558
|
if (!$self->{filters}->{$filter_name}) { |
114
|
15
|
100
|
|
|
|
87
|
die new WWW::Shopify::Liquid::Exception::Parser::UnknownFilter($filter) if !$self->accept_unknown_filters; |
115
|
14
|
|
|
|
|
48
|
$filter_package = 'WWW::Shopify::Liquid::Filter::Unknown'; |
116
|
|
|
|
|
|
|
} else { |
117
|
114
|
|
|
|
|
312
|
$filter_package = $self->{filters}->{$filter_name}; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
} |
120
|
143
|
50
|
66
|
|
|
869
|
die new WWW::Shopify::Liquid::Exception::Parser::Arguments($filter, "In order to have arguments, filter must be followed by a colon.") if int(@tokens) > 0 && $tokens[0]->{core} ne ":"; |
121
|
|
|
|
|
|
|
|
122
|
143
|
|
|
|
|
363
|
my @arguments = (); |
123
|
|
|
|
|
|
|
# Get rid of our colon. |
124
|
143
|
100
|
|
|
|
468
|
if (shift(@tokens)) { |
125
|
91
|
|
|
|
|
200
|
my $i = 0; |
126
|
91
|
100
|
66
|
177
|
|
582
|
@arguments = map { $self->parse_argument_tokens(grep { !$_->isa('WWW::Shopify::Liquid::Token::Separator') } @{$_}) } part { $i++ if $_->isa('WWW::Shopify::Liquid::Token::Separator') && $_->{core} eq ","; $i; } @tokens; |
|
134
|
|
|
|
|
308
|
|
|
177
|
|
|
|
|
985
|
|
|
134
|
|
|
|
|
329
|
|
|
177
|
|
|
|
|
962
|
|
|
177
|
|
|
|
|
504
|
|
127
|
|
|
|
|
|
|
} |
128
|
143
|
|
|
|
|
2007
|
$filter = $filter_package->new($self, $filter->{line}, $filter_name, $initial, @arguments); |
129
|
143
|
|
|
|
|
893
|
$filter->verify($self); |
130
|
142
|
|
|
|
|
513
|
return $filter; |
131
|
|
|
|
|
|
|
} |
132
|
37
|
|
|
37
|
|
375
|
use List::MoreUtils qw(part); |
|
37
|
|
|
|
|
109
|
|
|
37
|
|
|
|
|
427
|
|
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
sub walk_groupings { |
135
|
15
|
|
|
15
|
0
|
47
|
my ($self, $aggregate) = @_; |
136
|
15
|
|
|
|
|
36
|
for (grep { |
137
|
28
|
|
|
|
|
97
|
$aggregate->{members}->[$_]->isa('WWW::Shopify::Liquid::Token::Grouping') |
138
|
15
|
|
|
|
|
79
|
} 0..(int(@{$aggregate->{members}})-1)) { |
139
|
3
|
|
|
|
|
16
|
($aggregate->{members}->[$_]) = $self->parse_argument_tokens($aggregate->{members}->[$_]->members); |
140
|
|
|
|
|
|
|
} |
141
|
15
|
100
|
|
|
|
69
|
$self->walk_groupings($_) for (grep { $_->isa('WWW::Shopify::Liquid::Token::Hash') || $_->isa('WWW::Shopify::Liquid::Token::Array') } $aggregate->members); |
|
28
|
|
|
|
|
157
|
|
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# Similar, but doesn't deal with tags; deals solely with order of operations. |
146
|
|
|
|
|
|
|
sub parse_argument_tokens { |
147
|
1220
|
|
|
1220
|
0
|
3293
|
my ($self, @argument_tokens) = @_; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# Process all function calls. That means groupings, preceded by a varialbe. |
150
|
|
|
|
|
|
|
my @ids = grep { |
151
|
1220
|
100
|
|
|
|
3317
|
$argument_tokens[$_]->isa('WWW::Shopify::Liquid::Token::Variable') && $argument_tokens[$_+1]->isa('WWW::Shopify::Liquid::Token::Grouping') |
|
1582
|
|
|
|
|
9157
|
|
152
|
|
|
|
|
|
|
} (0..$#argument_tokens-1); |
153
|
1220
|
|
|
|
|
3141
|
for (@ids) { |
154
|
3
|
|
|
|
|
9
|
$argument_tokens[$_] = WWW::Shopify::Liquid::Token::FunctionCall->new($argument_tokens[$_]->{line}, pop(@{$argument_tokens[$_]->{core}}), $argument_tokens[$_], $self->parse_argument_tokens($argument_tokens[$_+1]->members)); |
|
3
|
|
|
|
|
18
|
|
155
|
3
|
|
|
|
|
13
|
$argument_tokens[$_+1] = undef; |
156
|
|
|
|
|
|
|
} |
157
|
1220
|
|
|
|
|
2393
|
@argument_tokens = grep { defined $_ } @argument_tokens; |
|
2727
|
|
|
|
|
6886
|
|
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# Process all groupings. |
160
|
1220
|
|
|
|
|
2928
|
($argument_tokens[$_]) = $self->parse_argument_tokens($argument_tokens[$_]->members) for (grep { $argument_tokens[$_]->isa('WWW::Shopify::Liquid::Token::Grouping') } 0..$#argument_tokens); |
|
2724
|
|
|
|
|
10706
|
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# Process all groupings inside named variables. |
163
|
1220
|
100
|
|
|
|
2794
|
($_->{core}) = $self->parse_argument_tokens($_->{core}->members) for (grep { $_->isa('WWW::Shopify::Liquid::Token::Variable::Named') && $_->{core}->isa('WWW::Shopify::Liquid::Token::Grouping') } @argument_tokens); |
|
2724
|
|
|
|
|
11434
|
|
164
|
|
|
|
|
|
|
# Preprocess all variant filters. |
165
|
1220
|
|
|
|
|
2527
|
for my $variable (grep { $_->isa('WWW::Shopify::Liquid::Token::Variable') } @argument_tokens) { |
|
2724
|
|
|
|
|
8757
|
|
166
|
1226
|
|
|
|
|
2456
|
my @core = @{$variable->{core}}; |
|
1226
|
|
|
|
|
3381
|
|
167
|
1226
|
|
|
|
|
2858
|
($variable->{core}->[$_]) = $self->parse_argument_tokens($core[$_]->members) for (grep { $core[$_]->isa('WWW::Shopify::Liquid::Token::Grouping') } 0..$#core); |
|
1680
|
|
|
|
|
7239
|
|
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Every member that's a grouping inside of the hash should be evaluated. |
171
|
1220
|
100
|
|
|
|
2629
|
$self->walk_groupings($_) for (grep { $_->isa('WWW::Shopify::Liquid::Token::Hash') || $_->isa('WWW::Shopify::Liquid::Token::Array') } @argument_tokens); |
|
2724
|
|
|
|
|
16902
|
|
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# Process unary operators first; these have highest priority, regardless of what the priority field says. |
174
|
1220
|
50
|
66
|
2726
|
|
8319
|
while ((my $idx = firstidx { $_->isa('WWW::Shopify::Liquid::Token::Operator') && defined $_->{core} && $self->operator($_->{core}) && $self->operator($_->{core})->arity eq "unary" } @argument_tokens) != -1) { |
|
2726
|
|
66
|
|
|
14300
|
|
175
|
3
|
|
|
|
|
11
|
my $op = $argument_tokens[$idx]; |
176
|
3
|
|
|
|
|
22
|
my $fixness = $self->operator($argument_tokens[$idx]->{core})->fixness; |
177
|
3
|
50
|
|
|
|
21
|
my $op1 = $fixness eq "postfix" ? $argument_tokens[$idx-1] : $argument_tokens[$idx+1]; |
178
|
3
|
50
|
|
|
|
23
|
my $start = $fixness eq "potsfix" ? $idx-1 : $idx; |
179
|
3
|
|
|
|
|
19
|
splice(@argument_tokens, $start, 2, $self->operator($argument_tokens[$idx]->{core})->new($op->{line}, $op->{core}, $op1)); |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# First, pull together filters. These are the highest priority operators, after parentheses. They also have their own weird syntax. |
183
|
1220
|
|
|
|
|
5042
|
my $top = undef; |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# Don't partition if we have any pipes. Pipes and multiple arguments don't play well together. |
186
|
1220
|
|
|
|
|
2077
|
my @partitions; |
187
|
1220
|
|
|
|
|
2121
|
my $has_pipe = 0; |
188
|
1220
|
100
|
|
|
|
2411
|
if (int(grep { $_->isa('WWW::Shopify::Liquid::Token::Operator') && $_->{core} eq "|" } @argument_tokens) == 0) { |
|
2721
|
100
|
|
|
|
12507
|
|
189
|
1100
|
|
|
|
|
2038
|
my $i = 0; |
190
|
1100
|
|
|
|
|
1866
|
$has_pipe = 1; |
191
|
1100
|
100
|
|
1973
|
|
5197
|
@partitions = part { $i++ if $_->isa('WWW::Shopify::Liquid::Token::Separator'); $i; } @argument_tokens; |
|
1973
|
|
|
|
|
7225
|
|
|
1973
|
|
|
|
|
6005
|
|
192
|
1100
|
|
|
|
|
3966
|
@partitions = map { my @n = grep { !$_->isa('WWW::Shopify::Liquid::Token::Separator') } @$_; \@n } @partitions; |
|
1031
|
|
|
|
|
2304
|
|
|
1973
|
|
|
|
|
7282
|
|
|
1031
|
|
|
|
|
3438
|
|
193
|
|
|
|
|
|
|
} else { |
194
|
120
|
|
|
|
|
396
|
@partitions = (\@argument_tokens); |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
|
199
|
1220
|
|
|
|
|
2280
|
my @tops; |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
|
203
|
1220
|
|
|
|
|
2680
|
foreach my $partition (@partitions) { |
204
|
1151
|
|
|
|
|
2632
|
my @tokens = @$partition; |
205
|
|
|
|
|
|
|
#@tokens = (grep { !$_->isa('WWW::Shopify::Liquid::Token::Separator') } @tokens) if !$has_pipe; |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Use the order of operations to create a binary tree structure. |
208
|
1151
|
|
|
|
|
3125
|
foreach my $operators ($self->order_of_operations) { |
209
|
11491
|
|
|
|
|
25137
|
my %ops = map { $_ => 1 } map { $_->symbol } @$operators; |
|
35620
|
|
|
|
|
80216
|
|
|
32176
|
|
|
|
|
112188
|
|
210
|
|
|
|
|
|
|
# If we have pipes, we deal with those, and parse their lower level arguments first; this is an exception. Rewrite? |
211
|
11491
|
100
|
|
|
|
31026
|
if ($operators->[0] eq 'WWW::Shopify::Liquid::Operator::Pipe') { |
212
|
1150
|
100
|
|
1945
|
|
4728
|
if ((my $idx = firstidx { $_->isa('WWW::Shopify::Liquid::Token::Operator') && $_->{core} eq "|" } @tokens) != -1) { |
|
1945
|
100
|
|
|
|
10653
|
|
213
|
120
|
50
|
|
|
|
432
|
die new WWW::Shopify::Liquid::Exception::Parser($tokens[0]) if $idx == 0; |
214
|
120
|
|
|
|
|
278
|
my $i = 0; |
215
|
|
|
|
|
|
|
# Part should consist of the first token before a pipe, and then split on all pipes after this., |
216
|
120
|
100
|
66
|
676
|
|
945
|
my @parts = map { shift(@{$_}) if $_->[0]->{core} && $_->[0]->{core} eq "|"; $_ } part { $i++ if $_->isa('WWW::Shopify::Liquid::Token::Operator') && $_->{core} eq "|"; $i; } splice(@tokens, $idx-1); |
|
264
|
100
|
66
|
|
|
1394
|
|
|
144
|
|
|
|
|
382
|
|
|
264
|
|
|
|
|
779
|
|
|
676
|
|
|
|
|
2727
|
|
|
676
|
|
|
|
|
1577
|
|
217
|
120
|
|
|
|
|
539
|
my $next = undef; |
218
|
120
|
|
|
|
|
291
|
$top = $self->parse_filter_tokens($self->parse_argument_tokens(@{shift(@parts)}), @{shift(@parts)}); |
|
120
|
|
|
|
|
738
|
|
|
120
|
|
|
|
|
584
|
|
219
|
118
|
|
|
|
|
568
|
while (my $part = shift(@parts)) { |
220
|
24
|
|
|
|
|
93
|
$top = $self->parse_filter_tokens($top, @$part); |
221
|
|
|
|
|
|
|
} |
222
|
118
|
|
|
|
|
616
|
push(@tokens, $top); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
} else { |
225
|
10341
|
100
|
|
16955
|
|
40708
|
while ((my $idx = firstidx { $_->isa('WWW::Shopify::Liquid::Token::Operator') && exists $ops{$_->{core}} } @tokens) != -1) { |
|
16955
|
|
|
|
|
92756
|
|
226
|
502
|
|
|
|
|
1968
|
my ($op1, $op, $op2) = @tokens[$idx-1..$idx+1]; |
227
|
|
|
|
|
|
|
# The one exception would be if we have a - operator, and nothing before, this is unary negative operator, i.e. 0 - number. |
228
|
502
|
100
|
33
|
|
|
8822
|
die new WWW::Shopify::Liquid::Exception::Parser::Operands($op, $op1, $op, $op2) unless |
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
229
|
|
|
|
|
|
|
$idx > 0 && $idx < $#tokens && |
230
|
|
|
|
|
|
|
($op1->isa('WWW::Shopify::Liquid::Operator') || $op1->isa('WWW::Shopify::Liquid::Token::Operand') || $op1->isa('WWW::Shopify::Liquid::Filter')) && |
231
|
|
|
|
|
|
|
($op2->isa('WWW::Shopify::Liquid::Operator') || $op2->isa('WWW::Shopify::Liquid::Token::Operand') || $op2->isa('WWW::Shopify::Liquid::Filter')); |
232
|
501
|
50
|
|
|
|
2097
|
($op1) = $self->parse_argument_tokens($op1->members) if $op1->isa('WWW::Shopify::Liquid::Token::Grouping'); |
233
|
501
|
50
|
|
|
|
1802
|
($op2) = $self->parse_argument_tokens($op2->members) if $op2->isa('WWW::Shopify::Liquid::Token::Grouping'); |
234
|
501
|
|
|
|
|
1576
|
splice(@tokens, $idx-1, 3, $self->operators->{$op->{core}}->new($op->{line}, $op->{core}, $op1, $op2)); |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# Only named variables can be without commas, for whatever reason. Goddammit shopify. |
240
|
1148
|
100
|
100
|
|
|
3083
|
die new WWW::Shopify::Liquid::Exception::Parser::Operands(@tokens) unless int(grep { !$_->isa('WWW::Shopify::Liquid::Token::Variable::Named') } @tokens) == 1 || int(grep { !$_->isa('WWW::Shopify::Liquid::Token::Variable::Named') } @tokens) == 0; |
|
1151
|
|
|
|
|
7099
|
|
|
7
|
|
|
|
|
49
|
|
241
|
1147
|
|
|
|
|
3226
|
push(@tops, @tokens); |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
1216
|
|
|
|
|
7505
|
return @tops; |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub parse_inner_tokens { |
249
|
834
|
|
|
834
|
0
|
2222
|
my ($self, @tokens) = @_; |
250
|
|
|
|
|
|
|
|
251
|
834
|
100
|
|
|
|
2403
|
return () if int(@tokens) == 0; |
252
|
|
|
|
|
|
|
|
253
|
801
|
|
|
|
|
1659
|
my @tags = (); |
254
|
|
|
|
|
|
|
# First we take a look and start matching up opening and ending tags. Those which are free tags we can leave as is. |
255
|
801
|
|
|
|
|
2501
|
while (my $token = shift(@tokens)) { |
256
|
1404
|
|
|
|
|
3075
|
my $line = $token->{line}; |
257
|
1404
|
100
|
|
|
|
7258
|
if ($token->isa('WWW::Shopify::Liquid::Token::Tag')) { |
|
|
100
|
|
|
|
|
|
258
|
493
|
|
|
|
|
1017
|
my $tag = undef; |
259
|
493
|
100
|
|
|
|
1495
|
if ($self->enclosing_tags->{$token->tag}) { |
|
|
100
|
|
|
|
|
|
260
|
318
|
|
|
|
|
760
|
my @internal = (); |
261
|
318
|
|
|
|
|
661
|
my @contents = (); |
262
|
318
|
|
|
|
|
896
|
my %allowed_internal_tags = map { $_ => 1 } $self->enclosing_tags->{$token->tag}->inner_tags; |
|
472
|
|
|
|
|
1677
|
|
263
|
318
|
|
|
|
|
902
|
my $level = 1; |
264
|
318
|
|
|
|
|
658
|
my $closed = undef; |
265
|
318
|
|
|
|
|
1202
|
for (0..$#tokens) { |
266
|
1647
|
100
|
|
|
|
6243
|
if ($tokens[$_]->isa('WWW::Shopify::Liquid::Token::Tag')) { |
267
|
922
|
100
|
100
|
|
|
2251
|
if ($self->enclosing_tags->{$tokens[$_]->tag}) { |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
268
|
175
|
|
|
|
|
462
|
++$level; |
269
|
|
|
|
|
|
|
} elsif (exists $allowed_internal_tags{$tokens[$_]->tag} && $level == 1) { |
270
|
82
|
|
|
|
|
198
|
$tokens[$_]->{arguments} = [$self->parse_argument_tokens(@{$tokens[$_]->{arguments}})]; |
|
82
|
|
|
|
|
367
|
|
271
|
82
|
|
|
|
|
1411
|
push(@internal, $_); |
272
|
|
|
|
|
|
|
} elsif ($tokens[$_]->tag eq "end" . $token->tag && $level == 1) { |
273
|
313
|
|
|
|
|
690
|
--$level; |
274
|
313
|
|
|
|
|
645
|
my $last_int = 0; |
275
|
313
|
|
|
|
|
860
|
foreach my $int (@internal, $_) { |
276
|
395
|
|
|
|
|
1416
|
push(@contents, [splice(@tokens, 0, $int-$last_int)]); |
277
|
395
|
100
|
66
|
|
|
1218
|
shift(@{$contents[0]}) if $self->enclosing_tags->{$token->tag}->inner_ignore_whitespace && int(@contents) > 0 && int(@{$contents[0]}) > 0 && $contents[0]->[0]->isa('WWW::Shopify::Liquid::Token::Text::Whitespace'); |
|
2
|
|
100
|
|
|
8
|
|
|
23
|
|
66
|
|
|
100
|
|
278
|
|
|
|
|
|
|
@contents = map { |
279
|
395
|
|
|
|
|
1100
|
my @array = @$_; |
|
507
|
|
|
|
|
1308
|
|
280
|
507
|
100
|
100
|
|
|
3563
|
if (int(@array) > 0 && $array[0]->isa('WWW::Shopify::Liquid::Token::Tag') && $allowed_internal_tags{$array[0]->tag}) { |
|
|
|
100
|
|
|
|
|
281
|
112
|
|
|
|
|
429
|
[$array[0], $self->parse_inner_tokens(@array[1..$#array])]; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
else { |
284
|
395
|
|
|
|
|
1722
|
[$self->parse_inner_tokens(@array)] |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
} @contents; |
287
|
393
|
|
|
|
|
1084
|
$last_int = $int; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
# Remove the endtag. |
290
|
311
|
|
|
|
|
631
|
shift(@tokens); |
291
|
311
|
|
|
|
|
623
|
$closed = 1; |
292
|
311
|
|
|
|
|
725
|
last; |
293
|
|
|
|
|
|
|
} elsif ($tokens[$_]->tag =~ m/^end/) { |
294
|
175
|
|
|
|
|
514
|
--$level; |
295
|
|
|
|
|
|
|
# TODO: Fix this whole thing; right now, no close tags are being spit out for the wrong tag. We do this to avoid an {% unless %}{% if %}{% else %}{% endif %}{% endunless%} situtation. |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
} |
299
|
316
|
100
|
|
|
|
1154
|
die new WWW::Shopify::Liquid::Exception::Parser::NoClose($token) unless $closed; |
300
|
311
|
|
|
|
|
980
|
$tag = $self->enclosing_tags->{$token->tag}->new($line, $token->tag, [$self->parse_argument_tokens(@{$token->{arguments}})], \@contents, $self); |
|
311
|
|
|
|
|
1145
|
|
301
|
311
|
|
|
|
|
1749
|
$tag->verify($self); |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
elsif ($self->free_tags->{$token->tag}) { |
304
|
169
|
|
|
|
|
481
|
$tag = $self->free_tags->{$token->tag}->new($line, $token->tag, [$self->parse_argument_tokens(@{$token->{arguments}})], undef, $self); |
|
169
|
|
|
|
|
720
|
|
305
|
169
|
|
|
|
|
885
|
$tag->verify($self); |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
else { |
308
|
6
|
0
|
33
|
|
|
25
|
die new WWW::Shopify::Liquid::Exception::Parser::NoOpen($token) if ($token->tag =~ m/^end(\w+)$/ && $self->enclosing_tags->{$1}); |
309
|
6
|
100
|
|
|
|
24
|
die new WWW::Shopify::Liquid::Exception::Parser::NakedInnerTag($token) if (exists $self->inner_tags->{$token->tag}); |
310
|
5
|
|
|
|
|
74
|
die new WWW::Shopify::Liquid::Exception::Parser::UnknownTag($token); |
311
|
|
|
|
|
|
|
} |
312
|
479
|
|
|
|
|
2087
|
push(@tags, $tag); |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
elsif ($token->isa('WWW::Shopify::Liquid::Token::Output')) { |
315
|
327
|
|
|
|
|
821
|
push(@tags, WWW::Shopify::Liquid::Tag::Output->new($line, [$self->parse_argument_tokens(@{$token->{core}})])); |
|
327
|
|
|
|
|
1256
|
|
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
else { |
318
|
584
|
|
|
|
|
2032
|
push(@tags, $token); |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
783
|
|
|
|
|
1529
|
my $top = undef; |
323
|
783
|
100
|
|
|
|
2078
|
if (int(@tags) > 1) { |
324
|
216
|
|
|
|
|
1169
|
$top = WWW::Shopify::Liquid::Operator::Concatenate->new($tags[0]->{line}, '', @tags); |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
else { |
327
|
567
|
|
|
|
|
1196
|
($top) = @tags; |
328
|
|
|
|
|
|
|
} |
329
|
783
|
|
|
|
|
3104
|
return $top; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub parse_tokens { |
333
|
327
|
|
|
327
|
0
|
5370
|
my ($self, @tokens) = @_; |
334
|
327
|
|
|
|
|
1319
|
my $ast = $self->parse_inner_tokens(@tokens); |
335
|
311
|
|
|
|
|
677
|
for (@{$self->{transient_elements}}) { |
|
311
|
|
|
|
|
1166
|
|
336
|
4
|
100
|
|
|
|
15
|
if ($_->isa('WWW::Shopify::Liquid::Tag')) { |
|
|
50
|
|
|
|
|
|
337
|
3
|
|
|
|
|
7
|
$self->deregister_tag($_); |
338
|
|
|
|
|
|
|
} elsif ($_->isa('WWW::Shopify::Liquid::Filter')) { |
339
|
1
|
|
|
|
|
4
|
$self->deregister_filter($_); |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
} |
342
|
311
|
|
|
|
|
921
|
$self->{transient_elements} = []; |
343
|
311
|
|
|
|
|
4987
|
return $ast; |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
sub unparse_argument_tokens { |
347
|
435
|
|
|
435
|
0
|
1030
|
my ($self, $ast) = @_; |
348
|
435
|
100
|
|
|
|
1176
|
return $ast if $self->is_processed($ast); |
349
|
434
|
100
|
|
|
|
2463
|
if ($ast->isa('WWW::Shopify::Liquid::Filter')) { |
|
|
100
|
|
|
|
|
|
350
|
|
|
|
|
|
|
my @optokens = ($self->unparse_argument_tokens($ast->{operand}), |
351
|
|
|
|
|
|
|
WWW::Shopify::Liquid::Token::Operator->new([0,0,0], '|'), |
352
|
|
|
|
|
|
|
($ast->transparent ? WWW::Shopify::Liquid::Token::Variable->new([0,0,0], WWW::Shopify::Liquid::Token::String->new([0,0,0], $ast->{core}->name)) : WWW::Shopify::Liquid::Token::Variable->new([0,0,0], WWW::Shopify::Liquid::Token::String->new([0,0,0], $ast->{core}))), |
353
|
30
|
100
|
|
|
|
126
|
(int(@{$ast->{arguments}}) > 0 ? (do { |
|
30
|
100
|
|
|
|
121
|
|
354
|
27
|
|
|
|
|
65
|
my @args = @{$ast->{arguments}}; |
|
27
|
|
|
|
|
82
|
|
355
|
|
|
|
|
|
|
( |
356
|
|
|
|
|
|
|
WWW::Shopify::Liquid::Token::Separator->new([0,0,0], ':'), |
357
|
27
|
100
|
|
|
|
106
|
(map { (($_ > 0 ? (WWW::Shopify::Liquid::Token::Separator->new([0,0,0], ',')) : ()), $self->unparse_argument_tokens($args[$_])) } 0..$#args) |
|
64
|
|
|
|
|
255
|
|
358
|
|
|
|
|
|
|
) |
359
|
|
|
|
|
|
|
}) : ()) |
360
|
|
|
|
|
|
|
); |
361
|
30
|
|
|
|
|
169
|
return @optokens; |
362
|
|
|
|
|
|
|
} elsif ($ast->isa('WWW::Shopify::Liquid::Operator')) { |
363
|
99
|
|
|
|
|
357
|
my @optokens = ($self->unparse_argument_tokens($ast->{operands}->[0]), WWW::Shopify::Liquid::Token::Operator->new([0,0,0], $ast->{core}), $self->unparse_argument_tokens($ast->{operands}->[1])); |
364
|
99
|
100
|
|
|
|
377
|
return WWW::Shopify::Liquid::Token::Grouping->new([0,0,0], @optokens) if $ast->requires_grouping; |
365
|
98
|
|
|
|
|
470
|
return @optokens; |
366
|
|
|
|
|
|
|
} else { |
367
|
305
|
|
|
|
|
1330
|
return $ast; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
sub unparse_tokens { |
372
|
218
|
|
|
218
|
0
|
4853
|
my ($self, $ast) = @_; |
373
|
218
|
100
|
100
|
|
|
643
|
return $ast if $self->is_processed($ast) || $ast->isa('WWW::Shopify::Liquid::Token'); |
374
|
159
|
100
|
|
|
|
782
|
if ($ast->isa('WWW::Shopify::Liquid::Tag')) { |
375
|
119
|
50
|
|
|
|
383
|
my @arguments = $ast->{arguments} ? $self->unparse_argument_tokens(@{$ast->{arguments}}) : (); |
|
119
|
|
|
|
|
397
|
|
376
|
119
|
100
|
|
|
|
697
|
if ($ast->isa('WWW::Shopify::Liquid::Tag::Enclosing')) { |
|
|
100
|
|
|
|
|
|
377
|
51
|
100
|
|
|
|
188
|
if ($ast->isa('WWW::Shopify::Liquid::Tag::If')) { |
378
|
29
|
100
|
|
|
|
191
|
return (WWW::Shopify::Liquid::Token::Tag->new([0,0,0], $ast->{core}, \@arguments), $self->unparse_tokens($ast->{true_path}), WWW::Shopify::Liquid::Token::Tag->new([0,0,0], 'end' . $ast->{core})) if !$ast->{false_path}; |
379
|
3
|
100
|
|
|
|
15
|
if ($ast->{false_path}->isa('WWW::Shopify::Liquid::Tag::If')) { |
380
|
|
|
|
|
|
|
# Untangle the thing, and spread out in an array all if statement that have if statements as their false_paths. |
381
|
1
|
|
|
|
|
6
|
my @elsifs; |
382
|
|
|
|
|
|
|
my $recursive; |
383
|
|
|
|
|
|
|
$recursive = sub { |
384
|
1
|
|
|
1
|
|
4
|
my ($ast) = @_; |
385
|
1
|
50
|
|
|
|
8
|
push(@elsifs, WWW::Shopify::Liquid::Token::Tag->new([0,0,0], 'elsif', ($ast->{arguments} ? [$self->unparse_argument_tokens(@{$ast->{arguments}})] : [])), $self->unparse_tokens($ast->{true_path})); |
|
1
|
|
|
|
|
8
|
|
386
|
1
|
50
|
33
|
|
|
15
|
if ($ast->{false_path} && $ast->{false_path}->isa('WWW::Shopify::Liquid::Tag::If')) { |
|
|
50
|
|
|
|
|
|
387
|
0
|
|
|
|
|
0
|
$recursive->($ast->{false_path}); |
388
|
|
|
|
|
|
|
} elsif ($ast->{false_path}) { |
389
|
1
|
|
|
|
|
7
|
push(@elsifs, WWW::Shopify::Liquid::Token::Tag->new([0,0,0], 'else'), $self->unparse_tokens($ast->{false_path})); |
390
|
|
|
|
|
|
|
} |
391
|
1
|
|
|
|
|
10
|
}; |
392
|
1
|
|
|
|
|
7
|
$recursive->($ast->{false_path}); |
393
|
1
|
|
|
|
|
7
|
return (WWW::Shopify::Liquid::Token::Tag->new([0,0,0], $ast->{core}, \@arguments), $self->unparse_tokens($ast->{true_path}), @elsifs, WWW::Shopify::Liquid::Token::Tag->new([0,0,0], 'end'. $ast->{core})); |
394
|
|
|
|
|
|
|
} |
395
|
2
|
|
|
|
|
12
|
return (WWW::Shopify::Liquid::Token::Tag->new([0,0,0], $ast->{core}, \@arguments), $self->unparse_tokens($ast->{true_path}), WWW::Shopify::Liquid::Token::Tag->new([0,0,0], 'else'), $self->unparse_tokens($ast->{false_path}), WWW::Shopify::Liquid::Token::Tag->new([0,0,0], 'end'. $ast->{core})); |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
else { |
398
|
22
|
|
|
|
|
122
|
return (WWW::Shopify::Liquid::Token::Tag->new([0,0,0], $ast->{core}, \@arguments), $self->unparse_tokens($ast->{contents}), WWW::Shopify::Liquid::Token::Tag->new([0,0,0], 'end' . $ast->{core})); |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
elsif ($ast->isa('WWW::Shopify::Liquid::Tag::Output')) { |
402
|
23
|
|
|
|
|
75
|
return (WWW::Shopify::Liquid::Token::Output->new([0,0,0], [$self->unparse_argument_tokens(@{$ast->{arguments}})])); |
|
23
|
|
|
|
|
68
|
|
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
else { |
405
|
45
|
|
|
|
|
263
|
return (WWW::Shopify::Liquid::Token::Tag->new([0,0,0], $ast->{core}, \@arguments)); |
406
|
|
|
|
|
|
|
} |
407
|
0
|
|
|
|
|
0
|
return $ast; |
408
|
|
|
|
|
|
|
} |
409
|
40
|
50
|
|
|
|
201
|
if ($ast->isa('WWW::Shopify::Liquid::Filter')) { |
410
|
0
|
|
|
|
|
0
|
return $ast; |
411
|
|
|
|
|
|
|
} |
412
|
40
|
50
|
|
|
|
157
|
return (map { $self->unparse_tokens($_) } @{$ast->{operands}}) if ($ast->isa('WWW::Shopify::Liquid::Operator::Concatenate')); |
|
139
|
|
|
|
|
732
|
|
|
40
|
|
|
|
|
136
|
|
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
1; |