Lox interpreter in rust
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

255 lines
5.0 KiB

use crate::tokens::TokenType::{
And, Bang, BangEqual, Comma, Dot, Equal, EqualEqual, Greater, GreaterEqual, Identifier,
LeftBrace, LeftParen, Less, LessEqual, Minus, NoToken, Number, Plus, RightBrace, RightParen,
Semicolon, Slash, Star,
};
use crate::tokens::{Token, TokenType};
use phf::phf_map;
use std::str::FromStr;
static KEYWORDS: phf::Map<&'static str, TokenType> = phf_map! {
"and" => TokenType::And,
"class" => TokenType::Class,
"else" => TokenType::Else,
"false" => TokenType::False,
"for" => TokenType::For,
"fun" => TokenType::Fun,
"if" => TokenType::If,
"nil" => TokenType::Nil,
"or" => TokenType::Or,
"print" => TokenType::Print,
"return" => TokenType::Return,
"super" => TokenType::Super,
"this" => TokenType::This,
"true" => TokenType::True,
"var" => TokenType::Var,
"while" => TokenType::While
};
struct Scanner {
source: String,
tokens: Vec<Token>,
start: usize,
current: usize,
line: usize,
}
impl Scanner {
pub fn new(source: String) -> Self {
Self {
source,
tokens: Vec::new(),
start: 0,
current: 0,
line: 1,
}
}
pub fn scan_tokens(&mut self) -> Vec<Token> {
while !self.is_at_end() {
self.start = self.current;
self.scan_token();
}
self.tokens
.push(Token::new(TokenType::Eof, "".to_string(), self.line));
self.tokens.clone()
}
fn scan_token(&mut self) -> Result<(), (usize, String)> {
let c = self.advance();
let token = match c {
'(' => LeftParen,
')' => RightParen,
'{' => LeftBrace,
'}' => RightBrace,
',' => Comma,
'.' => Dot,
'-' => Minus,
'+' => Plus,
';' => Semicolon,
'*' => Star,
'!' => {
if self.match_next('=') {
BangEqual
} else {
Bang
}
}
'=' => {
if self.match_next('=') {
EqualEqual
} else {
Equal
}
}
'<' => {
if self.match_next('=') {
LessEqual
} else {
Less
}
}
'>' => {
if self.match_next('=') {
GreaterEqual
} else {
Greater
}
}
'/' => {
if self.match_next('/') {
// Handle comments
while self.peek() != '\n' && !self.is_at_end() {
self.advance();
}
NoToken
} else {
Slash
}
}
' ' | '\r' | '\t' => NoToken, // Whitespace
'\n' => {
self.line += 1;
NoToken
}
'"' => {
self.string();
NoToken
}
'0'..='9' => {
self.number();
NoToken
}
_ => {
if Scanner::is_alpha(c) {
self.identifier();
NoToken
} else {
return Err((self.line, "Unexpected character.".to_string()));
}
}
};
self.add_token(token);
Ok(())
}
fn is_at_end(&self) -> bool {
self.current >= self.source.len()
}
fn advance(&mut self) -> char {
self.current += 1;
self.source.chars().collect::<Vec<char>>()[self.current - 1]
}
fn add_token(&mut self, token_type: TokenType) {
if token_type == NoToken {
return;
}
let text: String = self.source.chars().collect::<Vec<char>>()[self.start..self.current]
.iter()
.collect();
self.tokens.push(Token::new(token_type, text, self.line));
}
fn match_next(&mut self, expected: char) -> bool {
if self.is_at_end() {
return false;
}
if self.source.chars().collect::<Vec<char>>()[self.current] != expected {
return false;
}
self.current += 1;
true
}
fn peek(&self) -> char {
if self.is_at_end() {
return '\0';
}
self.chars()[self.current]
}
fn peek_next(&self) -> char {
if self.current + 1 >= self.source.chars().count() {
return '\0';
}
self.chars()[self.current + 1]
}
fn string(&mut self) -> Result<(), (usize, String)> {
while self.peek() != '"' && !self.is_at_end() {
if self.peek() == '\n' {
self.line += 1;
}
self.advance();
}
if self.is_at_end() {
// Unterminated string
return Err((self.line, "Unterminated string.".to_string()));
}
self.advance(); // Closing "
let value: String = self.chars()[(self.start + 1)..(self.current - 1)]
.iter()
.collect();
self.add_token(TokenType::String(value));
Ok(())
}
fn chars(&self) -> Vec<char> {
self.source.chars().collect()
}
fn number(&mut self) {
while Scanner::is_digit(self.peek()) {}
if self.peek() == '.' && Scanner::is_digit(self.peek_next()) {
self.advance(); // Consume "."
while Scanner::is_digit(self.peek()) {} // Keep consuming digits
}
let s: String = self.chars()[(self.start + 1)..(self.current - 1)]
.iter()
.collect();
self.add_token(Number(f64::from_str(&s).unwrap()))
}
fn identifier(&mut self) {
while Scanner::is_alphanumeric(self.peek()) {
self.advance();
}
let text: String = self.chars()[self.start..self.current].iter().collect();
let token: TokenType = match KEYWORDS.get(text.as_ref()) {
Some(x) => x.clone(),
None => Identifier,
};
self.add_token(token);
}
// Struct methods
fn is_digit(c: char) -> bool {
('0'..='9').contains(&c)
}
fn is_alpha(c: char) -> bool {
('a'..='z').contains(&c) || ('A'..'Z').contains(&c) || c == '_'
}
fn is_alphanumeric(c: char) -> bool {
Scanner::is_digit(c) || Scanner::is_alpha(c)
}
}