File Coverage

blib/lib/HTML/Template/Expr.pm
Criterion Covered Total %
statement 177 213 83.1
branch 61 90 67.7
condition 13 21 61.9
subroutine 19 20 95.0
pod 2 3 66.6
total 272 347 78.3


line stmt bran cond sub pod time code
1             package HTML::Template::Expr;
2              
3 7     7   206528 use strict;
  7         18  
  7         279  
4 7     7   39 use vars qw($VERSION);
  7         17  
  7         437  
5              
6             $VERSION = '0.07';
7              
8 7     7   11628 use HTML::Template 2.4;
  7         125855  
  7         276  
9 7     7   88 use Carp qw(croak confess carp);
  7         14  
  7         565  
10 7     7   11895 use Parse::RecDescent;
  7         352858  
  7         64  
11              
12 7     7   341 use base 'HTML::Template';
  7         16  
  7         948  
13              
14 7     7   35 use vars qw($GRAMMAR);
  7         18  
  7         442  
15             $GRAMMAR = <<'END';
16             expression : paren /^$/ { $return = $item[1] }
17              
18             paren : '(' binary_op ')' { $item[2] }
19             | '(' subexpression ')' { $item[2] }
20             | subexpression { $item[1] }
21             | '(' paren ')' { $item[2] }
22              
23             subexpression : function_call
24             | var
25             | literal
26             |
27              
28             binary_op : paren (op paren { [ $item[2], $item[1] ] })(s)
29             { $return = [ 'SUB_EXPR', $item[1], map { @$_ } @{$item[2]} ] }
30              
31             op : />=?|<=?|!=|==/ { [ 'BIN_OP', $item[1] ] }
32             | /le|ge|eq|ne|lt|gt/ { [ 'BIN_OP', $item[1] ] }
33             | /\|\||or|&&|and/ { [ 'BIN_OP', $item[1] ] }
34             | /[-+*\/%]/ { [ 'BIN_OP', $item[1] ] }
35              
36             function_call : function_name '(' args ')'
37             { [ 'FUNCTION_CALL', $item[1], $item[3] ] }
38             | function_name ...'(' paren
39             { [ 'FUNCTION_CALL', $item[1], [ $item[3] ] ] }
40             | function_name '(' ')'
41             { [ 'FUNCTION_CALL', $item[1] ] }
42              
43             function_name : /[A-Za-z_][A-Za-z0-9_]*/
44              
45             args :
46              
47             var : /[A-Za-z_][A-Za-z0-9_]*/ { [ 'VAR', $item[1] ] }
48              
49             literal : /-?\d*\.\d+/ { [ 'LITERAL', $item[1] ] }
50             | /-?\d+/ { [ 'LITERAL', $item[1] ] }
51             | { [ 'LITERAL', $item[1][2] ] }
52              
53             END
54              
55              
56             # create global parser
57 7     7   34 use vars qw($PARSER);
  7         12  
  7         423  
58             $PARSER = Parse::RecDescent->new($GRAMMAR);
59              
60             # initialize preset function table
61 7     7   35 use vars qw(%FUNC);
  7         12  
  7         18205  
