ATNConfigSet.php$0 ➔ add()   B
last analyzed

Complexity

Conditions 10

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 24.0608

Importance

Changes 0
Metric Value
cc 10
c 0
b 0
f 0
dl 0
loc 55
ccs 12
cts 25
cp 0.48
crap 24.0608
rs 7.1151

How to fix   Long Method    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
declare(strict_types=1);
4
5
namespace Antlr\Antlr4\Runtime\Atn;
6
7
use Antlr\Antlr4\Runtime\Atn\SemanticContexts\SemanticContext;
8
use Antlr\Antlr4\Runtime\Comparison\Equality;
9
use Antlr\Antlr4\Runtime\Comparison\Equivalence;
10
use Antlr\Antlr4\Runtime\Comparison\Hashable;
11
use Antlr\Antlr4\Runtime\Comparison\Hasher;
12
use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext;
13
use Antlr\Antlr4\Runtime\Utils\BitSet;
14
use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap;
15
use Antlr\Antlr4\Runtime\Utils\Set;
16
17
/**
18
 * Specialized {@see Set} of `{@see ATNConfig}`s that can track info
19
 * about the set, with support for combining similar configurations using
20
 * a graph-structured stack.
21
 */
22
class ATNConfigSet implements Hashable
23
{
24
    /**
25
     * Indicates that the set of configurations is read-only. Do not
26
     * allow any code to manipulate the set; DFA states will point at
27
     * the sets and they must not change. This does not protect the other
28
     * fields; in particular, conflictingAlts is set after
29
     * we've made this readonly.
30
     *
31
     * @var bool
32
     */
33
    protected $readOnly = false;
34
35
    /**
36
     * All configs but hashed by (s, i, _, pi) not including context. Wiped out
37
     * when we go readonly as this set becomes a DFA state.
38
     *
39
     * @var Set|null
40
     */
41
    public $configLookup;
42
43
    /**
44
     * Track the elements as they are added to the set; supports get(i).
45
     *
46
     * @var array<ATNConfig>
47
     */
48
    public $configs = [];
49
50
    /** @var int */
51
    public $uniqueAlt = 0;
52
53
    /**
54
     * Currently this is only used when we detect SLL conflict; this does
55
     * not necessarily represent the ambiguous alternatives. In fact, I should
56
     * also point out that this seems to include predicated alternatives that
57
     * have predicates that evaluate to false. Computed in computeTargetState().
58
     *
59
     * @var BitSet|null
60
     */
61
    protected $conflictingAlts;
62
63
    /**
64
     * Used in parser and lexer. In lexer, it indicates we hit a pred
65
     * while computing a closure operation. Don't make a DFA state from this.
66
     *
67
     * @var bool
68
     */
69
    public $hasSemanticContext = false;
70
71
    /** @var bool */
72
    public $dipsIntoOuterContext = false;
73
74
    /**
75
     * Indicates that this configuration set is part of a full context LL
76
     * prediction. It will be used to determine how to merge $. With SLL it's
77
     * a wildcard whereas it is not for LL context merge.
78
     *
79
     * @var bool
80
     */
81
    public $fullCtx;
82
83
    /** @var int|null */
84
    private $cachedHashCode;
85
86 7
    public function __construct(bool $fullCtx = true)
87
    {
88
        /*
89
         * The reason that we need this is because we don't want the hash map to
90
         * use the standard hash code and equals. We need all configurations with
91
         * the same `(s,i,_,semctx)` to be equal. Unfortunately, this key
92
         * effectively doubles the number of objects associated with ATNConfigs.
93
         * The other solution is to use a hash table that lets us specify the
94
         * equals/hashcode operation. All configs but hashed by (s, i, _, pi)
95
         * not including context. Wiped out when we go readonly as this se
96
         * becomes a DFA state.
97
         */
98 7
        $this->configLookup = new Set(new class implements Equivalence {
99
            public function equivalent(Hashable $left, Hashable $right) : bool
100
            {
101 4
                if ($left === $right) {
102
                    return true;
103
                }
104
105 4
                if (!$left instanceof ATNConfig || !$right instanceof ATNConfig) {
106
                    return false;
107
                }
108
109 4
                return $left->alt === $right->alt
110 4
                    && $left->semanticContext->equals($right->semanticContext)
111 4
                    && Equality::equals($left->state, $right->state);
112
            }
113
114
            public function hash(Hashable $value) : int
115
            {
116 4
                return $value->hashCode();
117
            }
118
119
            public function equals(object $other) : bool
120
            {
121
                return $other instanceof self;
122
            }
123
        });
124
125 7
        $this->fullCtx = $fullCtx;
126 7
    }
127
128
    /**
129
     * Adding a new config means merging contexts with existing configs for
130
     * `(s, i, pi, _)`, where `s` is the {@see ATNConfig::$state}, `i` is the
131
     * {@see ATNConfig::$alt}, and `pi` is the {@see ATNConfig::$semanticContext}.
132
     * We use `(s,i,pi)` as key.
133
     *
134
     * This method updates {@see ATNConfigSet::$dipsIntoOuterContext} and
135
     * {@see ATNConfigSet::$hasSemanticContext} when necessary.
136
     *
137
     * @throws \InvalidArgumentException
138
     */
139 5
    public function add(ATNConfig $config, ?DoubleKeyMap $mergeCache = null) : bool
140
    {
141 5
        if ($this->readOnly || $this->configLookup === null) {
142
            throw new \InvalidArgumentException('This set is readonly.');
143
        }
144
145 5
        if ($config->semanticContext !== SemanticContext::none()) {
0 ignored issues
show
introduced by
The condition $config->semanticContext...SemanticContext::none() is always true.
Loading history...
146 2
            $this->hasSemanticContext = true;
147
        }
148
149 5
        if ($config->reachesIntoOuterContext > 0) {
150 4
            $this->dipsIntoOuterContext = true;
151
        }
152
153 5
        if ($this->configLookup === null) {
154
            throw new \RuntimeException('This set is readonly.');
155
        }
156
157
        /** @var ATNConfig $existing */
158 5
        $existing = $this->configLookup->getOrAdd($config);
159
160 5
        if ($existing->equals($config)) {
161 5
            $this->cachedHashCode = null;
162
163 5
            $this->configs[] = $config; // track order here
164
165 5
            return true;
166
        }
167
168
        // A previous (s,i,pi,_), merge with it and save result
169
        $rootIsWildcard = !$this->fullCtx;
170
171
        if ($existing->context === null || $config->context === null) {
172
            throw new \RuntimeException('Unexpected null context.');
173
        }
174
175
        $merged = PredictionContext::merge($existing->context, $config->context, $rootIsWildcard, $mergeCache);
176
177
        // No need to check for existing->context, config->context in cache
178
        // since only way to create new graphs is "call rule" and here. We
179
        // cache at both places.
180
        $existing->reachesIntoOuterContext = \max(
181
            $existing->reachesIntoOuterContext,
182
            $config->reachesIntoOuterContext
183
        );
184
185
        // Make sure to preserve the precedence filter suppression during the merge
186
        if ($config->isPrecedenceFilterSuppressed()) {
187
            $existing->setPrecedenceFilterSuppressed(true);
188
        }
189
190
        // Replace context; no need to alt mapping
191
        $existing->context = $merged;
192
193
        return true;
194
    }
195
196
    /**
197
     * Return a List holding list of configs.
198
     *
199
     * @return array<ATNConfig>
200
     */
201 7
    public function elements() : array
202
    {
203 7
        return $this->configs;
204
    }
205
206
    public function getStates() : Set
207
    {
208
        $states = new Set();
209
        foreach ($this->configs as $config) {
210
            if ($config !== null) {
211
                $states->add($config->state);
212
            }
213
        }
214
215
        return $states;
216
    }
217
218
    /**
219
     * Gets the complete set of represented alternatives for the configurationc set.
220
     *
221
     * @return BitSet The set of represented alternatives in this configuration set.
222
     */
223
    public function getAlts() : BitSet
224
    {
225
        $alts = new BitSet();
226
        foreach ($this->configs as $config) {
227
            if ($config !== null) {
228
                $alts->add($config->alt);
229
            }
230
        }
231
232
        return $alts;
233
    }
234
235
    /**
236
     * @return array<SemanticContext>
237
     */
238
    public function getPredicates() : array
239
    {
240
        $predicates = [];
241
        foreach ($this->configs as $config) {
242
            if ($config->semanticContext !== SemanticContext::none()) {
243
                $predicates[] = $config->semanticContext;
244
            }
245
        }
246
247
        return $predicates;
248
    }
249
250
    public function get(int $index) : ATNConfig
251
    {
252
        return $this->configs[$index];
253
    }
254
255 3
    public function optimizeConfigs(ATNSimulator $interpreter) : void
256
    {
257 3
        if ($this->readOnly || $this->configLookup === null) {
258
            throw new \InvalidArgumentException('This set is readonly');
259
        }
260
261 3
        if ($this->configLookup->isEmpty()) {
262
            return;
263
        }
264
265 3
        foreach ($this->configs as $config) {
266 3
            if ($config !== null && $config->context !== null) {
267 3
                $config->context = $interpreter->getCachedContext($config->context);
268
            }
269
        }
270 3
    }
271
272
    /**
273
     * @param array<ATNConfig> $configs
274
     */
275
    public function addAll(array $configs) : void
276
    {
277
        foreach ($configs as $config) {
278
            $this->add($config);
279
        }
280
    }
281
282 2
    public function equals(object $other) : bool
283
    {
284 2
        if ($this === $other) {
285
            return true;
286
        }
287
288 2
        if (!$other instanceof self) {
289
            return false;
290
        }
291
292 2
        return $this->fullCtx === $other->fullCtx
293 2
            && $this->uniqueAlt === $other->uniqueAlt
294 2
            && $this->hasSemanticContext === $other->hasSemanticContext
295 2
            && $this->dipsIntoOuterContext === $other->dipsIntoOuterContext
296 2
            && Equality::equals($this->configs, $other->configs)
297 2
            && Equality::equals($this->conflictingAlts, $other->conflictingAlts);
298
    }
299
300 4
    public function hashCode() : int
301
    {
302 4
        if (!$this->isReadOnly()) {
303 4
            return Hasher::hash($this->configs);
304
        }
305
306 4
        if ($this->cachedHashCode === null) {
307 4
            $this->cachedHashCode = Hasher::hash($this->configs);
308
        }
309
310 4
        return $this->cachedHashCode;
311
    }
312
313 3
    public function getLength() : int
314
    {
315 3
        return \count($this->configs);
316
    }
317
318 3
    public function isEmpty() : bool
319
    {
320 3
        return $this->getLength() === 0;
321
    }
322
323
    public function contains(object $item) : bool
324
    {
325
        if ($this->configLookup === null) {
326
            throw new \InvalidArgumentException('This method is not implemented for readonly sets.');
327
        }
328
329
        return $this->configLookup->contains($item);
330
    }
331
332
    public function containsFast(ATNConfig $item) : bool
333
    {
334
        return $this->contains($item);
335
    }
336
337
    public function getIterator() : \Iterator
338
    {
339
        return new \ArrayIterator($this->configs);
340
    }
341
342
    public function clear() : void
343
    {
344
        if ($this->readOnly) {
345
            throw new \InvalidArgumentException('This set is readonly');
346
        }
347
348
        $this->configs = [];
349
        $this->cachedHashCode = -1;
350
        $this->configLookup = new Set();
351
    }
352
353 4
    public function isReadOnly() : bool
354
    {
355 4
        return $this->readOnly;
356
    }
357
358 4
    public function setReadonly(bool $readOnly) : void
359
    {
360 4
        $this->readOnly = $readOnly;
361
362 4
        if ($readOnly) {
363 4
            $this->configLookup = null; // can't mod, no need for lookup cache
364
        }
365 4
    }
366
367
    public function getConflictingAlts() : ?BitSet
368
    {
369
        return $this->conflictingAlts;
370
    }
371
372
    public function setConflictingAlts(BitSet $conflictingAlts) : void
373
    {
374
        $this->conflictingAlts = $conflictingAlts;
375
    }
376
377
    public function __toString() : string
378
    {
379
        return \sprintf(
380
            '[%s]%s%s%s%s',
381
            \implode(', ', $this->configs),
382
            $this->hasSemanticContext ? ',hasSemanticContext=' . $this->hasSemanticContext : '',
0 ignored issues
show
Bug introduced by
Are you sure $this->hasSemanticContext of type true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

382
            $this->hasSemanticContext ? ',hasSemanticContext=' . /** @scrutinizer ignore-type */ $this->hasSemanticContext : '',
Loading history...
383
            $this->uniqueAlt !== ATN::INVALID_ALT_NUMBER ? ',uniqueAlt=' . $this->uniqueAlt : '',
384
            $this->conflictingAlts !== null ? ',conflictingAlts=' . $this->conflictingAlts : '',
385
            $this->dipsIntoOuterContext ? ',dipsIntoOuterContext' : ''
386
        );
387
    }
388
}
389