1 package Squirrel::Template::Processor;
3 use Squirrel::Template::Constants qw(:node);
6 our $VERSION = "1.025";
8 use constant ACTS => 0;
9 use constant TMPLT => 1;
10 use constant PARMS => 2;
11 use constant WRAPPED => 3;
12 use constant EVAL => 4;
15 my ($class, $acts, $tmplt, $wrapped) = @_;
23 Squirrel::Template::Expr::Eval->new($tmplt, $acts)
27 # return an error node matching the supplied node
29 my ($self, $node, $message) = @_;
31 my $error = [ error => "", $node->[NODE_LINE], $node->[NODE_FILENAME], $message ];
33 return $self->_process_error($error);
37 my ($self, $node) = @_;
39 my $method = "_process_$node->[NODE_TYPE]";
40 return $self->$method($node);
43 sub _process_content {
44 my ($self, $node) = @_;
46 return $node->[NODE_ORIG];
50 my ($self, $node) = @_;
56 my ($self, $node) = @_;
61 for my $expr (@{$node->[NODE_EXPR_EXPR]}) {
62 $self->[EVAL]->process($expr);
68 if ($msg =~ /\bENOIMPL\b/) {
69 $self->[TMPLT]->trace_noimpl("stmt:$node->[NODE_FILENAME]:$node->[NODE_LINE]:$msg\n");
70 return $node->[NODE_ORIG];
73 push @errors, $self->_error($node, ref $msg ? $msg->[1] : $msg );
80 my ($self, $node) = @_;
84 unless (eval { $value = $self->[EVAL]->process($node->[NODE_EXPR_EXPR]); 1 }) {
87 if ($msg =~ /\bENOIMPL\b/) {
88 $self->[TMPLT]->trace_noimpl("expr:$node->[NODE_FILENAME]:$node->[NODE_LINE]:$msg\n");
89 return $node->[NODE_ORIG];
92 push @errors, $self->_error($node, ref $msg ? $msg->[1] : $msg );
96 if ($node->[NODE_EXPR_FORMAT]) {
97 $value = $self->[TMPLT]->format($value, $node->[NODE_EXPR_FORMAT]);
99 return ( @errors, $value );
103 my ($self, $node) = @_;
107 if (eval { $value = $self->[EVAL]->process($node->[NODE_SET_EXPR]); 1 }) {
108 my @var = @{$node->[NODE_SET_VAR]};
110 my $top_name = shift @var;
111 my $var = $self->[TMPLT]->get_var($top_name);
114 $self->[TMPLT]->set_var($top_name, $var);
116 my @seen = $top_name;
118 my $subkey = shift @var;
119 Scalar::Util::blessed($var)
120 and die [ error => "Cannot set values in an object ".join(".", @seen) ];
121 my $type = Scalar::Util::reftype($var);
122 if ($type eq 'HASH') {
123 exists $type->{$subkey}
124 or die [ error => "$subkey not found in ".join(".", @seen) ];
125 $var = $var->{$subkey};
129 die [ error => "Only hashes supported for now" ];
132 Scalar::Util::blessed($var)
133 and die [ error => "Cannot set values in an object ".join(".", @seen) ];
135 or die [ error => join(".", @seen) . " isn't a reference" ];
136 Scalar::Util::reftype($var) eq 'HASH'
137 or die [ error => "Only hashes supported for now" ];
138 $var->{$var[0]} = $value;
141 $self->[TMPLT]->set_var($var[0] => $value);
147 if ($msg =~ /\bENOIMPL\b/) {
148 $self->[TMPLT]->trace_noimpl("set:$node->[NODE_FILENAME]:$node->[NODE_LINE]:$msg\n");
149 return $node->[NODE_ORIG];
152 push @errors, $self->_error($node, ref $msg ? $msg->[1] : $msg );
158 sub _process_define {
159 my ($self, $define) = @_;
161 $self->[TMPLT]->define_macro($define->[NODE_DEFINE_NAME], $define->[NODE_DEFINE_CONTENT], $define->[NODE_DEFINE_DEFAULTS]);
167 my ($self, $node) = @_;
175 $name = $self->[EVAL]->process($node->[NODE_CALL_NAME]);
176 for my $arg (@{$node->[NODE_CALL_LIST]}) {
177 my $key = $self->[EVAL]->process($arg->[0]);
178 my $value = $self->[EVAL]->process($arg->[1]);
179 $args{$key} = $value;
182 ($parsed, $defaults) = $self->[TMPLT]->get_macro($name);
184 ($parsed, my $message) = $self->[TMPLT]->parse_file($name);
186 $self->[TMPLT]->trace_noimpl("call:$node->[NODE_FILENAME]:$node->[NODE_LINE]:$message\n");
187 die "ENOIMPL - $name not found\n";
192 my $ctx = ".call '$name' from $node->[NODE_FILENAME]:$node->[NODE_LINE]";
193 if (eval { $self->[TMPLT]->start_scope($ctx, \%args), 1 }) {
195 for my $entry (@$defaults) {
196 my ($name_expr, $value_expr)= @$entry;
197 my $name = $self->[EVAL]->process($name_expr);
198 unless (exists $args{$name}) {
199 my $value = $self->[EVAL]->process($value_expr);
200 $self->[TMPLT]->set_var($name, $value);
204 @result = $self->process($parsed);
205 $self->[TMPLT]->end_scope();
211 "Error opening scope for call: $error\nBacktrace:\n",
212 map {; " ", $_, "\n" } ( reverse $self->[TMPLT]->backtrace );
216 @result = $node->[NODE_ORIG];
223 my ($self, $node) = @_;
227 $list = $self->[EVAL]->process($node->[NODE_FOR_EXPR]); 1;
230 if ($msg =~ /\bENOIMPL\b/) {
234 $self->process($node->[NODE_FOR_CONTENT]),
235 $node->[NODE_FOR_END][NODE_ORIG]
239 return $self->_error($node, $@);
244 Scalar::Util::blessed($list)
245 and return $self->_error($node, ".for expression cannot be a blessed object");
247 if (Scalar::Util::reftype($list) eq "ARRAY") {
250 elsif (Scalar::Util::reftype($list) eq "HASH") {
251 my @work = map +{ key => $_, value => $list->{$_} }, keys %$list;
255 return $self->_error($node, ".for expression cannot be a blessed object");
265 first => @$list ? $list->[0] : undef,
266 last => @$list ? $list->[-1] : undef,
267 size => scalar @$list,
268 is_last => sub { $index == $#$list },
269 is_first => sub { $index == 0 },
270 count => sub { $index + 1 },
271 index => sub { $index },
273 # the parity/odd/even are based on counting from 1, $index counts
275 parity => sub { $index % 2 ? "even" : "odd" },
276 odd => sub { $index % 2 == 0 },
277 even => sub { $index % 2 != 0 },
279 prev => sub { $index > 0 ? $list->[$index-1] : undef },
280 next => sub { $index < $#$list ? $list->[$index+1] : undef },
283 my $name = $node->[NODE_FOR_NAME];
285 my $scope = $self->[TMPLT]->top_scope;
286 local $scope->{$name};
287 local $scope->{loop} = \%loop;
290 for my $current (@$list) {
291 $scope->{$name} = $current;
292 $loop{current} = $current;
293 push @result, $self->process($node->[NODE_FOR_CONTENT]);
301 my ($self, $node) = @_;
303 return "* " . $node->[NODE_ERROR_MESSAGE] . " *";
307 my ($self, $node) = @_;
310 my $acts = $self->[ACTS];
312 my $name = $node->[NODE_TAG_NAME];
316 if (exists $acts->{"if$name"}) {
317 #print STDERR " found cond if$name\n" if DEBUG > 1;
318 $cond = !!$self->[TMPLT]->low_perform($acts, "if$name", $node->[NODE_TAG_ARGS], undef);
320 elsif (exists $acts->{lcfirst $name}) {
321 #print STDERR " found cond $name\n" if DEBUG > 1;
322 $cond = !!$self->[TMPLT]->low_perform($acts, lcfirst $name, $node->[NODE_TAG_ARGS], undef);
327 if ($msg !~ /\bENOIMPL\b/) {
328 @errors = $self->_error($node, $msg);
332 return (@errors, $self->process($cond ? $node->[NODE_COND_TRUE]
333 : $node->[NODE_COND_FALSE]));
336 return (@errors, $node->[NODE_ORIG], $self->process($node->[NODE_COND_TRUE]), $node->[NODE_COND_OR][NODE_ORIG], $self->process($node->[NODE_COND_FALSE]), $node->[NODE_COND_EIF][NODE_ORIG]);
340 sub _process_condnot {
341 my ($self, $node) = @_;
344 my $acts = $self->[ACTS];
346 my $name = $node->[NODE_TAG_NAME];
350 if (exists $acts->{"if$name"}) {
351 #print STDERR " found cond if$name\n" if DEBUG > 1;
352 $cond = !!$self->[TMPLT]->low_perform($acts, "if$name", $node->[NODE_TAG_ARGS], undef);
354 elsif (exists $acts->{lcfirst $name}) {
355 #print STDERR " found cond $name\n" if DEBUG > 1;
356 $cond = !!$self->[TMPLT]->low_perform($acts, lcfirst $name, $node->[NODE_TAG_ARGS], undef);
361 if ($msg !~ /\bENOIMPL\b/) {
362 @errors = $self->_error($node, $msg);
366 return (@errors, $cond ? "" : $self->process($node->[NODE_COND_TRUE]));
369 return (@errors, $node->[NODE_ORIG], $self->process($node->[NODE_COND_TRUE]), $node->[NODE_COND_EIF][NODE_ORIG]);
373 sub _process_iterator {
374 my ($self, $node) = @_;
376 my $name = $node->[NODE_TAG_NAME];
377 my $args = $node->[NODE_TAG_ARGS];
379 my $entry = $self->[ACTS]{"iterate_$name"};
381 my $reset = $self->[ACTS]{"iterate_${name}_reset"};
382 my ($resetf, @rargs);
384 if (ref $reset eq 'ARRAY') {
385 ($resetf, @rargs) = @$reset;
392 my ($entryf, @eargs);
393 if (ref $entry eq 'ARRAY') {
394 ($entryf, @eargs) = @$entry;
402 #print STDERR " resetting (func)\n" if DEBUG > 1;
403 $resetf->(@rargs, $args, $self->[ACTS], $name, $self->[TMPLT]);
406 my $obj = shift @rargs;
407 #print STDERR " resetting (method) $obj->$resetf\n" if DEBUG > 1;
408 $obj->$resetf(@rargs, $args, $self->[ACTS], $name, $self->[TMPLT]);
410 #print STDERR " reset done\n" if DEBUG > 1;
413 ref $entryf or $eobj = shift @eargs;
416 while ($eobj ? $eobj->$entryf(@eargs, $name, $args)
417 : $entryf->(@eargs, $name, $args)) {
418 push @result, $self->process($node->[NODE_ITERATOR_SEPARATOR])
420 push @result, $self->process($node->[NODE_ITERATOR_LOOP]);
429 $self->process($node->[NODE_ITERATOR_LOOP]),
430 $node->[NODE_ITERATOR_SEPTOK][NODE_ORIG],
431 $self->process($node->[NODE_ITERATOR_SEPARATOR]),
432 $node->[NODE_ITERATOR_ENDTOK][NODE_ORIG]
438 my ($self, $node) = @_;
440 my $name = $node->[NODE_TAG_NAME];
441 my $args = $node->[NODE_TAG_ARGS];
443 my $entry = $self->[ACTS]{"with_$name"};
446 if (ref $entry eq 'ARRAY') {
447 ($code, @args) = @$entry;
454 ref $code or $obj = shift @args;
455 my $work = join('', $self->process($node->[NODE_WITH_CONTENT]));
457 ? $obj->$code(@args, $args, $work, "", $self->[ACTS], $name, $self->[TMPLT])
458 : $code->(@args, $args, $work, "", $self->[ACTS], $name, $self->[TMPLT]);
464 $self->process($node->[NODE_WITH_CONTENT]),
465 $node->[NODE_WITH_END][NODE_ORIG]
471 my ($self, $node) = @_;
473 my ($filename, $args, $content) =
474 @{$node}[NODE_WRAP_FILENAME, NODE_WRAP_ARGS, NODE_WRAP_CONTENT];
477 my $parms_re = $self->[TMPLT]->parms_re;
480 while ($args =~ s/^\s*(\w+)\s*=>\s*\"([^\"]+)\"//
481 || $args =~ s/^\s*(\w+)\s*=>\s*($parms_re)//
482 || $args =~ s/^\s*(\w+)\s*=>\s*([^\s,]+)//) {
483 my ($name, $value) = ($1, $2);
484 $value =~ s/\A($parms_re)\z/ $self->[TMPLT]->perform($self->[ACTS], $2, $3, $1) /egs;
486 $params{$name} = $value;
487 $args =~ s/\s*,\s*//;
490 or push @errors, $self->_error($node, "WARNING: Extra data after parameters '$args'");
493 if ($self->[TMPLT]->start_wrap(\%params)) {
494 my ($wrap_node, $error) = $self->[TMPLT]->parse_file($node->[NODE_WRAP_FILENAME]);
497 my $proc = __PACKAGE__->new($self->[ACTS], $self->[TMPLT],
498 sub { $self->process($content) });
500 push @result, $proc->process($wrap_node);
503 push @result, $self->_error($node, "Loading wrap: $error");
505 $self->[TMPLT]->end_wrap;
508 push @errors, $self->_error($node, "Error starting wrap: Too many levels of wrap for '$node->[NODE_WRAP_FILENAME]'");
509 @result = $self->process($content);
512 return ( @errors, @result );
515 sub _process_ext_wrap {
516 my ($self, $node) = @_;
518 my ($filename, $args, $content) =
519 @{$node}[NODE_WRAP_FILENAME, NODE_WRAP_ARGS, NODE_WRAP_CONTENT];
526 $name = $self->[EVAL]->process($filename);
527 for my $arg (@$args) {
528 my $key = $self->[EVAL]->process($arg->[0]);
529 my $value = $self->[EVAL]->process($arg->[1]);
530 $args{$key} = $value;
534 my $ctx = ".wrap '$name' from $node->[NODE_FILENAME]:$node->[NODE_LINE]";
535 if ($self->[TMPLT]->start_wrap(\%args)) {
537 my $parsed = $self->[TMPLT]->get_macro($name);
539 ($parsed, my $message) = $self->[TMPLT]->parse_file($name);
541 $self->[TMPLT]->trace_noimpl("call:$node->[NODE_FILENAME]:$node->[NODE_LINE]:$message\n");
542 push @result, $self->_error($node, "Loading wrap: $message");
547 my $proc = __PACKAGE__->new($self->[ACTS], $self->[TMPLT],
548 sub { $self->process($content) });
549 push @result, $proc->process($parsed);
551 $self->[TMPLT]->end_wrap;
554 push @result, $self->_error($node, "Starting wrap: too many levels?");
561 $self->process($node->[NODE_WRAP_CONTENT]),
562 $node->[NODE_WRAP_END][NODE_ORIG],
566 return ( @errors, @result );
569 sub _process_wraphere {
570 my ($self, $node) = @_;
573 or return $self->_error($node, "wrap here without being wrapped");
575 return $self->[WRAPPED]->();
578 sub _process_switch {
579 my ($self, $node) = @_;
581 my $cases = $node->[NODE_SWITCH_CASES];
583 for my $i (0 .. $#$cases) {
584 my ($case, $content) = @{$cases->[$i]};
586 my ($type, $func, $args) =
587 @{$case}[NODE_TYPE, NODE_TAG_NAME, NODE_TAG_ARGS];
589 if ($func eq "default") {
590 return $self->process($content);
598 if (exists $self->[ACTS]{"if$func"}) {
599 $result = $self->[TMPLT]->low_perform($self->[ACTS], "if$func", $args, "");
601 elsif (exists $self->[ACTS]{lcfirst $func}) {
602 $result = $self->[TMPLT]->low_perform($self->[ACTS], lcfirst $func, $args, '');
605 die "ENOIMPL $func not found for switch case\n";
611 if ($msg =~ /^ENOIMPL\b/) {
618 $_->[0][NODE_ORIG], $self->process($_->[1])
619 } @{$cases}[$i .. $#$cases ]
621 $node->[NODE_SWITCH_END][NODE_ORIG],
624 push @errors, $self->_error($case, $msg);
626 if ($type eq 'case' ? $result : !$result) {
627 return (@errors, $self->process($content));
634 sub _process_ext_if {
635 my ($self, $node) = @_;
637 my @conds = @{$node->[NODE_EXTIF_CONDS]};
638 while (my $cond = shift @conds) {
640 if (eval { $result = $self->[EVAL]->process($cond->[2]); 1 }) {
642 return $self->process($cond->[1]);
647 if (!ref $msg && $msg =~ /\bENOIMPL\b/) {
648 unshift @conds, $cond;
651 if (@conds < @{$node->[NODE_EXTIF_CONDS]}) {
652 $prefix = "<:.if 0 :>";
659 map { $_->[0][NODE_ORIG], $self->process($_->[1]) } @conds
661 $node->[NODE_EXTIF_ELSE][0][NODE_ORIG],
662 $self->process($node->[NODE_EXTIF_ELSE][1]),
663 $node->[NODE_EXTIF_END][NODE_ORIG]
667 return $self->_error($node, ref $msg ? $msg->[1] : $msg);
672 return $self->process($node->[NODE_EXTIF_ELSE][1]);
676 my ($self, $node) = @_;
678 return map $self->process($_), @{$node}[NODE_COMP_FIRST .. $#$node];
682 my ($self, $node) = @_;
684 my $name = $node->[NODE_TAG_NAME];
686 my $tag_method = "tag_$name";
687 if (exists $self->[ACTS]{$name} || $self->[TMPLT]->can($tag_method)) {
689 if (eval { $value = $self->[TMPLT]->low_perform($self->[ACTS], $name, $node->[NODE_TAG_ARGS], $node->[NODE_ORIG]); 1 }) {
693 unless ($msg =~ /\bENOIMPL\b/) {
694 return $self->_error($node, $msg);
698 return Squirrel::Template::Deparser->deparse($node);
701 sub _process_iterateover {
702 my ($self, $node) = @_;
707 $call = $self->[EVAL]->process($node->[NODE_ITERATEOVER_CALL]);
708 @args = map $self->[EVAL]->process($_), @{$node->[NODE_ITERATEOVER_ARGS]};
712 if ($msg =~ /\bENOIMPL\b/) {
716 $self->process($node->[NODE_FOR_CONTENT]),
717 $node->[NODE_FOR_END][NODE_ORIG]
721 return $self->_error($node, $@);
725 unless (ref $call && !Scalar::Util::blessed($call)
726 && Scalar::Util::reftype($call) eq 'CODE') {
727 return $self->_error($node, ".iterateover expression must be a code reference");
735 push @result, $self->process($node->[NODE_ITERATEOVER_CONTENT]);
743 push @result, $self->_error($node, "exception in .iterateover callback ".$@);
750 my ($self, $node) = @_;
752 my $cond = $node->[NODE_WHILE_COND];
754 unless (eval { $result = $self->[EVAL]->process($cond); 1 }) {
756 if (!ref $msg && $msg =~ /\bENOIMPL\b/) {
760 $self->process($node->[NODE_WHILE_CONTENT]),
761 $node->[NODE_WHILE_END][NODE_ORIG],
765 return $self->_error($node, ref $msg ? $msg->[1] : $msg);
770 push @output, $self->process($node->[NODE_WHILE_CONTENT]);
772 unless (eval { $result = $self->[EVAL]->process($cond); 1 }) {
774 if (!ref $msg && $msg ==~ /\bENOIMPL\b/) {
779 $self->process($node->[NODE_WHILE_CONTENT]),
780 $node->[NODE_WHILE_END][NODE_ORIG],
787 $self->_error($node, ref $msg ? $msg->[1] : $msg),
800 Squirrel::Template::Processor - process a parsed template
804 use Squirrel::Template;
805 my $tmpl = Squirrel::Template->new(...);
806 my $proc = Squirrel::Template::Processor->new(\%acts, $tmpl);
807 my @content = $proc->process($node);
811 Processes a parsed template node producing text.
813 Calls back into the templater to find and parse wrapper files, to set
814 wrap parameters and to evaluate some tags.
820 =item new(\%acts, $tmpl)
822 Create a new processor. A third C<$wrapped> parameter can be supplied
823 when processing wrapped subtemplates.
827 Process a parsed template node returning the results as a list.
833 Tony Cook <tony@develop-help.com>