Compare commits

..

3 Commits

Author SHA1 Message Date
be15364e3b refactor: remove specification of operators for ternary operator
I don't plan on adding other ternary operators. So we just gonna assume
that we only have one ternary operator, which is ? !
2025-01-06 07:07:39 +04:00
50a9e062e0 feat: implement expression evaluation(ch.7)
baby steps :)

Will do the challenges as well
2025-01-06 07:03:57 +04:00
8b29a43900 refactor(literal): remove the Option wrapper for literal for Expr
Simplifies other aspect of the code. Having the LiteralType be wrapped
in an Option was a mistake (i was super sleepy when i was writing that
code :D)
2025-01-06 01:43:12 +04:00
7 changed files with 181 additions and 44 deletions

View File

@ -4,9 +4,7 @@ use crate::token::{LiteralType, Token};
pub enum Expr { pub enum Expr {
Ternary { Ternary {
first: Box<Expr>, first: Box<Expr>,
first_op: Token,
second: Box<Expr>, second: Box<Expr>,
second_op: Token,
third: Box<Expr>, third: Box<Expr>,
}, },
Binary { Binary {
@ -18,7 +16,7 @@ pub enum Expr {
expression: Box<Expr>, expression: Box<Expr>,
}, },
Literal { Literal {
value: Option<LiteralType>, value: LiteralType,
}, },
Unary { Unary {
op: Token, op: Token,

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 }
@ -40,15 +50,12 @@ impl Parser<'_> {
let expr = self.equality()?; let expr = self.equality()?;
if self.match_token(&[Question]) { if self.match_token(&[Question]) {
let first_op = self.previous().clone();
let second = self.expression()?; let second = self.expression()?;
let second_op = self.consume(Colon, "Expected : after ternary operator ?")?; let _ = self.consume(Colon, "Expected : after ternary operator ?")?;
let third = self.ternary()?; let third = self.ternary()?;
return Ok(Expr::Ternary { return Ok(Expr::Ternary {
first: Box::new(expr), first: Box::new(expr),
first_op,
second: Box::new(second), second: Box::new(second),
second_op,
third: Box::new(third), third: Box::new(third),
}); });
} }
@ -99,25 +106,30 @@ impl Parser<'_> {
use LiteralType::*; use LiteralType::*;
use TokenType::*; use TokenType::*;
fn create_literal(l_type: Option<LiteralType>) -> Expr { fn create_literal(l_type: LiteralType) -> Expr {
Expr::Literal { value: l_type } Expr::Literal { value: l_type }
} }
if self.match_token(&[False]) { if self.match_token(&[False]) {
return Ok(create_literal(Some(Bool(false)))); return Ok(create_literal(Bool(false)));
} }
if self.match_token(&[True]) { if self.match_token(&[True]) {
return Ok(create_literal(Some(Bool(true)))); return Ok(create_literal(Bool(true)));
} }
if self.match_token(&[TokenType::Number, TokenType::String]) { if self.match_token(&[TokenType::Number, TokenType::String]) {
return Ok(create_literal(self.previous().literal.clone())); return Ok(create_literal(
self.previous()
.literal
.clone()
.expect("The number and string token should have a literal"),
));
} }
// i included the enum name bcs of ambiguity of LiteralType and TokenType // i included the enum name bcs of ambiguity of LiteralType and TokenType
if self.match_token(&[TokenType::Nil]) { if self.match_token(&[TokenType::Nil]) {
return Ok(create_literal(Some(LiteralType::Nil))); return Ok(create_literal(LiteralType::Nil));
} }
if self.match_token(&[LeftParen]) { if self.match_token(&[LeftParen]) {

View File

@ -5,23 +5,17 @@ pub fn pretty_print(expr: &Expr) -> String {
Expr::Binary { left, op, right } => parenthesize(&op.lexeme, &[left, right]), Expr::Binary { left, op, right } => parenthesize(&op.lexeme, &[left, right]),
Expr::Grouping { expression } => parenthesize("group", &[expression]), Expr::Grouping { expression } => parenthesize("group", &[expression]),
Expr::Literal { value } => match value { Expr::Literal { value } => match value {
Some(LiteralType::String(v)) => v.to_string(), LiteralType::String(v) => v.to_string(),
Some(LiteralType::Number(v)) => v.to_string(), LiteralType::Number(v) => v.to_string(),
Some(LiteralType::Bool(v)) => v.to_string(), LiteralType::Bool(v) => v.to_string(),
Some(LiteralType::Nil) => "Nil".to_string(), LiteralType::Nil => "Nil".to_string(),
None => "None".to_string(),
}, },
Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]), Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]),
Expr::Ternary { Expr::Ternary {
first, first,
first_op,
second, second,
second_op,
third, third,
} => parenthesize( } => parenthesize("?:", &[first, second, third]),
&format!("{}{}", first_op.lexeme, second_op.lexeme),
&[first, second, third],
),
} }
} }
@ -50,7 +44,7 @@ mod test {
use TokenType::*; use TokenType::*;
let expression = Binary { let expression = Binary {
left: Box::new(Literal { left: Box::new(Literal {
value: Some(LiteralType::Number(10.2)), value: LiteralType::Number(10.2),
}), }),
op: Token { op: Token {
t_type: Plus, t_type: Plus,
@ -59,7 +53,7 @@ mod test {
line: 0, line: 0,
}, },
right: Box::new(Literal { right: Box::new(Literal {
value: Some(LiteralType::Number(10.2)), value: LiteralType::Number(10.2),
}), }),
}; };
@ -77,13 +71,13 @@ mod test {
left: Box::new(Unary { left: Box::new(Unary {
op: Token::new(Minus, "-", None, 0), op: Token::new(Minus, "-", None, 0),
right: Box::new(Expr::Literal { right: Box::new(Expr::Literal {
value: Some(LiteralType::number_literal(123.0)), value: LiteralType::number_literal(123.0),
}), }),
}), }),
op: Token::new(Star, "*", None, 0), op: Token::new(Star, "*", None, 0),
right: Box::new(Grouping { right: Box::new(Grouping {
expression: Box::new(Literal { expression: Box::new(Literal {
value: Some(LiteralType::number_literal(45.67)), value: LiteralType::number_literal(45.67),
}), }),
}), }),
}; };

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,