Test Failed
Push — master ( 33bfdc...47f867 )
by Kirill
02:32
created

Builder::coroutine()   B

Complexity

Conditions 11
Paths 52

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
cc 11
nc 52
nop 3
dl 0
loc 48
ccs 0
cts 0
cp 0
crap 132
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\CompilerException;
17
use Railt\SDL\Exception\InternalException;
18
use Railt\SDL\Exception\SyntaxException;
19
use Railt\SDL\Frontend\Context\ContextInterface;
20
use Railt\SDL\Frontend\Context\GlobalContext;
21
use Railt\SDL\Frontend\Deferred\Deferred;
22
use Railt\SDL\Frontend\Deferred\DeferredInterface;
23
use Railt\SDL\Frontend\Deferred\Identifiable;
24
use Railt\SDL\Frontend\Deferred\Storage;
25
use Railt\SDL\IR\SymbolTable;
26
use Railt\SDL\IR\SymbolTable\ValueInterface;
27
use Railt\SDL\IR\SymbolTableInterface;
28
29
/**
30
 * Class Builder
31
 */
32
class Builder
33
{
34
    /**
35
     * @var string[]|Builder\BuilderInterface[]
36
     */
37
    private const DEFAULT_BUILDER_DEFINITIONS = [
38
        //
39
        // Instruction builders.
40
        // Provides variadic result.
41
        //
42
        Builder\Instruction\ImportBuilder::class,
43
        Builder\Instruction\NamespaceBuilder::class,
44
        Builder\Instruction\VariableBuilder::class,
45
        Builder\Instruction\VariableReassigmentBuilder::class,
46
47
        //
48
        // Definition builders.
49
        // Provides deferred builders.
50
        //
51
        Builder\Definition\ObjectDefinitionBuilder::class,
52
        Builder\Definition\SchemaDefinitionBuilder::class,
53
54
        //
55
        // Value builders.
56
        // Provides: \Railt\SDL\IR\SymbolTable\ValueInterface
57
        //
58
        Builder\Value\ScalarValueBuilder::class,
59
        Builder\Value\VariableValueBuilder::class,
60
        Builder\Value\TypeInvocationBuilder::class,
61
        Builder\Value\ConstantValueBuilder::class,
62
        Builder\Value\BooleanValueBuilder::class,
63
64
        //
65
        // Common builders.
66
        // Provides:
67
        //  - TypeNameBuilder: Railt\SDL\IR\Type\TypeNameInterface
68
        //  - ConstantNameBuilder: Railt\SDL\IR\Type\TypeNameInterface
69
        //  - TypeDefinitionBuilder: Railt\SDL\Frontend\Definition\DefinitionInterface
70
        //
71
        Builder\Common\TypeNameBuilder::class,
72
        Builder\Common\ConstantNameBuilder::class,
73
        Builder\Common\TypeDefinitionBuilder::class,
74
    ];
75
76
    /**
77
     * @var array|Builder\BuilderInterface[]
78
     */
79
    private $builders = [];
80
81
    /**
82
     * @var Storage
83
     */
84
    private $store;
85
86
    /**
87
     * @var SymbolTableInterface
88
     */
89
    private $table;
90
91
    /**
92
     * @var Parser
93
     */
94
    private $parser;
95
96
    /**
97
     * Builder constructor.
98
     */
99
    public function __construct()
100
    {
101
        $this->parser = new Parser();
102
        $this->store = new Storage();
103
        $this->table = new SymbolTable();
104
105
        $this->bootDefaults();
106
    }
107
108
    /**
109
     * @return void
110
     */
111
    private function bootDefaults(): void
112
    {
113
        foreach (self::DEFAULT_BUILDER_DEFINITIONS as $builder) {
114
            $this->builders[] = new $builder($this, $this->store);
115
        }
116
    }
117
118
    /**
119
     * @param Readable $readable
120
     * @return array[]|iterable
121
     * @throws SyntaxException
122
     * @throws \Railt\Io\Exception\ExternalFileException
123
     */
124
    public function buildFile(Readable $readable): iterable
125
    {
126
        $ast = $this->parse($readable);
127
128
        return $this->buildAst($readable, $ast);
0 ignored issues
show
Documentation introduced by
$ast is of type object<Railt\Parser\Ast\RuleInterface>, but the function expects a object<Railt\SDL\Frontend\iterable>.

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...
129
    }
130
131
    /**
132
     * Parse the file using top-down parser and
133
     * return the Abstract Syntax Tree.
134
     *
135
     * @param Readable $file
136
     * @return RuleInterface
137
     * @throws SyntaxException
138
     */
139
    public function parse(Readable $file): RuleInterface
140
    {
141
        try {
142
            return $this->parser->parse($file);
143
        } catch (UnexpectedTokenException | UnrecognizedTokenException $e) {
144
            $error = new SyntaxException($e->getMessage(), $e->getCode());
145
            $error->throwsIn($file, $e->getLine(), $e->getColumn());
146
147
            throw $error;
148
        }
149
    }
150
151
    /**
152
     * @param Readable $readable
153
     * @param iterable $ast
154
     * @return iterable|array[]
155
     * @throws \Railt\Io\Exception\ExternalFileException
156
     */
157
    public function buildAst(Readable $readable, iterable $ast): iterable
158
    {
159
        $context = new GlobalContext($readable, $this->table);
160
161
        foreach ($ast as $child) {
162
            [$context, $result] = $this->buildNode($context, $child);
0 ignored issues
show
Bug introduced by
The variable $result does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
163
164
            yield [$context, $result];
165
        }
166
167
        yield from $this->after($context);
168
    }
169
170
    /**
171
     * @param ContextInterface $context
172
     * @param RuleInterface $ast
173
     * @return array|iterable<int,ContextInterface|mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array|iterable<int,ContextInterface|mixed> could not be parsed: Expected "|" or "end of type", but got "<" at position 14. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
174
     * @throws \Railt\Io\Exception\ExternalFileException
175
     */
176
    public function buildNode(ContextInterface $context, RuleInterface $ast): array
177
    {
178
        try {
179
            $process = $this->resolve($context, $ast);
180
181
            if ($process instanceof \Generator) {
182
                return $this->coroutine($context, $process, $ast->getOffset());
183
            }
184
185
            return [$context, $process];
186
        } catch (CompilerException $e) {
187
            throw $e->throwsIn($context->getFile(), $ast->getOffset());
188
        }
189
    }
190
191
    /**
192
     * @param ContextInterface $context
193
     * @param RuleInterface $ast
194
     * @return mixed|\Traversable|void
195
     * @throws \Railt\Io\Exception\ExternalFileException
196
     */
197
    private function resolve(ContextInterface $context, RuleInterface $ast)
198
    {
199
        foreach ($this->builders as $builder) {
200
            if ($builder->match($ast)) {
201
                return $builder->reduce($context, $ast);
202
            }
203
        }
204
205
        $error = \sprintf('Unrecognized rule %s in (%s)', $ast->getName(), $ast);
206
        throw (new InternalException($error))->throwsIn($context->getFile(), $ast->getOffset());
207
    }
208
209
    /**
210
     * @param ContextInterface $ctx
211
     * @param \Generator $process
212
     * @param int $offset
213
     * @return array|iterable<int, ContextInterface|mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array|iterable<int, could not be parsed: Expected "|" or "end of type", but got "<" at position 14. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
214
     */
215
    private function coroutine(ContextInterface $ctx, \Generator $process, int $offset = 0): array
216
    {
217
        while ($process->valid()) {
218
            try {
219
                $value = $process->current();
220
221
                switch (true) {
222
                    case $value instanceof ContextInterface:
223
                        $ctx = $value;
224
                        break;
225
226
                    case $value instanceof RuleInterface:
227
                        [$ctx, $value] = $this->buildNode($ctx, $value);
228
                        break;
229
230
                    case $value instanceof ValueInterface:
231
                        $value = $ctx->declare($process->key(), $value);
0 ignored issues
show
Documentation introduced by
$value is of type object<Railt\SDL\IR\SymbolTable\ValueInterface>, but the function expects a null|object<Railt\SDL\IR\Type\TypeInterface>.

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...
232
                        break;
233
234
                    case \is_string($value):
235
                        $value = $ctx->fetch($value);
236
                        break;
237
238
                    /** @noinspection PhpMissingBreakStatementInspection */
239
                    case $value instanceof \Closure:
240
                        $value = new Deferred($ctx, $value);
241
242
                    case $value instanceof DeferredInterface:
243
                        /** @noinspection SuspiciousAssignmentsInspection */
244
                        $value = $this->store->add($value);
245
246
                        if (! $value->getOffset()) {
247
                            $value->definedIn($offset);
248
                        }
249
250
                        break;
251
                }
252
253
                $process->send($value);
254
            } catch (CompilerException $e) {
255
                $process->throw($e->throwsIn($ctx->getFile(), $offset));
256
            } catch (\Throwable $e) {
257
                $process->throw($e);
258
            }
259
        }
260
261
        return [$ctx, $process->getReturn()];
262
    }
263
264
    /**
265
     * @param ContextInterface $context
266
     * @return \Generator
267
     * @throws CompilerException
268
     */
269
    private function after(ContextInterface $context): \Generator
270
    {
271
        $after = $this->store->extract(function (DeferredInterface $deferred): bool {
272
            return ! $deferred instanceof Identifiable || ! $deferred->getDefinition()->isGeneric();
273
        });
274
275
        foreach ($after as $deferred) {
276
            try {
277
                yield $this->coroutine($context, $deferred->invoke());
278
            } catch (CompilerException $e) {
279
                throw $e->throwsIn($context->getFile(), $deferred->getOffset());
280
            }
281
        }
282
    }
283
}
284