File Coverage

blib/lib/HTML/Template/Expr.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


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