template re-work: implement .call and .define
authorTony Cook <tony@develop-help.com>
Wed, 4 Apr 2012 02:11:48 +0000 (12:11 +1000)
committerTony Cook <tony@develop-help.com>
Fri, 20 Apr 2012 01:05:47 +0000 (11:05 +1000)
Changes.txt
MANIFEST
site/cgi-bin/modules/Squirrel/Template.pm
site/cgi-bin/modules/Squirrel/Template/Constants.pm
site/cgi-bin/modules/Squirrel/Template/Expr.pm
site/cgi-bin/modules/Squirrel/Template/Parser.pm
site/cgi-bin/modules/Squirrel/Template/Processor.pm
site/cgi-bin/modules/Squirrel/Template/Tokenizer.pm
t/t010template.t
t/templates/called.tmpl [new file with mode: 0644]

index b1e0cb4e0adc17788c5eabd6c4baf843d2e6a689..70ea4a83522c349d5e67eb7270107646eeeabcfe 100644 (file)
@@ -1,3 +1,15 @@
+Template re-re-work:
+
+ - new, faster, more regular processing internal to tags:
+   - variables supplied by code, similarly to TT, Mason
+   - macro definitions
+   - call macros or files with parameters and localized variables
+
+  TODO:
+    - documentation
+    - integration into BSE template processing
+    - preloaded template
+
 BSE 0.22 - unreleased
 ========
 
index c6d458beda5a2006112f81918b745a41712f8543..6e15618a604a960144b03baef0391a5d679e1fba 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -862,6 +862,7 @@ t/templater/00load.t
 t/templater/10token.t
 t/templater/20parse.t
 t/templater/30expr.t
+t/templates/called.tmpl
 t/templates/included.include   Used by t010template.t
 t/templates/included.recursive
 t/templates/wrapinner.tmpl
