From 82854093719c0cc8844c248d52a901b5179b340f Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 9 May 2023 21:43:13 +0200 Subject: [PATCH 01/15] Implement property hooks syntax --- .../php/lang/ast/nodes/Property.class.php | 1 + src/main/php/lang/ast/syntax/PHP.class.php | 35 ++++++++++++++++--- .../ast/unittest/parse/MembersTest.class.php | 24 +++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/main/php/lang/ast/nodes/Property.class.php b/src/main/php/lang/ast/nodes/Property.class.php index 44766f1..7966f70 100755 --- a/src/main/php/lang/ast/nodes/Property.class.php +++ b/src/main/php/lang/ast/nodes/Property.class.php @@ -3,6 +3,7 @@ class Property extends Annotated implements Member { public $kind= 'property'; public $name, $modifiers, $expression, $type, $holder; + public $hooks= null; public function __construct($modifiers, $name, $type, $expression= null, $annotations= null, $comment= null, $line= -1, $holder= null) { $this->modifiers= $modifiers; diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 10e6a77..9475bd7 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1282,7 +1282,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->comment= null; $annotations= $meta[DETAIL_ANNOTATIONS] ?? null; - while (';' !== $parse->token->value) { + do { $line= $parse->token->line; // Untyped `$a` vs. typed `int $a` @@ -1304,13 +1304,40 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { } else { $expr= null; } + $body[$lookup]= new Property($modifiers, $name, $type, $expr, $annotations, $comment, $line, $holder); - if (',' === $parse->token->value) { + // Check for property hooks + if (';' === $parse->token->value) { + $parse->forward(); + return; + } else if (',' === $parse->token->value) { + $parse->forward(); + continue; + } else if ('{' === $parse->token->value) { $parse->forward(); + do { + $hook= $parse->token->value; + $parse->forward(); + + if ('=>' === $parse->token->value) { + $parse->forward(); + $body[$lookup]->hooks[$hook]= $this->expression($parse, 0); + } else if ('{' === $parse->token->value) { + $line= $parse->token->line; + $parse->forward(); + $body[$lookup]->hooks[$hook]= new Block($this->statements($parse), $line); + } else if ('}' === $parse->token->value) { + break; + } + } while (null !== $parse->token->value); + + $parse->forward(); + return; + } else { + $parse->expecting(';, , or {', 'field'); } - } - $parse->expecting(';', 'field declaration'); + } while (null !== $parse->token->value); } /** Parses PHP 8 attributes (#[Test]) */ 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 c4b00fe..dc4085b 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -3,6 +3,7 @@ use lang\ast\Type; use lang\ast\nodes\{ Annotations, + Block, ClassDeclaration, Constant, Expression, @@ -11,6 +12,7 @@ Literal, Method, Property, + ReturnStatement, ScopeExpression, Signature, Variable, @@ -128,6 +130,28 @@ public function method_with_annotations() { $this->assertParsed([$class], 'class A { #[Test, Ignore("Not implemented")] public function a() { } }'); } + #[Test] + public function property_with_get_and_set_hooks() { + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); + $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); + $prop->hooks['get']= new Block([$return], self::LINE); + $prop->hooks['set']= new Block([], self::LINE); + $class->declare($prop); + + $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set { } } }'); + } + + #[Test] + public function property_with_short_get_hook() { + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); + $prop->hooks['get']= new Literal('"Hello"', self::LINE); + $class->declare($prop); + + $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); + } + #[Test] public function instance_property_access() { $this->assertParsed( From a60ac685159582028b9a2b1fea19d78219f03a4d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 9 May 2023 22:17:40 +0200 Subject: [PATCH 02/15] Wrap hooks in lang.ast.nodes.Hook --- src/main/php/lang/ast/nodes/Hook.class.php | 16 +++++++++++ src/main/php/lang/ast/syntax/PHP.class.php | 28 ++++++++++++++----- .../ast/unittest/parse/MembersTest.class.php | 9 +++--- 3 files changed, 42 insertions(+), 11 deletions(-) create mode 100755 src/main/php/lang/ast/nodes/Hook.class.php diff --git a/src/main/php/lang/ast/nodes/Hook.class.php b/src/main/php/lang/ast/nodes/Hook.class.php new file mode 100755 index 0000000..007907c --- /dev/null +++ b/src/main/php/lang/ast/nodes/Hook.class.php @@ -0,0 +1,16 @@ +type= $type; + $this->field= $field; + $this->expression= $expression; + $this->argument= $argument; + $this->line= $line; + } +} \ 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 9475bd7..ebde42c 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -27,6 +27,7 @@ ForeachLoop, FunctionDeclaration, GotoStatement, + Hook, IfStatement, InstanceExpression, InstanceOfExpression, @@ -1316,21 +1317,34 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { continue; } else if ('{' === $parse->token->value) { $parse->forward(); - do { + + while ('}' !== $parse->token->value) { $hook= $parse->token->value; $parse->forward(); + if ('(' === $parse->token->value) { + $parse->forward(); + $parse->expecting('(variable)', 'field hook'); + $argument= $parse->token->value; + $parse->forward(); + $parse->expecting(')', 'field hook'); + } else { + $argument= null; + } + + $line= $parse->token->line; if ('=>' === $parse->token->value) { $parse->forward(); - $body[$lookup]->hooks[$hook]= $this->expression($parse, 0); + $expr= $this->expression($parse, 0); + $parse->expecting(';', 'field hook'); } else if ('{' === $parse->token->value) { - $line= $parse->token->line; $parse->forward(); - $body[$lookup]->hooks[$hook]= new Block($this->statements($parse), $line); - } else if ('}' === $parse->token->value) { - break; + $expr= new Block($this->statements($parse), $line); + $parse->expecting('}', 'field hook'); } - } while (null !== $parse->token->value); + + $body[$lookup]->hooks[$hook]= new Hook($hook, $lookup, $expr, $argument, $line); + } $parse->forward(); return; 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 dc4085b..af68287 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -7,6 +7,7 @@ ClassDeclaration, Constant, Expression, + Hook, InstanceExpression, InvokeExpression, Literal, @@ -135,18 +136,18 @@ public function property_with_get_and_set_hooks() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); - $prop->hooks['get']= new Block([$return], self::LINE); - $prop->hooks['set']= new Block([], self::LINE); + $prop->hooks['get']= new Hook('get', '$a', new Block([$return], self::LINE), null, self::LINE); + $prop->hooks['set']= new Hook('set', '$a', new Block([], self::LINE), 'value', self::LINE); $class->declare($prop); - $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set { } } }'); + $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set($value) { } } }'); } #[Test] public function property_with_short_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Literal('"Hello"', self::LINE); + $prop->hooks['get']= new Hook('get', '$a', new Literal('"Hello"', self::LINE), null, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); From d4202917edc5fe334766a1a223c0cc2e3b394fe1 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 9 May 2023 22:21:21 +0200 Subject: [PATCH 03/15] Omit leading `$` from field name --- src/main/php/lang/ast/syntax/PHP.class.php | 2 +- src/test/php/lang/ast/unittest/parse/MembersTest.class.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index ebde42c..b3bd4f2 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1343,7 +1343,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->expecting('}', 'field hook'); } - $body[$lookup]->hooks[$hook]= new Hook($hook, $lookup, $expr, $argument, $line); + $body[$lookup]->hooks[$hook]= new Hook($hook, $name, $expr, $argument, $line); } $parse->forward(); 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 af68287..a002a1a 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -136,8 +136,8 @@ public function property_with_get_and_set_hooks() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); - $prop->hooks['get']= new Hook('get', '$a', new Block([$return], self::LINE), null, self::LINE); - $prop->hooks['set']= new Hook('set', '$a', new Block([], self::LINE), 'value', self::LINE); + $prop->hooks['get']= new Hook('get', 'a', new Block([$return], self::LINE), null, self::LINE); + $prop->hooks['set']= new Hook('set', 'a', new Block([], self::LINE), 'value', self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set($value) { } } }'); @@ -147,7 +147,7 @@ public function property_with_get_and_set_hooks() { public function property_with_short_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Hook('get', '$a', new Literal('"Hello"', self::LINE), null, self::LINE); + $prop->hooks['get']= new Hook('get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); From 9a6891e0c57749d7f4c41d9bb3e3f54bdaaa3143 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 May 2023 13:08:11 +0200 Subject: [PATCH 04/15] Add full parameter syntax support --- src/main/php/lang/ast/nodes/Hook.class.php | 6 +-- src/main/php/lang/ast/syntax/PHP.class.php | 42 +++++++++++++++++-- .../ast/unittest/parse/MembersTest.class.php | 14 ++++++- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/main/php/lang/ast/nodes/Hook.class.php b/src/main/php/lang/ast/nodes/Hook.class.php index 007907c..ca851f4 100755 --- a/src/main/php/lang/ast/nodes/Hook.class.php +++ b/src/main/php/lang/ast/nodes/Hook.class.php @@ -4,13 +4,13 @@ class Hook extends Node { public $kind= 'hook'; - public $type, $field, $expression, $argument; + public $type, $field, $expression, $parameter; - public function __construct($type, $field, $expression, $argument= null, $line= -1) { + public function __construct($type, $field, $expression, $parameter= null, $line= -1) { $this->type= $type; $this->field= $field; $this->expression= $expression; - $this->argument= $argument; + $this->parameter= $parameter; $this->line= $line; } } \ 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 b3bd4f2..40bbddb 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1324,12 +1324,46 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { if ('(' === $parse->token->value) { $parse->forward(); - $parse->expecting('(variable)', 'field hook'); - $argument= $parse->token->value; + + if ('#[' === $parse->token->value) { + $parse->forward(); + $annotations= $this->annotations($parse, 'parameter annotations'); + } else { + $annotations= null; + } + + $line= $parse->token->line; + $type= $this->type($parse); + + if ('...' === $parse->token->value) { + $variadic= true; + $parse->forward(); + } else { + $variadic= false; + } + + if ('&' === $parse->token->value) { + $byref= true; + $parse->forward(); + } else { + $byref= false; + } + + $parse->forward(); + $param= $parse->token->value; $parse->forward(); + + if ('=' === $parse->token->value) { + $parse->forward(); + $default= $this->expression($parse, 0); + } else { + $default= null; + } + + $parameter= new Parameter($param, $type, $default, $byref, $variadic, null, $annotations, null, $line); $parse->expecting(')', 'field hook'); } else { - $argument= null; + $parameter= null; } $line= $parse->token->line; @@ -1343,7 +1377,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->expecting('}', 'field hook'); } - $body[$lookup]->hooks[$hook]= new Hook($hook, $name, $expr, $argument, $line); + $body[$lookup]->hooks[$hook]= new Hook($hook, $name, $expr, $parameter, $line); } $parse->forward(); 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 a002a1a..4c20542 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -136,8 +136,9 @@ public function property_with_get_and_set_hooks() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); + $parameter= new Parameter('value', null, null, false, false, null, null, null, self::LINE); $prop->hooks['get']= new Hook('get', 'a', new Block([$return], self::LINE), null, self::LINE); - $prop->hooks['set']= new Hook('set', 'a', new Block([], self::LINE), 'value', self::LINE); + $prop->hooks['set']= new Hook('set', 'a', new Block([], self::LINE), $parameter, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set($value) { } } }'); @@ -153,6 +154,17 @@ public function property_with_short_get_hook() { $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); } + #[Test] + public function property_with_typed_hook() { + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); + $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); + $prop->hooks['set']= new Hook('set', 'a', new Block([], self::LINE), $parameter, self::LINE); + $class->declare($prop); + + $this->assertParsed([$class], 'class A { public $a { set(string $value) { } } }'); + } + #[Test] public function instance_property_access() { $this->assertParsed( From 90a74a1cad66065ca23a5ace448963324dd5a563 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 May 2023 13:12:59 +0200 Subject: [PATCH 05/15] Add support for final hooks --- src/main/php/lang/ast/nodes/Hook.class.php | 5 +++-- src/main/php/lang/ast/syntax/PHP.class.php | 9 ++++++++- .../ast/unittest/parse/MembersTest.class.php | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/php/lang/ast/nodes/Hook.class.php b/src/main/php/lang/ast/nodes/Hook.class.php index ca851f4..f2b559c 100755 --- a/src/main/php/lang/ast/nodes/Hook.class.php +++ b/src/main/php/lang/ast/nodes/Hook.class.php @@ -4,9 +4,10 @@ class Hook extends Node { public $kind= 'hook'; - public $type, $field, $expression, $parameter; + public $modifiers, $type, $field, $expression, $parameter; - public function __construct($type, $field, $expression, $parameter= null, $line= -1) { + public function __construct($modifiers, $type, $field, $expression, $parameter= null, $line= -1) { + $this->modifiers= $modifiers; $this->type= $type; $this->field= $field; $this->expression= $expression; diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 40bbddb..650b60d 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1319,6 +1319,13 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->forward(); while ('}' !== $parse->token->value) { + if ('final' === $parse->token->value) { + $modifiers= ['final']; + $parse->forward(); + } else { + $modifiers= []; + } + $hook= $parse->token->value; $parse->forward(); @@ -1377,7 +1384,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->expecting('}', 'field hook'); } - $body[$lookup]->hooks[$hook]= new Hook($hook, $name, $expr, $parameter, $line); + $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $name, $expr, $parameter, $line); } $parse->forward(); 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 4c20542..26d77a5 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -137,8 +137,8 @@ public function property_with_get_and_set_hooks() { $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); $parameter= new Parameter('value', null, null, false, false, null, null, null, self::LINE); - $prop->hooks['get']= new Hook('get', 'a', new Block([$return], self::LINE), null, self::LINE); - $prop->hooks['set']= new Hook('set', 'a', new Block([], self::LINE), $parameter, self::LINE); + $prop->hooks['get']= new Hook([], 'get', 'a', new Block([$return], self::LINE), null, self::LINE); + $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set($value) { } } }'); @@ -148,7 +148,7 @@ public function property_with_get_and_set_hooks() { public function property_with_short_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Hook('get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE); + $prop->hooks['get']= new Hook([], 'get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); @@ -159,12 +159,23 @@ public function property_with_typed_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook('set', 'a', new Block([], self::LINE), $parameter, self::LINE); + $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { set(string $value) { } } }'); } + #[Test] + public function property_with_final_hook() { + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); + $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); + $prop->hooks['set']= new Hook(['final'], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE); + $class->declare($prop); + + $this->assertParsed([$class], 'class A { public $a { final set(string $value) { } } }'); + } + #[Test] public function instance_property_access() { $this->assertParsed( From 527e9cd5b7e411bf49729dc48c3100f6eaf2217c Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 May 2023 13:17:09 +0200 Subject: [PATCH 06/15] Add holder to hooks --- src/main/php/lang/ast/nodes/Hook.class.php | 5 +++-- src/main/php/lang/ast/syntax/PHP.class.php | 2 +- .../php/lang/ast/unittest/parse/MembersTest.class.php | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/php/lang/ast/nodes/Hook.class.php b/src/main/php/lang/ast/nodes/Hook.class.php index f2b559c..169ae0e 100755 --- a/src/main/php/lang/ast/nodes/Hook.class.php +++ b/src/main/php/lang/ast/nodes/Hook.class.php @@ -4,14 +4,15 @@ class Hook extends Node { public $kind= 'hook'; - public $modifiers, $type, $field, $expression, $parameter; + public $modifiers, $type, $field, $expression, $parameter, $holder; - public function __construct($modifiers, $type, $field, $expression, $parameter= null, $line= -1) { + public function __construct($modifiers, $type, $field, $expression, $parameter= null, $line= -1, $holder= null) { $this->modifiers= $modifiers; $this->type= $type; $this->field= $field; $this->expression= $expression; $this->parameter= $parameter; $this->line= $line; + $this->holder= $holder; } } \ 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 650b60d..a6bc3d4 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1384,7 +1384,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->expecting('}', 'field hook'); } - $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $name, $expr, $parameter, $line); + $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $name, $expr, $parameter, $line, $holder); } $parse->forward(); 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 26d77a5..584df4c 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -137,8 +137,8 @@ public function property_with_get_and_set_hooks() { $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); $parameter= new Parameter('value', null, null, false, false, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', 'a', new Block([$return], self::LINE), null, self::LINE); - $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE); + $prop->hooks['get']= new Hook([], 'get', 'a', new Block([$return], self::LINE), null, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set($value) { } } }'); @@ -148,7 +148,7 @@ public function property_with_get_and_set_hooks() { public function property_with_short_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE); + $prop->hooks['get']= new Hook([], 'get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); @@ -159,7 +159,7 @@ public function property_with_typed_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE); + $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { set(string $value) { } } }'); @@ -170,7 +170,7 @@ public function property_with_final_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook(['final'], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE); + $prop->hooks['set']= new Hook(['final'], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { final set(string $value) { } } }'); From 590c67a8d10c8059add3db8db244f29e4f38b64b Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 May 2023 19:36:34 +0200 Subject: [PATCH 07/15] Add abbreviated form for `get` hooks --- src/main/php/lang/ast/syntax/PHP.class.php | 7 +++++++ .../php/lang/ast/unittest/parse/MembersTest.class.php | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index a6bc3d4..27d1ef3 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1315,6 +1315,13 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { } else if (',' === $parse->token->value) { $parse->forward(); continue; + } else if ('=>' === $parse->token->value) { + $parse->forward(); + $expr= $this->expression($parse, 0); + $parse->expecting(';', 'field hook'); + + $body[$lookup]->hooks['get']= new Hook([], 'get', $name, $expr, null, $line, $holder); + return; } else if ('{' === $parse->token->value) { $parse->forward(); 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 584df4c..7bb4920 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -154,6 +154,16 @@ public function property_with_short_get_hook() { $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); } + #[Test] + public function property_with_abbreviated_get_hook() { + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); + $prop->hooks['get']= new Hook([], 'get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE, new IsValue('\\A')); + $class->declare($prop); + + $this->assertParsed([$class], 'class A { public $a => "Hello"; }'); + } + #[Test] public function property_with_typed_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); From 34a779b138af2692489071f9cc4db15e0d3c8918 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 14 May 2023 09:25:14 +0200 Subject: [PATCH 08/15] Support abstract hooks --- src/main/php/lang/ast/syntax/PHP.class.php | 6 ++++++ .../php/lang/ast/unittest/parse/MembersTest.class.php | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 27d1ef3..6e3ea48 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1329,6 +1329,9 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { if ('final' === $parse->token->value) { $modifiers= ['final']; $parse->forward(); + } else if ('abstract' === $parse->token->value) { + $modifiers= ['abstract']; + $parse->forward(); } else { $modifiers= []; } @@ -1389,6 +1392,9 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->forward(); $expr= new Block($this->statements($parse), $line); $parse->expecting('}', 'field hook'); + } else if (';' === $parse->token->value) { + $parse->forward(); + $expr= null; } $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $name, $expr, $parameter, $line, $holder); 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 7bb4920..7fb23ad 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -186,6 +186,17 @@ public function property_with_final_hook() { $this->assertParsed([$class], 'class A { public $a { final set(string $value) { } } }'); } + #[Test] + public function property_with_abstract_hook() { + $class= new ClassDeclaration(['abstract'], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); + $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); + $prop->hooks['set']= new Hook(['abstract'], 'set', 'a', null, $parameter, self::LINE, new IsValue('\\A')); + $class->declare($prop); + + $this->assertParsed([$class], 'abstract class A { public $a { abstract set(string $value); } }'); + } + #[Test] public function instance_property_access() { $this->assertParsed( From 4490d7e079e269620cba1b0f9ab565135bfcf6f9 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 14 May 2023 09:26:06 +0200 Subject: [PATCH 09/15] Remove support for variadic and by-ref hook parameters Consistency with PHP --- src/main/php/lang/ast/syntax/PHP.class.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 6e3ea48..e02a990 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1352,20 +1352,6 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $line= $parse->token->line; $type= $this->type($parse); - if ('...' === $parse->token->value) { - $variadic= true; - $parse->forward(); - } else { - $variadic= false; - } - - if ('&' === $parse->token->value) { - $byref= true; - $parse->forward(); - } else { - $byref= false; - } - $parse->forward(); $param= $parse->token->value; $parse->forward(); @@ -1377,7 +1363,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $default= null; } - $parameter= new Parameter($param, $type, $default, $byref, $variadic, null, $annotations, null, $line); + $parameter= new Parameter($param, $type, $default, false, false, null, $annotations, null, $line); $parse->expecting(')', 'field hook'); } else { $parameter= null; From bb9f4de08d95bb81b5446d81c4768852247f6117 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 14 May 2023 09:28:33 +0200 Subject: [PATCH 10/15] Consistently use "property" for properties --- src/main/php/lang/ast/syntax/PHP.class.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index e02a990..f50bde3 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1318,7 +1318,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { } else if ('=>' === $parse->token->value) { $parse->forward(); $expr= $this->expression($parse, 0); - $parse->expecting(';', 'field hook'); + $parse->expecting(';', 'property hook'); $body[$lookup]->hooks['get']= new Hook([], 'get', $name, $expr, null, $line, $holder); return; @@ -1364,7 +1364,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { } $parameter= new Parameter($param, $type, $default, false, false, null, $annotations, null, $line); - $parse->expecting(')', 'field hook'); + $parse->expecting(')', 'property hook parameters'); } else { $parameter= null; } @@ -1373,11 +1373,11 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { if ('=>' === $parse->token->value) { $parse->forward(); $expr= $this->expression($parse, 0); - $parse->expecting(';', 'field hook'); + $parse->expecting(';', 'property hook'); } else if ('{' === $parse->token->value) { $parse->forward(); $expr= new Block($this->statements($parse), $line); - $parse->expecting('}', 'field hook'); + $parse->expecting('}', 'property hook'); } else if (';' === $parse->token->value) { $parse->forward(); $expr= null; @@ -1389,7 +1389,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $parse->forward(); return; } else { - $parse->expecting(';, , or {', 'field'); + $parse->expecting(';, , or {', 'property'); } } while (null !== $parse->token->value); } From 9f261c6e8ae198b98c56af767654d30a016aba5f Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 14 May 2023 10:38:52 +0200 Subject: [PATCH 11/15] Make Node::children() return by reference, enabling replacement --- src/main/php/lang/ast/nodes/Annotations.class.php | 4 ++-- src/main/php/lang/ast/nodes/ArrayLiteral.class.php | 4 ++-- src/main/php/lang/ast/nodes/Assignment.class.php | 2 +- src/main/php/lang/ast/nodes/BinaryExpression.class.php | 2 +- src/main/php/lang/ast/nodes/Braced.class.php | 2 +- src/main/php/lang/ast/nodes/BreakStatement.class.php | 2 +- .../php/lang/ast/nodes/CallableExpression.class.php | 2 +- .../php/lang/ast/nodes/CallableNewExpression.class.php | 2 +- src/main/php/lang/ast/nodes/CaseLabel.class.php | 4 ++-- src/main/php/lang/ast/nodes/CastExpression.class.php | 2 +- src/main/php/lang/ast/nodes/CatchStatement.class.php | 6 +----- .../php/lang/ast/nodes/ContinueStatement.class.php | 4 +--- src/main/php/lang/ast/nodes/DoLoop.class.php | 4 ++-- src/main/php/lang/ast/nodes/Expression.class.php | 2 +- src/main/php/lang/ast/nodes/ForLoop.class.php | 10 +++++----- src/main/php/lang/ast/nodes/ForeachLoop.class.php | 4 ++-- src/main/php/lang/ast/nodes/IfStatement.class.php | 6 +++--- .../php/lang/ast/nodes/InstanceExpression.class.php | 4 +--- .../php/lang/ast/nodes/InstanceOfExpression.class.php | 2 +- src/main/php/lang/ast/nodes/InvokeExpression.class.php | 4 ++-- src/main/php/lang/ast/nodes/LambdaExpression.class.php | 2 +- src/main/php/lang/ast/nodes/MatchCondition.class.php | 4 ++-- src/main/php/lang/ast/nodes/MatchExpression.class.php | 4 ++-- src/main/php/lang/ast/nodes/OffsetExpression.class.php | 2 +- src/main/php/lang/ast/nodes/ReturnStatement.class.php | 4 +--- src/main/php/lang/ast/nodes/ScopeExpression.class.php | 2 +- src/main/php/lang/ast/nodes/SwitchStatement.class.php | 4 ++-- .../php/lang/ast/nodes/TernaryExpression.class.php | 2 +- src/main/php/lang/ast/nodes/ThrowExpression.class.php | 4 +--- src/main/php/lang/ast/nodes/ThrowStatement.class.php | 4 +--- src/main/php/lang/ast/nodes/TryStatement.class.php | 6 +++--- src/main/php/lang/ast/nodes/UnaryExpression.class.php | 2 +- src/main/php/lang/ast/nodes/UnpackExpression.class.php | 2 +- src/main/php/lang/ast/nodes/UsingStatement.class.php | 6 +++--- src/main/php/lang/ast/nodes/WhileLoop.class.php | 4 ++-- src/main/php/lang/ast/nodes/YieldExpression.class.php | 2 +- .../php/lang/ast/nodes/YieldFromExpression.class.php | 2 +- 37 files changed, 57 insertions(+), 71 deletions(-) diff --git a/src/main/php/lang/ast/nodes/Annotations.class.php b/src/main/php/lang/ast/nodes/Annotations.class.php index f7723f8..e64117c 100755 --- a/src/main/php/lang/ast/nodes/Annotations.class.php +++ b/src/main/php/lang/ast/nodes/Annotations.class.php @@ -70,8 +70,8 @@ public function getIterator(): Traversable { } /** @return iterable */ - public function children() { - foreach ($this->named as $annotation) { + public function &children() { + foreach ($this->named as &$annotation) { yield $annotation; } } diff --git a/src/main/php/lang/ast/nodes/ArrayLiteral.class.php b/src/main/php/lang/ast/nodes/ArrayLiteral.class.php index 52d9409..181e5c0 100755 --- a/src/main/php/lang/ast/nodes/ArrayLiteral.class.php +++ b/src/main/php/lang/ast/nodes/ArrayLiteral.class.php @@ -17,8 +17,8 @@ public function __construct($values, $line= -1) { } /** @return iterable */ - public function children() { - foreach ($this->values as $pair) { + public function &children() { + foreach ($this->values as &$pair) { if (null !== $pair[0]) yield $pair[0]; yield $pair[1]; } diff --git a/src/main/php/lang/ast/nodes/Assignment.class.php b/src/main/php/lang/ast/nodes/Assignment.class.php index 8e6bb9b..1d3c4f6 100755 --- a/src/main/php/lang/ast/nodes/Assignment.class.php +++ b/src/main/php/lang/ast/nodes/Assignment.class.php @@ -14,5 +14,5 @@ public function __construct($variable, $operator, $expression, $line= -1) { } /** @return iterable */ - public function children() { return [$this->variable, $this->expression]; } + public function children() { return [&$this->variable, &$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/BinaryExpression.class.php b/src/main/php/lang/ast/nodes/BinaryExpression.class.php index 774c474..6caa4e8 100755 --- a/src/main/php/lang/ast/nodes/BinaryExpression.class.php +++ b/src/main/php/lang/ast/nodes/BinaryExpression.class.php @@ -14,6 +14,6 @@ public function __construct($left, $operator, $right, $line= -1) { } /** @return iterable */ - public function children() { return [$this->left, $this->right]; } + public function children() { return [&$this->left, &$this->right]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/Braced.class.php b/src/main/php/lang/ast/nodes/Braced.class.php index dafc11d..2b793d6 100755 --- a/src/main/php/lang/ast/nodes/Braced.class.php +++ b/src/main/php/lang/ast/nodes/Braced.class.php @@ -12,5 +12,5 @@ public function __construct($expression, $line= -1) { } /** @return iterable */ - public function children() { return [$this->expression]; } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/BreakStatement.class.php b/src/main/php/lang/ast/nodes/BreakStatement.class.php index 0c9560b..6b3bae0 100755 --- a/src/main/php/lang/ast/nodes/BreakStatement.class.php +++ b/src/main/php/lang/ast/nodes/BreakStatement.class.php @@ -12,5 +12,5 @@ public function __construct($expression= null, $line= -1) { } /** @return iterable */ - public function children() { return $this->expression ? [$this->expression] : []; } + public function children() { return $this->expression ? [&$this->expression] : []; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/CallableExpression.class.php b/src/main/php/lang/ast/nodes/CallableExpression.class.php index ae956f3..841f9fc 100755 --- a/src/main/php/lang/ast/nodes/CallableExpression.class.php +++ b/src/main/php/lang/ast/nodes/CallableExpression.class.php @@ -12,5 +12,5 @@ public function __construct($expression, $line= -1) { } /** @return iterable */ - public function children() { return [$this->expression]; } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/CallableNewExpression.class.php b/src/main/php/lang/ast/nodes/CallableNewExpression.class.php index d883a47..2fdba8c 100755 --- a/src/main/php/lang/ast/nodes/CallableNewExpression.class.php +++ b/src/main/php/lang/ast/nodes/CallableNewExpression.class.php @@ -12,5 +12,5 @@ public function __construct($type, $line= -1) { } /** @return iterable */ - public function children() { return [$this->type]; } + public function children() { return [&$this->type]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/CaseLabel.class.php b/src/main/php/lang/ast/nodes/CaseLabel.class.php index 1038383..55b569c 100755 --- a/src/main/php/lang/ast/nodes/CaseLabel.class.php +++ b/src/main/php/lang/ast/nodes/CaseLabel.class.php @@ -18,11 +18,11 @@ public function __construct($expression, $body, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { if (null !== $this->expression) { yield $this->expression; } - foreach ($this->body as $node) { + foreach ($this->body as &$node) { yield $node; } } diff --git a/src/main/php/lang/ast/nodes/CastExpression.class.php b/src/main/php/lang/ast/nodes/CastExpression.class.php index 555be4c..0e6b606 100755 --- a/src/main/php/lang/ast/nodes/CastExpression.class.php +++ b/src/main/php/lang/ast/nodes/CastExpression.class.php @@ -13,5 +13,5 @@ public function __construct($type, $expression, $line= -1) { } /** @return iterable */ - public function children() { return [$this->expression]; } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/CatchStatement.class.php b/src/main/php/lang/ast/nodes/CatchStatement.class.php index daae8c4..dc8cd33 100755 --- a/src/main/php/lang/ast/nodes/CatchStatement.class.php +++ b/src/main/php/lang/ast/nodes/CatchStatement.class.php @@ -18,9 +18,5 @@ public function __construct($types, $variable, $body, $line= -1) { } /** @return iterable */ - public function children() { - foreach ($this->body as $element) { - yield $element; - } - } + public function children() { return $this->body; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/ContinueStatement.class.php b/src/main/php/lang/ast/nodes/ContinueStatement.class.php index 9d318aa..545630a 100755 --- a/src/main/php/lang/ast/nodes/ContinueStatement.class.php +++ b/src/main/php/lang/ast/nodes/ContinueStatement.class.php @@ -12,7 +12,5 @@ public function __construct($expression= null, $line= -1) { } /** @return iterable */ - public function children() { - return $this->expression ? [$this->expression] : []; - } + public function children() { return $this->expression ? [&$this->expression] : []; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/DoLoop.class.php b/src/main/php/lang/ast/nodes/DoLoop.class.php index 6ed776d..7cceca7 100755 --- a/src/main/php/lang/ast/nodes/DoLoop.class.php +++ b/src/main/php/lang/ast/nodes/DoLoop.class.php @@ -13,9 +13,9 @@ public function __construct($expression, $body, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { yield $this->expression; - foreach ($this->body as $element) { + foreach ($this->body as &$element) { yield $element; } } diff --git a/src/main/php/lang/ast/nodes/Expression.class.php b/src/main/php/lang/ast/nodes/Expression.class.php index 8c6a0da..cb2a979 100755 --- a/src/main/php/lang/ast/nodes/Expression.class.php +++ b/src/main/php/lang/ast/nodes/Expression.class.php @@ -12,5 +12,5 @@ public function __construct($inline, $line= -1) { } /** @return iterable */ - public function children() { return [$this->inline]; } + public function children() { return [&$this->inline]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/ForLoop.class.php b/src/main/php/lang/ast/nodes/ForLoop.class.php index f19c94c..225e4b8 100755 --- a/src/main/php/lang/ast/nodes/ForLoop.class.php +++ b/src/main/php/lang/ast/nodes/ForLoop.class.php @@ -15,17 +15,17 @@ public function __construct($initialization, $condition, $loop, $body, $line= -1 } /** @return iterable */ - public function children() { - foreach ($this->initialization as $element) { + public function &children() { + foreach ($this->initialization as &$element) { yield $element; } - foreach ($this->condition as $element) { + foreach ($this->condition as &$element) { yield $element; } - foreach ($this->loop as $element) { + foreach ($this->loop as &$element) { yield $element; } - foreach ($this->body as $element) { + foreach ($this->body as &$element) { yield $element; } } diff --git a/src/main/php/lang/ast/nodes/ForeachLoop.class.php b/src/main/php/lang/ast/nodes/ForeachLoop.class.php index 426a892..a26a498 100755 --- a/src/main/php/lang/ast/nodes/ForeachLoop.class.php +++ b/src/main/php/lang/ast/nodes/ForeachLoop.class.php @@ -15,13 +15,13 @@ public function __construct($expression, $key, $value, $body, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { yield $this->expression; if ($this->key) { yield $this->key; } yield $this->value; - foreach ($this->body as $element) { + foreach ($this->body as &$element) { yield $element; } } diff --git a/src/main/php/lang/ast/nodes/IfStatement.class.php b/src/main/php/lang/ast/nodes/IfStatement.class.php index 671db8a..e581b7c 100755 --- a/src/main/php/lang/ast/nodes/IfStatement.class.php +++ b/src/main/php/lang/ast/nodes/IfStatement.class.php @@ -19,12 +19,12 @@ public function __construct($expression, $body, $otherwise= null, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { yield $this->expression; - foreach ($this->body as $node) { + foreach ($this->body as &$node) { yield $node; } - foreach ((array)$this->otherwise as $node) { + foreach ((array)$this->otherwise as &$node) { yield $node; } } diff --git a/src/main/php/lang/ast/nodes/InstanceExpression.class.php b/src/main/php/lang/ast/nodes/InstanceExpression.class.php index a352f3e..e26c17b 100755 --- a/src/main/php/lang/ast/nodes/InstanceExpression.class.php +++ b/src/main/php/lang/ast/nodes/InstanceExpression.class.php @@ -13,7 +13,5 @@ public function __construct($expression, $member, $line= -1) { } /** @return iterable */ - public function children() { - return [$this->expression, $this->member]; - } + public function children() { return [&$this->expression, &$this->member]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/InstanceOfExpression.class.php b/src/main/php/lang/ast/nodes/InstanceOfExpression.class.php index 9b1e06c..8307eba 100755 --- a/src/main/php/lang/ast/nodes/InstanceOfExpression.class.php +++ b/src/main/php/lang/ast/nodes/InstanceOfExpression.class.php @@ -13,5 +13,5 @@ public function __construct($expression, $type, $line= 1) { } /** @return iterable */ - public function children() { return [$this->expression]; } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/InvokeExpression.class.php b/src/main/php/lang/ast/nodes/InvokeExpression.class.php index 53703ee..792f249 100755 --- a/src/main/php/lang/ast/nodes/InvokeExpression.class.php +++ b/src/main/php/lang/ast/nodes/InvokeExpression.class.php @@ -13,9 +13,9 @@ public function __construct($expression, $arguments, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { yield $this->expression; - foreach ($this->arguments as $element) { + foreach ($this->arguments as &$element) { yield $element; } } diff --git a/src/main/php/lang/ast/nodes/LambdaExpression.class.php b/src/main/php/lang/ast/nodes/LambdaExpression.class.php index 30c9346..61cfe18 100755 --- a/src/main/php/lang/ast/nodes/LambdaExpression.class.php +++ b/src/main/php/lang/ast/nodes/LambdaExpression.class.php @@ -13,6 +13,6 @@ public function __construct($signature, $body, $static= false, $line= -1) { /** @return iterable */ public function children() { - return is_array($this->body) ? $this->body : [$this->body]; + return is_array($this->body) ? $this->body : [&$this->body]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/MatchCondition.class.php b/src/main/php/lang/ast/nodes/MatchCondition.class.php index fba6cd4..da5fc30 100755 --- a/src/main/php/lang/ast/nodes/MatchCondition.class.php +++ b/src/main/php/lang/ast/nodes/MatchCondition.class.php @@ -16,8 +16,8 @@ public function __construct($expressions, $body, $line= -1) { } /** @return iterable */ - public function children() { - foreach ($this->expressions as $expression) { + public function &children() { + foreach ($this->expressions as &$expression) { yield $expression; } yield $this->body; diff --git a/src/main/php/lang/ast/nodes/MatchExpression.class.php b/src/main/php/lang/ast/nodes/MatchExpression.class.php index aebbac6..fc71da3 100755 --- a/src/main/php/lang/ast/nodes/MatchExpression.class.php +++ b/src/main/php/lang/ast/nodes/MatchExpression.class.php @@ -20,9 +20,9 @@ public function __construct($expression, $cases, $default, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { if (null !== $this->expression) yield $this->expression; - foreach ($this->cases as $element) { + foreach ($this->cases as &$element) { yield $element; } if (null !== $this->default) yield $this->default; diff --git a/src/main/php/lang/ast/nodes/OffsetExpression.class.php b/src/main/php/lang/ast/nodes/OffsetExpression.class.php index 05b7522..f971228 100755 --- a/src/main/php/lang/ast/nodes/OffsetExpression.class.php +++ b/src/main/php/lang/ast/nodes/OffsetExpression.class.php @@ -14,6 +14,6 @@ public function __construct($expression, $offset, $line= -1) { /** @return iterable */ public function children() { - return $this->offset ? [$this->expression, $this->offset] : [$this->expression]; + return $this->offset ? [&$this->expression, &$this->offset] : [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/ReturnStatement.class.php b/src/main/php/lang/ast/nodes/ReturnStatement.class.php index 4baccdc..a043df0 100755 --- a/src/main/php/lang/ast/nodes/ReturnStatement.class.php +++ b/src/main/php/lang/ast/nodes/ReturnStatement.class.php @@ -17,7 +17,5 @@ public function __construct($expression= null, $line= -1) { } /** @return iterable */ - public function children() { - return $this->expression ? [$this->expression] : []; - } + public function children() { return $this->expression ? [&$this->expression] : []; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/ScopeExpression.class.php b/src/main/php/lang/ast/nodes/ScopeExpression.class.php index ea3d47b..a374e14 100755 --- a/src/main/php/lang/ast/nodes/ScopeExpression.class.php +++ b/src/main/php/lang/ast/nodes/ScopeExpression.class.php @@ -14,6 +14,6 @@ public function __construct($type, $member, $line= -1) { /** @return iterable */ public function children() { - return $this->type instanceof parent ? [$this->type, $this->member] : [$this->member]; + return $this->type instanceof parent ? [&$this->type, &$this->member] : [&$this->member]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/SwitchStatement.class.php b/src/main/php/lang/ast/nodes/SwitchStatement.class.php index 4e8cf7c..9fd4a10 100755 --- a/src/main/php/lang/ast/nodes/SwitchStatement.class.php +++ b/src/main/php/lang/ast/nodes/SwitchStatement.class.php @@ -18,9 +18,9 @@ public function __construct($expression, $cases, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { yield $this->expression; - foreach ($this->cases as $element) { + foreach ($this->cases as &$element) { yield $element; } } diff --git a/src/main/php/lang/ast/nodes/TernaryExpression.class.php b/src/main/php/lang/ast/nodes/TernaryExpression.class.php index 543b7fa..cf3f3b9 100755 --- a/src/main/php/lang/ast/nodes/TernaryExpression.class.php +++ b/src/main/php/lang/ast/nodes/TernaryExpression.class.php @@ -14,7 +14,7 @@ public function __construct($condition, $expression, $otherwise, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { yield $this->condition; if ($this->expression) { yield $this->expression; diff --git a/src/main/php/lang/ast/nodes/ThrowExpression.class.php b/src/main/php/lang/ast/nodes/ThrowExpression.class.php index 275a2d4..8a53fd1 100755 --- a/src/main/php/lang/ast/nodes/ThrowExpression.class.php +++ b/src/main/php/lang/ast/nodes/ThrowExpression.class.php @@ -12,7 +12,5 @@ public function __construct($expression, $line= -1) { } /** @return iterable */ - public function children() { - return [$this->expression]; - } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/ThrowStatement.class.php b/src/main/php/lang/ast/nodes/ThrowStatement.class.php index dfb09d9..b412191 100755 --- a/src/main/php/lang/ast/nodes/ThrowStatement.class.php +++ b/src/main/php/lang/ast/nodes/ThrowStatement.class.php @@ -12,7 +12,5 @@ public function __construct($expression, $line= -1) { } /** @return iterable */ - public function children() { - return [$this->expression]; - } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/TryStatement.class.php b/src/main/php/lang/ast/nodes/TryStatement.class.php index d03d5cb..bd93318 100755 --- a/src/main/php/lang/ast/nodes/TryStatement.class.php +++ b/src/main/php/lang/ast/nodes/TryStatement.class.php @@ -14,11 +14,11 @@ public function __construct($body, $catches, $finally, $line= -1) { } /** @return iterable */ - public function children() { - foreach ($this->body as $element) { + public function &children() { + foreach ($this->body as &$element) { yield $element; } - foreach ($this->catches as $catch) { + foreach ($this->catches as &$catch) { yield $catch; } if ($this->finally) { diff --git a/src/main/php/lang/ast/nodes/UnaryExpression.class.php b/src/main/php/lang/ast/nodes/UnaryExpression.class.php index fde37f7..7048335 100755 --- a/src/main/php/lang/ast/nodes/UnaryExpression.class.php +++ b/src/main/php/lang/ast/nodes/UnaryExpression.class.php @@ -13,5 +13,5 @@ public function __construct($kind, $expression, $operator, $line= -1) { } /** @return iterable */ - public function children() { return [$this->expression]; } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/UnpackExpression.class.php b/src/main/php/lang/ast/nodes/UnpackExpression.class.php index f50dd95..9ff7036 100755 --- a/src/main/php/lang/ast/nodes/UnpackExpression.class.php +++ b/src/main/php/lang/ast/nodes/UnpackExpression.class.php @@ -12,5 +12,5 @@ public function __construct($expression, $line= -1) { } /** @return iterable */ - public function children() { return [$this->expression]; } + public function children() { return [&$this->expression]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/UsingStatement.class.php b/src/main/php/lang/ast/nodes/UsingStatement.class.php index aa715f0..0f89a0a 100755 --- a/src/main/php/lang/ast/nodes/UsingStatement.class.php +++ b/src/main/php/lang/ast/nodes/UsingStatement.class.php @@ -13,11 +13,11 @@ public function __construct($arguments, $body, $line= -1) { } /** @return iterable */ - public function children() { - foreach ($this->arguments as $element) { + public function &children() { + foreach ($this->arguments as &$element) { yield $element; } - foreach ($this->body as $element) { + foreach ($this->body as &$element) { yield $element; } } diff --git a/src/main/php/lang/ast/nodes/WhileLoop.class.php b/src/main/php/lang/ast/nodes/WhileLoop.class.php index bcd33a5..20da03e 100755 --- a/src/main/php/lang/ast/nodes/WhileLoop.class.php +++ b/src/main/php/lang/ast/nodes/WhileLoop.class.php @@ -13,9 +13,9 @@ public function __construct($expression, $body, $line= -1) { } /** @return iterable */ - public function children() { + public function &children() { yield $this->expression; - foreach ($this->body as $element) { + foreach ($this->body as &$element) { yield $element; } } diff --git a/src/main/php/lang/ast/nodes/YieldExpression.class.php b/src/main/php/lang/ast/nodes/YieldExpression.class.php index 7143b9e..588d784 100755 --- a/src/main/php/lang/ast/nodes/YieldExpression.class.php +++ b/src/main/php/lang/ast/nodes/YieldExpression.class.php @@ -14,6 +14,6 @@ public function __construct($key, $value, $line= -1) { /** @return iterable */ public function children() { - return $this->key ? [$this->key, $this->value] : [$this->value]; + return $this->key ? [&$this->key, &$this->value] : [&$this->value]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/YieldFromExpression.class.php b/src/main/php/lang/ast/nodes/YieldFromExpression.class.php index 81b69f6..bb645d4 100755 --- a/src/main/php/lang/ast/nodes/YieldFromExpression.class.php +++ b/src/main/php/lang/ast/nodes/YieldFromExpression.class.php @@ -12,5 +12,5 @@ public function __construct($iterable, $line= -1) { } /** @return iterable */ - public function children() { return [$this->iterable]; } + public function children() { return [&$this->iterable]; } } \ No newline at end of file From 54392db6ce219a835db94e5026212d3d97ca0060 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 21 May 2023 15:01:19 +0200 Subject: [PATCH 12/15] Add support for by-ref hooks, e.g. `&get` --- src/main/php/lang/ast/nodes/Hook.class.php | 6 +++--- src/main/php/lang/ast/syntax/PHP.class.php | 11 +++++++++-- .../lang/ast/unittest/parse/MembersTest.class.php | 14 +++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/main/php/lang/ast/nodes/Hook.class.php b/src/main/php/lang/ast/nodes/Hook.class.php index 169ae0e..c9ba74a 100755 --- a/src/main/php/lang/ast/nodes/Hook.class.php +++ b/src/main/php/lang/ast/nodes/Hook.class.php @@ -4,13 +4,13 @@ class Hook extends Node { public $kind= 'hook'; - public $modifiers, $type, $field, $expression, $parameter, $holder; + public $modifiers, $type, $expression, $byref, $parameter, $holder; - public function __construct($modifiers, $type, $field, $expression, $parameter= null, $line= -1, $holder= null) { + public function __construct($modifiers, $type, $expression, $byref= false, $parameter= null, $line= -1, $holder= null) { $this->modifiers= $modifiers; $this->type= $type; - $this->field= $field; $this->expression= $expression; + $this->byref= $byref; $this->parameter= $parameter; $this->line= $line; $this->holder= $holder; diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index f50bde3..b9adc35 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1320,7 +1320,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $expr= $this->expression($parse, 0); $parse->expecting(';', 'property hook'); - $body[$lookup]->hooks['get']= new Hook([], 'get', $name, $expr, null, $line, $holder); + $body[$lookup]->hooks['get']= new Hook([], 'get', $expr, false, null, $line, $holder); return; } else if ('{' === $parse->token->value) { $parse->forward(); @@ -1336,6 +1336,13 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $modifiers= []; } + if ('&' === $parse->token->value) { + $byref= true; + $parse->forward(); + } else { + $byref= false; + } + $hook= $parse->token->value; $parse->forward(); @@ -1383,7 +1390,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $expr= null; } - $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $name, $expr, $parameter, $line, $holder); + $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $expr, $byref, $parameter, $line, $holder); } $parse->forward(); 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 7fb23ad..086cbbb 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -137,8 +137,8 @@ public function property_with_get_and_set_hooks() { $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); $parameter= new Parameter('value', null, null, false, false, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', 'a', new Block([$return], self::LINE), null, self::LINE, new IsValue('\\A')); - $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['get']= new Hook([], 'get', new Block([$return], self::LINE), false, null, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook([], 'set', new Block([], self::LINE), false, $parameter, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set($value) { } } }'); @@ -148,7 +148,7 @@ public function property_with_get_and_set_hooks() { public function property_with_short_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE, new IsValue('\\A')); + $prop->hooks['get']= new Hook([], 'get', new Literal('"Hello"', self::LINE), false, null, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); @@ -158,7 +158,7 @@ public function property_with_short_get_hook() { public function property_with_abbreviated_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', 'a', new Literal('"Hello"', self::LINE), null, self::LINE, new IsValue('\\A')); + $prop->hooks['get']= new Hook([], 'get', new Literal('"Hello"', self::LINE), false, null, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a => "Hello"; }'); @@ -169,7 +169,7 @@ public function property_with_typed_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook([], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook([], 'set', new Block([], self::LINE), false, $parameter, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { set(string $value) { } } }'); @@ -180,7 +180,7 @@ public function property_with_final_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook(['final'], 'set', 'a', new Block([], self::LINE), $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook(['final'], 'set', new Block([], self::LINE), false, $parameter, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { final set(string $value) { } } }'); @@ -191,7 +191,7 @@ public function property_with_abstract_hook() { $class= new ClassDeclaration(['abstract'], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook(['abstract'], 'set', 'a', null, $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook(['abstract'], 'set', null, false, $parameter, self::LINE, new IsValue('\\A')); $class->declare($prop); $this->assertParsed([$class], 'abstract class A { public $a { abstract set(string $value); } }'); From 0e57d4ae2f2a9f6e675ba2f7be56042502782ecf Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Thu, 8 Jun 2023 13:58:06 +0200 Subject: [PATCH 13/15] MFH --- .github/workflows/ci.yml | 2 +- ChangeLog.md | 8 ++++ .../php/lang/ast/nodes/Constant.class.php | 6 +-- .../php/lang/ast/nodes/EnumCase.class.php | 6 +-- src/main/php/lang/ast/nodes/Hook.class.php | 5 +- src/main/php/lang/ast/nodes/Method.class.php | 6 +-- .../php/lang/ast/nodes/Property.class.php | 6 +-- .../lang/ast/nodes/TypeDeclaration.class.php | 9 +--- src/main/php/lang/ast/syntax/PHP.class.php | 48 +++++++++---------- .../unittest/parse/AttributesTest.class.php | 8 ++++ .../ast/unittest/parse/MembersTest.class.php | 14 +++--- 11 files changed, 59 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b93ab4f..c96170a 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,4 +52,4 @@ jobs: sh .github/workflows/composer.sh - name: Run test suite - run: sh xp-run xp.test.Runner src/test/php + run: sh xp-run xp.test.Runner -r Dots src/test/php diff --git a/ChangeLog.md b/ChangeLog.md index b53e176..7e7ad40 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,14 @@ XP AST ChangeLog ## ?.?.? / ????-??-?? +## 10.2.1 / 2023-06-03 + +* Fix trailing commas in annotation lists - @thekid + +## 10.2.0 / 2023-05-27 + +* Merged PR #47: Remove holder property for members - @thekid + ## 10.1.0 / 2023-05-21 * Merged PR #46: Implement returning by reference from methods - @thekid diff --git a/src/main/php/lang/ast/nodes/Constant.class.php b/src/main/php/lang/ast/nodes/Constant.class.php index 49cc118..fcd3769 100755 --- a/src/main/php/lang/ast/nodes/Constant.class.php +++ b/src/main/php/lang/ast/nodes/Constant.class.php @@ -2,15 +2,13 @@ class Constant extends Annotated implements Member { public $kind= 'const'; - public $name, $modifiers, $expression, $type, $holder; + public $name, $modifiers, $expression, $type; - public function __construct($modifiers, $name, $type, $expression, $annotations= null, $comment= null, $line= -1, $holder= null) { + public function __construct($modifiers, $name, $type, $expression, $annotations= null, $comment= null, $line= -1) { $this->modifiers= $modifiers; $this->name= $name; $this->type= $type; $this->expression= $expression; - $this->holder= $holder; - parent::__construct($annotations, $comment, $line); } diff --git a/src/main/php/lang/ast/nodes/EnumCase.class.php b/src/main/php/lang/ast/nodes/EnumCase.class.php index 947a70a..f551573 100755 --- a/src/main/php/lang/ast/nodes/EnumCase.class.php +++ b/src/main/php/lang/ast/nodes/EnumCase.class.php @@ -2,13 +2,11 @@ class EnumCase extends Annotated implements Member { public $kind= 'enumcase'; - public $name, $expression, $holder; + public $name, $expression; - public function __construct($name, $expression, $annotations, $comment, $line= -1, $holder= null) { + public function __construct($name, $expression, $annotations, $comment, $line= -1) { $this->name= $name; $this->expression= $expression; - $this->holder= $holder; - parent::__construct($annotations, $comment, $line); } diff --git a/src/main/php/lang/ast/nodes/Hook.class.php b/src/main/php/lang/ast/nodes/Hook.class.php index c9ba74a..f0731ac 100755 --- a/src/main/php/lang/ast/nodes/Hook.class.php +++ b/src/main/php/lang/ast/nodes/Hook.class.php @@ -4,15 +4,14 @@ class Hook extends Node { public $kind= 'hook'; - public $modifiers, $type, $expression, $byref, $parameter, $holder; + public $modifiers, $type, $expression, $byref, $parameter; - public function __construct($modifiers, $type, $expression, $byref= false, $parameter= null, $line= -1, $holder= null) { + public function __construct($modifiers, $type, $expression, $byref= false, $parameter= null, $line= -1) { $this->modifiers= $modifiers; $this->type= $type; $this->expression= $expression; $this->byref= $byref; $this->parameter= $parameter; $this->line= $line; - $this->holder= $holder; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/Method.class.php b/src/main/php/lang/ast/nodes/Method.class.php index 52d512c..6dad794 100755 --- a/src/main/php/lang/ast/nodes/Method.class.php +++ b/src/main/php/lang/ast/nodes/Method.class.php @@ -2,15 +2,13 @@ class Method extends Annotated implements Member { public $kind= 'method'; - public $name, $modifiers, $signature, $body, $holder; + public $name, $modifiers, $signature, $body; - public function __construct($modifiers, $name, $signature, $body= null, $annotations= null, $comment= null, $line= -1, $holder= null) { + public function __construct($modifiers, $name, $signature, $body= null, $annotations= null, $comment= null, $line= -1) { $this->name= $name; $this->modifiers= $modifiers; $this->signature= $signature; $this->body= $body; - $this->holder= $holder; - parent::__construct($annotations, $comment, $line); } diff --git a/src/main/php/lang/ast/nodes/Property.class.php b/src/main/php/lang/ast/nodes/Property.class.php index 7966f70..eba0dab 100755 --- a/src/main/php/lang/ast/nodes/Property.class.php +++ b/src/main/php/lang/ast/nodes/Property.class.php @@ -2,16 +2,14 @@ class Property extends Annotated implements Member { public $kind= 'property'; - public $name, $modifiers, $expression, $type, $holder; + public $name, $modifiers, $expression, $type; public $hooks= null; - public function __construct($modifiers, $name, $type, $expression= null, $annotations= null, $comment= null, $line= -1, $holder= null) { + public function __construct($modifiers, $name, $type, $expression= null, $annotations= null, $comment= null, $line= -1) { $this->modifiers= $modifiers; $this->name= $name; $this->type= $type; $this->expression= $expression; - $this->holder= $holder; - parent::__construct($annotations, $comment, $line); } diff --git a/src/main/php/lang/ast/nodes/TypeDeclaration.class.php b/src/main/php/lang/ast/nodes/TypeDeclaration.class.php index 5aa6dd0..d76aec2 100755 --- a/src/main/php/lang/ast/nodes/TypeDeclaration.class.php +++ b/src/main/php/lang/ast/nodes/TypeDeclaration.class.php @@ -9,12 +9,7 @@ abstract class TypeDeclaration extends Annotated { public function __construct($modifiers, $name, $body= [], $annotations= null, $comment= null, $line= -1) { $this->modifiers= $modifiers; $this->name= $name; - $this->body= []; - foreach ($body as $lookup => $node) { - $node->holder= $this->name; - $this->body[$lookup]= $node; - } - + $this->body= $body; parent::__construct($annotations, $comment, $line); } @@ -71,7 +66,6 @@ public function declare(Member $member) { $lookup= $member->lookup(); if (isset($this->body[$lookup])) return false; - $member->holder= $this->name; $this->body[$lookup]= $member; return true; } @@ -86,7 +80,6 @@ public function overwrite(Member $member) { $lookup= $member->lookup(); $overwritten= isset($this->body[$lookup]); - $member->holder= $this->name; $this->body[$lookup]= $member; return $overwritten; } diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 7561184..5fa758c 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -888,7 +888,7 @@ public function __construct() { $decl= new InterfaceDeclaration([], $name, $parents, [], null, $comment, $token->line); $parse->expecting('{', 'interface'); - $decl->body= $this->typeBody($parse, $decl->name); + $decl->body= $this->typeBody($parse); $parse->expecting('}', 'interface'); return $decl; @@ -901,7 +901,7 @@ public function __construct() { $decl= new TraitDeclaration([], $name, [], null, $comment, $token->line); $parse->expecting('{', 'trait'); - $decl->body= $this->typeBody($parse, $decl->name); + $decl->body= $this->typeBody($parse); $parse->expecting('}', 'trait'); return $decl; @@ -939,13 +939,13 @@ public function __construct() { $decl= new EnumDeclaration([], $name, $base, $implements, [], null, $comment, $token->line); $parse->expecting('{', 'enum'); - $decl->body= $this->typeBody($parse, $decl->name); + $decl->body= $this->typeBody($parse); $parse->expecting('}', 'enum'); return $decl; }); - $this->body('case', function($parse, &$body, $meta, $modifiers, $holder) { + $this->body('case', function($parse, &$body, $meta, $modifiers) { $comment= $parse->comment; $parse->comment= null; @@ -962,13 +962,13 @@ public function __construct() { $expr= null; } - $body[$name]= new EnumCase($name, $expr, $meta[DETAIL_ANNOTATIONS] ?? null, $comment, $line, $holder); + $body[$name]= new EnumCase($name, $expr, $meta[DETAIL_ANNOTATIONS] ?? null, $comment, $line); } while (',' === $parse->token->value && true | $parse->forward()); $parse->expecting(';', 'case'); }); - $this->body('use', function($parse, &$body, $meta, $modifiers, $holder) { + $this->body('use', function($parse, &$body, $meta, $modifiers) { $line= $parse->token->line; $parse->forward(); @@ -1010,7 +1010,7 @@ public function __construct() { $body[]= new UseExpression($types, $aliases, $line); }); - $this->body('const', function($parse, &$body, $meta, $modifiers, $holder) { + $this->body('const', function($parse, &$body, $meta, $modifiers) { $comment= $parse->comment; $parse->comment= null; $parse->forward(); @@ -1047,8 +1047,7 @@ public function __construct() { $this->expression($parse, 0), $meta[DETAIL_ANNOTATIONS] ?? null, $comment, - $line, - $holder + $line ); if (',' === $parse->token->value) { $parse->forward(); @@ -1057,11 +1056,11 @@ public function __construct() { $parse->expecting(';', 'constant declaration'); }); - $this->body('$', function($parse, &$body, $meta, $modifiers, $holder) { - $this->properties($parse, $body, $meta, $modifiers, null, $holder); + $this->body('$', function($parse, &$body, $meta, $modifiers) { + $this->properties($parse, $body, $meta, $modifiers, null); }); - $this->body('function', function($parse, &$body, $meta, $modifiers, $holder) { + $this->body('function', function($parse, &$body, $meta, $modifiers) { $comment= $parse->comment; $parse->comment= null; $line= $parse->token->line; @@ -1102,8 +1101,7 @@ public function __construct() { $statements, $meta[DETAIL_ANNOTATIONS] ?? null, $comment, - $line, - $holder + $line ); }); } @@ -1293,7 +1291,7 @@ private function type0($parse, $optional) { return isset($literal[$type]) ? new IsLiteral($type) : new IsValue($type); } - private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { + private function properties($parse, &$body, $meta, $modifiers, $type) { $comment= $parse->comment; $parse->comment= null; $annotations= $meta[DETAIL_ANNOTATIONS] ?? null; @@ -1321,7 +1319,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $expr= null; } - $body[$lookup]= new Property($modifiers, $name, $type, $expr, $annotations, $comment, $line, $holder); + $body[$lookup]= new Property($modifiers, $name, $type, $expr, $annotations, $comment, $line); // Check for property hooks if (';' === $parse->token->value) { @@ -1335,7 +1333,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $expr= $this->expression($parse, 0); $parse->expecting(';', 'property hook'); - $body[$lookup]->hooks['get']= new Hook([], 'get', $expr, false, null, $line, $holder); + $body[$lookup]->hooks['get']= new Hook([], 'get', $expr, false, null, $line); return; } else if ('{' === $parse->token->value) { $parse->forward(); @@ -1405,7 +1403,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { $expr= null; } - $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $expr, $byref, $parameter, $line, $holder); + $body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $expr, $byref, $parameter, $line); } $parse->forward(); @@ -1420,7 +1418,7 @@ private function properties($parse, &$body, $meta, $modifiers, $type, $holder) { private function annotations($parse, $context) { $annotations= new Annotations([], $parse->token->line); - do { + while ('name' === $parse->token->kind) { $name= ltrim($parse->scope->resolve($parse->token->value), '\\'); $parse->forward(); @@ -1431,7 +1429,9 @@ private function annotations($parse, $context) { } else { $annotations->add(new Annotation($name, [], $parse->token->line)); } - } while (',' === $parse->token->value && true | $parse->forward()); + + if (',' === $parse->token->value) $parse->forward(); + } $parse->expecting(']', $context); return $annotations; @@ -1511,7 +1511,7 @@ public function body($id, $func) { $this->body[$id]= $func->bindTo($this, static::class); } - public function typeBody($parse, $holder) { + public function typeBody($parse) { static $modifier= [ 'private' => true, 'protected' => true, @@ -1530,14 +1530,14 @@ public function typeBody($parse, $holder) { $modifiers[]= $parse->token->value; $parse->forward(); } else if ($f= $this->body[$parse->token->value] ?? $this->body['@'.$parse->token->kind] ?? null) { - $f($parse, $body, $meta, $modifiers, $holder); + $f($parse, $body, $meta, $modifiers); $modifiers= []; $meta= []; } else if ('#[' === $parse->token->value) { $parse->forward(); $meta[DETAIL_ANNOTATIONS]= $this->annotations($parse, 'member annotations'); } else if ($type= $this->type($parse)) { - $this->properties($parse, $body, $meta, $modifiers, $type, $holder); + $this->properties($parse, $body, $meta, $modifiers, $type); $modifiers= []; $meta= []; } else { @@ -1636,7 +1636,7 @@ public function class($parse, $name, $comment= null, $modifiers= []) { $decl= new ClassDeclaration($modifiers, $name, $parent, $implements, [], null, $comment, $line); $parse->expecting('{', 'class'); - $decl->body= $this->typeBody($parse, $decl->name); + $decl->body= $this->typeBody($parse); $parse->expecting('}', 'class'); return $decl; 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 2aa8663..5e7c16a 100755 --- a/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php @@ -135,6 +135,14 @@ public function two_annotations() { ); } + #[Test] + public function trailing_comma() { + $this->assertAnnotated( + ['Author' => [new Literal('"Test"', self::LINE)]], + $this->type('#[Author("Test"), ] class T { }') + ); + } + #[Test] public function name_resolved_to_namespace() { $this->assertAnnotated( 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 29dfd48..e160684 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -145,8 +145,8 @@ public function property_with_get_and_set_hooks() { $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $return= new ReturnStatement(new Literal('"Hello"', self::LINE), self::LINE); $parameter= new Parameter('value', null, null, false, false, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', new Block([$return], self::LINE), false, null, self::LINE, new IsValue('\\A')); - $prop->hooks['set']= new Hook([], 'set', new Block([], self::LINE), false, $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['get']= new Hook([], 'get', new Block([$return], self::LINE), false, null, self::LINE); + $prop->hooks['set']= new Hook([], 'set', new Block([], self::LINE), false, $parameter, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get { return "Hello"; } set($value) { } } }'); @@ -156,7 +156,7 @@ public function property_with_get_and_set_hooks() { public function property_with_short_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', new Literal('"Hello"', self::LINE), false, null, self::LINE, new IsValue('\\A')); + $prop->hooks['get']= new Hook([], 'get', new Literal('"Hello"', self::LINE), false, null, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); @@ -166,7 +166,7 @@ public function property_with_short_get_hook() { public function property_with_abbreviated_get_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); - $prop->hooks['get']= new Hook([], 'get', new Literal('"Hello"', self::LINE), false, null, self::LINE, new IsValue('\\A')); + $prop->hooks['get']= new Hook([], 'get', new Literal('"Hello"', self::LINE), false, null, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a => "Hello"; }'); @@ -177,7 +177,7 @@ public function property_with_typed_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook([], 'set', new Block([], self::LINE), false, $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook([], 'set', new Block([], self::LINE), false, $parameter, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { set(string $value) { } } }'); @@ -188,7 +188,7 @@ public function property_with_final_hook() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook(['final'], 'set', new Block([], self::LINE), false, $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook(['final'], 'set', new Block([], self::LINE), false, $parameter, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { final set(string $value) { } } }'); @@ -199,7 +199,7 @@ public function property_with_abstract_hook() { $class= new ClassDeclaration(['abstract'], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook(['abstract'], 'set', null, false, $parameter, self::LINE, new IsValue('\\A')); + $prop->hooks['set']= new Hook(['abstract'], 'set', null, false, $parameter, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'abstract class A { public $a { abstract set(string $value); } }'); From 33d6f86f7004efc26fe39f97204f3795d295a288 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 18 May 2024 18:58:22 +0800 Subject: [PATCH 14/15] Remove abstract hooks, they are no longer included in the RFC See https://wiki.php.net/rfc/property-hooks#abstract_properties --- src/main/php/lang/ast/syntax/PHP.class.php | 3 --- src/test/php/lang/ast/unittest/parse/MembersTest.class.php | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 067dfea..d2ce08b 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1350,9 +1350,6 @@ private function properties($parse, &$body, $meta, $modifiers, $type) { if ('final' === $parse->token->value) { $modifiers= ['final']; $parse->forward(); - } else if ('abstract' === $parse->token->value) { - $modifiers= ['abstract']; - $parse->forward(); } else { $modifiers= []; } 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 e160684..bbcdb0d 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -197,12 +197,12 @@ public function property_with_final_hook() { #[Test] public function property_with_abstract_hook() { $class= new ClassDeclaration(['abstract'], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $prop= new Property(['public'], 'a', null, null, null, null, self::LINE); + $prop= new Property(['public', 'abstract'], 'a', null, null, null, null, self::LINE); $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook(['abstract'], 'set', null, false, $parameter, self::LINE); + $prop->hooks['set']= new Hook([], 'set', null, false, $parameter, self::LINE); $class->declare($prop); - $this->assertParsed([$class], 'abstract class A { public $a { abstract set(string $value); } }'); + $this->assertParsed([$class], 'abstract class A { public abstract $a { set(string $value); } }'); } #[Test] From 6bfe8c0378c94ea4ef8840530299c343ca9f1633 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 18 May 2024 18:59:20 +0800 Subject: [PATCH 15/15] Simplify abstract property test --- src/test/php/lang/ast/unittest/parse/MembersTest.class.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 bbcdb0d..ae8c2e5 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -195,14 +195,13 @@ public function property_with_final_hook() { } #[Test] - public function property_with_abstract_hook() { + public function abstract_property_with_hook() { $class= new ClassDeclaration(['abstract'], new IsValue('\\A'), null, [], [], null, null, self::LINE); $prop= new Property(['public', 'abstract'], 'a', null, null, null, null, self::LINE); - $parameter= new Parameter('value', new IsLiteral('string'), null, false, false, null, null, null, self::LINE); - $prop->hooks['set']= new Hook([], 'set', null, false, $parameter, self::LINE); + $prop->hooks['set']= new Hook([], 'set', null, false, null, self::LINE); $class->declare($prop); - $this->assertParsed([$class], 'abstract class A { public abstract $a { set(string $value); } }'); + $this->assertParsed([$class], 'abstract class A { public abstract $a { set; } }'); } #[Test]