mod ident;
mod punctuation;
#[cfg(test)]
mod tests;
use crate::SyntaxKind;
pub use ident::{classify_keyword, is_ident_char, is_ink_ident_codepoint, scan_ident};
pub use punctuation::lex_punctuation;
pub fn lex(source: &str) -> Vec<(SyntaxKind, &str)> {
Lexer::new(source).run()
}
struct Lexer<'src> {
source: &'src str,
bytes: &'src [u8],
pos: usize,
string_depth: u32,
tokens: Vec<(SyntaxKind, &'src str)>,
}
impl<'src> Lexer<'src> {
fn new(source: &'src str) -> Self {
Self {
source,
bytes: source.as_bytes(),
pos: 0,
string_depth: 0,
tokens: Vec::new(),
}
}
fn in_string(&self) -> bool {
self.string_depth % 2 == 1
}
fn run(mut self) -> Vec<(SyntaxKind, &'src str)> {
while self.pos < self.bytes.len() {
if self.in_string() {
self.lex_string_token();
} else {
self.lex_code_token();
}
}
self.tokens
}
fn emit(&mut self, kind: SyntaxKind, start: usize) {
self.tokens.push((kind, &self.source[start..self.pos]));
}
fn lex_string_token(&mut self) {
let start = self.pos;
let b = self.bytes[self.pos];
if b == b'"' {
self.pos += 1;
self.string_depth -= 1;
self.emit(SyntaxKind::QUOTE, start);
return;
}
if b == b'\\' && self.pos + 1 < self.bytes.len() {
let next = self.bytes[self.pos + 1];
if matches!(next, b'n' | b't' | b'\\' | b'"') {
self.pos += 2;
self.emit(SyntaxKind::STRING_ESCAPE, start);
return;
}
}
if b == b'{' {
self.pos += 1;
self.string_depth += 1;
self.emit(SyntaxKind::L_BRACE, start);
return;
}
if b == b'[' {
self.pos += 1;
self.emit(SyntaxKind::L_BRACKET, start);
return;
}
if b == b']' {
self.pos += 1;
self.emit(SyntaxKind::R_BRACKET, start);
return;
}
if b == b'<' && self.pos + 1 < self.bytes.len() && self.bytes[self.pos + 1] == b'>' {
self.pos += 2;
self.emit(SyntaxKind::GLUE, start);
return;
}
if b == b'\n' || b == b'\r' {
self.pos += 1;
if b == b'\r' && self.pos < self.bytes.len() && self.bytes[self.pos] == b'\n' {
self.pos += 1;
}
self.string_depth -= 1;
self.emit(SyntaxKind::NEWLINE, start);
return;
}
self.pos += 1;
while self.pos < self.bytes.len() {
match self.bytes[self.pos] {
b'"' | b'\\' | b'{' | b'\n' | b'\r' | b'[' | b']' => break,
b'<' if self.pos + 1 < self.bytes.len() && self.bytes[self.pos + 1] == b'>' => {
break;
}
_ => self.pos += 1,
}
}
self.emit(SyntaxKind::STRING_TEXT, start);
}
fn lex_code_token(&mut self) {
let start = self.pos;
let b = self.bytes[self.pos];
if b == b'\n' {
self.pos += 1;
self.emit(SyntaxKind::NEWLINE, start);
return;
}
if b == b'\r' {
self.pos += 1;
if self.pos < self.bytes.len() && self.bytes[self.pos] == b'\n' {
self.pos += 1;
}
self.emit(SyntaxKind::NEWLINE, start);
return;
}
if b == 0xEF
&& self.pos + 2 < self.bytes.len()
&& self.bytes[self.pos + 1] == 0xBB
&& self.bytes[self.pos + 2] == 0xBF
{
self.pos += 3;
self.emit(SyntaxKind::WHITESPACE, start);
return;
}
if b == b' ' || b == b'\t' {
self.pos += 1;
while self.pos < self.bytes.len()
&& (self.bytes[self.pos] == b' ' || self.bytes[self.pos] == b'\t')
{
self.pos += 1;
}
self.emit(SyntaxKind::WHITESPACE, start);
return;
}
if b == b'/'
&& let Some(kind) = self.try_lex_comment()
{
self.emit(kind, start);
return;
}
if b == b'}' && self.string_depth > 0 {
self.pos += 1;
self.string_depth -= 1;
self.emit(SyntaxKind::R_BRACE, start);
return;
}
if let Some((kind, advance)) = lex_punctuation(self.bytes, self.pos) {
self.pos += advance;
if kind == SyntaxKind::QUOTE {
self.string_depth += 1;
}
self.emit(kind, start);
return;
}
if b.is_ascii_digit() {
self.lex_number_or_ident();
return;
}
if is_ident_char(self.bytes, self.pos) {
let end = scan_ident(self.bytes, self.pos + char_len_utf8(self.bytes, self.pos));
let text = &self.source[start..end];
let kind = classify_keyword(text);
self.pos = end;
self.tokens.push((kind, text));
return;
}
self.pos += char_len_utf8(self.bytes, self.pos);
self.emit(SyntaxKind::ERROR_TOKEN, start);
}
fn try_lex_comment(&mut self) -> Option<SyntaxKind> {
if self.pos + 1 >= self.bytes.len() {
return None;
}
match self.bytes[self.pos + 1] {
b'/' => {
self.pos += 2;
while self.pos < self.bytes.len()
&& self.bytes[self.pos] != b'\n'
&& self.bytes[self.pos] != b'\r'
{
self.pos += 1;
}
Some(SyntaxKind::LINE_COMMENT)
}
b'*' => {
self.pos += 2;
loop {
if self.pos + 1 < self.bytes.len()
&& self.bytes[self.pos] == b'*'
&& self.bytes[self.pos + 1] == b'/'
{
self.pos += 2;
break;
}
if self.pos >= self.bytes.len() {
break; }
self.pos += 1;
}
Some(SyntaxKind::BLOCK_COMMENT)
}
_ => None,
}
}
fn lex_number_or_ident(&mut self) {
let start = self.pos;
while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
self.pos += 1;
}
if self.pos < self.bytes.len() && is_ident_char(self.bytes, self.pos) {
self.pos = scan_ident(self.bytes, self.pos + char_len_utf8(self.bytes, self.pos));
self.emit(SyntaxKind::IDENT, start);
return;
}
if self.pos < self.bytes.len()
&& self.bytes[self.pos] == b'.'
&& self.pos + 1 < self.bytes.len()
&& self.bytes[self.pos + 1].is_ascii_digit()
{
self.pos += 1; while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
self.pos += 1;
}
if self.pos < self.bytes.len() && is_ident_char(self.bytes, self.pos) {
self.pos = scan_ident(self.bytes, self.pos + char_len_utf8(self.bytes, self.pos));
self.emit(SyntaxKind::IDENT, start);
return;
}
self.emit(SyntaxKind::FLOAT, start);
return;
}
self.emit(SyntaxKind::INTEGER, start);
}
}
pub(crate) fn char_len_utf8(bytes: &[u8], pos: usize) -> usize {
let b = bytes[pos];
if b < 0x80 {
1
} else if b < 0xE0 {
2
} else if b < 0xF0 {
3
} else {
4
}
}