Formatting

This commit is contained in:
Michael Schimmel
2026-02-22 02:35:06 +01:00
parent 2123f1d279
commit 329b885c4b
25 changed files with 8053 additions and 6400 deletions
+219 -191
View File
@@ -1,191 +1,219 @@
use std::iter::Peekable;
use std::str::Chars;
use std::rc::Rc;
use crate::ast::types::SourceLocation;
#[derive(Debug, Clone, PartialEq)]
pub enum TokenKind {
LeftParen, RightParen,
LeftBracket, RightBracket,
LeftBrace, RightBrace,
Quote, Backtick, Tilde, At,
Identifier(Rc<str>),
Keyword(Rc<str>),
Integer(i64),
Float(f64),
String(Rc<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(Rc::from(id))
}
'"' => TokenKind::String(Rc::from(self.read_string()?)),
c if c.is_ascii_digit() || (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) => {
let mut num_str = String::from(c);
while let Some(&next) = self.peek() {
if next.is_ascii_digit() || next == '.' {
num_str.push(self.input.next().unwrap());
self.col += 1;
} else {
break;
}
}
if num_str.contains('.') {
TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?)
} else {
TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?)
}
}
c if is_ident_start(c) => {
let mut id = String::from(c);
id.push_str(&self.read_while(is_ident_char));
TokenKind::Identifier(Rc::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() || is_invisible(c) || c == ',' {
if c == '\n' {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
self.input.next();
} else if c == ';' {
for c in self.input.by_ref() {
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()
}
/// Returns true if the character is an invisible control character
/// that should be ignored by the lexer (like BOM or Zero Width Space).
fn is_invisible(c: char) -> bool {
match c {
'\u{FEFF}' | // BOM
'\u{200B}' | // Zero Width Space
'\u{200C}' | // Zero Width Non-Joiner
'\u{200D}' | // Zero Width Joiner
'\u{2060}' // Word Joiner
=> true,
_ => false,
}
}
use crate::ast::types::SourceLocation;
use std::iter::Peekable;
use std::rc::Rc;
use std::str::Chars;
#[derive(Debug, Clone, PartialEq)]
pub enum TokenKind {
LeftParen,
RightParen,
LeftBracket,
RightBracket,
LeftBrace,
RightBrace,
Quote,
Backtick,
Tilde,
At,
Identifier(Rc<str>),
Keyword(Rc<str>),
Integer(i64),
Float(f64),
String(Rc<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(Rc::from(id))
}
'"' => TokenKind::String(Rc::from(self.read_string()?)),
c if c.is_ascii_digit()
|| (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) =>
{
let mut num_str = String::from(c);
while let Some(&next) = self.peek() {
if next.is_ascii_digit() || next == '.' {
num_str.push(self.input.next().unwrap());
self.col += 1;
} else {
break;
}
}
if num_str.contains('.') {
TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?)
} else {
TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?)
}
}
c if is_ident_start(c) => {
let mut id = String::from(c);
id.push_str(&self.read_while(is_ident_char));
TokenKind::Identifier(Rc::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() || is_invisible(c) || c == ',' {
if c == '\n' {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
self.input.next();
} else if c == ';' {
for c in self.input.by_ref() {
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()
}
/// Returns true if the character is an invisible control character
/// that should be ignored by the lexer (like BOM or Zero Width Space).
fn is_invisible(c: char) -> bool {
match c {
'\u{FEFF}' | // BOM
'\u{200B}' | // Zero Width Space
'\u{200C}' | // Zero Width Non-Joiner
'\u{200D}' | // Zero Width Joiner
'\u{2060}' // Word Joiner
=> true,
_ => false,
}
}