Completed
Push — master ( b2a65d...63a169 )
by Kirill
02:18
created

src/Frontend/Builder.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Frontend;
11
12
use Railt\Io\Readable;
13
use Railt\Parser\Ast\RuleInterface;
14
use Railt\Parser\Exception\UnexpectedTokenException;
15
use Railt\Parser\Exception\UnrecognizedTokenException;
16
use Railt\SDL\Exception\InternalException;
17
use Railt\SDL\Exception\SyntaxException;
18
use Railt\SDL\Frontend\Builder\BuilderInterface;
19
use Railt\SDL\Frontend\Context\ContextInterface;
20
use Railt\SDL\Frontend\Context\GlobalContext;
21
use Railt\SDL\Frontend\Deferred\DeferredCollection as DeferredStorage;
22
use Railt\SDL\Frontend\Definition\DefinitionInterface;
23
use Railt\SDL\Frontend\Definition\Invocation;
24
use Railt\SDL\Frontend\Definition\Storage as TypesStorage;
25
use Railt\SDL\Frontend\Interceptor;
26
use Railt\SDL\Frontend\Interceptor\Factory;
27
use Railt\SDL\IR\SymbolTable;
28
use Railt\SDL\IR\SymbolTableInterface;
29
use Railt\SDL\Naming\StrategyInterface;
30
31
/**
32
 * Class Builder
33
 */
