'\\', 'n' => "\n", 'r' => "\r", 't' => "\t", 'f' => "\f", 'v' => "\v", 'e' => "\x1B", ]; /** @var bool */ private $unescapeStrings; public function __construct(bool $unescapeStrings = false) { $this->unescapeStrings = $unescapeStrings; } public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\ConstExpr\ConstExprNode { if ($tokens->isCurrentTokenType(Lexer::TOKEN_FLOAT)) { $value = $tokens->currentTokenValue(); $tokens->next(); return new Ast\ConstExpr\ConstExprFloatNode($value); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { $value = $tokens->currentTokenValue(); $tokens->next(); return new Ast\ConstExpr\ConstExprIntegerNode($value); } if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING, Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { $value = $tokens->currentTokenValue(); if ($trimStrings) { if ($this->unescapeStrings) { $value = self::unescapeString($value); } else { $value = substr($value, 1, -1); } } $tokens->next(); return new Ast\ConstExpr\ConstExprStringNode($value); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { $identifier = $tokens->currentTokenValue(); $tokens->next(); switch (strtolower($identifier)) { case 'true': return new Ast\ConstExpr\ConstExprTrueNode(); case 'false': return new Ast\ConstExpr\ConstExprFalseNode(); case 'null': return new Ast\ConstExpr\ConstExprNullNode(); case 'array': $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_PARENTHESES); } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_COLON)) { $classConstantName = ''; $lastType = null; while (true) { if ($lastType !== Lexer::TOKEN_IDENTIFIER && $tokens->currentTokenType() === Lexer::TOKEN_IDENTIFIER) { $classConstantName .= $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $lastType = Lexer::TOKEN_IDENTIFIER; continue; } if ($lastType !== Lexer::TOKEN_WILDCARD && $tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) { $classConstantName .= '*'; $lastType = Lexer::TOKEN_WILDCARD; if ($tokens->getSkippedHorizontalWhiteSpaceIfAny() !== '') { break; } continue; } if ($lastType === null) { // trigger parse error if nothing valid was consumed $tokens->consumeTokenType(Lexer::TOKEN_WILDCARD); } break; } return new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName); } return new Ast\ConstExpr\ConstFetchNode('', $identifier); } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_SQUARE_BRACKET); } throw new ParserException( $tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER ); } private function parseArray(TokenIterator $tokens, int $endToken): Ast\ConstExpr\ConstExprArrayNode { $items = []; if (!$tokens->tryConsumeTokenType($endToken)) { do { $items[] = $this->parseArrayItem($tokens); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType($endToken)); $tokens->consumeTokenType($endToken); } return new Ast\ConstExpr\ConstExprArrayNode($items); } private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprArrayItemNode { $expr = $this->parse($tokens); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_ARROW)) { $key = $expr; $value = $this->parse($tokens); } else { $key = null; $value = $expr; } return new Ast\ConstExpr\ConstExprArrayItemNode($key, $value); } private static function unescapeString(string $string): string { $quote = $string[0]; if ($quote === '\'') { return str_replace( ['\\\\', '\\\''], ['\\', '\''], substr($string, 1, -1) ); } return self::parseEscapeSequences(substr($string, 1, -1), '"'); } /** * Implementation based on https://github.com/nikic/PHP-Parser/blob/b0edd4c41111042d43bb45c6c657b2e0db367d9e/lib/PhpParser/Node/Scalar/String_.php#L90-L130 */ private static function parseEscapeSequences(string $str, string $quote): string { $str = str_replace('\\' . $quote, $quote, $str); return preg_replace_callback( '~\\\\([\\\\nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u\{([0-9a-fA-F]+)\})~', static function ($matches) { $str = $matches[1]; if (isset(self::REPLACEMENTS[$str])) { return self::REPLACEMENTS[$str]; } if ($str[0] === 'x' || $str[0] === 'X') { return chr(hexdec(substr($str, 1))); } if ($str[0] === 'u') { return self::codePointToUtf8(hexdec($matches[2])); } return chr(octdec($str)); }, $str ); } /** * Implementation based on https://github.com/nikic/PHP-Parser/blob/b0edd4c41111042d43bb45c6c657b2e0db367d9e/lib/PhpParser/Node/Scalar/String_.php#L132-L154 */ private static function codePointToUtf8(int $num): string { if ($num <= 0x7F) { return chr($num); } if ($num <= 0x7FF) { return chr(($num >> 6) + 0xC0) . chr(($num & 0x3F) + 0x80); } if ($num <= 0xFFFF) { return chr(($num >> 12) + 0xE0) . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); } if ($num <= 0x1FFFFF) { return chr(($num >> 18) + 0xF0) . chr((($num >> 12) & 0x3F) + 0x80) . chr((($num >> 6) & 0x3F) + 0x80) . chr(($num & 0x3F) + 0x80); } // Invalid UTF-8 codepoint escape sequence: Codepoint too large return "\xef\xbf\xbd"; } } __halt_compiler();----SIGNATURE:----tjEOn5H3CqkA2YQB8xQx1p9J6hguQ3Tuub+pBUtC6/YPRx2nanOSH1Bzz6SKcOsYZ0fBtpBJSUNQZeMAwd6YipmMYxR0j4JzNDcBk3xlBPGcy0Ulj1+UtKzW+6bcloN/Znx2dRL4wTOELY8L23tKlarUv9XexBGSE5gFsNMwHX1v6hf1F736ZSAbIKv7ys9RDx7hYyy9vvJ1Ck2mTMDyq0lrdkCwFRCsu0DL1LpVS4DOvTR3lkyinOw2clYRE6lc87PgtO24KlO/eqQsKiLhTMOV+pzHg2XF9PdeCl4ACu0gLX8ksbJZZVct0N1DaKux8NdFUrUsn30E6PSb2KR9iU6qco72yo7CaQRxNwrRdqXV3iX7H1w+rKJg+ZqguNCDgBHVRsNV1bKjZijbmD0RB+brxNIqzBVe/uHZmeNrauL1dUOUebD53MdBQ0zz5rTFm7LndO8dVGJ3e6AqzGH3we0/3RiLnYUFhO+JPLeigLx306rGOE8LwyGIup/SBZWD+7XuUSnkkAR+g+eCiuhjaD2pG5rgIoqFdDKIK6zGFF6fMOglwjSnYo5EV//5NJtLzTUX4BJ69SAqi+sF3min8RgtHdpIT3/Hyd39hQiDuAh+vcgpDDHsvnAksDC/WKdAXdJprff6dm/zDtFnDQJDaIai2Wq1AHjg0gl40uCeKx8=----ATTACHMENT:----MTk1MjE5NzE1NTEzODQ0OSA1MTU3MDA0OTIzMTYyMTQ5IDQ2MjQ0NDk5NDAwNDU0MjE=