diff --git a/src/Parser/Ast/ExpressionNode.php b/src/Parser/Ast/ExpressionNode.php index aa26e8c0..d8437c1d 100644 --- a/src/Parser/Ast/ExpressionNode.php +++ b/src/Parser/Ast/ExpressionNode.php @@ -39,10 +39,11 @@ private function __construct( public static function fromString(string $expressionAsString): self { + $tokens = Tokenizer::fromSource( + Source::fromString($expressionAsString) + )->getIterator(); return self::fromTokens( - Tokenizer::fromSource( - Source::fromString($expressionAsString) - )->getIterator() + $tokens ); } @@ -51,7 +52,7 @@ public static function fromString(string $expressionAsString): self * @param Precedence $precedence * @return self */ - public static function fromTokens(\Iterator $tokens, Precedence $precedence = Precedence::SEQUENCE): self + public static function fromTokens(\Iterator &$tokens, Precedence $precedence = Precedence::SEQUENCE): self { Scanner::skipSpaceAndComments($tokens); @@ -122,8 +123,14 @@ public static function fromTokens(\Iterator $tokens, Precedence $precedence = Pr } Scanner::skipSpaceAndComments($tokens); + if (Scanner::isEnd($tokens) || $precedence->mustStopAt(Scanner::type($tokens))) { + return new self( + root: $root + ); + } while (!Scanner::isEnd($tokens) && !$precedence->mustStopAt(Scanner::type($tokens))) { + Scanner::skipSpaceAndComments($tokens); switch (Scanner::type($tokens)) { case TokenType::OPERATOR_BOOLEAN_AND: case TokenType::OPERATOR_BOOLEAN_OR: diff --git a/src/Parser/Tokenizer/LookAhead.php b/src/Parser/Tokenizer/LookAhead.php index 3b9f3176..6dfb7252 100644 --- a/src/Parser/Tokenizer/LookAhead.php +++ b/src/Parser/Tokenizer/LookAhead.php @@ -58,7 +58,9 @@ public function getIterator(): \Iterator yield $token; } - yield from $this->tokens; + if (!Scanner::isEnd($this->tokens)) { + yield from $this->tokens; + } } public function shift(): void @@ -68,8 +70,11 @@ public function shift(): void Scanner::skipOne($this->tokens); } - public function type(): TokenType + public function type(): ?TokenType { + if (Scanner::isEnd($this->tokens)) { + return null; + } return Scanner::type($this->tokens); } } diff --git a/src/Parser/Tokenizer/Scanner.php b/src/Parser/Tokenizer/Scanner.php index c08b0c62..5aa89761 100644 --- a/src/Parser/Tokenizer/Scanner.php +++ b/src/Parser/Tokenizer/Scanner.php @@ -165,4 +165,26 @@ public static function isEnd(\Iterator $tokens): bool { return !$tokens->valid(); } + + /** + * @param \Iterator $tokens + */ + public static function debugPrint(\Iterator &$tokens): string + { + $tokens = (function(): \Generator { + throw new \Exception('Once debugged, $tokens is empty.'); + // @phpstan-ignore-next-line + yield; + })(); + + $tokensAsArray = []; + while ($tokens->valid()) { + $tokensAsArray[] = [ + "type" => $tokens->current()->type, + "value" => $tokens->current()->value + ]; + $tokens->next(); + } + return json_encode($tokensAsArray, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + } } diff --git a/src/Parser/Tokenizer/Tokenizer.php b/src/Parser/Tokenizer/Tokenizer.php index fc532c80..346861b6 100644 --- a/src/Parser/Tokenizer/Tokenizer.php +++ b/src/Parser/Tokenizer/Tokenizer.php @@ -44,7 +44,10 @@ public static function fromSource(Source $source): Tokenizer */ public function getIterator(): \Iterator { - yield from self::block($this->source->getIterator()); + $fragments = $this->source->getIterator(); + while ($fragments->valid()) { + yield from self::block($fragments); + } } /** diff --git a/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php b/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php index 25047efc..0fe2c9d7 100644 --- a/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php @@ -24,10 +24,13 @@ use PackageFactory\ComponentEngine\Module\ModuleId; use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode; +use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; use PackageFactory\ComponentEngine\Parser\Ast\IdentifierNode; +use PackageFactory\ComponentEngine\Target\Php\Transpiler\Expression\ExpressionTranspiler; use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; use PackageFactory\ComponentEngine\Target\Php\Transpiler\Identifier\IdentifierTranspiler; use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType; +use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PHPUnit\Framework\TestCase; final class IdentifierTranspilerTest extends TestCase @@ -82,4 +85,36 @@ public function transpilesIdentifierNodesReferringToEnums(): void $actualTranspilationResult ); } + + public static function identifierInParenthesisExamples(): mixed + { + // @todo find a better place for these tests, as we actually test the ExpressionNode + return [ + '(foo)' => ['(foo)', '$this->foo'], + '((foo))' => ['((foo))', '$this->foo'], + '(((foo)))' => ['(((foo)))', '$this->foo'] + ]; + } + + /** + * @dataProvider identifierInParenthesisExamples + * @test + */ + public function identifierInParenthesis(string $expression, string $expectedTranspilationResult): void + { + $expressionTranspiler = new ExpressionTranspiler( + scope: new DummyScope([ + "foo" => StringType::get() + ]) + ); + + $actualTranspilationResult = $expressionTranspiler->transpile( + ExpressionNode::fromString($expression) + ); + + $this->assertEquals( + $expectedTranspilationResult, + $actualTranspilationResult + ); + } } diff --git a/test/Unit/Target/Php/Transpiler/TernaryOperation/TernaryOperationTranspilerTest.php b/test/Unit/Target/Php/Transpiler/TernaryOperation/TernaryOperationTranspilerTest.php index b8bb72b8..242739ea 100644 --- a/test/Unit/Target/Php/Transpiler/TernaryOperation/TernaryOperationTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/TernaryOperation/TernaryOperationTranspilerTest.php @@ -40,6 +40,7 @@ public static function ternaryOperationExamples(): array { return [ 'true ? 42 : "foo"' => ['true ? 42 : "foo"', '(true ? 42 : \'foo\')'], + '(true) ? 42 : "foo"' => ['(true) ? 42 : "foo"', '(true ? 42 : \'foo\')'], 'a ? 42 : "foo"' => ['a ? 42 : "foo"', '($this->a ? 42 : \'foo\')'], 'true ? b : "foo"' => ['true ? b : "foo"', '(true ? $this->b : \'foo\')'], 'true ? 42 : c' => ['true ? 42 : c', '(true ? 42 : $this->c)'],