1
0
mirror of https://github.com/danog/PHP-Parser.git synced 2024-11-30 04:19:30 +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\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;
}
}

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);
}
}