|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This file is part of Railt package. |
|
4
|
|
|
* |
|
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
6
|
|
|
* file that was distributed with this source code. |
|
7
|
|
|
*/ |
|
8
|
|
|
declare(strict_types=1); |
|
9
|
|
|
|
|
10
|
|
|
namespace Railt\SDL; |
|
11
|
|
|
|
|
12
|
|
|
use Psr\Log\LoggerAwareInterface; |
|
13
|
|
|
use Psr\Log\LoggerAwareTrait; |
|
14
|
|
|
use Psr\Log\LoggerInterface; |
|
15
|
|
|
use Railt\Io\File; |
|
16
|
|
|
use Railt\Io\Readable; |
|
17
|
|
|
use Railt\Parser\Ast\RuleInterface; |
|
18
|
|
|
use Railt\Parser\Exception\UnexpectedTokenException; |
|
19
|
|
|
use Railt\Parser\Exception\UnrecognizedTokenException; |
|
20
|
|
|
use Railt\Reflection\Contracts\Document as DocumentInterface; |
|
21
|
|
|
use Railt\Reflection\Contracts\Reflection as ReflectionInterface; |
|
22
|
|
|
use Railt\Reflection\Reflection; |
|
23
|
|
|
use Railt\SDL\Compiler\Builder; |
|
24
|
|
|
use Railt\SDL\Compiler\Dictionary; |
|
25
|
|
|
use Railt\SDL\Compiler\Parser; |
|
26
|
|
|
use Railt\SDL\Exception\CompilerException; |
|
27
|
|
|
use Railt\SDL\Exception\InternalException; |
|
28
|
|
|
use Railt\SDL\Exception\SyntaxException; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* Class Compiler |
|
32
|
|
|
*/ |
|
33
|
|
|
class Compiler implements LoggerAwareInterface |
|
34
|
|
|
{ |
|
35
|
|
|
use LoggerAwareTrait; |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* @var ReflectionInterface |
|
39
|
|
|
*/ |
|
40
|
|
|
private $reflection; |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* @var Dictionary |
|
44
|
|
|
*/ |
|
45
|
|
|
private $dictionary; |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* @var Parser |
|
49
|
|
|
*/ |
|
50
|
|
|
private $parser; |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* @var Builder |
|
54
|
|
|
*/ |
|
55
|
|
|
private $builder; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @var array|\Railt\Reflection\Contracts\Document[] |
|
59
|
|
|
*/ |
|
60
|
|
|
private $documents = []; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* Compiler constructor. |
|
64
|
|
|
* @param LoggerInterface|null $logger |
|
65
|
|
|
* @throws \Railt\Io\Exception\ExternalFileException |
|
66
|
|
|
* @throws \Railt\Reflection\Exception\TypeConflictException |
|
67
|
|
|
*/ |
|
68
|
119 |
|
public function __construct(LoggerInterface $logger = null) |
|
69
|
|
|
{ |
|
70
|
119 |
|
$this->parser = new Parser(); |
|
71
|
119 |
|
$this->builder = new Builder(); |
|
72
|
119 |
|
$this->dictionary = new Dictionary($this); |
|
73
|
119 |
|
$this->reflection = new Reflection($this->dictionary); |
|
74
|
|
|
|
|
75
|
119 |
|
if ($logger) { |
|
76
|
|
|
$this->setLogger($logger); |
|
77
|
|
|
} |
|
78
|
119 |
|
} |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @param LoggerInterface $logger |
|
82
|
|
|
*/ |
|
83
|
|
|
public function setLogger(LoggerInterface $logger): void |
|
84
|
|
|
{ |
|
85
|
|
|
$this->logger = $logger; |
|
86
|
|
|
|
|
87
|
|
|
$this->builder->setLogger($logger); |
|
88
|
|
|
$this->dictionary->setLogger($logger); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* @param \Closure $then |
|
93
|
|
|
* @return Compiler |
|
94
|
|
|
*/ |
|
95
|
|
|
public function autoload(\Closure $then): Compiler |
|
96
|
|
|
{ |
|
97
|
|
|
$this->dictionary->onTypeNotFound($then); |
|
98
|
|
|
|
|
99
|
|
|
return $this; |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* @param Readable $file |
|
104
|
|
|
* @return DocumentInterface |
|
105
|
|
|
* @throws CompilerException |
|
106
|
|
|
* @throws \Railt\Io\Exception\NotReadableException |
|
107
|
|
|
*/ |
|
108
|
|
|
public function compile(Readable $file): DocumentInterface |
|
109
|
|
|
{ |
|
110
|
|
|
try { |
|
111
|
119 |
|
return $this->memoize($file, function (Readable $file): DocumentInterface { |
|
112
|
119 |
|
return $this->builder->run($this->reflection, $file, $this->parse($file)); |
|
113
|
119 |
|
}); |
|
114
|
|
|
} catch (CompilerException $e) { |
|
115
|
|
|
throw $e; |
|
116
|
|
|
} catch (\Throwable $e) { |
|
117
|
|
|
$error = new InternalException($e->getMessage(), $e->getCode()); |
|
118
|
|
|
$error->throwsIn(File::fromPathname($e->getFile()), $e->getLine(), 0); |
|
|
|
|
|
|
119
|
|
|
|
|
120
|
|
|
throw $error; |
|
121
|
|
|
} |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
/** |
|
125
|
|
|
* @param Readable $file |
|
126
|
|
|
* @param \Closure $otherwise |
|
127
|
|
|
* @return DocumentInterface |
|
128
|
|
|
*/ |
|
129
|
119 |
|
private function memoize(Readable $file, \Closure $otherwise): DocumentInterface |
|
130
|
|
|
{ |
|
131
|
119 |
|
if (isset($this->documents[$file->getHash()])) { |
|
132
|
|
|
// |
|
133
|
|
|
// Log memoized document selection |
|
134
|
|
|
// |
|
135
|
|
|
if ($this->logger) { |
|
136
|
|
|
$this->logger->debug(\sprintf('Avoid duplication compilation of %s', $file->getPathname())); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
return $this->documents[$file->getHash()]; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
119 |
|
return $this->documents[$file->getHash()] = $otherwise($file); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
/** |
|
146
|
|
|
* @param Readable $file |
|
147
|
|
|
* @return RuleInterface |
|
148
|
|
|
* @throws CompilerException |
|
149
|
|
|
*/ |
|
150
|
119 |
|
private function parse(Readable $file): RuleInterface |
|
151
|
|
|
{ |
|
152
|
|
|
try { |
|
153
|
119 |
|
return $this->parser->parse($file); |
|
154
|
|
|
} catch (UnexpectedTokenException | UnrecognizedTokenException $e) { |
|
155
|
|
|
$error = new SyntaxException($e->getMessage(), $e->getCode()); |
|
156
|
|
|
$error->throwsIn($file, $e->getLine(), $e->getColumn()); |
|
157
|
|
|
|
|
158
|
|
|
throw $error; |
|
159
|
|
|
} |
|
160
|
|
|
} |
|
161
|
|
|
} |
|
162
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.