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   71 use strict;
  10         21  
  10         324  
10 10     10   54 use warnings;
  10         20  
  10         335  
11 10     10   66 use Template::Alloy;
  10         21  
  10         327  
12 10     10   63 use base qw(Exporter);
  10         17  
  10         2358  
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   76 ['right', 96, ['**', 'pow'], sub { no warnings; $_[0] ** $_[1] } ],
  10         27  
  10         842  
32 10     10   80 ['prefix', 93, ['!'], sub { no warnings; ! $_[0] } ],
  10         23  
  10         822  
33 10     10   75 ['prefix', 93, ['-'], sub { no warnings; @_ == 1 ? 0 - $_[0] : $_[0] - $_[1] } ],
  10         24  
  10         737  
34 10     10   65 ['left', 90, ['*'], sub { no warnings; $_[0] * $_[1] } ],
  10         20  
  10         781  
35 10     10   70 ['left', 90, ['/'], sub { no warnings; $_[0] / $_[1] } ],
  10         41  
  10         632  
36 10     10   84 ['left', 90, ['div', 'DIV'], sub { no warnings; int($_[0] / $_[1]) } ],
  10         19  
  10         643  
37 10     10   62 ['left', 90, ['%', 'mod', 'MOD'], sub { no warnings; $_[0] % $_[1] } ],
  10         21  
  10         583  
38 10     10   65 ['left', 85, ['+'], sub { no warnings; $_[0] + $_[1] } ],
  10         16  
  10         674  
39 10     10   64 ['left', 85, ['-'], sub { no warnings; @_ == 1 ? 0 - $_[0] : $_[0] - $_[1] } ],
  10         18  
  10         782  
40             ['left', 85, ['~', '_'], undef],
41 10     10   71 ['none', 80, ['<'], sub { no warnings; $_[0] < $_[1] } ],
  10         18  
  10         645  
42 10     10   62 ['none', 80, ['>'], sub { no warnings; $_[0] > $_[1] } ],
  10         27  
  10         563  
43 10     10   60 ['none', 80, ['<='], sub { no warnings; $_[0] <= $_[1] } ],
  10         36  
  10         567  
44 10     10   78 ['none', 80, ['>='], sub { no warnings; $_[0] >= $_[1] } ],
  10         20  
  10         586  
45 10     10   70 ['none', 80, ['lt'], sub { no warnings; $_[0] lt $_[1] } ],
  10         26  
  10         596  
46 10     10   62 ['none', 80, ['gt'], sub { no warnings; $_[0] gt $_[1] } ],
  10         21  
  10         849  
47 10     10   124 ['none', 80, ['le'], sub { no warnings; $_[0] le $_[1] } ],
  10         26  
  10         609  
48 10     10   67 ['none', 80, ['ge'], sub { no warnings; $_[0] ge $_[1] } ],
  10         33  
  10         658  
49 10     10   83 ['none', 75, ['=='], sub { no warnings; $_[0] == $_[1] } ],
  10         18  
  10         683  
50 10     10   64 ['none', 75, ['eq'], sub { no warnings; $_[0] eq $_[1] } ],
  10         20  
  10         596  
51 10     10   62 ['none', 75, ['!='], sub { no warnings; $_[0] != $_[1] } ],
  10         24  
  10         655  
52 10     10   64 ['none', 75, ['ne'], sub { no warnings; $_[0] ne $_[1] } ],
  10         24  
  10         595  
53 10     10   69 ['none', 75, ['<=>'], sub { no warnings; $_[0] <=> $_[1] } ],
  10         20  
  10         674  
54 10     10   73 ['none', 75, ['cmp'], sub { no warnings; $_[0] cmp $_[1] } ],
  10         20  
  10         751  
55             ['left', 70, ['&&'], undef],
56             ['right', 65, ['||'], undef],
57             ['right', 65, ['//'], undef],
58 10     10   65 ['none', 60, ['..'], sub { no warnings; $_[0] .. $_[1] } ],
  10         33  
  10         1072  
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   64 ['prefix', 50, ['not', 'NOT'], sub { no warnings; ! $_[0] } ],
  10         17  
  10         12120  
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   53 my %used;
83 30         56 my $chrs = join '|', reverse sort map {quotemeta $_} grep {++$used{$_} < 2} grep {! /\{\}|\[\]/} grep {/^\W{2,}$/} @_;
  320         657  
  320         680  
  320         629  
  700         1447  
