�ɲɾ�����ӯ�����һ��ˣ��������С���˴��ͣ�������P���ҹ��ñ˽��ά�Բ��������˸߸ԣ�������ơ��ҹ��ñ�����ά�Բ���ˡ���˳^�ӣ������ӡ� ���ͯj�ӣ��ƺ���ӣ� ? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!PKg 3]O1%1%TokenStream.phpnu[tokens = $tokens; $this->indentMap = $this->calcIndentMap($tabWidth); } /** * Whether the given position is immediately surrounded by parenthesis. * * @param int $startPos Start position * @param int $endPos End position */ public function haveParens(int $startPos, int $endPos): bool { return $this->haveTokenImmediatelyBefore($startPos, '(') && $this->haveTokenImmediatelyAfter($endPos, ')'); } /** * Whether the given position is immediately surrounded by braces. * * @param int $startPos Start position * @param int $endPos End position */ public function haveBraces(int $startPos, int $endPos): bool { return ($this->haveTokenImmediatelyBefore($startPos, '{') || $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN)) && $this->haveTokenImmediatelyAfter($endPos, '}'); } /** * Check whether the position is directly preceded by a certain token type. * * During this check whitespace and comments are skipped. * * @param int $pos Position before which the token should occur * @param int|string $expectedTokenType Token to check for * * @return bool Whether the expected token was found */ public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType): bool { $tokens = $this->tokens; $pos--; for (; $pos >= 0; $pos--) { $token = $tokens[$pos]; if ($token->is($expectedTokenType)) { return true; } if (!$token->isIgnorable()) { break; } } return false; } /** * Check whether the position is directly followed by a certain token type. * * During this check whitespace and comments are skipped. * * @param int $pos Position after which the token should occur * @param int|string $expectedTokenType Token to check for * * @return bool Whether the expected token was found */ public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType): bool { $tokens = $this->tokens; $pos++; for ($c = \count($tokens); $pos < $c; $pos++) { $token = $tokens[$pos]; if ($token->is($expectedTokenType)) { return true; } if (!$token->isIgnorable()) { break; } } return false; } /** @param int|string|(int|string)[] $skipTokenType */ public function skipLeft(int $pos, $skipTokenType): int { $tokens = $this->tokens; $pos = $this->skipLeftWhitespace($pos); if ($skipTokenType === \T_WHITESPACE) { return $pos; } if (!$tokens[$pos]->is($skipTokenType)) { // Shouldn't happen. The skip token MUST be there throw new \Exception('Encountered unexpected token'); } $pos--; return $this->skipLeftWhitespace($pos); } /** @param int|string|(int|string)[] $skipTokenType */ public function skipRight(int $pos, $skipTokenType): int { $tokens = $this->tokens; $pos = $this->skipRightWhitespace($pos); if ($skipTokenType === \T_WHITESPACE) { return $pos; } if (!$tokens[$pos]->is($skipTokenType)) { // Shouldn't happen. The skip token MUST be there throw new \Exception('Encountered unexpected token'); } $pos++; return $this->skipRightWhitespace($pos); } /** * Return first non-whitespace token position smaller or equal to passed position. * * @param int $pos Token position * @return int Non-whitespace token position */ public function skipLeftWhitespace(int $pos): int { $tokens = $this->tokens; for (; $pos >= 0; $pos--) { if (!$tokens[$pos]->isIgnorable()) { break; } } return $pos; } /** * Return first non-whitespace position greater or equal to passed position. * * @param int $pos Token position * @return int Non-whitespace token position */ public function skipRightWhitespace(int $pos): int { $tokens = $this->tokens; for ($count = \count($tokens); $pos < $count; $pos++) { if (!$tokens[$pos]->isIgnorable()) { break; } } return $pos; } /** @param int|string|(int|string)[] $findTokenType */ public function findRight(int $pos, $findTokenType): int { $tokens = $this->tokens; for ($count = \count($tokens); $pos < $count; $pos++) { if ($tokens[$pos]->is($findTokenType)) { return $pos; } } return -1; } /** * Whether the given position range contains a certain token type. * * @param int $startPos Starting position (inclusive) * @param int $endPos Ending position (exclusive) * @param int|string $tokenType Token type to look for * @return bool Whether the token occurs in the given range */ public function haveTokenInRange(int $startPos, int $endPos, $tokenType): bool { $tokens = $this->tokens; for ($pos = $startPos; $pos < $endPos; $pos++) { if ($tokens[$pos]->is($tokenType)) { return true; } } return false; } public function haveTagInRange(int $startPos, int $endPos): bool { return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG) || $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG); } /** * Get indentation before token position. * * @param int $pos Token position * * @return int Indentation depth (in spaces) */ public function getIndentationBefore(int $pos): int { return $this->indentMap[$pos]; } /** * Get the code corresponding to a token offset range, optionally adjusted for indentation. * * @param int $from Token start position (inclusive) * @param int $to Token end position (exclusive) * @param int $indent By how much the code should be indented (can be negative as well) * * @return string Code corresponding to token range, adjusted for indentation */ public function getTokenCode(int $from, int $to, int $indent): string { $tokens = $this->tokens; $result = ''; for ($pos = $from; $pos < $to; $pos++) { $token = $tokens[$pos]; $id = $token->id; $text = $token->text; if ($id === \T_CONSTANT_ENCAPSED_STRING || $id === \T_ENCAPSED_AND_WHITESPACE) { $result .= $text; } else { // TODO Handle non-space indentation if ($indent < 0) { $result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $text); } elseif ($indent > 0) { $result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $text); } else { $result .= $text; } } } return $result; } /** * Precalculate the indentation at every token position. * * @return int[] Token position to indentation map */ private function calcIndentMap(int $tabWidth): array { $indentMap = []; $indent = 0; foreach ($this->tokens as $i => $token) { $indentMap[] = $indent; if ($token->id === \T_WHITESPACE) { $content = $token->text; $newlinePos = \strrpos($content, "\n"); if (false !== $newlinePos) { $indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth); } elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG && $this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") { // Special case: Newline at the end of opening tag followed by whitespace. $indent = $this->getIndent($content, $tabWidth); } } } // Add a sentinel for one past end of the file $indentMap[] = $indent; return $indentMap; } private function getIndent(string $ws, int $tabWidth): int { $spaces = \substr_count($ws, " "); $tabs = \substr_count($ws, "\t"); assert(\strlen($ws) === $spaces + $tabs); return $spaces + $tabs * $tabWidth; } } PKg 3]m PrintableNewAnonClassNode.phpnu[ $attributes Attributes */ public function __construct( array $attrGroups, int $flags, array $args, ?Node\Name $extends, array $implements, array $stmts, array $attributes ) { parent::__construct($attributes); $this->attrGroups = $attrGroups; $this->flags = $flags; $this->args = $args; $this->extends = $extends; $this->implements = $implements; $this->stmts = $stmts; } public static function fromNewNode(Expr\New_ $newNode): self { $class = $newNode->class; assert($class instanceof Node\Stmt\Class_); // We don't assert that $class->name is null here, to allow consumers to assign unique names // to anonymous classes for their own purposes. We simplify ignore the name here. return new self( $class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements, $class->stmts, $newNode->getAttributes() ); } public function getType(): string { return 'Expr_PrintableNewAnonClass'; } public function getSubNodeNames(): array { return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts']; } } PKg 3]  Differ.phpnu[isEqual = $isEqual; } /** * Calculate diff (edit script) from $old to $new. * * @param T[] $old Original array * @param T[] $new New array * * @return DiffElem[] Diff (edit script) */ public function diff(array $old, array $new): array { $old = \array_values($old); $new = \array_values($new); list($trace, $x, $y) = $this->calculateTrace($old, $new); return $this->extractDiff($trace, $x, $y, $old, $new); } /** * Calculate diff, including "replace" operations. * * If a sequence of remove operations is followed by the same number of add operations, these * will be coalesced into replace operations. * * @param T[] $old Original array * @param T[] $new New array * * @return DiffElem[] Diff (edit script), including replace operations */ public function diffWithReplacements(array $old, array $new): array { return $this->coalesceReplacements($this->diff($old, $new)); } /** * @param T[] $old * @param T[] $new * @return array{array>, int, int} */ private function calculateTrace(array $old, array $new): array { $n = \count($old); $m = \count($new); $max = $n + $m; $v = [1 => 0]; $trace = []; for ($d = 0; $d <= $max; $d++) { $trace[] = $v; for ($k = -$d; $k <= $d; $k += 2) { if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { $x = $v[$k + 1]; } else { $x = $v[$k - 1] + 1; } $y = $x - $k; while ($x < $n && $y < $m && ($this->isEqual)($old[$x], $new[$y])) { $x++; $y++; } $v[$k] = $x; if ($x >= $n && $y >= $m) { return [$trace, $x, $y]; } } } throw new \Exception('Should not happen'); } /** * @param array> $trace * @param T[] $old * @param T[] $new * @return DiffElem[] */ private function extractDiff(array $trace, int $x, int $y, array $old, array $new): array { $result = []; for ($d = \count($trace) - 1; $d >= 0; $d--) { $v = $trace[$d]; $k = $x - $y; if ($k === -$d || ($k !== $d && $v[$k - 1] < $v[$k + 1])) { $prevK = $k + 1; } else { $prevK = $k - 1; } $prevX = $v[$prevK]; $prevY = $prevX - $prevK; while ($x > $prevX && $y > $prevY) { $result[] = new DiffElem(DiffElem::TYPE_KEEP, $old[$x - 1], $new[$y - 1]); $x--; $y--; } if ($d === 0) { break; } while ($x > $prevX) { $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $old[$x - 1], null); $x--; } while ($y > $prevY) { $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $new[$y - 1]); $y--; } } return array_reverse($result); } /** * Coalesce equal-length sequences of remove+add into a replace operation. * * @param DiffElem[] $diff * @return DiffElem[] */ private function coalesceReplacements(array $diff): array { $newDiff = []; $c = \count($diff); for ($i = 0; $i < $c; $i++) { $diffType = $diff[$i]->type; if ($diffType !== DiffElem::TYPE_REMOVE) { $newDiff[] = $diff[$i]; continue; } $j = $i; while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) { $j++; } $k = $j; while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) { $k++; } if ($j - $i === $k - $j) { $len = $j - $i; for ($n = 0; $n < $len; $n++) { $newDiff[] = new DiffElem( DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new ); } } else { for (; $i < $k; $i++) { $newDiff[] = $diff[$i]; } } $i = $k - 1; } return $newDiff; } } PKg 3]m a&&TokenPolyfill.phpnu[= 80000) { class TokenPolyfill extends \PhpToken { } return; } /** * This is a polyfill for the PhpToken class introduced in PHP 8.0. We do not actually polyfill * PhpToken, because composer might end up picking a different polyfill implementation, which does * not meet our requirements. * * @internal */ class TokenPolyfill { /** @var int The ID of the token. Either a T_* constant of a character code < 256. */ public int $id; /** @var string The textual content of the token. */ public string $text; /** @var int The 1-based starting line of the token (or -1 if unknown). */ public int $line; /** @var int The 0-based starting position of the token (or -1 if unknown). */ public int $pos; /** @var array Tokens ignored by the PHP parser. */ private const IGNORABLE_TOKENS = [ \T_WHITESPACE => true, \T_COMMENT => true, \T_DOC_COMMENT => true, \T_OPEN_TAG => true, ]; /** @var array Tokens that may be part of a T_NAME_* identifier. */ private static array $identifierTokens; /** * Create a Token with the given ID and text, as well optional line and position information. */ final public function __construct(int $id, string $text, int $line = -1, int $pos = -1) { $this->id = $id; $this->text = $text; $this->line = $line; $this->pos = $pos; } /** * Get the name of the token. For single-char tokens this will be the token character. * Otherwise it will be a T_* style name, or null if the token ID is unknown. */ public function getTokenName(): ?string { if ($this->id < 256) { return \chr($this->id); } $name = token_name($this->id); return $name === 'UNKNOWN' ? null : $name; } /** * Check whether the token is of the given kind. The kind may be either an integer that matches * the token ID, a string that matches the token text, or an array of integers/strings. In the * latter case, the function returns true if any of the kinds in the array match. * * @param int|string|(int|string)[] $kind */ public function is($kind): bool { if (\is_int($kind)) { return $this->id === $kind; } if (\is_string($kind)) { return $this->text === $kind; } if (\is_array($kind)) { foreach ($kind as $entry) { if (\is_int($entry)) { if ($this->id === $entry) { return true; } } elseif (\is_string($entry)) { if ($this->text === $entry) { return true; } } else { throw new \TypeError( 'Argument #1 ($kind) must only have elements of type string|int, ' . gettype($entry) . ' given'); } } return false; } throw new \TypeError( 'Argument #1 ($kind) must be of type string|int|array, ' .gettype($kind) . ' given'); } /** * Check whether this token would be ignored by the PHP parser. Returns true for T_WHITESPACE, * T_COMMENT, T_DOC_COMMENT and T_OPEN_TAG, and false for everything else. */ public function isIgnorable(): bool { return isset(self::IGNORABLE_TOKENS[$this->id]); } /** * Return the textual content of the token. */ public function __toString(): string { return $this->text; } /** * Tokenize the given source code and return an array of tokens. * * This performs certain canonicalizations to match the PHP 8.0 token format: * * Bad characters are represented using T_BAD_CHARACTER rather than omitted. * * T_COMMENT does not include trailing newlines, instead the newline is part of a following * T_WHITESPACE token. * * Namespaced names are represented using T_NAME_* tokens. * * @return static[] */ public static function tokenize(string $code, int $flags = 0): array { self::init(); $tokens = []; $line = 1; $pos = 0; $origTokens = \token_get_all($code, $flags); $numTokens = \count($origTokens); for ($i = 0; $i < $numTokens; $i++) { $token = $origTokens[$i]; if (\is_string($token)) { if (\strlen($token) === 2) { // b" and B" are tokenized as single-char tokens, even though they aren't. $tokens[] = new static(\ord('"'), $token, $line, $pos); $pos += 2; } else { $tokens[] = new static(\ord($token), $token, $line, $pos); $pos++; } } else { $id = $token[0]; $text = $token[1]; // Emulate PHP 8.0 comment format, which does not include trailing whitespace anymore. if ($id === \T_COMMENT && \substr($text, 0, 2) !== '/*' && \preg_match('/(\r\n|\n|\r)$/D', $text, $matches) ) { $trailingNewline = $matches[0]; $text = \substr($text, 0, -\strlen($trailingNewline)); $tokens[] = new static($id, $text, $line, $pos); $pos += \strlen($text); if ($i + 1 < $numTokens && $origTokens[$i + 1][0] === \T_WHITESPACE) { // Move trailing newline into following T_WHITESPACE token, if it already exists. $origTokens[$i + 1][1] = $trailingNewline . $origTokens[$i + 1][1]; $origTokens[$i + 1][2]--; } else { // Otherwise, we need to create a new T_WHITESPACE token. $tokens[] = new static(\T_WHITESPACE, $trailingNewline, $line, $pos); $line++; $pos += \strlen($trailingNewline); } continue; } // Emulate PHP 8.0 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and // T_STRING into a single token. if (($id === \T_NS_SEPARATOR || isset(self::$identifierTokens[$id]))) { $newText = $text; $lastWasSeparator = $id === \T_NS_SEPARATOR; for ($j = $i + 1; $j < $numTokens; $j++) { if ($lastWasSeparator) { if (!isset(self::$identifierTokens[$origTokens[$j][0]])) { break; } $lastWasSeparator = false; } else { if ($origTokens[$j][0] !== \T_NS_SEPARATOR) { break; } $lastWasSeparator = true; } $newText .= $origTokens[$j][1]; } if ($lastWasSeparator) { // Trailing separator is not part of the name. $j--; $newText = \substr($newText, 0, -1); } if ($j > $i + 1) { if ($id === \T_NS_SEPARATOR) { $id = \T_NAME_FULLY_QUALIFIED; } elseif ($id === \T_NAMESPACE) { $id = \T_NAME_RELATIVE; } else { $id = \T_NAME_QUALIFIED; } $tokens[] = new static($id, $newText, $line, $pos); $pos += \strlen($newText); $i = $j - 1; continue; } } $tokens[] = new static($id, $text, $line, $pos); $line += \substr_count($text, "\n"); $pos += \strlen($text); } } return $tokens; } /** Initialize private static state needed by tokenize(). */ private static function init(): void { if (isset(self::$identifierTokens)) { return; } // Based on semi_reserved production. self::$identifierTokens = \array_fill_keys([ \T_STRING, \T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY, \T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND, \T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE, \T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH, \T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO, \T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT, \T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS, \T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN, \T_MATCH, ], true); } } PKg 3]a  DiffElem.phpnu[type = $type; $this->old = $old; $this->new = $new; } } PKg 3]O1%1%TokenStream.phpnu[PKg 3]m p%PrintableNewAnonClassNode.phpnu[PKg 3]  q0Differ.phpnu[PKg 3]m a&&BETokenPolyfill.phpnu[PKg 3]a  lDiffElem.phpnu[PKfo