feat: implement expression evaluation(ch.7)

baby steps :)

Will do the challenges as well
This commit is contained in:
Seymur Bagirov 2025-01-06 07:03:57 +04:00
parent 8b29a43900
commit 50a9e062e0
5 changed files with 160 additions and 17 deletions

98
src/interpreter.rs Normal file
View File

@ -0,0 +1,98 @@
use crate::{
ast::Expr,
token::{LiteralType, Token, TokenType},
};
#[derive(Debug)]
pub struct RuntimeError {
pub token: Token,
pub message: String,
}
impl RuntimeError {
pub fn new(token: &Token, message: &str) -> RuntimeError {
RuntimeError {
token: token.clone(),
message: message.to_string(),
}
}
}
pub fn interpret(expr: &Expr) -> Result<LiteralType, RuntimeError> {
match expr {
Expr::Ternary {
first,
second,
third,
..
} => ternary(first, second, third),
Expr::Binary { left, op, right } => binary(&interpret(left)?, &interpret(right)?, op),
Expr::Grouping { expression } => interpret(expression),
Expr::Literal { value } => Ok(value.clone()),
Expr::Unary { op, right } => Ok(unary(&interpret(right)?, op)),
}
}
fn ternary(first: &Expr, second: &Expr, third: &Expr) -> Result<LiteralType, RuntimeError> {
let first = interpret(first)?;
if is_truthy(&first) {
return interpret(second);
}
interpret(third)
}
fn binary(
left: &LiteralType,
right: &LiteralType,
op: &Token,
) -> Result<LiteralType, RuntimeError> {
use LiteralType::{Bool, Number, String};
use TokenType::{
BangEqual, Comma, EqualEqual, Greater, GreaterEqual, Less, LessEqual, Minus, Plus, Slash,
Star,
};
match (op.t_type, &left, &right) {
(Greater, Number(left), Number(right)) => Ok(Bool(left > right)),
(GreaterEqual, Number(left), Number(right)) => Ok(Bool(left >= right)),
(Less, Number(left), Number(right)) => Ok(Bool(left < right)),
(LessEqual, Number(left), Number(right)) => Ok(Bool(left <= right)),
(BangEqual, _, _) => Ok(Bool(!is_equal(left, right))),
(EqualEqual, _, _) => Ok(Bool(is_equal(left, right))),
(Minus, Number(left), Number(right)) => Ok(Number(left - right)),
(Plus, Number(left), Number(right)) => Ok(Number(left + right)),
(Plus, String(left), String(right)) => Ok(String(format!("{left}{right}"))),
(Slash, Number(left), Number(right)) => Ok(Number(left / right)),
(Star, Number(left), Number(right)) => Ok(Number(left * right)),
(Comma, _,_) => Ok(right.clone()),
(Greater | GreaterEqual | Less | LessEqual | Minus | Slash | Star, _, _) => Err(RuntimeError::new(op, "Operands must be numbers")),
(Plus, _, _) => Err(RuntimeError::new(op, "Operands must be two numbers or two strings")),
/* comma operator discard the left operand, so we just return the evaluation of the right operand */
_ => unreachable!("Shouldn't happen. Expr::Binary for interpret. Some case is a binary operation that wasn't matched")
}
}
fn unary(right: &LiteralType, op: &Token) -> LiteralType {
match (op.t_type, &right) {
(TokenType::Minus, LiteralType::Number(num)) => LiteralType::Number(-num),
(TokenType::Bang, _) => LiteralType::Bool(!is_truthy(right)),
_ => unreachable!("Shouldn't happen. Expr::Unary for interpret"),
}
}
fn is_truthy(literal: &LiteralType) -> bool {
match literal {
LiteralType::Nil => false,
LiteralType::Bool(val) => *val,
_ => true,
}
}
fn is_equal(left: &LiteralType, right: &LiteralType) -> bool {
match (left, right) {
(LiteralType::Nil, LiteralType::Nil) => true,
(LiteralType::Nil, _) => false,
_ => left == right,
}
}

View File

