package HTML::Template::Compiled::Expr;
use strict;
use warnings;
use Carp qw(croak carp);
#use HTML::Template::Compiled::Expression qw(:expressions);
use HTML::Template::Compiled;
use Parse::RecDescent;
our $VERSION = '1.003'; # VERSION
my $re = qr# (?:
\b(?:eq | ne | ge | le | gt | lt )\b
|
(?: == | != | <= | >= | > | <)
|
(?: [0-9]+ )
) #x;
my $GRAMMAR = <<'END';
expression : paren /^$/ { $return = $item[1] }
paren : '(' binary_op ')' { $item[2] }
| '(' subexpression ')' { $item[2] }
| subexpression { $item[1] }
| '(' paren ')' { $item[2] }
subexpression : function_call
| method_call
| var_deref
| var
| literal
|
binary_op : paren (op paren { [ $item[2], $item[1] ] })(s)
{ $return = [ 'SUB_EXPR', $item[1], map { @$_ } @{$item[2]} ] }
op : />=?|<=?|!=|==/ { [ 'BIN_OP', $item[1] ] }
| /le|ge|eq|ne|lt|gt/ { [ 'BIN_OP', $item[1] ] }
| /\|\||or|&&|and/ { [ 'BIN_OP', $item[1] ] }
| /[-+*\/%.]/ { [ 'BIN_OP', $item[1] ] }
method_call : var '(' args ')' { [ 'METHOD_CALL', $item[1], $item[3] ] }
function_call : function_name '(' args ')'
{ [ 'FUNCTION_CALL', $item[1], $item[3] ] }
| function_name ...'(' paren
{ [ 'FUNCTION_CALL', $item[1], [ $item[3] ] ] }
| function_name '(' ')'
{ [ 'FUNCTION_CALL', $item[1] ] }
function_name : /[A-Za-z_][A-Za-z0-9_]*/
args :
var : /[.\/A-Za-z_][.\/A-Za-z0-9_]*/ { [ 'VAR', $item[1] ] }
| /\$[.\/A-Za-z_][.\/A-Za-z0-9_]*/ { [ 'VAR', $item[1] ] }
var_deref : var deref(s) { [ 'VAR_DEREF', $item[1], $item[2] ] }
| var deref(s) { [ 'VAR_DEREF', $item[1], $item[2] ] }
deref : deref_hash | deref_array
deref_hash : '{' hash_key '}' { [ 'DEREF_HASH', $item[2] ] }
deref_array : '[' array_index ']' { [ 'DEREF_ARRAY', $item[2] ] }
hash_key : literal | paren | var
array_index : /-?\d+/ | paren | var
literal : /-?\d*\.\d+/ { [ 'LITERAL', $item[1] ] }
| /-?\d+/ { [ 'LITERAL', $item[1] ] }
| { [ 'LITERAL_STRING', $item[1][1], $item[1][2] ] }
END
my %FUNC = (
'sprintf' => sub { sprintf( shift, @_ ); },
'substr' => sub {
return substr( $_[0], $_[1] ) if @_ == 2;
return substr( $_[0], $_[1], $_[2] );
},
'lc' => sub { lc( $_[0] ); },
'lcfirst' => sub { lcfirst( $_[0] ); },
'uc' => sub { uc( $_[0] ); },
'ucfirst' => sub { ucfirst( $_[0] ); },
'length' => sub { length( $_[0] ); },
'defined' => sub { defined( $_[0] ); },
'abs' => sub { abs( $_[0] ); },
'atan2' => sub { atan2( $_[0], $_[1] ); },
'cos' => sub { cos( $_[0] ); },
'exp' => sub { exp( $_[0] ); },
'hex' => sub { hex( $_[0] ); },
'int' => sub { int( $_[0] ); },
'log' => sub { log( $_[0] ); },
'oct' => sub { oct( $_[0] ); },
'rand' => sub { rand( $_[0] ); },
'sin' => sub { sin( $_[0] ); },
'sqrt' => sub { sqrt( $_[0] ); },
'srand' => sub { srand( $_[0] ); },
);
# under construction
my $DEFAULT_PARSER;
sub parse_expr {
my ($class, $compiler, $htc, %args) = @_;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\%args], ['args']);
my $string = $args{expr};
my $PARSER = $DEFAULT_PARSER ||= Parse::RecDescent->new($GRAMMAR);
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$string], ['string']);
my $tree = $PARSER->expression("( $string )");
# warn Data::Dumper->Dump([\$tree], ['tree']);
my $expr = $class->sub_expression($tree, $compiler, $htc, %args);
# warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$expr], ['expr']);
return $expr;
}
sub bin_op {
my ($class, $op, $args, $compiler, $htc, %args) = @_;
unless (@$args) {
return '';
}
my $right = pop @$args;
my $right_expr = $class->sub_expression($right, $compiler, $htc, %args);
my $left_expr = '';
if (@$args > 1) {
my $new_op = pop @$args;
my $sub = $class->bin_op($new_op->[1], $args, $compiler, $htc, %args);
$left_expr = $sub;
}
else {
$left_expr = $class->sub_expression($args->[0], $compiler, $htc, %args);
}
my $expr = ' ( ' . $left_expr
. ' ' . $op . ' '
. $right_expr
. ' ) ';
# warn __PACKAGE__.':'.__LINE__.": !!! $expr\n";
return $expr;
}
sub sub_expression {
my ($class, $tree, $compiler, $htc, %args) = @_;
my ($type, @args) = @$tree;
#warn __PACKAGE__.':'.__LINE__.": $type\n";
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$tree], ['tree']);
if ($type eq 'SUB_EXPR') {
my $op = pop @args;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$op], ['op']);
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@args], ['args']);
my $expr = '';
if ($op->[0] eq 'BIN_OP') {
$expr = $class->bin_op($op->[1], [@args], $compiler, $htc, %args);
}
#warn __PACKAGE__.':'.__LINE__.": $expr\n";
return $expr;
}
elsif ($type eq 'VAR') {
my $expr = $compiler->parse_var($htc,
%args,
var => $args[0],
);
#warn __PACKAGE__.':'.__LINE__.": VAR $expr\n";
return $expr;
}
elsif ($type eq 'LITERAL') {
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@args], ['args']);
my $expr = $args[0];
return $expr;
}
elsif ($type eq 'LITERAL_STRING') {
my $expr = $args[0] . $args[1] . $args[0];
return $expr;
}
elsif ($type eq 'METHOD_CALL') {
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@args], ['args']);
my ($var, $params) = @args[0,1];
my $method_args = '';
for my $i (0 .. $#$params) {
$method_args .= $class->sub_expression($params->[$i], $compiler, $htc, %args) . ' , ';
}
my $expr = $compiler->parse_var($htc,
%args,
var => $var->[1],
method_args => $method_args,
);
}
elsif ($type eq 'VAR_DEREF') {
my ($var, $deref) = @args;
my $str = $class->sub_expression($var, $compiler, $htc, %args);
for my $d (@$deref) {
my $deref_str = $class->sub_expression($d, $compiler, $htc, %args);
$str .= $deref_str;
}
return $str;
}
elsif ($type eq 'DEREF_HASH') {
my ($key) = @args;
my $str = $class->sub_expression($args[0], $compiler, $htc, %args);
$str = '->{' . $str . '}';
return $str;
}
elsif ($type eq 'DEREF_ARRAY') {
my ($index) = @args;
my $str;
if (ref $index) {
$str = $class->sub_expression($index, $compiler, $htc, %args);
}
elsif ($index !~ m/-?[0-9]+/) {
die "invalid array index $index";
}
else {
$str = $index;
}
$str = '->[' . $str . ']';
return $str;
}
elsif ($type eq 'FUNCTION_CALL') {
my $name = shift @args;
@args = @{ $args[0] || [] };
my $expr = "$name( ";
for my $i (0 .. $#args) {
$expr .= $class->sub_expression($args[$i], $compiler, $htc, %args) . ' , ';
}
$expr .= ")";
return $expr;
}
}
1;
__END__
=pod
=head1 NAME
HTML::Template::Compiled::Expr - Expressions for HTC
=head1 DESCRIPTION
The expressions work like in L, with some additional
possibilities regarding object method calls and arbitrary data structures.
Different from L, you don't use it as the module
class, but you activate it by passing the option C with
a true value.
See C in L
=cut