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:
parent
51095f77ed
commit
c38576b667
9 changed files with 543 additions and 625 deletions
306
src/env/core.rs
vendored
Normal file
306
src/env/core.rs
vendored
Normal 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
44
src/env/mod.rs
vendored
Normal 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
17
src/error.rs
Normal 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
166
src/evaluator.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,14 @@
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
mod env;
|
||||||
|
mod error;
|
||||||
mod evaluator;
|
mod evaluator;
|
||||||
|
mod macros;
|
||||||
mod node;
|
mod node;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let env = evaluator::core_environment();
|
let env = env::Environment::new();
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
|
||||||
println!("Mute -- REPL");
|
println!("Mute -- REPL");
|
||||||
|
@ -23,11 +26,11 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ast = parser::parse_str(&input).unwrap();
|
let ast = parser::parse_str(&input).unwrap();
|
||||||
let res = evaluator::eval(env.clone(), ast);
|
let res = evaluator::eval(&env, ast);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")),
|
Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")),
|
||||||
Err(err) => println!("{}", err.to_string()),
|
Err(err) => println!("{}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
input.clear();
|
input.clear();
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::evaluator::Environment;
|
use crate::env::Environment;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
|
@ -22,7 +22,8 @@ pub enum Node {
|
||||||
env: Environment,
|
env: Environment,
|
||||||
body: Box<Node>,
|
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 {
|
impl std::fmt::Display for Node {
|
||||||
|
@ -74,6 +75,7 @@ impl std::fmt::Display for Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::NativeFunc(func) => write!(f, "{func:?}"),
|
Node::NativeFunc(func) => write!(f, "{func:?}"),
|
||||||
|
Node::Special(func) => write!(f, "{func:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue