Completed
Push — master ( 88ff28...2dc58f )
by Kirill
06:03
created

RulesBuilder   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 211
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 6
dl 0
loc 211
ccs 0
cts 90
cp 0
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B build() 0 26 4
A isCompleted() 0 4 1
A add() 0 7 1
A root() 0 4 2
A getId() 0 12 3
A isKept() 0 4 1
A reduce() 0 16 3
A reduceToken() 0 10 1
A reduceConcat() 0 4 1
A complete() 0 17 2
A current() 0 4 1
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\Reader\Resolver;
11
12
use Railt\Compiler\Iterator\SlicedIterator;
13
use Railt\Compiler\Lexer;
14
use Railt\Compiler\Reader\Resolver\Builder\Builder;
15
use Railt\Compiler\Reader\Resolver\Builder\ConcatenationBuilder;
16
use Railt\Compiler\Reader\Resolver\Builder\TokenBuilder;
17
use Railt\Io\Readable;
18
use Railt\Lexer\TokenInterface;
19
20
/**
21
 * Class RulesBuilder
22
 */
23
class RulesBuilder
24
{
25
    /**
26
     * @var array
27
     */
28
    private $rules;
29
30
    /**
31
     * @var RuleResolver
32
     */
33
    private $resolver;
34
35
    /**
36
     * @var array
37
     */
38
    private $parsed = [];
39
40
    /**
41
     * @var \SplStack
42
     */
43
    private $stack;
44
45
    /**
46
     * @var Readable|null
47
     */
48
    private $file;
49
50
    /**
51
     * @var array
52
     */
53
    private $mappings = [];
54
55
    /**
56
     * @var int
57
     */
58
    private $lastRuleId = 0;
59
60
    /**
61
     * @var string
62
     */
63
    private $text = '';
64
65
    /**
66
     * RulesBuilder constructor.
67
     * @param RuleResolver $resolver
68
     */
69
    public function __construct(RuleResolver $resolver)
70
    {
71
        $this->resolver = $resolver;
72
        $this->rules    = $resolver->getRules();
73
        $this->stack    = new \SplStack();
74
    }
75
76
    /**
77
     * @return array
78
     */
79
    public function build(): array
80
    {
81
        foreach ($this->rules as $rule => $tokens) {
82
            if ($this->isCompleted($rule)) {
83
                continue;
84
            }
85
86
87
            $this->file = $this->resolver->getFiles()[$rule];
88
89
            $iterator = new SlicedIterator($tokens);
90
91
            $this->stack->push($current = $this->root($rule));
92
93
            while ($iterator->valid()) {
94
                $this->text .= $iterator->current()->value() . ' ';
95
96
                $this->reduce($iterator->current(), $iterator);
97
                $iterator->next();
98
            }
99
100
            $this->complete();
101
        }
102
103
        return $this->parsed;
104
    }
105
106
    /**
107
     * @param string $rule
108
     * @return bool
109
     */
110
    private function isCompleted(string $rule): bool
111
    {
112
        return \array_key_exists($rule, $this->parsed);
113
    }
114
115
    /**
116
     * @param Builder $builder
117
     * @return RulesBuilder
118
     */
119
    private function add(Builder $builder): RulesBuilder
120
    {
121
        $this->current()->jump($builder->getId());
122
        $this->stack->push($builder);
123
124
        return $this;
125
    }
126
127
    /**
128
     * @param string $rule
129
     * @return ConcatenationBuilder
130
     */
131
    private function root(string $rule): ConcatenationBuilder
132
    {
133
        return new ConcatenationBuilder($this->getId($rule), $this->isKept($rule) ? $rule : null);
134
    }
135
136
    /**
137
     * @param string $rule
138
     * @return int
139
     */
140
    private function getId(string $rule = null): int
141
    {
142
        if ($rule === null) {
143
            return $this->lastRuleId++;
144
        }
145
146
        if (\array_key_exists($rule, $this->mappings)) {
147
            return $this->mappings[$rule];
148
        }
149
150
        return $this->mappings[$rule] = $this->lastRuleId++;
151
    }
152
153
    /**
154
     * @param string $rule
155
     * @return bool
156
     */
157
    private function isKept(string $rule): bool
158
    {
159
        return \in_array($rule, $this->resolver->getKeep(), true);
160
    }
161
162
    /**
163
     * @param TokenInterface $token
164
     * @param SlicedIterator $tokens
165
     * @return int
166
     */
167
    private function reduce(TokenInterface $token, SlicedIterator $tokens): int
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...
168
    {
169
        switch ($token->name()) {
170
            case Lexer::T_KEPT:
171
                return $this->reduceToken($token, true);
172
173
            case Lexer::T_SKIPPED:
174
                return $this->reduceToken($token, false);
175
176
            //default:
177
            //    $error = \sprintf('Unexpected token %s', $token);
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
178
            //    throw (new GrammarException($error))->throwsIn($this->file, $token->offset());
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
179
        }
180
181
        return 0;
182
    }
183
184
    /**
185
     * @param TokenInterface $token
186
     * @param bool $keep
187
     * @return int
188
     */
189
    private function reduceToken(TokenInterface $token, bool $keep): int
190
    {
191
        $key = $token->value(1) . ':' . $token->name();
192
193
        $builder = new TokenBuilder($this->getId($key), $token->value(1), $keep);
194
195
        $this->add($builder)->complete();
196
197
        return $builder->getId();
198
    }
199
200
    private function reduceConcat(SlicedIterator $iterator)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
201
    {
202
        dd($iterator);
203
    }
204
205
    /**
206
     * @return RulesBuilder
207
     */
208
    private function complete(): RulesBuilder
209
    {
210
        /** @var Builder $rule */
211
        $rule = $this->stack->pop();
212
        $instance = $rule->build();
213
214
        if ($rule->getName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rule->getName() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
215
            $this->parsed[$rule->getName()] = $instance;
216
        } else {
217
            $this->parsed[] = $instance;
218
        }
219
220
        $instance->{'--grammar--'} = $this->text;
221
        $this->text = '';
222
223
        return $this;
224
    }
225
226
    /**
227
     * @return Builder
228
     */
229
    private function current(): Builder
230
    {
231
        return $this->stack->top();
232
    }
233
}
234