From c38576b6677bcbf2a90f33fc99249b68e8a9ba19 Mon Sep 17 00:00:00 2001 From: Roman Godmaire Date: Sat, 4 May 2024 16:40:02 -0400 Subject: [PATCH] refactor: extract env, macros, error, and remove expression Rather than using expressions, we can instead just parse into nodes then work with those instead. Everything in this language is an Expression, so there's no reason to differentiate between nodes and expressions. --- src/env/core.rs | 306 +++++++++++++++++++++++++++++ src/env/mod.rs | 44 +++++ src/error.rs | 17 ++ src/evaluator.rs | 166 ++++++++++++++++ src/evaluator/env.rs | 352 ---------------------------------- src/evaluator/mod.rs | 268 -------------------------- src/{evaluator => }/macros.rs | 0 src/main.rs | 9 +- src/node.rs | 6 +- 9 files changed, 543 insertions(+), 625 deletions(-) create mode 100644 src/env/core.rs create mode 100644 src/env/mod.rs create mode 100644 src/error.rs create mode 100644 src/evaluator.rs delete mode 100644 src/evaluator/env.rs delete mode 100644 src/evaluator/mod.rs rename src/{evaluator => }/macros.rs (100%) diff --git a/src/env/core.rs b/src/env/core.rs new file mode 100644 index 0000000..3b00535 --- /dev/null +++ b/src/env/core.rs @@ -0,0 +1,306 @@ +use std::borrow::Borrow; +use std::collections::HashMap; + +use crate::error::Error; +use crate::evaluator::eval_node; +use crate::macros::arg_count; +use crate::node::Node; + +pub(super) fn core() -> HashMap { + [ + // Arithmetic operations + ( + "+", + Node::NativeFunc(|_env, args| { + let res = args + .into_iter() + .reduce(|lhs, rhs| match (lhs, rhs) { + (Node::Int(lhs), Node::Int(rhs)) => Node::Int(lhs + rhs), + _ => todo!(), + }) + .unwrap_or(Node::Int(0)); + + Ok(res) + }), + ), + ( + "-", + Node::NativeFunc(|_env, args| { + let res = args + .into_iter() + .reduce(|lhs, rhs| match (lhs, rhs) { + (Node::Int(lhs), Node::Int(rhs)) => Node::Int(lhs - rhs), + _ => todo!(), + }) + .unwrap_or(Node::Int(0)); + + Ok(res) + }), + ), + ( + "*", + Node::NativeFunc(|_env, args| { + let res = args + .into_iter() + .reduce(|lhs, rhs| match (lhs, rhs) { + (Node::Int(lhs), Node::Int(rhs)) => Node::Int(lhs * rhs), + _ => todo!(), + }) + .unwrap_or(Node::Int(0)); + + Ok(res) + }), + ), + ( + "/", + Node::NativeFunc(|_env, args| { + let res = args + .into_iter() + .reduce(|lhs, rhs| match (lhs, rhs) { + (Node::Int(lhs), Node::Int(rhs)) => Node::Int(lhs / rhs), + _ => todo!(), + }) + .unwrap_or(Node::Int(0)); + + Ok(res) + }), + ), + // Collections + ( + "vector", + Node::NativeFunc(|_env, args| Ok(Node::Vector(args))), + ), + ( + "vector?", + Node::NativeFunc(|_env, args| { + arg_count!(1, args.len()); + + if let Node::Vector(_vec) = args[0].borrow() { + return Ok(Node::Boolean(true)); + } + + Ok(Node::Boolean(false)) + }), + ), + ( + "hashmap", + Node::NativeFunc(|_env, args| { + arg_count!(modulo: 2, args.len()); + + let mut index = -1; + let (keys, values): (Vec<_>, Vec<_>) = args.into_iter().partition(|_| { + index += 1; + index % 2 == 0 + }); + + let res = keys + .into_iter() + // We turn the keys into strings because they're hashable + // This feels so hacky, but ¯\_(ツ)_/¯ + .map(|key| key.to_string()) + .zip(values) + .collect(); + + Ok(Node::Map(res)) + }), + ), + ( + "hashmap?", + Node::NativeFunc(|_env, args| { + arg_count!(1, args.len()); + + if let Node::Map(_map) = args[0].borrow() { + return Ok(Node::Boolean(true)); + } + + Ok(Node::Boolean(false)) + }), + ), + // Ordering + ( + "eq?", + Node::NativeFunc(|_env, args| { + arg_count!(2, args.len()); + + let lhs = args[0].borrow(); + let rhs = args[1].borrow(); + + match (lhs, rhs) { + (Node::Int(lhs), Node::Int(rhs)) => Ok(Node::Boolean(lhs == rhs)), + (Node::Boolean(lhs), Node::Boolean(rhs)) => Ok(Node::Boolean(lhs == rhs)), + (Node::String(lhs), Node::String(rhs)) => Ok(Node::Boolean(lhs == rhs)), + (Node::Nil, Node::Nil) => Ok(Node::Boolean(true)), + _ => Err(Error::ExpectedNumber)?, + } + }), + ), + ( + "not", + Node::NativeFunc(|_env, args| { + arg_count!(1, args.len()); + + let expr = args[0].borrow(); + if let Node::Boolean(false) = expr { + return Ok(Node::Boolean(true)); + } + + Ok(Node::Boolean(false)) + }), + ), + ( + "<", + Node::NativeFunc(|_env, args| { + arg_count!(2, args.len()); + + let less_than = match (args[0].borrow(), args[1].borrow()) { + (Node::Int(lhs), Node::Int(rhs)) => lhs < rhs, + _ => Err(Error::ExpectedNumber)?, + }; + + Ok(Node::Boolean(less_than)) + }), + ), + ( + ">", + Node::NativeFunc(|_env, args| { + arg_count!(2, args.len()); + + let greater_than = match (args[0].borrow(), args[1].borrow()) { + (Node::Int(lhs), Node::Int(rhs)) => lhs > rhs, + _ => Err(Error::ExpectedNumber)?, + }; + + Ok(Node::Boolean(greater_than)) + }), + ), + ( + "<=", + Node::NativeFunc(|_env, args| { + arg_count!(2, args.len()); + + let less_than_equal = match (args[0].borrow(), args[1].borrow()) { + (Node::Int(lhs), Node::Int(rhs)) => lhs <= rhs, + _ => Err(Error::ExpectedNumber)?, + }; + + Ok(Node::Boolean(less_than_equal)) + }), + ), + ( + ">=", + Node::NativeFunc(|_env, args| { + arg_count!(2, args.len()); + + let greater_than_equal = match (args[0].borrow(), args[1].borrow()) { + (Node::Int(lhs), Node::Int(rhs)) => lhs >= rhs, + _ => Err(Error::ExpectedNumber)?, + }; + + Ok(Node::Boolean(greater_than_equal)) + }), + ), + // Branching + ( + "if", + Node::NativeFunc(|_env, args| { + arg_count!(3, args.len()); + + let (cond, consequence, alternative) = + (args[0].clone(), args[1].clone(), args[2].clone()); + + // If the value is anything other than false, then it is truthy + if let Node::Boolean(false) = cond { + return Ok(alternative); + }; + + Ok(consequence) + }), + ), + // Environment Manipulation + ( + "define!", + Node::Special(|env, args| { + let mut args = args.into_iter(); + arg_count!(2, args.len()); + + let key = match args.next().unwrap() { + Node::Symbol(name) => name, + _ => Err(Error::ExpectedSymbol)?, + }; + + let val = eval_node(env, args.next().unwrap())?; + + env.set(key, val.clone()); + + Ok(val) + }), + ), + ( + "let*", + Node::Special(|env, args| { + arg_count!(2, args.len()); + dbg!("Hi mom"); + + let mut args = args.into_iter(); + let new_env = match args.next().unwrap() { + Node::List(list) if list.len() == 2 => { + let mut list = list; + let sym = match list.remove(0) { + Node::Symbol(name) => name, + _ => Err(Error::ExpectedSymbol)?, + }; + + let val = eval_node(env, list.remove(0))?; + env.wrap(vec![(sym, val)]) + } + Node::List(list) => Err(Error::MismatchedArgCount(2, list.len()))?, + _ => Err(Error::ExpectedList)?, + }; + + eval_node(&new_env, args.next().unwrap()) + }), + ), + ( + "fn*", + Node::Special(|env, args| { + let mut args = args; + arg_count!(2, args.len()); + + let arg_list = args.remove(0); + let body = args.remove(0); + + let args: Vec = match arg_list { + Node::List(args) => args + .into_iter() + .map(|v| match v { + Node::Symbol(sym) => Ok(sym), + _ => Err(Error::ExpectedSymbol), + }) + .collect(), + _ => Err(Error::ExpectedList), + }?; + + Ok(Node::Function { + params: args, + env: env.clone(), + body: Box::new(body), + }) + }), + ), + // IO + ( + "display", + Node::NativeFunc(|_env, args| { + arg_count!(1, args.len()); + + let val = args[0].borrow(); + print!("{}", val); + + Ok(val.clone()) + }), + ), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect() +} diff --git a/src/env/mod.rs b/src/env/mod.rs new file mode 100644 index 0000000..f39c3c5 --- /dev/null +++ b/src/env/mod.rs @@ -0,0 +1,44 @@ +use std::cell::RefCell; +use std::collections::HashMap; + +use crate::node::Node; + +mod core; + +type RawEnvironment = HashMap; + +#[derive(Debug, Clone)] +pub struct Environment { + current: RefCell, + outer: Option>, +} + +impl Environment { + pub fn new() -> Environment { + let current = RefCell::new(core::core()); + + Environment { + current, + outer: None, + } + } + + pub fn get(&self, ident: &str) -> Option { + if let Some(val) = self.current.borrow().get(ident) { + return Some(val.clone()); + } + + self.outer.as_ref()?.get(ident) + } + + pub fn wrap(&self, records: Vec<(String, Node)>) -> Environment { + Environment { + current: RefCell::new(records.into_iter().collect()), + outer: Some(Box::new(self.clone())), + } + } + + fn set(&self, ident: String, val: Node) { + self.current.borrow_mut().insert(ident, val); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..0e6ba91 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,17 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("could not find symbol '{0}' in environment")] + NotInEnv(String), + #[error("expression does not have a valid operator")] + InvalidOperator, + #[error("expected symbol")] + ExpectedSymbol, + #[error("expected list")] + ExpectedList, + #[error("expected number")] + ExpectedNumber, + #[error("expected {0} arguments, got {1}")] + MismatchedArgCount(usize, usize), +} diff --git a/src/evaluator.rs b/src/evaluator.rs new file mode 100644 index 0000000..65bd195 --- /dev/null +++ b/src/evaluator.rs @@ -0,0 +1,166 @@ +use std::borrow::Borrow; + +use anyhow::Result; + +use crate::env::Environment; +use crate::error::Error; +use crate::node::Node; + +pub fn eval(env: &Environment, ast: Vec) -> Result> { + let mut res = Vec::new(); + + for node in ast { + res.push(eval_node(env, node)?); + } + + Ok(res) +} + +pub fn eval_node(env: &Environment, ast_node: Node) -> Result { + let expr = match ast_node { + Node::List(list) => { + // Empty lists are nil in scheme + if list.is_empty() { + return Ok(Node::Nil); + } + + // We always assume lists evaluate + let mut list = list.into_iter(); + let operator = eval_node(env, list.next().ok_or(Error::InvalidOperator)?)?; + + match operator.borrow() { + Node::NativeFunc(func) => { + let mut args = Vec::new(); + for node in list { + args.push(eval_node(env, node)?); + } + + func(env, args)? + } + + Node::Special(func) => { + let args = list.collect(); + func(&env, args)? + } + + Node::Function { + params, + env: inner_env, + body, + } => { + let args = list; + if args.len() != params.len() { + Err(Error::MismatchedArgCount(params.len(), args.len()))? + } + + let args = args + .map(|node| eval_node(env, node)) + .collect::>>()?; + + let records = params.iter().map(|k| k.to_owned()).zip(args).collect(); + + let env = inner_env.wrap(records); + + eval_node(&env, *(*body).clone())? + } + _ => Err(Error::InvalidOperator)?, + } + } + Node::Symbol(sym) => env.get(&sym).ok_or(Error::NotInEnv(sym))?.to_owned(), + + val => val, + }; + + Ok(expr) +} + +#[cfg(test)] +mod test { + use crate::parser; + + use super::*; + use rstest::rstest; + + #[rstest] + // Raw values + #[case("1", "1")] + #[case("\"uwu\"", "uwu")] + #[case(":owo", ":owo")] + #[case("()", "()")] + #[case("nil", "()")] + // Arithmetic + #[case("(+ 1 2)", "3")] + #[case("(- 5 1)", "4")] + #[case("(* 8 9)", "72")] + #[case("(/ 86 2)", "43")] + // Native functions + #[case("(+ 1 2 (- 3 4))", "2")] + #[case("(vector 1 2 3)", "[1 2 3]")] + // Native functions defaults + #[case("(+)", "0")] + #[case("(-)", "0")] + #[case("(*)", "0")] + #[case("(/)", "0")] + #[case("(vector)", "[]")] + // Collections + #[case("[]", "[]")] + #[case("[1 2]", "[1 2]")] + #[case("[1 (+ 1 2)]", "[1 3]")] + #[case("{}", "{}")] + #[case("{:a \"uwu\"}", "{:a: uwu}")] + // Environment manipulation + #[case("(define! asdf (+ 2 2)) (+ asdf 2)", "4\n6")] + #[case("(let* (a 2) (+ a 2))", "4")] + // Branching + #[case("(if true true false)", "true")] + #[case("(if nil true false)", "true")] + #[case("(if false true false)", "false")] + #[case("(if 4 true false)", "true")] + #[case("(if \"blue\" true false)", "true")] + // Functions + #[case("((fn* (a) a) 3)", "3")] + #[case("((fn* (a) (+ a 2)) 1)", "3")] + #[case("((fn* (a b) (+ a b)) 1 2)", "3")] + // Boolean operations + #[case("(eq? 1 1)", "true")] + #[case("(eq? 1 2)", "false")] + #[case("(not false)", "true")] + #[case("(not true)", "false")] + #[case("(not nil)", "false")] + #[case("(not 1)", "false")] + // Ordering + #[case("(< 1 2)", "true")] + #[case("(< 2 1)", "false")] + #[case("(> 1 2)", "false")] + #[case("(> 2 1)", "true")] + #[case("(<= 1 2)", "true")] + #[case("(<= 1 1)", "true")] + #[case("(<= 2 1)", "false")] + #[case("(>= 2 1)", "true")] + #[case("(>= 1 1)", "true")] + #[case("(>= 1 2)", "false")] + fn test_evaluator(#[case] input: &str, #[case] expected: &str) { + let env = Environment::new(); + let ast = parser::parse_str(input).unwrap(); + let res = eval(&env, ast) + .unwrap() + .into_iter() + .map(|elem| elem.to_string()) + .reduce(|lhs, rhs| format!("{lhs}\n{rhs}")) + .unwrap(); + + assert_eq!(res, expected); + } + + #[rstest] + #[case("{:a}")] + #[case("(not-a-func :uwu)")] + #[case("{:a}")] + fn test_evaluator_fail(#[case] input: &str) { + let env = Environment::new(); + let ast = parser::parse_str(input).unwrap(); + let res = eval(&env, ast); + + assert!(res.is_err()) + } +} diff --git a/src/evaluator/env.rs b/src/evaluator/env.rs deleted file mode 100644 index 58d5a9d..0000000 --- a/src/evaluator/env.rs +++ /dev/null @@ -1,352 +0,0 @@ -use std::{borrow::Borrow, cell::RefCell, collections::HashMap, rc::Rc}; - -use super::macros::arg_count; -use super::{eval_ast_node, Error, Expression}; -use crate::node::Node; - -pub type RawEnvironment = HashMap>; -#[derive(Debug, Clone)] -pub struct Environment { - current: RefCell, - outer: Option>, -} - -impl Environment { - pub fn get(&self, ident: &str) -> Option> { - if let Some(val) = self.current.borrow().get(ident) { - return Some(val.clone()); - } - - self.outer.clone()?.get(ident) - } - - pub fn wrap(&self, records: Vec<(String, Rc)>) -> Environment { - Environment { - current: RefCell::new(records.into_iter().collect()), - outer: Some(Rc::new(self.clone())), - } - } - - fn set(&self, ident: String, val: Rc) { - self.current.borrow_mut().insert(ident, val); - } -} - -pub fn core_environment() -> Rc { - let env = [ - // Arithmetic operations - ( - "+", - Expression::NativeFunc(|args| { - let res = args - .into_iter() - .reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => { - Expression::Int(lhs + rhs).into() - } - _ => todo!(), - }) - .unwrap_or(Rc::new(Expression::Int(0))); - - Ok(res) - }), - ), - ( - "-", - Expression::NativeFunc(|args| { - let res = args - .into_iter() - .reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => { - Expression::Int(lhs - rhs).into() - } - _ => todo!(), - }) - .unwrap_or(Rc::new(Expression::Int(0))); - - Ok(res) - }), - ), - ( - "*", - Expression::NativeFunc(|args| { - let res = args - .into_iter() - .reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => { - Expression::Int(lhs * rhs).into() - } - _ => todo!(), - }) - .unwrap_or(Rc::new(Expression::Int(0))); - - Ok(res) - }), - ), - ( - "/", - Expression::NativeFunc(|args| { - let res = args - .into_iter() - .reduce(|lhs, rhs| match (lhs.borrow(), rhs.borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => { - Expression::Int(lhs / rhs).into() - } - _ => todo!(), - }) - .unwrap_or(Rc::new(Expression::Int(0))); - - Ok(res) - }), - ), - // Collections - ( - "vector", - Expression::NativeFunc(|args| Ok(Rc::new(Expression::Vector(args)))), - ), - ( - "vector?", - Expression::NativeFunc(|args| { - arg_count!(1, args.len()); - - if let Expression::Vector(_vec) = args[0].borrow() { - return Ok(Expression::Boolean(true).into()); - } - - Ok(Expression::Boolean(false).into()) - }), - ), - ( - "hashmap", - Expression::NativeFunc(|args| { - arg_count!(modulo: 2, args.len()); - - let mut index = -1; - let (keys, values): (Vec<_>, Vec<_>) = args.into_iter().partition(|_| { - index += 1; - index % 2 == 0 - }); - - let res = keys - .into_iter() - // We turn the keys into strings because they're hashable - // This feels so hacky, but ¯\_(ツ)_/¯ - .map(|key| key.to_string()) - .zip(values) - .collect(); - - Ok(Rc::new(Expression::HashMap(res))) - }), - ), - ( - "hashmap?", - Expression::NativeFunc(|args| { - arg_count!(1, args.len()); - - if let Expression::HashMap(_map) = args[0].borrow() { - return Ok(Expression::Boolean(true).into()); - } - - Ok(Expression::Boolean(false).into()) - }), - ), - // Ordering - ( - "eq?", - Expression::NativeFunc(|args| { - arg_count!(2, args.len()); - - let lhs: &Expression = args[0].borrow(); - let rhs: &Expression = args[1].borrow(); - - match (lhs, rhs) { - (Expression::Int(lhs), Expression::Int(rhs)) => { - Ok(Expression::Boolean(lhs == rhs).into()) - } - (Expression::Boolean(lhs), Expression::Boolean(rhs)) => { - Ok(Expression::Boolean(lhs == rhs).into()) - } - (Expression::String(lhs), Expression::String(rhs)) => { - Ok(Expression::Boolean(lhs == rhs).into()) - } - (Expression::Nil, Expression::Nil) => Ok(Expression::Boolean(true).into()), - _ => Err(Error::ExpectedNumber)?, - } - }), - ), - ( - "not", - Expression::NativeFunc(|args| { - arg_count!(1, args.len()); - - let expr: &Expression = args[0].borrow(); - if let Expression::Boolean(false) = expr { - return Ok(Expression::Boolean(true).into()); - } - - Ok(Expression::Boolean(false).into()) - }), - ), - ( - "<", - Expression::NativeFunc(|args| { - arg_count!(2, args.len()); - - let less_than = match (args[0].borrow(), args[1].borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => lhs < rhs, - _ => Err(Error::ExpectedNumber)?, - }; - - Ok(Expression::Boolean(less_than).into()) - }), - ), - ( - ">", - Expression::NativeFunc(|args| { - arg_count!(2, args.len()); - - let greater_than = match (args[0].borrow(), args[1].borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => lhs > rhs, - _ => Err(Error::ExpectedNumber)?, - }; - - Ok(Expression::Boolean(greater_than).into()) - }), - ), - ( - "<=", - Expression::NativeFunc(|args| { - arg_count!(2, args.len()); - - let less_than_equal = match (args[0].borrow(), args[1].borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => lhs <= rhs, - _ => Err(Error::ExpectedNumber)?, - }; - - Ok(Expression::Boolean(less_than_equal).into()) - }), - ), - ( - ">=", - Expression::NativeFunc(|args| { - arg_count!(2, args.len()); - - let greater_than_equal = match (args[0].borrow(), args[1].borrow()) { - (Expression::Int(lhs), Expression::Int(rhs)) => lhs >= rhs, - _ => Err(Error::ExpectedNumber)?, - }; - - Ok(Expression::Boolean(greater_than_equal).into()) - }), - ), - // Branching - ( - "if", - Expression::NativeFunc(|args| { - arg_count!(3, args.len()); - - let (cond, consequence, alternative) = - (args[0].clone(), args[1].clone(), args[2].clone()); - - // If the value is anything other than false, then it is truthy - if let Expression::Boolean(false) = cond.borrow() { - return Ok(alternative); - }; - - Ok(consequence) - }), - ), - // Environment Manipulation - ( - "define!", - Expression::Special(|env, args| { - let mut args = args.into_iter(); - arg_count!(2, args.len()); - - let key = match args.next().unwrap() { - Node::Symbol(name) => name, - _ => Err(Error::ExpectedSymbol)?, - }; - - let val = eval_ast_node(env.clone(), args.next().unwrap())?; - - env.set(key, val.clone()); - - Ok(val) - }), - ), - ( - "let*", - Expression::Special(|env, args| { - arg_count!(2, args.len()); - - let mut args = args.into_iter(); - let new_env = match args.next().unwrap() { - Node::List(list) if list.len() == 2 => { - let mut list = list; - let sym = match list.remove(0) { - Node::Symbol(name) => name, - _ => Err(Error::ExpectedSymbol)?, - }; - - let val = eval_ast_node(env.clone(), list.remove(0))?; - Environment { - current: RefCell::new([(sym.clone(), val)].into()), - outer: Some(env.clone()), - } - } - Node::List(list) => Err(Error::MismatchedArgCount(2, list.len()))?, - _ => Err(Error::ExpectedList)?, - }; - - eval_ast_node(Rc::new(new_env), args.next().unwrap()) - }), - ), - ( - "fn*", - Expression::Special(|env, args| { - let mut args = args; - arg_count!(2, args.len()); - - let arg_list = args.remove(0); - let body = args.remove(0); - - let args: Vec = match arg_list { - Node::List(args) => args - .into_iter() - .map(|v| match v { - Node::Symbol(sym) => Ok(sym), - _ => Err(Error::ExpectedSymbol), - }) - .collect(), - _ => Err(Error::ExpectedList), - }?; - - Ok(Rc::new(Expression::Function { - params: args, - env: (*env).clone(), - body, - })) - }), - ), - // IO - ( - "display", - Expression::NativeFunc(|args| { - arg_count!(1, args.len()); - - print!("{}", args[0]); - - Ok(Expression::Void.into()) - }), - ), - ] - .into_iter() - .map(|(k, v)| (k.to_string(), Rc::new(v))); - - Environment { - current: RefCell::new(HashMap::from_iter(env)), - outer: None, - } - .into() -} diff --git a/src/evaluator/mod.rs b/src/evaluator/mod.rs deleted file mode 100644 index 6744bda..0000000 --- a/src/evaluator/mod.rs +++ /dev/null @@ -1,268 +0,0 @@ -use std::{borrow::Borrow, collections::HashMap, rc::Rc}; - -use anyhow::Result; -use thiserror::Error; - -use crate::node::Node; - -mod env; -mod macros; - -pub use env::{core_environment, Environment}; - -#[derive(Debug)] -pub enum Expression { - // Values - Int(i64), - Boolean(bool), - Keyword(String), - String(String), - Nil, - Void, - - // Collections - Vector(Vec>), - HashMap(HashMap>), - - Function { - params: Vec, - env: Environment, - body: Node, - }, - NativeFunc(fn(args: Vec>) -> Result>), - Special(fn(env: Rc, args: Vec) -> Result>), -} - -impl std::fmt::Display for Expression { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Expression::Int(val) => write!(f, "{}", val), - Expression::Boolean(true) => write!(f, "true"), - Expression::Boolean(false) => write!(f, "false"), - Expression::Keyword(val) => write!(f, ":{}", val), - Expression::String(val) => write!(f, "{}", val), - Expression::Nil => write!(f, "()"), - Expression::Void => write!(f, ""), - - Expression::Vector(vec) => { - let s = vec - .iter() - .map(|elem| elem.to_string()) - .reduce(|lhs, rhs| format!("{lhs} {rhs}")) - .unwrap_or_default(); - - write!(f, "[{s}]") - } - Expression::HashMap(map) => { - let res = map - .iter() - .map(|(k, v)| format!("{k}: {v}")) - .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) - .unwrap_or_default(); - - write!(f, "{{{res}}}") - } - - Expression::Function { - params: _, - env: _, - body: _, - } => { - write!(f, "#") - } - - Expression::NativeFunc(func) => write!(f, "{func:?}"), - Expression::Special(func) => write!(f, "{func:?}"), - } - } -} - -#[derive(Debug, Error)] -pub enum Error { - #[error("could not find symbol '{0}' in environment")] - NotInEnv(String), - #[error("expression does not have a valid operator")] - InvalidOperator, - #[error("expected symbol")] - ExpectedSymbol, - #[error("expected list")] - ExpectedList, - #[error("expected number")] - ExpectedNumber, - #[error("expected {0} arguments, got {1}")] - MismatchedArgCount(usize, usize), -} - -pub fn eval(env: Rc, ast: Vec) -> Result>> { - let mut res = Vec::new(); - - for node in ast { - res.push(eval_ast_node(env.clone(), node)?); - } - - Ok(res) -} - -fn eval_ast_node(env: Rc, ast_node: Node) -> Result> { - let expr = match ast_node { - Node::List(list) => { - // Empty lists are nil in scheme - if list.is_empty() { - return Ok(Expression::Nil.into()); - } - - // We always assume lists evaluate - let mut list = list.into_iter(); - let operator = eval_ast_node(env.clone(), list.next().ok_or(Error::InvalidOperator)?)?; - - match operator.borrow() { - Expression::NativeFunc(func) => { - let mut args = Vec::new(); - for node in list { - args.push(eval_ast_node(env.clone(), node)?); - } - - func(args)? - } - - Expression::Special(func) => { - let args = list.collect(); - func(env, args)? - } - - Expression::Function { - params, - env: inner_env, - body, - } => { - let args = list; - if args.len() != params.len() { - Err(Error::MismatchedArgCount(params.len(), args.len()))? - } - - let args = args - .map(|node| eval_ast_node(env.clone(), node)) - .collect::>>>()?; - - let records = params - .into_iter() - .map(|k| k.to_owned()) - .zip(args.into_iter()) - .collect(); - - let env = inner_env.wrap(records).into(); - - eval_ast_node(env, (*body).clone())? - } - _ => Err(Error::InvalidOperator)?, - } - } - Node::Symbol(sym) => env.get(&sym).ok_or(Error::NotInEnv(sym))?.to_owned(), - - Node::Int(i) => Expression::Int(i).into(), - Node::Keyword(val) => Expression::Keyword(val).into(), - Node::String(s) => Expression::String(s).into(), - - Node::Boolean(true) => Expression::Boolean(true).into(), - Node::Boolean(false) => Expression::Boolean(false).into(), - Node::Nil => Expression::Nil.into(), - - Node::Vector(vec) => todo!(), - Node::Map(map) => todo!(), - Node::Function { params, env, body } => todo!(), - Node::NativeFunc(_) => todo!(), - }; - - Ok(expr) -} - -#[cfg(test)] -mod test { - use crate::parser; - - use super::*; - use rstest::rstest; - - #[rstest] - // Raw values - #[case("1", "1")] - #[case("\"uwu\"", "uwu")] - #[case(":owo", ":owo")] - #[case("()", "()")] - #[case("nil", "()")] - // Arithmetic - #[case("(+ 1 2)", "3")] - #[case("(- 5 1)", "4")] - #[case("(* 8 9)", "72")] - #[case("(/ 86 2)", "43")] - // Native functions - #[case("(+ 1 2 (- 3 4))", "2")] - #[case("(vector 1 2 3)", "[1 2 3]")] - // Native functions defaults - #[case("(+)", "0")] - #[case("(-)", "0")] - #[case("(*)", "0")] - #[case("(/)", "0")] - #[case("(vector)", "[]")] - // Collections - #[case("[]", "[]")] - #[case("[1 2]", "[1 2]")] - #[case("[1 (+ 1 2)]", "[1 3]")] - #[case("{}", "{}")] - #[case("{:a \"uwu\"}", "{:a: uwu}")] - // Environment manipulation - #[case("(define! asdf (+ 2 2)) (+ asdf 2)", "4\n6")] - #[case("(let* (a 2) (+ a 2))", "4")] - // Branching - #[case("(if true true false)", "true")] - #[case("(if nil true false)", "true")] - #[case("(if false true false)", "false")] - #[case("(if 4 true false)", "true")] - #[case("(if \"blue\" true false)", "true")] - // Functions - #[case("((fn* (a) a) 3)", "3")] - #[case("((fn* (a) (+ a 2)) 1)", "3")] - #[case("((fn* (a b) (+ a b)) 1 2)", "3")] - // Boolean operations - #[case("(eq? 1 1)", "true")] - #[case("(eq? 1 2)", "false")] - #[case("(not false)", "true")] - #[case("(not true)", "false")] - #[case("(not nil)", "false")] - #[case("(not 1)", "false")] - // Ordering - #[case("(< 1 2)", "true")] - #[case("(< 2 1)", "false")] - #[case("(> 1 2)", "false")] - #[case("(> 2 1)", "true")] - #[case("(<= 1 2)", "true")] - #[case("(<= 1 1)", "true")] - #[case("(<= 2 1)", "false")] - #[case("(>= 2 1)", "true")] - #[case("(>= 1 1)", "true")] - #[case("(>= 1 2)", "false")] - fn test_evaluator(#[case] input: &str, #[case] expected: &str) { - let env = core_environment(); - let ast = parser::parse_str(input).unwrap(); - let res = eval(env, ast) - .unwrap() - .into_iter() - .map(|elem| elem.to_string()) - .reduce(|lhs, rhs| format!("{lhs}\n{rhs}")) - .unwrap(); - - assert_eq!(res, expected); - } - - #[rstest] - #[case("{:a}")] - #[case("(not-a-func :uwu)")] - #[case("{:a}")] - fn test_evaluator_fail(#[case] input: &str) { - let env = core_environment(); - let ast = parser::parse_str(input).unwrap(); - let res = eval(env, ast); - - assert!(res.is_err()) - } -} diff --git a/src/evaluator/macros.rs b/src/macros.rs similarity index 100% rename from src/evaluator/macros.rs rename to src/macros.rs diff --git a/src/main.rs b/src/main.rs index 8d71a83..c503571 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ use std::io::{self, Write}; +mod env; +mod error; mod evaluator; +mod macros; mod node; mod parser; fn main() { - let env = evaluator::core_environment(); + let env = env::Environment::new(); let mut input = String::new(); println!("Mute -- REPL"); @@ -23,11 +26,11 @@ fn main() { } let ast = parser::parse_str(&input).unwrap(); - let res = evaluator::eval(env.clone(), ast); + let res = evaluator::eval(&env, ast); match res { Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), - Err(err) => println!("{}", err.to_string()), + Err(err) => println!("{}", err), } input.clear(); diff --git a/src/node.rs b/src/node.rs index f79dbdc..7cfd97c 100644 --- a/src/node.rs +++ b/src/node.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use anyhow::Result; -use crate::evaluator::Environment; +use crate::env::Environment; #[derive(Debug, Clone)] pub enum Node { @@ -22,7 +22,8 @@ pub enum Node { env: Environment, body: Box, }, - NativeFunc(fn(env: Environment, args: Vec) -> Result), + NativeFunc(fn(env: &Environment, args: Vec) -> Result), + Special(fn(env: &Environment, args: Vec) -> Result), } impl std::fmt::Display for Node { @@ -74,6 +75,7 @@ impl std::fmt::Display for Node { } Node::NativeFunc(func) => write!(f, "{func:?}"), + Node::Special(func) => write!(f, "{func:?}"), } } }