@ -1,43 +1,55 @@
use std::{ use std::{
error::Error, error::Error,
fmt::Display,
fs, fs,
io::{self, Write}, io::{self, Write},
}; };
use parser::{ParseError, Parser}; use interpreter::RuntimeError;
use printer::pretty_print; use parser::Parser;
use scanner::Scanner; use scanner::Scanner;
use token::{Token, TokenType}; use token::{Token, TokenType};
mod ast; mod ast;
mod interpreter;
mod parser; mod parser;
mod printer; mod printer;
mod scanner; mod scanner;
mod token; mod token;
mod utils; mod utils;
pub fn run_file(path: &str) -> Result<(), Box<dyn Error>> { #[derive(Debug)]
let file = fs::read_to_string(path)?; pub enum RunError {
FileReadError(io::Error),
OtherError(Box<dyn Error>), // to be added,
RuntimeError(RuntimeError),
}
impl<E: Error + 'static> From<E> for RunError {
fn from(value: E) -> Self {
Self::OtherError(Box::new(value))
}
}
pub fn run_file(path: &str) -> Result<(), RunError> {
let file = fs::read_to_string(path).map_err(RunError::FileReadError)?;
run(&file)?; run(&file)?;
Ok(()) Ok(())
} }
pub fn run(src: &str) -> Result<(), Box<dyn Error>> { pub fn run(src: &str) -> Result<(), RunError> {
let mut scanner = Scanner::new(src.to_string()); let mut scanner = Scanner::new(src.to_string());
let tokens = scanner.scan_tokens()?; let tokens = scanner.scan_tokens()?;
let mut parser = Parser::new(tokens); let mut parser = Parser::new(tokens);
let expression = parser.parse(); let expression = parser.parse().inspect_err(|e| error(&e.token, &e.msg))?;
match expression { let interpreted_value = interpreter::interpret(&expression)
Ok(expr) => println!("{}", pretty_print(&expr)), .inspect_err(runtime_error)
Err(e) => { .map_err(RunError::RuntimeError)?;
error(e.token, &e.msg);
} println!("{interpreted_value}");
}
Ok(()) Ok(())
} }
@ -64,9 +76,13 @@ pub fn report(line: usize, location: &str, message: &str) {
eprintln!("[line {line}] Error {location}: {message}"); eprintln!("[line {line}] Error {location}: {message}");
} }
fn error(token: Token, message: &str) { fn error(token: &Token, message: &str) {
match token.t_type { match token.t_type {
TokenType::EOF => report(token.line, " at end", message), TokenType::EOF => report(token.line, " at end", message),
_ => report(token.line, &format!(" at '{}'", token.lexeme), message), _ => report(token.line, &format!(" at '{}'", token.lexeme), message),
} }
} }
fn runtime_error(err: &RuntimeError) {
eprintln!("{}\n[line {}]", err.message, err.token.line);
}

View File

@ -1,6 +1,6 @@
use std::{env::args_os, ffi::OsString, process::ExitCode}; use std::{env::args_os, ffi::OsString, process::ExitCode};
use izanami::{run_file, run_prompt}; use izanami::{run_file, run_prompt, RunError};
fn main() -> ExitCode { fn main() -> ExitCode {
let args: Vec<OsString> = args_os().collect(); let args: Vec<OsString> = args_os().collect();
@ -11,10 +11,18 @@ fn main() -> ExitCode {
} else if args.len() == 2 { } else if args.len() == 2 {
let result = run_file(args[1].to_str().unwrap()); let result = run_file(args[1].to_str().unwrap());
if let Err(res) = result { if let Err(RunError::FileReadError(e)) = result {
println!("Couldn't read the file. Reason: {}", &*res); println!("Couldn't read the file. Reason: {}", e);
return ExitCode::from(1); return ExitCode::from(1);
} }
if let Err(RunError::OtherError(e)) = result {
println!("Error occured. Error: {}", e);
return ExitCode::from(75);
}
if let Err(RunError::RuntimeError(r)) = result {
return ExitCode::from(70);
}
} else { } else {
let result = run_prompt(); let result = run_prompt();

View File

@ -1,3 +1,5 @@
use std::fmt::Display;
use crate::{ use crate::{
ast::Expr, ast::Expr,
token::{LiteralType, Token, TokenType}, token::{LiteralType, Token, TokenType},
@ -14,6 +16,14 @@ pub struct ParseError {
pub msg: String, pub msg: String,
} }
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ParseError: {} {}", self.token, self.msg)
}
}
impl std::error::Error for ParseError {}
impl Parser<'_> { impl Parser<'_> {
pub fn new(tokens: &Vec<Token>) -> Parser<'_> { pub fn new(tokens: &Vec<Token>) -> Parser<'_> {
Parser { tokens, current: 0 } Parser { tokens, current: 0 }

View File

@ -68,6 +68,17 @@ impl LiteralType {
} }
} }
impl Display for LiteralType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LiteralType::String(v) => write!(f, "{v}"),
LiteralType::Number(v) => write!(f, "{v:.2}"),
LiteralType::Bool(v) => write!(f, "{v}"),
LiteralType::Nil => write!(f, "nil"),
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Token { pub struct Token {
pub t_type: TokenType, pub t_type: TokenType,