From 6abd9a7d01009305c604eb744647e5272e695141 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 20 Jun 2026 11:56:50 +0200 Subject: [PATCH 1/2] Emit strings and numbers as `lang.ast.nodes.Scalar` --- src/main/php/lang/ast/nodes/Scalar.class.php | 16 +++++++++ src/main/php/lang/ast/syntax/PHP.class.php | 3 +- .../unittest/parse/AttributesTest.class.php | 36 +++++++++++-------- .../ast/unittest/parse/ClosuresTest.class.php | 3 +- .../ast/unittest/parse/CommentTest.class.php | 26 +++++++------- .../unittest/parse/ConditionalTest.class.php | 27 +++++++++----- .../unittest/parse/FunctionsTest.class.php | 11 +++--- .../ast/unittest/parse/InvokeTest.class.php | 21 +++++++---- .../ast/unittest/parse/LambdasTest.class.php | 20 +++++++++-- .../ast/unittest/parse/LiteralsTest.class.php | 34 +++++++++--------- .../ast/unittest/parse/LoopsTest.class.php | 9 ++--- .../parse/MatchExpressionTest.class.php | 29 +++++++++------ .../ast/unittest/parse/MembersTest.class.php | 36 ++++++++++--------- .../ast/unittest/parse/OperatorTest.class.php | 23 ++++++------ .../unittest/parse/StartTokensTest.class.php | 9 +++-- .../ast/unittest/parse/TypesTest.class.php | 6 ++-- .../unittest/parse/VariablesTest.class.php | 6 ++-- 17 files changed, 198 insertions(+), 117 deletions(-) create mode 100755 src/main/php/lang/ast/nodes/Scalar.class.php diff --git a/src/main/php/lang/ast/nodes/Scalar.class.php b/src/main/php/lang/ast/nodes/Scalar.class.php new file mode 100755 index 00000000..51c346b7 --- /dev/null +++ b/src/main/php/lang/ast/nodes/Scalar.class.php @@ -0,0 +1,16 @@ +literal= $literal; + $this->kind= $kind; + $this->line= $line; + } + + /** @return string */ + public function __toString() { return $this->literal; } +} \ 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 2b3a932a..b846e818 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -49,6 +49,7 @@ Placeholder, Property, ReturnStatement, + Scalar, ScopeExpression, Signature, StaticLocals, @@ -484,7 +485,7 @@ public function __construct() { }); $this->prefix('(literal)', 0, function($parse, $token) { - return new Literal($token->value, $token->line); + return new Scalar($token->value, $token->kind, $token->line); }); $this->prefix('(name)', 0, function($parse, $token) { 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 a6af550d..bb84e818 100755 --- a/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/AttributesTest.class.php @@ -1,6 +1,6 @@ []]]; - yield ['#[Test(version: 1)]', ['Test' => ['version' => new Literal('1', self::LINE)]]]; - yield ['#[Service, Version(1)]', ['Service' => [], 'Version' => [new Literal('1', self::LINE)]]]; + yield ['#[Test(version: 1)]', ['Test' => ['version' => new Scalar('1', 'integer', self::LINE)]]]; + yield ['#[Service, Version(1)]', ['Service' => [], 'Version' => [new Scalar('1', 'integer', self::LINE)]]]; } /** @@ -55,7 +55,15 @@ public function with_empty_arguments() { ); } - #[Test, Values(['"test"', '1', '1.5', 'true', 'false', 'null'])] + #[Test, Values([['"test"', 'string'], ['1', 'integer'], ['1.5', 'decimal']])] + public function with_scalar_argument($value, $kind) { + $this->assertAnnotated( + ['Service' => [new Scalar($value, $kind, self::LINE)]], + $this->type('#[Service('.$value.')] class T { }') + ); + } + + #[Test, Values(['true', 'false', 'null'])] public function with_literal_argument($value) { $this->assertAnnotated( ['Service' => [new Literal($value, self::LINE)]], @@ -66,8 +74,8 @@ public function with_literal_argument($value) { #[Test] public function with_array_argument() { $elements= [ - [null, new Literal('1', self::LINE)], - [null, new Literal('2', self::LINE)], + [null, new Scalar('1', 'integer', self::LINE)], + [null, new Scalar('2', 'integer', self::LINE)], ]; $this->assertAnnotated( ['Service' => [new ArrayLiteral($elements, self::LINE)]], @@ -78,8 +86,8 @@ public function with_array_argument() { #[Test] public function with_map_argument() { $elements= [ - [new Literal('"one"', self::LINE), new Literal('1', self::LINE)], - [new Literal('"two"', self::LINE), new Literal('2', self::LINE)], + [new Scalar('"one"', 'string', self::LINE), new Scalar('1', 'integer', self::LINE)], + [new Scalar('"two"', 'string', self::LINE), new Scalar('2', 'integer', self::LINE)], ]; $this->assertAnnotated( ['Service' => [new ArrayLiteral($elements, self::LINE)]], @@ -90,7 +98,7 @@ public function with_map_argument() { #[Test] public function with_named_arguments() { $this->assertAnnotated( - ['Impl' => ['class' => new Literal('"T"', self::LINE), 'version' => new Literal('1', self::LINE)]], + ['Impl' => ['class' => new Scalar('"T"', 'string', self::LINE), 'version' => new Scalar('1', 'integer', self::LINE)]], $this->type('#[Impl(class: "T", version: 1)] class T { }') ); } @@ -98,8 +106,8 @@ public function with_named_arguments() { #[Test] public function multiline() { $elements= [ - [new Literal('"one"', self::LINE + 2), new Literal('1', self::LINE + 2)], - [new Literal('"two"', self::LINE + 3), new Literal('2', self::LINE + 3)], + [new Scalar('"one"', 'string', self::LINE + 2), new Scalar('1', 'integer', self::LINE + 2)], + [new Scalar('"two"', 'string', self::LINE + 3), new Scalar('2', 'integer', self::LINE + 3)], ]; $this->assertAnnotated(['Values' => [new ArrayLiteral($elements, self::LINE + 1)]], $this->type(' #[Values([ @@ -127,7 +135,7 @@ class T { } #[Test] public function with_two_arguments() { $this->assertAnnotated( - ['Service' => [new Literal('1', self::LINE), new Literal('2', self::LINE)]], + ['Service' => [new Scalar('1', 'integer', self::LINE), new Scalar('2', 'integer', self::LINE)]], $this->type('#[Service(1, 2)] class T { }') ); } @@ -135,7 +143,7 @@ public function with_two_arguments() { #[Test] public function two_annotations() { $this->assertAnnotated( - ['Author' => [new Literal('"Test"', self::LINE)], 'Version' => [new Literal('2', self::LINE)]], + ['Author' => [new Scalar('"Test"', 'string', self::LINE)], 'Version' => [new Scalar('2', 'integer', self::LINE)]], $this->type('#[Author("Test"), Version(2)] class T { }') ); } @@ -143,7 +151,7 @@ public function two_annotations() { #[Test] public function trailing_comma() { $this->assertAnnotated( - ['Author' => [new Literal('"Test"', self::LINE)]], + ['Author' => [new Scalar('"Test"', 'string', self::LINE)]], $this->type('#[Author("Test"), ] class T { }') ); } diff --git a/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php b/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php index 10049767..106e6f91 100755 --- a/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php @@ -9,6 +9,7 @@ Literal, Parameter, ReturnStatement, + Scalar, Signature, Variable }; @@ -25,7 +26,7 @@ public function returns() { new BinaryExpression( new Variable('a', self::LINE), '+', - new Literal('1', self::LINE), + new Scalar('1', 'integer', self::LINE), self::LINE ), self::LINE diff --git a/src/test/php/lang/ast/unittest/parse/CommentTest.class.php b/src/test/php/lang/ast/unittest/parse/CommentTest.class.php index d6d34f2b..3d56d6f8 100755 --- a/src/test/php/lang/ast/unittest/parse/CommentTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/CommentTest.class.php @@ -1,6 +1,6 @@ assertParsed([new Literal('"test"', 3)], ' + $this->assertParsed([new Scalar('"test"', 'string', 3)], ' // This is a comment "test"; '); @@ -16,14 +16,14 @@ public function oneline_double_slash() { #[Test] public function oneline_double_slash_at_end() { - $this->assertParsed([new Literal('"test"', 2)], ' + $this->assertParsed([new Scalar('"test"', 'string', 2)], ' "test"; // This is a comment '); } #[Test] public function two_oneline_double_slash() { - $this->assertParsed([new Literal('"test"', 4)], ' + $this->assertParsed([new Scalar('"test"', 'string', 4)], ' // This is a comment // This is another "test"; @@ -32,7 +32,7 @@ public function two_oneline_double_slash() { #[Test] public function oneline_hashtag() { - $this->assertParsed([new Literal('"test"', 3)], ' + $this->assertParsed([new Scalar('"test"', 'string', 3)], ' # This is a comment "test"; '); @@ -40,14 +40,14 @@ public function oneline_hashtag() { #[Test] public function oneline_hashtag_at_end() { - $this->assertParsed([new Literal('"test"', 2)], ' + $this->assertParsed([new Scalar('"test"', 'string', 2)], ' "test"; # This is a comment '); } #[Test] public function two_oneline_hashtags() { - $this->assertParsed([new Literal('"test"', 4)], ' + $this->assertParsed([new Scalar('"test"', 'string', 4)], ' # This is a comment # This is another "test"; @@ -56,7 +56,7 @@ public function two_oneline_hashtags() { #[Test] public function oneline_slash_asterisk() { - $this->assertParsed([new Literal('"test"', 3)], ' + $this->assertParsed([new Scalar('"test"', 'string', 3)], ' /* This is a comment */ "test"; '); @@ -64,21 +64,21 @@ public function oneline_slash_asterisk() { #[Test] public function oneline_slash_asterisk_at_end() { - $this->assertParsed([new Literal('"test"', 2)], ' + $this->assertParsed([new Scalar('"test"', 'string', 2)], ' "test"; /* This is a comment */ '); } #[Test] public function oneline_slash_asterisk_inbetween() { - $this->assertParsed([new Literal('"before"', 2), new Literal('"after"', 2)], ' + $this->assertParsed([new Scalar('"before"', 'string', 2), new Scalar('"after"', 'string', 2)], ' "before"; /* This is a comment */ "after"; '); } #[Test] public function multiline_slash_asterisk() { - $this->assertParsed([new Literal('"test"', 5)], ' + $this->assertParsed([new Scalar('"test"', 'string', 5)], ' /* This is a comment * spanning multiple lines. */ @@ -88,7 +88,7 @@ public function multiline_slash_asterisk() { #[Test] public function apidoc_comment_at_end_discarded() { - $this->assertParsed([new Literal('"test"', 2)], ' + $this->assertParsed([new Scalar('"test"', 'string', 2)], ' "test"; /** Discarded */ '); } @@ -120,7 +120,7 @@ class T { } #[Test] public function apidoc_comment_attached_to_next_constant() { $class= new ClassDeclaration([], new IsValue('\\T'), null, [], [], null, null, 2); - $class->declare(new Constant(['public'], 'FIXTURE', null, new Literal('1', 4), null, new Comment('/** @api */', 3), 4)); + $class->declare(new Constant(['public'], 'FIXTURE', null, new Scalar('1', 'integer', 4), null, new Comment('/** @api */', 3), 4)); $this->assertParsed([$class], ' class T { diff --git a/src/test/php/lang/ast/unittest/parse/ConditionalTest.class.php b/src/test/php/lang/ast/unittest/parse/ConditionalTest.class.php index 44fd55ba..118a2227 100755 --- a/src/test/php/lang/ast/unittest/parse/ConditionalTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/ConditionalTest.class.php @@ -1,7 +1,18 @@ blocks[1], self::LINE)]; + $cases= [new CaseLabel(new Scalar('1', 'integer', self::LINE), $this->blocks[1], self::LINE)]; $this->assertParsed( [new SwitchStatement(new Variable('condition', self::LINE), $cases, self::LINE)], 'switch ($condition) { case 1: action1(); }' @@ -85,8 +96,8 @@ public function switch_with_class_constant() { #[Test] public function switch_with_two_cases() { $cases= [ - new CaseLabel(new Literal('1', self::LINE), $this->blocks[1], self::LINE), - new CaseLabel(new Literal('2', self::LINE), $this->blocks[2], self::LINE) + new CaseLabel(new Scalar('1', 'integer', self::LINE), $this->blocks[1], self::LINE), + new CaseLabel(new Scalar('2', 'integer', self::LINE), $this->blocks[2], self::LINE) ]; $this->assertParsed( [new SwitchStatement(new Variable('condition', self::LINE), $cases, self::LINE)], @@ -114,7 +125,7 @@ public function empty_match() { #[Test] public function match_with_trailing_comma() { $cases= [ - new MatchCondition([new Literal('1', self::LINE)], $this->blocks[1][0], self::LINE), + new MatchCondition([new Scalar('1', 'integer', self::LINE)], $this->blocks[1][0], self::LINE), ]; $this->assertParsed( [new MatchExpression(new Variable('condition', self::LINE), $cases, null, self::LINE)], @@ -125,8 +136,8 @@ public function match_with_trailing_comma() { #[Test] public function match_with_two_cases() { $cases= [ - new MatchCondition([new Literal('1', self::LINE)], $this->blocks[1][0], self::LINE), - new MatchCondition([new Literal('2', self::LINE)], $this->blocks[2][0], self::LINE) + new MatchCondition([new Scalar('1', 'integer', self::LINE)], $this->blocks[1][0], self::LINE), + new MatchCondition([new Scalar('2', 'integer', self::LINE)], $this->blocks[2][0], self::LINE) ]; $this->assertParsed( [new MatchExpression(new Variable('condition', self::LINE), $cases, null, self::LINE)], @@ -137,7 +148,7 @@ public function match_with_two_cases() { #[Test] public function match_with_multi_expression_case_and_default() { $cases= [ - new MatchCondition([new Literal('1', self::LINE), new Literal('2', self::LINE)], $this->blocks[1][0], self::LINE), + new MatchCondition([new Scalar('1', 'integer', self::LINE), new Scalar('2', 'integer', self::LINE)], $this->blocks[1][0], self::LINE), ]; $this->assertParsed( [new MatchExpression(new Variable('condition', self::LINE), $cases, $this->blocks[2][0], self::LINE)], diff --git a/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php b/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php index 116ed520..7b7f08b5 100755 --- a/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php @@ -10,6 +10,7 @@ Literal, Parameter, ReturnStatement, + Scalar, Signature, Variable, YieldExpression, @@ -50,7 +51,7 @@ public function empty_function_without_parameters() { #[Test] public function single_expression_function() { - $scope= new Literal('"A"', self::LINE); + $scope= new Scalar('"A"', 'string', self::LINE); $this->assertParsed( [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), $scope, self::LINE)], 'function a() => "A";' @@ -176,7 +177,7 @@ public function generator() { #[Test] public function generator_with_value() { - $yield= new YieldExpression(null, new Literal('1', self::LINE), self::LINE); + $yield= new YieldExpression(null, new Scalar('1', 'integer', self::LINE), self::LINE); $this->assertParsed( [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { yield 1; }' @@ -185,7 +186,7 @@ public function generator_with_value() { #[Test] public function generator_with_key_and_value() { - $yield= new YieldExpression(new Literal('"number"', self::LINE), new Literal('1', self::LINE), self::LINE); + $yield= new YieldExpression(new Scalar('"number"', 'string', self::LINE), new Scalar('1', 'integer', self::LINE), self::LINE); $this->assertParsed( [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { yield "number" => 1; }' @@ -220,7 +221,7 @@ public function assign_to_yield_with_braced() { $yield= new Assignment( new Variable('value', self::LINE), '=', - new YieldExpression(null, new Braced(new Literal('1', self::LINE), self::LINE), self::LINE), + new YieldExpression(null, new Braced(new Scalar('1', 'integer', self::LINE), self::LINE), self::LINE), self::LINE ); $this->assertParsed( @@ -263,7 +264,7 @@ public function assign_to_yield_in_map($declaration) { $yield= new Assignment( new Variable('value', self::LINE), '=', - new ArrayLiteral([[new YieldExpression(null, null, self::LINE), new Literal('1', self::LINE)]], self::LINE), + new ArrayLiteral([[new YieldExpression(null, null, self::LINE), new Scalar('1', 'integer', self::LINE)]], self::LINE), self::LINE ); $this->assertParsed( diff --git a/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php b/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php index a0e6e137..931404c9 100755 --- a/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php @@ -10,6 +10,7 @@ ScopeExpression, Literal, Placeholder, + Scalar, Variable }; use lang\ast\types\IsValue; @@ -41,7 +42,7 @@ public function invoke_method() { #[Test] public function invoke_function_with_argument() { - $arguments= [new Literal('1', self::LINE)]; + $arguments= [new Scalar('1', 'integer', self::LINE)]; $this->assertParsed( [new InvokeExpression(new Literal('test', self::LINE), $arguments, self::LINE)], 'test(1);' @@ -50,7 +51,7 @@ public function invoke_function_with_argument() { #[Test] public function invoke_function_with_arguments() { - $arguments= [new Literal('1', self::LINE), new Literal('2', self::LINE)]; + $arguments= [new Scalar('1', 'integer', self::LINE), new Scalar('2', 'integer', self::LINE)]; $this->assertParsed( [new InvokeExpression(new Literal('test', self::LINE), $arguments, self::LINE)], 'test(1, 2);' @@ -59,7 +60,7 @@ public function invoke_function_with_arguments() { #[Test] public function invoke_function_with_dangling_comma() { - $arguments= [new Literal('1', self::LINE), new Literal('2', self::LINE)]; + $arguments= [new Scalar('1', 'integer', self::LINE), new Scalar('2', 'integer', self::LINE)]; $this->assertParsed( [new InvokeExpression(new Literal('test', self::LINE), $arguments, self::LINE)], 'test(1, 2, );' @@ -68,7 +69,7 @@ public function invoke_function_with_dangling_comma() { #[Test] public function invoke_function_with_positional_and_named_arguments() { - $arguments= [0 => new Literal('1', self::LINE), 'named' => new Literal('2', self::LINE)]; + $arguments= [0 => new Scalar('1', 'integer', self::LINE), 'named' => new Scalar('2', 'integer', self::LINE)]; $this->assertParsed( [new InvokeExpression(new Literal('test', self::LINE), $arguments, self::LINE)], 'test(1, named: 2);' @@ -125,7 +126,11 @@ public function partial_function_application() { $this->assertParsed( [new CallableExpression( new Literal('str_replace', self::LINE), - [new Literal('"test"', self::LINE), new Literal('"ok"', self::LINE), Placeholder::$ARGUMENT], + [ + new Scalar('"test"', 'string', self::LINE), + new Scalar('"ok"', 'string', self::LINE), + Placeholder::$ARGUMENT + ], self::LINE )], 'str_replace("test", "ok", ?);' @@ -137,7 +142,11 @@ public function partial_function_application_named() { $this->assertParsed( [new CallableExpression( new Literal('str_replace', self::LINE), - [new Literal('"test"', self::LINE), new Literal('"ok"', self::LINE), 'subject' => Placeholder::$ARGUMENT], + [ + new Scalar('"test"', 'string', self::LINE), + new Scalar('"ok"', 'string', self::LINE), + 'subject' => Placeholder::$ARGUMENT, + ], self::LINE )], 'str_replace("test", "ok", subject: ?);' diff --git a/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php b/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php index 9c410ed9..aab9cf5c 100755 --- a/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php @@ -1,6 +1,17 @@ expression= new BinaryExpression(new Variable('a', self::LINE), '+', new Literal('1', self::LINE), self::LINE); + $this->expression= new BinaryExpression( + new Variable('a', self::LINE), + '+', + new Scalar('1', 'integer', self::LINE), + self::LINE + ); $this->parameter= new Parameter('a', null, null, false, false, null, null, null, self::LINE); } diff --git a/src/test/php/lang/ast/unittest/parse/LiteralsTest.class.php b/src/test/php/lang/ast/unittest/parse/LiteralsTest.class.php index cfc231dc..ea10d47a 100755 --- a/src/test/php/lang/ast/unittest/parse/LiteralsTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/LiteralsTest.class.php @@ -1,28 +1,28 @@ assertParsed([new Literal($input, self::LINE)], $input.';'); + $this->assertParsed([new Scalar($input, 'integer', self::LINE)], $input.';'); } #[Test, Values(['0x00', '0x01', '0xFF', '0xff'])] public function hexadecimal($input) { - $this->assertParsed([new Literal($input, self::LINE)], $input.';'); + $this->assertParsed([new Scalar($input, 'integer', self::LINE)], $input.';'); } #[Test, Values(['00', '01', '010', '0777', '0o16', '0O16'])] public function octal($input) { - $this->assertParsed([new Literal($input, self::LINE)], $input.';'); + $this->assertParsed([new Scalar($input, 'integer', self::LINE)], $input.';'); } #[Test, Values(['1.0', '1.5'])] public function decimal($input) { - $this->assertParsed([new Literal($input, self::LINE)], $input.';'); + $this->assertParsed([new Scalar($input, 'decimal', self::LINE)], $input.';'); } #[Test] @@ -42,17 +42,17 @@ public function null() { #[Test] public function empty_string() { - $this->assertParsed([new Literal('""', self::LINE)], '"";'); + $this->assertParsed([new Scalar('""', 'string', self::LINE)], '"";'); } #[Test] public function non_empty_string() { - $this->assertParsed([new Literal('"Test"', self::LINE)], '"Test";'); + $this->assertParsed([new Scalar('"Test"', 'string', self::LINE)], '"Test";'); } #[Test] public function exec_statement() { - $this->assertParsed([new Literal('`ls -al`', self::LINE)], '`ls -al`;'); + $this->assertParsed([new Scalar('`ls -al`', 'string', self::LINE)], '`ls -al`;'); } #[Test, Values(['[];', ['array();']])] @@ -63,27 +63,27 @@ public function empty_array($declaration) { #[Test, Values(['[1, 2];', ['array(1, 2);']])] public function int_array($declaration) { $pairs= [ - [null, new Literal('1', self::LINE)], - [null, new Literal('2', self::LINE)] + [null, new Scalar('1', 'integer', self::LINE)], + [null, new Scalar('2', 'integer', self::LINE)] ]; $this->assertParsed([new ArrayLiteral($pairs, self::LINE)], $declaration); } #[Test, Values(['["key" => "value"];', ['array("key" => "value");']])] public function key_value_map($declaration) { - $pair= [new Literal('"key"', self::LINE), new Literal('"value"', self::LINE)]; + $pair= [new Scalar('"key"', 'string', self::LINE), new Scalar('"value"', 'string', self::LINE)]; $this->assertParsed([new ArrayLiteral([$pair], self::LINE)], $declaration); } #[Test, Values(['[1, ];', 'array(1, );'])] public function dangling_comma_in_array($declaration) { - $pair= [null, new Literal('1', self::LINE)]; + $pair= [null, new Scalar('1', 'integer', self::LINE)]; $this->assertParsed([new ArrayLiteral([$pair], self::LINE)], $declaration); } #[Test, Values(['["key" => "value", ];', 'array("key" => "value", );'])] public function dangling_comma_in_key_value_map($declaration) { - $pair= [new Literal('"key"', self::LINE), new Literal('"value"', self::LINE)]; + $pair= [new Scalar('"key"', 'string', self::LINE), new Scalar('"value"', 'string', self::LINE)]; $this->assertParsed([new ArrayLiteral([$pair], self::LINE)], $declaration); } @@ -97,7 +97,7 @@ public function heredoc($label) { "Line 4\n". "EOD" ); - $this->assertParsed([new Literal($nowdoc, self::LINE)], $nowdoc.';'); + $this->assertParsed([new Scalar($nowdoc, 'heredoc', self::LINE)], $nowdoc.';'); } #[Test] @@ -110,7 +110,7 @@ public function heredoc_indentation() { " Line 4\n". " EOD" ); - $this->assertParsed([new Literal($nowdoc, self::LINE)], $nowdoc.';'); + $this->assertParsed([new Scalar($nowdoc, 'heredoc', self::LINE)], $nowdoc.';'); } #[Test] @@ -121,7 +121,7 @@ public function line_number_after_multiline_string() { "'" ); $this->assertParsed( - [new Literal($string, self::LINE), new Literal('null', self::LINE + 3)], + [new Scalar($string, 'string', self::LINE), new Literal('null', self::LINE + 3)], $string.";\nnull;" ); } @@ -134,7 +134,7 @@ public function line_number_after_heredoc() { " EOD" ); $this->assertParsed( - [new Literal($nowdoc, self::LINE), new Literal('null', self::LINE + 3)], + [new Scalar($nowdoc, 'heredoc', self::LINE), new Literal('null', self::LINE + 3)], $nowdoc.";\nnull;" ); } diff --git a/src/test/php/lang/ast/unittest/parse/LoopsTest.class.php b/src/test/php/lang/ast/unittest/parse/LoopsTest.class.php index a06522c1..86bcc4dd 100755 --- a/src/test/php/lang/ast/unittest/parse/LoopsTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/LoopsTest.class.php @@ -12,6 +12,7 @@ InvokeExpression, Label, Literal, + Scalar, UnaryExpression, Variable, WhileLoop @@ -72,8 +73,8 @@ public function foreach_value_without_curly_braces() { public function for_loop() { $this->assertParsed( [new ForLoop( - [new Assignment(new Variable('i', self::LINE), '=', new Literal('0', self::LINE), self::LINE)], - [new BinaryExpression(new Variable('i', self::LINE), '<', new Literal('10', self::LINE), self::LINE)], + [new Assignment(new Variable('i', self::LINE), '=', new Scalar('0', 'integer', self::LINE), self::LINE)], + [new BinaryExpression(new Variable('i', self::LINE), '<', new Scalar('10', 'integer', self::LINE), self::LINE)], [new UnaryExpression('suffix', new Variable('i', self::LINE), '++', self::LINE)], [$this->loop], self::LINE @@ -141,7 +142,7 @@ public function break_statement() { #[Test] public function break_statement_with_level() { $this->assertParsed( - [new BreakStatement(new Literal('2', self::LINE), self::LINE)], + [new BreakStatement(new Scalar('2', 'integer', self::LINE), self::LINE)], 'break 2;' ); } @@ -157,7 +158,7 @@ public function continue_statement() { #[Test] public function continue_statement_with_level() { $this->assertParsed( - [new ContinueStatement(new Literal('2', self::LINE), self::LINE)], + [new ContinueStatement(new Scalar('2', 'integer', self::LINE), self::LINE)], 'continue 2;' ); } diff --git a/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php b/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php index 006b8ac0..dbf567ec 100755 --- a/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php @@ -8,7 +8,8 @@ Block, ReturnStatement, ThrowExpression, - NewExpression + NewExpression, + Scalar }; use lang\ast\types\IsValue; use test\{Assert, Test}; @@ -26,8 +27,8 @@ public function empty_match() { #[Test] public function match() { $cases= [ - new MatchCondition([new Literal('0', self::LINE)], new Literal('true', self::LINE), self::LINE), - new MatchCondition([new Literal('1', self::LINE)], new Literal('false', self::LINE), self::LINE) + new MatchCondition([new Scalar('0', 'integer', self::LINE)], new Literal('true', self::LINE), self::LINE), + new MatchCondition([new Scalar('1', 'integer', self::LINE)], new Literal('false', self::LINE), self::LINE) ]; $this->assertParsed( [new MatchExpression(new Variable('arg', self::LINE), $cases, null, self::LINE)], @@ -47,7 +48,7 @@ public function match_with_default_block() { #[Test] public function match_with_case_block() { $cases= [new MatchCondition( - [new Literal('0', self::LINE)], + [new Scalar('0', 'integer', self::LINE)], new Block([new ReturnStatement(new Literal('false', self::LINE), self::LINE)], self::LINE), self::LINE )]; @@ -77,8 +78,8 @@ public function match_without_condition() { #[Test] public function match_with_trailing_comma() { $cases= [ - new MatchCondition([new Literal('0', self::LINE)], new Literal('true', self::LINE), self::LINE), - new MatchCondition([new Literal('1', self::LINE)], new Literal('false', self::LINE), self::LINE) + new MatchCondition([new Scalar('0', 'integer', self::LINE)], new Literal('true', self::LINE), self::LINE), + new MatchCondition([new Scalar('1', 'integer', self::LINE)], new Literal('false', self::LINE), self::LINE) ]; $this->assertParsed( [new MatchExpression(new Variable('arg', self::LINE), $cases, null, self::LINE)], @@ -89,8 +90,16 @@ public function match_with_trailing_comma() { #[Test] public function match_with_multiple_cases() { $cases= [ - new MatchCondition([new Literal('0', self::LINE), new Literal('1', self::LINE)], new Literal('true', self::LINE), self::LINE), - new MatchCondition([new Literal('2', self::LINE), new Literal('3', self::LINE)], new Literal('false', self::LINE), self::LINE) + new MatchCondition( + [new Scalar('0', 'integer', self::LINE), new Scalar('1', 'integer', self::LINE)], + new Literal('true', self::LINE), + self::LINE + ), + new MatchCondition( + [new Scalar('2', 'integer', self::LINE), new Scalar('3', 'integer', self::LINE)], + new Literal('false', self::LINE), + self::LINE + ) ]; $this->assertParsed( [new MatchExpression(new Variable('arg', self::LINE), $cases, null, self::LINE)], @@ -101,8 +110,8 @@ public function match_with_multiple_cases() { #[Test] public function match_with_default() { $cases= [ - new MatchCondition([new Literal('0', self::LINE)], new Literal('true', self::LINE), self::LINE), - new MatchCondition([new Literal('1', self::LINE)], new Literal('false', self::LINE), self::LINE) + new MatchCondition([new Scalar('0', 'integer', self::LINE)], new Literal('true', self::LINE), self::LINE), + new MatchCondition([new Scalar('1', 'integer', self::LINE)], new Literal('false', self::LINE), self::LINE) ]; $this->assertParsed( [new MatchExpression(new Variable('arg', self::LINE), $cases, new Literal('null', self::LINE), self::LINE)], diff --git a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php index 172374bd..97deee34 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -14,6 +14,7 @@ Method, Property, ReturnStatement, + Scalar, ScopeExpression, Signature, Variable, @@ -95,7 +96,7 @@ public function method_returning_reference() { #[Test] public function single_expression_method() { - $scope= new Literal('"A"', self::LINE); + $scope= new Scalar('"A"', 'string', self::LINE); $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $class->declare(new Method(['private'], 'a', new Signature([], null, false, self::LINE), $scope, null, null, self::LINE)); @@ -105,7 +106,7 @@ public function single_expression_method() { #[Test] public function class_constant() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Constant([], 'T', null, new Literal('1', self::LINE), null, null, self::LINE)); + $class->declare(new Constant([], 'T', null, new Scalar('1', 'integer', self::LINE), null, null, self::LINE)); $this->assertParsed([$class], 'class A { const T = 1; }'); } @@ -113,8 +114,8 @@ public function class_constant() { #[Test] public function class_constants() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Constant([], 'T', null, new Literal('1', self::LINE), null, null, self::LINE)); - $class->declare(new Constant([], 'S', null, new Literal('2', self::LINE), null, null, self::LINE)); + $class->declare(new Constant([], 'T', null, new Scalar('1', 'integer', self::LINE), null, null, self::LINE)); + $class->declare(new Constant([], 'S', null, new Scalar('2', 'integer', self::LINE), null, null, self::LINE)); $this->assertParsed([$class], 'class A { const T = 1, S = 2; }'); } @@ -122,7 +123,7 @@ public function class_constants() { #[Test] public function private_class_constant() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Constant(['private'], 'T', null, new Literal('1', self::LINE), null, null, self::LINE)); + $class->declare(new Constant(['private'], 'T', null, new Scalar('1', 'integer', self::LINE), null, null, self::LINE)); $this->assertParsed([$class], 'class A { private const T = 1; }'); } @@ -155,7 +156,10 @@ public function method_with_annotation() { #[Test] public function method_with_annotations() { - $annotations= new Annotations(['Test' => [], 'Ignore' => [new Literal('"Not implemented"', self::LINE)]], self::LINE); + $annotations= new Annotations([ + 'Test' => [], + 'Ignore' => [new Scalar('"Not implemented"', 'string', self::LINE)], + ], self::LINE); $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $class->declare(new Method(['public'], 'a', new Signature([], null, false, self::LINE), $this->empty, $annotations, null, self::LINE)); @@ -166,7 +170,7 @@ public function method_with_annotations() { 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); + $return= new ReturnStatement(new Scalar('"Hello"', 'string', 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); $prop->hooks['set']= new Hook([], 'set', new Block([], self::LINE), false, $parameter, self::LINE); @@ -179,7 +183,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); + $prop->hooks['get']= new Hook([], 'get', new Scalar('"Hello"', 'string' ,self::LINE), false, null, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a { get => "Hello"; } }'); @@ -189,7 +193,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); + $prop->hooks['get']= new Hook([], 'get', new Scalar('"Hello"', 'string' ,self::LINE), false, null, self::LINE); $class->declare($prop); $this->assertParsed([$class], 'class A { public $a => "Hello"; }'); @@ -333,7 +337,7 @@ public function instance_method_invocation() { $this->assertParsed( [new InvokeExpression( new InstanceExpression(new Variable('a', self::LINE), new Literal('member', self::LINE), self::LINE), - [new Literal('1', self::LINE)], + [new Scalar('1', 'integer', self::LINE)], self::LINE )], '$a->member(1);' @@ -345,7 +349,7 @@ public function static_method_invocation() { $this->assertParsed( [new ScopeExpression( '\\A', - new InvokeExpression(new Literal('member', self::LINE), [new Literal('1', self::LINE)], self::LINE), + new InvokeExpression(new Literal('member', self::LINE), [new Scalar('1', 'integer', self::LINE)], self::LINE), self::LINE )], 'A::member(1);' @@ -363,7 +367,7 @@ public function typed_property() { #[Test] public function typed_property_with_value() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Property(['private'], 'a', new Type('string'), new Literal('"test"', self::LINE), null, null, self::LINE)); + $class->declare(new Property(['private'], 'a', new Type('string'), new Scalar('"test"', 'string', self::LINE), null, null, self::LINE)); $this->assertParsed([$class], 'class A { private string $a = "test"; }'); } @@ -389,7 +393,7 @@ public function readonly_property() { #[Test] public function typed_constant() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Constant([], 'T', new Type('int'), new Literal('1', self::LINE), null, null, self::LINE)); + $class->declare(new Constant([], 'T', new Type('int'), new Scalar('1', 'integer', self::LINE), null, null, self::LINE)); $this->assertParsed([$class], 'class A { const int T = 1; }'); } @@ -397,9 +401,9 @@ public function typed_constant() { #[Test] public function typed_constants() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Constant([], 'T', new Type('int'), new Literal('1', self::LINE), null, null, self::LINE)); - $class->declare(new Constant([], 'S', new Type('int'), new Literal('2', self::LINE), null, null, self::LINE)); - $class->declare(new Constant([], 'I', new Type('string'), new Literal('"i"', self::LINE), null, null, self::LINE)); + $class->declare(new Constant([], 'T', new Type('int'), new Scalar('1', 'integer', self::LINE), null, null, self::LINE)); + $class->declare(new Constant([], 'S', new Type('int'), new Scalar('2', 'integer', self::LINE), null, null, self::LINE)); + $class->declare(new Constant([], 'I', new Type('string'), new Scalar('"i"', 'string', self::LINE), null, null, self::LINE)); $this->assertParsed([$class], 'class A { const int T = 1, S = 2, string I = "i"; }'); } diff --git a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php index fd298f68..873314f5 100755 --- a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php @@ -14,6 +14,7 @@ NewClassExpression, NewExpression, OffsetExpression, + Scalar, ScopeExpression, TernaryExpression, UnaryExpression, @@ -35,7 +36,7 @@ public function binary($operator) { #[Test] public function ternary() { $this->assertParsed( - [new TernaryExpression(new Variable('a', self::LINE), new Literal('1', self::LINE), new Literal('2', self::LINE), self::LINE)], + [new TernaryExpression(new Variable('a', self::LINE), new Scalar('1', 'integer', self::LINE), new Scalar('2', 'integer', self::LINE), self::LINE)], '$a ? 1 : 2;' ); } @@ -81,7 +82,7 @@ public function logical_assignment($operator) { #[Test] public function assignment_to_offset() { - $target= new OffsetExpression(new Variable('a', self::LINE), new Literal('0', self::LINE), self::LINE); + $target= new OffsetExpression(new Variable('a', self::LINE), new Scalar('0', 'integer', self::LINE), self::LINE); $this->assertParsed( [new Assignment($target, '=', new Variable('b', self::LINE), self::LINE)], '$a[0]= $b;' @@ -101,8 +102,8 @@ public function destructuring_assignment() { public function comparison_to_assignment() { $this->assertParsed( [new BinaryExpression( - new Literal('1', self::LINE), '===', new Braced( - new Assignment(new Variable('a', self::LINE), '=', new Literal('1', self::LINE), self::LINE), + new Scalar('1', 'integer', self::LINE), '===', new Braced( + new Assignment(new Variable('a', self::LINE), '=', new Scalar('1', 'integer', self::LINE), self::LINE), self::LINE ), self::LINE @@ -130,7 +131,7 @@ public function clone_expression() { #[Test] public function clone_with() { - $with= [[new Literal('"id"', self::LINE), new Literal('6100', self::LINE)]]; + $with= [[new Scalar('"id"', 'string', self::LINE), new Scalar('6100', 'integer', self::LINE)]]; $this->assertParsed( [new CloneExpression([new Variable('a', self::LINE), new ArrayLiteral($with, self::LINE)], self::LINE)], 'clone($a, ["id" => 6100]);' @@ -241,7 +242,7 @@ public function precedence_of_object_operator_binary() { [new BinaryExpression( new InstanceExpression(new Variable('this', self::LINE), new Literal('a', self::LINE), self::LINE), '.', - new Literal('"test"', self::LINE), + new Scalar('"test"', 'string', self::LINE), self::LINE )], '$this->a."test";' @@ -267,7 +268,7 @@ public function precedence_of_scope_resolution_operator_binary() { [new BinaryExpression( new ScopeExpression('self', new Literal('class', self::LINE), self::LINE), '.', - new Literal('"test"', self::LINE), + new Scalar('"test"', 'string', self::LINE), self::LINE )], 'self::class."test";' @@ -316,7 +317,7 @@ public function instanceof_generic() { public function precedence_of_prefix($operator) { $this->assertParsed( [new BinaryExpression( - new UnaryExpression('prefix', new Literal('2', self::LINE), $operator, self::LINE), + new UnaryExpression('prefix', new Scalar('2', 'integer', self::LINE), $operator, self::LINE), '===', new Variable('value', self::LINE), self::LINE @@ -343,7 +344,7 @@ public function precedence_of_offset_unary() { $this->assertParsed( [new UnaryExpression( 'prefix', - new OffsetExpression(new Variable('a', self::LINE), new Literal('0', self::LINE), self::LINE), + new OffsetExpression(new Variable('a', self::LINE), new Scalar('0', 'integer', self::LINE), self::LINE), '!', self::LINE )], @@ -355,8 +356,8 @@ public function precedence_of_offset_unary() { public function multiple_semicolons() { $this->assertParsed( [ - new Assignment(new Variable('a', self::LINE), '=', new Literal('1', self::LINE), self::LINE), - new Assignment(new Variable('b', self::LINE), '=', new Literal('2', self::LINE), self::LINE) + new Assignment(new Variable('a', self::LINE), '=', new Scalar('1', 'integer', self::LINE), self::LINE), + new Assignment(new Variable('b', self::LINE), '=', new Scalar('2', 'integer', self::LINE), self::LINE) ], ';; $a= 1 ;;; $b= 2;' ); diff --git a/src/test/php/lang/ast/unittest/parse/StartTokensTest.class.php b/src/test/php/lang/ast/unittest/parse/StartTokensTest.class.php index f8c61266..f4b50978 100755 --- a/src/test/php/lang/ast/unittest/parse/StartTokensTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/StartTokensTest.class.php @@ -1,7 +1,7 @@ new Literal('1', self::LINE)]; + $declare= ['strict_types' => new Scalar('1', 'integer', self::LINE)]; $this->assertParsed( [new Directives($declare, self::LINE)], ' new Literal('1', self::LINE), 'encoding' => new Literal('"UTF-8"', self::LINE)]; + $declare= [ + 'strict_types' => new Scalar('1', 'integer', self::LINE), + 'encoding' => new Scalar('"UTF-8"', 'string', self::LINE), + ]; $this->assertParsed( [new Directives($declare, self::LINE)], 'declare(new EnumCase('ONE', new Literal('1', self::LINE), null, null, self::LINE)); - $enum->declare(new EnumCase('TWO', new Literal('2', self::LINE), null, null, self::LINE)); + $enum->declare(new EnumCase('ONE', new Scalar('1', 'integer', self::LINE), null, null, self::LINE)); + $enum->declare(new EnumCase('TWO', new Scalar('2', 'integer', self::LINE), null, null, self::LINE)); $this->assertParsed([$enum], 'enum A: int { case ONE = 1; case TWO = 2; }'); } diff --git a/src/test/php/lang/ast/unittest/parse/VariablesTest.class.php b/src/test/php/lang/ast/unittest/parse/VariablesTest.class.php index ea8a1ba6..0f51bea3 100755 --- a/src/test/php/lang/ast/unittest/parse/VariablesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/VariablesTest.class.php @@ -1,6 +1,6 @@ assertParsed( - [new StaticLocals(['id' => new Literal('0', self::LINE)], self::LINE)], + [new StaticLocals(['id' => new Scalar('0', 'integer', self::LINE)], self::LINE)], 'static $id= 0;' ); } @@ -72,7 +72,7 @@ public function static_variable_with_initialization() { #[Test] public function array_offset() { $this->assertParsed( - [new OffsetExpression(new Variable('a', self::LINE), new Literal('0', self::LINE), self::LINE)], + [new OffsetExpression(new Variable('a', self::LINE), new Scalar('0', 'integer', self::LINE), self::LINE)], '$a[0];' ); } From 5f61aecfd7b5ae5f323ac77adee28f4b967a845e Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 20 Jun 2026 16:00:23 +0200 Subject: [PATCH 2/2] Add backwards-compatible implementations for scalars --- src/main/php/lang/ast/Visitor.class.php | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/main/php/lang/ast/Visitor.class.php b/src/main/php/lang/ast/Visitor.class.php index 8c782141..e5720174 100755 --- a/src/main/php/lang/ast/Visitor.class.php +++ b/src/main/php/lang/ast/Visitor.class.php @@ -1,5 +1,7 @@ literal(new Literal($self->literal, $self->line)); + } + + /** + * Visits heredoc + * + * @param lang.ast.Node $self + * @return var + */ + public function heredoc($self) { + return $this->literal(new Literal($self->literal, $self->line)); + } + + /** + * Visits integers + * + * @param lang.ast.Node $self + * @return var + */ + public function integer($self) { + return $this->literal(new Literal($self->literal, $self->line)); + } + + /** + * Visits decimals + * + * @param lang.ast.Node $self + * @return var + */ + public function decimal($self) { + return $this->literal(new Literal($self->literal, $self->line)); + } + /** * Visits methods *