Files
endorphin-aster-vm/crates/frontend/src/lexer.rs
T
codex-bot be4f1add58
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 6m57s
refactor: define lexical rules in a separate PEG grammar
2026-10-19 13:56:05 +08:00

73 lines
2.2 KiB
Rust

use crate::{Diagnostic, Span, Token, TokenKind};
/// Lex a reader snapshot and preserve provenance in any diagnostic.
pub fn lex_source(source: &crate::SourceUnit) -> Result<Vec<Token>, crate::SourceDiagnostic> {
lex(source.text()).map_err(|error| source.annotate(error))
}
/// Lex the basic Endorphin draft syntax without reading files or changing
/// locale. Positions always refer to bytes in this exact UTF-8 source.
pub fn lex(source: &str) -> Result<Vec<Token>, Diagnostic> {
let mut lexer = Lexer { source, offset: 0 };
let mut tokens = Vec::new();
while let Some(token) = lexer.next()? {
tokens.push(token);
}
Ok(tokens)
}
struct Lexer<'source> {
source: &'source str,
offset: usize,
}
impl Lexer<'_> {
fn next(&mut self) -> Result<Option<Token>, Diagnostic> {
let base = self.offset;
let (consumed, token) = lexical::next(&self.source[base..]).map_err(|error| {
let start = base + error.location.offset;
let end = start
+ self.source[start..]
.chars()
.next()
.map_or(0, char::len_utf8);
Diagnostic::lexical(Span::new(start, end), "unexpected source character")
})?;
self.offset += consumed;
token
.map(|token| {
token.map(|mut token| {
token.span.start += base;
token.span.end += base;
token
})
})
.map_err(|mut error| {
error.span.start += base;
error.span.end += base;
error
})
}
}
pub(crate) fn decode_string(raw: &str) -> String {
let mut result = String::new();
let mut characters = raw.chars();
while let Some(character) = characters.next() {
if character == '\\' {
result.push(match characters.next().expect("lexer validated escape") {
'n' => '\n',
't' => '\t',
'"' => '"',
'\\' => '\\',
_ => unreachable!("lexer validated escape"),
});
} else {
result.push(character);
}
}
result
}
include!("lexer.peg");