diff --git a/composer.json b/composer.json
index 66160e1..5160d18 100644
--- a/composer.json
+++ b/composer.json
@@ -12,7 +12,8 @@
],
"require": {
"php": "^8.0",
- "ext-mbstring": "*"
+ "ext-mbstring": "^8.0",
+ "psr/log": "^2.0 || ^3.0"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.15",
diff --git a/composer.lock b/composer.lock
index 69e923e..ec737fb 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,59 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "c9bd0e80e7e22f425eb286b5c42b5f54",
- "packages": [],
+ "content-hash": "6d73951acc3119aabd404dbbe0967053",
+ "packages": [
+ {
+ "name": "psr/log",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.0"
+ },
+ "time": "2021-07-14T16:46:02+00:00"
+ }
+ ],
"packages-dev": [
{
"name": "dealerdirect/phpcodesniffer-composer-installer",
@@ -526,16 +577,16 @@
},
{
"name": "phpstan/phpdoc-parser",
- "version": "1.13.0",
+ "version": "1.13.1",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "33aefcdab42900e36366d0feab6206e2dd68f947"
+ "reference": "aac44118344d197e6d5f7c6cee91885f0a89acdd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/33aefcdab42900e36366d0feab6206e2dd68f947",
- "reference": "33aefcdab42900e36366d0feab6206e2dd68f947",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/aac44118344d197e6d5f7c6cee91885f0a89acdd",
+ "reference": "aac44118344d197e6d5f7c6cee91885f0a89acdd",
"shasum": ""
},
"require": {
@@ -565,22 +616,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.13.0"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.13.1"
},
- "time": "2022-10-21T09:57:39+00:00"
+ "time": "2022-11-20T08:52:26+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.8.11",
+ "version": "1.9.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "46e223dd68a620da18855c23046ddb00940b4014"
+ "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014",
- "reference": "46e223dd68a620da18855c23046ddb00940b4014",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa",
+ "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa",
"shasum": ""
},
"require": {
@@ -610,7 +661,7 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
- "source": "https://github.com/phpstan/phpstan/tree/1.8.11"
+ "source": "https://github.com/phpstan/phpstan/tree/1.9.2"
},
"funding": [
{
@@ -626,7 +677,7 @@
"type": "tidelift"
}
],
- "time": "2022-10-24T15:45:13+00:00"
+ "time": "2022-11-10T09:56:11+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -851,7 +902,7 @@
"prefer-lowest": false,
"platform": {
"php": "^8.0",
- "ext-mbstring": "*"
+ "ext-mbstring": "^8.0"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index bf40ce2..fe0b85e 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -636,6 +636,10 @@
5
+
+ src/StdoutMessageLogger.php
+
+
src/Atn/Transitions/AbstractPredicateTransition.php
diff --git a/src/Atn/ATNConfig.php b/src/Atn/ATNConfig.php
index 425b9b0..7e9c9b9 100644
--- a/src/Atn/ATNConfig.php
+++ b/src/Atn/ATNConfig.php
@@ -157,7 +157,7 @@ public function toString(bool $showAlt): string
$buf .= ',[' . $this->context . ']';
}
- if ($this->semanticContext->equals(SemanticContext::none())) {
+ if (!$this->semanticContext->equals(SemanticContext::none())) {
$buf .= ',' . $this->semanticContext;
}
@@ -177,9 +177,9 @@ public function __toString(): string
$this->state,
$this->alt,
$this->context !== null ? ',[' . $this->context . ']' : '',
- $this->semanticContext->equals(SemanticContext::none()) ?
- ',' . $this->semanticContext :
- '',
+ $this->semanticContext->equals(SemanticContext::none())
+ ? ''
+ : ',' . $this->semanticContext,
$this->reachesIntoOuterContext > 0 ? ',up=' . $this->reachesIntoOuterContext : '',
);
}
diff --git a/src/Atn/ParserATNSimulator.php b/src/Atn/ParserATNSimulator.php
index 36c31a4..57fb55a 100644
--- a/src/Atn/ParserATNSimulator.php
+++ b/src/Atn/ParserATNSimulator.php
@@ -24,6 +24,7 @@
use Antlr\Antlr4\Runtime\Error\Exceptions\RecognitionException;
use Antlr\Antlr4\Runtime\IntervalSet;
use Antlr\Antlr4\Runtime\IntStream;
+use Antlr\Antlr4\Runtime\LoggerProvider;
use Antlr\Antlr4\Runtime\Parser;
use Antlr\Antlr4\Runtime\ParserRuleContext;
use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext;
@@ -35,6 +36,7 @@
use Antlr\Antlr4\Runtime\Utils\BitSet;
use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap;
use Antlr\Antlr4\Runtime\Utils\Set;
+use Psr\Log\LoggerInterface as Logger;
/**
* The embodiment of the adaptive LL(*), ALL(*), parsing strategy.
@@ -231,6 +233,8 @@
*/
final class ParserATNSimulator extends ATNSimulator
{
+ public static bool $traceAtnSimulation = false;
+
protected Parser $parser;
/** @var array */
@@ -260,6 +264,8 @@ final class ParserATNSimulator extends ATNSimulator
protected ?DFA $dfa = null;
+ private Logger $logger;
+
/**
* @param array $decisionToDFA
*/
@@ -273,6 +279,7 @@ public function __construct(
$this->parser = $parser;
$this->decisionToDFA = $decisionToDFA;
+ $this->logger = LoggerProvider::getLogger();
}
public function reset(): void
@@ -296,6 +303,18 @@ public function clearDFA(): void
*/
public function adaptivePredict(TokenStream $input, int $decision, ParserRuleContext $outerContext): int
{
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug(
+ 'adaptivePredict decision {decision} exec LA(1)=={token} line {line}:{pos}',
+ [
+ 'decision' => $decision,
+ 'token' => $this->getTokenName($input->LA(1)),
+ 'line' => $input->LT(1)?->getLine(),
+ 'pos' => $input->LT(1)?->getCharPositionInLine(),
+ ],
+ );
+ }
+
$this->input = $input;
$this->startIndex = $input->getIndex();
$this->outerContext = $outerContext;
@@ -404,6 +423,19 @@ public function execATN(
int $startIndex,
ParserRuleContext $outerContext,
): ?int {
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug(
+ 'execATN decision {decision}, DFA state {state}, LA(1)=={token} line {line}:{pos}',
+ [
+ 'decision' => $dfa->decision,
+ 'state' => $s0->__toString(),
+ 'token' => $this->getTokenName($input->LA(1)),
+ 'line' => $input->LT(1)?->getLine(),
+ 'pos' => $input->LT(1)?->getCharPositionInLine(),
+ ],
+ );
+ }
+
$previousD = $s0;
$t = $input->LA(1);
@@ -655,6 +687,12 @@ protected function execATNWithFullContext(
int $startIndex,
ParserRuleContext $outerContext,
): int {
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug('execATNWithFullContext {state}', [
+ 'state' => $s0->__toString(),
+ ]);
+ }
+
$fullCtx = true;
$foundExactAmbig = false;
$reach = null;
@@ -890,15 +928,18 @@ protected function computeReachSet(ATNConfigSet $closure, int $t, bool $fullCtx)
* multiple alternatives are viable.*/
if ($skippedStopStates !== null && (!$fullCtx || !PredictionMode::hasConfigInRuleStopState($reach))) {
- if (\count($skippedStopStates) === 0) {
- throw new \LogicException('Skipped stop states cannot be empty.');
- }
-
foreach ($skippedStopStates as $lValue) {
$reach->add($lValue, $this->mergeCache);
}
}
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug('computeReachSet {closure} -> {reach}', [
+ 'closure' => $closure->__toString(),
+ 'reach' => $reach->__toString(),
+ ]);
+ }
+
if ($reach->isEmpty()) {
return null;
}
@@ -964,6 +1005,13 @@ protected function computeStartState(ATNState $p, RuleContext $ctx, bool $fullCt
$initialContext = PredictionContext::fromRuleContext($this->atn, $ctx);
$configs = new ATNConfigSet($fullCtx);
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug('computeStartState from ATN state {state} initialContext={initialContext}', [
+ 'state' => $p->__toString(),
+ 'initialContext' => $initialContext->__toString(),
+ ]);
+ }
+
foreach ($p->getTransitions() as $i => $t) {
$c = new ATNConfig(null, $t->target, $initialContext, null, $i + 1);
$closureBusy = new Set();
@@ -1464,6 +1512,12 @@ protected function closureCheckingStopState(
int $depth,
bool $treatEofAsEpsilon,
): void {
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug('closure({config})', [
+ 'config' => $config->toString(true),
+ ]);
+ }
+
if ($config->state instanceof RuleStopState) {
// We hit rule end. If we have context info, use it run thru all possible stack tops in ctx
$context = $config->context;
@@ -2150,6 +2204,12 @@ protected function addDFAState(DFA $dfa, DFAState $D): DFAState
$existing = $dfa->states->get($D);
if ($existing instanceof DFAState) {
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug('addDFAState {state} exists', [
+ 'state' => $D->__toString(),
+ ]);
+ }
+
return $existing;
}
@@ -2160,6 +2220,12 @@ protected function addDFAState(DFA $dfa, DFAState $D): DFAState
$D->configs->setReadonly(true);
}
+ if (self::$traceAtnSimulation) {
+ $this->logger->debug('addDFAState new {state}', [
+ 'state' => $D->__toString(),
+ ]);
+ }
+
$dfa->states->add($D);
return $D;
diff --git a/src/LoggerProvider.php b/src/LoggerProvider.php
new file mode 100644
index 0000000..5bb5cd1
--- /dev/null
+++ b/src/LoggerProvider.php
@@ -0,0 +1,26 @@
+getByTwoKeys($a, $b);
if ($previous !== null) {
+ if (ParserATNSimulator::$traceAtnSimulation) {
+ LoggerProvider::getLogger()
+ ->debug('mergeArrays a={a},b={b} -> previous', [
+ 'a' => $a->__toString(),
+ 'b' => $b->__toString(),
+ ]);
+ }
+
return $previous;
}
$previous = $mergeCache->getByTwoKeys($b, $a);
if ($previous !== null) {
+ if (ParserATNSimulator::$traceAtnSimulation) {
+ LoggerProvider::getLogger()
+ ->debug('mergeArrays a={a},b={b} -> previous', [
+ 'a' => $a->__toString(),
+ 'b' => $b->__toString(),
+ ]);
+ }
+
return $previous;
}
}
@@ -500,6 +518,14 @@ public static function mergeArrays(
$mergeCache->set($a, $b, $a);
}
+ if (ParserATNSimulator::$traceAtnSimulation) {
+ LoggerProvider::getLogger()
+ ->debug('mergeArrays a={a},b={b} -> a', [
+ 'a' => $a->__toString(),
+ 'b' => $b->__toString(),
+ ]);
+ }
+
return $a;
}
@@ -508,6 +534,14 @@ public static function mergeArrays(
$mergeCache->set($a, $b, $b);
}
+ if (ParserATNSimulator::$traceAtnSimulation) {
+ LoggerProvider::getLogger()
+ ->debug('mergeArrays a={a},b={b} -> b', [
+ 'a' => $a->__toString(),
+ 'b' => $b->__toString(),
+ ]);
+ }
+
return $b;
}
@@ -515,6 +549,15 @@ public static function mergeArrays(
$mergeCache->set($a, $b, $M);
}
+ if (ParserATNSimulator::$traceAtnSimulation) {
+ LoggerProvider::getLogger()
+ ->debug('mergeArrays a={a},b={b} -> M', [
+ 'a' => $a->__toString(),
+ 'b' => $b->__toString(),
+ 'M' => $M->__toString(),
+ ]);
+ }
+
return $M;
}
diff --git a/src/StdoutMessageLogger.php b/src/StdoutMessageLogger.php
new file mode 100644
index 0000000..58d2af4
--- /dev/null
+++ b/src/StdoutMessageLogger.php
@@ -0,0 +1,33 @@
+ $context
+ */
+ public function log($level, \Stringable|string $message, array $context = []): void
+ {
+ \fwrite(\STDOUT, self::formatMessage($message, $context) . \PHP_EOL);
+ }
+
+ /**
+ * @param array $context
+ */
+ private static function formatMessage(\Stringable|string $message, array $context): string
+ {
+ $replace = [];
+ foreach ($context as $key => $val) {
+ $replace['{' . $key . '}'] = $val;
+ }
+
+ return \strtr((string) $message, $replace);
+ }
+}
diff --git a/src/Utils/Pair.php b/src/Utils/Pair.php
index fc80417..3a3c5a3 100644
--- a/src/Utils/Pair.php
+++ b/src/Utils/Pair.php
@@ -38,6 +38,14 @@ public function hashCode(): int
public function __toString(): string
{
- return \sprintf('%s, %s', (string) $this->a, (string) $this->b);
+ return \sprintf(
+ '%s, %s',
+ $this->a === null
+ ? 'null'
+ : ($this->a instanceof \Stringable ? (string) $this->a : $this->a::class),
+ $this->b === null
+ ? 'null'
+ : ($this->b instanceof \Stringable ? (string) $this->b : $this->b::class),
+ );
}
}