From ca48cd3a51a19f35f6d2b25a5b3c011c759b6be3 Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 24 Mar 2021 13:04:17 +0530 Subject: add lisp primitives: set!, define, lambda, if --- src/lisp/env.rs | 206 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 171 insertions(+), 35 deletions(-) diff --git a/src/lisp/env.rs b/src/lisp/env.rs index 94c0e05..6338f91 100644 --- a/src/lisp/env.rs +++ b/src/lisp/env.rs @@ -1,18 +1,17 @@ use crate::{ app::AppState, - lisp::{error::LispError, expr::LispExpr, number::LispNumber, Environment}, + lisp::{ + error::LispError, + expr::{LispExpr, LispFunction}, + number::LispNumber, + Environment, + }, primitive, }; -use log::warn; +use std::collections::HashMap; -pub fn is_bound>(env: &mut Environment, name: S) -> bool { - env.get(name.as_ref()).is_some() -} - -pub fn new_binding>(env: &mut Environment, name: S, value: LispExpr) { - let _ = env.insert(name.as_ref().into(), value); -} +use log::{error, info}; pub fn with_prelude() -> Environment { let mut env = Environment::new(); @@ -27,7 +26,7 @@ pub fn with_prelude() -> Environment { )) } }); - primitive!(env, Some(2), "sub", |args, _| { + primitive!(env, Some(2), "-", |args, _| { if args.iter().any(|arg| !matches!(arg, LispExpr::Number(_))) { Err(LispError::EvalError) } else { @@ -49,43 +48,95 @@ pub fn with_prelude() -> Environment { )) } }); + 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()) + } + } + _ => { + error!("invalid args for `if` primitive"); + Err(LispError::EvalError) + } + } + }); + primitive!(env, Some(1), "and", |args, _| { + if args + .iter() + .any(|arg| matches!(arg, LispExpr::BoolLit(false))) + { + Ok(LispExpr::BoolLit(false)) + } else { + Ok(LispExpr::BoolLit(true)) + } + }); env } -pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result { - let env = &mut app.lisp_env; +pub fn eval( + expr: &LispExpr, + app: &mut AppState, + extra_env: Option<&Environment>, +) -> Result { match expr { LispExpr::Unit => Ok(expr.clone()), LispExpr::StringLit(_) => Ok(expr.clone()), LispExpr::Number(_) => Ok(expr.clone()), LispExpr::BoolLit(_) => Ok(expr.clone()), - LispExpr::Ident(ref id) => env - .get(id) - .ok_or_else(|| LispError::EvalError) - .map(Clone::clone), + LispExpr::Ident(ref id) => { + lookup_extended(extra_env.unwrap_or(&Environment::new()), &app.lisp_env, id) + } LispExpr::List(li) => { - let head = &li[0]; - match head { - LispExpr::Ident(func) => { - let func_expr = env - .get(func) - .map(Clone::clone) - .ok_or_else(|| LispError::EvalError)?; - match func_expr { - LispExpr::PrimitiveFunc(f) => { - let mut args = Vec::new(); - for item in li[1..].iter() { - args.push(eval(item, app)?); + 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..]), + _ => { + let func_expr = eval(&func_expr, app, extra_env)?; + match func_expr { + LispExpr::PrimitiveFunc(f) => { + let mut args = Vec::new(); + for item in li[1..].iter() { + args.push(eval(item, app, extra_env)?); + } + f.call(&args, app) } - f.call(&args, app) + LispExpr::Function(f) => { + info!("eval custom func"); + let mut args = Vec::new(); + for item in li[1..].iter() { + args.push(eval(item, app, extra_env)?); + } + if f.params.len() != args.len() { + info!("too many or too little number of args"); + Err(LispError::EvalError) // too many or too little number of args + } else { + let mut local_env: Environment = + f.params.into_iter().zip(args).collect(); + local_env.extend(app.lisp_env.clone()); + if let Some(env) = extra_env { + local_env.extend(env.clone()); + } + if f.body.is_empty() { + return Ok(LispExpr::Unit); + } else { + eval(&LispExpr::List(f.body), app, Some(&local_env)) + } + } + } + _ => Err(LispError::EvalError), } - _ => Err(LispError::EvalError), } - } - LispExpr::List(_) => { - // TODO - todo!(); - } + }, _ => Err(LispError::EvalError), } } @@ -93,9 +144,94 @@ pub fn eval(expr: &LispExpr, app: &mut AppState) -> Result } } +pub fn define_var(args: &[LispExpr], app: &mut AppState) -> Result { + if args.len() != 2 { + error!("Invalid arity for `define`"); + return Err(LispError::EvalError); + } + match args { + [LispExpr::Ident(id), expr] => { + let value = eval(&expr, app, None)?; + app.lisp_env.insert(id.into(), value); + return Ok(LispExpr::Unit); + } + _ => { + error!("Invalid usage of `define`"); + return Err(LispError::EvalError); + } + } +} + +pub fn set_var(args: &[LispExpr], app: &mut AppState) -> Result { + if args.len() != 2 { + error!("Invalid arity for `define`"); + return Err(LispError::EvalError); + } + match args { + [LispExpr::Ident(id), expr] => { + let value = eval(&expr, app, None)?; + return app + .lisp_env + .insert(id.into(), value) + .ok_or(LispError::EvalError); + } + _ => { + error!("Invalid usage of `define`"); + return Err(LispError::EvalError); + } + } +} + +pub fn create_lambda(cdr: &[LispExpr]) -> Result { + if cdr.len() != 2 { + // needs params and body + error!("needs params and body"); + return Err(LispError::EvalError); + } + info!("creating lambda"); + match cdr { + [LispExpr::List(params), LispExpr::List(body)] + if params.iter().all(|p| matches!(p, LispExpr::Ident(_))) => + { + return Ok(LispExpr::Function(LispFunction { + params: params + .into_iter() + .map(|p| unwrap_ident(p.clone())) + .collect::>(), + body: body.clone(), + })); + } + _ => { + error!("Invalid usage of `define`"); + return Err(LispError::EvalError); + } + } +} + +pub fn lookup_extended( + local: &Environment, + super_: &Environment, + key: &str, +) -> Result { + if let Some(e) = local.get(key) { + Ok(e.clone()) + } else if let Some(e) = super_.get(key) { + Ok(e.clone()) + } else { + Err(LispError::EvalError) + } +} + pub fn unwrap_number(n: &LispExpr) -> &LispNumber { match n { LispExpr::Number(i) => i, _ => panic!("unwrap_number expected number"), } } + +pub fn unwrap_ident(i: LispExpr) -> String { + match i { + LispExpr::Ident(i) => i, + _ => panic!("unwrap_ident expected string"), + } +} -- cgit v1.2.3