From 4e4175e7260aec427aa6585877d251a42103575f Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:08:00 +0200 Subject: [PATCH 1/3] TASK: Add tests for the current implementation for multiple chained operands: `true && true && true` --- .../BinaryOperation/BinaryOperationTranspilerTest.php | 6 +++++- .../BinaryOperation/BinaryOperationTypeResolverTest.php | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php b/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php index d4b5112a..7e1eb96a 100644 --- a/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php @@ -99,6 +99,10 @@ public function binaryOperationExamples(): array 'a <= 2' => ['a <= 2', '($this->a <= 2)'], '4 <= b' => ['4 <= b', '(4 <= $this->b)'], 'a <= b' => ['a <= b', '($this->a <= $this->b)'], + + 'true && true && true' => ['true && true && true', '(true && true && true)'], + '1 === 1 === true' => ['1 === 1 === true', '(1 === 1 === true)'], + '1 + 1 + 1' => ['1 + 1 + 1', '(1 + 1 + 1)'], ]; } @@ -126,4 +130,4 @@ public function transpilesBinaryOperationNodes(string $binaryOperationAsString, $actualTranspilationResult ); } -} \ No newline at end of file +} diff --git a/test/Unit/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolverTest.php b/test/Unit/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolverTest.php index 4dc3e7e0..eddb1041 100644 --- a/test/Unit/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolverTest.php @@ -60,6 +60,10 @@ public function binaryOperationExamples(): array '4 >= 2' => ['4 >= 2', BooleanType::get()], '4 < 2' => ['4 < 2', BooleanType::get()], '4 <= 2' => ['4 <= 2', BooleanType::get()], + + 'true && true && true' => ['true && true && true', BooleanType::get()], + '1 === 1 === true' => ['1 === 1 === true', BooleanType::get()], + '1 + 1 + 1' => ['1 + 1 + 1', NumberType::get()], ]; } From 174bb3f08e0c613ac8b12805a5d12eafff216fd2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 23 Apr 2023 15:27:49 +0200 Subject: [PATCH 2/3] WIP: Preparation for BinaryOperandNode with $left and $right instead of multiple operands i have the feeling this will make working with the BinaryOperandNode (eg type resolving/inference) easier. Also the php and js AST use the $left $right and $operator as a sideeffect it transpiles - transpile `true === true === true` correctly (with Parenthesis): `(true === true) === true` without Parenthesis its invalid php --- src/Parser/Ast/BinaryOperandNodes.php | 44 +-- .../BinaryOperationTranspiler.php | 12 +- .../Examples/Numbers/Numbers.ast.json | 315 ++++++++++++------ test/Integration/Examples/Numbers/Numbers.php | 2 +- .../BinaryOperationTranspilerTest.php | 6 +- 5 files changed, 229 insertions(+), 150 deletions(-) diff --git a/src/Parser/Ast/BinaryOperandNodes.php b/src/Parser/Ast/BinaryOperandNodes.php index 94c2c64f..782861a7 100644 --- a/src/Parser/Ast/BinaryOperandNodes.php +++ b/src/Parser/Ast/BinaryOperandNodes.php @@ -25,23 +25,16 @@ use PackageFactory\ComponentEngine\Definition\BinaryOperator; use PackageFactory\ComponentEngine\Parser\Tokenizer\Scanner; use PackageFactory\ComponentEngine\Parser\Tokenizer\Token; -use PackageFactory\ComponentEngine\Parser\Tokenizer\TokenType; /** * @implements \IteratorAggregate */ final class BinaryOperandNodes implements \IteratorAggregate, \JsonSerializable { - /** - * @var ExpressionNode[] - */ - public readonly array $rest; - private function __construct( public readonly ExpressionNode $first, - ExpressionNode ...$rest + public readonly ExpressionNode $second ) { - $this->rest = $rest; } /** @@ -55,36 +48,11 @@ public static function fromTokens(ExpressionNode $first, \Iterator $tokens, Bina $precedence = $operator->toPrecedence(); $operands = [$first]; - while (true) { - Scanner::skipSpaceAndComments($tokens); - - $operands[] = ExpressionNode::fromTokens($tokens, $precedence); - - Scanner::skipSpaceAndComments($tokens); + Scanner::skipSpaceAndComments($tokens); - if (Scanner::isEnd($tokens)) { - break; - } + $operands[] = ExpressionNode::fromTokens($tokens, $precedence); - switch (Scanner::type($tokens)) { - case TokenType::BRACKET_ROUND_CLOSE: - case TokenType::BRACKET_CURLY_CLOSE: - case TokenType::BRACKET_SQUARE_CLOSE: - case TokenType::ARROW_SINGLE: - case TokenType::QUESTIONMARK: - break 2; - case $operator->toTokenType(): - Scanner::skipOne($tokens); - break; - default: - if ($precedence->mustStopAt(Scanner::type($tokens))) { - break 2; - } else { - Scanner::assertType($tokens, $operator->toTokenType()); - } - break; - } - } + Scanner::skipSpaceAndComments($tokens); return new self(...$operands); } @@ -92,11 +60,11 @@ public static function fromTokens(ExpressionNode $first, \Iterator $tokens, Bina public function getIterator(): \Traversable { yield $this->first; - yield from $this->rest; + yield $this->second; } public function jsonSerialize(): mixed { - return [$this->first, ...$this->rest]; + return [$this->first, $this->second]; } } diff --git a/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php b/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php index 32196424..71f6b6d8 100644 --- a/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php +++ b/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php @@ -59,14 +59,10 @@ public function transpile(BinaryOperationNode $binaryOperationNode): string shouldAddQuotesIfNecessary: true ); - $result = $expressionTranspiler->transpile($binaryOperationNode->operands->first); - $operator = sprintf(' %s ', $this->transpileBinaryOperator($binaryOperationNode->operator)); + $first = $expressionTranspiler->transpile($binaryOperationNode->operands->first); + $operator = $this->transpileBinaryOperator($binaryOperationNode->operator); + $second = $expressionTranspiler->transpile($binaryOperationNode->operands->second); - foreach ($binaryOperationNode->operands->rest as $operandNode) { - $result .= $operator; - $result .= $expressionTranspiler->transpile($operandNode); - } - - return sprintf('(%s)', $result); + return sprintf('(%s %s %s)', $first, $operator, $second); } } diff --git a/test/Integration/Examples/Numbers/Numbers.ast.json b/test/Integration/Examples/Numbers/Numbers.ast.json index 793a37fd..56d43578 100644 --- a/test/Integration/Examples/Numbers/Numbers.ast.json +++ b/test/Integration/Examples/Numbers/Numbers.ast.json @@ -1,118 +1,233 @@ { "type": "ModuleNode", "payload": { - "imports": {}, + "imports": [], "exports": [ { "type": "ComponentDeclarationNode", "payload": { "componentName": "Numbers", - "propertyDeclarations": {}, + "propertyDeclarations": [], "returnExpression": { "type": "BinaryOperationNode", "payload": { "operator": "PLUS", "operands": [ { - "type": "NumberLiteralNode", - "payload": { "value": "0", "format": "DECIMAL" } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "1234567890", - "format": "DECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "42", - "format": "DECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0b10000000000000000000000000000000", - "format": "BINARY" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0b01111111100000000000000000000000", - "format": "BINARY" - } - }, - { - "type": "NumberLiteralNode", + "type": "BinaryOperationNode", "payload": { - "value": "0B00000000011111111111111111111111", - "format": "BINARY" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0o755", - "format": "OCTAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0o644", - "format": "OCTAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0xFFFFFFFFFFFFFFFFF", - "format": "HEXADECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0x123456789ABCDEF", - "format": "HEXADECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0xA", - "format": "HEXADECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "1E3", - "format": "DECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "2e6", - "format": "DECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "123.456", - "format": "DECIMAL" - } - }, - { - "type": "NumberLiteralNode", - "payload": { - "value": "0.1e2", - "format": "DECIMAL" + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "BinaryOperationNode", + "payload": { + "operator": "PLUS", + "operands": [ + { + "type": "NumberLiteralNode", + "payload": { + "value": "0", + "format": "DECIMAL" + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "1234567890", + "format": "DECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "42", + "format": "DECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0b10000000000000000000000000000000", + "format": "BINARY" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0b01111111100000000000000000000000", + "format": "BINARY" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0B00000000011111111111111111111111", + "format": "BINARY" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0o755", + "format": "OCTAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0o644", + "format": "OCTAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0xFFFFFFFFFFFFFFFFF", + "format": "HEXADECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0x123456789ABCDEF", + "format": "HEXADECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0xA", + "format": "HEXADECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "1E3", + "format": "DECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "2e6", + "format": "DECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "123.456", + "format": "DECIMAL" + } + } + ] + } + }, + { + "type": "NumberLiteralNode", + "payload": { + "value": "0.1e2", + "format": "DECIMAL" + } + } + ] } }, { diff --git a/test/Integration/Examples/Numbers/Numbers.php b/test/Integration/Examples/Numbers/Numbers.php index b61927f3..58555d25 100644 --- a/test/Integration/Examples/Numbers/Numbers.php +++ b/test/Integration/Examples/Numbers/Numbers.php @@ -10,6 +10,6 @@ final class Numbers extends BaseClass { public function render(): string { - return (string) (0 + 1234567890 + 42 + 0b10000000000000000000000000000000 + 0b01111111100000000000000000000000 + 0b00000000011111111111111111111111 + 0o755 + 0o644 + 0xFFFFFFFFFFFFFFFFF + 0x123456789ABCDEF + 0xA + 1E3 + 2e6 + 123.456 + 0.1e2 + .22); + return (string) (((((((((((((((0 + 1234567890) + 42) + 0b10000000000000000000000000000000) + 0b01111111100000000000000000000000) + 0b00000000011111111111111111111111) + 0o755) + 0o644) + 0xFFFFFFFFFFFFFFFFF) + 0x123456789ABCDEF) + 0xA) + 1E3) + 2e6) + 123.456) + 0.1e2) + .22); } } diff --git a/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php b/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php index 7e1eb96a..973ee237 100644 --- a/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php @@ -100,9 +100,9 @@ public function binaryOperationExamples(): array '4 <= b' => ['4 <= b', '(4 <= $this->b)'], 'a <= b' => ['a <= b', '($this->a <= $this->b)'], - 'true && true && true' => ['true && true && true', '(true && true && true)'], - '1 === 1 === true' => ['1 === 1 === true', '(1 === 1 === true)'], - '1 + 1 + 1' => ['1 + 1 + 1', '(1 + 1 + 1)'], + 'true && true && true' => ['true && true && true', '((true && true) && true)'], + '1 === 1 === true' => ['1 === 1 === true', '((1 === 1) === true)'], + '1 + 1 + 1' => ['1 + 1 + 1', '((1 + 1) + 1)'], ]; } From 7d379234cfcbe729a044419cc43755fe29ca0d9c Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 26 Apr 2023 10:40:09 +0200 Subject: [PATCH 3/3] TASK: Inline `BinaryOperandNodes` --- src/Parser/Ast/BinaryOperandNodes.php | 70 ------------------- src/Parser/Ast/BinaryOperationNode.php | 16 +++-- .../BinaryOperationTranspiler.php | 6 +- .../BinaryOperationTypeResolver.php | 28 +++----- 4 files changed, 26 insertions(+), 94 deletions(-) delete mode 100644 src/Parser/Ast/BinaryOperandNodes.php diff --git a/src/Parser/Ast/BinaryOperandNodes.php b/src/Parser/Ast/BinaryOperandNodes.php deleted file mode 100644 index 782861a7..00000000 --- a/src/Parser/Ast/BinaryOperandNodes.php +++ /dev/null @@ -1,70 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace PackageFactory\ComponentEngine\Parser\Ast; - -use PackageFactory\ComponentEngine\Definition\BinaryOperator; -use PackageFactory\ComponentEngine\Parser\Tokenizer\Scanner; -use PackageFactory\ComponentEngine\Parser\Tokenizer\Token; - -/** - * @implements \IteratorAggregate - */ -final class BinaryOperandNodes implements \IteratorAggregate, \JsonSerializable -{ - private function __construct( - public readonly ExpressionNode $first, - public readonly ExpressionNode $second - ) { - } - - /** - * @param ExpressionNode $first - * @param \Iterator $tokens - * @param BinaryOperator $operator - * @return self - */ - public static function fromTokens(ExpressionNode $first, \Iterator $tokens, BinaryOperator $operator): self - { - $precedence = $operator->toPrecedence(); - $operands = [$first]; - - Scanner::skipSpaceAndComments($tokens); - - $operands[] = ExpressionNode::fromTokens($tokens, $precedence); - - Scanner::skipSpaceAndComments($tokens); - - return new self(...$operands); - } - - public function getIterator(): \Traversable - { - yield $this->first; - yield $this->second; - } - - public function jsonSerialize(): mixed - { - return [$this->first, $this->second]; - } -} diff --git a/src/Parser/Ast/BinaryOperationNode.php b/src/Parser/Ast/BinaryOperationNode.php index dabac37c..f3dcd3bd 100644 --- a/src/Parser/Ast/BinaryOperationNode.php +++ b/src/Parser/Ast/BinaryOperationNode.php @@ -29,8 +29,9 @@ final class BinaryOperationNode implements \JsonSerializable { private function __construct( + public readonly ExpressionNode $left, public readonly BinaryOperator $operator, - public readonly BinaryOperandNodes $operands + public readonly ExpressionNode $right ) { } @@ -46,11 +47,18 @@ public static function fromTokens(ExpressionNode $left, \Iterator $tokens): self Scanner::skipOne($tokens); - $operands = BinaryOperandNodes::fromTokens($left, $tokens, $operator); + $precedence = $operator->toPrecedence(); + + Scanner::skipSpaceAndComments($tokens); + + $right = ExpressionNode::fromTokens($tokens, $precedence); + + Scanner::skipSpaceAndComments($tokens); return new self( + left: $left, operator: $operator, - operands: $operands + right: $right ); } @@ -60,7 +68,7 @@ public function jsonSerialize(): mixed 'type' => 'BinaryOperationNode', 'payload' => [ 'operator' => $this->operator, - 'operands' => $this->operands + 'operands' => [$this->left, $this->right] ] ]; } diff --git a/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php b/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php index 71f6b6d8..b5268b19 100644 --- a/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php +++ b/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php @@ -59,10 +59,10 @@ public function transpile(BinaryOperationNode $binaryOperationNode): string shouldAddQuotesIfNecessary: true ); - $first = $expressionTranspiler->transpile($binaryOperationNode->operands->first); + $left = $expressionTranspiler->transpile($binaryOperationNode->left); $operator = $this->transpileBinaryOperator($binaryOperationNode->operator); - $second = $expressionTranspiler->transpile($binaryOperationNode->operands->second); + $right = $expressionTranspiler->transpile($binaryOperationNode->right); - return sprintf('(%s %s %s)', $first, $operator, $second); + return sprintf('(%s %s %s)', $left, $operator, $right); } } diff --git a/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php b/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php index 7f573aa5..cf95fe8b 100644 --- a/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php +++ b/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php @@ -23,7 +23,6 @@ namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\BinaryOperation; use PackageFactory\ComponentEngine\Definition\BinaryOperator; -use PackageFactory\ComponentEngine\Parser\Ast\BinaryOperandNodes; use PackageFactory\ComponentEngine\Parser\Ast\BinaryOperationNode; use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; @@ -43,13 +42,13 @@ public function resolveTypeOf(BinaryOperationNode $binaryOperationNode): TypeInt { return match ($binaryOperationNode->operator) { BinaryOperator::AND, - BinaryOperator::OR => $this->resolveTypeOfBooleanOperation($binaryOperationNode->operands), + BinaryOperator::OR => $this->resolveTypeOfBooleanOperation($binaryOperationNode), BinaryOperator::PLUS, BinaryOperator::MINUS, BinaryOperator::MULTIPLY_BY, BinaryOperator::DIVIDE_BY, - BinaryOperator::MODULO => $this->resolveTypeOfArithmeticOperation($binaryOperationNode->operands), + BinaryOperator::MODULO => $this->resolveTypeOfArithmeticOperation($binaryOperationNode), BinaryOperator::EQUAL, BinaryOperator::NOT_EQUAL, @@ -60,36 +59,31 @@ public function resolveTypeOf(BinaryOperationNode $binaryOperationNode): TypeInt }; } - private function resolveTypeOfBooleanOperation(BinaryOperandNodes $operandNodes): TypeInterface + private function resolveTypeOfBooleanOperation(BinaryOperationNode $binaryOperationNode): TypeInterface { $expressionTypeResolver = new ExpressionTypeResolver( scope: $this->scope ); - $operandTypes = []; - foreach ($operandNodes as $operandNode) { - $operandTypes[] = $expressionTypeResolver->resolveTypeOf($operandNode); - } - - return UnionType::of(...$operandTypes); + return UnionType::of( + $expressionTypeResolver->resolveTypeOf($binaryOperationNode->left), + $expressionTypeResolver->resolveTypeOf($binaryOperationNode->right) + ); } - private function resolveTypeOfArithmeticOperation(BinaryOperandNodes $operandNodes): TypeInterface + private function resolveTypeOfArithmeticOperation(BinaryOperationNode $binaryOperationNode): TypeInterface { $expressionTypeResolver = new ExpressionTypeResolver( scope: $this->scope ); - $numberType = NumberType::get(); - foreach ($operandNodes as $operandNode) { + foreach ([$binaryOperationNode->left, $binaryOperationNode->right] as $operandNode) { $typeOfOperandNode = $expressionTypeResolver->resolveTypeOf($operandNode); - $typeOfOperandNodeIsNumberType = $typeOfOperandNode->is($numberType); - - if (!$typeOfOperandNodeIsNumberType) { + if (!$typeOfOperandNode->is(NumberType::get())) { throw new \Exception('@TODO: Operand must be of type number'); } } - return $numberType; + return NumberType::get(); } }