use std::str::CharIndices;
use crate::gql::lexer::Lexer;
use crate::gql::token::{Token, TokenKind};
use crate::syn::error::{SyntaxError, bail};
use crate::syn::token::Span;
impl Lexer<'_> {
pub(super) fn lex_quoted_token(&mut self, quote: u8, no_escape: bool) -> Token {
if let Err(e) = self.lex_quoted(quote, no_escape) {
return self.invalid_token(e);
}
let kind = match quote {
b'\'' => TokenKind::SingleQuoted {
no_escape,
},
b'"' => TokenKind::DoubleQuoted {
no_escape,
},
_ => TokenKind::AccentQuoted {
no_escape,
},
};
self.finish_token(kind)
}
pub(super) fn lex_quoted(&mut self, quote: u8, no_escape: bool) -> Result<(), SyntaxError> {
let start_span = self.current_span();
loop {
let Some(byte) = self.reader.next() else {
bail!(
"Unexpected end of file, expected the quoted sequence to end",
@start_span => "Quoted sequence starting here"
);
};
match byte {
b'\r' | b'\n' => {
let span = Span {
offset: self.reader.offset() - 1,
len: 1,
};
bail!(
"Quoted strings and identifiers may not contain raw newline characters, use the `\\n` escape sequence instead",
@span,
@start_span => "Quoted sequence starting here"
);
}
x if x == quote => {
if self.eat(quote) {
continue;
}
return Ok(());
}
b'\\' if !no_escape => {
let Some(next) = self.reader.next() else {
bail!(
"Unexpected end of file, expected the quoted sequence to end",
@start_span => "Quoted sequence starting here"
);
};
match next {
b'\r' | b'\n' => {
let span = Span {
offset: self.reader.offset() - 1,
len: 1,
};
bail!(
"Quoted strings and identifiers may not contain raw newline characters, use the `\\n` escape sequence instead",
@span,
@start_span => "Quoted sequence starting here"
);
}
x if !x.is_ascii() => {
self.reader.complete_char(x)?;
}
_ => {}
}
}
x if !x.is_ascii() => {
self.reader.complete_char(x)?;
}
_ => {}
}
}
}
pub fn unescape_quoted_span(text: &str, span: Span) -> Result<String, SyntaxError> {
let mut chars = text.char_indices();
let (no_escape, quote) = match chars.next() {
Some((_, '@')) => match chars.next() {
Some((_, quote @ ('\'' | '"' | '`'))) => (true, quote),
_ => bail!("Expected a quoted token", @span),
},
Some((_, quote @ ('\'' | '"' | '`'))) => (false, quote),
_ => bail!("Expected a quoted token", @span),
};
let mut result = String::with_capacity(text.len());
while let Some((at, char)) = chars.next() {
if char == quote {
match chars.next() {
Some((_, x)) if x == quote => result.push(quote),
_ => break,
}
} else if char == '\\' && !no_escape {
Self::unescape_escape_sequence(&mut chars, span, at, &mut result)?;
} else {
result.push(char);
}
}
Ok(result)
}
pub fn parameter_name_span(text: &str, span: Span) -> Result<String, SyntaxError> {
let Some(name) = text.strip_prefix("$$").or_else(|| text.strip_prefix('$')) else {
bail!("Expected a parameter token", @span);
};
if name.starts_with(['"', '`', '@']) {
let prefix_len = (text.len() - name.len()) as u32;
let span = Span {
offset: span.offset + prefix_len,
len: span.len - prefix_len,
};
return Self::unescape_quoted_span(name, span);
}
Ok(name.to_owned())
}
fn unescape_escape_sequence(
chars: &mut CharIndices,
span: Span,
at: usize,
result: &mut String,
) -> Result<(), SyntaxError> {
let Some((_, char)) = chars.next() else {
let span = Span {
offset: span.offset + at as u32,
len: 1,
};
bail!("Invalid escape sequence, missing the escape character", @span);
};
match char {
'\\' => result.push('\\'),
'\'' => result.push('\''),
'"' => result.push('"'),
'`' => result.push('`'),
't' => result.push('\t'),
'b' => result.push('\u{0008}'),
'n' => result.push('\n'),
'r' => result.push('\r'),
'f' => result.push('\u{000C}'),
'u' => result.push(Self::unescape_unicode_sequence(chars, span, at, 4)?),
'U' => result.push(Self::unescape_unicode_sequence(chars, span, at, 6)?),
x => {
let span = Span {
offset: span.offset + at as u32,
len: 1 + x.len_utf8() as u32,
};
bail!(
"Invalid escape sequence `\\{}`",
x.escape_debug(),
@span => "Expected one of `\\`, `'`, `\"`, `` ` ``, `t`, `b`, `n`, `r`, `f`, `uXXXX` or `UXXXXXX`; escape letters are case-sensitive"
);
}
}
Ok(())
}
fn unescape_unicode_sequence(
chars: &mut CharIndices,
span: Span,
at: usize,
digits: u32,
) -> Result<char, SyntaxError> {
let mut value: u32 = 0;
for i in 0..digits {
let digit = chars.next().and_then(|(_, x)| x.to_digit(16));
let Some(digit) = digit else {
let span = Span {
offset: span.offset + at as u32,
len: 2 + i,
};
bail!(
"Invalid unicode escape sequence, expected {digits} hexadecimal digits",
@span
);
};
value = (value << 4) | digit;
}
let Some(char) = char::from_u32(value) else {
let span = Span {
offset: span.offset + at as u32,
len: 2 + digits,
};
bail!(
"Invalid unicode escape sequence, `{value:#x}` is not a valid unicode code point",
@span
);
};
Ok(char)
}
}