Skip to content

Commit 279738d

Browse files
authored
Merge pull request #18 from PackageFactory/bugfix/parenthesisInExpressions
BUGFIX: edgecasy parenthesis in expressions
2 parents 1b0aeb3 + 8556d4e commit 279738d

File tree

6 files changed

+80
-7
lines changed

6 files changed

+80
-7
lines changed

src/Parser/Ast/ExpressionNode.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ private function __construct(
3939

4040
public static function fromString(string $expressionAsString): self
4141
{
42+
$tokens = Tokenizer::fromSource(
43+
Source::fromString($expressionAsString)
44+
)->getIterator();
4245
return self::fromTokens(
43-
Tokenizer::fromSource(
44-
Source::fromString($expressionAsString)
45-
)->getIterator()
46+
$tokens
4647
);
4748
}
4849

@@ -51,7 +52,7 @@ public static function fromString(string $expressionAsString): self
5152
* @param Precedence $precedence
5253
* @return self
5354
*/
54-
public static function fromTokens(\Iterator $tokens, Precedence $precedence = Precedence::SEQUENCE): self
55+
public static function fromTokens(\Iterator &$tokens, Precedence $precedence = Precedence::SEQUENCE): self
5556
{
5657
Scanner::skipSpaceAndComments($tokens);
5758

@@ -122,8 +123,14 @@ public static function fromTokens(\Iterator $tokens, Precedence $precedence = Pr
122123
}
123124

124125
Scanner::skipSpaceAndComments($tokens);
126+
if (Scanner::isEnd($tokens) || $precedence->mustStopAt(Scanner::type($tokens))) {
127+
return new self(
128+
root: $root
129+
);
130+
}
125131

126132
while (!Scanner::isEnd($tokens) && !$precedence->mustStopAt(Scanner::type($tokens))) {
133+
Scanner::skipSpaceAndComments($tokens);
127134
switch (Scanner::type($tokens)) {
128135
case TokenType::OPERATOR_BOOLEAN_AND:
129136
case TokenType::OPERATOR_BOOLEAN_OR:

src/Parser/Tokenizer/LookAhead.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ public function getIterator(): \Iterator
5858
yield $token;
5959
}
6060

61-
yield from $this->tokens;
61+
if (!Scanner::isEnd($this->tokens)) {
62+
yield from $this->tokens;
63+
}
6264
}
6365

6466
public function shift(): void
@@ -68,8 +70,11 @@ public function shift(): void
6870
Scanner::skipOne($this->tokens);
6971
}
7072

71-
public function type(): TokenType
73+
public function type(): ?TokenType
7274
{
75+
if (Scanner::isEnd($this->tokens)) {
76+
return null;
77+
}
7378
return Scanner::type($this->tokens);
7479
}
7580
}

src/Parser/Tokenizer/Scanner.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,26 @@ public static function isEnd(\Iterator $tokens): bool
165165
{
166166
return !$tokens->valid();
167167
}
168+
169+
/**
170+
* @param \Iterator<mixed,Token> $tokens
171+
*/
172+
public static function debugPrint(\Iterator &$tokens): string
173+
{
174+
$tokens = (function(): \Generator {
175+
throw new \Exception('Once debugged, $tokens is empty.');
176+
// @phpstan-ignore-next-line
177+
yield;
178+
})();
179+
180+
$tokensAsArray = [];
181+
while ($tokens->valid()) {
182+
$tokensAsArray[] = [
183+
"type" => $tokens->current()->type,
184+
"value" => $tokens->current()->value
185+
];
186+
$tokens->next();
187+
}
188+
return json_encode($tokensAsArray, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
189+
}
168190
}

src/Parser/Tokenizer/Tokenizer.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ public static function fromSource(Source $source): Tokenizer
4444
*/
4545
public function getIterator(): \Iterator
4646
{
47-
yield from self::block($this->source->getIterator());
47+
$fragments = $this->source->getIterator();
48+
while ($fragments->valid()) {
49+
yield from self::block($fragments);
50+
}
4851
}
4952

5053
/**

test/Unit/Target/Php/Transpiler/Identifier/IdentifierTranspilerTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424

2525
use PackageFactory\ComponentEngine\Module\ModuleId;
2626
use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode;
27+
use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode;
2728
use PackageFactory\ComponentEngine\Parser\Ast\IdentifierNode;
29+
use PackageFactory\ComponentEngine\Target\Php\Transpiler\Expression\ExpressionTranspiler;
2830
use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope;
2931
use PackageFactory\ComponentEngine\Target\Php\Transpiler\Identifier\IdentifierTranspiler;
3032
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumStaticType;
33+
use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType;
3134
use PHPUnit\Framework\TestCase;
3235

3336
final class IdentifierTranspilerTest extends TestCase
@@ -82,4 +85,36 @@ public function transpilesIdentifierNodesReferringToEnums(): void
8285
$actualTranspilationResult
8386
);
8487
}
88+
89+
public static function identifierInParenthesisExamples(): mixed
90+
{
91+
// @todo find a better place for these tests, as we actually test the ExpressionNode
92+
return [
93+
'(foo)' => ['(foo)', '$this->foo'],
94+
'((foo))' => ['((foo))', '$this->foo'],
95+
'(((foo)))' => ['(((foo)))', '$this->foo']
96+
];
97+
}
98+
99+
/**
100+
* @dataProvider identifierInParenthesisExamples
101+
* @test
102+
*/
103+
public function identifierInParenthesis(string $expression, string $expectedTranspilationResult): void
104+
{
105+
$expressionTranspiler = new ExpressionTranspiler(
106+
scope: new DummyScope([
107+
"foo" => StringType::get()
108+
])
109+
);
110+
111+
$actualTranspilationResult = $expressionTranspiler->transpile(
112+
ExpressionNode::fromString($expression)
113+
);
114+
115+
$this->assertEquals(
116+
$expectedTranspilationResult,
117+
$actualTranspilationResult
118+
);
119+
}
85120
}

test/Unit/Target/Php/Transpiler/TernaryOperation/TernaryOperationTranspilerTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static function ternaryOperationExamples(): array
4040
{
4141
return [
4242
'true ? 42 : "foo"' => ['true ? 42 : "foo"', '(true ? 42 : \'foo\')'],
43+
'(true) ? 42 : "foo"' => ['(true) ? 42 : "foo"', '(true ? 42 : \'foo\')'],
4344
'a ? 42 : "foo"' => ['a ? 42 : "foo"', '($this->a ? 42 : \'foo\')'],
4445
'true ? b : "foo"' => ['true ? b : "foo"', '(true ? $this->b : \'foo\')'],
4546
'true ? 42 : c' => ['true ? 42 : c', '(true ? 42 : $this->c)'],

0 commit comments

Comments
 (0)