62             %FUNC =
63             (
64             'sprintf' => sub { sprintf(shift, @_); },
65             'substr' => sub {
66             return substr($_[0], $_[1]) if @_ == 2;
67             return substr($_[0], $_[1], $_[2]);
68             },
69             'lc' => sub { lc($_[0]); },
70             'lcfirst' => sub { lcfirst($_[0]); },
71             'uc' => sub { uc($_[0]); },
72             'ucfirst' => sub { ucfirst($_[0]); },
73             'length' => sub { length($_[0]); },
74             'defined' => sub { defined($_[0]); },
75             'abs' => sub { abs($_[0]); },
76             'atan2' => sub { atan2($_[0], $_[1]); },
77             'cos' => sub { cos($_[0]); },
78             'exp' => sub { exp($_[0]); },
79             'hex' => sub { hex($_[0]); },
80             'int' => sub { int($_[0]); },
81             'log' => sub { log($_[0]); },
82             'oct' => sub { oct($_[0]); },
83             'rand' => sub { rand($_[0]); },
84             'sin' => sub { sin($_[0]); },
85             'sqrt' => sub { sqrt($_[0]); },
86             'srand' => sub { srand($_[0]); },
87             );
88              
89             sub new {
90 11     11 1 16292 my $pkg = shift;
91 11         21 my $self;
92              
93             # check hashworthyness
94 11 50       57 croak("HTML::Template::Expr->new() called with odd number of option parameters - should be of the form option => value")
95             if (@_ % 2);
96 11         71 my %options = @_;
97              
98             # check for unsupported options file_cache and shared_cache
99 11 50 33     100 croak("HTML::Template::Expr->new() : sorry, this module won't work with file_cache or shared_cache modes. This will hopefully be fixed in an upcoming version.")
100             if ($options{file_cache} or $options{shared_cache});
101              
102             # push on our filter, one way or another. Why did I allow so many
103             # different ways to say the same thing? Was I smoking crack?
104 11         30 my @expr;
105 11 50       43 if (exists $options{filter}) {
106             # CODE => ARRAY
107 0 0       0 $options{filter} = [ { 'sub' => $options{filter},
108             'format' => 'scalar' } ]
109             if ref($options{filter}) eq 'CODE';
110              
111             # HASH => ARRAY
112 0 0       0 $options{filter} = [ $options{filter} ]
113             if ref($options{filter}) eq 'HASH';
114              
115             # push onto ARRAY
116 0 0       0 if (ref($options{filter}) eq 'ARRAY') {
117 0     0   0 push(@{$options{filter}}, { 'sub' => sub { _expr_filter(\@expr, @_); },
  0         0  
118 0         0 'format' => 'scalar' });
119             } else {
120             # unrecognized
121 0         0 croak("HTML::Template::Expr->new() : bad format for filter argument. Please check the HTML::Template docs for the allowed forms.");
122             }
123             } else {
124             # new filter
125 9     9   3868 $options{filter} = [ { 'sub' => sub { _expr_filter(\@expr, @_) },
126 11         111 'format' => 'scalar'
127             } ];
128             }
129              
130             # force global_vars on
131 11         29 $options{global_vars} = 1;
132              
133             # create an HTML::Template object, catch the results to keep error
134             # message line-numbers helpful.
135 11         18 eval {
136 11   100     175 $self = $pkg->SUPER::new(%options,
137             expr => \@expr,
138             expr_func => $options{functions} || {});
139             };
140 11 50       11015 croak("HTML::Template::Expr->new() : Error creating HTML::Template object : $@") if $@;
141              
142 11         160 return $self;
143             }
144              
145             sub _expr_filter {
146 9     9   19 my $expr = shift;
147 9         19 my $text = shift;
148              
149             # find expressions and create parse trees
150 9         19 my ($ref, $tree, $before_expr, $expr_text, $after_expr, $vars, $which, $out);
151 9         178 $$text =~ s/
152             <(?:!--\s*)?
153             [Tt][Mm][Pp][Ll]_
154             ([Ii][Ff]|[Uu][Nn][Ll][Ee][Ss][Ss]|[Vv][Aa][Rr]) # $1 => which tag
155             (\s+[^<]+)? # $2 => before expr
156             \s+[Ee][Xx][Pp][Rr]=
157             "([^"]*)" # $3 => the actual expr
158             (\s+[^>-]+)? # $4 => after expr
159             \s*(?:--)?>
160             /
161 49         143 $which = $1;
162 49   100     267 $before_expr = $2 || '';
163 49         123 $expr_text = $3;
164 49   100     253 $after_expr = $4 || '';
165              
166             # add enclosing parens to keep grammar simple
167 49         124 $expr_text = "($expr_text)";
168              
169             # parse the expression
170 49         79 eval {
171 49         466 $tree = $PARSER->expression($expr_text);
172             };
173 49 50 33     517729 croak("HTML::Template::Expr : Unable to parse expression: $expr_text")
174             if $@ or not $tree;
175              
176             # stub out variables needed by the expression
177 49         116 $out = "";
178 49         189 foreach my $var (_expr_vars($tree)) {
179 52 50       116 next unless defined $var;
180 52         192 $out .= "";
181             }
182              
183             # save parse tree for later
184 49         125 push(@$expr, $tree);
185            
186             # add the expression placeholder and replace
187 49         175 $out . "<\/tmpl_if>";
  49         959  
