From eac366f38b8a297693c1cfe54b03c8e1fe3eefe8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 7 Apr 2023 20:20:20 +0200 Subject: [PATCH] FEATURE: String concatenation --- .../BinaryOperationTranspiler.php | 16 ++++++++++++-- .../BinaryOperationTypeResolver.php | 20 +++++++++++++++++ .../BinaryOperationTranspilerTest.php | 22 +++++++++++++++++-- .../BinaryOperationTypeResolverTest.php | 16 +++++++++++++- 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php b/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php index 32196424..f4e77097 100644 --- a/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php +++ b/src/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspiler.php @@ -23,9 +23,12 @@ namespace PackageFactory\ComponentEngine\Target\Php\Transpiler\BinaryOperation; use PackageFactory\ComponentEngine\Definition\BinaryOperator; +use PackageFactory\ComponentEngine\Parser\Ast\BinaryOperandNodes; use PackageFactory\ComponentEngine\Parser\Ast\BinaryOperationNode; use PackageFactory\ComponentEngine\Target\Php\Transpiler\Expression\ExpressionTranspiler; +use PackageFactory\ComponentEngine\TypeSystem\Resolver\BinaryOperation\BinaryOperationTypeResolver; use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; +use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; final class BinaryOperationTranspiler { @@ -51,7 +54,7 @@ private function transpileBinaryOperator(BinaryOperator $binaryOperator): string BinaryOperator::LESS_THAN_OR_EQUAL => '<=' }; } - + public function transpile(BinaryOperationNode $binaryOperationNode): string { $expressionTranspiler = new ExpressionTranspiler( @@ -60,7 +63,16 @@ public function transpile(BinaryOperationNode $binaryOperationNode): string ); $result = $expressionTranspiler->transpile($binaryOperationNode->operands->first); - $operator = sprintf(' %s ', $this->transpileBinaryOperator($binaryOperationNode->operator)); + + $binaryOperationTypeResolver = new BinaryOperationTypeResolver(scope: $this->scope); + + if ($binaryOperationNode->operator === BinaryOperator::PLUS + && $binaryOperationTypeResolver->resolveTypeOf($binaryOperationNode)->is(StringType::get()) + ) { + $operator = ' . '; + } else { + $operator = sprintf(' %s ', $this->transpileBinaryOperator($binaryOperationNode->operator)); + } foreach ($binaryOperationNode->operands->rest as $operandNode) { $result .= $operator; diff --git a/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php b/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php index 7f573aa5..fe465535 100644 --- a/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php +++ b/src/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolver.php @@ -29,6 +29,7 @@ use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface; use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType; use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType; use PackageFactory\ComponentEngine\TypeSystem\TypeInterface; @@ -41,6 +42,11 @@ public function __construct( public function resolveTypeOf(BinaryOperationNode $binaryOperationNode): TypeInterface { + if ($binaryOperationNode->operator === BinaryOperator::PLUS + && $this->checkIfAnyOfBinaryOperandNodesIsOfTypeString($binaryOperationNode->operands)) { + return StringType::get(); + } + return match ($binaryOperationNode->operator) { BinaryOperator::AND, BinaryOperator::OR => $this->resolveTypeOfBooleanOperation($binaryOperationNode->operands), @@ -92,4 +98,18 @@ private function resolveTypeOfArithmeticOperation(BinaryOperandNodes $operandNod return $numberType; } + + private function checkIfAnyOfBinaryOperandNodesIsOfTypeString(BinaryOperandNodes $operands): bool + { + $expressionTypeResolver = new ExpressionTypeResolver( + scope: $this->scope + ); + foreach ($operands as $operand) { + $operandResolvesToTypeString = $expressionTypeResolver->resolveTypeOf($operand)->is(StringType::get()); + if ($operandResolvesToTypeString) { + return true; + } + } + return false; + } } diff --git a/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php b/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php index d4b5112a..b2ac9847 100644 --- a/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php +++ b/test/Unit/Target/Php/Transpiler/BinaryOperation/BinaryOperationTranspilerTest.php @@ -26,6 +26,8 @@ use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode; use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope; use PackageFactory\ComponentEngine\Target\Php\Transpiler\BinaryOperation\BinaryOperationTranspiler; +use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType; +use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType; use PHPUnit\Framework\TestCase; final class BinaryOperationTranspilerTest extends TestCase @@ -102,8 +104,19 @@ public function binaryOperationExamples(): array ]; } + public function stringConcatenationExamples(): array + { + return [ + '"foo" + "bar"' => ['"foo" + "bar"', '(\'foo\' . \'bar\')'], + 'someString + "bar"' => ['someString + "bar"', '($this->someString . \'bar\')'], + '8 + 15 + 42 + someString' => ['8 + 15 + 42 + someString', '(8 . 15 . 42 . $this->someString)'], + 'someNumber + someString' => ['someNumber + someString', '($this->someNumber . $this->someString)'] + ]; + } + /** * @dataProvider binaryOperationExamples + * @dataProvider stringConcatenationExamples * @test * @param string $binaryOperationAsString * @param string $expectedTranspilationResult @@ -112,7 +125,12 @@ public function binaryOperationExamples(): array public function transpilesBinaryOperationNodes(string $binaryOperationAsString, string $expectedTranspilationResult): void { $binaryOperationTranspiler = new BinaryOperationTranspiler( - scope: new DummyScope() + scope: new DummyScope(identifierToTypeMap: [ + "a" => NumberType::get(), + "b" => NumberType::get(), + "someString" => StringType::get(), + "someNumber" => NumberType::get(), + ]) ); $binaryOperationNode = ExpressionNode::fromString($binaryOperationAsString)->root; assert($binaryOperationNode instanceof BinaryOperationNode); @@ -126,4 +144,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..f5691df5 100644 --- a/test/Unit/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolverTest.php +++ b/test/Unit/TypeSystem/Resolver/BinaryOperation/BinaryOperationTypeResolverTest.php @@ -63,8 +63,19 @@ public function binaryOperationExamples(): array ]; } + public function stringConcatenationExamples(): array + { + return [ + '"foo" + "bar"' => ['"foo" + "mhs"', StringType::get()], + 'someString + "bar"' => ['someString + "bar"', StringType::get()], + '8 + 15 + 42 + someString' => ['8 + 15 + 42 + someString', StringType::get()], + 'someNumber + someString' => ['someNumber + someString', StringType::get()] + ]; + } + /** * @dataProvider binaryOperationExamples + * @dataProvider stringConcatenationExamples * @test * @param string $binaryOperationAsString * @param TypeInterface $expectedType @@ -72,7 +83,10 @@ public function binaryOperationExamples(): array */ public function resolvesBinaryOperationToResultingType(string $binaryOperationAsString, TypeInterface $expectedType): void { - $scope = new DummyScope(); + $scope = new DummyScope(identifierToTypeMap: [ + "someString" => StringType::get(), + "someNumber" => NumberType::get() + ]); $binaryOperationTypeResolver = new BinaryOperationTypeResolver( scope: $scope );