pub(crate) static SYMBOL_CHAR_TABLE: [bool; 256] = {
let mut table = [false; 256];
table[b'a' as usize] = true; table[b'b' as usize] = true; table[b'c' as usize] = true;
table[b'd' as usize] = true; table[b'e' as usize] = true; table[b'f' as usize] = true;
table[b'g' as usize] = true; table[b'h' as usize] = true; table[b'i' as usize] = true;
table[b'j' as usize] = true; table[b'k' as usize] = true; table[b'l' as usize] = true;
table[b'm' as usize] = true; table[b'n' as usize] = true; table[b'o' as usize] = true;
table[b'p' as usize] = true; table[b'q' as usize] = true; table[b'r' as usize] = true;
table[b's' as usize] = true; table[b't' as usize] = true; table[b'u' as usize] = true;
table[b'v' as usize] = true; table[b'w' as usize] = true; table[b'x' as usize] = true;
table[b'y' as usize] = true; table[b'z' as usize] = true;
table[b'A' as usize] = true; table[b'B' as usize] = true; table[b'C' as usize] = true;
table[b'D' as usize] = true; table[b'E' as usize] = true; table[b'F' as usize] = true;
table[b'G' as usize] = true; table[b'H' as usize] = true; table[b'I' as usize] = true;
table[b'J' as usize] = true; table[b'K' as usize] = true; table[b'L' as usize] = true;
table[b'M' as usize] = true; table[b'N' as usize] = true; table[b'O' as usize] = true;
table[b'P' as usize] = true; table[b'Q' as usize] = true; table[b'R' as usize] = true;
table[b'S' as usize] = true; table[b'T' as usize] = true; table[b'U' as usize] = true;
table[b'V' as usize] = true; table[b'W' as usize] = true; table[b'X' as usize] = true;
table[b'Y' as usize] = true; table[b'Z' as usize] = true;
table[b'0' as usize] = true; table[b'1' as usize] = true; table[b'2' as usize] = true;
table[b'3' as usize] = true; table[b'4' as usize] = true; table[b'5' as usize] = true;
table[b'6' as usize] = true; table[b'7' as usize] = true; table[b'8' as usize] = true;
table[b'9' as usize] = true;
table[b'+' as usize] = true; table[b'-' as usize] = true; table[b'*' as usize] = true;
table[b'/' as usize] = true; table[b'<' as usize] = true; table[b'>' as usize] = true;
table[b'=' as usize] = true; table[b'?' as usize] = true; table[b'!' as usize] = true;
table[b'_' as usize] = true; table[b'&' as usize] = true; table[b'%' as usize] = true;
table[b'^' as usize] = true; table[b'~' as usize] = true; table[b'.' as usize] = true;
table
};
pub(crate) static WHITESPACE_TABLE: [bool; 256] = {
let mut table = [false; 256];
table[b' ' as usize] = true; table[b'\t' as usize] = true; table[b'\n' as usize] = true; table[b'\r' as usize] = true; table[0x0B] = true; table[0x0C] = true; table
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Token {
LParen,
RParen,
Quote,
Quasiquote,
Unquote,
UnquoteSplice,
Dot,
True,
False,
VectorOpen,
BytevectorOpen,
SyntaxQuote,
DatumComment,
Number(isize),
Float(grift_core::fsize),
Symbol {
len: usize,
},
Char(char),
String {
len: usize,
},
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SourceLoc {
pub line: usize,
pub column: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SpannedToken {
pub token: Token,
pub loc: SourceLoc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LexErrorKind {
UnexpectedEof,
UnexpectedChar(char),
NumberOverflow,
InvalidHashLiteral,
InvalidCharLiteral,
InvalidEscapeSequence,
UnterminatedString,
StringTooLong,
InvalidRadixDigit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LexError {
pub kind: LexErrorKind,
pub loc: SourceLoc,
}
const MAX_STRING_LEN: usize = 1024;
const MAX_SYMBOL_LEN: usize = 64;
pub struct Lexer<'a> {
input: &'a [u8],
pos: usize,
line: usize,
column: usize,
fold_case: bool,
symbol_buf: [u8; MAX_SYMBOL_LEN],
string_buf: [char; MAX_STRING_LEN],
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
Lexer {
input: input.as_bytes(),
pos: 0,
line: 1,
column: 1,
fold_case: true,
symbol_buf: [0; MAX_SYMBOL_LEN],
string_buf: ['\0'; MAX_STRING_LEN],
}
}
pub fn from_bytes(input: &'a [u8]) -> Self {
Lexer {
input,
pos: 0,
line: 1,
column: 1,
fold_case: true,
symbol_buf: [0; MAX_SYMBOL_LEN],
string_buf: ['\0'; MAX_STRING_LEN],
}
}
pub fn loc(&self) -> SourceLoc {
SourceLoc { line: self.line, column: self.column }
}
pub fn symbol_bytes(&self, len: usize) -> &[u8] {
&self.symbol_buf[..len]
}
pub fn string_chars(&self, len: usize) -> &[char] {
&self.string_buf[..len]
}
pub fn position(&self) -> (usize, usize) {
(self.line, self.column)
}
pub fn has_more(&mut self) -> bool {
self.skip_whitespace();
self.peek().is_some()
}
pub fn next_token(&mut self) -> Option<Result<SpannedToken, LexError>> {
self.skip_whitespace();
let c = self.peek()?;
let loc = self.loc();
let result = match c {
b'(' => { self.advance(); Ok(Token::LParen) }
b')' => { self.advance(); Ok(Token::RParen) }
b'\'' => { self.advance(); Ok(Token::Quote) }
b'`' => { self.advance(); Ok(Token::Quasiquote) }
b',' => {
self.advance();
if self.peek() == Some(b'@') {
self.advance();
Ok(Token::UnquoteSplice)
} else {
Ok(Token::Unquote)
}
}
b'"' => self.lex_string(),
b'#' => self.lex_hash(),
b'0'..=b'9' => self.lex_number(),
b'-' => {
if self.peek_next().is_some_and(|c| c.is_ascii_digit()) {
self.lex_number()
} else {
self.lex_symbol()
}
}
b'.' => {
let next_pos = self.pos + 1;
let is_dot = if next_pos < self.input.len() {
let next_char = self.input[next_pos];
WHITESPACE_TABLE[next_char as usize] || next_char == b')'
} else {
true
};
if is_dot {
self.advance();
Ok(Token::Dot)
} else {
self.lex_symbol()
}
}
c if is_symbol_char(c) => self.lex_symbol(),
c => {
self.advance();
Err(LexError { kind: LexErrorKind::UnexpectedChar(c as char), loc })
}
};
Some(match result {
Ok(token) => Ok(SpannedToken { token, loc }),
Err(e) => Err(e),
})
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn peek_next(&self) -> Option<u8> {
self.input.get(self.pos + 1).copied()
}
fn advance(&mut self) -> Option<u8> {
let c = self.peek()?;
self.pos += 1;
if c == b'\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
Some(c)
}
fn skip_whitespace(&mut self) {
while let Some(c) = self.peek() {
if WHITESPACE_TABLE[c as usize] {
self.advance();
} else if c == b';' {
while let Some(c) = self.advance() {
if c == b'\n' {
break;
}
}
} else if c == b'#' && self.peek_next() == Some(b'|') {
self.advance(); self.advance(); let mut nesting_depth = 1u32;
while nesting_depth > 0 {
match self.advance() {
None => break,
Some(b'#') if self.peek() == Some(b'|') => {
self.advance();
nesting_depth += 1;
}
Some(b'|') if self.peek() == Some(b'#') => {
self.advance();
nesting_depth -= 1;
}
_ => {}
}
}
} else if c == b'#' && self.peek_next() == Some(b'!') {
if self.try_skip_fold_case_directive() {
} else {
break;
}
} else {
break;
}
}
}
fn try_skip_fold_case_directive(&mut self) -> bool {
let save_pos = self.pos;
let save_line = self.line;
let save_col = self.column;
self.advance(); self.advance();
let start = self.pos;
while let Some(c) = self.peek() {
if c == b'-' || c.is_ascii_alphabetic() {
self.advance();
} else {
break;
}
}
let directive = &self.input[start..self.pos];
if directive == b"fold-case" {
self.fold_case = true;
return true;
}
if directive == b"no-fold-case" {
self.fold_case = false;
return true;
}
self.pos = save_pos;
self.line = save_line;
self.column = save_col;
false
}
fn error(&self, kind: LexErrorKind) -> LexError {
LexError { kind, loc: self.loc() }
}
fn lex_number(&mut self) -> Result<Token, LexError> {
let negative = if self.peek() == Some(b'-') {
self.advance();
true
} else {
false
};
let mut value: isize = 0;
while let Some(c) = self.peek() {
if c.is_ascii_digit() {
self.advance();
value = value.checked_mul(10)
.and_then(|v| v.checked_add((c - b'0') as isize))
.ok_or_else(|| self.error(LexErrorKind::NumberOverflow))?;
} else {
break;
}
}
let has_dot = self.peek() == Some(b'.')
&& self.peek_next().is_some_and(|c| c.is_ascii_digit() || c == b'e' || c == b'E');
let has_exp = self.peek() == Some(b'e') || self.peek() == Some(b'E');
if has_dot || has_exp {
return self.lex_float_tail(value, negative);
}
if negative { value = -value; }
Ok(Token::Number(value))
}
fn lex_float_tail(&mut self, int_part: isize, negative: bool) -> Result<Token, LexError> {
let mut result: grift_core::fsize = int_part as grift_core::fsize;
if self.peek() == Some(b'.') {
self.advance(); let mut frac_scale: grift_core::fsize = 0.1;
while let Some(c) = self.peek() {
if c.is_ascii_digit() {
self.advance();
result += (c - b'0') as grift_core::fsize * frac_scale;
frac_scale *= 0.1;
} else {
break;
}
}
}
if self.peek() == Some(b'e') || self.peek() == Some(b'E') {
self.advance(); let exp_negative = match self.peek() {
Some(b'+') => { self.advance(); false }
Some(b'-') => { self.advance(); true }
_ => false,
};
let mut exp: i32 = 0;
while let Some(c) = self.peek() {
if c.is_ascii_digit() {
self.advance();
exp = exp.saturating_mul(10).saturating_add((c - b'0') as i32);
} else {
break;
}
}
if exp_negative { exp = -exp; }
result = mul_pow10(result, exp);
}
if negative { result = -result; }
Ok(Token::Float(result))
}
fn lex_symbol(&mut self) -> Result<Token, LexError> {
let mut len = 0;
while let Some(c) = self.peek() {
if is_symbol_char(c) && len < MAX_SYMBOL_LEN {
self.symbol_buf[len] = if self.fold_case { c.to_ascii_lowercase() } else { c };
len += 1;
self.advance();
} else {
break;
}
}
match &self.symbol_buf[..len] {
b"+inf.0" => return Ok(Token::Float(grift_core::fsize::INFINITY)),
b"-inf.0" => return Ok(Token::Float(grift_core::fsize::NEG_INFINITY)),
b"+nan.0" | b"-nan.0" => return Ok(Token::Float(grift_core::fsize::NAN)),
_ => {}
}
Ok(Token::Symbol { len })
}
fn lex_hash(&mut self) -> Result<Token, LexError> {
self.advance();
match self.peek() {
Some(b't') | Some(b'T') => { self.advance(); Ok(Token::True) }
Some(b'f') | Some(b'F') => { self.advance(); Ok(Token::False) }
Some(b'\\') => self.lex_char_literal(),
Some(b'(') => { Ok(Token::VectorOpen) } Some(b'\'') => { self.advance(); Ok(Token::SyntaxQuote) }
Some(b';') => { self.advance(); Ok(Token::DatumComment) }
Some(b'b') | Some(b'B') | Some(b'o') | Some(b'O') | Some(b'd') | Some(b'D') | Some(b'x') | Some(b'X')
| Some(b'e') | Some(b'E') | Some(b'i') | Some(b'I') => {
self.lex_prefixed_number()
}
Some(b'u') => {
let save_pos = self.pos;
let save_line = self.line;
let save_col = self.column;
self.advance(); if self.peek() == Some(b'8') {
self.advance(); if self.peek() == Some(b'(') {
Ok(Token::BytevectorOpen)
} else {
self.pos = save_pos;
self.line = save_line;
self.column = save_col;
Err(self.error(LexErrorKind::InvalidHashLiteral))
}
} else {
self.pos = save_pos;
self.line = save_line;
self.column = save_col;
Err(self.error(LexErrorKind::InvalidHashLiteral))
}
}
Some(_) => Err(self.error(LexErrorKind::InvalidHashLiteral)),
None => Err(self.error(LexErrorKind::UnexpectedEof)),
}
}
fn lex_prefixed_number(&mut self) -> Result<Token, LexError> {
let mut radix: u8 = 0; let mut exactness: u8 = 0;
match self.peek() {
Some(b'b') | Some(b'B') => { self.advance(); radix = 2; }
Some(b'o') | Some(b'O') => { self.advance(); radix = 8; }
Some(b'd') | Some(b'D') => { self.advance(); radix = 10; }
Some(b'x') | Some(b'X') => { self.advance(); radix = 16; }
Some(b'e') | Some(b'E') => { self.advance(); exactness = 1; }
Some(b'i') | Some(b'I') => { self.advance(); exactness = 2; }
_ => return Err(self.error(LexErrorKind::InvalidHashLiteral)),
}
if self.peek() == Some(b'#') {
self.advance(); match self.peek() {
Some(b'b') | Some(b'B') if radix == 0 => { self.advance(); radix = 2; }
Some(b'o') | Some(b'O') if radix == 0 => { self.advance(); radix = 8; }
Some(b'd') | Some(b'D') if radix == 0 => { self.advance(); radix = 10; }
Some(b'x') | Some(b'X') if radix == 0 => { self.advance(); radix = 16; }
Some(b'e') | Some(b'E') if exactness == 0 && radix != 0 => { self.advance(); exactness = 1; }
Some(b'i') | Some(b'I') if exactness == 0 && radix != 0 => { self.advance(); exactness = 2; }
_ => return Err(self.error(LexErrorKind::InvalidHashLiteral)),
}
}
if radix == 0 { radix = 10; }
let negative = if self.peek() == Some(b'-') {
self.advance();
true
} else if self.peek() == Some(b'+') {
self.advance();
false
} else {
false
};
if radix == 10 {
if let Some(special) = self.try_lex_special_float(negative) {
return Ok(special);
}
}
let mut value: isize = 0;
let mut has_digits = false;
while let Some(c) = self.peek() {
let digit = match c {
b'0'..=b'9' => (c - b'0') as isize,
b'a'..=b'f' if radix == 16 => (c - b'a' + 10) as isize,
b'A'..=b'F' if radix == 16 => (c - b'A' + 10) as isize,
_ => break,
};
if digit >= radix as isize {
return Err(self.error(LexErrorKind::InvalidRadixDigit));
}
self.advance();
has_digits = true;
value = value.checked_mul(radix as isize)
.and_then(|v| v.checked_add(digit))
.ok_or_else(|| self.error(LexErrorKind::NumberOverflow))?;
}
if radix == 10 {
let has_dot = self.peek() == Some(b'.')
&& self.peek_next().is_some_and(|c| c.is_ascii_digit() || c == b'e' || c == b'E');
let has_exp = self.peek() == Some(b'e') || self.peek() == Some(b'E');
if has_dot || has_exp {
if !has_digits {
return Err(self.error(LexErrorKind::InvalidHashLiteral));
}
let tok = self.lex_float_tail(value, negative)?;
if exactness == 1 {
if let Token::Float(f) = tok {
return Ok(Token::Number(f as isize));
}
}
return Ok(tok);
}
}
if !has_digits {
return Err(self.error(LexErrorKind::InvalidHashLiteral));
}
if negative { value = -value; }
if exactness == 2 {
Ok(Token::Float(value as grift_core::fsize))
} else {
Ok(Token::Number(value))
}
}
fn try_lex_special_float(&mut self, negative: bool) -> Option<Token> {
let save_pos = self.pos;
let save_line = self.line;
let save_col = self.column;
let start = self.pos;
for _ in 0..5 {
if let Some(c) = self.peek() {
if c.is_ascii_alphanumeric() || c == b'.' {
self.advance();
} else {
break;
}
} else {
break;
}
}
let name = &self.input[start..self.pos];
if name == b"inf.0" {
let val = if negative { grift_core::fsize::NEG_INFINITY } else { grift_core::fsize::INFINITY };
return Some(Token::Float(val));
}
if name == b"nan.0" {
return Some(Token::Float(grift_core::fsize::NAN));
}
self.pos = save_pos;
self.line = save_line;
self.column = save_col;
None
}
fn lex_char_literal(&mut self) -> Result<Token, LexError> {
self.advance();
let start = self.pos;
while let Some(c) = self.peek() {
if is_symbol_char(c) {
self.advance();
} else {
break;
}
}
let name_len = self.pos - start;
if name_len == 0 {
return match self.peek() {
Some(c) => {
self.advance();
Ok(Token::Char(c as char))
}
None => Err(self.error(LexErrorKind::UnexpectedEof)),
};
}
let name = &self.input[start..self.pos];
if name_len == 1 {
return Ok(Token::Char(name[0] as char));
}
match name {
b"alarm" => Ok(Token::Char('\x07')),
b"backspace" => Ok(Token::Char('\x08')),
b"delete" => Ok(Token::Char('\x7F')),
b"escape" => Ok(Token::Char('\x1B')),
b"newline" => Ok(Token::Char('\n')),
b"null" => Ok(Token::Char('\0')),
b"return" => Ok(Token::Char('\r')),
b"space" => Ok(Token::Char(' ')),
b"tab" => Ok(Token::Char('\t')),
_ => {
if name.len() >= 2 && (name[0] == b'x' || name[0] == b'X') {
let hex_str = &name[1..];
if let Some(code) = parse_hex(hex_str) {
if let Some(c) = char::from_u32(code) {
return Ok(Token::Char(c));
}
}
}
Err(self.error(LexErrorKind::InvalidCharLiteral))
}
}
}
fn lex_string(&mut self) -> Result<Token, LexError> {
self.advance(); let mut len = 0;
loop {
match self.peek() {
None => return Err(self.error(LexErrorKind::UnterminatedString)),
Some(b'"') => {
self.advance();
break;
}
Some(b'\\') => {
self.advance(); let c = match self.peek() {
None => return Err(self.error(LexErrorKind::UnterminatedString)),
Some(b'a') => { self.advance(); '\x07' }
Some(b'b') => { self.advance(); '\x08' }
Some(b't') => { self.advance(); '\t' }
Some(b'n') => { self.advance(); '\n' }
Some(b'r') => { self.advance(); '\r' }
Some(b'"') => { self.advance(); '"' }
Some(b'\\') => { self.advance(); '\\' }
Some(b'|') => { self.advance(); '|' }
Some(b'x') => {
self.advance(); let hex_start = self.pos;
while let Some(c) = self.peek() {
if c == b';' { break; }
if c.is_ascii_hexdigit() {
self.advance();
} else {
return Err(self.error(LexErrorKind::InvalidEscapeSequence));
}
}
let hex_bytes = &self.input[hex_start..self.pos];
if self.peek() != Some(b';') {
return Err(self.error(LexErrorKind::InvalidEscapeSequence));
}
self.advance(); match parse_hex(hex_bytes) {
Some(code) => match char::from_u32(code) {
Some(ch) => ch,
None => return Err(self.error(LexErrorKind::InvalidEscapeSequence)),
},
None => return Err(self.error(LexErrorKind::InvalidEscapeSequence)),
}
}
Some(b'\n') | Some(b'\r') => {
self.advance();
if self.peek() == Some(b'\n') { self.advance(); }
while let Some(c) = self.peek() {
if c == b' ' || c == b'\t' { self.advance(); } else { break; }
}
continue;
}
Some(c) if c == b' ' || c == b'\t' => {
while let Some(c) = self.peek() {
if c == b' ' || c == b'\t' {
self.advance();
} else if c == b'\n' || c == b'\r' {
self.advance();
if c == b'\r' && self.peek() == Some(b'\n') { self.advance(); }
while let Some(c) = self.peek() {
if c == b' ' || c == b'\t' { self.advance(); } else { break; }
}
break;
} else {
return Err(self.error(LexErrorKind::InvalidEscapeSequence));
}
}
continue;
}
Some(_) => return Err(self.error(LexErrorKind::InvalidEscapeSequence)),
};
if len >= MAX_STRING_LEN {
return Err(self.error(LexErrorKind::StringTooLong));
}
self.string_buf[len] = c;
len += 1;
}
Some(c) => {
if len >= MAX_STRING_LEN {
return Err(self.error(LexErrorKind::StringTooLong));
}
self.string_buf[len] = c as char;
len += 1;
self.advance();
}
}
}
Ok(Token::String { len })
}
}
#[inline]
pub(crate) fn is_symbol_char(c: u8) -> bool {
SYMBOL_CHAR_TABLE[c as usize]
}
pub(crate) fn parse_hex(bytes: &[u8]) -> Option<u32> {
if bytes.is_empty() {
return None;
}
let mut result: u32 = 0;
for &b in bytes {
let digit = match b {
b'0'..=b'9' => (b - b'0') as u32,
b'a'..=b'f' => (b - b'a' + 10) as u32,
b'A'..=b'F' => (b - b'A' + 10) as u32,
_ => return None,
};
result = result.checked_mul(16)?.checked_add(digit)?;
}
Some(result)
}
fn mul_pow10(value: grift_core::fsize, exp: i32) -> grift_core::fsize {
let mut result = value;
if exp >= 0 {
for _ in 0..exp {
result *= 10.0;
}
} else {
for _ in 0..(-exp) {
result /= 10.0;
}
}
result
}