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...)
This commit is contained in:
Devon Tingley 2023-03-10 09:02:48 -05:00
parent 0321e9ba89
commit 124218cfd8
12 changed files with 440 additions and 87 deletions

7
Cargo.lock generated
View file

@ -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",
]

View file

@ -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]

View file

@ -1 +1,2 @@
let x = 5 + 6 + 7;
x

26
src/evaluator/error.rs Normal file
View file

@ -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 {}

265
src/evaluator/mod.rs Normal file
View file

@ -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<String, Object>;
pub fn evaluate(ast: Program, env: &mut Environment) -> Result<Object, EvalError> {
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<Object, EvalError> {
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<Object, EvalError> {
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)
}
}

35
src/evaluator/object.rs Normal file
View file

@ -0,0 +1,35 @@
use crate::parser::Node;
#[derive(Debug, Clone)]
pub enum Object {
Integer(i64),
Boolean(bool),
Null,
Function {
parameters: Vec<String>,
body: Box<Node>,
},
}
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,
),
}
}
}

View file

@ -48,22 +48,8 @@ fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>, 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,

View file

@ -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,

View file

@ -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<String>,
}
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<String> {
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())
}

View file

@ -8,6 +8,10 @@ impl Program {
pub fn new(vec: Vec<Node>) -> Program {
Program(vec)
}
pub fn program(self) -> Vec<Node> {
self.0
}
}
impl Display for Program {
@ -18,14 +22,14 @@ impl Display for Program {
self.0
.iter()
.map(|stmt| stmt.to_string())
.collect::<Vec<String>>()
.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::<Vec<String>>()
.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<Expression>, Box<Expression>),
LessThan(Box<Expression>, Box<Expression>),
GreaterThan(Box<Expression>, Box<Expression>),
LessThanEqual(Box<Expression>, Box<Expression>),
GreaterThanEqual(Box<Expression>, Box<Expression>),
If {
condition: Box<Expression>,
@ -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::<Vec<String>>()
.join(", ")
.reduce(|acc, elem| acc + ", " + &elem)
.unwrap_or(String::new())
),
}
}

View file

@ -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<Program, ParserError> {
// 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<Result<Node, ParserError>> {
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<Node, ParserError> {
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)
}
}

View file

@ -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,