Skip to content

Commit ac71eb3

Browse files
authored
Add support for ATN tracing (#32)
1 parent 64fd1e2 commit ac71eb3

9 files changed

+257
-25
lines changed

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
],
1313
"require": {
1414
"php": "^8.0",
15-
"ext-mbstring": "*"
15+
"ext-mbstring": "^8.0",
16+
"psr/log": "^2.0 || ^3.0"
1617
},
1718
"require-dev": {
1819
"ergebnis/composer-normalize": "^2.15",

composer.lock

+66-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

phpcs.xml.dist

+4
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,10 @@
636636
<severity>5</severity>
637637
</rule>
638638

639+
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint">
640+
<exclude-pattern>src/StdoutMessageLogger.php</exclude-pattern>
641+
</rule>
642+
639643
<rule ref="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix">
640644
<exclude-pattern>src/Atn/Transitions/AbstractPredicateTransition.php</exclude-pattern>
641645
</rule>

src/Atn/ATNConfig.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public function toString(bool $showAlt): string
157157
$buf .= ',[' . $this->context . ']';
158158
}
159159

160-
if ($this->semanticContext->equals(SemanticContext::none())) {
160+
if (!$this->semanticContext->equals(SemanticContext::none())) {
161161
$buf .= ',' . $this->semanticContext;
162162
}
163163

@@ -177,9 +177,9 @@ public function __toString(): string
177177
$this->state,
178178
$this->alt,
179179
$this->context !== null ? ',[' . $this->context . ']' : '',
180-
$this->semanticContext->equals(SemanticContext::none()) ?
181-
',' . $this->semanticContext :
182-
'',
180+
$this->semanticContext->equals(SemanticContext::none())
181+
? ''
182+
: ',' . $this->semanticContext,
183183
$this->reachesIntoOuterContext > 0 ? ',up=' . $this->reachesIntoOuterContext : '',
184184
);
185185
}

src/Atn/ParserATNSimulator.php

+70-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Antlr\Antlr4\Runtime\Error\Exceptions\RecognitionException;
2525
use Antlr\Antlr4\Runtime\IntervalSet;
2626
use Antlr\Antlr4\Runtime\IntStream;
27+
use Antlr\Antlr4\Runtime\LoggerProvider;
2728
use Antlr\Antlr4\Runtime\Parser;
2829
use Antlr\Antlr4\Runtime\ParserRuleContext;
2930
use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext;
@@ -35,6 +36,7 @@
3536
use Antlr\Antlr4\Runtime\Utils\BitSet;
3637
use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap;
3738
use Antlr\Antlr4\Runtime\Utils\Set;
39+
use Psr\Log\LoggerInterface as Logger;
3840

