Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. Emulative.php

class Emulative

Hierarchy

  • class \PhpParser\Lexer
    • class \PhpParser\Lexer\Emulative extends \PhpParser\Lexer

Expanded class hierarchy of Emulative

File

vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php, line 22

Namespace

PhpParser\Lexer
View source
class Emulative extends Lexer {
    
    /** @var array{int, string, string}[] Patches used to reverse changes introduced in the code */
    private array $patches = [];
    
    /** @var list<TokenEmulator> */
    private array $emulators = [];
    private PhpVersion $targetPhpVersion;
    private PhpVersion $hostPhpVersion;
    
    /**
     * @param PhpVersion|null $phpVersion PHP version to emulate. Defaults to newest supported.
     */
    public function __construct(?PhpVersion $phpVersion = null) {
        $this->targetPhpVersion = $phpVersion ?? PhpVersion::getNewestSupported();
        $this->hostPhpVersion = PhpVersion::getHostVersion();
        $emulators = [
            new MatchTokenEmulator(),
            new NullsafeTokenEmulator(),
            new AttributeEmulator(),
            new EnumTokenEmulator(),
            new ReadonlyTokenEmulator(),
            new ExplicitOctalEmulator(),
            new ReadonlyFunctionTokenEmulator(),
            new PropertyTokenEmulator(),
            new AsymmetricVisibilityTokenEmulator(),
        ];
        // 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;
            }
            elseif ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
                $this->emulators[] = new ReverseEmulator($emulator);
            }
        }
    }
    public function tokenize(string $code, ?ErrorHandler $errorHandler = null) : array {
        $emulators = array_filter($this->emulators, function ($emulator) use ($code) {
            return $emulator->isEmulationNeeded($code);
        });
        if (empty($emulators)) {
            // Nothing to emulate, yay
            return parent::tokenize($code, $errorHandler);
        }
        if ($errorHandler === null) {
            $errorHandler = new ErrorHandler\Throwing();
        }
        $this->patches = [];
        foreach ($emulators as $emulator) {
            $code = $emulator->preprocessCode($code, $this->patches);
        }
        $collector = new ErrorHandler\Collecting();
        $tokens = parent::tokenize($code, $collector);
        $this->sortPatches();
        $tokens = $this->fixupTokens($tokens);
        $errors = $collector->getErrors();
        if (!empty($errors)) {
            $this->fixupErrors($errors);
            foreach ($errors as $error) {
                $errorHandler->handleError($error);
            }
        }
        foreach ($emulators as $emulator) {
            $tokens = $emulator->emulate($code, $tokens);
        }
        return $tokens;
    }
    private function isForwardEmulationNeeded(PhpVersion $emulatorPhpVersion) : bool {
        return $this->hostPhpVersion
            ->older($emulatorPhpVersion) && $this->targetPhpVersion
            ->newerOrEqual($emulatorPhpVersion);
    }
    private function isReverseEmulationNeeded(PhpVersion $emulatorPhpVersion) : bool {
        return $this->hostPhpVersion
            ->newerOrEqual($emulatorPhpVersion) && $this->targetPhpVersion
            ->older($emulatorPhpVersion);
    }
    private function sortPatches() : void {
        // Patches may be contributed by different emulators.
        // Make sure they are sorted by increasing patch position.
        usort($this->patches, function ($p1, $p2) {
            return $p1[0] <=> $p2[0];
        });
    }
    
    /**
     * @param list<Token> $tokens
     * @return list<Token>
     */
    private function fixupTokens(array $tokens) : array {
        if (\count($this->patches) === 0) {
            return $tokens;
        }
        // Load first patch
        $patchIdx = 0;
        list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
        // We use a manual loop over the tokens, because we modify the array on the fly
        $posDelta = 0;
        $lineDelta = 0;
        for ($i = 0, $c = \count($tokens); $i < $c; $i++) {
            $token = $tokens[$i];
            $pos = $token->pos;
            $token->pos += $posDelta;
            $token->line += $lineDelta;
            $localPosDelta = 0;
            $len = \strlen($token->text);
            while ($patchPos >= $pos && $patchPos < $pos + $len) {
                $patchTextLen = \strlen($patchText);
                if ($patchType === 'remove') {
                    if ($patchPos === $pos && $patchTextLen === $len) {
                        // Remove token entirely
                        array_splice($tokens, $i, 1, []);
                        $i--;
                        $c--;
                    }
                    else {
                        // Remove from token string
                        $token->text = substr_replace($token->text, '', $patchPos - $pos + $localPosDelta, $patchTextLen);
                        $localPosDelta -= $patchTextLen;
                    }
                    $lineDelta -= \substr_count($patchText, "\n");
                }
                elseif ($patchType === 'add') {
                    // Insert into the token string
                    $token->text = substr_replace($token->text, $patchText, $patchPos - $pos + $localPosDelta, 0);
                    $localPosDelta += $patchTextLen;
                    $lineDelta += \substr_count($patchText, "\n");
                }
                elseif ($patchType === 'replace') {
                    // Replace inside the token string
                    $token->text = substr_replace($token->text, $patchText, $patchPos - $pos + $localPosDelta, $patchTextLen);
                }
                else {
                    assert(false);
                }
                // Fetch the next patch
                $patchIdx++;
                if ($patchIdx >= \count($this->patches)) {
                    // No more patches. However, we still need to adjust position.
                    $patchPos = \PHP_INT_MAX;
                    break;
                }
                list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
            }
            $posDelta += $localPosDelta;
        }
        return $tokens;
    }
    
    /**
     * Fixup line and position information in errors.
     *
     * @param Error[] $errors
     */
    private function fixupErrors(array $errors) : void {
        foreach ($errors as $error) {
            $attrs = $error->getAttributes();
            $posDelta = 0;
            $lineDelta = 0;
            foreach ($this->patches as $patch) {
                list($patchPos, $patchType, $patchText) = $patch;
                if ($patchPos >= $attrs['startFilePos']) {
                    // No longer relevant
                    break;
                }
                if ($patchType === 'add') {
                    $posDelta += strlen($patchText);
                    $lineDelta += substr_count($patchText, "\n");
                }
                elseif ($patchType === 'remove') {
                    $posDelta -= strlen($patchText);
                    $lineDelta -= substr_count($patchText, "\n");
                }
            }
            $attrs['startFilePos'] += $posDelta;
            $attrs['endFilePos'] += $posDelta;
            $attrs['startLine'] += $lineDelta;
            $attrs['endLine'] += $lineDelta;
            $error->setAttributes($attrs);
        }
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
Emulative::$emulators private property @var list&lt;TokenEmulator&gt;
Emulative::$hostPhpVersion private property
Emulative::$patches private property @var array{int, string, string}[] Patches used to reverse changes introduced in the code
Emulative::$targetPhpVersion private property
Emulative::fixupErrors private function Fixup line and position information in errors.
Emulative::fixupTokens private function
Emulative::isForwardEmulationNeeded private function
Emulative::isReverseEmulationNeeded private function
Emulative::sortPatches private function
Emulative::tokenize public function Tokenize the provided source code. Overrides Lexer::tokenize
Emulative::__construct public function
Lexer::handleInvalidCharacter private function
Lexer::isUnterminatedComment private function
Lexer::postprocessTokens protected function

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal