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.
This commit is contained in:
Roman Godmaire 2024-05-04 16:40:02 -04:00
parent 51095f77ed
commit c38576b667
9 changed files with 543 additions and 625 deletions

306
src/env/core.rs vendored Normal file
View file

@ -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<String, Node> {
[
// 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<String> = 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()
}

44
src/env/mod.rs vendored Normal file
View file

@ -0,0 +1,44 @@
use std::cell::RefCell;
use std::collections::HashMap;
use crate::node::Node;
mod core;
type RawEnvironment = HashMap<String, Node>;
#[derive(Debug, Clone)]
pub struct Environment {
current: RefCell<RawEnvironment>,
outer: Option<Box<Environment>>,
}
impl Environment {
pub fn new() -> Environment {
let current = RefCell::new(core::core());
Environment {
current,
outer: None,
}
}
pub fn get(&self, ident: &str) -> Option<Node> {
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);
}
}

17
src/error.rs Normal file
View file

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

166
src/evaluator.rs Normal file
View file

@ -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<Node>) -> Result<Vec<Node>> {
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<Node> {
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::<Result<Vec<Node>>>()?;
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())
}
}

View file

@ -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<String, Rc<Expression>>;
#[derive(Debug, Clone)]
pub struct Environment {
current: RefCell<RawEnvironment>,
outer: Option<Rc<Environment>>,
}
impl Environment {
pub fn get(&self, ident: &str) -> Option<Rc<Expression>> {
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<Expression>)>) -> Environment {
Environment {
current: RefCell::new(records.into_iter().collect()),
outer: Some(Rc::new(self.clone())),
}
}
fn set(&self, ident: String, val: Rc<Expression>) {
self.current.borrow_mut().insert(ident, val);
}
}
pub fn core_environment() -> Rc<Environment> {
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<String> = 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()
}

View file

@ -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<Rc<Expression>>),
HashMap(HashMap<String, Rc<Expression>>),
Function {
params: Vec<String>,
env: Environment,
body: Node,
},
NativeFunc(fn(args: Vec<Rc<Expression>>) -> Result<Rc<Expression>>),
Special(fn(env: Rc<Environment>, args: Vec<Node>) -> Result<Rc<Expression>>),
}
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, "#<function>")
}
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<Environment>, ast: Vec<Node>) -> Result<Vec<Rc<Expression>>> {
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<Environment>, ast_node: Node) -> Result<Rc<Expression>> {
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::<Result<Vec<Rc<Expression>>>>()?;
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())
}
}

View file

@ -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();

View file

@ -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<Node>,
},
NativeFunc(fn(env: Environment, args: Vec<Node>) -> Result<Node>),
NativeFunc(fn(env: &Environment, args: Vec<Node>) -> Result<Node>),
Special(fn(env: &Environment, args: Vec<Node>) -> Result<Node>),
}
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:?}"),
}
}
}