File Coverage

blib/lib/WWW/Shopify/Liquid/Parser.pm
Criterion Covered Total %
statement 287 295 97.2
branch 131 158 82.9
condition 48 66 72.7
subroutine 40 41 97.5
pod 0 25 0.0
total 506 585 86.5


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;