xp-framework / compiler

Compiles future PHP to today's PHP.
19 stars 0 forks source link

(fstat(STDOUT)) causes a parse error #98

Closed thekid closed 3 years ago

thekid commented 3 years ago

Exception lang.ast.Errors (Errors { Expected ")", have ";" in braced at line 3, Missing semicolon after operator statement at line 3})

$ git diff
--- a/src/test/php/lang/ast/unittest/emit/BracesTest.class.php
+++ b/src/test/php/lang/ast/unittest/emit/BracesTest.class.php
@@ -93,6 +93,17 @@ class BracesTest extends EmittingTest {
     Assert::equals($expected, $r);
   }

+  #[Test]
+  public function function_call_in_braces() {
+    $r= $this->run('class <T> {
+      public function run() {
+        return false !== (fstat(STDOUT));
+      }
+    }');
+
+    Assert::true($r);
+  }
+
thekid commented 3 years ago

This is in the AST library, inside the following block:

// This is ambiguous:
//
// - An expression surrounded by parentheses `($a ?? $b)->invoke()`;
// - A cast `(int)$a` or `(int)($a / 2)`.
//
// Resolve by looking ahead after the closing ")"
$this->prefix('(', 0, function($parse, $token) {
  ...
});
thekid commented 3 years ago

This fixes is but isn't satisfactory:

--- a/src/main/php/lang/ast/syntax/PHP.class.php
+++ b/src/main/php/lang/ast/syntax/PHP.class.php
@@ -222,7 +222,6 @@ class PHP extends Language {

     // This is ambiguous:
     //
-    // - An arrow function `($a) ==> $a + 1`
     // - An expression surrounded by parentheses `($a ?? $b)->invoke()`;
     // - A cast `(int)$a` or `(int)($a / 2)`.
     //
@@ -262,7 +261,14 @@ class PHP extends Language {
         $parse->forward();
         $parse->expecting('(', 'braced');
         $expr= $this->expression($parse, 0);
-        $parse->expecting(')', 'braced');
+
+        // See https://github.com/xp-framework/compiler/issues/98, unsure why we are
+        // short one ')' in this case though.
+        if (';' === $parse->token->value) {
+          $parse->queue= [$parse->token];
+        } else {
+          $parse->expecting(')', 'braced');
+        }
         return new Braced($expr, $token->line);
       }
     });
thekid commented 3 years ago

Fix released in https://github.com/xp-framework/ast/releases/tag/v7.0.1 after this happened last night:

Brain of a developer

thekid commented 3 years ago

Oops, this fix wasn't the right one after all!

This parses without problems:

public function run() {
  $e= STDOUT;
  return false !== (fstat($e));
}

0: [lang.ast.Token(kind= operator, value= "("), lang.ast.Token(kind= name, value= "fstat")]
// 1: [`(`, `fstat`, `(`, `$e`, `)`, `)`, `;`]
// Q: [lang.ast.Token(kind= operator, value= ";")]
// T: lang.ast.Token(kind= operator, value= ")")
// E: lang.ast.nodes.InvokeExpression {
//   kind => "invoke"
//   expression => lang.ast.nodes.Literal {
//     kind => "literal"
//     expression => "fstat"
//     line => 4
//   }
//   arguments => [lang.ast.nodes.Variable {
//     kind => "variable"
//     name => "e"
//     line => 4
//   }]
//   line => 4
// }

This causes the problems:

public function run() {
  return false !== (fstat(STDOUT));
}

// 0: [lang.ast.Token(kind= operator, value= "("), lang.ast.Token(kind= name, value= "fstat")]
// 1: [`(`, `fstat`, `(`, `STDOUT`, `)`, `)`, `;`]
// Q: [lang.ast.Token(kind= operator, value= ")")]
// T: lang.ast.Token(kind= operator, value= ";")
// E: lang.ast.nodes.InvokeExpression {
//   kind => "invoke"
//   expression => lang.ast.nodes.Literal {
//     kind => "literal"
//     expression => "fstat"
//     line => 4
//   }
//   arguments => [lang.ast.nodes.Literal {
//     kind => "literal"
//     expression => "STDOUT"
//     line => 4
//   }]
//   line => 4
// }

This points us to the fact that the above fix wasn't the correct place after all, the look-ahead code is working perfectly, somehow the argument parser is incorrect, though!

thekid commented 3 years ago

Really fixed by https://github.com/xp-framework/ast/releases/tag/v7.0.1 - unittest included in XP Compiler, v6.1.0