From 39b046007d0aa16844e9ab1d8c30c39520e1bcf2 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sun, 6 Sep 2020 15:50:52 +0200 Subject: [PATCH] Refactor token emulator registration Only determine needed emulators based on PHP version once, and add an adaptor that allows treating forward and reverse emulation the same. Previously the isEmulationNeeded() check was too conservative, as it also considered emulators that are not relevant for the version. Though possibly that check should just be dropped altogether. --- lib/PhpParser/Lexer/Emulative.php | 56 ++++++++++++------- .../Lexer/TokenEmulator/ReverseEmulator.php | 32 +++++++++++ 2 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php diff --git a/lib/PhpParser/Lexer/Emulative.php b/lib/PhpParser/Lexer/Emulative.php index 4b92a67..bf4837a 100644 --- a/lib/PhpParser/Lexer/Emulative.php +++ b/lib/PhpParser/Lexer/Emulative.php @@ -10,6 +10,7 @@ use PhpParser\Lexer\TokenEmulator\FnTokenEmulator; use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator; use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator; use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator; +use PhpParser\Lexer\TokenEmulator\ReverseEmulator; use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface; use PhpParser\Parser\Tokens; @@ -29,7 +30,7 @@ REGEX; private $patches = []; /** @var TokenEmulatorInterface[] */ - private $tokenEmulators = []; + private $emulators = []; /** @var string */ private $targetPhpVersion; @@ -46,11 +47,24 @@ REGEX; parent::__construct($options); - $this->tokenEmulators[] = new FnTokenEmulator(); - $this->tokenEmulators[] = new MatchTokenEmulator(); - $this->tokenEmulators[] = new CoaleseEqualTokenEmulator(); - $this->tokenEmulators[] = new NumericLiteralSeparatorEmulator(); - $this->tokenEmulators[] = new NullsafeTokenEmulator(); + $emulators = [ + new FnTokenEmulator(), + new MatchTokenEmulator(), + new CoaleseEqualTokenEmulator(), + new NumericLiteralSeparatorEmulator(), + new NullsafeTokenEmulator(), + ]; + + // Collect emulators that are relevant for the PHP version we're running + // and the PHP version we're targeting for emulation. + foreach ($emulators as $emulator) { + $emulatorPhpVersion = $emulator->getPhpVersion(); + if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = $emulator; + } else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) { + $this->emulators[] = new ReverseEmulator($emulator); + } + } } public function startLexing(string $code, ErrorHandler $errorHandler = null) { @@ -77,24 +91,26 @@ REGEX; } } - foreach ($this->tokenEmulators as $tokenEmulator) { - $emulatorPhpVersion = $tokenEmulator->getPhpVersion(); - if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '<') - && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=') - && $tokenEmulator->isEmulationNeeded($code)) { - $this->tokens = $tokenEmulator->emulate($code, $this->tokens); - } else if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=') - && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<') - && $tokenEmulator->isEmulationNeeded($code)) { - $this->tokens = $tokenEmulator->reverseEmulate($code, $this->tokens); + foreach ($this->emulators as $emulator) { + if ($emulator->isEmulationNeeded($code)) { + $this->tokens = $emulator->emulate($code, $this->tokens); } } } + private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool { + return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<') + && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>='); + } + + private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool { + return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=') + && version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<'); + } + private function isHeredocNowdocEmulationNeeded(string $code): bool { - // skip version where this works without emulation - if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) { + if (!$this->isForwardEmulationNeeded(self::PHP_7_3)) { return false; } @@ -148,8 +164,8 @@ REGEX; private function isEmulationNeeded(string $code): bool { - foreach ($this->tokenEmulators as $emulativeToken) { - if ($emulativeToken->isEmulationNeeded($code)) { + foreach ($this->emulators as $emulator) { + if ($emulator->isEmulationNeeded($code)) { return true; } } diff --git a/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php b/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php new file mode 100644 index 0000000..d324f63 --- /dev/null +++ b/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php @@ -0,0 +1,32 @@ +emulator = $emulator; + } + + public function getPhpVersion(): string { + return $this->emulator->getPhpVersion(); + } + + public function isEmulationNeeded(string $code): bool { + return $this->emulator->isEmulationNeeded($code); + } + + public function emulate(string $code, array $tokens): array { + return $this->emulator->reverseEmulate($code, $tokens); + } + + public function reverseEmulate(string $code, array $tokens): array { + return $this->emulator->emulate($code, $tokens); + } +} \ No newline at end of file