use crate::{Error, SyntaxError, parse_number};
pub(crate) fn syntax_error(message: &'static str, offset: usize) -> Error {
Error::Syntax(SyntaxError { message, offset })
}
#[derive(Clone, Copy)]
pub(crate) enum Tok<'src> {
LParen,
RParen,
LBracket,
RBracket,
Comma,
Dot,
Star,
Bang,
BangEqual,
EqualEqual,
Greater,
GreaterEqual,
Less,
LessEqual,
And,
Or,
Number(f64),
Str,
Ident(&'src str),
True,
False,
Null,
}
#[derive(Clone, Copy)]
pub(crate) struct Token<'src> {
pub(crate) tok: Tok<'src>,
pub(crate) start: usize,
pub(crate) end: usize,
}
fn is_boundary(byte: u8) -> bool {
byte.is_ascii_whitespace()
|| matches!(
byte,
b'(' | b')' | b'[' | b']' | b',' | b'.' | b'!' | b'<' | b'>' | b'=' | b'&' | b'|'
)
}
fn is_identifier_start(byte: u8) -> bool {
byte.is_ascii_alphabetic() || byte == b'_'
}
fn is_legal_identifier(lexeme: &str) -> bool {
let mut bytes = lexeme.bytes();
match bytes.next() {
Some(first) if is_identifier_start(first) => {}
_ => return false,
}
bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
}
pub(crate) fn lex(src: &str) -> Result<Vec<Token<'_>>, Error> {
Lexer {
src,
pos: 0,
tokens: Vec::with_capacity(src.len() / 2),
}
.run()
}
struct Lexer<'src> {
src: &'src str,
pos: usize,
tokens: Vec<Token<'src>>,
}
impl<'src> Lexer<'src> {
fn bytes(&self) -> &'src [u8] {
self.src.as_bytes()
}
fn run(mut self) -> Result<Vec<Token<'src>>, Error> {
let bytes = self.bytes();
loop {
self.pos += bytes[self.pos..]
.iter()
.take_while(|&&b| b.is_ascii_whitespace())
.count();
if self.pos >= bytes.len() {
break;
}
let start = self.pos;
let byte = bytes[start];
let next = bytes.get(start + 1).copied();
match byte {
b'(' => self.push(Tok::LParen, start, start + 1),
b')' => self.push(Tok::RParen, start, start + 1),
b'[' => self.push(Tok::LBracket, start, start + 1),
b']' => self.push(Tok::RBracket, start, start + 1),
b',' => self.push(Tok::Comma, start, start + 1),
b'*' => self.push(Tok::Star, start, start + 1),
b'!' if next == Some(b'=') => self.push(Tok::BangEqual, start, start + 2),
b'!' => self.push(Tok::Bang, start, start + 1),
b'<' if next == Some(b'=') => self.push(Tok::LessEqual, start, start + 2),
b'<' => self.push(Tok::Less, start, start + 1),
b'>' if next == Some(b'=') => self.push(Tok::GreaterEqual, start, start + 2),
b'>' => self.push(Tok::Greater, start, start + 1),
b'=' if next == Some(b'=') => self.push(Tok::EqualEqual, start, start + 2),
b'=' => return Err(syntax_error("expected `==`", start)),
b'&' if next == Some(b'&') => self.push(Tok::And, start, start + 2),
b'&' => return Err(syntax_error("expected `&&`", start)),
b'|' if next == Some(b'|') => self.push(Tok::Or, start, start + 2),
b'|' => return Err(syntax_error("expected `||`", start)),
b'\'' => self.lex_string()?,
b'.' if self.prev_allows_dot() => self.push(Tok::Dot, start, start + 1),
b'.' | b'+' | b'-' | b'0'..=b'9' => self.lex_number()?,
_ => self.lex_identifier()?,
}
}
Ok(self.tokens)
}
fn push(&mut self, tok: Tok<'src>, start: usize, end: usize) {
self.tokens.push(Token { tok, start, end });
self.pos = end;
}
fn prev_allows_dot(&self) -> bool {
matches!(
self.tokens.last().map(|t| t.tok),
Some(Tok::Ident(_) | Tok::RBracket | Tok::RParen | Tok::Star)
)
}
fn lex_string(&mut self) -> Result<(), Error> {
let bytes = self.bytes();
let start = self.pos;
let mut i = start + 1;
loop {
match bytes.get(i) {
None => return Err(syntax_error("unterminated string literal", start)),
Some(b'\'') if bytes.get(i + 1) == Some(&b'\'') => i += 2,
Some(b'\'') => break,
Some(_) => i += 1,
}
}
self.push(Tok::Str, start, i + 1);
Ok(())
}
fn lex_number(&mut self) -> Result<(), Error> {
let start = self.pos;
let end = start
+ self.bytes()[start..]
.iter()
.take_while(|&&b| !is_boundary(b) || b == b'.')
.count();
let value = parse_number(&self.src[start..end]);
if value.is_nan() {
return Err(syntax_error("invalid numeric literal", start));
}
self.push(Tok::Number(value), start, end);
Ok(())
}
fn lex_identifier(&mut self) -> Result<(), Error> {
let start = self.pos;
let end = start
+ self.bytes()[start..]
.iter()
.take_while(|&&b| !is_boundary(b))
.count();
let lexeme = &self.src[start..end];
if !is_legal_identifier(lexeme) {
return Err(syntax_error("unexpected symbol", start));
}
let after_dot = matches!(self.tokens.last().map(|t| t.tok), Some(Tok::Dot));
let tok = match lexeme {
"true" if !after_dot => Tok::True,
"false" if !after_dot => Tok::False,
"null" if !after_dot => Tok::Null,
"NaN" if !after_dot => Tok::Number(f64::NAN),
"Infinity" if !after_dot => Tok::Number(f64::INFINITY),
_ => Tok::Ident(lexeme),
};
self.push(tok, start, end);
Ok(())
}
}