Files
rlox/src/frontend/lexer.rs
T

324 lines
11 KiB
Rust
Raw Normal View History

use crate::common::base_value::{BaseValue, Number};
use crate::common::lox_result::{lexical_error, LoxError, LoxResult};
use crate::frontend::source_registry::{SourceId, SourcePosition, SourceSlice};
use crate::frontend::tokens::{Token, TokenType};
pub struct Lexer {
input: String,
start_char: usize,
current_char: usize,
start_pos: SourcePosition,
end_pos: SourcePosition,
source_id: SourceId,
}
fn get_keyword_token(word: &str) -> Option<TokenType> {
match word {
"and" => Some(TokenType::And),
"class" => Some(TokenType::Class),
"do" => Some(TokenType::StartBlock),
"end" => Some(TokenType::EndBlock),
"false" => Some(TokenType::False),
"for" => Some(TokenType::For),
"fun" => Some(TokenType::Fun),
"if" => Some(TokenType::If),
2025-10-04 19:02:33 +02:00
"then" => Some(TokenType::Then),
"elif" => Some(TokenType::Elif),
"else" => Some(TokenType::Else),
"or" => Some(TokenType::Or),
"in" => Some(TokenType::In),
"print" => Some(TokenType::Print),
"return" => Some(TokenType::Return),
"super" => Some(TokenType::Super),
"this" => Some(TokenType::This),
"true" => Some(TokenType::True),
"var" => Some(TokenType::Var),
"val" => Some(TokenType::Val),
"while" => Some(TokenType::While),
"True" => Some(TokenType::True),
"False" => Some(TokenType::False),
"Nil" => Some(TokenType::Nil),
2025-10-06 18:52:32 +02:00
"fn" => Some(TokenType::Fn),
_ => None,
}
}
impl Lexer {
pub fn new(input: String, source_id: SourceId) -> Lexer {
Lexer {
input,
start_char: 0,
current_char: 0,
start_pos: SourcePosition::default(),
end_pos: SourcePosition::default(),
source_id,
}
}
fn advance_column(&mut self) {
self.current_char += 1;
self.end_pos.column += 1;
}
fn advance_line(&mut self) {
self.end_pos.line += 1;
self.end_pos.column = 0;
}
pub fn scans_tokens(&mut self) -> LoxResult<Vec<Token>> {
let mut tokens = Vec::new();
while !self.is_at_end() {
self.start_char = self.current_char;
self.start_pos = self.end_pos.clone();
match self.scan_token() {
Ok(Some(token)) => tokens.push(token),
Ok(None) => {}
Err(err) => return Err(err),
}
}
tokens.push(self.make_token(TokenType::Eof));
Ok(tokens)
}
fn is_at_end(&self) -> bool {
self.current_char >= self.input.len()
}
fn advance(&mut self) -> char {
self.advance_column();
self.input.chars().nth(self.current_char - 1).unwrap()
}
fn peek(&self) -> char {
if self.is_at_end() {
'\0'
} else {
self.input.chars().nth(self.current_char).unwrap()
}
}
fn peek_next(&self) -> char {
if self.current_char + 1 >= self.input.len() {
'\0'
} else {
self.input.chars().nth(self.current_char + 1).unwrap()
}
}
fn make_token(&self, token_type: TokenType) -> Token {
let text = self.input[self.start_char..self.current_char].to_string();
Token::new(
token_type,
text,
SourceSlice::from_positions(
self.source_id.clone(),
self.start_pos.clone(),
self.end_pos.clone(),
),
)
}
fn make_token_with_literal(&self, token_type: TokenType, literal: BaseValue) -> Token {
let text = self.input[self.start_char..self.current_char].to_string();
Token::new_complete(
token_type,
text,
Some(literal),
SourceSlice::from_positions(
self.source_id.clone(),
self.start_pos.clone(),
self.end_pos.clone(),
),
)
}
fn scan_token(&mut self) -> LoxResult<Option<Token>> {
let c = self.advance();
match (c, self.peek()) {
('(', _) => Ok(Some(self.make_token(TokenType::LeftParen))),
(')', _) => Ok(Some(self.make_token(TokenType::RightParen))),
('{', _) => Ok(Some(self.make_token(TokenType::LeftBrace))),
('}', _) => Ok(Some(self.make_token(TokenType::RightBrace))),
('[', _) => Ok(Some(self.make_token(TokenType::LeftBracket))),
(']', _) => Ok(Some(self.make_token(TokenType::RightBracket))),
(',', _) => Ok(Some(self.make_token(TokenType::Comma))),
('.', _) => Ok(Some(self.make_token(TokenType::Dot))),
('-', _) => Ok(Some(self.make_token(TokenType::Minus))),
('+', _) => Ok(Some(self.make_token(TokenType::Plus))),
(';', _) => Ok(Some(self.make_token(TokenType::Semicolon))),
2025-10-06 18:52:32 +02:00
(':', _) => Ok(Some(self.make_token(TokenType::Colon))),
('*', _) => Ok(Some(self.make_token(TokenType::Star))),
('%', _) => Ok(Some(self.make_token(TokenType::Percent))),
('!', '=') => {
self.advance(); // consuma il '='
Ok(Some(self.make_token(TokenType::BangEqual)))
}
('!', _) => Ok(Some(self.make_token(TokenType::Bang))),
('=', '=') => {
self.advance(); // consuma il '='
Ok(Some(self.make_token(TokenType::EqualEqual)))
}
('=', _) => Ok(Some(self.make_token(TokenType::Equal))),
('<', '=') => {
self.advance(); // consuma il '='
Ok(Some(self.make_token(TokenType::LessEqual)))
}
('<', _) => Ok(Some(self.make_token(TokenType::Less))),
('>', '=') => {
self.advance(); // consuma il '='
Ok(Some(self.make_token(TokenType::GreaterEqual)))
}
('>', _) => Ok(Some(self.make_token(TokenType::Greater))),
('/', '/') => {
// Commento single-line
while self.peek() != '\n' && !self.is_at_end() {
self.advance();
}
Ok(None)
}
('/', '*') => {
// Commento multi-line
while (self.peek() != '*' || self.peek_next() != '/') && !self.is_at_end() {
if self.peek() == '\n' {
self.advance_line();
}
self.advance();
}
if self.is_at_end() {
return lexical_error(
SourceSlice::from_positions(
self.source_id.clone(),
self.start_pos.clone(),
self.end_pos.clone(),
),
"Unterminated comment".to_string(),
);
} else {
self.advance(); // consuma '*'
self.advance(); // consuma '/'
}
Ok(None)
}
('/', _) => Ok(Some(self.make_token(TokenType::Slash))),
(' ', _) | ('\r', _) | ('\t', _) => Ok(None),
('\n', _) => {
self.advance_line();
Ok(None)
}
('"', _) => self.string(),
(c, _) if c.is_digit(10) => self.number(),
(c, _) if c.is_alphanumeric() || c == '_' => self.identifier(),
_ => Err(LoxError::LexicalError {
source_slice: SourceSlice::from_positions(
self.source_id.clone(),
self.start_pos.clone(),
self.end_pos.clone(),
),
message: format!("Unexpected character: {}", c),
}),
}
}
fn string(&mut self) -> LoxResult<Option<Token>> {
while self.peek() != '"' && !self.is_at_end() {
self.advance();
}
if self.is_at_end() {
return Err(LoxError::LexicalError {
source_slice: SourceSlice::from_positions(
self.source_id.clone(),
self.start_pos.clone(),
self.end_pos.clone(),
),
message: "Unterminated string".to_string(),
});
}
self.advance();
Ok(Some(self.make_token_with_literal(
TokenType::String,
BaseValue::String(self.input[self.start_char..self.current_char].to_string()),
)))
}
fn number(&mut self) -> LoxResult<Option<Token>> {
// Leggi la parte intera
while self.peek().is_digit(10) {
self.advance();
}
// Controlla se c'è una parte decimale
let has_decimal = if self.peek() == '.' && self.peek_next().is_digit(10) {
self.advance(); // consuma il '.'
while self.peek().is_digit(10) {
self.advance();
}
true
} else {
false
};
// Controlla se c'è un suffisso (f o u)
let suffix = if self.peek() == 'f' || self.peek() == 'u' {
let s = self.peek();
self.advance();
Some(s)
} else {
None
};
let num_str = &self.input[self.start_char..self.current_char];
let num_str_without_suffix = if suffix.is_some() {
&num_str[..num_str.len() - 1]
} else {
num_str
};
// Determina il tipo di numero basandosi sul contenuto e suffisso
let number_value = match suffix {
Some('f') => {
// Float esplicito con suffisso 'f'
Number::F64(num_str_without_suffix.parse().unwrap())
}
Some('u') => {
// Unsigned esplicito con suffisso 'u'
Number::U128(num_str_without_suffix.parse().unwrap())
}
_ if has_decimal => {
// Float implicito (contiene un punto decimale)
Number::F64(num_str.parse().unwrap())
}
_ => {
// Intero con segno di default
Number::I32(num_str.parse().unwrap())
}
};
Ok(Some(self.make_token_with_literal(
TokenType::Number,
BaseValue::Number(number_value),
)))
}
fn identifier(&mut self) -> LoxResult<Option<Token>> {
2025-10-06 18:52:32 +02:00
while self.peek().is_alphanumeric() || self.peek() == '_' {
self.advance();
}
let text = self.input[self.start_char..self.current_char].to_string();
match get_keyword_token(&text) {
Some(TokenType::True) => Ok(Some(
self.make_token_with_literal(TokenType::True, BaseValue::Boolean(true)),
)),
Some(TokenType::False) => Ok(Some(
self.make_token_with_literal(TokenType::False, BaseValue::Boolean(false)),
)),
Some(TokenType::Nil) => Ok(Some(
self.make_token_with_literal(TokenType::Nil, BaseValue::Nil),
)),
Some(token_type) => Ok(Some(self.make_token(token_type))),
None => Ok(Some(self.make_token_with_literal(
TokenType::Identifier,
BaseValue::String(text.clone()),
))),
}
}
}