84 30         89 my $chr = join '', sort map {quotemeta $_} grep {++$used{$_} < 2} grep {/^\W$/} @_;
  150         322  
  150         413  
  700         1296  
85 30         71 my $word = join '|', reverse sort grep {++$used{$_} < 2} grep {/^\w+$/} @_;
  210         602  
  700         1351  
86 30 50       449 $chr = "[$chr]" if $chr;
87 30 100       97 $word = "\\b(?:$word)\\b" if $word;
88 30   50     55 return join('|', grep {length} $chrs, $chr, $word) || die "Missing operator regex";
89             }
90              
91             sub _build_ops {
92 10     10   32 $QR_OP = _op_qr(map {@{ $_->[2] }} grep {$_->[0] ne 'prefix'} @$OPERATORS);
  420         514  
  420         681  
  480         772  
93 10         60 $QR_OP_PREFIX = _op_qr(map {@{ $_->[2] }} grep {$_->[0] eq 'prefix'} @$OPERATORS);
  60         82  
  60         120  
  480         760  
94 10         29 $QR_OP_ASSIGN = _op_qr(map {@{ $_->[2] }} grep {$_->[0] eq 'assign'} @$OPERATORS);
  100         128  
  100         176  
  480         768  
95 10         32 $OP = {map {my $ref = $_; map {$_ => $ref} @{$ref->[2]}} grep {$_->[0] ne 'prefix' } @$OPERATORS}; # all non-prefix
  420         536  
  420         537  
  520         1162  
  420         586  
  480         748  
96 10         67 $OP_PREFIX = {map {my $ref = $_; map {$_ => $ref} @{$ref->[2]}} grep {$_->[0] eq 'prefix' } @$OPERATORS};
  60         91  
  60         415  
  70         164  
  60         121  
  480         783  
97 10         29 $OP_DISPATCH = {map {my $ref = $_; map {$_ => $ref->[3]} @{$ref->[2]}} grep {$_->[3] } @$OPERATORS};
  250         324  
  250         301  
  300         649  
  250         350  
  480         644  
98 10         72 $OP_ASSIGN = {map {my $ref = $_; map {$_ => 1} @{$ref->[2]}} grep {$_->[0] eq 'assign' } @$OPERATORS};
  100         136  
  100         121  
  110         220  
  100         146  
  480         748  
99 10         33 $OP_POSTFIX = {map {my $ref = $_; map {$_ => 1} @{$ref->[2]}} grep {$_->[0] eq 'postfix'} @$OPERATORS}; # bool is postfix
  20         37  
  20         28  
  20         84  
  20         61  
  480         762  
100 10         76 $OP_TERNARY = {map {my $ref = $_; map {$_ => 1} @{$ref->[2]}} grep {$_->[0] eq 'ternary'} @$OPERATORS}; # bool is ternary
  10         24  
  10         18  
  20         83  
  10         19  
  480         813  
101             }
102              
103             ###----------------------------------------------------------------###
104              
105             sub play_operator {
106 9014     9014 1 17032 my ($self, $tree) = @_;
107             ### $tree looks like [undef, '+', 4, 5]
108              
109             return $OP_DISPATCH->{$tree->[1]}->(@$tree == 3 ? $self->play_expr($tree->[2]) : ($self->play_expr($tree->[2]), $self->play_expr($tree->[3])))
110 9014 100       27515 if $OP_DISPATCH->{$tree->[1]};
    100          
111              
112 5025         8649 my $op = $tree->[1];
113              
114             ### do custom and short-circuitable operators
115 5025 100 100     31371 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         7089 my $val = $self->play_expr($tree->[3]);
117 3192         9328 $self->set_variable($tree->[2], $val);
118 3192         7862 return $val;
119              
120             } elsif ($op eq '||' || $op eq 'or' || $op eq 'OR') {
121 115   100     392 my $val = $self->play_expr($tree->[2]) || $self->play_expr($tree->[3]);
122 115 50       431 return defined($val) ? $val : '';
123              
124             } elsif ($op eq '&&' || $op eq 'and' || $op eq 'AND') {
125 15   66     53 my $val = $self->play_expr($tree->[2]) && $self->play_expr($tree->[3]);
126 15 50       52 return defined($val) ? $val : '';
127              
128             } elsif ($op eq '//' || $op eq 'err' || $op eq 'ERR') {
129 234         626 my $val = $self->play_expr($tree->[2]);
130 234 100       811 return $val if defined $val;
131 50         152 return $self->play_expr($tree->[3]);
132              
133             } elsif ($op eq '?') {
134 10     10   87 no warnings;
  10         24  
  10         754  
135 63 100       182 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   63 no warnings;
  10         27  
  10         1092  
139 222         473 my $s = '';
140 222         1138 $s .= $self->play_expr($tree->[$_]) for 2 .. $#$tree;
141 222         777 return $s;
142              
143             } elsif ($op eq '[]') {
144 321         1052 return [map {$self->play_expr($tree->[$_])} 2 .. $#$tree];
  466         1225  
145              
146             } elsif ($op eq '{}') {
147 10     10   77 no warnings;
  10         23  
  10         733  
148 635         1104 my @e;
149 635         3051 push @e, $self->play_expr($tree->[$_]) for 2 .. $#$tree;
150 635         2816 return {@e};
151              
152             } elsif ($op eq '++') {
153 10     10   66 no warnings;
  10         28  
  10         716  
154 15         51 my $val = 0 + $self->play_expr($tree->[2]);
155 15         67 $self->set_variable($tree->[2], $val + 1);
156 15 100       63 return $tree->[3] ? $val : $val + 1; # ->[3] is set to 1 during parsing of postfix ops
157              
158             } elsif ($op eq '--') {
159 10     10   61 no warnings;
  10         23  
  10         6962  
160 15         48 my $val = 0 + $self->play_expr($tree->[2]);
161 15         63 $self->set_variable($tree->[2], $val - 1);
162 15 100       65 return $tree->[3] ? $val : $val - 1; # ->[3] is set to 1 during parsing of postfix ops
163              
164             } elsif ($op eq '@()') {
165 45         117 local $self->{'CALL_CONTEXT'} = 'list';
166 45         144 return $self->play_expr($tree->[2]);
167              
168             } elsif ($op eq '$()') {
169 45         132 local $self->{'CALL_CONTEXT'} = 'item';
170 45         136 return $self->play_expr($tree->[2]);
171              
172             } elsif ($op eq '\\') {
173 54         103 my $var = $tree->[2];
174              
175 54         242 my $ref = $self->play_expr($var, {return_ref => 1});
176 54 100       191 return $ref if ! ref $ref;
177 42 100 66 27   287 return sub { sub { $$ref } } if ref $ref eq 'SCALAR' || ref $ref eq 'REF';
  27         111  
  27         76  
178              
179 15         28 my $self_copy = $self;
180 15         29 eval {require Scalar::Util; Scalar::Util::weaken($self_copy)};
  15         87  
  15         56  
181              
182 15 100       52 my $last = ['temp deref key', $var->[-1] ? [@{ $var->[-1] }] : 0];
  6         20  
183             return sub { sub { # return a double sub so that the current play_expr will return a coderef
184 12         38 local $self_copy->{'_vars'}->{'temp deref key'} = $ref;
185 12 100       43 $last->[-1] = (ref $last->[-1] ? [@{ $last->[-1] }, @_] : [@_]) if @_;
  3 100       9  
186 12         38 return $self->play_expr($last);
187 15     15   110 } };
  15         82  
188             } elsif ($op eq '->') {
189 27         121 my $code = $self->_macro_sub($tree->[2], $tree->[3]);
190 27     27   157 return sub { $code }; # do the double sub dance
  27         74  
191             } elsif ($op eq 'qr') {
192 27 100       484 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__