188             /xeg;
189             # stupid emacs - /
190              
191 9         71 return;
192             }
193              
194             # find all variables in a parse tree
195             sub _expr_vars {
196 49     49   203 my $tree = shift;
197 49         77 my %vars;
198              
199             # hunt for VAR nodes in the tree
200 49         153 my @stack = @$tree;
201 49         149 while (@stack) {
202 509         608 my $node = shift @stack;
203 509 100 66     1752 if (ref $node and ref $node eq 'ARRAY') {
204 219 100       481 if ($node->[0] eq 'VAR') {
205 65         225 $vars{$node->[1]} = 1;
206             } else {
207 154         431 push @stack, @$node;
208             }
209             }
210             }
211 49         240 return keys %vars;
212             }
213              
214             # allow loops to stay as HTML::Template objects, we don't need to
215             # override output for them
216             sub _new_from_loop {
217 3     3   3217 my ($pkg, @args) = @_;
218 3         16 return HTML::Template->_new_from_loop(@args);
219             }
220              
221             sub output {
222 12     12 1 3457 my $self = shift;
223 12         40 my $parse_stack = $self->{parse_stack};
224 12         26 my $options = $self->{options};
225 12         21 my ($expr, $expr_func);
226              
227             # pull expr and expr_func out of the parse_stack for cache mode.
228 12 100       45 if ($options->{cache}) {
229 3         6 $expr = pop @$parse_stack;
230 3         5 $expr_func = pop @$parse_stack;
231             } else {
232 9         19 $expr = $options->{expr};
233 9         19 $expr_func = $options->{expr_func};
234             }
235              
236             # setup expression evaluators
237 12         25 my %param;
238 12         55 for (my $x = 0; $x < @$expr; $x++) {
239 60         85 my $node = $expr->[$x];
240 60     76   330 $param{"__expr_" . $x . "__"} = sub { _expr_evaluate($node, @_) };
  76         9883  
241             }
242 12         49 $self->param(\%param);
243              
244             # setup %FUNC
245 12         1297 local %FUNC = (%FUNC, %$expr_func);
246              
247 12         91 my $result = $self->SUPER::output(@_);
248              
249             # restore cached values to their hideout in the parse_stack
250 12 100       1092 if ($options->{cache}) {
251 3         5 push @$parse_stack, $expr_func;
252 3         7 push @$parse_stack, $expr;
253             }
254            
255 12         100 return $result;
256             }
257              
258             sub _expr_evaluate {
259 136     136   188 my ($tree, $template) = @_;
260 136         152 my ($op, $lhs, $rhs, $node, $type, @stack);
261              
262 136         241 my @nodes = $tree;
263 136         268 while (@nodes) {
264 335         434 my $node = shift @nodes;
265 335         496 my $type = $node->[0];
266              
267 335 100       592 if ($type eq 'LITERAL') {
268 62         103 push @stack, $node->[1];
269 62         148 next;
270             }
271              
272 273 100       474 if ($type eq 'VAR') {
273 92         274 push @stack, $template->param($node->[1]);
274 92         1501 next;
275             }
276              
277 181 100       302 if ($type eq 'SUB_EXPR') {
278 63         77 unshift @nodes, @{$node}[1..$#{$node}];
  63         154  
  63         112  
279 63         158 next;
280             }
281              
282 118 100       234 if ($type eq 'BIN_OP') {
283 68         90 $op = $node->[1];
284 68         79 $rhs = pop(@stack);
285 68         86 $lhs = pop(@stack);
286              
287             # do the op
288 68 50       146 if ($op eq '==') {push @stack, $lhs == $rhs; next; }
  0         0  
  0         0  
289 68 100       120 if ($op eq 'eq') {push @stack, $lhs eq $rhs; next; }
  8         14  
  8         20  
290 60 100       106 if ($op eq '>') {push @stack, $lhs > $rhs; next; }
  19         43  
  19         48  
291 41 100       70 if ($op eq '<') {push @stack, $lhs < $rhs; next; }
  5         8  
  5         13  
292              
293 36 50       78 if ($op eq '!=') {push @stack, $lhs != $rhs; next; }
  0         0  
  0         0  
294 36 50       89 if ($op eq 'ne') {push @stack, $lhs ne $rhs; next; }
  0         0  
  0         0  
295 36 50       62 if ($op eq '>=') {push @stack, $lhs >= $rhs; next; }
  0         0  
  0         0  
296 36 50       59 if ($op eq '<=') {push @stack, $lhs <= $rhs; next; }
  0         0  
  0         0  
297            
298 36 100       66 if ($op eq '+') {push @stack, $lhs + $rhs; next; }
  19         41  
  19         44  
299 17 100       34 if ($op eq '-') {push @stack, $lhs - $rhs; next; }
  2         5  
  2         5  
300 15 50       47 if ($op eq '/') {push @stack, $lhs / $rhs; next; }
  0         0  
  0         0  
301 15 100       28 if ($op eq '*') {push @stack, $lhs * $rhs; next; }
  5         17  
  5         16  
302 10 50       15 if ($op eq '%') {push @stack, $lhs % $rhs; next; }
  0         0  
  0         0  
303              
304 10 50       18 if ($op eq 'le') {push @stack, $lhs le $rhs; next; }
  0         0  
  0         0  
305 10 50       16 if ($op eq 'ge') {push @stack, $lhs ge $rhs; next; }
  0         0  
  0         0  
306 10 50       28 if ($op eq 'lt') {push @stack, $lhs lt $rhs; next; }
  0         0  
  0         0  
307 10 50       18 if ($op eq 'gt') {push @stack, $lhs gt $rhs; next; }
  0         0  
  0         0  
308              
309             # short circuit or
310 10 100 66     39 if ($op eq 'or' or $op eq '||') {
311 8 100       15 if ($lhs) {
312 4         5 push @stack, 1;
313 4         10 next;
314             }
315 4 50       7 if ($rhs) {
316 4         4 push @stack, 1;
317 4         10 next;
318             }
319 0         0 push @stack, 0;
320 0         0 next;
321             }
322              
323             # short circuit and
324 2 50 33     7 if ($op eq '&&' or $op eq 'and') {
325 2 100       5 unless ($lhs) {
326 1         2 push @stack, 0;
327 1         4 next;
328             }
329 1 50       5 unless ($rhs) {
330 1         1 push @stack, 0;
331 1         3 next;
332             }
333 0         0 push @stack, 1;
334 0         0 next;
335             }
336            
337 0         0 confess("HTML::Template::Expr : unknown op: $op");
338             }
339              
340 50 50       105 if ($type eq 'FUNCTION_CALL') {
341 50         74 my $name = $node->[1];
342 50         72 my $args = $node->[2];
343 50 50       128 croak("HTML::Template::Expr : found unknown subroutine call ".
344             ": $name.\n")
345             unless exists($FUNC{$name});
346 50 100       87 if (defined $args) {
347 60         163 push @stack,
348             scalar
349 49         90 $FUNC{$name}->(map { _expr_evaluate($_, $template) } @$args);
350             } else {
351 1         4 push @stack, scalar $FUNC{$name}->();
352             }
353 50         202 next;
354             }
355              
356 0         0 confess("HTML::Template::Expr : unrecognized node in tree: $node");
357             }
358              
359 136 50       291 unless (@stack == 1) {
360 0         0 confess("HTML::Template::Expr : stack overflow! ".
361             "Please report this bug to the maintainer.");
362             }
363              
364 136         764 return $stack[0];
365             }
366              
367             sub register_function {
368 4     4 0 1602 my($class, $name, $sub) = @_;
369              
370 4 100       231 croak("HTML::Template::Expr : args 3 of register_function must be subroutine reference\n")
371             unless ref($sub) eq 'CODE';
372              
373 3         14 $FUNC{$name} = $sub;
374             }
375              
376              
377             # Make caching work right by hiding our vars in the parse_stack
378             # between cache store and load. This is such a hack.
379             sub _commit_to_cache {
380 1     1   1523 my $self = shift;
381 1         4 my $parse_stack = $self->{parse_stack};
382              
383 1         4 push @$parse_stack, $self->{options}{expr_func};
384 1         4 push @$parse_stack, $self->{options}{expr};
385              
386 1         6 my $result = HTML::Template::_commit_to_cache($self, @_);
387             }
388              
389             1;
390             __END__