File Coverage

blib/lib/Template/Alloy/Operator.pm
Criterion Covered Total %
statement 221 228 96.9
branch 58 62 93.5
condition 26 32 81.2
subroutine 40 42 95.2
pod 2 3 66.6
total 347 367 94.5


line stmt bran cond sub pod time code
1             package Template::Alloy::Operator;
2              
3             =head1 NAME
4              
5             Template::Alloy::Operator - Operator role.
6              
7             =cut
8              
9 10     10   63 use strict;
  10         26  
  10         380  
10 10     10   57 use warnings;
  10         17  
  10         296  
11 10     10   64 use Template::Alloy;
  10         26  
  10         281  
12 10     10   98 use base qw(Exporter);
  10         32  
  10         2460  
13             our @EXPORT_OK = qw(play_operator define_operator
14             $QR_OP $QR_OP_ASSIGN $QR_OP_PREFIX $QR_PRIVATE
15             $OP $OP_ASSIGN $OP_PREFIX $OP_POSTFIX $OP_DISPATCH);
16              
17             our $VERSION = $Template::Alloy::VERSION;
18              
19 0     0 0 0 sub new { die "This class is a role for use by packages such as Template::Alloy" }
20              
21             ###----------------------------------------------------------------###
22              
23             ### setup the operator parsing
24             our $OPERATORS = [
25             # type precedence symbols action (undef means play_operator will handle)
26             ['prefix', 99, ['\\'], undef],
27             ['postfix', 98, ['++'], undef],
28             ['postfix', 98, ['--'], undef],
29             ['prefix', 97, ['++'], undef],
30             ['prefix', 97, ['--'], undef],
31 10     10   60 ['right', 96, ['**', 'pow'], sub { no warnings; $_[0] ** $_[1] } ],
  10         36  
  10         1656  
32 10     10   384 ['prefix', 93, ['!'], sub { no warnings; ! $_[0] } ],
  10         26  
  10         721  
33 10     10   59 ['prefix', 93, ['-'], sub { no warnings; @_ == 1 ? 0 - $_[0] : $_[0] - $_[1] } ],
  10         26  
  10         736  
34 10     10   53 ['left', 90, ['*'], sub { no warnings; $_[0] * $_[1] } ],
  10         21  
  10         722  
35 10     10   54 ['left', 90, ['/'], sub { no warnings; $_[0] / $_[1] } ],
  10         20  
  10         623  
36 10     10   54 ['left', 90, ['div', 'DIV'], sub { no warnings; int($_[0] / $_[1]) } ],
  10         20  
  10         1239  
37 10     10   55 ['left', 90, ['%', 'mod', 'MOD'], sub { no warnings; $_[0] % $_[1] } ],
  10         20  
  10         571  
38 10     10   52 ['left', 85, ['+'], sub { no warnings; $_[0] + $_[1] } ],
  10         722  
  10         2841  
39 10     10   59 ['left', 85, ['-'], sub { no warnings; @_ == 1 ? 0 - $_[0] : $_[0] - $_[1] } ],
  10         691  
  10         721  
40             ['left', 85, ['~', '_'], undef],
41 10     10   49 ['none', 80, ['<'], sub { no warnings; $_[0] < $_[1] } ],
  10         29  
  10         529  
42 10     10   50 ['none', 80, ['>'], sub { no warnings; $_[0] > $_[1] } ],
  10         16  
  10         593  
43 10     10   49 ['none', 80, ['<='], sub { no warnings; $_[0] <= $_[1] } ],
  10         21  
  10         518  
44 10     10   55 ['none', 80, ['>='], sub { no warnings; $_[0] >= $_[1] } ],
  10         15  
  10         516  
45 10     10   59 ['none', 80, ['lt'], sub { no warnings; $_[0] lt $_[1] } ],
  10         15  
  10         852  
46 10     10   50 ['none', 80, ['gt'], sub { no warnings; $_[0] gt $_[1] } ],
  10         22  
  10         591  
47 10     10   53 ['none', 80, ['le'], sub { no warnings; $_[0] le $_[1] } ],
  10         595  
  10         541  
48 10     10   408 ['none', 80, ['ge'], sub { no warnings; $_[0] ge $_[1] } ],
  10         23  
  10         540  
49 10     10   58 ['none', 75, ['=='], sub { no warnings; $_[0] == $_[1] } ],
  10         23  
  10         1030  
50 10     10   51 ['none', 75, ['eq'], sub { no warnings; $_[0] eq $_[1] } ],
  10         24  
  10         571  
51 10     10   46 ['none', 75, ['!='], sub { no warnings; $_[0] != $_[1] } ],
  10         18  
  10         542  
52 10     10   46 ['none', 75, ['ne'], sub { no warnings; $_[0] ne $_[1] } ],
  10         13  
  10         535  
53 10     10   59 ['none', 75, ['<=>'], sub { no warnings; $_[0] <=> $_[1] } ],
  10         14  
  10         537  
54 10     10   659 ['none', 75, ['cmp'], sub { no warnings; $_[0] cmp $_[1] } ],
  10         16  
  10         741  
55             ['left', 70, ['&&'], undef],
56             ['right', 65, ['||'], undef],
57             ['right', 65, ['//'], undef],
58 10     10   51 ['none', 60, ['..'], sub { no warnings; $_[0] .. $_[1] } ],
  10         14  
  10         1231  
59             ['ternary', 55, ['?', ':'], undef],
60             ['assign', 53, ['+='], undef],
61             ['assign', 53, ['-='], undef],
62             ['assign', 53, ['*='], undef],
63             ['assign', 53, ['/='], undef],
64             ['assign', 53, ['%='], undef],
65             ['assign', 53, ['**='], undef],
66             ['assign', 53, ['~=', '_='], undef],
67             ['assign', 53, ['//='], undef],
68             ['assign', 53, ['||='], undef],
69             ['assign', 52, ['='], undef],
70 10     10   54 ['prefix', 50, ['not', 'NOT'], sub { no warnings; ! $_[0] } ],
  10         16  
  10         17583  
71             ['left', 45, ['and', 'AND'], undef],
72             ['right', 40, ['or', 'OR' ], undef],
73             ['right', 40, ['err', 'ERR'], undef],
74             ];
75              
76             our ($QR_OP, $QR_OP_PREFIX, $QR_OP_ASSIGN, $OP, $OP_PREFIX, $OP_DISPATCH, $OP_ASSIGN, $OP_POSTFIX, $OP_TERNARY);
77             _build_ops();
78              
79             ###----------------------------------------------------------------###
80              
81             sub _op_qr { # no mixed \w\W operators
82 30     30   51 my %used;
83 30         56 my $chrs = join '|', reverse sort map {quotemeta $_} grep {++$used{$_} < 2} grep {! /\{\}|\[\]/} grep {/^\W{2,}$/} @_;
  320         694  
  320         773  
  320         758  
  700         1551  
84 30         90 my $chr = join '', sort map {quotemeta $_} grep {++$used{$_} < 2} grep {/^\W$/} @_;
  150         309  
  150         536  
  700         1360  
85 30         78 my $word = join '|', reverse sort grep {++$used{$_} < 2} grep {/^\w+$/} @_;
  210         614  
  700         1535  
86 30 50       153 $chr = "[$chr]" if $chr;
87 30 100       104 $word = "\\b(?:$word)\\b" if $word;
88 30   50     49 return join('|', grep {length} $chrs, $chr, $word) || die "Missing operator regex";
89             }
90              
91             sub _build_ops {
92 10     10   38 $QR_OP = _op_qr(map {@{ $_->[2] }} grep {$_->[0] ne 'prefix'} @$OPERATORS);
  420         401  
  420         755  
  480         859  
93 10         59 $QR_OP_PREFIX = _op_qr(map {@{ $_->[2] }} grep {$_->[0] eq 'prefix'} @$OPERATORS);
  60         64  
  60         138  
  480         738  
94 10         33 $QR_OP_ASSIGN = _op_qr(map {@{ $_->[2] }} grep {$_->[0] eq 'assign'} @$OPERATORS);
  100         100  
  100         198  
  480         772  
95 10         34 $OP = {map {my $ref = $_; map {$_ => $ref} @{$ref->[2]}} grep {$_->[0] ne 'prefix' } @$OPERATORS}; # all non-prefix
  420         446  
  420         421  
  520         1549  
  420         588  
  480         774  
96 10         73 $OP_PREFIX = {map {my $ref = $_; map {$_ => $ref} @{$ref->[2]}} grep {$_->[0] eq 'prefix' } @$OPERATORS};
  60         81  
  60         66  
  70         229  
  60         106  
  480         823  
97 10         38 $OP_DISPATCH = {map {my $ref = $_; map {$_ => $ref->[3]} @{$ref->[2]}} grep {$_->[3] } @$OPERATORS};
  250         287  
  250         267  
  300         834  
  250         356  
  480         609  
98 10         50 $OP_ASSIGN = {map {my $ref = $_; map {$_ => 1} @{$ref->[2]}} grep {$_->[0] eq 'assign' } @$OPERATORS};
  100         853  
  100         100  
  110         359  
  100         138  
  480         892  
99 10         42 $OP_POSTFIX = {map {my $ref = $_; map {$_ => 1} @{$ref->[2]}} grep {$_->[0] eq 'postfix'} @$OPERATORS}; # bool is postfix
  20         37  
  20         425  
  20         74  
  20         53  
  480         758  
100 10         30 $OP_TERNARY = {map {my $ref = $_; map {$_ => 1} @{$ref->[2]}} grep {$_->[0] eq 'ternary'} @$OPERATORS}; # bool is ternary
  10         23  
  10         18  
  20         67  
  10         22  
  480         810  
101             }
102              
103             ###----------------------------------------------------------------###
104              
105             sub play_operator {
106 9014     9014 1 12833 my ($self, $tree) = @_;
107             ### $tree looks like [undef, '+', 4, 5]
108              
109 9014 100       41415 return $OP_DISPATCH->{$tree->[1]}->(@$tree == 3 ? $self->play_expr($tree->[2]) : ($self->play_expr($tree->[2]), $self->play_expr($tree->[3])))
    100          
110             if $OP_DISPATCH->{$tree->[1]};
111              
112 5025         9327 my $op = $tree->[1];
113              
114             ### do custom and short-circuitable operators
115 5025 100 100     57559 if ($op eq '=') {
    100 66        
    100 66        
    100 66        
    100 100        
    100 100        
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
116 3192         9555 my $val = $self->play_expr($tree->[3]);
117 3192         11131 $self->set_variable($tree->[2], $val);
118 3192         11891 return $val;
119              
120             } elsif ($op eq '||' || $op eq 'or' || $op eq 'OR') {
121 115   100     576 my $val = $self->play_expr($tree->[2]) || $self->play_expr($tree->[3]);
122 115 50       516 return defined($val) ? $val : '';
123              
124             } elsif ($op eq '&&' || $op eq 'and' || $op eq 'AND') {
125 15   66     60 my $val = $self->play_expr($tree->[2]) && $self->play_expr($tree->[3]);
126 15 50       69 return defined($val) ? $val : '';
127              
128             } elsif ($op eq '//' || $op eq 'err' || $op eq 'ERR') {
129 234         910 my $val = $self->play_expr($tree->[2]);
130 234 100       982 return $val if defined $val;
131 50         191 return $self->play_expr($tree->[3]);
132              
133             } elsif ($op eq '?') {
134 10     10   88 no warnings;
  10         18  
  10         732  
135 63 100       233 return $self->play_expr($tree->[2]) ? $self->play_expr($tree->[3]) : $self->play_expr($tree->[4]);
136              
137             } elsif ($op eq '~' || $op eq '_') {
138 10     10   49 no warnings;
  10         15  
  10         1003  
139 222         383 my $s = '';
140 222         1273 $s .= $self->play_expr($tree->[$_]) for 2 .. $#$tree;
141 222         901 return $s;
142              
143             } elsif ($op eq '[]') {
144 321         1348 return [map {$self->play_expr($tree->[$_])} 2 .. $#$tree];
  466         1543  
145              
146             } elsif ($op eq '{}') {
147 10     10   52 no warnings;
  10         19  
  10         779  
148 635         1082 my @e;
149 635         3655 push @e, $self->play_expr($tree->[$_]) for 2 .. $#$tree;
150 635         3723 return {@e};
151              
152             } elsif ($op eq '++') {
153 10     10   53 no warnings;
  10         21  
  10         896  
154 15         61 my $val = 0 + $self->play_expr($tree->[2]);
155 15         98 $self->set_variable($tree->[2], $val + 1);
156 15 100       72 return $tree->[3] ? $val : $val + 1; # ->[3] is set to 1 during parsing of postfix ops
157              
158             } elsif ($op eq '--') {
159 10     10   59 no warnings;
  10         16  
  10         30575  
160 15         58 my $val = 0 + $self->play_expr($tree->[2]);
161 15         62 $self->set_variable($tree->[2], $val - 1);
162 15 100       66 return $tree->[3] ? $val : $val - 1; # ->[3] is set to 1 during parsing of postfix ops
163              
164             } elsif ($op eq '@()') {
165 45         184 local $self->{'CALL_CONTEXT'} = 'list';
166 45         204 return $self->play_expr($tree->[2]);
167              
168             } elsif ($op eq '$()') {
169 45         143 local $self->{'CALL_CONTEXT'} = 'item';
170 45         214 return $self->play_expr($tree->[2]);
171              
172             } elsif ($op eq '\\') {
173 54         107 my $var = $tree->[2];
174              
175 54         270 my $ref = $self->play_expr($var, {return_ref => 1});
176 54 100       286 return $ref if ! ref $ref;
177 42 100 66 27   430 return sub { sub { $$ref } } if ref $ref eq 'SCALAR' || ref $ref eq 'REF';
  27         149  
  27         86  
178              
179 15         29 my $self_copy = $self;
180 15         23 eval {require Scalar::Util; Scalar::Util::weaken($self_copy)};
  15         96  
  15         60  
181              
182 15 100       66 my $last = ['temp deref key', $var->[-1] ? [@{ $var->[-1] }] : 0];
  6         23  
183             return sub { sub { # return a double sub so that the current play_expr will return a coderef
184 12         44 local $self_copy->{'_vars'}->{'temp deref key'} = $ref;
185 12 100       46 $last->[-1] = (ref $last->[-1] ? [@{ $last->[-1] }, @_] : [@_]) if @_;
  3 100       14  
186 12         44 return $self->play_expr($last);
187 15     15   112 } };
  15         100  
188             } elsif ($op eq '->') {
189 27         164 my $code = $self->_macro_sub($tree->[2], $tree->[3]);
190 27     27   202 return sub { $code }; # do the double sub dance
  27         88  
191             } elsif ($op eq 'qr') {
192 27 100       491 return $tree->[3] ? qr{(?$tree->[3]:$tree->[2])} : qr{$tree->[2]};
193             }
194              
195 0           $self->throw('operator', "Un-implemented operation $op");
196             }
197              
198             ###----------------------------------------------------------------###
199              
200             sub define_operator {
201 0     0 1   my ($self, $args) = @_;
202 0           push @$OPERATORS, [@{ $args }{qw(type precedence symbols play_sub)}];
  0            
203 0           _build_ops();
204 0           return 1;
205             }
206              
207             ###----------------------------------------------------------------###
208              
209             1;
210              
211             __END__