Skip to content

Commit 4b98624

Browse files
committed
Implement Transpilation for ImportExport example
1 parent f1deb89 commit 4b98624

29 files changed

Lines changed: 705 additions & 61 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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\Module\Loader\ModuleFile;
24+
25+
use PackageFactory\ComponentEngine\Module\LoaderInterface;
26+
use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode;
27+
use PackageFactory\ComponentEngine\Parser\Ast\ImportNode;
28+
use PackageFactory\ComponentEngine\Parser\Ast\ModuleNode;
29+
use PackageFactory\ComponentEngine\Parser\Ast\StructDeclarationNode;
30+
use PackageFactory\ComponentEngine\Parser\Source\Path;
31+
use PackageFactory\ComponentEngine\Parser\Source\Source;
32+
use PackageFactory\ComponentEngine\Parser\Tokenizer\Tokenizer;
33+
use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType;
34+
use PackageFactory\ComponentEngine\TypeSystem\Type\StructType\StructType;
35+
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
36+
37+
final class ModuleFileLoader implements LoaderInterface
38+
{
39+
public function resolveTypeOfImport(ImportNode $importNode): TypeInterface
40+
{
41+
$pathToImportFrom = $importNode->source->path->resolveRelationTo(
42+
Path::fromString($importNode->path)
43+
);
44+
$source = Source::fromFile($pathToImportFrom->value);
45+
$tokenizer = Tokenizer::fromSource($source);
46+
$module = ModuleNode::fromTokens($tokenizer->getIterator());
47+
$export = $module->exports->get($importNode->name->value);
48+
49+
if ($export === null) {
50+
throw new \Exception(
51+
'@TODO: Module "' . $importNode->path . '" has no exported member "' . $importNode->name->value . '".'
52+
);
53+
}
54+
55+
return match ($export->declaration::class) {
56+
ComponentDeclarationNode::class => ComponentType::fromComponentDeclarationNode($export->declaration),
57+
StructDeclarationNode::class => StructType::fromStructDeclarationNode($export->declaration),
58+
default => throw new \Exception('@TODO: Get type of ' . $export->declaration::class)
59+
};
60+
}
61+
}

src/Module/LoaderInterface.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\Module;
24+
25+
use PackageFactory\ComponentEngine\Parser\Ast\ImportNode;
26+
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
27+
28+
interface LoaderInterface
29+
{
30+
public function resolveTypeOfImport(ImportNode $importNode): TypeInterface;
31+
}

src/Parser/Ast/ImportNode.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
namespace PackageFactory\ComponentEngine\Parser\Ast;
2424

2525
use PackageFactory\ComponentEngine\Parser\Ast\IdentifierNode;
26+
use PackageFactory\ComponentEngine\Parser\Source\Source;
2627
use PackageFactory\ComponentEngine\Parser\Tokenizer\Scanner;
2728
use PackageFactory\ComponentEngine\Parser\Tokenizer\TokenType;
2829

