use crate::{ app::AppState, lisp::{ error::{EvalError, LispError}, expr::{Arity, Ident, LispExpr, LispFunction}, number::LispNumber, Environment, }, type_match, }; use std::convert::TryInto; use log::{error, info}; pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result { match expr { LispExpr::Unit => Ok(expr.clone()), LispExpr::StringLit(_) => Ok(expr.clone()), LispExpr::Char(_) => Ok(expr.clone()), LispExpr::Number(_) => Ok(expr.clone()), LispExpr::BoolLit(_) => Ok(expr.clone()), LispExpr::Ident(ref id) => lookup_extended(&app.lisp_env, id), LispExpr::Quote(item, _) => match item.as_ref() { i @ LispExpr::Unit | i @ LispExpr::StringLit(_) | i @ LispExpr::Char(_) | i @ LispExpr::Number(_) | i @ LispExpr::BoolLit(_) => Ok(i.clone()), _ => Ok(*item.clone()), }, LispExpr::List(li) => { let func_expr = &li[0]; match func_expr { LispExpr::Ident(s) => match s.as_ref() { "define" => define_var(&li[1..], app), "set!" => set_var(&li[1..], app), "lambda" => create_lambda(&li[1..]), "if" => eval_if(&li[1..], app), "quote" => eval_quote(&li[1..]), _ => { let mut new_ls = vec![eval(&func_expr, app)?]; new_ls.extend(li[1..].to_vec()); eval(&(LispExpr::List(new_ls)), app) } }, LispExpr::PrimitiveFunc(f) => { let mut args = Vec::new(); for item in li[1..].iter() { args.push(eval(item, app)?); } f.call(&args, app) } LispExpr::Function(f) => { let mut args = Vec::new(); for item in li[1..].iter() { let i = eval(item, app)?; args.push(i); } if f.params.len() != args.len() { info!("too many or too little number of args"); Err(EvalError::ArgumentCount(Arity::Exact(f.params.len())).into()) } else { let nested_env: Environment = f.params.clone().into_iter().zip(args).collect(); app.lisp_env.push(nested_env); let result = if f.body.is_empty() { Ok(LispExpr::Unit) } else { eval(&LispExpr::List(f.body.clone()), app) }; app.lisp_env.pop(); return result; } } LispExpr::List(_) => { info!("list as funciton"); let func_expr = eval(&func_expr, app)?; let mut new_ls = vec![func_expr]; new_ls.extend(li[1..].to_vec()); eval(&(LispExpr::List(new_ls)), app) } _ => Err(EvalError::BadForm.into()), } } _ => Err(EvalError::BadForm.into()), } } pub fn define_var(args: &[LispExpr], app: &mut AppState) -> Result { info!("defining"); let arity = Arity::Exact(2); if !arity.is_valid(args) { return Err(arity.to_error()); } match args { [LispExpr::Ident(id), expr] => { let value = eval(&expr, app)?; let local_env = app.lisp_env.last_mut(); if let Some(env) = local_env { env.insert(id.into(), value); } else { error!("Unable to create global definition"); return Err(EvalError::BadForm.into()); } return Ok(LispExpr::Unit); } [LispExpr::List(shorthand), LispExpr::List(body)] => { // (define (func arg) ) shorthand let id = shorthand[0].unwrap_ident(); let params = if shorthand.len() > 1 { &shorthand[1..] } else { &[] } .to_vec() .into_iter() .map(|arg| arg.try_into()) .collect::, LispError>>()?; let value = LispExpr::Function(LispFunction { params, body: body.to_vec(), }); let local_env = app.lisp_env.last_mut(); if let Some(env) = local_env { info!("creating function {}", id); env.insert(id.into(), value); } else { error!("Unable to create global definition"); return Err(EvalError::BadForm.into()); } return Ok(LispExpr::Unit); } _ => { error!("Invalid usage of `define`"); Err(EvalError::BadForm.into()) } } } pub fn set_var(args: &[LispExpr], app: &mut AppState) -> Result { let arity = Arity::Exact(2); if !arity.is_valid(args) { return Err(arity.to_error()); } match args { [LispExpr::Ident(id), expr] => { let value = eval(&expr, app)?; let local_env = app.lisp_env.last_mut(); if let Some(env) = local_env { return env .insert(id.into(), value) .ok_or(EvalError::UnboundVariable(id.into()).into()); } else { error!("Unable to set in global env!"); return Err(EvalError::BadForm.into()); } } _ => { error!("Invalid usage of `set!`"); return Err(EvalError::BadForm.into()); } } } pub fn create_lambda(cdr: &[LispExpr]) -> Result { let arity: Arity = Arity::Exact(2); if !arity.is_valid(cdr) { return Err(arity.to_error()); } info!("creating lambda"); match cdr { [LispExpr::List(params), LispExpr::List(body)] if type_match!(params, (..) => LispExpr::Ident(_)) => { return Ok(LispExpr::Function(LispFunction { params: params .into_iter() .map(|p| p.unwrap_ident()) .collect::>(), body: body.clone(), })); } _ => { error!("Invalid usage of `lambda`"); return Err(EvalError::BadForm.into()); } } } pub fn eval_if(args: &[LispExpr], app: &mut AppState) -> Result { let arity = Arity::Exact(3); if !arity.is_valid(args) { return Err(arity.to_error()); } else { match args { [predicate, then, else_] => { let predicate = eval(&predicate, app)?; if matches!(predicate, LispExpr::BoolLit(false)) { return eval(&else_, app); } else { return eval(&then, app); } } _ => { panic!("panicked at `if` expression") } } } } pub fn eval_quote(args: &[LispExpr]) -> Result { let arity = Arity::Exact(1); if !arity.is_valid(args) { return Err(arity.to_error()); } else { match &args[0] { LispExpr::Quote(item, depth) => Ok(LispExpr::Quote(item.clone(), depth + 1)), i @ LispExpr::Unit | i @ LispExpr::StringLit(_) | i @ LispExpr::Char(_) | i @ LispExpr::Number(_) | i @ LispExpr::BoolLit(_) => Ok(i.clone()), _ => { let quoted_expr = Box::new(args[0].clone()); Ok(LispExpr::Quote(quoted_expr, 1)) } } } } pub fn lookup_extended(env_list: &[Environment], key: &str) -> Result { if env_list.is_empty() { return Err(EvalError::UnboundVariable(key.into()).into()); } else { let local_env = env_list.last().unwrap(); if let Some(val) = local_env.get(key) { return Ok(val.clone()); } else { return lookup_extended(&env_list[..env_list.len() - 1], key); } } } #[cfg(test)] mod tests { use super::*; use crate::lisp::{expr::LispExpr, lex::Lexer, number::LispNumber, parse::Parser}; fn run(code: &str, app: &mut AppState) -> LispExpr { let mut parser = Parser::new(Lexer::new(code, 0)); eval(&parser.parse_single_expr().unwrap(), app).unwrap() } #[test] fn eval_all() { let sdl_context = sdl2::init().unwrap(); let ttf_context = sdl2::ttf::init().unwrap(); let mut app = AppState::init(100, 100, &sdl_context, &ttf_context, None, None); eval_arithmetic(&mut app); eval_logical(&mut app); } fn eval_arithmetic(app: &mut AppState) { assert_eq!( run("(+ 1 2 3)", app), LispExpr::Number(LispNumber::Integer(6)) ); assert_eq!( run("(+ 1.1 2.2 3.3)", app), LispExpr::Number(LispNumber::Float(6.6)) ); assert_eq!( run("(* 1 2 3 4 5)", app), LispExpr::Number(LispNumber::Integer(120)) ); assert_eq!(run("(< 1 2)", app), LispExpr::BoolLit(true)); assert_eq!(run("(> 6 5 4 3 2 1)", app), LispExpr::BoolLit(true)); assert_eq!(run("(< 1 2 3 4 5 6)", app), LispExpr::BoolLit(true)); assert_eq!(run("(>= 5 5 4 3 2 1)", app), LispExpr::BoolLit(true)); assert_eq!(run("(<= 2 2 3 4 5 6)", app), LispExpr::BoolLit(true)); } fn eval_logical(app: &mut AppState) { assert_eq!(run("(and #t #t)", app), LispExpr::BoolLit(true)); assert_eq!(run("(or #f #t)", app), LispExpr::BoolLit(true)); assert_eq!(run("(not #t)", app), LispExpr::BoolLit(false)); assert_eq!(run("(not #f)", app), run("(not (not #t))", app)); } }