use std::fmt::{Display, Formatter};
use crate::{location::Location, Lox};
#[derive(Clone, Debug, PartialEq)]
pub enum TokenType {
LeftParen,
RightParen,
LeftBrace,
RightBrace,
Comma,
Dot,
Minus,
Plus,
Semicolon,
Slash,
Star,
Tilde,
Bang,
BangEqual,
Equal,
EqualEqual,
Greater,
GreaterEqual,
Less,
LessEqual,
Identifier,
Literal,
And,
Class,
Else,
Fun,
For,
If,
Or,
Print,
Return,
Super,
This,
Var,
While,
Eof,
}
pub const BINARY_OPERATORS: [TokenType; 12] = [
TokenType::Plus,
TokenType::Minus,
TokenType::Star,
TokenType::Slash,
TokenType::And,
TokenType::Or,
TokenType::Greater,
TokenType::GreaterEqual,
TokenType::Less,
TokenType::LessEqual,
TokenType::EqualEqual,
TokenType::BangEqual,
];
pub const UNARY_OPERATORS: [TokenType; 2] = [TokenType::Minus, TokenType::Bang];
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Literal {
String(String),
Number(f64),
Boolean(bool),
Nil,
}
impl Display for Literal {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Literal::String(s) => write!(f, "\"{}\"", s),
Literal::Number(n) => write!(f, "{}", n),
Literal::Boolean(b) => write!(f, "{}", b),
Literal::Nil => write!(f, "nil"),
}
}
}
impl Literal {
pub fn as_string(&self) -> String {
match self {
Literal::String(s) => s.clone(),
_ => panic!("Not a string"),
}
}
pub fn as_number(&self) -> f64 {
match self {
Literal::Number(n) => *n,
_ => panic!("Not a number"),
}
}
pub fn as_boolean(&self) -> bool {
match self {
Literal::Boolean(b) => *b,
_ => panic!("Not a boolean"),
}
}
}
#[derive(Clone, Debug)]
pub struct Token {
pub token_type: TokenType,
pub lexeme: String,
pub location: Location,
pub literal: Option<Literal>,
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(literal) = &self.literal {
write!(f, "{:?} {} {}", self.token_type, self.lexeme, literal)
} else {
write!(f, "{:?} {}", self.token_type, self.lexeme)
}
}
}
impl Token {
pub fn new(token_type: TokenType, lexeme: &str, line: usize, column: usize) -> Self {
Self {
token_type,
lexeme: lexeme.to_string(),
location: Location { line, column },
literal: None,
}
}
pub fn get_token_type(&self) -> TokenType {
self.token_type.clone()
}
pub fn new_with_literal(
token_type: TokenType,
lexeme: &str,
line: usize,
column: usize,
literal: Literal,
) -> Self {
Self {
token_type,
lexeme: lexeme.to_string(),
location: Location { line, column },
literal: Some(literal),
}
}
}
#[derive(Debug)]
pub struct Lexer<'a> {
lox: &'a mut Lox,
source: String,
start: usize,
current: usize,
line: usize,
column: usize,
tokens: Vec<Token>,
}
impl<'a> Lexer<'a> {
pub fn new(lox: &'a mut Lox, source: &str) -> Self {
Self {
lox,
source: source.to_string(),
start: 0,
current: 0,
line: 1,
column: 0,
tokens: Vec::new(),
}
}
fn is_at_end(&self) -> bool {
self.current >= self.source.len()
}
fn advance(&mut self) -> char {
let c = self.source.chars().nth(self.current).unwrap();
self.current += 1;
self.column += 1;
c
}
fn add_token(&mut self, token_type: TokenType) {
self.internal_add_token(token_type, None);
}
fn add_token_with_literal(&mut self, token_type: TokenType, literal: Literal) {
self.internal_add_token(token_type, Some(literal))
}
fn internal_add_token(&mut self, token_type: TokenType, literal: Option<Literal>) {
let text = &self.source[self.start..self.current];
self.tokens.push(Token {
token_type,
lexeme: text.to_string(),
location: Location {
line: self.line,
column: self.column,
},
literal,
});
}
fn match_next(&mut self, expected: char) -> bool {
if self.is_at_end() {
return false;
}
if self.source.chars().nth(self.current).unwrap() != expected {
return false;
}
self.current += 1;
self.column += 1;
true
}
fn peek(&self) -> char {
if self.is_at_end() {
return '\0';
}
self.source.chars().nth(self.current).unwrap()
}
fn peek_next(&self) -> char {
if self.current + 1 >= self.source.len() {
return '\0';
}
self.source.chars().nth(self.current + 1).unwrap()
}
fn string(&mut self) {
while self.peek() != '"' && !self.is_at_end() {
if self.peek() == '\n' {
self.line += 1;
self.column = 0;
}
self.advance();
}
if self.is_at_end() {
self.lox.error(self.line, "Unterminated string.");
return;
}
self.advance();
let value = self.source[self.start + 1..self.current - 1].to_string();
self.add_token_with_literal(TokenType::Literal, Literal::String(value));
}
fn number(&mut self) {
while self.peek().is_digit(10) {
self.advance();
}
if self.peek() == '.' && self.peek_next().is_digit(10) {
self.advance();
while self.peek().is_digit(10) {
self.advance();
}
}
let value = self.source[self.start..self.current].parse().unwrap();
self.add_token_with_literal(TokenType::Literal, Literal::Number(value));
}
fn identifier(&mut self) {
let column = self.column;
while self.peek().is_alphanumeric() {
self.advance();
}
let text = &self.source[self.start..self.current];
let mut literal = None;
let token_type = match text {
"false" | "true" => {
literal = Some(Literal::Boolean(text == "true"));
TokenType::Literal
}
"nil" => {
literal = Some(Literal::Nil);
TokenType::Literal
}
"and" => TokenType::And,
"class" => TokenType::Class,
"else" => TokenType::Else,
"for" => TokenType::For,
"fun" => TokenType::Fun,
"if" => TokenType::If,
"or" => TokenType::Or,
"print" => TokenType::Print,
"return" => TokenType::Return,
"super" => TokenType::Super,
"this" => TokenType::This,
"var" => TokenType::Var,
"while" => TokenType::While,
_ => TokenType::Identifier,
};
let next_column = self.column;
self.column = column;
if let Some(literal) = literal {
self.add_token_with_literal(token_type, literal);
} else {
self.add_token(token_type);
}
self.column = next_column;
}
fn scan_token(&mut self) {
let c = self.advance();
match c {
'(' => self.add_token(TokenType::LeftParen),
')' => self.add_token(TokenType::RightParen),
'{' => self.add_token(TokenType::LeftBrace),
'}' => self.add_token(TokenType::RightBrace),
',' => self.add_token(TokenType::Comma),
'.' => self.add_token(TokenType::Dot),
'-' => self.add_token(TokenType::Minus),
'+' => self.add_token(TokenType::Plus),
';' => self.add_token(TokenType::Semicolon),
'*' => self.add_token(TokenType::Star),
'~' => self.add_token(TokenType::Tilde),
'!' => {
let token_type = if self.match_next('=') {
TokenType::BangEqual
} else {
TokenType::Bang
};
self.add_token(token_type);
}
'=' => {
let token_type = if self.match_next('=') {
TokenType::EqualEqual
} else {
TokenType::Equal
};
self.add_token(token_type);
}
'<' => {
let token_type = if self.match_next('=') {
TokenType::LessEqual
} else {
TokenType::Less
};
self.add_token(token_type);
}
'>' => {
let token_type = if self.match_next('=') {
TokenType::GreaterEqual
} else {
TokenType::Greater
};
self.add_token(token_type);
}
'/' => {
if self.match_next('/') {
while self.peek() != '\n' && !self.is_at_end() {
self.advance();
}
} else {
self.add_token(TokenType::Slash);
}
}
'\n' => {
self.line += 1;
self.column = 0;
}
'"' => {
self.string();
}
c if c.is_whitespace() => {
}
c if c.is_digit(10) => {
self.number();
}
c if c.is_alphabetic() => {
self.identifier();
}
c => {
self.lox
.error(self.line, &format!("Unexpected character: {}", c));
}
}
}
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, "", self.line, self.column));
self.tokens.clone()
}
}