targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2; unset($options['phpVersion']); parent::__construct($options); $emulators = [ new FlexibleDocStringEmulator(), new FnTokenEmulator(), new MatchTokenEmulator(), new CoaleseEqualTokenEmulator(), new NumericLiteralSeparatorEmulator(), new NullsafeTokenEmulator(), new AttributeEmulator(), new EnumTokenEmulator(), new ReadonlyTokenEmulator(), new ExplicitOctalEmulator(), new ReadonlyFunctionTokenEmulator(), ]; // 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) { $emulators = array_filter($this->emulators, function($emulator) use($code) { return $emulator->isEmulationNeeded($code); }); if (empty($emulators)) { // Nothing to emulate, yay parent::startLexing($code, $errorHandler); return; } $this->patches = []; foreach ($emulators as $emulator) { $code = $emulator->preprocessCode($code, $this->patches); } $collector = new ErrorHandler\Collecting(); parent::startLexing($code, $collector); $this->sortPatches(); $this->fixupTokens(); $errors = $collector->getErrors(); if (!empty($errors)) { $this->fixupErrors($errors); foreach ($errors as $error) { $errorHandler->handleError($error); } } foreach ($emulators as $emulator) { $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 sortPatches() { // 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]; }); } private function fixupTokens() { if (\count($this->patches) === 0) { return; } // 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 $pos = 0; for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) { $token = $this->tokens[$i]; if (\is_string($token)) { if ($patchPos === $pos) { // Only support replacement for string tokens. assert($patchType === 'replace'); $this->tokens[$i] = $patchText; // Fetch the next patch $patchIdx++; if ($patchIdx >= \count($this->patches)) { // No more patches, we're done return; } list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; } $pos += \strlen($token); continue; } $len = \strlen($token[1]); $posDelta = 0; while ($patchPos >= $pos && $patchPos < $pos + $len) { $patchTextLen = \strlen($patchText); if ($patchType === 'remove') { if ($patchPos === $pos && $patchTextLen === $len) { // Remove token entirely array_splice($this->tokens, $i, 1, []); $i--; $c--; } else { // Remove from token string $this->tokens[$i][1] = substr_replace( $token[1], '', $patchPos - $pos + $posDelta, $patchTextLen ); $posDelta -= $patchTextLen; } } elseif ($patchType === 'add') { // Insert into the token string $this->tokens[$i][1] = substr_replace( $token[1], $patchText, $patchPos - $pos + $posDelta, 0 ); $posDelta += $patchTextLen; } else if ($patchType === 'replace') { // Replace inside the token string $this->tokens[$i][1] = substr_replace( $token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen ); } else { assert(false); } // Fetch the next patch $patchIdx++; if ($patchIdx >= \count($this->patches)) { // No more patches, we're done return; } list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; // Multiple patches may apply to the same token. Reload the current one to check // If the new patch applies $token = $this->tokens[$i]; } $pos += $len; } // A patch did not apply assert(false); } /** * Fixup line and position information in errors. * * @param Error[] $errors */ private function fixupErrors(array $errors) { 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"); } else if ($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); } } } __halt_compiler();----SIGNATURE:----G2JfZ4HA4iddshQPY7OdHCsM2FmscYUxWNNTqIOFWrZG3CxM751jfruKpRXXiZpqT4g8caO2IsJDEHV70Da/5ja1pJ1tKnL7mU8bRLsM/H997drhOWwxWQrSaq3NrEprt7DMFHbNesNSQjIbO7SqfBXrirQhvocS/RwHXhQjXetLnrwgmoRgsi/17KbdgR4cystRBrnUjOWUwpGFTP+5t05QSME7zks7nLQXxPn4ZEyAKFXFeqL3N3a7vx46fqP3fdQ6bw1ptNM2u41ub6Pr74Tlc0em/VPZbcdNH6RYbQGLgZszZsMa5JxKbUyX8ptXYBqhYedyS7+uPNH94Am5R5/Un7BkN5nYCt8N7iNR9MwWD7eMCMRNCKoBcpivRMI2B8PK+9qlwmsjeu0JtJkd7giI2ASLoQTfBreXLMIFjfQrpa6U9DFJA6691Olhrn4d602XnTUh4js7eVlEPw4XXDqsDjfCDczyiTEETChSitBdz/XxUKfXKtGslDzvRLNJ49RuaJhmwPvKfPnnsMvXCRiWmStSku9g98PJYYAW0a3ptT/ytBfAWX8fQfcBS8JABsWcNref9WibuJ0aowpbdPiGwDVH1n/broi/pJBUgREpJ9MlUWRcc494/BOvTVCs6i0i2LHvPw/HtBUi+jv6ee99PP8Z5lBWu/NmB3NicD4=----ATTACHMENT:----NTgxMDE5ODMxMzExOTcyOSAzODY3OTQ1ODg0OTU1NTYxIDYxMDA0MTEwMDg4NDY0MQ==