/** * @param $code * @return string */ public static function printCharCode($code) { if (null === $code) { return '<EOF>'; } return $code < 0x7f ? json_encode(Utils::chr($code)) : '"\\u' . dechex($code) . '"'; }
/** * @it accepts BOM header */ public function testAcceptsBomHeader() { $bom = Utils::chr(0xfeff); $expected = ['kind' => Token::NAME, 'start' => 2, 'end' => 5, 'value' => 'foo']; $this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo')); }
private function readString($start) { $body = $this->source->body; $bodyLength = $this->source->length; $position = $start + 1; $chunkStart = $position; $code = null; $value = ''; while ($position < $bodyLength && ($code = Utils::charCodeAt($body, $position)) && $code !== 34 && $code !== 10 && $code !== 13 && $code !== 0x2028 && $code !== 0x2029) { ++$position; if ($code === 92) { // \ $value .= mb_substr($body, $chunkStart, $position - 1 - $chunkStart, 'UTF-8'); $code = Utils::charCodeAt($body, $position); switch ($code) { case 34: $value .= '"'; break; case 47: $value .= '\\/'; break; case 92: $value .= '\\'; break; case 98: $value .= '\\b'; break; case 102: $value .= '\\f'; break; case 110: $value .= '\\n'; break; case 114: $value .= '\\r'; break; case 116: $value .= '\\t'; break; case 117: $hex = mb_substr($body, $position + 1, 4); if (!preg_match('/[0-9a-fA-F]{4}/', $hex)) { throw Exception::create($this->source, $position, 'Bad character escape sequence'); } $value .= Utils::chr(hexdec($hex)); $position += 4; break; default: throw Exception::create($this->source, $position, 'Bad character escape sequence'); } ++$position; $chunkStart = $position; } } if ($code !== 34) { throw Exception::create($this->source, $position, 'Unterminated string'); } $value .= mb_substr($body, $chunkStart, $position - $chunkStart, 'UTF-8'); return new Token(Token::STRING, $start, $position + 1, $value); }
/** * @param int $start * @param int $line * @param int $col * @param Token $prev * @return Token * @throws SyntaxError */ private function readString($start, $line, $col, Token $prev) { $body = $this->source->body; $bodyLength = $this->source->length; $position = $start + 1; $chunkStart = $position; $code = null; $value = ''; while ($position < $bodyLength && ($code = Utils::charCodeAt($body, $position)) && $code !== 0xa && $code !== 0xd && $code !== 34) { $this->assertValidStringCharacterCode($code, $position); ++$position; if ($code === 92) { // \ $value .= mb_substr($body, $chunkStart, $position - 1 - $chunkStart, 'UTF-8'); $code = Utils::charCodeAt($body, $position); switch ($code) { case 34: $value .= '"'; break; case 47: $value .= '/'; break; case 92: $value .= '\\'; break; case 98: $value .= chr(8); break; // \b (backspace) // \b (backspace) case 102: $value .= "\f"; break; case 110: $value .= "\n"; break; case 114: $value .= "\r"; break; case 116: $value .= "\t"; break; case 117: $hex = mb_substr($body, $position + 1, 4, 'UTF-8'); if (!preg_match('/[0-9a-fA-F]{4}/', $hex)) { throw new SyntaxError($this->source, $position, 'Invalid character escape sequence: \\u' . $hex); } $code = hexdec($hex); $this->assertValidStringCharacterCode($code, $position - 1); $value .= Utils::chr($code); $position += 4; break; default: throw new SyntaxError($this->source, $position, 'Invalid character escape sequence: \\' . Utils::chr($code)); } ++$position; $chunkStart = $position; } } if ($code !== 34) { throw new SyntaxError($this->source, $position, 'Unterminated string'); } $value .= mb_substr($body, $chunkStart, $position - $chunkStart, 'UTF-8'); return new Token(Token::STRING, $start, $position + 1, $line, $col, $prev, $value); }
/** * @it parses multi-byte characters */ public function testParsesMultiByteCharacters() { // Note: \u0A0A could be naively interpretted as two line-feed chars. $char = Utils::chr(0xa0a); $query = <<<HEREDOC # This comment has a {$char} multi-byte character. { field(arg: "Has a {$char} multi-byte character.") } HEREDOC; $result = Parser::parse($query, ['noLocation' => true]); $expected = new SelectionSetNode(['selections' => [new FieldNode(['name' => new NameNode(['value' => 'field']), 'arguments' => [new ArgumentNode(['name' => new NameNode(['value' => 'arg']), 'value' => new StringValueNode(['value' => "Has a {$char} multi-byte character."])])], 'directives' => []])]]); $this->assertEquals($expected, $result->definitions[0]->selectionSet); }