line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
|
3
|
30
|
|
|
30
|
|
151
|
use strict; |
|
30
|
|
|
|
|
48
|
|
|
30
|
|
|
|
|
906
|
|
4
|
30
|
|
|
30
|
|
129
|
use warnings; |
|
30
|
|
|
|
|
43
|
|
|
30
|
|
|
|
|
1235
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package WWW::Shopify::Liquid::Parser; |
7
|
30
|
|
|
30
|
|
132
|
use base 'WWW::Shopify::Liquid::Pipeline'; |
|
30
|
|
|
|
|
50
|
|
|
30
|
|
|
|
|
11253
|
|
8
|
30
|
|
|
30
|
|
177
|
use Module::Find; |
|
30
|
|
|
|
|
48
|
|
|
30
|
|
|
|
|
2026
|
|
9
|
30
|
|
|
30
|
|
191
|
use List::MoreUtils qw(firstidx part); |
|
30
|
|
|
|
|
51
|
|
|
30
|
|
|
|
|
212
|
|
10
|
30
|
|
|
30
|
|
11872
|
use List::Util qw(first); |
|
30
|
|
|
|
|
49
|
|
|
30
|
|
|
|
|
2600
|
|
11
|
30
|
|
|
30
|
|
13946
|
use WWW::Shopify::Liquid::Exception; |
|
30
|
|
|
|
|
92
|
|
|
30
|
|
|
|
|
26421
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
useall WWW::Shopify::Liquid::Operator; |
14
|
|
|
|
|
|
|
useall WWW::Shopify::Liquid::Tag; |
15
|
|
|
|
|
|
|
useall WWW::Shopify::Liquid::Filter; |
16
|
|
|
|
|
|
|
|
17
|
0
|
|
|
0
|
|
|
sub new { return bless { |
18
|
|
|
|
|
|
|
order_of_operations => [], |
19
|
|
|
|
|
|
|
operators => {}, |
20
|
|
|
|
|
|
|
enclosing_tags => {}, |
21
|
|
|
|
|
|
|
free_tags => {}, |
22
|
|
|
|
|
|
|
filters => {}, |
23
|
|
|
|
|
|
|
inner_tags => {}, |
24
|
|
|
|
|
|
|
security => WWW::Shopify::Liquid::Security->new, |
25
|
|
|
|
|
|
|
accept_unknown_filters => 0 |
26
|
|
|
|
|
|
|
}, $_[0]; } |
27
|
0
|
0
|
|
0
|
|
|
sub accept_unknown_filters { $_[0]->{accept_unknown_filters} = $_[1] if defined $_[1]; return $_[0]->{accept_unknown_filters}; } |
|
0
|
|
|
|
|
|
|
28
|
0
|
|
|
0
|
|
|
sub operator { return $_[0]->{operators}->{$_[1]}; } |
29
|
0
|
|
|
0
|
|
|
sub operators { return $_[0]->{operators}; } |
30
|
0
|
|
|
0
|
|
|
sub order_of_operations { return @{$_[0]->{order_of_operations}}; } |
|
0
|
|
|
|
|
|
|
31
|
0
|
|
|
0
|
|
|
sub free_tags { return $_[0]->{free_tags}; } |
32
|
0
|
|
|
0
|
|
|
sub enclosing_tags { return $_[0]->{enclosing_tags}; } |
33
|
0
|
|
|
0
|
|
|
sub inner_tags { return $_[0]->{inner_tags}; } |
34
|
0
|
|
|
0
|
|
|
sub filters { return $_[0]->{filters}; } |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub register_tag { |
37
|
0
|
0
|
|
0
|
|
|
$_[0]->free_tags->{$_[1]->name} = $_[1] if $_[1]->is_free; |
38
|
0
|
0
|
|
|
|
|
if ($_[1]->is_enclosing) { |
39
|
0
|
|
|
|
|
|
$_[0]->enclosing_tags->{$_[1]->name} = $_[1]; |
40
|
0
|
|
|
|
|
|
foreach my $tag ($_[1]->inner_tags) { |
41
|
0
|
|
|
|
|
|
$_[0]->inner_tags->{$tag} = 1; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub register_operator { |
48
|
0
|
|
|
0
|
|
|
$_[0]->SUPER::register_operator($_[1]); |
49
|
0
|
|
|
|
|
|
$_[0]->operators->{$_} = $_[1] for($_[1]->symbol); |
50
|
0
|
|
|
|
|
|
my $ooo = $_[0]->{order_of_operations}; |
51
|
0
|
|
|
0
|
|
|
my $element = first { $_->[0]->priority == $_[1]->priority } @$ooo; |
|
0
|
|
|
|
|
|
|
52
|
0
|
0
|
|
|
|
|
if ($element) { |
53
|
0
|
|
|
|
|
|
push(@$element, $_[1]); |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
else { |
56
|
0
|
|
|
|
|
|
push(@$ooo, [$_[1]]); |
57
|
|
|
|
|
|
|
} |
58
|
0
|
|
|
|
|
|
$_[0]->{order_of_operations} = [sort { $b->[0]->priority <=> $a->[0]->priority } @$ooo]; |
|
0
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
sub register_filter { |
61
|
0
|
|
|
0
|
|
|
$_[0]->filters->{$_[1]->name} = $_[1]; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub parse_filter_tokens { |
67
|
0
|
|
|
0
|
|
|
my ($self, $initial, @tokens) = @_; |
68
|
0
|
|
|
|
|
|
my $filter = shift(@tokens); |
69
|
0
|
|
|
|
|
|
my $filter_name = $filter->{core}->[0]->{core}; |
70
|
|
|
|
|
|
|
# TODO God, this is stupid, but temporary patch. |
71
|
0
|
|
|
|
|
|
my $filter_package; |
72
|
0
|
0
|
|
|
|
|
if ($filter_name =~ m/::/) { |
73
|
0
|
|
|
|
|
|
$filter_package = $filter_name; |
74
|
0
|
|
|
|
|
|
eval { $filter_package->name }; |
|
0
|
|
|
|
|
|
|
75
|
0
|
0
|
|
|
|
|
if ($@) { |
76
|
0
|
0
|
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser::UnknownFilter($filter) if !$self->accept_unknown_filters; |
77
|
0
|
|
|
|
|
|
$filter_package = 'WWW::Shopify::Liquid::Filter::Unknown'; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
} else { |
80
|
0
|
0
|
|
|
|
|
if (!$self->{filters}->{$filter_name}) { |
81
|
0
|
0
|
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser::UnknownFilter($filter) if !$self->accept_unknown_filters; |
82
|
0
|
|
|
|
|
|
$filter_package = 'WWW::Shopify::Liquid::Filter::Unknown'; |
83
|
|
|
|
|
|
|
} else { |
84
|
0
|
|
|
|
|
|
$filter_package = $self->{filters}->{$filter_name}; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
} |
87
|
0
|
0
|
0
|
|
|
|
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 ":"; |
88
|
|
|
|
|
|
|
|
89
|
0
|
|
|
|
|
|
my @arguments = (); |
90
|
|
|
|
|
|
|
# Get rid of our colon. |
91
|
0
|
0
|
|
|
|
|
if (shift(@tokens)) { |
92
|
0
|
|
|
|
|
|
my $i = 0; |
93
|
0
|
0
|
0
|
0
|
|
|
@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; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
} |
95
|
0
|
|
|
|
|
|
$filter = $filter_package->new($initial->{line}, $filter_name, $initial, @arguments); |
96
|
0
|
|
|
|
|
|
$filter->verify($self); |
97
|
0
|
|
|
|
|
|
return $filter; |
98
|
|
|
|
|
|
|
} |
99
|
30
|
|
|
30
|
|
195
|
use List::MoreUtils qw(part); |
|
30
|
|
|
|
|
61
|
|
|
30
|
|
|
|
|
251
|
|
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
# Similar, but doesn't deal with tags; deals solely with order of operations. |
102
|
|
|
|
|
|
|
sub parse_argument_tokens { |
103
|
0
|
|
|
0
|
|
|
my ($self, @argument_tokens) = @_; |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Process all groupings. |
106
|
0
|
|
|
|
|
|
($argument_tokens[$_]) = $self->parse_argument_tokens($argument_tokens[$_]->members) for (grep { $argument_tokens[$_]->isa('WWW::Shopify::Liquid::Token::Grouping') } 0..$#argument_tokens); |
|
0
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Process all groupings inside named variables. |
111
|
0
|
0
|
|
|
|
|
($_->{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); |
|
0
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Preprocess all variant filters. |
113
|
0
|
|
|
|
|
|
for my $variable (grep { $_->isa('WWW::Shopify::Liquid::Token::Variable') } @argument_tokens) { |
|
0
|
|
|
|
|
|
|
114
|
0
|
|
|
|
|
|
my @core = @{$variable->{core}}; |
|
0
|
|
|
|
|
|
|
115
|
0
|
|
|
|
|
|
($variable->{core}->[$_]) = $self->parse_argument_tokens($core[$_]->members) for (grep { $core[$_]->isa('WWW::Shopify::Liquid::Token::Grouping') } 0..$#core); |
|
0
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# Process unary operators first; these have highest priority, regardless of what the priority field says. |
121
|
0
|
0
|
0
|
0
|
|
|
while ((my $idx = firstidx { $_->isa('WWW::Shopify::Liquid::Token::Operator') && defined $_->{core} && $self->operator($_->{core}) && $self->operator($_->{core})->arity eq "unary" } @argument_tokens) != -1) { |
|
0
|
|
0
|
|
|
|
|
122
|
0
|
|
|
|
|
|
my $op = $argument_tokens[$idx]; |
123
|
0
|
|
|
|
|
|
my $fixness = $self->operator($argument_tokens[$idx]->{core})->fixness; |
124
|
0
|
0
|
|
|
|
|
my $op1 = $fixness eq "postfix" ? $argument_tokens[$idx-1] : $argument_tokens[$idx+1]; |
125
|
0
|
0
|
|
|
|
|
my $start = $fixness eq "potsfix" ? $idx-1 : $idx; |
126
|
0
|
|
|
|
|
|
splice(@argument_tokens, $start, 2, $self->operator($argument_tokens[$idx]->{core})->new($op->{line}, $op->{core}, $op1)); |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# First, pull together filters. These are the highest priority operators, after parentheses. They also have their own weird syntax. |
130
|
0
|
|
|
|
|
|
my $top = undef; |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# Don't partition if we have any pipes. Pipes and multiple arguments don't play well together. |
133
|
0
|
|
|
|
|
|
my @partitions; |
134
|
0
|
|
|
|
|
|
my $has_pipe = 0; |
135
|
0
|
0
|
|
|
|
|
if (int(grep { $_->isa('WWW::Shopify::Liquid::Token::Operator') && $_->{core} eq "|" } @argument_tokens) == 0) { |
|
0
|
0
|
|
|
|
|
|
136
|
0
|
|
|
|
|
|
my $i = 0; |
137
|
0
|
|
|
|
|
|
$has_pipe = 1; |
138
|
0
|
0
|
|
0
|
|
|
@partitions = part { $i++ if $_->isa('WWW::Shopify::Liquid::Token::Separator'); $i; } @argument_tokens; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
139
|
0
|
|
|
|
|
|
@partitions = map { my @n = grep { !$_->isa('WWW::Shopify::Liquid::Token::Separator') } @$_; \@n } @partitions; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
} else { |
141
|
0
|
|
|
|
|
|
@partitions = (\@argument_tokens); |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
0
|
|
|
|
|
|
my @tops; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
|
148
|
0
|
|
|
|
|
|
foreach my $partition (@partitions) { |
149
|
0
|
|
|
|
|
|
my @tokens = @$partition; |
150
|
|
|
|
|
|
|
#@tokens = (grep { !$_->isa('WWW::Shopify::Liquid::Token::Separator') } @tokens) if !$has_pipe; |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# Use the order of operations to create a binary tree structure. |
153
|
0
|
|
|
|
|
|
foreach my $operators ($self->order_of_operations) { |
154
|
0
|
|
|
|
|
|
my %ops = map { $_ => 1 } map { $_->symbol } @$operators; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# If we have pipes, we deal with those, and parse their lower level arguments first; this is an exception. Rewrite? |
156
|
0
|
0
|
|
|
|
|
if ($operators->[0] eq 'WWW::Shopify::Liquid::Operator::Pipe') { |
157
|
0
|
0
|
|
0
|
|
|
if ((my $idx = firstidx { $_->isa('WWW::Shopify::Liquid::Token::Operator') && $_->{core} eq "|" } @tokens) != -1) { |
|
0
|
0
|
|
|
|
|
|
158
|
0
|
0
|
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser($tokens[0]) if $idx == 0; |
159
|
0
|
|
|
|
|
|
my $i = 0; |
160
|
|
|
|
|
|
|
# Part should consist of the first token before a pipe, and then split on all pipes after this., |
161
|
0
|
0
|
0
|
0
|
|
|
my @parts = map { shift(@{$_}) if $_->[0]->{core} eq "|"; $_ } part { $i++ if $_->isa('WWW::Shopify::Liquid::Token::Operator') && $_->{core} eq "|"; $i; } splice(@tokens, $idx-1); |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
162
|
0
|
|
|
|
|
|
my $next = undef; |
163
|
0
|
|
|
|
|
|
$top = $self->parse_filter_tokens($self->parse_argument_tokens(@{shift(@parts)}), @{shift(@parts)}); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
164
|
0
|
|
|
|
|
|
while (my $part = shift(@parts)) { |
165
|
0
|
|
|
|
|
|
$top = $self->parse_filter_tokens($top, @$part); |
166
|
|
|
|
|
|
|
} |
167
|
0
|
|
|
|
|
|
push(@tokens, $top); |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
} else { |
170
|
0
|
0
|
|
0
|
|
|
while ((my $idx = firstidx { $_->isa('WWW::Shopify::Liquid::Token::Operator') && exists $ops{$_->{core}} } @tokens) != -1) { |
|
0
|
|
|
|
|
|
|
171
|
0
|
|
|
|
|
|
my ($op1, $op, $op2) = @tokens[$idx-1..$idx+1]; |
172
|
|
|
|
|
|
|
# The one exception would be if we have a - operator, and nothing before, this is unary negative operator, i.e. 0 - number. |
173
|
0
|
0
|
0
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser::Operands($tokens[0], $op1, $op, $op2) unless |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
174
|
|
|
|
|
|
|
$idx > 0 && $idx < $#tokens && |
175
|
|
|
|
|
|
|
($op1->isa('WWW::Shopify::Liquid::Operator') || $op1->isa('WWW::Shopify::Liquid::Token::Operand') || $op1->isa('WWW::Shopify::Liquid::Filter')) && |
176
|
|
|
|
|
|
|
($op2->isa('WWW::Shopify::Liquid::Operator') || $op2->isa('WWW::Shopify::Liquid::Token::Operand') || $op2->isa('WWW::Shopify::Liquid::Filter')); |
177
|
0
|
0
|
|
|
|
|
($op1) = $self->parse_argument_tokens($op1->members) if $op1->isa('WWW::Shopify::Liquid::Token::Grouping'); |
178
|
0
|
0
|
|
|
|
|
($op2) = $self->parse_argument_tokens($op2->members) if $op2->isa('WWW::Shopify::Liquid::Token::Grouping'); |
179
|
0
|
|
|
|
|
|
splice(@tokens, $idx-1, 3, $self->operators->{$op->{core}}->new($op->{line}, $op->{core}, $op1, $op2)); |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# Only named variables can be without commas, for whatever reason. Goddammit shopify. |
185
|
0
|
0
|
0
|
|
|
|
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; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
186
|
0
|
|
|
|
|
|
push(@tops, @tokens); |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
|
return @tops; |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub parse_tokens { |
193
|
0
|
|
|
0
|
|
|
my ($self, @tokens) = @_; |
194
|
|
|
|
|
|
|
|
195
|
0
|
0
|
|
|
|
|
return () if int(@tokens) == 0; |
196
|
|
|
|
|
|
|
|
197
|
0
|
|
|
|
|
|
my @tags = (); |
198
|
|
|
|
|
|
|
# First we take a look and start matching up opening and ending tags. Those which are free tags we can leave as is. |
199
|
0
|
|
|
|
|
|
while (my $token = shift(@tokens)) { |
200
|
0
|
|
|
|
|
|
my $line = $token->{line}; |
201
|
0
|
0
|
|
|
|
|
if ($token->isa('WWW::Shopify::Liquid::Token::Tag')) { |
|
|
0
|
|
|
|
|
|
202
|
0
|
|
|
|
|
|
my $tag = undef; |
203
|
0
|
0
|
|
|
|
|
if ($self->enclosing_tags->{$token->tag}) { |
|
|
0
|
|
|
|
|
|
204
|
0
|
|
|
|
|
|
my @internal = (); |
205
|
0
|
|
|
|
|
|
my @contents = (); |
206
|
0
|
|
|
|
|
|
my %allowed_internal_tags = map { $_ => 1 } $self->enclosing_tags->{$token->tag}->inner_tags; |
|
0
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
|
my $level = 1; |
208
|
0
|
|
|
|
|
|
my $closed = undef; |
209
|
0
|
|
|
|
|
|
for (0..$#tokens) { |
210
|
0
|
0
|
|
|
|
|
if ($tokens[$_]->isa('WWW::Shopify::Liquid::Token::Tag')) { |
211
|
0
|
0
|
0
|
|
|
|
if ($self->enclosing_tags->{$tokens[$_]->tag}) { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
212
|
0
|
|
|
|
|
|
++$level; |
213
|
|
|
|
|
|
|
} elsif (exists $allowed_internal_tags{$tokens[$_]->tag} && $level == 1) { |
214
|
0
|
|
|
|
|
|
$tokens[$_]->{arguments} = [$self->parse_argument_tokens(@{$tokens[$_]->{arguments}})]; |
|
0
|
|
|
|
|
|
|
215
|
0
|
|
|
|
|
|
push(@internal, $_); |
216
|
|
|
|
|
|
|
} elsif ($tokens[$_]->tag eq "end" . $token->tag && $level == 1) { |
217
|
0
|
|
|
|
|
|
--$level; |
218
|
0
|
|
|
|
|
|
my $last_int = 0; |
219
|
0
|
|
|
|
|
|
foreach my $int (@internal, $_) { |
220
|
0
|
|
|
|
|
|
push(@contents, [splice(@tokens, 0, $int-$last_int)]); |
221
|
0
|
0
|
0
|
|
|
|
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'); |
|
0
|
|
0
|
|
|
|
|
|
0
|
|
0
|
|
|
|
|
222
|
|
|
|
|
|
|
@contents = map { |
223
|
0
|
|
|
|
|
|
my @array = @$_; |
|
0
|
|
|
|
|
|
|
224
|
0
|
0
|
0
|
|
|
|
if (int(@array) > 0 && $array[0]->isa('WWW::Shopify::Liquid::Token::Tag') && $allowed_internal_tags{$array[0]->tag}) { |
|
|
|
0
|
|
|
|
|
225
|
0
|
|
|
|
|
|
[$array[0], $self->parse_tokens(@array[1..$#array])]; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
else { |
228
|
0
|
|
|
|
|
|
[$self->parse_tokens(@array)] |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
} @contents; |
231
|
0
|
|
|
|
|
|
$last_int = $int; |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
# Remove the endtag. |
234
|
0
|
|
|
|
|
|
shift(@tokens); |
235
|
0
|
|
|
|
|
|
$closed = 1; |
236
|
0
|
|
|
|
|
|
last; |
237
|
|
|
|
|
|
|
} elsif ($tokens[$_]->tag =~ m/^end/) { |
238
|
0
|
|
|
|
|
|
--$level; |
239
|
|
|
|
|
|
|
# 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. |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
} |
243
|
0
|
0
|
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser::NoClose($token) unless $closed; |
244
|
0
|
|
|
|
|
|
$tag = $self->enclosing_tags->{$token->tag}->new($line, $token->tag, [$self->parse_argument_tokens(@{$token->{arguments}})], \@contents); |
|
0
|
|
|
|
|
|
|
245
|
0
|
|
|
|
|
|
$tag->verify($self); |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
elsif ($self->free_tags->{$token->tag}) { |
248
|
0
|
|
|
|
|
|
$tag = $self->free_tags->{$token->tag}->new($line, $token->tag, [$self->parse_argument_tokens(@{$token->{arguments}})]); |
|
0
|
|
|
|
|
|
|
249
|
0
|
|
|
|
|
|
$tag->verify($self); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
else { |
252
|
0
|
0
|
0
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser::NoOpen($token) if ($token->tag =~ m/^end(\w+)$/ && $self->enclosing_tags->{$1}); |
253
|
0
|
0
|
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser::NakedInnerTag($token) if (exists $self->inner_tags->{$token->tag}); |
254
|
0
|
|
|
|
|
|
die new WWW::Shopify::Liquid::Exception::Parser::UnknownTag($token); |
255
|
|
|
|
|
|
|
} |
256
|
0
|
|
|
|
|
|
push(@tags, $tag); |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
elsif ($token->isa('WWW::Shopify::Liquid::Token::Output')) { |
259
|
0
|
|
|
|
|
|
push(@tags, WWW::Shopify::Liquid::Tag::Output->new($line, [$self->parse_argument_tokens(@{$token->{core}})])); |
|
0
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
else { |
262
|
0
|
|
|
|
|
|
push(@tags, $token); |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
0
|
|
|
|
|
|
my $top = undef; |
267
|
0
|
0
|
|
|
|
|
if (int(@tags) > 1) { |
268
|
0
|
|
|
|
|
|
$top = WWW::Shopify::Liquid::Operator::Concatenate->new($tags[0]->{line}, '', @tags); |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
else { |
271
|
0
|
|
|
|
|
|
($top) = @tags; |
272
|
|
|
|
|
|
|
} |
273
|
0
|
|
|
|
|
|
return $top; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub unparse_argument_tokens { |
277
|
0
|
|
|
0
|
|
|
my ($self, $ast) = @_; |
278
|
0
|
0
|
|
|
|
|
return $ast if $self->is_processed($ast); |
279
|
0
|
0
|
|
|
|
|
if ($ast->isa('WWW::Shopify::Liquid::Filter')) { |
|
|
0
|
|
|
|
|
|
280
|
|
|
|
|
|
|
my @optokens = ($self->unparse_argument_tokens($ast->{operand}), |
281
|
|
|
|
|
|
|
WWW::Shopify::Liquid::Token::Operator->new([0,0,0], '|'), |
282
|
|
|
|
|
|
|
WWW::Shopify::Liquid::Token::Variable->new([0,0,0], WWW::Shopify::Liquid::Token::String->new([0,0,0], $ast->{core})), |
283
|
0
|
0
|
|
|
|
|
(int(@{$ast->{arguments}}) > 0 ? (do { |
|
0
|
|
|
|
|
|
|
284
|
0
|
|
|
|
|
|
my @args = @{$ast->{arguments}}; |
|
0
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
( |
286
|
|
|
|
|
|
|
WWW::Shopify::Liquid::Token::Separator->new([0,0,0], ':'), |
287
|
0
|
0
|
|
|
|
|
(map { (($_ > 0 ? (WWW::Shopify::Liquid::Token::Separator->new([0,0,0], ',')) : ()), $self->unparse_argument_tokens($args[$_])) } 0..$#args) |
|
0
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
) |
289
|
|
|
|
|
|
|
}) : ()) |
290
|
|
|
|
|
|
|
); |
291
|
0
|
|
|
|
|
|
return @optokens; |
292
|
|
|
|
|
|
|
} elsif ($ast->isa('WWW::Shopify::Liquid::Operator')) { |
293
|
0
|
|
|
|
|
|
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])); |
294
|
0
|
0
|
|
|
|
|
return WWW::Shopify::Liquid::Token::Grouping->new([0,0,0], @optokens) if $ast->requires_grouping; |
295
|
0
|
|
|
|
|
|
return @optokens; |
296
|
|
|
|
|
|
|
} else { |
297
|
0
|
|
|
|
|
|
return $ast; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
sub unparse_tokens { |
302
|
0
|
|
|
0
|
|
|
my ($self, $ast) = @_; |
303
|
0
|
0
|
0
|
|
|
|
return $ast if $self->is_processed($ast) || $ast->isa('WWW::Shopify::Liquid::Token'); |
304
|
0
|
0
|
|
|
|
|
if ($ast->isa('WWW::Shopify::Liquid::Tag')) { |
305
|
0
|
0
|
|
|
|
|
my @arguments = $ast->{arguments} ? $self->unparse_argument_tokens(@{$ast->{arguments}}) : (); |
|
0
|
|
|
|
|
|
|
306
|
0
|
0
|
|
|
|
|
if ($ast->isa('WWW::Shopify::Liquid::Tag::Enclosing')) { |
|
|
0
|
|
|
|
|
|
307
|
0
|
0
|
|
|
|
|
if ($ast->isa('WWW::Shopify::Liquid::Tag::If')) { |
308
|
0
|
0
|
|
|
|
|
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}; |
309
|
0
|
|
|
|
|
|
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})); |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
else { |
312
|
0
|
|
|
|
|
|
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})); |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
elsif ($ast->isa('WWW::Shopify::Liquid::Tag::Output')) { |
316
|
0
|
|
|
|
|
|
return (WWW::Shopify::Liquid::Token::Output->new([0,0,0], [$self->unparse_argument_tokens(@{$ast->{arguments}})])); |
|
0
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
else { |
319
|
0
|
|
|
|
|
|
return (WWW::Shopify::Liquid::Token::Tag->new([0,0,0], $ast->{core}, \@arguments)); |
320
|
|
|
|
|
|
|
} |
321
|
0
|
|
|
|
|
|
return $ast; |
322
|
|
|
|
|
|
|
} |
323
|
0
|
0
|
|
|
|
|
if ($ast->isa('WWW::Shopify::Liquid::Filter')) { |
324
|
0
|
|
|
|
|
|
return $ast; |
325
|
|
|
|
|
|
|
} |
326
|
0
|
0
|
|
|
|
|
return (map { $self->unparse_tokens($_) } @{$ast->{operands}}) if ($ast->isa('WWW::Shopify::Liquid::Operator::Concatenate')); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
1; |