use unicase::UniCase;
use crate::gql::lexer::{Lexer, keywords};
use crate::gql::token::{Token, TokenKind};
use crate::syn::error::syntax_error;
pub(super) fn is_ident_start(char: char) -> bool {
if char.is_ascii() {
return matches!(char, 'a'..='z' | 'A'..='Z' | '_');
}
char.is_alphabetic() || is_connector_punctuation(char)
}
pub(super) fn is_ident_continue(char: char) -> bool {
if char.is_ascii() {
return matches!(char, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_');
}
char.is_alphanumeric() || is_connector_punctuation(char)
}
fn is_connector_punctuation(char: char) -> bool {
matches!(
char,
'\u{005F}' | '\u{203F}' | '\u{2040}' | '\u{2054}' | '\u{FE33}' | '\u{FE34}' | '\u{FE4D}'
..='\u{FE4F}' | '\u{FF3F}'
)
}
impl Lexer<'_> {
pub(super) fn lex_ident(&mut self) -> Token {
self.eat_ident_continue();
let str = self.span_str(self.current_span());
if let Some(keyword) = keywords::KEYWORDS.get(&UniCase::ascii(str)) {
return self.finish_token(TokenKind::Keyword(*keyword));
}
self.finish_token(TokenKind::Identifier)
}
pub(super) fn lex_param(&mut self) -> Token {
let kind = if self.eat(b'$') {
TokenKind::SubstitutedParameter
} else {
TokenKind::Parameter
};
match self.reader.peek() {
Some(quote @ (b'"' | b'`')) => {
self.reader.next();
if let Err(e) = self.lex_quoted(quote, false) {
return self.invalid_token(e);
}
}
Some(b'@') => {
if let Some(quote @ (b'"' | b'`')) = self.reader.peek1() {
self.reader.next();
self.reader.next();
if let Err(e) = self.lex_quoted(quote, true) {
return self.invalid_token(e);
}
} else {
let error = syntax_error!(
"Unexpected token `{}`, expected a parameter name",
self.span_str(self.current_span()),
@self.current_span() => "Parameter names are identifier characters or a `\"…\"`/`` `…` `` delimited identifier"
);
return self.invalid_token(error);
}
}
_ => {
if !self.eat_ident_continue() {
let error = syntax_error!(
"Unexpected token `{}`, expected a parameter name",
self.span_str(self.current_span()),
@self.current_span() => "Parameter names are identifier characters or a `\"…\"`/`` `…` `` delimited identifier"
);
return self.invalid_token(error);
}
}
}
self.finish_token(kind)
}
fn eat_ident_continue(&mut self) -> bool {
let start = self.reader.offset();
while let Some(byte) = self.reader.peek() {
if byte.is_ascii() {
if !is_ident_continue(byte as char) {
break;
}
self.reader.next();
continue;
}
let backup = self.reader.offset();
self.reader.next();
match self.reader.complete_char(byte) {
Ok(char) if is_ident_continue(char) => {}
_ => {
self.reader.backup(backup);
break;
}
}
}
self.reader.offset() != start
}
}