1
0
mirror of https://github.com/danog/PHP-Parser.git synced 2024-12-02 09:17:58 +01:00

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.
This commit is contained in:
Nikita Popov 2020-09-06 15:50:52 +02:00
parent e3872b8906
commit 39b046007d
2 changed files with 68 additions and 20 deletions

View File

@ -10,6 +10,7 @@ use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator; use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator; use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator; use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface; use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
use PhpParser\Parser\Tokens; use PhpParser\Parser\Tokens;
@ -29,7 +30,7 @@ REGEX;
private $patches = []; private $patches = [];
/** @var TokenEmulatorInterface[] */ /** @var TokenEmulatorInterface[] */
private $tokenEmulators = []; private $emulators = [];
/** @var string */ /** @var string */
private $targetPhpVersion; private $targetPhpVersion;
@ -46,11 +47,24 @@ REGEX;
parent::__construct($options); parent::__construct($options);
$this->tokenEmulators[] = new FnTokenEmulator(); $emulators = [
$this->tokenEmulators[] = new MatchTokenEmulator(); new FnTokenEmulator(),
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator(); new MatchTokenEmulator(),
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator(); new CoaleseEqualTokenEmulator(),
$this->tokenEmulators[] = new NullsafeTokenEmulator(); 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) { public function startLexing(string $code, ErrorHandler $errorHandler = null) {
@ -77,24 +91,26 @@ REGEX;
} }
} }
foreach ($this->tokenEmulators as $tokenEmulator) { foreach ($this->emulators as $emulator) {
$emulatorPhpVersion = $tokenEmulator->getPhpVersion(); if ($emulator->isEmulationNeeded($code)) {
if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '<') $this->tokens = $emulator->emulate($code, $this->tokens);
&& 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);
} }
} }
} }
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 private function isHeredocNowdocEmulationNeeded(string $code): bool
{ {
// skip version where this works without emulation if (!$this->isForwardEmulationNeeded(self::PHP_7_3)) {
if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
return false; return false;
} }
@ -148,8 +164,8 @@ REGEX;
private function isEmulationNeeded(string $code): bool private function isEmulationNeeded(string $code): bool
{ {
foreach ($this->tokenEmulators as $emulativeToken) { foreach ($this->emulators as $emulator) {
if ($emulativeToken->isEmulationNeeded($code)) { if ($emulator->isEmulationNeeded($code)) {
return true; return true;
} }
} }

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/**
* Reverses emulation direction of the inner emulator.
*/
final class ReverseEmulator implements TokenEmulatorInterface
{
/** @var TokenEmulatorInterface Inner emulator */
private $emulator;
public function __construct(TokenEmulatorInterface $emulator) {
$this->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);
}
}