34
class Builder
35
{
36
    /**
37
     * @var string[]|Builder\BuilderInterface[]
38
     */
39
    private const DEFAULT_BUILDER_DEFINITIONS = [
40
        //
41
        // Instruction builders.
42
        // Provides variadic result.
43
        //
44
        Builder\Instruction\ImportBuilder::class,
45
        Builder\Instruction\NamespaceBuilder::class,
46
        Builder\Instruction\VariableBuilder::class,
47
        Builder\Instruction\VariableReassigmentBuilder::class,
48
49
        //
50
        // Definition builders.
51
        // Provides deferred builders.
52
        //
53
        Builder\Definition\ObjectDefinitionBuilder::class,
54
        Builder\Definition\SchemaDefinitionBuilder::class,
55
56
        //
57
        // Value builders.
58
        // Provides: \Railt\SDL\IR\SymbolTable\ValueInterface
59
        //
60
        Builder\Value\NullValueBuilder::class,
61
        Builder\Value\VariableValueBuilder::class,
62
        Builder\Value\TypeInvocationBuilder::class,
63
        Builder\Value\ConstantValueBuilder::class,
64
        Builder\Value\BooleanValueBuilder::class,
65
        Builder\Value\NumberValueBuilder::class,
66
        Builder\Value\StringValueBuilder::class,
67
68
        //
69
        // Common builders.
70
        // Provides:
71
        //  - TypeNameBuilder: Railt\SDL\IR\Type\TypeNameInterface
72
        //  - ConstantNameBuilder: Railt\SDL\IR\Type\TypeNameInterface
73
        //  - TypeDefinitionBuilder: Railt\SDL\Frontend\Definition\DefinitionInterface
74
        //
75
        Builder\Common\TypeNameBuilder::class,
76
        Builder\Common\ConstantNameBuilder::class,
77
        Builder\Common\TypeDefinitionBuilder::class,
78
    ];
79
80
    /**
81
     * @var array|Builder\BuilderInterface[]
82
     */
83
    private $builders;
84
85
    /**
86
     * @var DeferredStorage
87
     */
88
    private $deferred;
89
90
    /**
91
     * @var SymbolTableInterface
92
     */
93
    private $table;
94
95
    /**
96
     * @var Parser
97
     */
98
    private $parser;
99
100
    /**
101
     * @var Process
102
     */
103
    private $process;
104
105
    /**
106
     * @var TypesStorage
107
     */
108
    private $types;
109
110
    /**
111
     * Builder constructor.
112
     * @param StrategyInterface $naming
113
     */
114
    public function __construct(StrategyInterface $naming)
115
    {
116
        $this->parser = new Parser();
117
        $this->table = new SymbolTable();
118
        $this->types = new TypesStorage($naming);
119
        $this->deferred = new DeferredStorage();
120
121
        $factory = $this->bootInterceptors($this->deferred, $this->types);
122
123
        $this->process = $this->bootProcess($factory);
124
        $this->builders = $this->bootBuilders();
125
    }
126
127
    /**
128
     * @param Factory $interceptors
129
     * @return Process
130
     */
131
    private function bootProcess(Factory $interceptors): Process
132
    {
133
        return new Process($interceptors);
134
    }
135
136
    /**
137
     * @param DeferredStorage $deferred
138
     * @param TypesStorage $types
139
     * @return Factory
140
     */
141
    private function bootInterceptors(DeferredStorage $deferred, TypesStorage $types): Factory
142
    {
143
        $wantsBuild = function (ContextInterface $ctx, RuleInterface $ast) {
144
            return $this->buildNode($ctx, $ast);
145
        };
146
147
        return new Factory([
148
            new Interceptor\ContextInterceptor(),
149
            new Interceptor\InvocationInterceptor($types),
150
            new Interceptor\DefinitionInterceptor($types),
151
            new Interceptor\RuleInterceptor($wantsBuild),
152
            new Interceptor\CallbackInterceptor($deferred),
153
            new Interceptor\DeferredInterceptor($deferred),
154
        ]);
155
    }
156
157
    /**
158
     * @param ContextInterface $ctx
159
     * @param RuleInterface $rule
160
     * @return array
161
     */
162
    public function buildNode(ContextInterface $ctx, RuleInterface $rule): array
163
    {
164
        $result = $this->runBuilder($ctx, $rule);
165
166
        return $this->process->run($ctx, $result);
167
    }
168
169
    /**
170
     * @param ContextInterface $context
171
     * @param RuleInterface $ast
172
     * @return mixed|\Traversable
173
     * @throws \Railt\Io\Exception\ExternalFileException
174
     */
175
    private function runBuilder(ContextInterface $context, RuleInterface $ast)
176
    {
177
        return $this->getBuilder($context, $ast)->reduce($context, $ast);
178
    }
179
180
    /**
181
     * @param ContextInterface $context
182
     * @param RuleInterface $ast
183
     * @return BuilderInterface
184
     */
185
    private function getBuilder(ContextInterface $context, RuleInterface $ast): BuilderInterface
186
    {
187
        foreach ($this->builders as $builder) {
188
            if ($builder->match($ast)) {
189
                return $builder;
190
            }
191
        }
192
193
        $error = \sprintf('Unrecognized rule %s in (%s)', $ast->getName(), $ast);
194
        throw (new InternalException($error))->throwsIn($context->getFile(), $ast->getOffset());
195
    }
196
197
    /**
198
     * @return array
199
     */
200
    private function bootBuilders(): array
201
    {
202
        $builders = [];
203
204
        foreach (self::DEFAULT_BUILDER_DEFINITIONS as $builder) {
205
            $builders[] = new $builder();
206
        }
207
208
        return $builders;
209
    }
210
211
    /**
212
     * @param Readable $readable
213
     * @return array[]|iterable
214
     * @throws SyntaxException
215
     * @throws \Railt\Io\Exception\ExternalFileException
216
     */
217
    public function buildFile(Readable $readable): iterable
218
    {
219
        $ast = $this->parse($readable);
220
221
        return $this->buildAst($readable, $ast);
0 ignored issues
show
$ast is of type object<Railt\Parser\Ast\RuleInterface>, but the function expects a object<Railt\SDL\Fronten...ser\Ast\RuleInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
222
    }
223
224
    /**
225
     * Parse the file using top-down parser and
226
     * return the Abstract Syntax Tree.
227
     *
228
     * @param Readable $file
229
     * @return RuleInterface
230
     * @throws SyntaxException
231
     */
232
    public function parse(Readable $file): RuleInterface
233
    {
234
        try {
235
            return $this->parser->parse($file);
236
        } catch (UnexpectedTokenException | UnrecognizedTokenException $e) {
237
            $error = new SyntaxException($e->getMessage(), $e->getCode());
238
            $error->throwsIn($file, $e->getLine(), $e->getColumn());
239
240
            throw $error;
241
        }
242
    }
243
244
    /**
245
     * @param Readable $readable
246
     * @param iterable|RuleInterface[] $rules
247
     * @return iterable|array[]
248
     * @throws \Railt\Io\Exception\ExternalFileException
249
     */
250
    public function buildAst(Readable $readable, iterable $rules): iterable
251
    {
252
        $context = new GlobalContext($readable, $this->table);
253
254
        $context = $this->run($context, $rules);
255
256
        $context = $this->deferred($context);
0 ignored issues
show
$context is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
257
258
        return [];
259
    }
260
261
    /**
262
     * @param ContextInterface $context
263
     * @param iterable|RuleInterface[] $rules
264
     * @return ContextInterface
265
     */
266
    private function run(ContextInterface $context, iterable $rules): ContextInterface
267
    {
268
        foreach ($rules as $child) {
269
            [$context] = $this->buildNode($context, $child);
270
        }
271
272
        return $context;
273
    }
274
275
    /**
276
     * @param ContextInterface $context
277
     * @return ContextInterface
278
     */
279
    private function deferred(ContextInterface $context): ContextInterface
280
    {
281
        $this->deferred->attach($this->types->export(function (DefinitionInterface $definition): bool {
282
            $invocation = new Invocation($definition->getName(), $definition->getContext());
283
284
            return ! $definition->isGeneric() && ! $this->types->resolved($invocation);
285
        }));
286
287
        [$context] = $this->process->run($context, $this->deferred->getIterator());
288
289
        return $context;
290
    }
291
}
292