2930
final class ImportNode implements \JsonSerializable
3031
{
3132
private function __construct(
32-
public readonly string $source,
33+
public readonly Source $source,
34+
public readonly string $path,
3335
public readonly IdentifierNode $name
3436
) {
3537
}
@@ -43,10 +45,12 @@ public static function fromTokens(\Iterator $tokens): \Iterator
4345
Scanner::skipSpaceAndComments($tokens);
4446
Scanner::assertType($tokens, TokenType::KEYWORD_FROM);
4547

48+
$source = Scanner::source($tokens);
49+
4650
Scanner::skipOne($tokens);
4751
Scanner::skipSpaceAndComments($tokens);
4852

49-
$source = Scanner::value($tokens);
53+
$path = Scanner::value($tokens);
5054

5155
Scanner::skipOne($tokens);
5256
Scanner::skipSpaceAndComments($tokens);
@@ -59,7 +63,7 @@ public static function fromTokens(\Iterator $tokens): \Iterator
5963

6064
while (true) {
6165
$identifier = IdentifierNode::fromTokens($tokens);
62-
yield new self($source, $identifier);
66+
yield new self($source, $path, $identifier);
6367

6468
Scanner::skipSpaceAndComments($tokens);
6569
if (Scanner::type($tokens) === TokenType::COMMA) {
@@ -78,7 +82,7 @@ public static function fromTokens(\Iterator $tokens): \Iterator
7882
public function jsonSerialize(): mixed
7983
{
8084
return [
81-
'source' => $this->source,
85+
'path' => $this->path,
8286
'name' => $this->name
8387
];
8488
}

src/Parser/Ast/ImportNodes.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public function withAddedImport(ImportNode $import): self
5151
return new self(...$this->items, ...[$name => $import]);
5252
}
5353

54+
public function get(string $name): ?ImportNode
55+
{
56+
return $this->items[$name] ?? null;
57+
}
58+
5459
public function jsonSerialize(): mixed
5560
{
5661
return array_values($this->items);

src/Parser/Source/Path.php

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,32 @@
2424

2525
final class Path implements \JsonSerializable
2626
{
27+
public readonly string $value;
28+
private readonly ?string $driveLetter;
29+
private readonly string $pathWithoutDriveLetter;
2730

28-
private function __construct(public readonly string $value)
31+
private function __construct(string $value)
2932
{
3033
if (empty(trim($value))) {
3134
throw new \Exception('@TODO: Invalid path');
3235
}
36+
37+
$this->value = ($value[0] === DIRECTORY_SEPARATOR ? DIRECTORY_SEPARATOR : '') . join(
38+
DIRECTORY_SEPARATOR,
39+
array_filter(
40+
explode(
41+
DIRECTORY_SEPARATOR,
42+
mb_ereg_replace('\\\\|/', DIRECTORY_SEPARATOR, $value, 'msr')
43+
),
44+
'mb_strlen'
45+
)
46+
);
47+
48+
preg_match('/^[a-zA-Z]:/', $this->value, $matches);
49+
$this->driveLetter = isset($matches[0]) ? $matches[0] : null;
50+
$this->pathWithoutDriveLetter = $this->driveLetter
51+
? mb_substr($this->value, mb_strlen($this->driveLetter))
52+
: $this->value;
3353
}
3454

3555
public static function createMemory(): self
@@ -54,43 +74,42 @@ public function isRelative(): bool
5474

5575
public function isAbsolute(): bool
5676
{
57-
return $this->value[0] === '/';
77+
return $this->pathWithoutDriveLetter[0] === DIRECTORY_SEPARATOR;
5878
}
5979

60-
public function getRelativePathTo(Path $target): self
80+
public function resolveRelationTo(Path $other): self
6181
{
62-
if ($this->isMemory()) {
63-
throw new \Exception('@TODO: Cannot create relative path for :memory:');
64-
} elseif ($this->isRelative() && $target->isAbsolute()) {
65-
throw new \Exception('@TODO: Cannot create relative path from realtive source to asbolute target.');
66-
} elseif ($this->isAbsolute() && $target->isAbsolute()) {
67-
$dirname = dirname($this->value);
68-
if (substr($target->value, 0, strlen($dirname)) === $dirname) {
69-
return new self('.' . substr($target->value, strlen($dirname)));
70-
} else {
71-
throw new \Exception('@TODO: Cannot create relative path due to incompatible absolute paths.');
72-
}
73-
} else {
74-
$dirname = dirname($this->value);
75-
$resultSegments = explode('/', $dirname);
76-
$overflowSegments = [];
77-
$targetSegments = explode('/', $target->value);
78-
79-
foreach ($targetSegments as $segment) {
80-
if ($segment === '.' || $segment === '') {
81-
// ignore
82-
} elseif ($segment === '..') {
83-
if (count($resultSegments)) {
84-
array_pop($resultSegments);
85-
} else {
86-
$overflowSegments[] = $segment;
87-
}
88-
} else {
89-
$resultSegments[] = $segment;
82+
if ($this->isAbsolute() && $other->isRelative()) {
83+
$pathSegments = array_merge(
84+
explode(DIRECTORY_SEPARATOR, dirname($this->pathWithoutDriveLetter)),
85+
explode(DIRECTORY_SEPARATOR, $other->value)
86+
);
87+
88+
$absolutePathSegments = [];
89+
foreach ($pathSegments as $pathSegment) {
90+
switch ($pathSegment) {
91+
case '.':
92+
continue 2;
93+
case '..':
94+
if ($absolutePathSegments) {
95+
array_pop($absolutePathSegments);
96+
} else {
97+
throw new \Exception('@TODO: Unable to resolve path ' . $other->value);
98+
}
99+
break;
100+
default:
101+
$absolutePathSegments[] = $pathSegment;
102+
break;
90103
}
91104
}
92105

93-
return new self(implode('/', [...$overflowSegments, ...$resultSegments]));
106+
return new self(
107+
($this->driveLetter ? $this->driveLetter : '')
108+
. DIRECTORY_SEPARATOR
109+
. join(DIRECTORY_SEPARATOR, $absolutePathSegments)
110+
);
111+
} else {
112+
return $other;
94113
}
95114
}
96115

src/Parser/Tokenizer/Scanner.php

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

2323
namespace PackageFactory\ComponentEngine\Parser\Tokenizer;
2424

25+
use PackageFactory\ComponentEngine\Parser\Source\Source;
26+
2527
final class Scanner
2628
{
2729
/**
@@ -145,6 +147,16 @@ public static function type(\Iterator $tokens): TokenType
145147
return $tokens->current()->type;
146148
}
147149

150+
/**
151+
* @param \Iterator<mixed,Token> $tokens
152+
* @return Source
153+
*/
154+
public static function source(\Iterator $tokens): Source
155+
{
156+
self::assertValid($tokens);
157+
return $tokens->current()->source;
158+
}
159+
148160
/**
149161
* @param \Iterator<mixed,Token> $tokens
150162
* @return bool

src/Transpiler/Php/ComponentDeclaration/ComponentDeclarationTranspiler.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
namespace PackageFactory\ComponentEngine\Transpiler\Php\ComponentDeclaration;
2424

2525
use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode;
26+
use PackageFactory\ComponentEngine\Parser\Ast\ModuleNode;
2627
use PackageFactory\ComponentEngine\Parser\Ast\PropertyDeclarationNodes;
2728
use PackageFactory\ComponentEngine\Transpiler\Php\Expression\ExpressionTranspiler;
2829
use PackageFactory\ComponentEngine\Transpiler\Php\TypeReference\TypeReferenceTranspiler;
@@ -33,21 +34,29 @@
3334

3435
final class ComponentDeclarationTranspiler
3536
{
36-
public function __construct(private readonly ScopeInterface $scope)
37-
{
37+
public function __construct(
38+
private readonly ScopeInterface $scope,
39+
private readonly ModuleNode $module
40+
) {
3841
}
3942

4043
public function transpile(ComponentDeclarationNode $componentDeclarationNode): string
4144
{
4245
$lines = [];
4346

47+
// @TODO: Generate Namespaces Dynamically
4448
$lines[] = '<?php';
4549
$lines[] = '';
4650
$lines[] = 'declare(strict_types=1);';
4751
$lines[] = '';
4852
$lines[] = 'namespace Vendor\\Project\\Component;';
4953
$lines[] = '';
5054
$lines[] = 'use Vendor\\Project\\BaseClass;';
55+
56+
foreach ($this->module->imports->items as $importNode) {
57+
$lines[] = 'use Vendor\\Project\\Component\\' . $importNode->name->value . ';';
58+
}
59+
5160
$lines[] = '';
5261
$lines[] = 'final class ' . $componentDeclarationNode->componentName . ' extends BaseClass';
5362
$lines[] = '{';

src/Transpiler/Php/EnumDeclaration/EnumDeclarationTranspiler.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public function transpile(EnumDeclarationNode $enumDeclarationNode): string
3030
{
3131
$lines = [];
3232

33+
// @TODO: Generate Namespaces Dynamically
3334
$lines[] = '<?php';
3435
$lines[] = '';
3536
$lines[] = 'declare(strict_types=1);';

src/Transpiler/Php/Module/ModuleTranspiler.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,36 @@
2222

2323
namespace PackageFactory\ComponentEngine\Transpiler\Php\Module;
2424

25+
use PackageFactory\ComponentEngine\Module\LoaderInterface;
2526
use PackageFactory\ComponentEngine\Parser\Ast\ComponentDeclarationNode;
2627
use PackageFactory\ComponentEngine\Parser\Ast\EnumDeclarationNode;
2728
use PackageFactory\ComponentEngine\Parser\Ast\ModuleNode;
2829
use PackageFactory\ComponentEngine\Parser\Ast\StructDeclarationNode;
2930
use PackageFactory\ComponentEngine\Transpiler\Php\ComponentDeclaration\ComponentDeclarationTranspiler;
3031
use PackageFactory\ComponentEngine\Transpiler\Php\EnumDeclaration\EnumDeclarationTranspiler;
3132
use PackageFactory\ComponentEngine\Transpiler\Php\StructDeclaration\StructDeclarationTranspiler;
33+
use PackageFactory\ComponentEngine\TypeSystem\Scope\ModuleScope\ModuleScope;
3234
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
3335

3436
final class ModuleTranspiler
3537
{
36-
public function __construct(private readonly ScopeInterface $globalScope)
37-
{
38+
public function __construct(
39+
private readonly LoaderInterface $loader,
40+
private readonly ScopeInterface $globalScope
41+
) {
3842
}
3943

4044
public function transpile(ModuleNode $moduleNode): string
4145
{
4246
foreach ($moduleNode->exports->items as $exportNode) {
4347
return match ($exportNode->declaration::class) {
4448
ComponentDeclarationNode::class => (new ComponentDeclarationTranspiler(
45-
scope: $this->globalScope
49+
scope: new ModuleScope(
50+
loader: $this->loader,
51+
moduleNode: $moduleNode,
52+
parentScope: $this->globalScope
53+
),
54+
module: $moduleNode
4655
))->transpile($exportNode->declaration),
4756
EnumDeclarationNode::class => (new EnumDeclarationTranspiler())->transpile($exportNode->declaration),
4857
StructDeclarationNode::class => (new StructDeclarationTranspiler())->transpile($exportNode->declaration)

src/Transpiler/Php/StructDeclaration/StructDeclarationTranspiler.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public function transpile(StructDeclarationNode $structDeclarationNode): string
3232
{
3333
$lines = [];
3434

35+
// @TODO: Generate Namespaces Dynamically
3536
$lines[] = '<?php';
3637
$lines[] = '';
3738
$lines[] = 'declare(strict_types=1);';

0 commit comments

Comments
 (0)