feat: Implement lexer and parser for AST
Introduces the lexer and parser modules, enabling the conversion of source code into an Abstract Syntax Tree (AST). This includes: - Defining token kinds and structures. - Implementing lexer logic to tokenize input. - Defining AST node kinds and structures. - Implementing parser logic to construct the AST from tokens. - Adding support for basic expressions, lists, keywords, and identifiers. - Adding the `lazy_static` dependency for keyword interning. - Refactoring `Value::Nil` to `Value::Void`.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use std::sync::Arc;
|
||||
use crate::ast::types::SourceLocation;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TokenKind {
|
||||
LeftParen, RightParen,
|
||||
LeftBracket, RightBracket,
|
||||
LeftBrace, RightBrace,
|
||||
Quote, Backtick, Tilde, At,
|
||||
Identifier(Arc<str>),
|
||||
Keyword(Arc<str>),
|
||||
Number(f64),
|
||||
String(Arc<str>),
|
||||
EOF,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub kind: TokenKind,
|
||||
pub location: SourceLocation,
|
||||
}
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
input: Peekable<Chars<'a>>,
|
||||
line: u32,
|
||||
col: u32,
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(input: &'a str) -> Self {
|
||||
Self {
|
||||
input: input.chars().peekable(),
|
||||
line: 1,
|
||||
col: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_token(&mut self) -> Result<Token, String> {
|
||||
self.skip_whitespace();
|
||||
|
||||
let start_location = SourceLocation { line: self.line, col: self.col };
|
||||
|
||||
let char = match self.input.next() {
|
||||
Some(c) => {
|
||||
let current_char = c;
|
||||
self.col += 1;
|
||||
current_char
|
||||
},
|
||||
None => return Ok(Token { kind: TokenKind::EOF, location: start_location }),
|
||||
};
|
||||
|
||||
let kind = match char {
|
||||
'(' => TokenKind::LeftParen,
|
||||
')' => TokenKind::RightParen,
|
||||
'[' => TokenKind::LeftBracket,
|
||||
']' => TokenKind::RightBracket,
|
||||
'{' => TokenKind::LeftBrace,
|
||||
'}' => TokenKind::RightBrace,
|
||||
'\'' => TokenKind::Quote,
|
||||
'`' => TokenKind::Backtick,
|
||||
'~' => TokenKind::Tilde,
|
||||
'@' => TokenKind::At,
|
||||
':' => {
|
||||
let id = self.read_while(|c| !c.is_whitespace() && !"()[]{}\"'`~@;".contains(c));
|
||||
TokenKind::Keyword(Arc::from(id))
|
||||
}
|
||||
'"' => TokenKind::String(Arc::from(self.read_string()?)),
|
||||
c if c.is_ascii_digit() || (c == '-' && self.peek().map_or(false, |p| p.is_ascii_digit())) => {
|
||||
let mut num_str = String::from(c);
|
||||
num_str.push_str(&self.read_while(|c| c.is_ascii_digit() || c == '.'));
|
||||
TokenKind::Number(num_str.parse().map_err(|_| "Invalid number format")?)
|
||||
}
|
||||
c if is_ident_start(c) => {
|
||||
let mut id = String::from(c);
|
||||
id.push_str(&self.read_while(is_ident_char));
|
||||
TokenKind::Identifier(Arc::from(id))
|
||||
}
|
||||
_ => return Err(format!("Unexpected character: {} at {}:{}", char, self.line, self.col - 1)),
|
||||
};
|
||||
|
||||
Ok(Token { kind, location: start_location })
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> Option<&char> {
|
||||
self.input.peek()
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
while let Some(&c) = self.peek() {
|
||||
if c.is_whitespace() {
|
||||
if c == '\n' {
|
||||
self.line += 1;
|
||||
self.col = 1;
|
||||
} else {
|
||||
self.col += 1;
|
||||
}
|
||||
self.input.next();
|
||||
} else if c == ';' {
|
||||
while let Some(c) = self.input.next() {
|
||||
if c == '\n' {
|
||||
self.line += 1;
|
||||
self.col = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_while<F>(&mut self, mut predicate: F) -> String
|
||||
where F: FnMut(char) -> bool {
|
||||
let mut s = String::new();
|
||||
while let Some(&c) = self.peek() {
|
||||
if predicate(c) {
|
||||
s.push(self.input.next().unwrap());
|
||||
self.col += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Result<String, String> {
|
||||
let mut s = String::new();
|
||||
while let Some(c) = self.input.next() {
|
||||
self.col += 1;
|
||||
match c {
|
||||
'"' => return Ok(s),
|
||||
'\\' => {
|
||||
let next = self.input.next().ok_or("Unterminated string escape")?;
|
||||
self.col += 1;
|
||||
match next {
|
||||
'n' => s.push('\n'),
|
||||
'r' => s.push('\r'),
|
||||
't' => s.push('\t'),
|
||||
'\\' => s.push('\\'),
|
||||
'"' => s.push('"'),
|
||||
_ => s.push(next),
|
||||
}
|
||||
}
|
||||
'\n' => {
|
||||
self.line += 1;
|
||||
self.col = 1;
|
||||
s.push(c);
|
||||
}
|
||||
_ => s.push(c),
|
||||
}
|
||||
}
|
||||
Err("Unterminated string".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ident_start(c: char) -> bool {
|
||||
c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c)
|
||||
}
|
||||
|
||||
fn is_ident_char(c: char) -> bool {
|
||||
is_ident_start(c) || c.is_ascii_digit()
|
||||
}
|
||||
Reference in New Issue
Block a user