3941
/**
4042
* The embodiment of the adaptive LL(*), ALL(*), parsing strategy.
@@ -231,6 +233,8 @@
231233
*/
232234
final class ParserATNSimulator extends ATNSimulator
233235
{
236+
public static bool $traceAtnSimulation = false;
237+
234238
protected Parser $parser;
235239

236240
/** @var array<DFA> */
@@ -260,6 +264,8 @@ final class ParserATNSimulator extends ATNSimulator
260264

261265
protected ?DFA $dfa = null;
262266

267+
private Logger $logger;
268+
263269
/**
264270
* @param array<DFA> $decisionToDFA
265271
*/
@@ -273,6 +279,7 @@ public function __construct(
273279

274280
$this->parser = $parser;
275281
$this->decisionToDFA = $decisionToDFA;
282+
$this->logger = LoggerProvider::getLogger();
276283
}
277284

278285
public function reset(): void
@@ -296,6 +303,18 @@ public function clearDFA(): void
296303
*/
297304
public function adaptivePredict(TokenStream $input, int $decision, ParserRuleContext $outerContext): int
298305
{
306+
if (self::$traceAtnSimulation) {
307+
$this->logger->debug(
308+
'adaptivePredict decision {decision} exec LA(1)=={token} line {line}:{pos}',
309+
[
310+
'decision' => $decision,
311+
'token' => $this->getTokenName($input->LA(1)),
312+
'line' => $input->LT(1)?->getLine(),
313+
'pos' => $input->LT(1)?->getCharPositionInLine(),
314+
],
315+
);
316+
}
317+
299318
$this->input = $input;
300319
$this->startIndex = $input->getIndex();
301320
$this->outerContext = $outerContext;
@@ -404,6 +423,19 @@ public function execATN(
404423
int $startIndex,
405424
ParserRuleContext $outerContext,
406425
): ?int {
426+
if (self::$traceAtnSimulation) {
427+
$this->logger->debug(
428+
'execATN decision {decision}, DFA state {state}, LA(1)=={token} line {line}:{pos}',
429+
[
430+
'decision' => $dfa->decision,
431+
'state' => $s0->__toString(),
432+
'token' => $this->getTokenName($input->LA(1)),
433+
'line' => $input->LT(1)?->getLine(),
434+
'pos' => $input->LT(1)?->getCharPositionInLine(),
435+
],
436+
);
437+
}
438+
407439
$previousD = $s0;
408440

409441
$t = $input->LA(1);
@@ -655,6 +687,12 @@ protected function execATNWithFullContext(
655687
int $startIndex,
656688
ParserRuleContext $outerContext,
657689
): int {
690+
if (self::$traceAtnSimulation) {
691+
$this->logger->debug('execATNWithFullContext {state}', [
692+
'state' => $s0->__toString(),
693+
]);
694+
}
695+
658696
$fullCtx = true;
659697
$foundExactAmbig = false;
660698
$reach = null;
@@ -890,15 +928,18 @@ protected function computeReachSet(ATNConfigSet $closure, int $t, bool $fullCtx)
890928
* multiple alternatives are viable.*/
891929

892930
if ($skippedStopStates !== null && (!$fullCtx || !PredictionMode::hasConfigInRuleStopState($reach))) {
893-
if (\count($skippedStopStates) === 0) {
894-
throw new \LogicException('Skipped stop states cannot be empty.');
895-
}
896-
897931
foreach ($skippedStopStates as $lValue) {
898932
$reach->add($lValue, $this->mergeCache);
899933
}
900934
}
901935

936+
if (self::$traceAtnSimulation) {
937+
$this->logger->debug('computeReachSet {closure} -> {reach}', [
938+
'closure' => $closure->__toString(),
939+
'reach' => $reach->__toString(),
940+
]);
941+
}
942+
902943
if ($reach->isEmpty()) {
903944
return null;
904945
}
@@ -964,6 +1005,13 @@ protected function computeStartState(ATNState $p, RuleContext $ctx, bool $fullCt
9641005
$initialContext = PredictionContext::fromRuleContext($this->atn, $ctx);
9651006
$configs = new ATNConfigSet($fullCtx);
9661007

1008+
if (self::$traceAtnSimulation) {
1009+
$this->logger->debug('computeStartState from ATN state {state} initialContext={initialContext}', [
1010+
'state' => $p->__toString(),
1011+
'initialContext' => $initialContext->__toString(),
1012+
]);
1013+
}
1014+
9671015
foreach ($p->getTransitions() as $i => $t) {
9681016
$c = new ATNConfig(null, $t->target, $initialContext, null, $i + 1);
9691017
$closureBusy = new Set();
@@ -1464,6 +1512,12 @@ protected function closureCheckingStopState(
14641512
int $depth,
14651513
bool $treatEofAsEpsilon,
14661514
): void {
1515+
if (self::$traceAtnSimulation) {
1516+
$this->logger->debug('closure({config})', [
1517+
'config' => $config->toString(true),
1518+
]);
1519+
}
1520+
14671521
if ($config->state instanceof RuleStopState) {
14681522
// We hit rule end. If we have context info, use it run thru all possible stack tops in ctx
14691523
$context = $config->context;
@@ -2150,6 +2204,12 @@ protected function addDFAState(DFA $dfa, DFAState $D): DFAState
21502204
$existing = $dfa->states->get($D);
21512205

21522206
if ($existing instanceof DFAState) {
2207+
if (self::$traceAtnSimulation) {
2208+
$this->logger->debug('addDFAState {state} exists', [
2209+
'state' => $D->__toString(),
2210+
]);
2211+
}
2212+
21532213
return $existing;
21542214
}
21552215

@@ -2160,6 +2220,12 @@ protected function addDFAState(DFA $dfa, DFAState $D): DFAState
21602220
$D->configs->setReadonly(true);
21612221
}
21622222

2223+
if (self::$traceAtnSimulation) {
2224+
$this->logger->debug('addDFAState new {state}', [
2225+
'state' => $D->__toString(),
2226+
]);
2227+
}
2228+
21632229
$dfa->states->add($D);
21642230

21652231
return $D;

src/LoggerProvider.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Antlr\Antlr4\Runtime;
6+
7+
use Psr\Log\LoggerInterface as PsrLogger;
8+
9+
final class LoggerProvider
10+
{
11+
private static ?PsrLogger $logger = null;
12+
13+
public static function setLogger(PsrLogger $logger): void
14+
{
15+
self::$logger = $logger;
16+
}
17+
18+
public static function getLogger(): PsrLogger
19+
{
20+
if (self::$logger === null) {
21+
self::$logger = new StdoutMessageLogger();
22+
}
23+
24+
return self::$logger;
25+
}
26+
}

0 commit comments

Comments
 (0)