use crate::{ lisp::{ error::{EvalError, LispError}, expr::LispExpr, number::LispNumber, Environment, }, primitive, }; use std::convert::TryInto; #[macro_export] macro_rules! primitive { ($env:expr, $arity:expr, $name:expr, $closure:expr) => { let val = crate::lisp::expr::LispExpr::PrimitiveFunc(crate::lisp::expr::PrimitiveFunc { arity: $arity, closure: $closure, }); let _ = $env.insert($name.to_string(), val); }; } pub fn new_env() -> Environment { let mut env = Environment::new(); primitive!(env, Some(2), "+", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; return Ok(LispExpr::Number( nums.iter().fold(LispNumber::Integer(0), |acc, &x| acc + *x), )); }); primitive!(env, Some(2), "-", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let mut acc = nums[0].clone(); for arg in nums.into_iter().skip(1) { acc = acc - *arg; } Ok(LispExpr::Number(acc)) }); primitive!(env, Some(2), "*", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; return Ok(LispExpr::Number( nums.iter().fold(LispNumber::Integer(1), |acc, &x| acc * *x), )); }); primitive!(env, Some(2), "/", |args, _| { let nums = args .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let mut acc = nums[0].clone(); for arg in nums.into_iter().skip(1) { acc = acc.div(*arg)?; } Ok(LispExpr::Number(acc)) }); primitive!(env, Some(0), "toggle-grid", |_, app| { app.toggle_grid(); Ok(LispExpr::Unit) }); primitive!(env, Some(3), "if", |args, _| { match args { [predicate, then, else_] => { if matches!(predicate, LispExpr::BoolLit(false)) { Ok(else_.clone()) } else { Ok(then.clone()) } } _ => { panic!("panicked at `if` expression") } } }); primitive!(env, Some(2), "and", |args, _| { if args .iter() .any(|arg| matches!(arg, LispExpr::BoolLit(false))) { Ok(LispExpr::BoolLit(false)) } else { Ok(LispExpr::BoolLit(true)) } }); primitive!(env, Some(2), "or", |args, _| { if args .iter() .any(|arg| matches!(arg, LispExpr::BoolLit(true))) { Ok(LispExpr::BoolLit(true)) } else { Ok(LispExpr::BoolLit(false)) } }); primitive!(env, Some(1), "not", |args, _| { match args { [val] => { if matches!(val, LispExpr::BoolLit(false)) { Ok(LispExpr::BoolLit(true)) } else { Ok(LispExpr::BoolLit(false)) } } _ => Err(EvalError::ArgumentCount(Some(1)).into()), } }); primitive!(env, None, "begin", |args, _| { if args.is_empty() { Err(EvalError::ArgumentCount(None).into()) } else { Ok(args.into_iter().last().unwrap().clone()) } }); primitive!(env, Some(0), "quit", |_, app| { app.quit(); Ok(LispExpr::Unit) }); env }