diff --git a/src/env.rs b/src/env.rs index 546afa3..a189f7d 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,4 +1,5 @@ -use crate::types::{MalMap, MalRet, MalType}; +use crate::types::{comparison_op, MalType::*}; +use crate::types::{MalArgs, MalMap, MalRet, MalType}; // This is the first time I implement a macro, and I'm copying it // so I will comment this a LOT @@ -43,6 +44,24 @@ impl Env { } } + pub fn init(&mut self, binds: MalArgs, exprs: MalArgs) -> Result<&mut Self, String> { + if binds.len() != exprs.len() { + return Err("Env init with unmatched length".to_string()); + } // TODO: May be possible to leave this be and not set additional elements at all + for (bind, expr) in binds.iter().zip(exprs.iter()) { + match bind { + Sym(sym) => self.set(sym, expr), + _ => { + return Err(format!( + "Initializing environment: {:?} is not a symbol", + bind + )) + } + } + } + Ok(self) + } + pub fn set(&mut self, sym: &str, val: &MalType) { self.data.insert(sym.to_string(), val.clone()); } @@ -58,7 +77,7 @@ impl Env { } } -use crate::types::int_op; +use crate::types::arithmetic_op; use crate::types::MalType::{Fun, Str}; use std::process::exit; @@ -66,9 +85,14 @@ pub fn env_init() -> Env { env_init!(None, "test" => Fun(|_| Ok(Str("This is a test function".to_string()))), "quit" => Fun(|_| {println!("Bye!"); exit(0)}), - "+" => Fun(|a| int_op(0, |a, b| a + b, a)), - "-" => Fun(|a| int_op(0, |a, b| a - b, a)), - "*" => Fun(|a| int_op(1, |a, b| a * b, a)), - "/" => Fun(|a| int_op(1, |a, b| a / b, a)) + "+" => Fun(|a| arithmetic_op(0, |a, b| a + b, a)), + "-" => Fun(|a| arithmetic_op(0, |a, b| a - b, a)), + "*" => Fun(|a| arithmetic_op(1, |a, b| a * b, a)), + "/" => Fun(|a| arithmetic_op(1, |a, b| a / b, a)), + "=" => Fun(|a| comparison_op(|a, b| a == b, a)), + ">" => Fun(|a| comparison_op(|a, b| a > b, a)), + "<" => Fun(|a| comparison_op(|a, b| a > b, a)), + ">=" => Fun(|a| comparison_op(|a, b| a >= b, a)), + "<=" => Fun(|a| comparison_op(|a, b| a <= b, a)) ) } diff --git a/src/eval.rs b/src/eval.rs index 91bb22a..3a58463 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,5 @@ use crate::env::Env; +use crate::types::car_cdr; use crate::types::MalType::*; use crate::types::{MalArgs, MalMap, MalRet, MalType}; @@ -9,27 +10,20 @@ fn call_func(func: &MalType, args: &[MalType]) -> MalRet { } } +/// Resolve the first element of the list as the function name and call it +/// with the other elements as arguments fn eval_func(list: &MalType) -> MalRet { match list { List(list) => { let (func, args) = car_cdr(list); call_func(func, args) } - _ => todo!("Yep! I hate it"), + _ => todo!("This should never happen, if you see this message I probably broke the code"), } } -fn car_cdr(list: &[MalType]) -> (&MalType, &[MalType]) { - ( - &list[0], - if list.len() > 1 { - &list[1..] - } else { - &list[0..0] - }, - ) -} - +/// def! special form: +/// Evaluate the second expression and assign it to the first symbol fn def_bang(list: &[MalType], env: &mut Env) -> MalRet { match list.len() { 2 => match &list[0] { @@ -47,6 +41,9 @@ fn def_bang(list: &[MalType], env: &mut Env) -> MalRet { } } +/// let* special form: +/// Create a temporary inner environment, assigning pair of elements in +/// the first list and returning the evaluation of the second expression fn let_star(list: &[MalType], env: &Env) -> MalRet { // Create the inner environment let mut inner_env = Env::new(Some(Box::new(env.clone()))); @@ -69,16 +66,49 @@ fn let_star(list: &[MalType], env: &Env) -> MalRet { } } +/// do special form: +/// Evaluate all the elements in a list using eval_ast and return the +/// result of the last evaluation +fn do_form(list: &[MalType], env: &mut Env) -> MalRet { + let mut last_ret = Nil; + for element in list.iter() { + last_ret = eval_ast(element, env)?; + // TODO: may use just "eval" to allow other expressions + } + Ok(last_ret) +} + +fn if_form(list: &[MalType], env: &mut Env) -> MalRet { + if !(2..=3).contains(&list.len()) { + return Err("Wrong number of arguments".to_string()); + } + let (cond, branches) = car_cdr(list); + match eval(cond, env)? { + Nil | Bool(false) => match branches.len() { + 1 => Ok(Nil), + _ => eval(&branches[1], env), + }, + _ => eval(&branches[0], env), + } +} + +/// Intermediate function to discern special forms from defined symbols fn apply(list: &MalArgs, env: &mut Env) -> MalRet { let (car, cdr) = car_cdr(list); match car { Sym(sym) if sym == "def!" => def_bang(cdr, env), Sym(sym) if sym == "let*" => let_star(cdr, env), + Sym(sym) if sym == "do" => do_form(cdr, env), + Sym(sym) if sym == "if" => if_form(cdr, env), + // Filter out special forms Sym(_) => eval_func(&eval_ast(&List(list.to_vec()), env)?), _ => Err(format!("{:?} is not a symbol", car)), } } +/// Switch ast evaluation depending on it being a list or not and return the +/// result of the evaluation, this function calls "eval_ast" to recursively +/// evaluate asts pub fn eval(ast: &MalType, env: &mut Env) -> MalRet { match &ast { List(list) if list.is_empty() => Ok(ast.clone()), @@ -87,6 +117,7 @@ pub fn eval(ast: &MalType, env: &mut Env) -> MalRet { } } +/// Separetely evaluate all elements in a collection (list or vector) fn eval_collection(list: &MalArgs, env: &mut Env) -> Result { let mut ret = MalArgs::new(); for el in list { @@ -95,6 +126,7 @@ fn eval_collection(list: &MalArgs, env: &mut Env) -> Result { Ok(ret) } +/// Evaluate the values of a map fn eval_map(map: &MalMap, env: &mut Env) -> MalRet { let mut ret = MalMap::new(); for (k, v) in map { @@ -103,6 +135,7 @@ fn eval_map(map: &MalMap, env: &mut Env) -> MalRet { Ok(Map(ret)) } +/// Eval the provided ast fn eval_ast(ast: &MalType, env: &mut Env) -> MalRet { match ast { Sym(sym) => env.get(sym), diff --git a/src/step4_if_fn_do.rs b/src/step4_if_fn_do.rs new file mode 100644 index 0000000..dba8e44 --- /dev/null +++ b/src/step4_if_fn_do.rs @@ -0,0 +1,35 @@ +// Structure the main functions of the interpreter +// +// For now just act as an echo, note that each function should not modify the +// input, thus this can be referenced by the previous step without the need +// to allocate more memory + +use crate::env::Env; +use crate::eval::eval; +use crate::printer::pr_str; +use crate::reader::read_str; +use crate::types::{MalRet, MalType}; + +#[allow(non_snake_case)] +/// Read input and generate an ast +fn READ(input: &str) -> MalRet { + read_str(input).map_err(|err| format!("@ READ: {}", err)) +} + +#[allow(non_snake_case)] +/// Evaluate the generated ast +fn EVAL(ast: MalType, env: &mut Env) -> MalRet { + eval(&ast, env).map_err(|err| format!("@ EVAL: {}", err)) +} + +#[allow(non_snake_case)] +/// Print out the result of the evaluation +fn PRINT(output: MalType) -> String { + pr_str(&output, true) +} + +pub fn rep(input: &str, env: &mut Env) -> Result { + let ast = READ(input)?; + let out = EVAL(ast, env)?; + Ok(PRINT(out)) +} diff --git a/src/types.rs b/src/types.rs index 8cfee65..ce238cd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -68,6 +68,18 @@ pub fn unescape_str(s: &str) -> String { .replace("\\\"", "\"") } +/// Extract the car and cdr from a list +pub fn car_cdr(list: &[MalType]) -> (&MalType, &[MalType]) { + ( + &list[0], + if list.len() > 1 { + &list[1..] + } else { + &list[0..0] + }, + ) +} + use MalType::Int; fn if_number(val: &MalType) -> Result { @@ -77,7 +89,7 @@ fn if_number(val: &MalType) -> Result { } } -pub fn int_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet { +pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet { if args.is_empty() { return Ok(Int(set)); } @@ -92,3 +104,23 @@ pub fn int_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> Mal Ok(Int(left)) } + +use MalType::{Bool, Nil}; + +pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet { + match args.len() { + 0 => Err("Comparison requires at least 1 argument".to_string()), + _ => { + let (left, rights) = car_cdr(args); + let mut left = if_number(left)?; + for right in rights { + let right = if_number(right)?; + if !f(left, right) { + return Ok(Nil); + } + left = right; + } + Ok(Bool(true)) + } + } +}