From 50a9e062e0ceea0eb6e2f3fde2b2ee7c2c75d5d3 Mon Sep 17 00:00:00 2001 From: Seymur Bagirov Date: Mon, 6 Jan 2025 07:03:57 +0400 Subject: [PATCH] feat: implement expression evaluation(ch.7) baby steps :) Will do the challenges as well --- src/interpreter.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 44 ++++++++++++++------- src/main.rs | 14 +++++-- src/parser.rs | 10 +++++ src/token.rs | 11 ++++++ 5 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 src/interpreter.rs diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000..ddffbb5 --- /dev/null +++ b/src/interpreter.rs @@ -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 { + 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 { + let first = interpret(first)?; + if is_truthy(&first) { + return interpret(second); + } + interpret(third) +} + +fn binary( + left: &LiteralType, + right: &LiteralType, + op: &Token, +) -> Result { + 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, + } +} diff --git a/src/lib.rs b/src/lib.rs index 54ac438..20220c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,43 +1,55 @@ use std::{ error::Error, - fmt::Display, fs, io::{self, Write}, }; -use parser::{ParseError, Parser}; -use printer::pretty_print; +use interpreter::RuntimeError; +use parser::Parser; use scanner::Scanner; use token::{Token, TokenType}; mod ast; +mod interpreter; mod parser; mod printer; mod scanner; mod token; mod utils; -pub fn run_file(path: &str) -> Result<(), Box> { - let file = fs::read_to_string(path)?; +#[derive(Debug)] +pub enum RunError { + FileReadError(io::Error), + OtherError(Box), // to be added, + RuntimeError(RuntimeError), +} + +impl From 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)?; Ok(()) } -pub fn run(src: &str) -> Result<(), Box> { +pub fn run(src: &str) -> Result<(), RunError> { let mut scanner = Scanner::new(src.to_string()); let tokens = scanner.scan_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 { - Ok(expr) => println!("{}", pretty_print(&expr)), - Err(e) => { - error(e.token, &e.msg); - } - } + let interpreted_value = interpreter::interpret(&expression) + .inspect_err(runtime_error) + .map_err(RunError::RuntimeError)?; + + println!("{interpreted_value}"); Ok(()) } @@ -64,9 +76,13 @@ pub fn report(line: usize, location: &str, message: &str) { eprintln!("[line {line}] Error {location}: {message}"); } -fn error(token: Token, message: &str) { +fn error(token: &Token, message: &str) { match token.t_type { TokenType::EOF => report(token.line, " at end", message), _ => report(token.line, &format!(" at '{}'", token.lexeme), message), } } + +fn runtime_error(err: &RuntimeError) { + eprintln!("{}\n[line {}]", err.message, err.token.line); +} diff --git a/src/main.rs b/src/main.rs index 5422fa7..a291031 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ 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 { let args: Vec = args_os().collect(); @@ -11,10 +11,18 @@ fn main() -> ExitCode { } else if args.len() == 2 { let result = run_file(args[1].to_str().unwrap()); - if let Err(res) = result { - println!("Couldn't read the file. Reason: {}", &*res); + if let Err(RunError::FileReadError(e)) = result { + println!("Couldn't read the file. Reason: {}", e); 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 { let result = run_prompt(); diff --git a/src/parser.rs b/src/parser.rs index 7a016f6..f4b6ce2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use crate::{ ast::Expr, token::{LiteralType, Token, TokenType}, @@ -14,6 +16,14 @@ pub struct ParseError { 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<'_> { pub fn new(tokens: &Vec) -> Parser<'_> { Parser { tokens, current: 0 } diff --git a/src/token.rs b/src/token.rs index c674f81..5c2cae9 100644 --- a/src/token.rs +++ b/src/token.rs @@ -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)] pub struct Token { pub t_type: TokenType,