minimal tests for parse time expression errors and some fixes
authorTony Cook <tony@develop-help.com>
Fri, 31 May 2013 07:55:57 +0000 (17:55 +1000)
committerTony Cook <tony@develop-help.com>
Fri, 31 May 2013 09:43:55 +0000 (19:43 +1000)
MANIFEST
site/cgi-bin/modules/Squirrel/Template/Expr.pm
site/cgi-bin/modules/Squirrel/Template/Parser.pm
t/020-templater/050-error.t [new file with mode: 0644]

index 3a04fb1..ad4a9ff 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -878,6 +878,7 @@ t/020-templater/010-token.t
 t/020-templater/020-parse.t
 t/020-templater/030-expr.t
 t/020-templater/040-original.t
+t/020-templater/050-error.t
 t/020-templater/080-parms.t
 t/030-tags/010-num.t
 t/030-tags/020-iter.t
index 1df9652..1407d61 100644 (file)
@@ -1,7 +1,7 @@
 package Squirrel::Template::Expr;
 use strict;
 
-our $VERSION = "1.011";
+our $VERSION = "1.012";
 
 package Squirrel::Template::Expr::Eval;
 use Scalar::Util ();
@@ -349,7 +349,7 @@ sub _parse_cond {
     my $true = $self->_parse_or($tok);
     my $colon = $tok->get;
     $colon->[0] eq 'op:'
-      or die [ error => "Expected : for ? : operator but found $tok->[1]" ];
+      or die [ error => "Expected : for ? : operator but found $colon->[0]" ];
     my $false = $self->_parse_cond($tok);
 
     $result = [ cond => $result, $true, $false ];
@@ -511,7 +511,7 @@ sub _parse_call {
        # get the real name
        $name = $tok->get;
        $name->[0] eq 'id'
-         or die [ error => "Expected an identifer after .\$ but found $name->[1]" ];
+         or die [ error => "Expected an identifier after .\$ but found $name->[1]" ];
        my $list = [];
        if ($tok->peektype eq 'op(') {
          $list = $self->_parse_paren_list($tok, "method");
@@ -519,7 +519,7 @@ sub _parse_call {
        $result = [ callvar => $name->[2], $result, $list ];
       }
       else {
-       die [ error => "Expected method name or \$var after '.' but found $name->[1]" ];
+       die [ error => "Expected method name or \$var after '.' but found $name->[1]" ];
       }
     }
     elsif ($next eq 'op[') {
@@ -527,7 +527,7 @@ sub _parse_call {
       my $index = $self->_parse_expr($tok);
       my $close = $tok->get;
       $close->[0] eq 'op]'
-       or die [ error => "Expected list end ']' but got $close->[0]" ];
+       or die [ error => "Expected closing ']' but got $close->[0]" ];
       $result = [ subscript => $result, $index ];
     }
     elsif ($next eq 'op(') {
@@ -581,7 +581,7 @@ sub _parse_primary {
     }
     my $close = $tok->get;
     $close->[0] eq 'op]'
-      or die [ error => "Expected ] but got $close->[0]" ];
+      or die [ error => "Expected list end ']' but got $close->[0]" ];
     return [ list => $list ];
   }
   elsif ($t->[0] eq 'op{') {
index 60f2894..a83c0a5 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Template::Parser;
 use strict;
 use Squirrel::Template::Constants qw(:token :node);
 
-our $VERSION = "1.015";
+our $VERSION = "1.016";
 
 use constant TOK => 0;
 use constant TMPLT => 1;
@@ -129,6 +129,7 @@ sub _parse_expr {
 
   my $parser = Squirrel::Template::Expr::Parser->new;
   my $parsed;
+  local $SIG{__DIE__};
   if (eval { $parsed = $parser->parse($expr->[TOKEN_EXPR_EXPR]); 1 }) {
     $expr->[NODE_EXPR_EXPR] = $parsed;
     $expr->[NODE_EXPR_FORMAT] ||= $self->[TMPLT]{def_format};
@@ -149,6 +150,7 @@ sub _parse_stmt {
   my $tokens = Squirrel::Template::Expr::Tokenizer->new($stmt->[TOKEN_EXPR_EXPR]);
   my @list;
   my $parsed;
+  local $SIG{__DIE__};
   my $good = eval {
     push @list, $parser->parse_tokens($tokens);
     while ($tokens->peektype eq "op;") {
@@ -176,6 +178,7 @@ sub _parse_set {
 
   my $parser = Squirrel::Template::Expr::Parser->new;
   my $parsed;
+  local $SIG{__DIE__};
   if (eval { $parsed = $parser->parse($set->[TOKEN_SET_EXPR]); 1 }) {
     $set->[NODE_SET_VAR] = [ split /\./, $set->[TOKEN_SET_VAR] ];
     $set->[NODE_SET_EXPR] = $parsed;
@@ -361,6 +364,7 @@ sub _parse_for {
   }
   my $list_expr;
   my $parser = Squirrel::Template::Expr::Parser->new;
+  local $SIG{__DIE__};
   unless (eval { $list_expr = $parser->parse($for->[TOKEN_FOR_EXPR]); 1 }) {
     return $self->_error($for, "Could not parse list for .for: " . (ref $@ ? $@->[0] : $@));
   }
@@ -470,6 +474,7 @@ sub _parse_call {
   my $error;
   my $parser = Squirrel::Template::Expr::Parser->new;
   my $name_expr;
+  local $SIG{__DIE__};
   unless (eval { $name_expr = $parser->parse_tokens($tokens); 1 }) {
     return $self->_error($call, "Could not parse expression: ".$@->[1]);
   }
@@ -545,6 +550,7 @@ sub _parse_ext_if {
 
   my $parser = Squirrel::Template::Expr::Parser->new;
   for my $cond (@conds) {
+    local $SIG{__DIE__};
     unless (eval { $cond->[2] = $parser->parse($cond->[0][TOKEN_EXT_EXPR]); 1 }) {
       $cond->[2] = [ const => "", "" ];
       push @errors, $self->_error($cond->[0], ref $@ ? $@->[1] : $@);
@@ -576,6 +582,7 @@ sub _parse_ext_while {
 
   my $parser = Squirrel::Template::Expr::Parser->new;
   my $cond_expr;
+  local $SIG{__DIE__};
   unless (eval { $cond_expr = $parser->parse($while->[TOKEN_EXT_EXPR]); 1 }) {
     return $self->_error($while, "Could not parse condition for .while: " . ref $@ ? $@->[0] : $@);
   }
@@ -623,6 +630,7 @@ sub _parse_expr_list {
   my $parser = Squirrel::Template::Expr::Parser->new;
   my @result;
   my $expr;
+  local $SIG{__DIE__};
   unless (eval { $expr = $parser->parse_tokens($tokens); 1 }) {
     $$rerror = $self->_error($token, "Could not parse expression list: ".$@->[1]);
     return;
diff --git a/t/020-templater/050-error.t b/t/020-templater/050-error.t
new file mode 100644 (file)
index 0000000..ccc5e1e
--- /dev/null
@@ -0,0 +1,100 @@
+#!perl -w
+use strict;
+
+# error handling tests
+use Squirrel::Template;
+use Test::More;
+use Carp qw(confess);
+use Data::Dumper;
+
+my @tests =
+  (
+   {
+    name => "missing : in ?:",
+    template => "<:= foo ? bar :>",
+    errors =>
+    [
+     [ error => '', 1, 'test.tmpl', "Expected : for ? : operator but found eof" ],
+    ],
+    result => q/* Expected : for ? : operator but found eof */,
+   },
+   {
+    name => "unrecognized junk after expr",
+    template => "<:= foo ; :>",
+    errors =>
+    [
+     [ error => '', 1, 'test.tmpl', "Expected eof but found op;" ],
+    ],
+    result => q/* Expected eof but found op; */,
+   },
+   {
+    name => "ranges chained",
+    template => "<:= [ 1...3...4 ] :>",
+    errors =>
+    [
+     [ error => '', 1, 'test.tmpl', "Can't use a range as the start of a range" ],
+    ],
+    result => q/* Can't use a range as the start of a range */,
+   },
+   {
+    name => "name.junk",
+    template => "<:= name.; :>",
+    errors =>
+    [
+     [ error => '', 1, 'test.tmpl', q(Expected a method name or $var after '.' but found ;) ],
+    ],
+    result => q/* Expected a method name or $var after '.' but found ; */,
+   },
+   {
+    name => "name.\$junk",
+    template => "<:= name.\$; :>",
+    errors =>
+    [
+     [ error => '', 1, 'test.tmpl', "Expected an identifier after .\$ but found ;" ],
+    ],
+    result => q/* Expected an identifier after .$ but found ; */,
+   },
+   {
+    name => "unterminated list",
+    template => "<:= [ 1 :>",
+    errors =>
+    [
+     [ error => '', 1, 'test.tmpl', "Expected list end ']' but got eof" ],
+    ],
+    result => q/* Expected list end ']' but got eof */,
+   },
+   {
+    name => "unterminated subscript",
+    template => "<:= name[1 :>",
+    errors =>
+    [
+     [ error => '', 1, 'test.tmpl', "Expected closing ']' but got eof" ],
+    ],
+    result => q/* Expected closing ']' but got eof */,
+   },
+  );
+
+plan tests => 3 * scalar(@tests);
+
+my %acts = ();
+
+my %vars = ();
+
+# the following ensures the code isolates evals from __DIE__handlers
+$SIG{__DIE__} = sub { confess @_ };
+
+for my $test (@tests) {
+  my ($name, $template, $want_errors, $want_result) =
+    @$test{qw/name template errors result/};
+
+  my $t = Squirrel::Template->new;
+  my $result;
+  my $good = eval {
+    $result = $t->replace_template($template, \%acts, undef, 'test.tmpl', \%vars);
+    1;
+  };
+  ok($good, "$name: compile and run template");
+  is($result, $want_result, "$name: expected template result");
+  is_deeply([ $t->errors ], $want_errors, "$name: expected errors")
+    or note Dumper([$t->errors]);
+}