| 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; |