diff --git a/src/main/php/lang/ast/nodes/CloneExpression.class.php b/src/main/php/lang/ast/nodes/CloneExpression.class.php new file mode 100755 index 0000000..65f2d66 --- /dev/null +++ b/src/main/php/lang/ast/nodes/CloneExpression.class.php @@ -0,0 +1,20 @@ +arguments= $arguments; + $this->line= $line; + } + + /** @return iterable */ + public function children() { + foreach ($this->arguments as &$expr) { + yield &$expr; + } + } +} \ No newline at end of file diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 8aa88ab..a55ea24 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -13,6 +13,7 @@ CastExpression, CatchStatement, ClassDeclaration, + CloneExpression, ClosureExpression, Comment, Constant, @@ -153,18 +154,8 @@ public function __construct() { if ('(' === $parse->token->value) { $parse->expecting('(', 'invoke expression'); - // Resolve ambiguity by looking ahead: `func(...)` which is a first-class - // callable reference vs. `func(...$it)` - a call with an unpacked argument - if ('...' === $parse->token->value) { - $dots= $parse->token; - $parse->forward(); - if (')' === $parse->token->value) { - $parse->forward(); - return new CallableExpression(new ScopeExpression($scope, $expr, $token->line), $left->line); - } - - array_unshift($parse->queue, $parse->token); - $parse->token= $dots; + if ($this->callable($parse)) { + return new CallableExpression(new ScopeExpression($scope, $expr, $token->line), $token->line); } $arguments= $this->arguments($parse); @@ -189,16 +180,8 @@ public function __construct() { // Resolve ambiguity by looking ahead: `func(...)` which is a first-class // callable reference vs. `func(...$it)` - a call with an unpacked argument - if ('...' === $parse->token->value) { - $dots= $parse->token; - $parse->forward(); - if (')' === $parse->token->value) { - $parse->forward(); - return new CallableExpression($left, $left->line); - } - - array_unshift($parse->queue, $parse->token); - $parse->token= $dots; + if ($this->callable($parse)) { + return new CallableExpression($left, $token->line); } $arguments= $this->arguments($parse); @@ -233,7 +216,6 @@ public function __construct() { $this->prefix('-', 90); $this->prefix('++', 90); $this->prefix('--', 90); - $this->prefix('clone', 90); $this->assignment('='); $this->assignment('&='); @@ -294,6 +276,24 @@ public function __construct() { } }); + $this->prefix('clone', 90, function($parse, $token) { + + // clone $x vs. clone($x) or clone($x, ["id" => 6100]) + if ('(' === $parse->token->value) { + $parse->forward(); + if ($this->callable($parse)) { + return new CallableExpression(new Literal('clone', $token->line), $token->line); + } + + $arguments= $this->arguments($parse); + $parse->expecting(')', 'clone arguments'); + } else { + $arguments= [$this->expression($parse, 90)]; + } + + return new CloneExpression($arguments, $token->line); + }); + $this->prefix('{', 0, function($parse, $token) { $statements= $this->statements($parse); $parse->expecting('}', 'block'); @@ -339,25 +339,16 @@ public function __construct() { // Resolve ambiguity by looking ahead: `new T(...)` which is a first-class // callable reference vs. `new T(...$it)` - a call with an unpacked argument - if ('...' === $parse->token->value) { - $dots= $parse->token; - $parse->forward(); - if (')' === $parse->token->value) { - $parse->forward(); - - if (null === $type) { - $class= $this->class($parse, null); - $class->annotations= $annotations; - $new= new NewClassExpression($class, null, $token->line); - } else { - $new= new NewExpression($type, null, $token->line); - } - - return new CallableNewExpression($new, $token->line); + if ($this->callable($parse)) { + if (null === $type) { + $class= $this->class($parse, null); + $class->annotations= $annotations; + $new= new NewClassExpression($class, null, $token->line); + } else { + $new= new NewExpression($type, null, $token->line); } - array_unshift($parse->queue, $parse->token); - $parse->token= $dots; + return new CallableNewExpression($new, $token->line); } $arguments= $this->arguments($parse); @@ -1725,6 +1716,22 @@ public function class($parse, $name, $comment= null, $modifiers= []) { return $decl; } + public function callable($parse) { + if ('...' === $parse->token->value) { + $dots= $parse->token; + $parse->forward(); + if (')' === $parse->token->value) { + $parse->forward(); + return true; + } + + // Not first-class callable syntax but unpack + array_unshift($parse->queue, $parse->token); + $parse->token= $dots; + } + return false; + } + public function arguments($parse) { $arguments= []; while (')' !== $parse->token->value) { diff --git a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php index e2b9f6a..89569bc 100755 --- a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php @@ -6,6 +6,7 @@ BinaryExpression, Braced, ClassDeclaration, + CloneExpression, InstanceExpression, InstanceOfExpression, InvokeExpression, @@ -115,11 +116,20 @@ public function append_array() { #[Test] public function clone_expression() { $this->assertParsed( - [new UnaryExpression('prefix', new Variable('a', self::LINE), 'clone', self::LINE)], + [new CloneExpression([new Variable('a', self::LINE)], self::LINE)], 'clone $a;' ); } + #[Test] + public function clone_with() { + $with= [[new Literal('"id"', self::LINE), new Literal('6100', self::LINE)]]; + $this->assertParsed( + [new CloneExpression([new Variable('a', self::LINE), new ArrayLiteral($with, self::LINE)], self::LINE)], + 'clone($a, ["id" => 6100]);' + ); + } + #[Test] public function error_suppression() { $this->assertParsed(