Skip to content

Commit 4512a7e

Browse files
committed
Implement Transpilation for TemplateLiteral example
1 parent 085e378 commit 4512a7e

12 files changed

Lines changed: 408 additions & 41 deletions

File tree

src/Parser/Ast/TemplateLiteralNode.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222

2323
namespace PackageFactory\ComponentEngine\Parser\Ast;
2424

25+
use PackageFactory\ComponentEngine\Parser\Source\Source;
2526
use PackageFactory\ComponentEngine\Parser\Tokenizer\Scanner;
2627
use PackageFactory\ComponentEngine\Parser\Tokenizer\Token;
28+
use PackageFactory\ComponentEngine\Parser\Tokenizer\Tokenizer;
2729
use PackageFactory\ComponentEngine\Parser\Tokenizer\TokenType;
2830

2931
final class TemplateLiteralNode implements \JsonSerializable
@@ -39,6 +41,15 @@ private function __construct(
3941
$this->segments = $segments;
4042
}
4143

44+
public static function fromString(string $stringLiteralAsString): self
45+
{
46+
return self::fromTokens(
47+
Tokenizer::fromSource(
48+
Source::fromString($stringLiteralAsString)
49+
)->getIterator()
50+
);
51+
}
52+
4253
/**
4354
* @param \Iterator<mixed,Token> $tokens
4455
* @return self

src/Transpiler/Php/Expression/ExpressionTranspiler.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use PackageFactory\ComponentEngine\Parser\Ast\NumberLiteralNode;
3232
use PackageFactory\ComponentEngine\Parser\Ast\StringLiteralNode;
3333
use PackageFactory\ComponentEngine\Parser\Ast\TagNode;
34+
use PackageFactory\ComponentEngine\Parser\Ast\TemplateLiteralNode;
3435
use PackageFactory\ComponentEngine\Parser\Ast\TernaryOperationNode;
3536
use PackageFactory\ComponentEngine\Transpiler\Php\Access\AccessTranspiler;
3637
use PackageFactory\ComponentEngine\Transpiler\Php\BinaryOperation\BinaryOperationTranspiler;
@@ -40,6 +41,7 @@
4041
use PackageFactory\ComponentEngine\Transpiler\Php\NumberLiteral\NumberLiteralTranspiler;
4142
use PackageFactory\ComponentEngine\Transpiler\Php\StringLiteral\StringLiteralTranspiler;
4243
use PackageFactory\ComponentEngine\Transpiler\Php\Tag\TagTranspiler;
44+
use PackageFactory\ComponentEngine\Transpiler\Php\TemplateLiteral\TemplateLiteralTranspiler;
4345
use PackageFactory\ComponentEngine\Transpiler\Php\TernaryOperation\TernaryOperationTranspiler;
4446
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
4547

@@ -78,6 +80,9 @@ public function transpile(ExpressionNode $expressionNode): string
7880
scope: $this->scope,
7981
shouldAddQuotes: $this->shouldAddQuotesIfNecessary
8082
),
83+
TemplateLiteralNode::class => new TemplateLiteralTranspiler(
84+
scope: $this->scope
85+
),
8186
default => throw new \Exception('@TODO: Transpile ' . $expressionNode->root::class)
8287
};
8388

src/Transpiler/Php/StringLiteral/StringLiteralTranspiler.php

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,42 @@ public function __construct(
3333

3434
public function transpile(StringLiteralNode $stringLiteralNode): string
3535
{
36-
return $this->shouldAddQuotes
37-
? sprintf('\'%s\'', $stringLiteralNode->value)
38-
: $stringLiteralNode->value;
36+
$result = $stringLiteralNode->value;
37+
$shouldAddLeadingQuote = $this->shouldAddQuotes;
38+
$shouldAddTrailingQuote = $this->shouldAddQuotes;
39+
40+
if (strpos($result, "\n") !== false) {
41+
$lines = explode("\n", $result);
42+
$result = array_shift($lines);
43+
$additionalLineBreaks = '';
44+
$shouldAddLeadingQuote = $shouldAddLeadingQuote && $result !== '';
45+
foreach ($lines as $line) {
46+
if ($line === '') {
47+
$additionalLineBreaks .= '\n';
48+
} else {
49+
$result .= $result
50+
? '\' . "\n' . $additionalLineBreaks . '" . \'' . $line
51+
: '"\n' . $additionalLineBreaks . '" . \'' . $line;
52+
$additionalLineBreaks = '';
53+
}
54+
}
55+
56+
if ($additionalLineBreaks) {
57+
$result .= $result
58+
? '\' . "' . $additionalLineBreaks . '"'
59+
: '"' . $additionalLineBreaks . '"';
60+
$shouldAddTrailingQuote = $shouldAddTrailingQuote && false;
61+
}
62+
}
63+
64+
if ($shouldAddLeadingQuote) {
65+
$result = '\'' . $result;
66+
}
67+
68+
if ($shouldAddTrailingQuote) {
69+
$result .= '\'';
70+
}
71+
72+
return $result;
3973
}
4074
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/**
4+
* PackageFactory.ComponentEngine - Universal View Components for PHP
5+
* Copyright (C) 2022 Contributors of PackageFactory.ComponentEngine
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace PackageFactory\ComponentEngine\Transpiler\Php\TemplateLiteral;
24+
25+
use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode;
26+
use PackageFactory\ComponentEngine\Parser\Ast\StringLiteralNode;
27+
use PackageFactory\ComponentEngine\Parser\Ast\TemplateLiteralNode;
28+
use PackageFactory\ComponentEngine\Transpiler\Php\Expression\ExpressionTranspiler;
29+
use PackageFactory\ComponentEngine\Transpiler\Php\StringLiteral\StringLiteralTranspiler;
30+
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
31+
32+
final class TemplateLiteralTranspiler
33+
{
34+
public function __construct(private readonly ScopeInterface $scope)
35+
{
36+
}
37+
38+
public function transpile(TemplateLiteralNode $templateLiteralNode): string
39+
{
40+
$stringLiteralTranspiler = new StringLiteralTranspiler(
41+
shouldAddQuotes: true
42+
);
43+
$expressionTranspiler = new ExpressionTranspiler(
44+
scope: $this->scope,
45+
shouldAddQuotesIfNecessary: true
46+
);
47+
$segments = [];
48+
49+
foreach ($templateLiteralNode->segments as $segmentNode) {
50+
$segments[] = match ($segmentNode::class) {
51+
StringLiteralNode::class => $stringLiteralTranspiler->transpile($segmentNode),
52+
ExpressionNode::class => $expressionTranspiler->transpile($segmentNode)
53+
};
54+
}
55+
56+
return join(' . ', $segments);
57+
}
58+
}

src/TypeSystem/Resolver/Expression/ExpressionTypeResolver.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use PackageFactory\ComponentEngine\Parser\Ast\NumberLiteralNode;
3131
use PackageFactory\ComponentEngine\Parser\Ast\StringLiteralNode;
3232
use PackageFactory\ComponentEngine\Parser\Ast\TagNode;
33+
use PackageFactory\ComponentEngine\Parser\Ast\TemplateLiteralNode;
3334
use PackageFactory\ComponentEngine\Parser\Ast\TernaryOperationNode;
3435
use PackageFactory\ComponentEngine\TypeSystem\Resolver\BinaryOperation\BinaryOperationTypeResolver;
3536
use PackageFactory\ComponentEngine\TypeSystem\Resolver\BooleanLiteral\BooleanLiteralTypeResolver;
@@ -38,6 +39,7 @@
3839
use PackageFactory\ComponentEngine\TypeSystem\Resolver\NumberLiteral\NumberLiteralTypeResolver;
3940
use PackageFactory\ComponentEngine\TypeSystem\Resolver\StringLiteral\StringLiteralTypeResolver;
4041
use PackageFactory\ComponentEngine\TypeSystem\Resolver\Tag\TagTypeResolver;
42+
use PackageFactory\ComponentEngine\TypeSystem\Resolver\TemplateLiteral\TemplateLiteralTypeResolver;
4143
use PackageFactory\ComponentEngine\TypeSystem\Resolver\TernaryOperation\TernaryOperationTypeResolver;
4244
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
4345
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
@@ -65,6 +67,7 @@ public function resolveTypeOf(ExpressionNode $expressionNode): TypeInterface
6567
NumberLiteralNode::class => new NumberLiteralTypeResolver(),
6668
StringLiteralNode::class => new StringLiteralTypeResolver(),
6769
TagNode::class => new TagTypeResolver(),
70+
TemplateLiteralNode::class => new TemplateLiteralTypeResolver(),
6871
TernaryOperationNode::class => new TernaryOperationTypeResolver(
6972
scope: $this->scope
7073
),
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/**
4+
* PackageFactory.ComponentEngine - Universal View Components for PHP
5+
* Copyright (C) 2022 Contributors of PackageFactory.ComponentEngine
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\TemplateLiteral;
24+
25+
use PackageFactory\ComponentEngine\Parser\Ast\TemplateLiteralNode;
26+
use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType;
27+
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
28+
29+
final class TemplateLiteralTypeResolver
30+
{
31+
public function resolveTypeOf(TemplateLiteralNode $templateLiteralNode): TypeInterface
32+
{
33+
return StringType::get();
34+
}
35+
}

test/Integration/Examples/TemplateLiteral/TemplateLiteral.php

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,18 @@
55
namespace Vendor\Project\Component;
66

77
use Vendor\Project\BaseClass;
8-
use Vendor\Project\Std;
9-
use Vendor\Project\Hyperscript;
108

119
final class TemplateLiteral extends BaseClass
1210
{
1311
public function __construct(
14-
private readonly string $expression,
15-
private readonly bool $isActive,
16-
private readonly int|float $someNumber
12+
public readonly string $expression,
13+
public readonly bool $isActive,
14+
public readonly int|float $someNumber
1715
) {
1816
}
1917

2018
public function render(): string
2119
{
22-
return (
23-
"A template literal may contain "
24-
. $this->expression
25-
. "s.\n\n It can span multiple lines.\n\n Interpolated Expressions can be arbitrarily complex:\n "
26-
. ($this->isActive ? (27 * $this->someNumber) : ($this->someNumber % 17))
27-
. "\n\n They can also contain other template literals:\n "
28-
. ($this->isActive ? (
29-
"Is 27? "
30-
. (($this->someNumber === 27) ? 'yes' : 'no')
31-
) : (
32-
"Number is "
33-
. 27
34-
))
35-
. "\n\n Even markup:\n "
36-
. Hyperscript::tag(
37-
'header',
38-
Hyperscript::attributes(),
39-
Hyperscript::children(
40-
Hyperscript::tag(
41-
'h1',
42-
Hyperscript::attributes(),
43-
Hyperscript::children(
44-
Hyperscript::text('Number is '),
45-
$this->someNumber
46-
)
47-
)
48-
)
49-
)
50-
. "\n "
51-
);
20+
return 'A template literal may contain ' . $this->expression . 's.' . "\n\n" . ' It can span multiple lines.' . "\n\n" . ' Interpolated Expressions can be arbitrarily complex:' . "\n" . ' ' . ($this->isActive ? (27 * $this->someNumber) : ($this->someNumber % 17)) . "\n\n" . ' They can also contain other template literals:' . "\n" . ' ' . ($this->isActive ? 'Is 27? ' . (($this->someNumber === 27) ? 'yes' : 'no') : 'Number is ' . 27) . "\n\n" . ' Even markup:' . "\n" . ' ' . '<header><h1>Number is ' . $this->someNumber . '</h1></header>' . "\n" . ' ';
5221
}
5322
}

test/Integration/PhpTranspilerIntegrationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function transpilerExamples(): array
4848
'Match' => ["Match"],
4949
'Numbers' => ["Numbers"],
5050
'Struct' => ["Struct"],
51+
'TemplateLiteral' => ["TemplateLiteral"],
5152
];
5253
}
5354

test/Unit/Transpiler/Php/StringLiteral/StringLiteralTranspilerTest.php

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,94 @@
2828

2929
final class StringLiteralTranspilerTest extends TestCase
3030
{
31+
public function stringExamples(): array
32+
{
33+
return [
34+
'"Hello World!"' => [
35+
'"Hello World!"',
36+
'Hello World!'
37+
],
38+
"\"Two\nLines\"" => [
39+
"\"Two\nLines\"",
40+
'Two\' . "\n" . \'Lines'
41+
],
42+
"\"Three\n\nLines\"" => [
43+
"\"Three\n\nLines\"",
44+
'Three\' . "\n\n" . \'Lines'
45+
],
46+
"\"\n\nLeading Line Breaks\"" => [
47+
"\"\n\nLeading Line Breaks\"",
48+
'"\n\n" . \'Leading Line Breaks'
49+
],
50+
"\"Trailing Line Breaks\n\n\"" => [
51+
"\"Trailing Line Breaks\n\n\"",
52+
'Trailing Line Breaks\' . "\n\n"'
53+
]
54+
];
55+
}
56+
3157
/**
58+
* @dataProvider stringExamples
3259
* @test
60+
* @param string $stringLiteralAsString
61+
* @param string $expectedTranspilationResult
3362
* @return void
3463
*/
35-
public function transpilesStringLiteralNodes(): void
64+
public function transpilesStringLiteralNodes(string $stringLiteralAsString, string $expectedTranspilationResult): void
3665
{
3766
$stringLiteralTranspiler = new StringLiteralTranspiler();
38-
$stringLiteralNode = StringLiteralNode::fromString('"Hello World!"');
67+
$stringLiteralNode = StringLiteralNode::fromString($stringLiteralAsString);
68+
69+
$actualTranspilationResult = $stringLiteralTranspiler->transpile(
70+
$stringLiteralNode
71+
);
72+
73+
$this->assertEquals(
74+
$expectedTranspilationResult,
75+
$actualTranspilationResult
76+
);
77+
}
78+
79+
public function stringExamplesWithAddedQuotes(): array
80+
{
81+
return [
82+
'"Hello World!"' => [
83+
'"Hello World!"',
84+
'\'Hello World!\''
85+
],
86+
"\"Two\nLines\"" => [
87+
"\"Two\nLines\"",
88+
'\'Two\' . "\n" . \'Lines\''
89+
],
90+
"\"Three\n\nLines\"" => [
91+
"\"Three\n\nLines\"",
92+
'\'Three\' . "\n\n" . \'Lines\''
93+
],
94+
"\"\n\nLeading Line Breaks\"" => [
95+
"\"\n\nLeading Line Breaks\"",
96+
'"\n\n" . \'Leading Line Breaks\''
97+
],
98+
"\"Trailing Line Breaks\n\n\"" => [
99+
"\"Trailing Line Breaks\n\n\"",
100+
'\'Trailing Line Breaks\' . "\n\n"'
101+
]
102+
];
103+
}
104+
105+
/**
106+
* @dataProvider stringExamplesWithAddedQuotes
107+
* @test
108+
* @param string $stringLiteralAsString
109+
* @param string $expectedTranspilationResult
110+
* @return void
111+
*/
112+
public function addsQuotesIfNecessary(string $stringLiteralAsString, string $expectedTranspilationResult): void
113+
{
114+
$stringLiteralTranspiler = new StringLiteralTranspiler(
115+
shouldAddQuotes: true
116+
);
117+
$stringLiteralNode = StringLiteralNode::fromString($stringLiteralAsString);
39118

40-
$expectedTranspilationResult = 'Hello World!';
41119
$actualTranspilationResult = $stringLiteralTranspiler->transpile(
42120
$stringLiteralNode
43121
);

0 commit comments

Comments
 (0)