Fix listop-hash-infix parsing
authorFather Chrysostomos <sprout@cpan.org>
Wed, 12 Sep 2012 21:03:08 +0000 (14:03 -0700)
committerFather Chrysostomos <sprout@cpan.org>
Wed, 12 Sep 2012 23:23:50 +0000 (16:23 -0700)
With some list operators, this happens:

$ ./miniperl -e 'warn({$_ => 1} + 1) if 0'
syntax error at -e line 1, near "} +"
Execution of -e aborted due to compilation errors.

Putting + before the { or changing warn to print makes the prob-
lem go away.

The lexer is losing track of what token it expects next, so it ends
up interpreting the + as a unary plus, instead of an infix plus.  The
parser doesn’t like that.

It happens because of this logic under case '{' (aka leftbracket:) in
toke.c:yylex:

switch (PL_expect) {
case XTERM:
    if (PL_oldoldbufptr == PL_last_lop)
PL_lex_brackstack[PL_lex_brackets++] = XTERM;
    else
PL_lex_brackstack[PL_lex_brackets++] = XOPERATOR;
    PL_lex_allbrackets++;
    OPERATOR(HASHBRACK);

The value we put on the brackstack is what we expect to find after the
closing brace (case '}' pops it off).

This particular if/else goes all the back to ef6361f9c226 (perl
5.000), or at least that was when it moved inside the XTERM case.
Before that, we had this:

if (oldoldbufptr == last_lop)
    lex_brackstack[lex_brackets++] = XTERM;
else
    lex_brackstack[lex_brackets++] = XOPERATOR;
if (expect == XTERM)
    OPERATOR(HASHBRACK);

So it appears that the XTERM/XOPERATOR distinction, based on last_lop
was the ‘old’ (and wrong) way of doing it, but it had to be changed in
perl 5.000 for cases other than XTERM.  That it remained for XTERM was
probably an oversight, which is easy to understand, since I seem to be
the first one to stumble across this after 18 years (what’s the rele-
vant Klortho number?).

Removing this last_lop check causes no tests to fail.  And it makes
sense, since anything coming right after an anonymous hash that could
be either an infix or prefix operator must be infix.

t/base/lex.t
toke.c

index 93985e7..dc4abe5 100644 (file)
@@ -1,6 +1,6 @@
 #!./perl
 
-print "1..74\n";
+print "1..75\n";
 
 $x = 'x';
 
@@ -367,3 +367,8 @@ print "$_ - s//\${\\%x}{3}/e\n";
 eval 's/${foo#}//e';
 print "not " unless $@;
 print "ok 74 - s/\${foo#}//e\n";
+
+eval 'warn ({$_ => 1} + 1) if 0';
+print "not " if $@;
+print "ok 75 - listop({$_ => 1} + 1)\n";
+print "# $@" if $@;
diff --git a/toke.c b/toke.c
index 1db835c..d6ac752 100644 (file)
--- a/toke.c
+++ b/toke.c
@@ -5753,10 +5753,7 @@ Perl_yylex(pTHX)
        }
        switch (PL_expect) {
        case XTERM:
-           if (PL_oldoldbufptr == PL_last_lop)
-               PL_lex_brackstack[PL_lex_brackets++] = XTERM;
-           else
-               PL_lex_brackstack[PL_lex_brackets++] = XOPERATOR;
+           PL_lex_brackstack[PL_lex_brackets++] = XOPERATOR;
            PL_lex_allbrackets++;
            OPERATOR(HASHBRACK);
        case XOPERATOR: