From 124218cfd8ec394e01a449377b7169e08871f716 Mon Sep 17 00:00:00 2001 From: Devon Tingley Date: Fri, 10 Mar 2023 09:02:48 -0500 Subject: [PATCH] Chapter 3 No idea if garbage collection is needed since it kinda mimics Rust's lifetime system but /shrugs/ probably not what the monkey spec recommends? idk, there aren't any pointers so I think it'd be fine (for now, at least...) --- Cargo.lock | 7 ++ Cargo.toml | 1 + examples/basic.ms | 1 + src/evaluator/error.rs | 26 ++++ src/evaluator/mod.rs | 265 +++++++++++++++++++++++++++++++++++++++ src/evaluator/object.rs | 35 ++++++ src/lexer/mod.rs | 18 +-- src/lexer/tokens.rs | 6 - src/main.rs | 55 ++++++-- src/parser/ast.rs | 28 ++--- src/parser/mod.rs | 83 ++++++------ src/parser/precedence.rs | 2 - 12 files changed, 440 insertions(+), 87 deletions(-) create mode 100644 src/evaluator/error.rs create mode 100644 src/evaluator/mod.rs create mode 100644 src/evaluator/object.rs diff --git a/Cargo.lock b/Cargo.lock index 9ff7ee4..10eaaec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + [[package]] name = "autocfg" version = "1.1.0" @@ -235,6 +241,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" name = "moose" version = "0.1.0" dependencies = [ + "anyhow", "clap", "rstest", ] diff --git a/Cargo.toml b/Cargo.toml index b5d2c76..b982d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ description = "Oxidized interpretor for the monkey programming language" edition = "2021" [dependencies] +anyhow = "1.0.69" clap = { version = "4.1.8", features = ["derive"] } [dev-dependencies] diff --git a/examples/basic.ms b/examples/basic.ms index 00f69d4..455b031 100644 --- a/examples/basic.ms +++ b/examples/basic.ms @@ -1 +1,2 @@ let x = 5 + 6 + 7; +x diff --git a/src/evaluator/error.rs b/src/evaluator/error.rs new file mode 100644 index 0000000..033dbaa --- /dev/null +++ b/src/evaluator/error.rs @@ -0,0 +1,26 @@ +use std::fmt::Display; + +use super::object::Object; + +#[derive(Debug)] +pub enum EvalError { + TypeError, + UndefinedVariable(String), + + // This is not actually an error, but we will abuse it's position in being propegated to + // return a value early. + Return(Object), +} + +impl Display for EvalError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + EvalError::TypeError => write!(f, "improper type"), + EvalError::UndefinedVariable(ident) => write!(f, "undefined variable: {}", ident), + + EvalError::Return(val) => write!(f, "return: {}", val), + } + } +} + +impl std::error::Error for EvalError {} diff --git a/src/evaluator/mod.rs b/src/evaluator/mod.rs new file mode 100644 index 0000000..e782e20 --- /dev/null +++ b/src/evaluator/mod.rs @@ -0,0 +1,265 @@ +use std::collections::HashMap; + +use crate::parser::{Expression, Node, Program}; + +mod error; +mod object; + +use error::EvalError; +use object::Object; + +pub type Environment = HashMap; + +pub fn evaluate(ast: Program, env: &mut Environment) -> Result { + let mut result = Object::Null; + + for statement in ast.program() { + match eval_statement(statement, env) { + Ok(res) => result = res, + Err(EvalError::Return(res)) => return Ok(res), + Err(err) => return Err(err), + }; + } + + Ok(result) +} + +fn eval_statement(statement: Node, env: &mut Environment) -> Result { + match statement { + Node::Let(ident, expr) => { + let value = eval_expression(expr, env)?; + env.insert(ident, value); + Ok(Object::Null) + } + + Node::Return(expr) => { + let res = eval_expression(expr, env)?; + return Err(EvalError::Return(res)); + } + + Node::Block(statements) => { + let mut result = Object::Null; + for statement in statements { + result = eval_statement(statement, env)?; + } + + Ok(result) + } + + Node::Expression(expr) => eval_expression(expr, env), + } +} + +fn eval_expression(expr: Expression, env: &mut Environment) -> Result { + match expr { + Expression::Identifier(ident) => { + // TODO: Is this necessary? Is there a better way than cloning? + env.get(&ident) + .ok_or(EvalError::UndefinedVariable(ident)) + .cloned() + } + + // Literals + Expression::Integer(val) => Ok(Object::Integer(val)), + Expression::Bool(val) => Ok(Object::Boolean(val)), + + // Prefix Operators + Expression::Negative(expr) => match eval_expression(*expr, env)? { + Object::Integer(val) => Ok(Object::Integer(-val)), + _ => Err(EvalError::TypeError), + }, + Expression::Not(expr) => match eval_expression(*expr, env)? { + Object::Boolean(val) => Ok(Object::Boolean(!val)), + _ => Err(EvalError::TypeError), + }, + + // Infix Numeric Operators + Expression::Add(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs + rhs)), + _ => Err(EvalError::TypeError), + } + } + Expression::Subtract(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs - rhs)), + _ => Err(EvalError::TypeError), + } + } + Expression::Multiply(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs * rhs)), + _ => Err(EvalError::TypeError), + } + } + Expression::Divide(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs / rhs)), + _ => Err(EvalError::TypeError), + } + } + + // Infix Boolean Operators + Expression::Equal(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Boolean(lhs), Object::Boolean(rhs)) => Ok(Object::Boolean(lhs == rhs)), + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs == rhs)), + _ => Err(EvalError::TypeError), + } + } + Expression::NotEqual(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Boolean(lhs), Object::Boolean(rhs)) => Ok(Object::Boolean(lhs != rhs)), + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs != rhs)), + _ => Err(EvalError::TypeError), + } + } + Expression::LessThan(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs < rhs)), + _ => Err(EvalError::TypeError), + } + } + Expression::GreaterThan(lhs, rhs) => { + match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) { + (Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs > rhs)), + _ => Err(EvalError::TypeError), + } + } + + Expression::If { + condition, + consequence, + alternative, + } => { + let condition = match eval_expression(*condition, env)? { + Object::Boolean(val) => val, + _ => return Err(EvalError::TypeError), + }; + + if condition { + eval_statement(*consequence, env) + } else if let Some(alt) = alternative { + eval_statement(*alt, env) + } else { + Ok(Object::Null) + } + } + + Expression::Function { parameters, body } => Ok(Object::Function { parameters, body }), + + Expression::Call { function, args } => { + let (params, body) = match eval_expression(*function, env)? { + Object::Function { parameters, body } => (parameters, body), + _ => return Err(EvalError::TypeError), + }; + + let mut func_env = env.clone(); + + args.into_iter() + .map(|arg| eval_expression(arg, env)) + .zip(params.iter()) + .try_for_each(|(arg, ident)| -> Result<(), EvalError> { + func_env.insert(ident.to_owned(), arg?); + Ok(()) + })?; + + eval_statement(*body, &mut func_env) + } + } +} + +#[cfg(test)] +mod tests { + use crate::lexer::tokenize; + use crate::parser::parse; + + use super::*; + use rstest::rstest; + + // General test function used for evaluation + fn test(input: &str, expected: &str) { + let tokens = tokenize(input).unwrap(); + let ast = parse(tokens).unwrap(); + let mut env: Environment = HashMap::new(); + let res = evaluate(ast, &mut env).unwrap(); + assert_eq!(&res.to_string(), expected) + } + + #[rstest] + #[case("5", "5")] + #[case("true", "true")] + #[case("-5", "-5")] + fn literal(#[case] input: &str, #[case] expected: &str) { + test(input, expected) + } + + #[rstest] + #[case("--5", "5")] + #[case("!true", "false")] + #[case("!!true", "true")] + fn prefix_expression(#[case] input: &str, #[case] expected: &str) { + test(input, expected) + } + + #[rstest] + #[case("2+2", "4")] + #[case("2-2", "0")] + #[case("2*3", "6")] + #[case("2/2", "1")] + // Booleans + #[case("true == true", "true")] + #[case("true == false", "false")] + #[case("5 == 5", "true")] + #[case("5 == 6", "false")] + #[case("true != false", "true")] + #[case("true != true", "false")] + #[case("5 != 5", "false")] + #[case("5 != 6", "true")] + #[case("1 < 2", "true")] + #[case("3 < 2", "false")] + #[case("1 > 2", "false")] + #[case("3 > 2", "true")] + // Complex + #[case("(3 + 4) * 32 > 16 == 3 < 17 + 90/2", "true")] + fn infix_expression(#[case] input: &str, #[case] expected: &str) { + test(input, expected) + } + + #[rstest] + #[case("if true { 2+2 }", "4")] + #[case("if false { 2+2 }", "null")] + #[case("if 4 > 2 { 2 + 3 } else { 2 + 2 }", "5")] + #[case("if 4 < 2 { 2 + 3 } else { 2 + 2 }", "4")] + #[should_panic] // Test that only boolean expressions are valid conditions + #[case("if 4 { 2 + 3 } else { 2 + 2 }", "4")] + fn if_expression(#[case] input: &str, #[case] expected: &str) { + test(input, expected) + } + + #[rstest] + #[case("return 2 + 2;", "4")] + #[case("return 2 + 2; 2 + 3", "4")] + #[case("if true { return 1; 2 + 2 };", "1")] + #[case("if true { return 1 }; return 2", "1")] + #[case("if true { if true { return 1 }; return 2 }", "1")] + fn return_statement(#[case] input: &str, #[case] expected: &str) { + test(input, expected) + } + + #[rstest] + #[case("let a = 5; a", "5")] + #[case("let a = 2 + 2; a", "4")] + #[case("let a = 2; let b = a; b", "2")] + fn let_statement(#[case] input: &str, #[case] expected: &str) { + test(input, expected) + } + + #[rstest] + #[case("fn(x) { x + 2 }", "fn(x) { (x + 2) }")] + #[case("fn(x) { x + 2 }(2)", "4")] + #[case("let addtwo = fn(x) { x + 2 }; addtwo(3)", "5")] + fn function(#[case] input: &str, #[case] expected: &str) { + test(input, expected) + } +} diff --git a/src/evaluator/object.rs b/src/evaluator/object.rs new file mode 100644 index 0000000..3a7ef19 --- /dev/null +++ b/src/evaluator/object.rs @@ -0,0 +1,35 @@ +use crate::parser::Node; + +#[derive(Debug, Clone)] +pub enum Object { + Integer(i64), + Boolean(bool), + Null, + Function { + parameters: Vec, + body: Box, + }, +} + +impl std::fmt::Display for Object { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Object::Integer(val) => write!(f, "{}", val), + Object::Boolean(val) => write!(f, "{}", val), + Object::Null => write!(f, "null"), + Object::Function { + parameters: params, + body, + } => write!( + f, + "fn({}) {}", + params + .iter() + .map(|obj| obj.to_string()) + .reduce(|acc, elem| acc + ", " + &elem) + .unwrap_or(String::new()), + body, + ), + } + } +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 3aa0801..e39e683 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -48,22 +48,8 @@ fn next_token(input: &mut Peekable) -> Result, LexerError> } } - '<' => { - if input.peek() == Some(&'=') { - input.next(); - Token::LessThanEqual - } else { - Token::LessThan - } - } - '>' => { - if input.peek() == Some(&'=') { - input.next(); - Token::GreaterThanEqual - } else { - Token::GreaterThan - } - } + '<' => Token::LessThan, + '>' => Token::GreaterThan, ',' => Token::Comma, ';' => Token::Semicolon, diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 598c64d..312af4c 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -36,8 +36,6 @@ pub enum Token { NotEqual, LessThan, GreaterThan, - LessThanEqual, - GreaterThanEqual, // Delimiters Comma, @@ -130,8 +128,6 @@ pub enum InfixOperator { NotEqual, LessThan, GreaterThan, - LessThanEqual, - GreaterThanEqual, Call, } @@ -155,9 +151,7 @@ impl TryFrom<&Token> for InfixOperator { Token::Equal => Self::Equal, Token::NotEqual => Self::NotEqual, Token::LessThan => Self::LessThan, - Token::LessThanEqual => Self::LessThanEqual, Token::GreaterThan => Self::GreaterThan, - Token::GreaterThanEqual => Self::GreaterThanEqual, Token::LeftParenthesis => Self::Call, diff --git a/src/main.rs b/src/main.rs index 2f64cef..9c92c76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,13 @@ +use std::collections::HashMap; use std::fs; use std::io::{self, Write}; +use anyhow::{Context, Result}; use clap::{CommandFactory, Parser}; +use crate::evaluator::Environment; + +mod evaluator; mod lexer; mod parser; @@ -12,37 +17,63 @@ struct Args { path: Option, } -fn main() { +fn main() -> Result<()> { let args = Args::parse(); let cmd = Args::command(); match args.path { Some(path) => { - let input = fs::read_to_string(&path).unwrap(); - let tokens = lexer::tokenize(&input).unwrap(); - let ast = parser::parse(tokens); - println!("{}", ast); + let input = fs::read_to_string(&path).context("could not open file")?; + let mut env: Environment = HashMap::new(); + let res = eval(&input, &mut env)?; + println!("{}", res); } - None => start_repl(cmd.get_version().unwrap()), - } + None => { + start_repl(cmd.get_version().unwrap()); + } + }; + + Ok(()) } fn start_repl(version: &str) { - println!("Moose {} REPL", version); let mut input = String::new(); + let mut env: Environment = HashMap::new(); + + println!("Moose {} -- REPL", version); + loop { print!("> "); + // We flush immediately otherwise the `>` is not actually printed until after + // the user has hit the return key. io::stdout().flush().expect("failed to write to stdout"); - io::stdin() + let bytes_read = io::stdin() .read_line(&mut input) .expect("failed to read from stdin"); - let tokens = lexer::tokenize(&input).unwrap(); - let ast = parser::parse(tokens); - println!("{}", ast); + // Check for Ctrl+D + if bytes_read == 0 { + break; + } + + let res = eval(&input, &mut env).unwrap_or_else(|err| format!("{:?}", err)); + if res == String::from("null") { + input.clear(); + continue; + } + + println!("{}", res); input.clear(); } } + +fn eval(input: &str, env: &mut Environment) -> Result { + let tokens = lexer::tokenize(&input).context("failed to tokenize input")?; + let ast = parser::parse(tokens).context("failed to parse statement")?; + let res = evaluator::evaluate(ast, env).context("failed to evaluate statement")?; + + Ok(res.to_string()) +} diff --git a/src/parser/ast.rs b/src/parser/ast.rs index b92e82b..a3624bf 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -8,6 +8,10 @@ impl Program { pub fn new(vec: Vec) -> Program { Program(vec) } + + pub fn program(self) -> Vec { + self.0 + } } impl Display for Program { @@ -18,14 +22,14 @@ impl Display for Program { self.0 .iter() .map(|stmt| stmt.to_string()) - .collect::>() - .join("\n") + .reduce(|acc, elem| acc + "\n" + &elem) + .unwrap_or(String::new()) ) } } // Statements -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Node { Let(String, Expression), Return(Expression), @@ -43,8 +47,8 @@ impl Display for Node { "{{ {} }}", vec.iter() .map(|node| node.to_string()) - .collect::>() - .join("\n") + .reduce(|acc, elem| acc + "\n" + &elem) + .unwrap_or(String::new()) ), Node::Expression(val) => write!(f, "{}", val), } @@ -52,7 +56,7 @@ impl Display for Node { } // Expressions -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Expression { Identifier(String), @@ -74,8 +78,6 @@ pub enum Expression { NotEqual(Box, Box), LessThan(Box, Box), GreaterThan(Box, Box), - LessThanEqual(Box, Box), - GreaterThanEqual(Box, Box), If { condition: Box, @@ -103,9 +105,7 @@ impl Expression { | Expression::Equal(_, _) | Expression::NotEqual(_, _) | Expression::LessThan(_, _) - | Expression::GreaterThan(_, _) - | Expression::LessThanEqual(_, _) - | Expression::GreaterThanEqual(_, _) => true, + | Expression::GreaterThan(_, _) => true, _ => false, } } @@ -143,8 +143,6 @@ impl Display for Expression { Expression::NotEqual(rhs, lhs) => write!(f, "({} != {})", rhs, lhs), Expression::LessThan(rhs, lhs) => write!(f, "({} < {})", rhs, lhs), Expression::GreaterThan(rhs, lhs) => write!(f, "({} > {})", rhs, lhs), - Expression::LessThanEqual(rhs, lhs) => write!(f, "({} <= {})", rhs, lhs), - Expression::GreaterThanEqual(rhs, lhs) => write!(f, "({} >= {})", rhs, lhs), Expression::If { condition, @@ -171,8 +169,8 @@ impl Display for Expression { function, args.iter() .map(|expr| expr.to_string()) - .collect::>() - .join(", ") + .reduce(|acc, elem| acc + ", " + &elem) + .unwrap_or(String::new()) ), } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8e94fd1..12e5ee6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10,7 +10,7 @@ pub use error::ParserError; use self::precedence::{get_prescedence, Precedence}; // Entrypoint -pub fn parse(tokens: Tokens) -> Program { +pub fn parse(tokens: Tokens) -> Result { // Redefine tokens are mutable so it can be used as an iterator let mut tokens = tokens; @@ -19,14 +19,10 @@ pub fn parse(tokens: Tokens) -> Program { // next_node return None on an EOF, so we know to end then while let Some(node) = next_node(&mut tokens) { - match node { - Ok(node) => ast.push(node), - // TODO: Handle this more gracefully than a panic - Err(err) => panic!("{}", err), - } + ast.push(node?) } - Program::new(ast) + Ok(Program::new(ast)) } // Get the next statement of our program @@ -56,11 +52,13 @@ fn next_node(tokens: &mut Tokens) -> Option> { Token::LeftBrace => parse_block_statement(tokens), Token::Semicolon => { - // Eat the Semicolon token + // Eat the token tokens.next(); next_node(tokens)? } + Token::RightBrace => return None, + tok => panic!("not implemented: {:?}", tok), }; @@ -105,11 +103,11 @@ fn parse_block_statement(tokens: &mut Tokens) -> Result { return Err(ParserError::ExpectedBlock); }; - while tokens.peek() != Some(&Token::RightBrace) { + while tokens.peek().ok_or(ParserError::EOF)? != &Token::RightBrace { match next_node(tokens) { Some(Ok(stmt)) => statements.push(stmt), Some(Err(err)) => return Err(err), - None => return Err(ParserError::EOF), + None => break, } } @@ -224,6 +222,8 @@ fn parse_prefix_operator( let expr = match operator { // Not PrefixOperator::Bang => match parse_expression(tokens, Precedence::Prefix)? { + // TODO: Technically monkey uses truthy values, so this + // should not actually exist...but it does for now expr if expr.is_bool() => Expression::Not(Box::new(expr)), _ => return Err(ParserError::ExpectedBoolean), }, @@ -243,6 +243,8 @@ fn parse_prefix_operator( let condition = parse_expression(tokens, Precedence::Lowest)?; // Conditions in if statements must evaluate to booleans + // TODO: Technically monkey uses truthy values, so this + // should not actually exist...but it does for now if !condition.is_bool() { return Err(ParserError::ExpectedBoolean); } @@ -254,6 +256,7 @@ fn parse_prefix_operator( tokens.next(); Some(Box::new(parse_block_statement(tokens)?)) } else { + dbg!(tokens.peek()); None }; @@ -363,8 +366,6 @@ fn parse_infix_operator( InfixOperator::GreaterThan => Expression::GreaterThan(lhs, rhs), InfixOperator::LessThan => Expression::LessThan(lhs, rhs), - InfixOperator::GreaterThanEqual => Expression::GreaterThanEqual(lhs, rhs), - InfixOperator::LessThanEqual => Expression::LessThanEqual(lhs, rhs), InfixOperator::Call => panic!("unreachable"), }; @@ -378,6 +379,17 @@ mod tests { use crate::lexer; use rstest::rstest; + // General test function used for parsing expressions + // + // Useful when splitting tests up into groups rather than making a massive block of cases + // via rstest for semi-related functionality such as testing term parsing and if + // expression parsing + fn expression_test(input: &str, expected: &str) { + let mut tokens = lexer::tokenize(input).unwrap(); + let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap(); + assert_eq!(&res.to_string(), expected); + } + #[rstest] #[case("let int = 5", "let int be 5")] #[case("return 7", "returning 7")] @@ -385,9 +397,9 @@ mod tests { #[case("return 5 + 6", "returning (5 + 6)")] #[case("5 + 6; 7+3", "(5 + 6)\n(7 + 3)")] #[case("(5 + 5) * 3; 2 + 2", "((5 + 5) * 3)\n(2 + 2)")] - fn test_parser<'a>(#[case] input: &str, #[case] expected: &str) { + fn parse_success(#[case] input: &str, #[case] expected: &str) { let tokens = lexer::tokenize(input).unwrap(); - let res = parse(tokens); + let res = parse(tokens).unwrap(); assert_eq!(&res.to_string(), expected); } @@ -397,22 +409,32 @@ mod tests { #[case("return")] #[case("let = 8")] #[should_panic] - fn test_parser_failure(#[case] input: &str) { + fn parse_failure(#[case] input: &str) { let tokens = lexer::tokenize(input).unwrap(); - parse(tokens); + parse(tokens).unwrap(); } - #[rstest] // Terms + #[rstest] #[case("5", "5")] #[case("uwu", "uwu")] #[case("true", "true")] #[case("false", "false")] + fn term(#[case] input: &str, #[case] expected: &str) { + expression_test(input, expected) + } + // Prefix operators + #[rstest] #[case("!true", "(!true)")] #[case("!false", "(!false)")] #[case("-5", "(-5)")] + fn prefix_operator(#[case] input: &str, #[case] expected: &str) { + expression_test(input, expected) + } + // Infix operators + #[rstest] #[case("5 + 6", "(5 + 6)")] #[case("5 - 6", "(5 - 6)")] #[case("5 * 6", "(5 * 6)")] @@ -421,8 +443,6 @@ mod tests { #[case("5 != 6", "(5 != 6)")] #[case("5 < 6", "(5 < 6)")] #[case("5 > 6", "(5 > 6)")] - #[case("5 <= 6", "(5 <= 6)")] - #[case("5 >= 6", "(5 >= 6)")] // Boolean and numeric operators #[case("3 < 5 == true", "((3 < 5) == true)")] // Operator associativity @@ -434,31 +454,24 @@ mod tests { #[case("5 - 6 * 7 + 2", "((5 - (6 * 7)) + 2)")] #[case("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)")] #[case("(5 + 5) * 2", "((5 + 5) * 2)")] - fn test_parse_expression(#[case] input: &str, #[case] expected: &str) { - let mut tokens = lexer::tokenize(input).unwrap(); - let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap(); - dbg!(&res); - assert_eq!(&res.to_string(), expected); + fn infix_operator(#[case] input: &str, #[case] expected: &str) { + expression_test(input, expected) } #[rstest] #[case("if true { 5 + 5 };", "if true then { (5 + 5) } else N/A")] #[case("if x > y { x }", "if (x > y) then { x } else N/A")] #[case("if x > y { x } else { y }", "if (x > y) then { x } else { y }")] - fn test_if_expression(#[case] input: &str, #[case] expected: &str) { - let mut tokens = lexer::tokenize(input).unwrap(); - let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap(); - assert_eq!(&res.to_string(), expected); + fn if_expression(#[case] input: &str, #[case] expected: &str) { + expression_test(input, expected) } #[rstest] #[case("fn() {true}", "fn() { true }")] #[case("fn(x) {x + 3}", "fn(x) { (x + 3) }")] #[case("fn(x, y) {x + y}", "fn(x, y) { (x + y) }")] - fn test_function_expression(#[case] input: &str, #[case] expected: &str) { - let mut tokens = lexer::tokenize(input).unwrap(); - let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap(); - assert_eq!(&res.to_string(), expected); + fn function_expression(#[case] input: &str, #[case] expected: &str) { + expression_test(input, expected) } #[rstest] @@ -466,9 +479,7 @@ mod tests { #[case("add(1 + 2, 3)", "add((1 + 2), 3)")] #[case("add(x, y)", "add(x, y)")] #[case("fn(x,y) { x + y }(1,2)", "fn(x, y) { (x + y) }(1, 2)")] - fn test_call_expression(#[case] input: &str, #[case] expected: &str) { - let mut tokens = lexer::tokenize(input).unwrap(); - let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap(); - assert_eq!(&res.to_string(), expected); + fn call_expression(#[case] input: &str, #[case] expected: &str) { + expression_test(input, expected) } } diff --git a/src/parser/precedence.rs b/src/parser/precedence.rs index c313c44..87caefa 100644 --- a/src/parser/precedence.rs +++ b/src/parser/precedence.rs @@ -18,8 +18,6 @@ pub(super) fn get_prescedence(tok: &InfixOperator) -> Precedence { InfixOperator::LessThan => Precedence::Ordering, InfixOperator::GreaterThan => Precedence::Ordering, - InfixOperator::LessThanEqual => Precedence::Ordering, - InfixOperator::GreaterThanEqual => Precedence::Ordering, InfixOperator::Plus => Precedence::Sum, InfixOperator::Minus => Precedence::Sum,