From e341fa777f52e59764e54f86a643e1ad283c1d85 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 7 Mar 2021 22:28:55 +0100 Subject: [PATCH 1/9] Initial support for PHP enums --- .../php/lang/ast/nodes/EnumCase.class.php | 13 +++++ .../lang/ast/nodes/EnumDeclaration.class.php | 18 +++++++ src/main/php/lang/ast/syntax/PHP.class.php | 52 +++++++++++++++++++ .../ast/unittest/parse/TypesTest.class.php | 26 +++++++++- 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100755 src/main/php/lang/ast/nodes/EnumCase.class.php create mode 100755 src/main/php/lang/ast/nodes/EnumDeclaration.class.php diff --git a/src/main/php/lang/ast/nodes/EnumCase.class.php b/src/main/php/lang/ast/nodes/EnumCase.class.php new file mode 100755 index 0000000..24a3291 --- /dev/null +++ b/src/main/php/lang/ast/nodes/EnumCase.class.php @@ -0,0 +1,13 @@ +name= $name; + $this->line= $line; + } +} \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/EnumDeclaration.class.php b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php new file mode 100755 index 0000000..ff4e32f --- /dev/null +++ b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php @@ -0,0 +1,18 @@ +parent= $parent; + $this->implements= $implements; + } + + public function parent() { return $this->parent; } + + public function interfaces() { return $this->implements; } +} \ 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 b5b3809..00b78e7 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -14,6 +14,8 @@ ContinueStatement, DoLoop, EchoStatement, + EnumCase, + EnumDeclaration, ForLoop, ForeachLoop, FunctionDeclaration, @@ -860,6 +862,56 @@ public function __construct() { return $decl; }); + $this->stmt('enum', function($parse, $token) { + $name= $parse->scope->resolve($parse->token->value); + $parse->forward(); + $comment= $parse->comment; + $parse->comment= null; + + $parent= null; + if ('extends' === $parse->token->value) { + $parse->forward(); + $parent= $parse->scope->resolve($parse->token->value); + $parse->forward(); + } + + $implements= []; + if ('implements' === $parse->token->value) { + $parse->forward(); + do { + $implements[]= $parse->scope->resolve($parse->token->value); + $parse->forward(); + if (',' === $parse->token->value) { + $parse->forward(); + continue; + } else if ('{' === $parse->token->value) { + break; + } else { + $parse->expecting(', or {', 'interfaces list'); + } + } while (null !== $parse->token->value); + } + + $decl= new EnumDeclaration([], $name, $parent, $implements, [], [], $comment, $token->line); + $parse->expecting('{', 'enum'); + $decl->body= $this->typeBody($parse, $decl->name); + $parse->expecting('}', 'enum'); + + return $decl; + }); + + $this->body('case', function($parse, &$body, $annotations, $modifiers, $holder) { + $line= $parse->token->line; + + $parse->forward(); + $name= $parse->token->value; + + $parse->forward(); + $parse->expecting(';', 'case'); + + $body[$name]= new EnumCase($name, $line); + }); + $this->body('use', function($parse, &$body, $annotations, $modifiers, $holder) { $line= $parse->token->line; diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index 185000f..e0cb0da 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -1,7 +1,15 @@ assertParsed( + [new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE)], + 'enum A { }' + ); + } + + #[Test] + public function enum_with_cases() { + $enum= new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE); + $enum->body['ONE']= new EnumCase('ONE', self::LINE); + $enum->body['TWO']= new EnumCase('TWO', self::LINE); + $this->assertParsed([$enum], 'enum A { case ONE; case TWO; }'); + } + #[Test] public function class_with_trait() { $class= new ClassDeclaration([], '\\A', null, [], [], [], null, self::LINE); From 21e66a48968f320cad3917b3fc8b60cf48e791b7 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 7 Mar 2021 22:32:35 +0100 Subject: [PATCH 2/9] Make EnumCase implement Member interface --- src/main/php/lang/ast/nodes/EnumCase.class.php | 5 ++++- src/main/php/lang/ast/syntax/PHP.class.php | 1 + src/test/php/lang/ast/unittest/parse/TypesTest.class.php | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/php/lang/ast/nodes/EnumCase.class.php b/src/main/php/lang/ast/nodes/EnumCase.class.php index 24a3291..e77326d 100755 --- a/src/main/php/lang/ast/nodes/EnumCase.class.php +++ b/src/main/php/lang/ast/nodes/EnumCase.class.php @@ -2,7 +2,7 @@ use lang\ast\Node; -class EnumCase extends Node { +class EnumCase extends Node implements Member { public $kind= 'enumcase'; public $name; @@ -10,4 +10,7 @@ public function __construct($name, $line= -1) { $this->name= $name; $this->line= $line; } + + /** @return string */ + public function lookup() { return $this->name; } } \ 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 00b78e7..24a551a 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -910,6 +910,7 @@ public function __construct() { $parse->expecting(';', 'case'); $body[$name]= new EnumCase($name, $line); + $body[$name]->holder= $holder; }); $this->body('use', function($parse, &$body, $annotations, $modifiers, $holder) { diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index e0cb0da..86bcabf 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -105,8 +105,8 @@ public function empty_enum() { #[Test] public function enum_with_cases() { $enum= new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE); - $enum->body['ONE']= new EnumCase('ONE', self::LINE); - $enum->body['TWO']= new EnumCase('TWO', self::LINE); + $enum->declare(new EnumCase('ONE', self::LINE)); + $enum->declare(new EnumCase('TWO', self::LINE)); $this->assertParsed([$enum], 'enum A { case ONE; case TWO; }'); } From 570b162fe743931aa864952ec6e9f059cd539d10 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 7 Mar 2021 22:38:00 +0100 Subject: [PATCH 3/9] Remove parent from enums Enums are by design a closed list, which inheritance would violate. Interfaces are allowed, but not parent classes. --- src/main/php/lang/ast/nodes/EnumDeclaration.class.php | 7 ++----- src/main/php/lang/ast/syntax/PHP.class.php | 9 +-------- src/test/php/lang/ast/unittest/parse/TypesTest.class.php | 4 ++-- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/main/php/lang/ast/nodes/EnumDeclaration.class.php b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php index ff4e32f..219b16d 100755 --- a/src/main/php/lang/ast/nodes/EnumDeclaration.class.php +++ b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php @@ -4,15 +4,12 @@ class EnumDeclaration extends TypeDeclaration { public $kind= 'enum'; - public $parent, $implements; + public $implements; - public function __construct($modifiers, $name, $parent= null, $implements= [], $body= [], $annotations= [], $comment= null, $line= -1) { + public function __construct($modifiers, $name, $implements= [], $body= [], $annotations= [], $comment= null, $line= -1) { parent::__construct($modifiers, $name, $body, $annotations, $comment, $line); - $this->parent= $parent; $this->implements= $implements; } - public function parent() { return $this->parent; } - public function interfaces() { return $this->implements; } } \ 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 24a551a..7487c55 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -868,13 +868,6 @@ public function __construct() { $comment= $parse->comment; $parse->comment= null; - $parent= null; - if ('extends' === $parse->token->value) { - $parse->forward(); - $parent= $parse->scope->resolve($parse->token->value); - $parse->forward(); - } - $implements= []; if ('implements' === $parse->token->value) { $parse->forward(); @@ -892,7 +885,7 @@ public function __construct() { } while (null !== $parse->token->value); } - $decl= new EnumDeclaration([], $name, $parent, $implements, [], [], $comment, $token->line); + $decl= new EnumDeclaration([], $name, $implements, [], [], $comment, $token->line); $parse->expecting('{', 'enum'); $decl->body= $this->typeBody($parse, $decl->name); $parse->expecting('}', 'enum'); diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index 86bcabf..3a3a566 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -97,14 +97,14 @@ public function empty_trait() { #[Test] public function empty_enum() { $this->assertParsed( - [new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE)], + [new EnumDeclaration([], '\\A', [], [], [], null, self::LINE)], 'enum A { }' ); } #[Test] public function enum_with_cases() { - $enum= new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE); + $enum= new EnumDeclaration([], '\\A', [], [], [], null, self::LINE); $enum->declare(new EnumCase('ONE', self::LINE)); $enum->declare(new EnumCase('TWO', self::LINE)); $this->assertParsed([$enum], 'enum A { case ONE; case TWO; }'); From c00ef08f1975f9c88f59f92cfe88b09a38e59239 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 12 Mar 2021 19:27:01 +0100 Subject: [PATCH 4/9] Add syntactic support for backed enums (`enum SortOrder: string { ... }`) --- .../php/lang/ast/nodes/EnumDeclaration.class.php | 5 +++-- src/main/php/lang/ast/syntax/PHP.class.php | 13 +++++++++++-- .../lang/ast/unittest/parse/TypesTest.class.php | 14 +++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/php/lang/ast/nodes/EnumDeclaration.class.php b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php index 219b16d..4c4dc49 100755 --- a/src/main/php/lang/ast/nodes/EnumDeclaration.class.php +++ b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php @@ -4,11 +4,12 @@ class EnumDeclaration extends TypeDeclaration { public $kind= 'enum'; - public $implements; + public $base, $implements; - public function __construct($modifiers, $name, $implements= [], $body= [], $annotations= [], $comment= null, $line= -1) { + public function __construct($modifiers, $name, $base, $implements= [], $body= [], $annotations= [], $comment= null, $line= -1) { parent::__construct($modifiers, $name, $body, $annotations, $comment, $line); $this->implements= $implements; + $this->base= $base; } public function interfaces() { return $this->implements; } diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 7487c55..525f636 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -884,8 +884,17 @@ public function __construct() { } } while (null !== $parse->token->value); } - - $decl= new EnumDeclaration([], $name, $implements, [], [], $comment, $token->line); + + // Backed enums vs. unit enums + if (':' === $parse->token->value) { + $parse->forward(); + $base= $parse->token->value; + $parse->forward(); + } else { + $base= null; + } + + $decl= new EnumDeclaration([], $name, $base, $implements, [], [], $comment, $token->line); $parse->expecting('{', 'enum'); $decl->body= $this->typeBody($parse, $decl->name); $parse->expecting('}', 'enum'); diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index 3a3a566..707cad4 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -95,16 +95,24 @@ public function empty_trait() { } #[Test] - public function empty_enum() { + public function empty_unit_enum() { $this->assertParsed( - [new EnumDeclaration([], '\\A', [], [], [], null, self::LINE)], + [new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE)], 'enum A { }' ); } + #[Test] + public function empty_backed_enum() { + $this->assertParsed( + [new EnumDeclaration([], '\\A', 'string', [], [], [], null, self::LINE)], + 'enum A: string { }' + ); + } + #[Test] public function enum_with_cases() { - $enum= new EnumDeclaration([], '\\A', [], [], [], null, self::LINE); + $enum= new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE); $enum->declare(new EnumCase('ONE', self::LINE)); $enum->declare(new EnumCase('TWO', self::LINE)); $this->assertParsed([$enum], 'enum A { case ONE; case TWO; }'); From c8b2f088172882bbba1f3e9ee81b91e20571d794 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 12 Mar 2021 19:32:53 +0100 Subject: [PATCH 5/9] Implement syntactic support for enum cases with value --- src/main/php/lang/ast/nodes/EnumCase.class.php | 5 +++-- src/main/php/lang/ast/syntax/PHP.class.php | 8 +++++++- .../lang/ast/unittest/parse/TypesTest.class.php | 17 +++++++++++++---- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/php/lang/ast/nodes/EnumCase.class.php b/src/main/php/lang/ast/nodes/EnumCase.class.php index e77326d..fc4fda0 100755 --- a/src/main/php/lang/ast/nodes/EnumCase.class.php +++ b/src/main/php/lang/ast/nodes/EnumCase.class.php @@ -4,10 +4,11 @@ class EnumCase extends Node implements Member { public $kind= 'enumcase'; - public $name; + public $name, $expression; - public function __construct($name, $line= -1) { + public function __construct($name, $expression, $line= -1) { $this->name= $name; + $this->expression= $expression; $this->line= $line; } diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 525f636..963c4f3 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -909,9 +909,15 @@ public function __construct() { $name= $parse->token->value; $parse->forward(); + if ('=' === $parse->token->value) { + $parse->forward(); + $expr= $this->expression($parse, 0); + } else { + $expr= null; + } $parse->expecting(';', 'case'); - $body[$name]= new EnumCase($name, $line); + $body[$name]= new EnumCase($name, $expr, $line); $body[$name]->holder= $holder; }); diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index 707cad4..6e60d9d 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -8,7 +8,8 @@ EnumCase, NamespaceDeclaration, TraitDeclaration, - UseExpression + UseExpression, + Literal }; use unittest\{Assert, Expect, Test}; @@ -111,13 +112,21 @@ public function empty_backed_enum() { } #[Test] - public function enum_with_cases() { + public function unit_enum_with_cases() { $enum= new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE); - $enum->declare(new EnumCase('ONE', self::LINE)); - $enum->declare(new EnumCase('TWO', self::LINE)); + $enum->declare(new EnumCase('ONE', null, self::LINE)); + $enum->declare(new EnumCase('TWO', null, self::LINE)); $this->assertParsed([$enum], 'enum A { case ONE; case TWO; }'); } + #[Test] + public function backed_enum_with_cases() { + $enum= new EnumDeclaration([], '\\A', 'int', [], [], [], null, self::LINE); + $enum->declare(new EnumCase('ONE', new Literal('1', self::LINE), self::LINE)); + $enum->declare(new EnumCase('TWO', new Literal('2', self::LINE), self::LINE)); + $this->assertParsed([$enum], 'enum A: int { case ONE = 1; case TWO = 2; }'); + } + #[Test] public function class_with_trait() { $class= new ClassDeclaration([], '\\A', null, [], [], [], null, self::LINE); From 0807a4c711b98424cb5175f5bfad912b22ab7af0 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 12 Mar 2021 19:52:48 +0100 Subject: [PATCH 6/9] Add ChangeLog entry --- ChangeLog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 500c4f6..d9ac4bb 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,12 @@ XP AST ChangeLog ## ?.?.? / ????-??-?? +## 7.1.0 / ????-??-?? + +* Merged PR #23: Add syntactic support for PHP 8.1 enums. Implementation + in the compiler is in pull request xp-framework/compiler#106 + (@thekid) + ## 7.0.4 / 2021-03-07 * Fixed *Call to undefined method ::emitoperator()* caused by standalone From 8f8e934b3f5c87d3f180ba16cae39eaef8c02588 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 12 Mar 2021 20:06:27 +0100 Subject: [PATCH 7/9] Allow enum cases to be annotated See https://github.com/xp-framework/ast/pull/23#pullrequestreview-611063550 --- src/main/php/lang/ast/nodes/EnumCase.class.php | 7 +++---- src/main/php/lang/ast/nodes/EnumDeclaration.class.php | 2 ++ src/main/php/lang/ast/syntax/PHP.class.php | 6 +++--- .../lang/ast/unittest/parse/AttributesTest.class.php | 11 +++++++++++ .../php/lang/ast/unittest/parse/TypesTest.class.php | 8 ++++---- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/php/lang/ast/nodes/EnumCase.class.php b/src/main/php/lang/ast/nodes/EnumCase.class.php index fc4fda0..d19baeb 100755 --- a/src/main/php/lang/ast/nodes/EnumCase.class.php +++ b/src/main/php/lang/ast/nodes/EnumCase.class.php @@ -1,14 +1,13 @@ name= $name; $this->expression= $expression; + $this->annotations= $annotations; $this->line= $line; } diff --git a/src/main/php/lang/ast/nodes/EnumDeclaration.class.php b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php index 4c4dc49..1e7402d 100755 --- a/src/main/php/lang/ast/nodes/EnumDeclaration.class.php +++ b/src/main/php/lang/ast/nodes/EnumDeclaration.class.php @@ -13,4 +13,6 @@ public function __construct($modifiers, $name, $base, $implements= [], $body= [] } public function interfaces() { return $this->implements; } + + public function case($name) { return $this->body[$name] ?? null; } } \ 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 963c4f3..2cb34ea 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -902,7 +902,7 @@ public function __construct() { return $decl; }); - $this->body('case', function($parse, &$body, $annotations, $modifiers, $holder) { + $this->body('case', function($parse, &$body, $meta, $modifiers, $holder) { $line= $parse->token->line; $parse->forward(); @@ -917,11 +917,11 @@ public function __construct() { } $parse->expecting(';', 'case'); - $body[$name]= new EnumCase($name, $expr, $line); + $body[$name]= new EnumCase($name, $expr, $meta[DETAIL_ANNOTATIONS] ?? [], $line); $body[$name]->holder= $holder; }); - $this->body('use', function($parse, &$body, $annotations, $modifiers, $holder) { + $this->body('use', function($parse, &$body, $meta, $modifiers, $holder) { $line= $parse->token->line; $parse->forward(); diff --git a/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php b/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php index 315965a..16ca13d 100755 --- a/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php @@ -199,6 +199,11 @@ public function on_interface($attributes, $expected) { $this->assertAnnotated($expected, $this->type($attributes.' interface T { }')); } + #[Test, Values('attributes')] + public function on_enum($attributes, $expected) { + $this->assertAnnotated($expected, $this->type($attributes.' enum T { }')); + } + #[Test, Values('attributes')] public function on_constant($attributes, $expected) { $type= $this->type('class T { '.$attributes.' const FIXTURE = 1; }'); @@ -222,4 +227,10 @@ public function on_parameter($attributes, $expected) { $type= $this->type('class T { public function fixture('.$attributes.' $p) { } }'); $this->assertAnnotated($expected, $type->method('fixture')->signature->parameters[0]); } + + #[Test, Values('attributes')] + public function on_enum_case($attributes, $expected) { + $type= $this->type('enum T { '.$attributes.' case ONE; }'); + $this->assertAnnotated($expected, $type->case('ONE')); + } } \ No newline at end of file diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index 6e60d9d..c433719 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -114,16 +114,16 @@ public function empty_backed_enum() { #[Test] public function unit_enum_with_cases() { $enum= new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE); - $enum->declare(new EnumCase('ONE', null, self::LINE)); - $enum->declare(new EnumCase('TWO', null, self::LINE)); + $enum->declare(new EnumCase('ONE', null, [], self::LINE)); + $enum->declare(new EnumCase('TWO', null, [], self::LINE)); $this->assertParsed([$enum], 'enum A { case ONE; case TWO; }'); } #[Test] public function backed_enum_with_cases() { $enum= new EnumDeclaration([], '\\A', 'int', [], [], [], null, self::LINE); - $enum->declare(new EnumCase('ONE', new Literal('1', self::LINE), self::LINE)); - $enum->declare(new EnumCase('TWO', new Literal('2', self::LINE), self::LINE)); + $enum->declare(new EnumCase('ONE', new Literal('1', self::LINE), [], self::LINE)); + $enum->declare(new EnumCase('TWO', new Literal('2', self::LINE), [], self::LINE)); $this->assertParsed([$enum], 'enum A: int { case ONE = 1; case TWO = 2; }'); } From 2bed704d287ef481c93285cc1361fd2c86bbc92c Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Fri, 12 Mar 2021 20:13:48 +0100 Subject: [PATCH 8/9] Overwrite Annotated::is() correctly --- src/main/php/lang/ast/nodes/EnumCase.class.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/php/lang/ast/nodes/EnumCase.class.php b/src/main/php/lang/ast/nodes/EnumCase.class.php index d19baeb..57d3a4a 100755 --- a/src/main/php/lang/ast/nodes/EnumCase.class.php +++ b/src/main/php/lang/ast/nodes/EnumCase.class.php @@ -13,4 +13,14 @@ public function __construct($name, $expression, $annotations, $line= -1) { /** @return string */ public function lookup() { return $this->name; } + + /** + * Checks whether this node is of a given kind + * + * @param string $kind + * @return bool + */ + public function is($kind) { + return $this->kind === $kind || '@member' === $kind || parent::is($kind); + } } \ No newline at end of file From 20b91953a3c894c0315efe222cda3aa6a0669c71 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 Mar 2021 13:00:28 +0100 Subject: [PATCH 9/9] Allow grouped syntax https://wiki.php.net/rfc/enumerations#grouped_syntax states that this "may cause syntactic issues with the planned addition of tagged unions", which are defined in https://wiki.php.net/rfc/tagged_unions and may be one of the following: case None { } case Some(private mixed $value) { ... } ...in addition to the existing declarations: case ASC; case DESC = "desc"; In none of these case would a comma be ambiguous, therefore I think we might as well have it, thus allowing more concise syntax for enum declarations --- src/main/php/lang/ast/syntax/PHP.class.php | 32 ++++++++++++------- .../ast/unittest/parse/TypesTest.class.php | 8 +++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 2cb34ea..24a465d 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -903,22 +903,30 @@ public function __construct() { }); $this->body('case', function($parse, &$body, $meta, $modifiers, $holder) { - $line= $parse->token->line; - $parse->forward(); - $name= $parse->token->value; + do { + $line= $parse->token->line; + $name= $parse->token->value; - $parse->forward(); - if ('=' === $parse->token->value) { $parse->forward(); - $expr= $this->expression($parse, 0); - } else { - $expr= null; - } - $parse->expecting(';', 'case'); + if ('=' === $parse->token->value) { + $parse->forward(); + $expr= $this->expression($parse, 0); + } else { + $expr= null; + } - $body[$name]= new EnumCase($name, $expr, $meta[DETAIL_ANNOTATIONS] ?? [], $line); - $body[$name]->holder= $holder; + $body[$name]= new EnumCase($name, $expr, $meta[DETAIL_ANNOTATIONS] ?? [], $line); + $body[$name]->holder= $holder; + + if (',' === $parse->token->value) { + $parse->forward(); + continue; + } else { + $parse->expecting(';', 'case'); + break; + } + } while ($parse->token->value); }); $this->body('use', function($parse, &$body, $meta, $modifiers, $holder) { diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index c433719..3e5dba9 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -127,6 +127,14 @@ public function backed_enum_with_cases() { $this->assertParsed([$enum], 'enum A: int { case ONE = 1; case TWO = 2; }'); } + #[Test] + public function unit_enum_with_grouped_cases() { + $enum= new EnumDeclaration([], '\\A', null, [], [], [], null, self::LINE); + $enum->declare(new EnumCase('ONE', null, [], self::LINE)); + $enum->declare(new EnumCase('TWO', null, [], self::LINE)); + $this->assertParsed([$enum], 'enum A { case ONE, TWO; }'); + } + #[Test] public function class_with_trait() { $class= new ClassDeclaration([], '\\A', null, [], [], [], null, self::LINE);