1<?php
2
3declare(strict_types=1);
4
5namespace Antlr\Antlr4\Runtime\Atn;
6
7use Antlr\Antlr4\Runtime\Atn\States\ATNState;
8use Antlr\Antlr4\Runtime\Atn\States\RuleStopState;
9use Antlr\Antlr4\Runtime\Atn\Transitions\ActionTransition;
10use Antlr\Antlr4\Runtime\Atn\Transitions\PredicateTransition;
11use Antlr\Antlr4\Runtime\Atn\Transitions\RuleTransition;
12use Antlr\Antlr4\Runtime\Atn\Transitions\Transition;
13use Antlr\Antlr4\Runtime\CharStream;
14use Antlr\Antlr4\Runtime\Dfa\DFA;
15use Antlr\Antlr4\Runtime\Dfa\DFAState;
16use Antlr\Antlr4\Runtime\Error\Exceptions\LexerNoViableAltException;
17use Antlr\Antlr4\Runtime\IntStream;
18use Antlr\Antlr4\Runtime\Lexer;
19use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext;
20use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContextCache;
21use Antlr\Antlr4\Runtime\PredictionContexts\SingletonPredictionContext;
22use Antlr\Antlr4\Runtime\Token;
23use Antlr\Antlr4\Runtime\Utils\StringUtils;
24
25class LexerATNSimulator extends ATNSimulator
26{
27    public const MIN_DFA_EDGE = 0;
28    public const MAX_DFA_EDGE = 127; // forces unicode to stay in ATN
29    private const NEW_LINE_CODE = 10;
30
31    /** @var array<string> */
32    public $log = [];
33
34    /** @var bool */
35    public $debug = false;
36
37    /** @var Lexer|null */
38    protected $recog;
39
40    /**
41     * The current token's starting index into the character stream.
42     * Shared across DFA to ATN simulation in case the ATN fails and the
43     * DFA did not have a previous accept state. In this case, we use the
44     * ATN-generated exception object.
45     *
46     * @var int
47     */
48    protected $startIndex = -1;
49
50    /**
51     * The line number 1..n within the input.
52     *
53     * @var int
54     */
55    protected $line = 1;
56
57    /**
58     * The index of the character relative to the beginning of the line 0..n-1.
59     *
60     * @var int
61     */
62    protected $charPositionInLine = 0;
63
64    /** @var array<DFA> */
65    public $decisionToDFA = [];
66
67    /** @var int */
68    protected $mode = Lexer::DEFAULT_MODE;
69
70    /**
71     * Used during DFA/ATN exec to record the most recent accept configuration info.
72     *
73     * @var SimState
74     */
75    protected $prevAccept;
76
77    /**
78     * @param array<DFA> $decisionToDFA
79     */
80    public function __construct(
81        ?Lexer $recog,
82        ATN $atn,
83        array $decisionToDFA,
84        PredictionContextCache $sharedContextCache
85    ) {
86        parent::__construct($atn, $sharedContextCache);
87
88        $this->decisionToDFA = $decisionToDFA;
89        $this->recog = $recog;
90        $this->prevAccept = new SimState();
91    }
92
93    public function copyState(LexerATNSimulator $simulator) : void
94    {
95        $this->charPositionInLine = $simulator->charPositionInLine;
96        $this->line = $simulator->line;
97        $this->mode = $simulator->mode;
98        $this->startIndex = $simulator->startIndex;
99    }
100
101    public function getLine() : int
102    {
103        return $this->line;
104    }
105
106    public function setLine(int $line) : void
107    {
108        $this->line = $line;
109    }
110
111    public function getCharPositionInLine() : int
112    {
113        return $this->charPositionInLine;
114    }
115
116    public function setCharPositionInLine(int $charPositionInLine) : void
117    {
118        $this->charPositionInLine = $charPositionInLine;
119    }
120
121    public function match(CharStream $input, int $mode) : int
122    {
123        static $match_calls;
124
125        if ($match_calls === null) {
126            $match_calls = 0;
127        }
128
129        $match_calls++;
130
131        $this->mode = $mode;
132        $mark = $input->mark();
133
134        try {
135            $this->startIndex = $input->getIndex();
136            $this->prevAccept->reset();
137
138            $dfa = $this->decisionToDFA[$mode];
139
140            if ($dfa->s0 === null) {
141                return $this->matchATN($input);
142            }
143            return $this->execATN($input, $dfa->s0);
144        } finally {
145            $input->release($mark);
146        }
147    }
148
149    public function reset() : void
150    {
151        $this->prevAccept->reset();
152        $this->startIndex = -1;
153        $this->line = 1;
154        $this->charPositionInLine = 0;
155        $this->mode = Lexer::DEFAULT_MODE;
156    }
157
158    protected function matchATN(CharStream $input) : int
159    {
160        $startState = $this->atn->modeToStartState[$this->mode];
161
162        if ($this->debug) {
163            $this->log[] = \sprintf('matchATN mode %d start: %s', $this->mode, (string) $startState);
164        }
165
166        $old_mode = $this->mode;
167
168        $s0_closure = $this->computeStartState($input, $startState);
169        $suppressEdge = $s0_closure->hasSemanticContext;
170        $s0_closure->hasSemanticContext = false;
171
172        $next = $this->addDFAState($s0_closure);
173
174        if (!$suppressEdge) {
175            $this->decisionToDFA[$this->mode]->s0 = $next;
176        }
177
178        $predict = $this->execATN($input, $next);
179
180        if ($this->debug) {
181            $this->log[] = \sprintf('DFA after matchATN: %s', $this->decisionToDFA[$old_mode]->toLexerString());
182        }
183
184        return $predict;
185    }
186
187    protected function execATN(CharStream $input, DFAState $ds0) : int
188    {
189        if ($this->debug) {
190            $this->log[] = \sprintf('start state closure=%s', (string) $ds0->configs);
191        }
192
193        if ($ds0->isAcceptState) {
194            // allow zero-length tokens
195            $this->captureSimState($this->prevAccept, $input, $ds0);
196        }
197
198        $t = $input->LA(1);
199        $s = $ds0; // s is current/from DFA state
200
201        while (true) {
202            if ($this->debug) {
203                $this->log[] = \sprintf('execATN loop starting closure: %s', (string) $s->configs);
204            }
205
206            // As we move src->trg, src->trg, we keep track of the previous trg to
207            // avoid looking up the DFA state again, which is expensive.
208            // If the previous target was already part of the DFA, we might
209            // be able to avoid doing a reach operation upon t. If s!=null,
210            // it means that semantic predicates didn't prevent us from
211            // creating a DFA state. Once we know s!=null, we check to see if
212            // the DFA state has an edge already for t. If so, we can just reuse
213            // it's configuration set; there's no point in re-computing it.
214            // This is kind of like doing DFA simulation within the ATN
215            // simulation because DFA simulation is really just a way to avoid
216            // computing reach/closure sets. Technically, once we know that
217            // we have a previously added DFA state, we could jump over to
218            // the DFA simulator. But, that would mean popping back and forth
219            // a lot and making things more complicated algorithmically.
220            // This optimization makes a lot of sense for loops within DFA.
221            // A character will take us back to an existing DFA state
222            // that already has lots of edges out of it. e.g., .* in comments.
223            // print("Target for:" + str(s) + " and:" + str(t))
224            $target = $this->getExistingTargetState($s, $t);
225
226            if ($target === null) {
227                $target = $this->computeTargetState($input, $s, $t);
228            }
229
230            if ($target === ATNSimulator::error()) {
231                break;
232            }
233
234            // If this is a consumable input element, make sure to consume before
235            // capturing the accept state so the input index, line, and char
236            // position accurately reflect the state of the interpreter at the
237            // end of the token.
238            if ($t !== Token::EOF) {
239                $this->consume($input);
240            }
241
242            if ($target->isAcceptState) {
243                $this->captureSimState($this->prevAccept, $input, $target);
244                if ($t === Token::EOF) {
245                    break;
246                }
247            }
248
249            $t = $input->LA(1);
250            $s = $target; // flip; current DFA target becomes new src/from state
251        }
252
253        return $this->failOrAccept($this->prevAccept, $input, $s->configs, $t);
254    }
255
256    /**
257     * Get an existing target state for an edge in the DFA. If the target state
258     * for the edge has not yet been computed or is otherwise not available,
259     * this method returns `null`.
260     *
261     * @param DFAState $s The current DFA state
262     * @param int      $t The next input symbol
263     *
264     * @return DFAState|null The existing target DFA state for the given input symbol
265     *                       `t`, or `null` if the target state for this edg
266     *                       is not already cached.
267     */
268    protected function getExistingTargetState(DFAState $s, int $t) : ?DFAState
269    {
270        if ($s->edges === null || $t < self::MIN_DFA_EDGE || $t > self::MAX_DFA_EDGE) {
271            return null;
272        }
273
274        $target = $s->edges[$t - self::MIN_DFA_EDGE] ?? null;
275
276        if ($this->debug && $target !== null) {
277            $this->log[] = \sprintf('reuse state %d edge to %d', $s->stateNumber, $target->stateNumber);
278        }
279
280        return $target;
281    }
282
283    /**
284     * Compute a target state for an edge in the DFA, and attempt to add the
285     * computed state and corresponding edge to the DFA.
286     *
287     * @param CharStream $input The input stream
288     * @param DFAState   $s     The current DFA state
289     * @param int        $t     The next input symbol
290     *
291     * @return DFAState The computed target DFA state for the given input symbol
292     *                  `t`. If `t` does not lead to a valid DFA state, this
293     *                  method returns {@see LexerATNSimulator::ERROR}.
294     */
295    protected function computeTargetState(CharStream $input, DFAState $s, int $t) : DFAState
296    {
297        $reach = new OrderedATNConfigSet();
298
299        // if we don't find an existing DFA state
300        // Fill reach starting from closure, following t transitions
301        $this->getReachableConfigSet($input, $s->configs, $reach, $t);
302
303        if (\count($reach->elements()) === 0) {
304            // we got nowhere on t from s
305            if (!$reach->hasSemanticContext) {
306                // we got nowhere on t, don't throw out this knowledge; it'd
307                // cause a failover from DFA later.
308                $this->addDFAEdge($s, $t, ATNSimulator::error());
309            }
310
311            // stop when we can't match any more char
312            return ATNSimulator::error();
313        }
314
315        // Add an edge from s to target DFA found/created for reach
316        return $this->addDFAEdgeATNConfigSet($s, $t, $reach) ?? ATNSimulator::error();
317    }
318
319    protected function failOrAccept(SimState $prevAccept, CharStream $input, ATNConfigSet $reach, int $t) : int
320    {
321        if ($this->prevAccept->getDfaState() !== null) {
322            $dfaState = $prevAccept->getDfaState();
323
324            if ($dfaState === null) {
325                throw new \RuntimeException('Unexpected null DFA State.');
326            }
327
328            $lexerActionExecutor = $dfaState->lexerActionExecutor;
329
330            $this->accept(
331                $input,
332                $lexerActionExecutor,
333                $this->startIndex,
334                $prevAccept->getIndex(),
335                $prevAccept->getLine(),
336                $prevAccept->getCharPos()
337            );
338
339            return $dfaState->prediction;
340        }
341
342        // if no accept and EOF is first char, return EOF
343        if ($t === IntStream::EOF && $input->getIndex() === $this->startIndex) {
344            return Token::EOF;
345        }
346
347        if ($this->recog === null) {
348            throw new \RuntimeException('Unexpected null recognizer.');
349        }
350
351        throw new LexerNoViableAltException($this->recog, $input, $this->startIndex, $reach);
352    }
353
354    /**
355     * Given a starting configuration set, figure out all ATN configurations
356     * we can reach upon input `t`. Parameter `reach` is a return parameter.
357     */
358    protected function getReachableConfigSet(
359        CharStream $input,
360        ATNConfigSet $closure,
361        ATNConfigSet $reach,
362        int $t
363    ) : void {
364        // this is used to skip processing for configs which have a lower priority
365        // than a config that already reached an accept state for the same rule
366        $skipAlt = ATN::INVALID_ALT_NUMBER;
367
368        foreach ($closure->elements() as $cfg) {
369            if (!$cfg instanceof LexerATNConfig) {
370                throw new \RuntimeException('Unexpected config type.');
371            }
372
373            $currentAltReachedAcceptState = ($cfg->alt === $skipAlt);
374
375            if ($currentAltReachedAcceptState && $cfg->isPassedThroughNonGreedyDecision()) {
376                continue;
377            }
378
379            if ($this->debug) {
380                $this->log[] = \sprintf(
381                    "testing %s at %s\n",
382                    $this->getTokenName($t),
383                    $cfg->toString(true)
384                );
385            }
386
387            foreach ($cfg->state->getTransitions() as $trans) {
388                $target = $this->getReachableTarget($trans, $t);
389
390                if ($target !== null) {
391                    $lexerExecutor = $cfg->getLexerActionExecutor();
392
393                    if ($lexerExecutor !== null) {
394                        $lexerExecutor = $lexerExecutor->fixOffsetBeforeMatch($input->getIndex() - $this->startIndex);
395                    }
396
397                    $treatEofAsEpsilon = ($t === Token::EOF);
398                    $config = new LexerATNConfig($cfg, $target, null, $lexerExecutor);
399
400                    if ($this->closure(
401                        $input,
402                        $config,
403                        $reach,
404                        $currentAltReachedAcceptState,
405                        true,
406                        $treatEofAsEpsilon
407                    )) {
408                        // any remaining configs for this alt have a lower priority
409                        // than the one that just reached an accept state.
410                        $skipAlt = $cfg->alt;
411                    }
412                }
413            }
414        }
415    }
416
417    protected function accept(
418        CharStream $input,
419        ?LexerActionExecutor $lexerActionExecutor,
420        int $startIndex,
421        int $index,
422        int $line,
423        int $charPos
424    ) : void {
425        if ($this->debug) {
426            $this->log[] = \sprintf('ACTION %s', (string) $lexerActionExecutor) . \PHP_EOL;
427        }
428
429        // seek to after last char in token
430        $input->seek($index);
431        $this->line = $line;
432        $this->charPositionInLine = $charPos;
433
434        if ($lexerActionExecutor !== null && $this->recog) {
435            $lexerActionExecutor->execute($this->recog, $input, $startIndex);
436        }
437    }
438
439    protected function getReachableTarget(Transition $trans, int $t) : ?ATNState
440    {
441        if ($trans->matches($t, Lexer::MIN_CHAR_VALUE, Lexer::MAX_CHAR_VALUE)) {
442            return $trans->target;
443        }
444
445        return null;
446    }
447
448    protected function computeStartState(CharStream $input, ATNState $p) : OrderedATNConfigSet
449    {
450        $initialContext = PredictionContext::empty();
451        $configs = new OrderedATNConfigSet();
452
453        foreach ($p->getTransitions() as $i => $t) {
454            $target = $t->target;
455            $cfg = new LexerATNConfig(null, $target, $initialContext, null, $i + 1);
456            $this->closure($input, $cfg, $configs, false, false, false);
457        }
458
459        return $configs;
460    }
461
462    /**
463     * Since the alternatives within any lexer decision are ordered by
464     * preference, this method stops pursuing the closure as soon as an accept
465     * state is reached. After the first accept state is reached by depth-first
466     * search from `config`, all other (potentially reachable) states for
467     * this rule would have a lower priority.
468     *
469     * @return bool `true` if an accept state is reached, otherwise `false`.
470     */
471    protected function closure(
472        CharStream $input,
473        LexerATNConfig $config,
474        ATNConfigSet $configs,
475        bool $currentAltReachedAcceptState,
476        bool $speculative,
477        bool $treatEofAsEpsilon
478    ) : bool {
479        $cfg = null;
480
481        if ($this->debug) {
482            $this->log[] = \sprintf('closure(%s)', $config->toString(true));
483        }
484
485        if ($config->state instanceof States\RuleStopState) {
486            if ($this->debug) {
487                if ($this->recog) {
488                    $this->log[] = \sprintf(
489                        "closure at %s rule stop %s\n",
490                        $this->recog->getRuleNames()[$config->state->ruleIndex],
491                        $config
492                    );
493                } else {
494                    $this->log[] = \sprintf("closure at rule stop %s\n", $config);
495                }
496            }
497
498            if ($config->context === null || $config->context->hasEmptyPath()) {
499                if ($config->context === null || $config->context->isEmpty()) {
500                    $configs->add($config);
501
502                    return true;
503                }
504
505                $configs->add(new LexerATNConfig($config, $config->state, PredictionContext::empty()));
506                $currentAltReachedAcceptState = true;
507            }
508
509            if ($config->context !== null && !$config->context->isEmpty()) {
510                for ($i = 0; $i < $config->context->getLength(); $i++) {
511                    if ($config->context->getReturnState($i) !== PredictionContext::EMPTY_RETURN_STATE) {
512                        $newContext = $config->context->getParent($i);// "pop" return state
513                        $returnState = $this->atn->states[$config->context->getReturnState($i)];
514                        $cfg = new LexerATNConfig($config, $returnState, $newContext);
515                        $currentAltReachedAcceptState = $this->closure(
516                            $input,
517                            $cfg,
518                            $configs,
519                            $currentAltReachedAcceptState,
520                            $speculative,
521                            $treatEofAsEpsilon
522                        );
523                    }
524                }
525            }
526
527            return $currentAltReachedAcceptState;
528        }
529
530        // optimization
531        if (!$config->state->epsilonOnlyTransitions) {
532            if (!$currentAltReachedAcceptState || !$config->isPassedThroughNonGreedyDecision()) {
533                $configs->add($config);
534            }
535        }
536
537        foreach ($config->state->getTransitions() as $trans) {
538            $cfg = $this->getEpsilonTarget($input, $config, $trans, $configs, $speculative, $treatEofAsEpsilon);
539
540            if ($cfg !== null) {
541                $currentAltReachedAcceptState = $this->closure(
542                    $input,
543                    $cfg,
544                    $configs,
545                    $currentAltReachedAcceptState,
546                    $speculative,
547                    $treatEofAsEpsilon
548                );
549            }
550        }
551
552        return $currentAltReachedAcceptState;
553    }
554
555    /**
556     * side-effect: can alter configs.hasSemanticContext
557     */
558    protected function getEpsilonTarget(
559        CharStream $input,
560        LexerATNConfig $config,
561        Transition $t,
562        ATNConfigSet $configs,
563        bool $speculative,
564        bool $treatEofAsEpsilon
565    ) : ?LexerATNConfig {
566        $cfg = null;
567
568        switch ($t->getSerializationType()) {
569            case Transition::RULE:
570                if (!$t instanceof RuleTransition) {
571                    throw new \RuntimeException('Unexpected transition type.');
572                }
573
574                $newContext = SingletonPredictionContext::create($config->context, $t->followState->stateNumber);
575                $cfg = new LexerATNConfig($config, $t->target, $newContext);
576
577                break;
578
579            case Transition::PRECEDENCE:
580                throw new \RuntimeException('Precedence predicates are not supported in lexers.');
581
582            case Transition::PREDICATE:
583                // Track traversing semantic predicates. If we traverse,
584                // we cannot add a DFA state for this "reach" computation
585                // because the DFA would not test the predicate again in the
586                // future. Rather than creating collections of semantic predicates
587                // like v3 and testing them on prediction, v4 will test them on the
588                // fly all the time using the ATN not the DFA. This is slower but
589                // semantically it's not used that often. One of the key elements to
590                // this predicate mechanism is not adding DFA states that see
591                // predicates immediately afterwards in the ATN. For example,
592
593                // a : ID {p1}? | ID {p2}? ;
594
595                // should create the start state for rule 'a' (to save start state
596                // competition), but should not create target of ID state. The
597                // collection of ATN states the following ID references includes
598                // states reached by traversing predicates. Since this is when we
599                // test them, we cannot cash the DFA state target of ID.
600
601                if (!$t instanceof PredicateTransition) {
602                    throw new \RuntimeException('Unexpected transition type.');
603                }
604
605                if ($this->debug) {
606                    $this->log[] = \sprintf('EVAL rule %d:%d', $t->ruleIndex, $t->predIndex);
607                }
608
609                $configs->hasSemanticContext = true;
610
611                if ($this->evaluatePredicate($input, $t->ruleIndex, $t->predIndex, $speculative)) {
612                    $cfg = new LexerATNConfig($config, $t->target);
613                }
614
615                break;
616
617            case Transition::ACTION:
618                if ($config->context === null || $config->context->hasEmptyPath()) {
619                    // execute actions anywhere in the start rule for a token.
620
621                    // TODO: if the entry rule is invoked recursively, some
622                    // actions may be executed during the recursive call. The
623                    // problem can appear when hasEmptyPath() is true but
624                    // isEmpty() is false. In this case, the config needs to be
625                    // split into two contexts - one with just the empty path
626                    // and another with everything but the empty path.
627                    // Unfortunately, the current algorithm does not allow
628                    // getEpsilonTarget to return two configurations, so
629                    // additional modifications are needed before we can support
630                    // the split operation.
631
632                    if (!$t instanceof ActionTransition) {
633                        throw new \RuntimeException('Unexpected transition type.');
634                    }
635
636                    $lexerAction = $this->atn->lexerActions[$t->actionIndex];
637
638                    $lexerActionExecutor = LexerActionExecutor::append($config->getLexerActionExecutor(), $lexerAction);
639
640                    $cfg = new LexerATNConfig($config, $t->target, null, $lexerActionExecutor);
641                } else {
642                    // ignore actions in referenced rules
643                    $cfg = new LexerATNConfig($config, $t->target);
644                }
645
646                break;
647
648            case Transition::EPSILON:
649                $cfg = new LexerATNConfig($config, $t->target);
650
651                break;
652
653            case Transition::ATOM:
654            case Transition::RANGE:
655            case Transition::SET:
656                if ($treatEofAsEpsilon) {
657                    if ($t->matches(Token::EOF, 0, Lexer::MAX_CHAR_VALUE)) {
658                        $cfg = new LexerATNConfig($config, $t->target);
659                    }
660                }
661
662                break;
663
664            default:
665                $cfg = null;
666        }
667
668        return $cfg;
669    }
670
671    /**
672     * Evaluate a predicate specified in the lexer.
673     *
674     * If `speculative` is `true`, this method was called before
675     * {@see LexerATNSimulator::consume()} for the matched character. This
676     * method should call {@see LexerATNSimulator::consume()} before evaluating
677     * the predicate to ensure position sensitive values, including
678     * {@see Lexer::getText()}, {@see Lexer::getLine()}, and
679     * {@see Lexer::getCharPositionInLine()}, properly reflect the current
680     * lexer state. This method should restore `input` and the simulator
681     * to the original state before returning (i.e. undo the actions made by
682     * the call to {@see LexerATNSimulator::consume()}.
683     *
684     * @param CharStream $input       The input stream.
685     * @param int        $ruleIndex   The rule containing the predicate.
686     * @param int        $predIndex   The index of the predicate within the rule.
687     * @param bool       $speculative `true` if the current index in `input` is
688     *                                one character before the predicate's location.
689     *
690     * @return bool `true` If the specified predicate evaluates to `true`.
691     */
692    protected function evaluatePredicate(CharStream $input, int $ruleIndex, int $predIndex, bool $speculative) : bool
693    {
694        if ($this->recog === null) {
695            return true;
696        }
697
698        if (!$speculative) {
699            return $this->recog->sempred(null, $ruleIndex, $predIndex);
700        }
701
702        $savedcolumn = $this->charPositionInLine;
703        $savedLine = $this->line;
704        $index = $input->getIndex();
705        $marker = $input->mark();
706
707        try {
708            $this->consume($input);
709
710            return $this->recog->sempred(null, $ruleIndex, $predIndex);
711        } finally {
712            $this->charPositionInLine = $savedcolumn;
713            $this->line = $savedLine;
714            $input->seek($index);
715            $input->release($marker);
716        }
717    }
718
719    protected function captureSimState(SimState $settings, CharStream $input, DFAState $dfaState) : void
720    {
721        $settings->setIndex($input->getIndex());
722        $settings->setLine($this->line);
723        $settings->setCharPos($this->charPositionInLine);
724        $settings->setDfaState($dfaState);
725    }
726
727    protected function addDFAEdgeATNConfigSet(DFAState $from, int $t, ATNConfigSet $configs) : DFAState
728    {
729        /* leading to this call, ATNConfigSet.hasSemanticContext is used as a
730         * marker indicating dynamic predicate evaluation makes this edge
731         * dependent on the specific input sequence, so the static edge in the
732         * DFA should be omitted. The target DFAState is still created since
733         * execATN has the ability to resynchronize with the DFA state cache
734         * following the predicate evaluation step.
735         *
736         * TJP notes: next time through the DFA, we see a pred again and eval.
737         * If that gets us to a previously created (but dangling) DFA
738         * state, we can continue in pure DFA mode from there.
739         */
740        $suppressEdge = $configs->hasSemanticContext;
741        $configs->hasSemanticContext = false;
742
743        $to = $this->addDFAState($configs);
744
745        if ($suppressEdge) {
746            return $to;
747        }
748
749        $this->addDFAEdge($from, $t, $to);
750
751        return $to;
752    }
753
754    protected function addDFAEdge(DFAState $from, int $t, ?DFAState $to) : void
755    {
756        // add the edge
757        if ($t < self::MIN_DFA_EDGE || $t > self::MAX_DFA_EDGE) {
758            // Only track edges within the DFA bounds
759            return;
760        }
761
762        if ($this->debug) {
763            $this->log[] = \sprintf('EDGE %s->%s upon %d', $from, $to, $t);
764        }
765
766        if ($from->edges === null) {
767            // make room for tokens 1..n and -1 masquerading as index 0
768            $from->edges = new \SplFixedArray(self::MAX_DFA_EDGE - self::MIN_DFA_EDGE + 1);
769        }
770
771        $from->edges[$t - self::MIN_DFA_EDGE] = $to; // connect
772    }
773
774    /**
775     * Add a new DFA state if there isn't one with this set of configurations
776     * already. This method also detects the first configuration containing
777     * an ATN rule stop state. Later, when traversing the DFA, we will know
778     * which rule to accept.
779     */
780    protected function addDFAState(ATNConfigSet $configs) : DFAState
781    {
782        if ($configs->hasSemanticContext) {
783            throw new \RuntimeException('ATN Config Set cannot have semantic context.');
784        }
785
786        $proposed = new DFAState($configs);
787
788        $firstConfigWithRuleStopState = null;
789
790        foreach ($configs->elements() as $config) {
791            if ($config->state instanceof RuleStopState) {
792                $firstConfigWithRuleStopState = $config;
793
794                break;
795            }
796        }
797
798        if ($firstConfigWithRuleStopState !== null) {
799            if (!$firstConfigWithRuleStopState instanceof LexerATNConfig) {
800                throw new \RuntimeException('Unexpected ATN config type.');
801            }
802
803            $proposed->isAcceptState = true;
804            $proposed->lexerActionExecutor = $firstConfigWithRuleStopState->getLexerActionExecutor();
805
806            $prediction = $this->atn->ruleToTokenType[$firstConfigWithRuleStopState->state->ruleIndex];
807
808            $proposed->prediction = $prediction ?? 0;
809        }
810
811        $dfa = $this->decisionToDFA[$this->mode];
812
813        $existing = $dfa->states->get($proposed);
814
815        if ($existing !== null && $existing instanceof DFAState) {
816            return $existing;
817        }
818
819        $newState = $proposed;
820        $newState->stateNumber = $dfa->states->count();
821        $configs->setReadonly(true);
822        $newState->configs = $configs;
823        $dfa->states->add($newState);
824
825        return $newState;
826    }
827
828    public function getDFA(int $mode) : DFA
829    {
830        return $this->decisionToDFA[$mode];
831    }
832
833    /**
834     * Get the text matched so far for the current token.
835     */
836    public function getText(CharStream $input) : string
837    {
838        // index is first lookahead char, don't include.
839        return $input->getText($this->startIndex, $input->getIndex() - 1);
840    }
841
842    public function consume(CharStream $input) : void
843    {
844        $curChar = $input->LA(1);
845
846        if ($curChar === self::NEW_LINE_CODE) {
847            $this->line++;
848            $this->charPositionInLine = 0;
849        } else {
850            $this->charPositionInLine++;
851        }
852
853        $input->consume();
854    }
855
856    public function getTokenName(int $t) : ?string
857    {
858        if ($t === -1) {
859            return 'EOF';
860        }
861
862        return \sprintf('\'%s\'', StringUtils::char($t));
863    }
864}
865