Passed
Push — master ( 04d7e0...bd8f2a )
by Kirill
02:28
created

Analyzer::repeat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 2
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\Compiler\Grammar\PP2;
11
12
use Railt\Compiler\Exception\GrammarException;
13
use Railt\Compiler\Grammar\PP2\Builder\AlternationBuilder;
14
use Railt\Compiler\Grammar\PP2\Builder\Builder;
15
use Railt\Compiler\Grammar\PP2\Builder\ConcatenationBuilder;
16
use Railt\Compiler\Grammar\PP2\Builder\TokenBuilder;
17
use Railt\Compiler\Iterator\LookaheadIterator;
18
use Railt\Compiler\Reader\ProvideRules;
19
use Railt\Compiler\Reader\ProvideTokens;
20
use Railt\Io\Readable;
21
use Railt\Lexer\TokenInterface;
22
use Railt\Parser\Rule\Production;
23
use Railt\Parser\Rule\Symbol;
24
use Railt\Parser\Rule\Terminal;
25
use Railt\Parser\Rule\Token;
26
27
/**
28
 * Class Analyzer
29
 */
30
class Analyzer
31
{
32
    /**
33
     * A list of parsed rules
34
     *
35
     * @var array|Symbol[]|Terminal[]|Production[]
36
     */
37
    private $parsed = [];
38
39
    /**
40
     * @var array|Builder[]
41
     */
42
    private $builders = [];
43
44
    /**
45
     * @var Mapping
46
     */
47
    private $mapping;
48
49
    /**
50
     * @var array
51
     */
52
    private $ruleTokens = [];
53
54
    /**
55
     * @var ProvideTokens
56
     */
57
    private $tokens;
58
59
    /**
60
     * @var Readable
61
     */
62
    private $file;
63
64
    /**
65
     * @var ProvideRules
66
     */
67
    private $rules;
68
69
    /**
70
     * @var Builder
71
     */
72
    private $lastRule;
73
74
    /**
75
     * Analyzer constructor.
76
     * @param ProvideTokens $tokens
77
     * @param ProvideRules $rules
78
     */
79
    public function __construct(ProvideTokens $tokens, ProvideRules $rules)
80
    {
81
        $this->mapping = new Mapping();
82
        $this->tokens  = $tokens;
83
        $this->rules = $rules;
84
    }
85
86
    /**
87
     * @param string $rule
88
     * @param iterable $tokens
89
     */
90
    public function add(string $rule, iterable $tokens): void
91
    {
92
        $this->ruleTokens[$rule] = $tokens;
93
    }
94
95
    /**
96
     * @param string $rule
97
     * @return bool
98
     */
99
    public function isCompleted(string $rule): bool
100
    {
101
        return \array_key_exists($rule, $this->parsed);
102
    }
103
104
    /**
105
     * @return array
106
     * @throws \InvalidArgumentException
107
     * @throws \Railt\Io\Exception\ExternalFileException
108
     */
109
    public function getResult(): array
110
    {
111
        $this->parsed = [];
112
        $this->parse();
113
114
        foreach ($this->builders as $builder) {
115
            $this->parsed[] = $builder->reduce();
116
        }
117
118
        return $this->parsed;
119
    }
120
121
    /**
122
     * @throws \InvalidArgumentException
123
     * @throws \Railt\Io\Exception\ExternalFileException
124
     */
125
    private function parse(): void
126
    {
127
        foreach ($this->ruleTokens as $name => $tokens) {
128
            $this->file = $this->rules->getFile($name);
129
130
            $iterator = new LookaheadIterator($tokens);
131
            $rule = $this->sequence($iterator);
132
133
            if ($this->rules->isKeep($name) && ! $rule->hasName()) {
134
                $rule->rename($name);
135
            }
136
137
            $this->store($rule);
138
        }
139
    }
140
141
    /**
142
     * @param Builder $builder
143
     * @return Builder
144
     */
145
    private function store(Builder $builder): Builder
146
    {
147
        return $this->builders[] = $this->lastRule = $builder;
148
    }
149
150
    /**
151
     * @param LookaheadIterator $tokens
152
     * @return Builder
153
     * @throws \InvalidArgumentException
154
     * @throws \Railt\Io\Exception\ExternalFileException
155
     */
156
    private function choice(LookaheadIterator $tokens): Builder
157
    {
158
        $choice = new AlternationBuilder($this->mapping);
159
        $choice->addChildBuilder($this->lastRule);
160
        $tokens->next();
161
162
        while ($tokens->valid()) {
163
            $child = $this->terminal($tokens);
164
165
            if ($child) {
166
                $choice->addChildBuilder($this->store($child));
167
            }
168
169
            $continue = $tokens->getNext() && $tokens->getNext()->name() === Lexer::T_OR;
170
171
            if (! $continue) {
172
                break;
173
            }
174
175
            $tokens->next();
176
            $tokens->next();
177
        }
178
179
        return $choice;
180
    }
181
182
    /**
183
     * @return Mapping
184
     */
185
    public function getMapping(): Mapping
186
    {
187
        return $this->mapping;
188
    }
189
190
    /**
191
     * @param LookaheadIterator $tokens
192
     * @return Builder
193
     * @throws \InvalidArgumentException
194
     * @throws \Railt\Io\Exception\ExternalFileException
195
     */
196
    private function sequence(LookaheadIterator $tokens): Builder
197
    {
198
        $children = [];
199
200
        while ($tokens->valid()) {
201
            $child = $this->terminal($tokens);
202
203
            if ($child) {
204
                $children[] = $this->store($child);
205
            }
206
207
            $tokens->next();
208
        }
209
210
        if (\count($children) > 1) {
211
            $sequence = new ConcatenationBuilder($this->mapping);
212
            $sequence->addChildrenBuilders($children);
213
214
            return $sequence;
215
        }
216
217
        return \reset($children);
218
    }
219
220
    private function repeat(LookaheadIterator $tokens): Builder
0 ignored issues
show
Unused Code introduced by
The parameter $tokens is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
221
    {
222
223
    }
224
225
    /**
226
     * @param LookaheadIterator $tokens
227
     * @return Builder
228
     * @throws \InvalidArgumentException
229
     * @throws \Railt\Io\Exception\ExternalFileException
230
     */
231
    private function group(LookaheadIterator $tokens): Builder
232
    {
233
        $children = [];
234
235
        $tokens->next();
236
        while ($tokens->valid() && $tokens->current()->name() !== Lexer::T_GROUP_CLOSE) {
237
            $children[] = $tokens->current();
238
            $tokens->next();
239
        }
240
241
        return $this->sequence(new LookaheadIterator($children));
0 ignored issues
show
Documentation introduced by
$children is of type array, but the function expects a object<Railt\Compiler\Iterator\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...
242
    }
243
244
    /**
245
     * @param LookaheadIterator $tokens
246
     * @return null|Builder
247
     * @throws \InvalidArgumentException
248
     * @throws \Railt\Io\Exception\ExternalFileException
249
     */
250
    private function terminal(LookaheadIterator $tokens): ?Builder
251
    {
252
        /** @var TokenInterface $current */
253
        $current = $tokens->current();
254
255
        switch ($current->name()) {
256
            case Lexer::T_OR:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
257
                return $this->choice($tokens);
258
259
            case Lexer::T_ZERO_OR_ONE:
260
            case Lexer::T_ONE_OR_MORE:
261
            case Lexer::T_ZERO_OR_MORE:
262
            case Lexer::T_N_TO_M:
263
            case Lexer::T_ZERO_TO_M:
264
            case Lexer::T_N_OR_MORE:
265
            case Lexer::T_EXACTLY_N:
266
                return $this->repeat($tokens);
267
268
            case Lexer::T_KEPT:
269
                return $this->token($current, true);
270
271
            case Lexer::T_SKIPPED:
272
                return $this->token($current, false);
273
274
            case Lexer::T_INVOKE:
275
                return $this->invoke($current);
276
277
            case Lexer::T_GROUP_OPEN:
278
                return $this->group($tokens);
279
280
            case Lexer::T_RENAME:
281
                $this->lastRule->rename($current->value(1));
282
                return null;
283
        }
284
285
        throw (new GrammarException(\sprintf('Unrecognized terminal %s', $current)))
286
            ->throwsIn($this->file, $current->offset());
287
    }
288
289
    /**
290
     * @param TokenInterface $token
291
     * @param bool $keep
292
     * @return TokenBuilder
293
     * @throws \Railt\Io\Exception\ExternalFileException
294
     */
295
    private function token(TokenInterface $token, bool $keep): TokenBuilder
296
    {
297
        $name = $token->value(1);
298
299
        if (! $this->tokens->has($name)) {
300
            $error = \sprintf('Token "%s" is not defined', $name);
301
            throw (new GrammarException($error))
302
                ->throwsIn($this->file, $token->offset());
303
        }
304
305
        return new TokenBuilder($this->mapping, $name, $keep);
306
    }
307
308
    /**
309
     * @param TokenInterface $invocation
310
     * @return Builder
311
     * @throws \Railt\Io\Exception\ExternalFileException
312
     */
313
    private function invoke(TokenInterface $invocation): Builder
314
    {
315
        $name = $invocation->value(1);
316
317
        if (! $this->rules->has($name)) {
318
            $error = \sprintf('Rule "%s" is not defined', $name);
319
            throw (new GrammarException($error))
320
                ->throwsIn($this->file, $invocation->offset());
321
        }
322
323
        // TODO
324
    }
325
}
326