The new 2.51 parser design (ditching yacc and relying on perl to handle
operator precedence) is excellent! It allows expressions in all the
right places, avoids problems with reserved words as variables and
lots of other nice things, which resolves numerous latent issues
in 2.0x. Nice work!
I have browsed through the code and I am proposing some new features
plus a few bug fixes. Sorry in advance for the long email.
The significant changes I am proposing are:
- moving "( expression )" from parse_expression to parse_term.
- moving dot-operator processing from parse_node to parse_term.
- adding "+" and "-" as unary operators.
- figuring out exact line numbers in multi-line directive error
messages (experimental).
The first two changes allow any type of term (eg: literal, qw[], [], {},
(expr)) to be used in a dotted expression, eg:
foo.qw[a b c] # -> [foo.a, foo.b, foo.c]
foo.(1+2) # -> foo.3
foo.(bar.baz) # -> foo.${bar.baz}
"string".length # -> 6
Using the (expr) form with the dot operator has the same effect as
${expr} interpolation, but might be slightly more efficient and is
perhaps more natural. The relevant snippets of the new grammar look
like:
expression: UNARYOP expression # UNARYOP is [-+!]
| expression ? expression : expression
| term BINOP expression
| term
term: ufloat
| literal
| quoted
| ( expression )
| ( assignment )
| [ list ]
| qw[ list ] # or qw( ), qw< >, qw{ }, qw| |
| variable
| { hash }
| resource
| term ( . term )+ # only first term does ufloat
variable: node
| node ( args )
node: integer
| ident
| $ident
| ${directive}
integer: [-+]?\d+ # signed decimal
| [-+]?0[xX][\dA-Fa-f]+ # signed hex
ufloat: \d+(\.\d+)?([eE][+-]?\d+)? # integer or float+exponent
| 0[xX][\dA-Fa-f]+ # hex
(There will be some changes necessary elsewhere (eg: stash) to handle
these new operands to the dot operator... Note also that one material
change is that calling parse_variable now only handles a single
node[(args)].)
One subtle bug, fixed below, relates to floating point numbers. Currently,
x.1.23 will incorrectly parse as x.(1.23), rather than x.(1).(23) (ie: it
should be two array indices, 1 followed by 23, not a single number, 1.23).
I've fixed this by splitting a number into two types: float and integer.
Float is the default in parse_term, and float is disabled on the RHS of
the dot operator. So you should be able to write 1.23.list.0 and get the
floating point number 1.23, since 1.23 is parsed as a single float.
Attached are patches to lib/Template/Parser.pm, lib/Parser/Handler.pm,
t/parse02.t and docs/xml/modules/Template/Parser.xml.
Here is a summary of the changes and miscellaneous bug fixes:
- Parser/Handler.pm: whitespace before ";" causes error:
- if ($dir =~ / \G \s* ([^;]+) /gcx) {
+ if ($dir =~ / \G \s* ([^\s;]+) /gcx) {
- Parser/Handler.pm: text after newlines in directive is lost:
- if ($dir =~ / \G \s* ; \s* (.+) /gx) {
+ if ($dir =~ / \G \s* ; \s* (.+) /gsx) {
- Parser/Handler.pm: couple of other experimental changes to track
line numbers within multiline directives, so that error messages
display the correct absolute line number.
- Template/Parser.pm $REGEXEN: added hex numbers, optional signed
exponent, changed number (signed) to ufloat (unsigned float),
added integer and added unaryop ([-+!]).
- Template/Parser.pm $PARENS: added '{' => '\}'
- Template/Parser.pm: variable pushback is problematic since not every
rule checks for presence of a pushback variable. For example,
"(foo !(bar))" does not produce a syntax error and incorrectly
parses as "(! (variable<literal<'bar'>, 0>))" (ie: the foo is pushed
back, but never recovered). I took the liberty of removing pushback,
relying on rewind. (I assume pushback was only an optimization to
avoid reparsing the variable... if not then removing pushback is
a big mistake. Sorry.)
- Template/Parser.pm: added unary operators "-" and "+", grouped
together with "!". Removed optional sign from number (now ufloat),
since it is handled by the unary ops. Unary "-" and "+" allow
expressions like "foo(-x)" or "x = -y", which didn't work before
(and don't work in 2.0x).
- Template/Parser.pm: moved "( expression )" rule from parse_expression
to parse_term. Discussed above.
- Template/Parser.pm: added do_dotop option to parse_term. Set on
recursive calls to parse_term for RHS of dotop. Used to turn off
variable compile, ufloat matching and dotop processing on RHS of dotop.
- Template/Parser.pm: moved parse_node dotop processing into
parse_term.
- Template/Parser.pm: collapsed parse_node into parse_variable. Note
that parse_variable now handles only a single variable[args], since
dotop has moved to parse_term. parse_variable is only called in a
couple of places where the argument is just a simple ident (\w+), so
this should be ok. But if you should call parse_term instead of
parse_variable is you want to parse a dotted variable.
In fact: parse_assign now calls parse_term instead of parse_variable
for the LHS. This allows lists to appear on the LHS. But it also
now allows illegal assignments (eg: "foo" = 1) that will have to be
checked for later. (Oops, I didn't update this changed in the
Parser.xml doc.)
- Template/Parser.pm: changed \s+ to \s* to allow optional (rather
than required) white space after commas in hash, list and args.
- Template/Parser.pm: change to parse_filename to fix possible
bug (but didn't test old or new version...)
- Template/Parser.pm: fixed the same $pre bug as 2.03 on interpolate
"0$foo".
- Template/Parser.pm: changes to sub location() to allow it to return
correct error line number in multi-line directives. Experimental,
not cleaned up and not well tested.
- Template/Parser.pm: needs same fix as 2.0[34] for not removing
last trailing newline with -%] tag.
Regards,
Craig
--- Template-Toolkit-2.51/docs/xml/modules/Template/Parser.xml Mon Jun 25 01:58:03
2001
+++ Template-Toolkit-2.51.new/docs/xml/modules/Template/Parser.xml Sun Jul 1
+22:06:59 2001
@@ -307,8 +307,9 @@
<method name="parse_term">
<args>$text</args>
<args>\$text</args>
- Parse a term which can be a number, literal or interpolated string, a variable,
- hash, list or quoted list.
+ Parse a term which can be an integer, floating point number,
+ literal or interpolated string, a variable, hash, list,
+ quoted list, or an expression in parentheses.
<template>
[% 3.14 %]
[% 'Hello World' %]
@@ -325,24 +326,13 @@
<method name="parse_variable">
<args>$text</args>
<args>\$text</args>
- Parse a variable.
+ Parse the single node of a variable.
<template>
[% foo %]
[% foo(10).bar(20) %]
</template>
</method>
- <method name="parse_node">
- <args>$text</args>
- <args>\$text</args>
- Parse a single node of a variable.
-<template>
-[% foo %]
-[% foo(10) %]
-[% bar(20) %]
-</template>
- </method>
-
<method name="parse_hash">
<args>$text</args>
<args>\$text</args>
@@ -406,8 +396,7 @@
<method name="location">
Returns the line number of the current parser position,
- e.g. "23", or in the case of directives that span multiple
- lines, a string of the form "n-m", e.g. "23-26".
+ e.g. "23".
</method>
<method name="parse_error" args="$error, $directive">
@@ -459,27 +448,27 @@
assignment: variable =>? expression
-expression: ! expression
- | ( expression )
- | ( assignment )
+expression: UNARYOP expression # UNARYOP is [-+!]
| expression ? expression : expression
| term BINOP expression
| term
-term: number
+term: ufloat
| literal
| quoted
- | variable
- | qw[ list ] # or qw( ), qw< >, qw{ }, qw| |
+ | ( expression )
+ | ( assignment )
| [ list ]
+ | qw[ list ] # or qw( ), qw< >, qw{ }, qw| |
+ | variable
| { hash }
+ | resource
+ | term ( . term )+ # only first term does ufloat
-variable: varnode ( . varnode )*
-
-varnode: node
+variable: node
| node ( args )
-node: number
+node: integer
| ident
| $ident
| ${directive}
@@ -504,8 +493,14 @@
ident: \w+
-number: [-+]\d+(\.\d+)?
+integer: [-+]?\d+ # signed decimal
+ | [-+]?0[xX][\dA-Fa-f]+ # signed hex
+
+ufloat: \d+(\.\d+)?([eE][+-]?\d+)? # integer or float+exponent
+ | 0[xX][\dA-Fa-f]+ # hex
+
+resource: \w+:[\w\.\/:]+
</pre>
</p>
</section>
-</module>
\ No newline at end of file
+</module>
--- Template-Toolkit-2.51/lib/Template/Parser/Handler.pm Wed Jun 20 05:37:34
2001
+++ Template-Toolkit-2.51.new/lib/Template/Parser/Handler.pm Sun Jul 1 21:43:31
+2001
@@ -124,7 +124,7 @@
sub text {
my ($self, $parser, $text) = @_;
- my $line = $parser->location();
+ my $line = $parser->location(\$text);
$self->DEBUG($self->ID, "->text('", $self->_inspect_text($text), "') at line
$line\n")
if $DEBUG;
push(@{ $self->{ CONTENT } }, $text);
@@ -146,7 +146,7 @@
my ($self, $parser, $dir) = @_;
my ($keyword, $handler, $object, $method, $args, $result, $gap);
- my $line = $parser->location();
+ my $line = $parser->location(\$dir);
$self->DEBUG($self->ID, "->directive('", $self->_inspect_text($dir), "') at line
$line\n")
if $DEBUG;
@@ -222,11 +222,16 @@
push(@{ $self->{ CONTENT } }, $result);
}
- if ($dir =~ / \G \s* ([^;]+) /gcx) {
+ if ($dir =~ / \G \s* ([^\s;]+) /gcx) {
$parser->parse_warning("ignored trailing cruft in directive: $1");
}
- if ($dir =~ / \G \s* ; \s* (.+) /gx) {
+ if ($dir =~ / \G \s* ; \s* /gx) {
+ $parser->location(\$dir, 1); # update current line count
+ } else {
+ last;
+ }
+ if ($dir =~ / \G (.+) /gsx) {
$dir = $1;
}
else {
--- Template-Toolkit-2.51/lib/Template/Parser.pm Mon Jun 25 02:00:58 2001
+++ Template-Toolkit-2.51.new/lib/Template/Parser.pm Sun Jul 1 22:03:43 2001
@@ -67,7 +67,9 @@
};
my $REGEXEN = {
- number => qr/ \G \s* ([+-]? \d+ (?:\.\d+)? ) /x,
+ ufloat => qr/ \G \s* ( 0[xX][\dA-Fa-f]+
+ | \d+ (?:\.\d+)? (?:[eE][+-]?\d+)? ) /x,
+ integer => qr/ \G \s* ( [+-]? (?: 0[xX][\dA-Fa-f]+ | \d+) ) /x,
literal => qr/ \G \s* ' ( (?:\\'|[^'])* ) ' /x,
quoted => qr/ \G \s* " ( (\\\\|\\"|.|\n)*? ) " /x,
ident => qr/ \G (\w+) /x,
@@ -75,7 +77,8 @@
embed => qr/ \G \${ \s* ([^}]*?) \s* } /x,
filename => qr/ \G ([\w\.\/:]+) /x,
resource => qr/ \G (\w+):([\w\.\/:]+) /x,
- binop => qr/ \G \s* ( [\-\+\*\/%]
+ unaryop => qr/ \G \s* ( [-+!] ) /x,
+ binop => qr/ \G \s* ( [-+*\/%]
| [=!<>]= | [<>] | eq | ne | [lg][et]
| &&? | \|\| | or | and ) \s* /x,
text => qr/ \G ( (?: \\. | [^\$] )+ ) /x,
@@ -86,10 +89,9 @@
'[' => '\]',
'<' => '\>',
'|' => '\|',
+ '{' => '\}',
};
-
-
#------------------------------------------------------------------------
# init(\%config)
#
@@ -175,7 +177,7 @@
# skip any leading whitespace
$$textref =~ / \G \s* /gcx;
- if (defined ($stmt = $self->parse_assign($textref, pushback => 1, no_compile =>
1))) {
+ if (defined ($stmt = $self->parse_assign($textref, no_compile => 1))) {
$self->DEBUG("stmt got an assign: [ $stmt->[0], $stmt->[1] ]\n")
if $DEBUG;
@@ -217,27 +219,16 @@
# skip any leading whitespace
$$textref =~ / \G \s* /gcx;
- # ! expression
- if ($$textref =~ / \G ! \s* /gcx) {
+ # unaryop expression
+ if ($$textref =~ /$REGEXEN->{ unaryop }/gc ) {
+ $op = $1;
+ $self->DEBUG("expr got a unaryop: $op\n")
+ if $DEBUG;
defined ($expr = $self->parse_expression($textref))
|| return;
- $expr = "! $expr";
+ $expr = "$op $expr";
return $expr;
}
- # ( expression )
- elsif ($$textref =~ / \G \( \s* /gcx) {
- if (defined ($term = $self->parse_assign($textref, pushback => 1))) {
- $self->DEBUG("expr got an assign: $term\n")
- if $DEBUG;
- $self->throw('no terminating )')
- unless $$textref =~ / \G \s* \) /gcx;
- }
- elsif (defined ($expr = $self->parse_expression($textref))) {
- $self->throw('no terminating )')
- unless $$textref =~ / \G \s* \) /gcx;
- $term = "($expr)";
- }
- }
elsif (defined ($term = $self->parse_term($textref))) {
$self->DEBUG("expr got a term: $term\n")
if $DEBUG;
@@ -278,7 +269,7 @@
}
return $expr;
-}
+}
#------------------------------------------------------------------------
@@ -300,7 +291,7 @@
# save string position in case we need to backtrack
my $pos = pos $$textref;
- if (defined ($var = $self->parse_variable($textref))) {
+ if (defined ($var = $self->parse_term($textref, in_assign => 1))) {
if ($$textref =~ / \G \s* (=>?) \s* /gcx) {
$op = $1;
@@ -330,21 +321,10 @@
return $assign;
}
else {
- # we check for following whitespace to ensure this really can
- # be passed off as a variable and not something like qw[
- # where 'qw' has been matched but shouldn't have
- if ($options->{ pushback } && $$textref =~ / \G \s+ /gcx) {
- # cache variable
-# $self->DEBUG("not an assign, CACHING\n")
-# if $DEBUG;
- $self->{ _PUSHBACK_VAR } = $var;
- }
- else {
- # rewind string position (backtrack)
-# $self->DEBUG("not an assign, REWINDING\n")
-# if $DEBUG;
- pos $$textref = $pos;
- }
+ # rewind string position (backtrack)
+ $self->DEBUG("not an assign, REWINDING\n")
+ if $DEBUG;
+ pos $$textref = $pos;
}
}
return undef;
@@ -395,20 +375,11 @@
my $textref = ref $text ? $text : \$text;
my $compiler = $self->{ _COMPILER }
|| return $self->throw('no compiler defined');
- my ($term, $args);
-
- # check variable cache for any variable read-ahead (e.g.
- # by looking ahead for an '=' assignment)
- if ($self->{ _PUSHBACK_VAR }
- && defined ($term = $self->parse_variable($textref))) {
- defined ($term = $compiler->variable($term))
- || $self->throw($compiler->error());
- $self->DEBUG("got pushback variable: $term\n") if $DEBUG;
- return $term;
- }
+ my ($term, $args, $gotVariable);
- # term can be an number such as: 5...
- if ($$textref =~ /$REGEXEN->{ number }/gc) {
+ # term can be an unsigned (possibly floating point) number such
+ # as: 5 or 1.61803...
+ if (!$options->{in_dotop} && $$textref =~ /$REGEXEN->{ ufloat }/gc) {
$self->DEBUG("got number: $1\n") if $DEBUG;
defined($term = $compiler->number($1))
|| return $self->error($compiler->error());
@@ -425,6 +396,20 @@
defined($term = $self->parse_interpolate($1))
|| return;
}
+ # ...or an expression in parens, like ( foo + bar )...
+ elsif ($$textref =~ / \G \( \s* /gcx) {
+ if (defined ($term = $self->parse_assign($textref))) {
+ $self->DEBUG("term got an assign: $term\n")
+ if $DEBUG;
+ $self->throw('no terminating )')
+ unless $$textref =~ / \G \s* \) /gcx;
+ }
+ elsif (defined ($term = $self->parse_expression($textref))) {
+ $self->parse_error('no terminating )')
+ unless $$textref =~ / \G \s* \) /gcx;
+ $term = "($term)";
+ }
+ }
# ...or a list: [ 1, two, '3' ]
elsif ($$textref =~ / \G \[ \s* /gcx) {
$term = $self->parse_list($textref);
@@ -463,23 +448,39 @@
# ...or a resource: http://www.example.com/blah.xml
elsif ($$textref =~ /$REGEXEN->{ resource }/gc) {
$self->DEBUG("got resource: ($1) ($2)\n") if $DEBUG;
- return "$1_resource($2)";
+ $term = "$1_resource($2)";
}
- # ...or a variable like: foo.bar(baz)
- elsif (defined ($term = $self->parse_variable($textref))) {
- defined ($term = $compiler->variable($term))
- || $self->throw($compiler->error());
- $self->DEBUG("got variable: $term\n") if $DEBUG;
+ # ...or a variable or function call like: foo or foo(bar)...
+ elsif (defined($term = $self->parse_variable($textref))) {
+ $gotVariable = 1;
}
# ...or it's not a term
else {
return undef;
}
-
+ # look for a following '.' then expect another term
+ if ( !$options->{in_dotop} && $$textref =~ / \G (?=\.[^\.]) /x ) {
+ $term = [$term, 0] unless $gotVariable;
+ $gotVariable = 1;
+ while ($$textref =~ / \G \.(?!\.) /gcx) {
+ my $nextTerm = $self->parse_term($textref, in_dotop => 1);
+ $self->throw("expected term after '.'")
+ unless defined $nextTerm;
+ push(@$term, @$nextTerm);
+ }
+ }
+ if ( $options->{in_dotop} ) {
+ return [$term, 0] if ( !$gotVariable );
+ return $term;
+ } elsif ( $gotVariable && !$options->{in_assign} ) {
+ # compile the variable last time through, unless we're doing an assign
+ defined ($term = $compiler->variable($term))
+ || $self->throw($compiler->error());
+ $self->DEBUG("got variable: $term\n") if $DEBUG;
+ }
return $term;
}
-
#------------------------------------------------------------------------
# parse_variable($self, $text)
#
@@ -493,56 +494,16 @@
my $text = shift;
my $options = ref $_[0] eq 'HASH' ? shift : { @_ };
my $textref = ref $text ? $text : \$text;
-
- if (my $pushback = $self->{ _PUSHBACK_VAR }) {
- $self->{ _PUSHBACK_VAR } = undef;
-# $self->DEBUG("parse_variable() returning pushed back variable: ",
-# $self->_inspect_ref($pushback), "\n")
-# if $DEBUG;
- return $pushback;
- }
+ my ($ident, $args);
my $compiler = $self->{ _COMPILER }
|| return $self->throw('no compiler defined');
- my ($node, @nodes);
# skip any leading whitespace
$$textref =~ / \G \s* /gcx;
- # parse the first node
- $node = $self->parse_node($textref);
- return undef unless defined $node;
- push(@nodes, @$node);
-
- # look for a following '.' then expect another node
- while ($$textref =~ / \G \.(?!\.) /gcx) {
- $node = $self->parse_node($textref);
- $self->throw("expected node after '.'")
- unless defined $node;
- push(@nodes, @$node);
- }
-
- return \@nodes;
-}
-
-
-#------------------------------------------------------------------------
-# parse_node($self, \$text)
-#
-# Parse a TT variable node containing an identifier with an optional
-# argument list in parentheseis, e.g. 'foo' or 'foo(x, y, z(a))'.
-#------------------------------------------------------------------------
-
-sub parse_node {
- my $self = shift;
- my $text = shift;
- my $options = ref $_[0] eq 'HASH' ? shift : { @_ };
- my $textref = ref $text ? $text : \$text;
- my $compiler = $self->{ _COMPILER }
- || return $self->throw('no compiler defined');
- my ($ident, $args);
-
- # node can be a number like '4'...
- if ($$textref =~ /$REGEXEN->{ number }/gc) {
+ # variable is a node, followed by optional arguments
+ # node can be a signed integer number like '4'...
+ if ($$textref =~ /$REGEXEN->{ integer }/gc) {
$self->DEBUG("got number: $1\n") if $DEBUG;
defined($ident = $compiler->number($1))
|| return $self->throw($compiler->error());
@@ -587,7 +548,6 @@
return [ $ident, $args ];
}
-
#------------------------------------------------------------------------
# parse_list($self, \$text)
#
@@ -653,7 +613,7 @@
push(@hash, $key, $value);
# skip comma and/or whitespace
- $$textref =~ / \G (?:\s*,)? \s+ /gcx;
+ $$textref =~ / \G (?:\s*,)? \s* /gcx;
}
else {
$self->throw("missing '=>' after '$key'");
@@ -729,7 +689,7 @@
}
continue {
# skip comma and/or whitespace
- $$textref =~ / \G (?:\s*,)? \s+ /gcx;
+ $$textref =~ / \G (?:\s*,)? \s* /gcx;
}
# trailing comma/whitespace
@@ -772,7 +732,7 @@
defined ($filename = $compiler->literal($1))
|| $self->throw($compiler->error());
}
- elsif ($$textref =~ / \G \$ /gcx) {
+ elsif ($$textref =~ /$REGEXEN->{ interp }/gc) {
defined ($filename = $self->parse_variable($1))
|| return;
defined ($filename = $compiler->variable($filename))
@@ -810,11 +770,11 @@
/gx ) {
# preceding text
- if ($1) {
+ if (defined($1)) {
push(@tokens, "\"$1\"");
}
# $variable reference
- elsif ($2) {
+ elsif (defined($2)) {
push(@tokens, $self->parse_embedded($2) || return);
}
else {
@@ -1230,17 +1190,35 @@
#------------------------------------------------------------------------
-# location()
+# location($textref, $updateDelta)
#
-# Return current parse location in terms of a file number or 'n-m' range.
+# Return current parse location in terms of a line number.
+# Optional arguments specify which text is being parsed, and
+# how many lines to count in case we jump over newlines.
#------------------------------------------------------------------------
sub location {
- my $self = shift;
+ my($self, $textref, $updateDelta) = @_;
my ($line, $linespan) = @$self{ qw( _LINE _LINESPAN ) };
+ my($pos, $deltaLines);
+
+ if ( defined($pos = pos ${$self->{_LINETEXT}}) ) {
+ $deltaLines = (substr(${$self->{_LINETEXT}}, 0, $pos) =~ tr/\n//);
+ }
+ if ( defined($textref) ) {
+ $self->{_LINETEXT} = $textref;
+ if ( defined($updateDelta) && defined($deltaLines) ) {
+ # note: only works for case where $self->{_LINE} is a ref.
+ ${$self->{_LINE}} += $deltaLines;
+ $deltaLines = 0;
+ }
+ }
$line = ref($line) ? $$line : $line;
$linespan = ref($linespan) ? $$linespan : $linespan;
- unless (defined $line) {
+ if ( defined $line && defined $deltaLines ) {
+ $line += $deltaLines;
+ }
+ elsif (!defined $line) {
$line = 'unknown';
}
elsif ($linespan) {
--- Template-Toolkit-2.51/t/parse02.t Sat Jun 23 02:22:06 2001
+++ Template-Toolkit-2.51.new/t/parse02.t Sun Jul 1 22:04:53 2001
@@ -114,7 +114,7 @@
#------------------------------------------------------------------------
# test numbers
-1
-number<-1>
+- number<1>
0
number<0>
@@ -123,16 +123,16 @@
number<1>
+2
-number<+2>
++ number<2>
2.718
number<2.718>
-2.718
-number<-2.718>
+- number<2.718>
+3.14159
-number<+3.14159>
++ number<3.14159>
#------------------------------------------------------------------------
# test literal strings
@@ -254,3 +254,81 @@
http://www.foo.com/bar.xml
http_resource(//www.foo.com/bar.xml)
+
+-x
+- variable<literal<'x'>, 0>
+
+!x
+! variable<literal<'x'>, 0>
+
+----x
+- - - - variable<literal<'x'>, 0>
+
+-1
+- number<1>
+
+foo.bar.$zzz + !!!! -1
+variable<literal<'foo'>, 0, literal<'bar'>, 0, variable<literal<'zzz'>, 0>, 0> + ! !
+! ! - number<1>
+
+1 + 2 + 3 + 4 + 5
+number<1> + number<2> + number<3> + number<4> + number<5>
+
+1 ? 2 : 3 ? 4 : 5 ? 6 : 7
+number<1> ? number<2> : number<3> ? number<4> : number<5> ? number<6> : number<7>
+
+foo.1.2
+variable<literal<'foo'>, 0, number<1>, 0, number<2>, 0>
+
+"1$foo"
+quoted<"1", variable<literal<'foo'>, 0>>
+
+"00$foo"
+quoted<"00", variable<literal<'foo'>, 0>>
+
+"0$foo"
+quoted<"0", variable<literal<'foo'>, 0>>
+
+"$foo"
+quoted<variable<literal<'foo'>, 0>>
+
+"xxxx".length
+variable<quoted<"xxxx">, 0, literal<'length'>, 0>
+
+qw{a b c}.1.2
+variable<[ qw( a b c ) ], 0, number<1>, 0, number<2>, 0>
+
+1.23.list.list.4.5
+variable<number<1.23>, 0, literal<'list'>, 0, literal<'list'>, 0, number<4>, 0,
+number<5>, 0>
+
+foo.qw[a b c]
+variable<literal<'foo'>, 0, [ qw( a b c ) ], 0>
+
+(foo+!(aaa))
+(variable<literal<'foo'>, 0> + ! (variable<literal<'aaa'>, 0>))
+
+-foo
+- variable<literal<'foo'>, 0>
+
+foo + (!bar)
+variable<literal<'foo'>, 0> + (! variable<literal<'bar'>, 0>)
+
+(foo.aaa.bbb + bar)
+(variable<literal<'foo'>, 0, literal<'aaa'>, 0, literal<'bbb'>, 0> +
+variable<literal<'bar'>, 0>)
+
+foo.$bar.aaa
+variable<literal<'foo'>, 0, variable<literal<'bar'>, 0>, 0, literal<'aaa'>, 0>
+
+foo(1,2).$bar(3,4)
+variable<literal<'foo'>, [ number<1>, number<2> ], variable<literal<'bar'>, 0>, [
+number<3>, number<4> ]>
+
+foo.(1+2)
+variable<literal<'foo'>, 0, (number<1> + number<2>), 0>
+
+foo(1,2).(bar(3 4))
+variable<literal<'foo'>, [ number<1>, number<2> ], (variable<literal<'bar'>, [
+number<3>, number<4> ]>), 0>
+
+[12e4, 12.3e-4, 12.3e+4, 0x0, 0xaBcDeF]
+[ number<12e4>, number<12.3e-4>, number<12.3e+4>, number<0x0>, number<0xaBcDeF> ]
+
+foo(-x, +y)
+variable<literal<'foo'>, [ - variable<literal<'x'>, 0>, + variable<literal<'y'>, 0> ]>