1 package Squirrel::Template::Expr;
4 our $VERSION = "1.016";
6 package Squirrel::Template::Expr::Eval;
8 use Squirrel::Template::Expr::WrapScalar;
9 use Squirrel::Template::Expr::WrapHash;
10 use Squirrel::Template::Expr::WrapArray;
11 use Squirrel::Template::Expr::WrapCode;
12 use Squirrel::Template::Expr::WrapClass;
14 use constant TMPL => 0;
15 use constant ACTS => 1;
18 my ($class, $templater, $acts) = @_;
20 return bless [ $templater, $acts ], $class;
24 my ($self, $val) = @_;
27 if (Scalar::Util::blessed($val)) {
31 my $type = Scalar::Util::reftype($val);
32 if ($type eq "ARRAY") {
33 return Squirrel::Template::Expr::WrapArray->new($val, $self->[TMPL], undef, $self);
35 elsif ($type eq "HASH") {
36 return Squirrel::Template::Expr::WrapHash->new($val, $self->[TMPL], undef, $self);
38 elsif ($type eq "CODE") {
39 return Squirrel::Template::Expr::WrapCode->new($val, $self->[TMPL], undef, $self);
44 return Squirrel::Template::Expr::WrapScalar->new($val, $self->[TMPL], $self->[ACTS], $self);
49 return $_[0][TMPL]->get_var($_[1][1]);
53 return $_[0]->process($_[1][1]) + $_[0]->process($_[1][2]);
56 sub _process_subtract {
57 return $_[0]->process($_[1][1]) - $_[0]->process($_[1][2]);
61 return $_[0]->process($_[1][1]) * $_[0]->process($_[1][2]);
65 return $_[0]->process($_[1][1]) / $_[0]->process($_[1][2]);
69 return int($_[0]->process($_[1][1]) / $_[0]->process($_[1][2]));
73 return $_[0]->process($_[1][1]) % $_[0]->process($_[1][2]);
82 return $_[0]->process($_[1][1]) eq $_[0]->process($_[1][2]);
86 return $_[0]->process($_[1][1]) ne $_[0]->process($_[1][2]);
90 return $_[0]->process($_[1][1]) gt $_[0]->process($_[1][2]);
94 return $_[0]->process($_[1][1]) lt $_[0]->process($_[1][2]);
98 return $_[0]->process($_[1][1]) ge $_[0]->process($_[1][2]);
102 return $_[0]->process($_[1][1]) le $_[0]->process($_[1][2]);
106 return $_[0]->process($_[1][1]) cmp $_[0]->process($_[1][2]);
111 return $_[0]->process($_[1][1]) == $_[0]->process($_[1][2]);
115 return $_[0]->process($_[1][1]) != $_[0]->process($_[1][2]);
119 return $_[0]->process($_[1][1]) > $_[0]->process($_[1][2]);
123 return $_[0]->process($_[1][1]) < $_[0]->process($_[1][2]);
127 return $_[0]->process($_[1][1]) >= $_[0]->process($_[1][2]);
131 return $_[0]->process($_[1][1]) <= $_[0]->process($_[1][2]);
135 return $_[0]->process($_[1][1]) =~ $_[0]->process($_[1][2]);
138 sub _process_notmatch {
139 return $_[0]->process($_[1][1]) !~ $_[0]->process($_[1][2]);
143 return $_[0]->process($_[1][1]) ? $_[0]->process($_[1][2]) : $_[0]->process($_[1][3]);
147 return $_[0]->process($_[1][1]) <=> $_[0]->process($_[1][2]);
150 sub _process_uminus {
151 return - ($_[0]->process($_[1][1]));
154 sub _process_concat {
155 return $_[0]->process($_[1][1]) . $_[0]->process($_[1][2]);
163 return bless [ $_[1][1], $_[1][2] ], "Squirrel::Template::Expr::Block";
167 my ($self, $val, $args, $method, $ctx) = @_;
169 if (Scalar::Util::blessed($val)
170 && !$val->isa("Squirrel::Template::Expr::WrapBase")) {
172 or die [ error => "No such method $method" ];
173 if ($val->can("restricted_method")) {
174 $val->restricted_method($method)
175 and die [ error => "method $method is restricted" ];
177 return $ctx && $ctx eq 'LIST' ? $val->$method(@$args)
178 : scalar($val->$method(@$args));
181 my $wrapped = $self->_wrapped($val);
182 return $wrapped->call($method, $args, $ctx);
187 my ($self, $node, $ctx) = @_;
191 my $val = $self->process($node->[2]);
192 my $args = $self->process_list($node->[3]);
193 my $method = $node->[1];
195 return $self->_do_call($val, $args, $method, $ctx);
198 sub _process_callvar {
199 my ($self, $node, $ctx) = @_;
203 my $val = $self->process($node->[2]);
204 my $args = $self->process_list($node->[3]);
205 my $method = $self->[TMPL]->get_var($node->[1]);
207 return $self->_do_call($val, $args, $method, $ctx);
211 my ($self, $ids, $exprs, $args) = @_;
215 @args{@$ids} = @$args;
216 $args{_arguments} = $args;
217 if (eval { $self->[TMPL]->start_scope("calling block", \%args), 1}) {
218 for my $expr (@$exprs) {
219 $result = $self->process($expr);
221 $self->[TMPL]->end_scope();
228 my ($self, $code, $args, $ctx) = @_;
232 if (Scalar::Util::reftype($code) eq "CODE") {
233 return $ctx eq "LIST" ? $code->(@$args) : scalar($code->(@$args));
235 elsif (Scalar::Util::blessed($code)
236 && $code->isa("Squirrel::Template::Expr::Block")) {
237 return $self->_do_callblock($code->[0], $code->[1], $args);
240 die [ error => "can't call non code as a function" ];
244 sub _process_funccall {
245 my ($self, $node, $ctx) = @_;
247 my $code = $self->process($node->[1]);
248 my $args = $self->process_list($node->[2]);
250 return $self->call_function($code, $args, $ctx);
254 my ($self, $node) = @_;
256 return $self->process_list($node->[1], 'LIST');
260 my ($self, $node, $ctx) = @_;
262 my $start = $self->process($node->[1]);
263 my $end = $self->process($node->[2]);
265 return $ctx eq 'LIST' ? ( $start .. $end ) : [ $start .. $end ];
269 my ($self, $node) = @_;
272 for my $pair (@{$node->[1]}) {
273 my $key = $self->process($pair->[0]);
274 my $value = $self->process($pair->[1]);
275 $result{$key} = $value;
281 sub _process_subscript {
282 my ($self, $node) = @_;
284 my $list = $self->process($node->[1]);
285 my $index = $self->process($node->[2]);
286 Scalar::Util::blessed($list)
287 and die [ error => "Cannot subscript an object" ];
288 my $type = Scalar::Util::reftype($list);
289 if ($type eq "HASH") {
290 return $list->{$index};
292 elsif ($type eq "ARRAY") {
293 return $list->[$index];
296 die [ error => "Cannot subscript a $type" ];
301 return !$_[0]->process($_[1][1]);
305 return $_[0]->process($_[1][1]) || $_[0]->process($_[1][2]);
309 return $_[0]->process($_[1][1]) && $_[0]->process($_[1][2]);
313 my ($self, $node, $ctx) = @_;
315 my $method = "_process_$node->[0]";
316 $self->can($method) or die "No handler for $node->[0]";
317 return $self->$method($node, $ctx);
321 my ($self, $list) = @_;
323 return [ map $self->process($_, 'LIST'), @$list ];
326 package Squirrel::Template::Expr::Parser;
331 return bless {}, $class;
335 my ($self, $text) = @_;
337 my $tokenizer = Squirrel::Template::Expr::Tokenizer->new($text);
338 my $result = $self->_parse_expr($tokenizer);
340 my $last = $tokenizer->get;
341 unless ($last->[0] eq 'eof') {
342 die [ error => "Expected eof but found $last->[0]" ];
349 my ($self, $tokenizer) = @_;
351 return $self->_parse_expr($tokenizer);
355 my ($self, $tok) = @_;
357 return $self->_parse_cond($tok);
385 'op!~' => "notmatch",
390 my ($self, $tok) = @_;
392 my $result = $self->_parse_or($tok);
393 if ($tok->peektype eq 'op?') {
395 my $true = $self->_parse_or($tok);
396 my $colon = $tok->get;
398 or die [ error => "Expected : for ? : operator but found $colon->[0]" ];
399 my $false = $self->_parse_cond($tok);
401 $result = [ cond => $result, $true, $false ];
408 my ($self, $tok) = @_;
410 my $result = $self->_parse_and($tok);
411 while ($tok->peektype eq 'or') {
413 my $other = $self->_parse_and($tok);
414 $result = [ or => $result, $other ];
421 my ($self, $tok) = @_;
423 my $result = $self->_parse_rel($tok);
424 while ($tok->peektype eq 'and') {
426 my $other = $self->_parse_rel($tok);
427 $result = [ and => $result, $other ];
433 my %relops = map {; "op$_" => 1 } qw(eq ne gt lt ge le cmp == != < > >= <= <=> =~ !~);
436 my ($self, $tok) = @_;
438 my $result = $self->_parse_additive($tok);
439 my $nexttype = $tok->peektype;
440 while ($relops{$nexttype}) {
442 my $other = $self->_parse_additive($tok);
443 $result = [ $ops{$nexttype}, $result, $other ];
444 $nexttype = $tok->peektype;
449 sub _parse_additive {
450 my ($self, $tok) = @_;
452 my $result = $self->_parse_mult($tok);
453 my $nexttype = $tok->peektype;
454 while ($nexttype eq 'op+' || $nexttype eq 'op-' || $nexttype eq 'op_') {
456 my $other = $self->_parse_mult($tok);
457 $result = [ $ops{$nexttype}, $result, $other ];
458 $nexttype = $tok->peektype;
464 my ($self, $tok) = @_;
466 my $result = $self->_parse_prefix($tok);
467 my $nexttype = $tok->peektype;
468 while ($nexttype eq 'op*' || $nexttype eq 'op/'
469 || $nexttype eq 'div' || $nexttype eq 'mod') {
471 my $other = $self->_parse_prefix($tok);
472 $result = [ $ops{$op->[0]}, $result, $other ];
473 $nexttype = $tok->peektype;
479 my ($self, $tok) = @_;
481 my $nexttype = $tok->peektype('TERM');
482 if ($nexttype eq 'op-') {
484 return [ uminus => $self->_parse_prefix($tok) ];
486 elsif ($nexttype eq 'op+') {
488 return $self->_parse_prefix($tok);
490 elsif ($nexttype eq 'op!' || $nexttype eq 'opnot') {
492 return [ not => $self->_parse_prefix($tok) ];
495 return $self->_parse_call($tok);
500 my ($self, $tok) = @_;
502 $tok->peektype("TERM") eq 'op)'
506 push @list, $self->_parse_expr($tok);
507 my $peek = $tok->peektype;
508 while ($peek eq 'op,' || $peek eq '..') {
511 my $start = pop @list;
512 $start->[0] ne 'range'
513 or die [ error => "Can't use a range as the start of a range" ];
514 my $end = $self->_parse_expr($tok);
515 push @list, [ range => $start, $end ];
518 push @list, $self->_parse_expr($tok);
520 $peek = $tok->peektype;
526 sub _parse_paren_list {
527 my ($self, $tok, $what) = @_;
529 my $open = $tok->get;
531 or die [ error => "Expected '(' for $what but found $open->[0]" ];
532 my $list = $self->_parse_list($tok);
533 my $close = $tok->get;
535 or die [ error => "Expected ')' for $what but found $close->[0]" ];
541 my ($self, $tok) = @_;
543 my $result = $self->_parse_postfix($tok);
544 my $next = $tok->peektype;
545 while ($next eq 'op.' || $next eq 'op[' || $next eq 'op(') {
546 if ($next eq 'op.') {
548 my $name = $tok->get;
549 if ($name->[0] eq "id") {
551 if ($tok->peektype eq 'op(') {
552 $list = $self->_parse_paren_list($tok, "method");
554 $result = [ call => $name->[2], $result, $list ];
556 elsif ($name->[0] eq 'op$') {
560 or die [ error => "Expected an identifier after .\$ but found $name->[1]" ];
562 if ($tok->peektype eq 'op(') {
563 $list = $self->_parse_paren_list($tok, "method");
565 $result = [ callvar => $name->[2], $result, $list ];
568 die [ error => "Expected a method name or \$var after '.' but found $name->[1]" ];
571 elsif ($next eq 'op[') {
573 my $index = $self->_parse_expr($tok);
574 my $close = $tok->get;
576 or die [ error => "Expected closing ']' but got $close->[0]" ];
577 $result = [ subscript => $result, $index ];
579 elsif ($next eq 'op(') {
580 my $args = $self->_parse_paren_list($tok, "call");
581 $result = [ funccall => $result, $args ];
583 $next = $tok->peektype;
590 my ($self, $tok) = @_;
592 return $self->_parse_primary($tok);
596 my ($self, $tok) = @_;
598 my $t = $tok->get('TERM');
599 if ($t->[0] eq 'op(') {
600 my $r = $self->_parse_expr($tok);
601 my $close = $tok->get;
602 unless ($close->[0] eq 'op)') {
603 die [ error => "Expected ')' but found $close->[0]" ];
607 elsif ($t->[0] eq 'str' || $t->[0] eq 'num') {
608 return [ const => $t->[2] ];
610 elsif ($t->[0] eq 're') {
613 my $sub = eval "sub { my \$str = shift; qr/\$str/$opts; }";
615 $sub and $re = eval { $sub->($str) };
617 or die [ error => "Cannot compile /$t->[2]/$opts: $@" ];
618 return [ const => $re ];
620 elsif ($t->[0] eq 'id') {
621 if ($t->[2] eq "undef") {
625 return [ var => $t->[2] ];
628 elsif ($t->[0] eq 'op[') {
630 if ($tok->peektype ne 'op]') {
631 $list = $self->_parse_list($tok);
633 my $close = $tok->get;
635 or die [ error => "Expected list end ']' but got $close->[0]" ];
636 return [ list => $list ];
638 elsif ($t->[0] eq 'op{') {
639 my $pairs = $self->parse_pairs($tok);
640 my $next = $tok->get;
642 or die [ error => "Expected , or } but found $next->[1]" ];
644 return [ hash => $pairs ];
646 elsif ($t->[0] eq 're') {
647 return [ re => $t->[2], $t->[3] ];
649 elsif ($t->[0] eq 'undef') {
652 elsif ($t->[0] eq 'blockstart') {
653 # @{ idlist: expr; ... }
654 # idlist can be empty:
656 # the expr list will become more complex at some point
658 my $nexttype = $tok->peektype;
659 if ($nexttype ne 'op:') {
661 or die [ error => "Expected id or : after \@{ but found $nexttype->[0]" ];
662 push @ids, $tok->get->[2];
663 while ($tok->peektype eq 'op,') {
665 $tok->peektype eq 'id'
666 or die [ error => "Expected id after , in \@{ but found $nexttype->[0]" ];
667 push @ids, $tok->get->[2];
671 or die [ error => "Expected : or , in identifier list in \@{ but found $end->[0]" ];
678 push @exprs, $self->_parse_expr($tok);
679 while ($tok->peektype eq 'op;') {
681 push @exprs, $self->_parse_expr($tok);
683 $nexttype = $tok->peektype;
685 or die [ error => "Expected } at end of \@{ but found $nexttype" ];
688 return [ block => \@ids, \@exprs ];
691 die [ error => "Expected term but got $t->[0]" ];
696 my ($self, $tok) = @_;
698 my $nexttype = $tok->peektype;
699 if ($nexttype eq 'op}' || $nexttype eq 'eof') {
707 if ($tok->peektype eq 'id') {
709 if ($tok->peektype eq 'op:') {
710 $key = [ const => $id->[2] ];
716 $key ||= $self->_parse_additive($tok);
717 my $colon = $tok->get;
719 or die [ error => "Expected : in hash but found $colon->[1]" ];
720 my $value = $self->_parse_expr($tok);
721 push @pairs, [ $key, $value ];
722 } while ($next = $tok->get and $next->[0] eq 'op,');
729 package Squirrel::Template::Expr::Tokenizer;
731 use constant TEXT => 0;
732 use constant QUEUE => 1;
735 my ($class, $text) = @_;
737 return bless [ $text, [] ], $class;
749 my ($self, $want) = @_;
751 my $queue = $self->[QUEUE];
753 and return shift @$queue;
759 if ($want ne 'TERM' &&
760 $self->[TEXT] =~ s/\A(\s*(div\b|mod\b|\.\.|and\b|or\b)\s*)//) {
761 push @$queue, [ $2 => $1 ];
763 elsif ($self->[TEXT] =~ s/\A(\s*(0x[0-9A-Fa-f]+)\s*)//) {
764 push @$queue, [ num => $1, oct $2 ];
766 elsif ($self->[TEXT] =~ s/\A(\s*(0b[01]+)\s*)//) {
767 push @$queue, [ num => $1, oct $2 ];
769 elsif ($self->[TEXT] =~ s/\A(\s*0o([0-7]+)\s*)//) {
770 push @$queue, [ num => $1, oct $2 ];
772 elsif ($self->[TEXT] =~ s/\A(\s*((?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[Ee][+-]?[0-9]+)?)\s*)//) {
773 push @$queue, [ num => $1, $2 ];
775 elsif ($want eq 'TERM' &&
776 $self->[TEXT] =~ s!\A(\s*/((?:[^/\\]|\\.)+)/([ismx]*\s)?\s*)!!) {
777 push @$queue, [ re => $1, $2, $3 || "" ];
779 elsif ($self->[TEXT] =~ s/\A(\s*(not\b|eq\b|ne\b|le\b|lt\b|ge\b|gt\b|cmp\b|<=>|<=|>=|[!=]\=|\=\~|!~|[\?:,\[\]\(\)<>=!.*\/+\{\};\$-]|_(?![A-Za-z0-9_]))\s*)//) {
780 push @$queue, [ "op$2" => $1 ];
782 elsif ($self->[TEXT] =~ s/\A(\s*([A-Za-z_][a-zA-Z_0-9]*)\s*)//) {
783 push @$queue, [ id => $1, $2 ];
785 elsif ($self->[TEXT] =~ s/\A(\s*\"((?:[^"\\]|\\["\\nt]|\\x[0-9a-fA-F]{2}|\\x\{[0-9a-fA-F]+\}|\\N\{[A-Za-z0-9 ]+\})*)\"\s*)//) {
787 my $str = _process_escapes($2);
788 push @$queue, [ str => $1, $str ];
790 elsif ($self->[TEXT] =~ s/\A(\s*\'([^\']*)\'\s*)//) {
791 push @$queue, [ str => $1, $2 ];
793 elsif ($self->[TEXT] =~ s/\A(\s*\@undef\bs*)//) {
794 push @$queue, [ undef => $1 ];
796 elsif ($self->[TEXT] =~ s/\A(\s*@\{\s*)//) {
797 push @$queue, [ blockstart => $1 ];
800 die [ error => "Unknown token '$self->[TEXT]'" ];
803 unless (length $self->[TEXT]) {
804 push @$queue, [ eof => "" ];
807 return shift @$queue;
811 my ($self, $tok) = @_;
813 unshift @{$self->[QUEUE]}, $tok;
817 my ($self, $what) = @_;
819 unless (@{$self->[QUEUE]}) {
820 my $t = $self->get($what)
822 unshift @{$self->[QUEUE]}, $t;
825 return $self->[QUEUE][0];
829 my ($self, $what) = @_;
831 return $self->peek($what)->[0];
834 sub _process_escapes {
841 \\x\{([0-9A-Fa-f]+)\}
845 \\N\{([A-Za-z0-9\ ]+)\}
856 my $charnames_loaded;
858 my ($name, $errors) = @_;
861 my $code = charnames::vianame($name);
862 unless (defined $code) {
863 die [ error => "Unknown \\N name '$name'" ];
874 Squirrel::Template::Expr - expression handling for Squirrel::Template
879 my $parser = Squirrel::Template::Expr::Parser->new;
881 my $expr = $parser->parse($expr_text);
883 my $tokens = Squirrel::Template::Expr::Tokenizer->new($expr_text);
885 my $expr = $parser->parse_tokens($tokenizer);
886 # and possibly process more tokens here
888 my $eval = Squirrel::Template::Expr::Parser->new($templater);
890 my $value = $eval->process($expr);
891 my $value = $eval->process($expr, "LIST");
893 my $arrayref = $eval->process(\@exprs);
897 <:= somevalue + 10 :>
898 <:.if somevalue == 10 :>
902 Squirrel::Template::Expr provides expression parsing and evaluation
903 for newer style tags for L<Squirrel::Template>.
905 =head1 EXPRESSION SYNTAX
909 Listed highest precedence first.
915 C<<[ I<list> ]>>, C<<{ I<key>:I<value>, ... }>>, literals
917 C<<[ I<list> ]>> allows you to build lists objects. Within C<[ ... ]>
918 you can use the C<..> operator to produce a list of numerically or
919 alphabetically ascending values per Perl's magic increment.
926 Method calls within C<<[ ... ]>> are done in perl's list context.
928 C<<{ ... }>> allows you to build hash objects.
932 { "somekey":somevariable, somekeyinvar:"somevalue" }
934 See L</Literals> for literals
938 method calls - methods are called as:
944 object.method(arguments)
948 Virtual methods are defined for hashes, arrays and scalars, see
949 L<Squirrel::Template::Expr::WrapHash>,
950 L<Squirrel::Template::Expr::WrapArray>,
951 L<Squirrel::Template::Expr::WrapScalar>,
952 L<Squirrel::Template::Expr::WrapCode> and
953 L<Squirrel::Template::Expr::WrapClass>.
957 function calls - functions are called as:
965 or any other expression that doesn't look like a method call:
967 somehash.get["foo"]();
971 unary -, unary +, unary !, unary not
975 * / div mod - simple arithmetic operators. C<div> returns the integer
976 portion of dividing the first operand by the second. C<mod> returns
977 the remainder of integer division.
981 + - _ - arithmetic addition and subtraction. C<_> does string
986 eq ne le lt ge gt == != > < >= <= =~ !~ - relational operators as per
991 and - boolean and, with shortcut.
995 or - boolean or, with shortcut.
999 Conditional (C<< I<cond> ? I<true> : I<false> >>) - return the value
1000 of I<true> or I<false> depending on I<cond>.
1006 Numbers can be represented in several formats:
1012 simple decimal - C<100>, C<3.14159>, C<1e10>.
1024 binary - C<0b1100100>
1028 an undefined value - C<@undef>
1032 blocks - C<< @{ I<idlist> : I<exprlist> } >> where C<< I<idlist> >> is
1033 a comma separated list of local variables that arguments are assigned
1034 to, and I<exprlist> is a semi-colon separated list of expressions.
1035 The block literal can be called as if it's a function, or supplied to
1036 methods like the array grep() method.
1040 Strings can be either " or ' delimited.
1042 Simple quote delimited strings allow no escaping, and may not contain
1043 single quotes. The contents are treated literally.
1045 Double quoted strings allow escaping as follows:
1051 Any of C<\">, C<\n>, C<\\>, C<\t> are treated as in C or perl,
1052 replaced with double quote, newline, backslash or tab respectively.
1056 C<<\x{I<hex-digits>}>> is replaced with the unicode code-point
1057 indicated by the hex number.
1061 C<< \xI<hex-digit>I<hex-digit> >> is replaced by the unicode
1062 code-point indicated by the 2-digit hex number.
1066 C<< \N{ I<unicode-character-name> } >> is replaced by the unicode
1071 =head1 Squirrel::Template::Expr::Parser
1073 Squirrel::Template::Expr::Parser provides parsing for expressions.
1081 Create a new parser object.
1085 Parse C<$text> as an expression. Parsing must reach the end of the
1086 text or an exception will be thrown.
1088 =item parse_tokens($tokenizer)
1090 Process tokens from C<$tokenizer>, a
1091 L</Squirrel::Template::Expr::Tokenizer> object. The caller can call
1092 these method several times with the same C<$tokenizer> to parse
1093 components of a statement, and should ensure the eof token is visible
1094 after the final component.
1098 =head1 Squirrel::Template::Expr::Tokenizer
1100 Split text into tokens. Token parsing is occasionally context
1109 Create a new tokenizer for parsing C<$text>.
1115 Retrieve a token from the stream, consuming it. If a term is expected
1116 $context should be set to C<'TERM'>.
1120 Push a token back into the stream.
1124 =item peek($context)
1126 Retrieve the next token from the stream without consuming it.
1130 =item peektype($context)
1132 Retrieve the type of the next token from the stream without consuming
1137 =head1 Squirrel::Template::Expr::Eval
1139 Used to evaluate an expression returned by
1140 Squirrel::Template::Expr::parse().
1146 =item new($templater)
1148 Create a new evaluator. C<$templater> should be a
1149 L<Squirrel::Template> object.
1155 L<Squirrel::Template>
1159 Tony Cook <tony@develop-help.com>