index 05a7758fd7bebe67cb4fff08432e06fcec3cdabe..fb60306343888af5535f1d6ef335261d58ec03ff 100644 (file)
@@ -17,7 +17,7 @@ BEGIN {
 
 use constant DEBUG_GET_PARMS => 0;
 
-our $VERSION = "1.014";
+our $VERSION = "1.015";
 
 my $tag_head = qr/(?:\s+<:-|<:-?)/;
 my $tag_tail = qr/(?:-:>\s*|:>)/;
@@ -350,6 +350,23 @@ sub set_var {
   $self->{scopes}[-1]{$name} = $value;
 }
 
+sub define_macro {
+  my ($self, $name, $content) = @_;
+
+  $self->{defines}{$name} = $content;
+
+  return 1;
+}
+
+sub get_macro {
+  my ($self, $name, $content) = @_;
+
+  my $content = $self->{defines}{$name}
+    or return;
+
+  return $content;
+}
+
 sub parse {
   my ($self, $template, $name) = @_;
 
@@ -425,12 +442,14 @@ sub parse_file {
 sub replace {
   my ($self, $parsed, $acts, $vars) = @_;
 
-  $self->{errors} = [];
+  local $self->{errors} = [];
 
-  $self->{scopes} = [];
+  local $self->{scopes} = [];
   push @{$self->{scopes}}, $vars if $vars;
   push @{$self->{scopes}}, { globals => {} };
 
+  local $self->{defines} = {};
+
   my $oldparam_tag = $acts->{param};
   local $acts->{param} = $oldparam_tag || [ tag_param => $self ];
 
index 8844274bbb1f1c504463eb9b2bc25e608de5b1b9..33efe08570ef615fc153af0fe26d2c6587d362e9 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Template::Constants;
 use strict;
 use Exporter qw(import);
 
-our $VERSION = "1.002";
+our $VERSION = "1.003";
 
 sub _define_sequence {
   my ($keys, $start) = @_;
@@ -25,6 +25,8 @@ my @token_expr = qw(TOKEN_EXPR_EXPR);
 _define_sequence(\@token_expr, 4);
 my @token_set = qw(TOKEN_SET_VAR TOKEN_SET_EXPR);
 _define_sequence(\@token_set, 4);
+my @token_end = qw(TOKEN_END_TYPE);
+_define_sequence(\@token_end, 4);
 
 my @node_base = qw(NODE_TYPE NODE_ORIG NODE_LINE NODE_FILENAME NODE_TAG_NAME NODE_TAG_ARGS);
 _define_sequence(\@node_base, 0);
@@ -46,14 +48,20 @@ my @node_expr = qw(NODE_EXPR_EXPR NODE_EXPR_FORMAT);
 _define_sequence(\@node_expr, 4);
 my @node_set = qw(NODE_SET_VAR NODE_SET_EXPR);
 _define_sequence(\@node_set, 4);
+my @node_define = qw(NODE_DEFINE_NAME NODE_DEFINE_END NODE_DEFINE_CONTENT);
+_define_sequence(\@node_define, 4);
+my @node_call = qw(NODE_CALL_NAME NODE_CALL_LIST);
+_define_sequence(\@node_call, 4);
 
 our %EXPORT_TAGS =
   (
-   token => [ @token_base, @token_generic, @token_error, @token_expr, @token_set ],
+   token => [ @token_base, @token_generic, @token_error, @token_expr,
+             @token_set, @token_end ],
    node =>
    [
     @node_base, @node_iter, @node_cond, @node_comp, @node_with,
-    @node_wrap, @node_switch, @node_error, @node_expr, @node_set
+    @node_wrap, @node_switch, @node_error, @node_expr, @node_set,
+    @node_define, @node_call,
    ],
   );
 
index e2b4c5b0fad0226e3fd43dfbb14dfa94cec3fee5..53a7d2da765050cf93985dfad2671df547d22ec4 100644 (file)
@@ -1,7 +1,7 @@
 package Squirrel::Template::Expr;
 use strict;
 
-our $VERSION = "1.001";
+our $VERSION = "1.002";
 
 package Squirrel::Template::Expr::Eval;
 use Scalar::Util ();
@@ -256,7 +256,14 @@ sub parse {
   my ($self, $text) = @_;
 
   my $tokenizer = Squirrel::Template::Expr::Tokenizer->new($text);
-  return $self->_parse_expr($tokenizer);
+  my $result = $self->_parse_expr($tokenizer);
+
+  my $last = $tokenizer->get;
+  unless ($last->[0] eq 'eof') {
+    die [ error => "Expected eof but found $last->[0]" ];
+  }
+
+  return $result;
 }
 
 sub parse_tokens {
index 70008cc6da35bdb52bad7f7e4df4e765df566a5a..63eea2b373ad923cc600262555bea507e19462d3 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Template::Parser;
 use strict;
 use Squirrel::Template::Constants qw(:token :node);
 
-our $VERSION = "1.007";
+our $VERSION = "1.008";
 
 use constant TOK => 0;
 use constant TMPLT => 1;
@@ -59,6 +59,9 @@ sub _parse_content {
     elsif ($type eq 'set') {
       push @result, $self->_parse_set($token);
     }
+    elsif ($type eq 'call') {
+      push @result, $self->_parse_call($token);
+    }
     elsif ($type eq 'if') {
       push @result, $self->_parse_if($token);
     }
@@ -80,6 +83,9 @@ sub _parse_content {
     elsif ($type eq 'error') {
       push @result, $self->_parse_error($token);
     }
+    elsif ($type eq 'define') {
+      push @result, $self->_parse_define($token);
+    }
     elsif ($type eq 'comment') {
       # discard comments
     }
@@ -408,7 +414,7 @@ sub _parse_wrap {
 
   my $content = $self->_parse_content;
   my $end = $self->[TOK]->get;
-  $end or $DB::single = 1;
+
   my $error;
   if ($end->[TOKEN_TYPE] eq 'endwrap') {
     # nothing to do
@@ -430,6 +436,78 @@ sub _parse_wrap {
   }
 }
 
+sub _parse_define {
+  my ($self, $define) = @_;
+
+  my $content = $self->_parse_content;
+  my $end = $self->[TOK]->get;
+  my $error;
+  if ($end->[TOKEN_TYPE] eq 'end') {
+    if ($end->[TOKEN_END_TYPE] && $end->[TOKEN_END_TYPE] ne 'define') {
+      $error = $self->_error($end, "Expected '.end' or '.end define' for .define started $define->[TOKEN_FILENAME]:$define->[TOKEN_LINE] but found '.end $end->[TOKEN_END_TYPE]'");
+    }
+  }
+  else {
+    $self->[TOK]->unget($end);
+    $error = $self->_error($end, "Expected '.end' for .define started $define->[TOKEN_FILENAME]:$define->[TOKEN_LINE] but found $end->[TOKEN_TYPE]");
+    $end = $self->_empty($end);
+  }
+  @{$define}[NODE_DEFINE_END, NODE_DEFINE_CONTENT] = ( $end, $content );
+
+  if ($error) {
+    return $self->_comp($define, $error);
+  }
+  else {
+    return $define;
+  }
+}
+
+sub _parse_call {
+  my ($self, $call) = @_;
+
+  my $tokens = Squirrel::Template::Expr::Tokenizer->new($call->[TOKEN_EXPR_EXPR]);
+
+  my $error;
+  my $parser = Squirrel::Template::Expr::Parser->new;
+  my $name_expr;
+  unless (eval { $name_expr = $parser->parse_tokens($tokens); 1 }) {
+    return $self->_error($call, "Could not parse expression: ".$@->[1]);
+  }
+
+  my @result;
+  my $next = $tokens->get;
+  my @args;
+  if ($next->[0] eq 'op,') {
+    unless (eval {
+      while ($next->[0] eq 'op,') {
+       my $key;
+       my $value;
+       $key = $parser->parse_tokens($tokens);
+       my $colon = $tokens->get;
+       $colon->[0] eq 'op:'
+         or die [ error => "Expected : but found $colon->[0]" ];
+       $value = $parser->parse_tokens($tokens);
+       push @args, [ $key, $value ];
+       $next = $tokens->get;
+      }
+
+      if ($next->[0] ne 'eof') {
+       die [ error => "Expected , or eof but found $next->[0]" ];
+      }
+      1;
+    }) {
+      return $self->_error($call, ref $@ ? $@->[0] : $@);
+    }
+  }
+  elsif ($next->[0] ne 'eof') {
+    $error = $self->_error($call, "Expected , or end of expression but found $next->[0]");
+  }
+
+  @{$call}[NODE_CALL_NAME, NODE_CALL_LIST] = ( $name_expr, \@args );
+
+  return $error ? $self->_comp($call, $error) : $call;
+}
+
 sub _parse_error {
   my ($self, $error) = @_;
 
index 2a634c5fe7a977c741d8d0762f3a610796679cb5..0ddf1d4185536d0e74a7469656d77a323374fdce 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Template::Processor;
 use strict;
 use Squirrel::Template::Constants qw(:node);
 
-our $VERSION = "1.009";
+our $VERSION = "1.010";
 
 use constant ACTS => 0;
 use constant TMPLT => 1;
@@ -137,6 +137,47 @@ sub _process_set {
   return @errors;
 }
 
+sub _process_define {
+  my ($self, $define) = @_;
+
+  $self->[TMPLT]->define_macro($define->[NODE_DEFINE_NAME], $define->[NODE_DEFINE_CONTENT]);
+
+  return;
+}
+
+sub _process_call {
+  my ($self, $node) = @_;
+
+  my $parsed;
+  my %args;
+  my @result;
+  if (eval {
+    my $name = $self->[EVAL]->process($node->[NODE_CALL_NAME]);
+    for my $arg (@{$node->[NODE_CALL_LIST]}) {
+      my $key = $self->[EVAL]->process($arg->[0]);
+      my $value = $self->[EVAL]->process($arg->[1]);
+      $args{$key} = $value;
+    }
+
+    $parsed = $self->[TMPLT]->get_macro($name);
+    if (!$parsed) {
+      ($parsed, my $message) = $self->[TMPLT]->parse_file($name);
+      $parsed
+       or die "ENOIMPL - $name not found\n";
+    }
+    1;
+  }) {
+    $self->[TMPLT]->start_scope(\%args);
+    @result = $self->process($parsed);
+    $self->[TMPLT]->end_scope();
+  }
+  else {
+    @result = $node->[NODE_ORIG];
+  }
+
+  return @result;
+}
+
 sub _process_error {
   my ($self, $node) = @_;
 
index 0258f51e327d9c1a1253116dad24c548217acc25..0be4965d45963132857640e965e6805759d4a1c1 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Template::Tokenizer;
 use strict;
 use Squirrel::Template::Constants qw(:token);
 
-our $VERSION = "1.005";
+our $VERSION = "1.006";
 
 use constant QUEUE => 0;
 use constant TEXT => 1;
@@ -103,8 +103,8 @@ sub get {
       elsif ($body =~ /\A\.end(?:\s+(\w+))?\z/) {
        push @$queue, [ end => $tag, $line, $name, defined $1 ? $1 : "" ];
       }
-      elsif ($body =~ /\A\.call\s+([^,]+)(?:\s*,\s*(\S.*))?\z/) {
-       push @$queue, [ call => $tag, $line, $name, $1, defined $2 ? $2 : "" ];
+      elsif ($body =~ /\A\.call\s+(\S.*)?\z/) {
+       push @$queue, [ call => $tag, $line, $name, $1 ];
       }
       elsif ($body =~ /\Aiterator\s+begin\s+(\w+)\s*(?:\s+(\S.*))?\z/s) {
        push @$queue, [ itbegin => $tag, $line, $name, $1, defined $2 ? $2 : '' ];
index 0bee734cfd1a93f0399221843cc0d016a5630754..ca7305a794918422c6f9aedcf197e1c5735e620a 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 # Basic tests for Squirrel::Template
 use strict;
-use Test::More tests => 99;
+use Test::More tests => 102;
 
 sub template_test($$$$;$$);
 
@@ -453,8 +453,25 @@ OUT
 
     template_test("<:= $expr :>", $result, "expr: $expr", \%acts, "", \%vars);
   }
+
+  template_test(<<IN, "", "define no use", \%acts, "both", \%vars);
+<:-.define foo:>
+<:.end-:>
+<:-.define bar:>
+<:.end define-:>
+IN
+  template_test(<<IN, "avalue", "define with call", \%acts, "both", \%vars);
+<:-.define foo:>
+<:-= avar -:>
+<:.end-:>
+<:.call "foo", "avar":"avalue"-:>
+IN
+  template_test(<<IN, "other value", "external call", \%acts, "", \%vars);
+<:.call "called.tmpl", "avar":"other value"-:>
+IN
 }
 
+
 sub template_test ($$$$;$$) {
   my ($in, $out, $desc, $acts, $stripnl, $vars) = @_;
 
diff --git a/t/templates/called.tmpl b/t/templates/called.tmpl
new file mode 100644 (file)
index 0000000..c1244c2
--- /dev/null
@@ -0,0 +1 @@
+<:= avar -:>