From 0468d7cc3237ae4d9ac0e55d8507931cc4758a1e Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 2 Aug 2024 21:40:30 +0200 Subject: [PATCH 1/3] Add syntactic support for `A::class --- src/main/php/lang/ast/syntax/PHP.class.php | 36 ++++++++++++++++++- .../ast/unittest/parse/MembersTest.class.php | 10 +++++- .../ast/unittest/parse/OperatorTest.class.php | 13 +++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index d2ce08b..a62c7e8 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -171,7 +171,7 @@ public function __construct() { $expr= new InvokeExpression($expr, $arguments, $token->line); } - return new ScopeExpression($scope, $expr, $left->line); + return new ScopeExpression($scope, $expr, $token->line); }); $this->infix('(', 100, function($parse, $token, $left) { @@ -482,6 +482,40 @@ public function __construct() { }); $this->prefix('(name)', 0, function($parse, $token) { + static $types= ['(' => 1, ')' => 1, ',' => 1, '?' => 1, ':' => 1, '|' => 1, '&' => 1]; + + // Disambiguate `Class` from `const < expr` by looking ahead + if ('<' === $parse->token->value) { + $generic= true; + $level= 1; + $skipped= [$parse->token]; + while ($level > 0) { + $parse->forward(); + $skipped[]= $parse->token; + + if ('<' === $parse->token->symbol->id) { + $level++; + } else if ('token->symbol->id) { + $level++; + } else if ('>' === $parse->token->symbol->id) { + $level--; + } else if ('>>' === $parse->token->symbol->id) { + $level-= 2; + } else if ('name' !== $parse->token->kind && !isset($types[$parse->token->value])) { + $generic= false; + break; + } + } + + $parse->queue= $parse->queue ? array_merge($skipped, $parse->queue) : $skipped; + if ($generic) { + $parse->token= $token; + return $this->type($parse, false); + } + + $parse->forward(); + } + return new Literal($token->value, $token->line); }); diff --git a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php index ae8c2e5..b24d634 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -19,7 +19,7 @@ Variable, Parameter }; -use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue}; +use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue, IsGeneric}; use test\{Assert, Test, Values}; class MembersTest extends ParseTest { @@ -297,6 +297,14 @@ public function class_resolution() { ); } + #[Test] + public function generic_class_resolution() { + $this->assertParsed( + [new ScopeExpression(new IsGeneric(new IsValue('\\A'), [new IsValue('\\T')]), new Literal('class', self::LINE), self::LINE)], + 'A::class;' + ); + } + #[Test] public function instance_method_invocation() { $this->assertParsed( 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 474a072..6040b96 100755 --- a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php @@ -332,4 +332,17 @@ public function multiple_semicolons() { ';; $a= 1 ;;; $b= 2;' ); } + + #[Test] + public function const_less_than_const() { + $this->assertParsed( + [new BinaryExpression( + new Literal('a', self::LINE), + '<', + new Literal('b', self::LINE), + self::LINE + )], + 'a < b;' + ); + } } \ No newline at end of file From 9936f812b983ad109734b8206da7b7948bf59858 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 4 Aug 2024 09:13:12 +0200 Subject: [PATCH 2/3] Test instanceof operator with generics --- .../lang/ast/unittest/parse/OperatorTest.class.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 6040b96..e2b9f6a 100755 --- a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php @@ -18,7 +18,7 @@ UnaryExpression, Variable }; -use lang\ast\types\{IsExpression, IsValue}; +use lang\ast\types\{IsExpression, IsGeneric, IsValue}; use test\{Assert, Test, Values}; class OperatorTest extends ParseTest { @@ -283,6 +283,18 @@ public function precedence_of_not_and_instance_of() { ); } + #[Test] + public function instanceof_generic() { + $this->assertParsed( + [new InstanceOfExpression( + new Variable('this', self::LINE), + new IsGeneric(new IsValue('self'), [new IsValue('T')]), + self::LINE + )], + '$this instanceof self;' + ); + } + #[Test, Values(['+', '-', '~'])] public function precedence_of_prefix($operator) { $this->assertParsed( From 9467b8764f82cabc98f56065ae142bc214765c7b Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 4 Aug 2024 20:07:56 +0200 Subject: [PATCH 3/3] Replace array_merge() with array spread operators --- src/main/php/lang/ast/syntax/PHP.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index a62c7e8..817cd3c 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -264,7 +264,7 @@ public function __construct() { $parse->forward(); $skipped[]= $parse->token; } - $parse->queue= $parse->queue ? array_merge($skipped, $parse->queue) : $skipped; + $parse->queue= $parse->queue ? [...$skipped, ...$parse->queue] : $skipped; if ($cast && ('operator' !== $parse->token->kind || '(' === $parse->token->value || '[' === $parse->token->value)) { $parse->forward(); @@ -507,7 +507,7 @@ public function __construct() { } } - $parse->queue= $parse->queue ? array_merge($skipped, $parse->queue) : $skipped; + $parse->queue= $parse->queue ? [...$skipped, ...$parse->queue] : $skipped; if ($generic) { $parse->token= $token; return $this->type($parse, false);