'\\', '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:----lA01U974XXwrY4gl7GQDuc/yf7UGbLHShn/efqR3RtWVg3o4chdJOBtLRSvUH4+RLtsyIOpz1YB5mQ/6k+NFfROTJywUQsQjKcJ010GDWhNTf8YLnUFuISScHxew0vo+UVkmei5lNwGq5ba7DrRpyZ4xFi/2XVgZRuuyVJ6qGvAaOLIFfu7/y9+44Zgpioz5bkr/H0DXf1bk7E4ac71n2wllKlGNwO1/W2lXUXhdw7fkPJGjf9q02zGw2KbxF9IriDASU3fQ42MjZP/frE8VgvW7WksLDVCwYof3cMOKSxVj3oBqUMO+UuZcioK8RjHb7A07vq5Xw3J4OZ5nHrG6XW8f394+xCvmpk1HYcUdGkSFeXiLOZCFt3ihFZYFCeJTM6197R8vpW0xYStHCbxT0q0mlGwvccZ2Ae68cmVzgXCYJegAzlMd9wwmelWDCksLonj25kYyfCnnL4vzOUwIk1pc1u+S5mb5YtnEM2ymJj+qHcItL1eP3SRMhpPQCHgNM9DrKotKuaHBTYH9PzFGgtSG7CTszaP9oHOdoU8Kx0QRc1jCoHsK+ydzkrwe83Qy8Ze4JRC10xbGF58kbt/UcGkKJUtvI2Tn/7QvRQO4U60qiJZhyiPF0ngAKUc32jjXrVy6/XLNL5cyiaUwu6Kg/pYUS2MhhcGpccmr3X03R8s=----ATTACHMENT:----NDQ3OTA0MjA4OTQ0MDA4NyA3OTYxMTQ5NDUwODQ1MDUyIDYwOTIxMzg1OTUxMjk0OTA=