diff --git a/Readme.org b/Readme.org index 5f8cac3..0cfb745 100644 --- a/Readme.org +++ b/Readme.org @@ -152,7 +152,34 @@ CURRENT VALUE AND/OR BODY: #+END_SRC *** TODO Quote and Eval -*** TODO Lambda +*** Lambda +Another form of homoiconicity is the *anonymous function*. +This is a nameless function being passed around as data. +It can be bound to a variable, or called directly. +An *anonymous function* is created with the ~lambda~ function. + +Here is an example of a lambda function: +#+BEGIN_SRC lisp + (lambda (x y) (add x y)) + ;; | ^ this is the function body + ;; +-> this is the argument list +#+END_SRC + +The result of the lambda call is returned as a piece of data. +It can later be called inline or bound to a variable. + +Here is an example of an inline lambda call: +#+BEGIN_SRC lisp + ((lambda (x y) (add x y)) 1 2) +#+END_SRC +This call returns ~3~. + +Here is the lambda bound to a variable inside a let statement: +#+BEGIN_SRC lisp + (let ((adder (lambda (x y) (add x y)))) ;; let form contains one local var + (adder 1 2)) ;; local var (lambda) called here +#+END_SRC + *** TODO Defining variables and functions **** TODO Anatomy **** TODO Naming conventions @@ -322,21 +349,6 @@ This contains any executable target of this project. Notably the main shell file Note: this section will not show the status of each item unless you are viewing it with a proper orgmode viewer. Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered. -*** TODO Lambda -IMPLEMENTATION: - - [-] implement a new Ctr type to encapsulate function and args - - [ ] need a new case in store for when bound to a var - (honestly store should be rewritten) - - [ ] need a case in eval that mirrors the function call case - -DOCUMENTATION: -- [ ] let case for creating and applying a lambda - -TEST: -- [ ] lambda to_string input equivalency -- [ ] (eq? ((lambda (x y) (add x y)) 1 2) (add 1 2)) -- [ ] let case for creating and applying a lambda - *** TODO list contains via circuit *** TODO Map function - DOCUMENTATION + TEST: diff --git a/src/eval.rs b/src/eval.rs index c6f7c7d..2714310 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -16,13 +16,14 @@ */ use crate::segment::{Ctr, Seg}; -use crate::sym::SymTable; +use crate::sym::{SymTable, call_lambda}; /* iterates over a syntax tree * returns a NEW LIST of values * representing the simplest possible form of the input */ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result, String> { + println!("E: {}", ast); // data to return let mut ret = Box::from(Ctr::None); let mut first = true; @@ -39,7 +40,17 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result, String> { let mut none = false; while !none { match &**arg_car { - Ctr::Seg(ref inner) => car = eval(inner, syms)?, + Ctr::Seg(ref inner) => { + let interm = eval(inner, syms)?; + if let Ctr::Lambda(ref l) = *interm { + match call_lambda(l, arg_cdr, syms) { + Ok(s) => return Ok(s.clone()), + Err(s) => return Err(format!("err in call to lambda: {}", s)), + } + } else { + car = interm; + } + }, Ctr::Symbol(ref tok) => { let outer_scope_seg_holder: Seg; diff --git a/src/segment.rs b/src/segment.rs index 7a38483..fc93d7f 100644 --- a/src/segment.rs +++ b/src/segment.rs @@ -244,7 +244,7 @@ impl Clone for Ctr { Ctr::Float(s) => Ctr::Float(*s), Ctr::Bool(s) => Ctr::Bool(*s), Ctr::Seg(s) => Ctr::Seg(s.clone()), - Ctr::Lambda(s) => Ctr::Seg(s.clone()), + Ctr::Lambda(s) => Ctr::Lambda(s.clone()), Ctr::None => Ctr::None, } } diff --git a/src/stl.rs b/src/stl.rs index d72de59..695b398 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -492,6 +492,17 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> { }, ); + syms.insert( + "lambda".to_string(), + Symbol { + name: String::from("lambda"), + args: Args::Lazy(2), + conditional_branches: true, + docs: decl::LAMBDA_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(decl::lambda_callback)), + } + ); + Ok(()) } diff --git a/src/stl/boolean.rs b/src/stl/boolean.rs index 25d7c4e..da0232d 100644 --- a/src/stl/boolean.rs +++ b/src/stl/boolean.rs @@ -127,7 +127,6 @@ Integers and Floats will cast to true if they are 0 and false otherwise."; pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result { match &*ast.car { Ctr::Bool(_) => Ok(*ast.car.clone()), - Ctr::Symbol(_) => Err("not clear how to cast a symbol to a bool".to_string()), Ctr::String(s) => { if s == "true" { Ok(Ctr::Bool(true)) @@ -139,7 +138,7 @@ pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result }, Ctr::Integer(i) => Ok(Ctr::Bool(*i == 0)), Ctr::Float(f) => Ok(Ctr::Bool(*f == 0.0)), - Ctr::Seg(_) => Err("cannot convert list to a boolean".to_string()), - Ctr::None => Err("Impossible task: cannot convert none to bool".to_string()), + _ => Err(format!("cannot convert a {} to a boolean", + ast.car.to_type())), } } diff --git a/src/stl/control.rs b/src/stl/control.rs index 6c2c7ae..64fc449 100644 --- a/src/stl/control.rs +++ b/src/stl/control.rs @@ -17,7 +17,7 @@ use crate::eval::eval; use crate::segment::{Ctr, Seg}; -use crate::sym::{Args, SymTable, Symbol, ValueType}; +use crate::sym::{SymTable, Symbol}; pub const IF_DOCSTRING: &str = "accepts three bodies, a condition, an unevaluated consequence, and an alternative consequence. @@ -151,15 +151,15 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result { localsyms.insert( name.clone(), - Symbol { - name: name.clone(), - args: Args::None, - conditional_branches: false, - docs: "variable used in let form".to_string(), - value: ValueType::VarForm(Box::new(*var_val_res.unwrap().clone())), - }, + Symbol::from_ast( + name, &"variable used in let form".to_string(), + &Seg::from_mono(Box::new(*var_val_res.unwrap().clone())), + None), ); } + } else if let Ctr::None = *var_form.car { + // nothing to declare + return true; } else { eprintln!("improper declaration of {}: not a list", var_decl); return false; @@ -176,6 +176,7 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result { let mut result: Box = Box::new(Ctr::None); if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool { let res: Result, String>; + println!("let: {}", eval_form); if let Ctr::Seg(ref eval_tree) = eval_form { res = eval(&eval_tree, &mut localsyms); } else { diff --git a/src/stl/decl.rs b/src/stl/decl.rs index d154212..3e9cf38 100644 --- a/src/stl/decl.rs +++ b/src/stl/decl.rs @@ -17,7 +17,7 @@ use crate::eval::eval; use crate::segment::{Ctr, Seg}; -use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType}; +use crate::sym::{SymTable, Symbol, UserFn, ValueType}; use std::env; pub const QUOTE_DOCSTRING: &str = "takes a single unevaluated tree and returns it as it is: unevaluated."; @@ -101,6 +101,7 @@ pub const STORE_DOCSTRING: &str = "allows user to define functions and variables (def useless-var)"; pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result { + println!("def: {}", ast); let is_var = ast.len() == 3; if let Ctr::Symbol(ref identifier) = *ast.car { match &*ast.cdr { @@ -109,27 +110,40 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result match eval(&Box::new(data_tree), syms) { - Ok(seg) => { - if let Ctr::Seg(ref val) = *seg { + Ctr::Seg(data_tree) if is_var => { + let eval_arg: &Seg; + let outer_maybe_eval_seg: Seg; + let mut expand = false; + if let Ctr::Seg(ref eval_me) = *data_tree.car { + eval_arg = eval_me; + } else { + outer_maybe_eval_seg = Seg::from_mono(data_tree.car.clone()); + eval_arg = &outer_maybe_eval_seg; + expand = true; + } + match eval(eval_arg, syms) { + Ok(ctr) => { + let mut body = ctr; + if expand { + if let Ctr::Seg(ref s) = *body { + body = s.car.clone(); + } else { + return Err("impossible expansion".to_string()) + } + } syms.insert( identifier.clone(), - Symbol { - value: ValueType::VarForm(val.car.clone()), - name: identifier.clone(), - args: Args::None, - docs: doc.to_owned(), - conditional_branches: false, - }, + Symbol::from_ast( + identifier, doc, + &Seg::from_mono(body.clone()), None + ), ); if env_cfg { - env::set_var(identifier.clone(), val.car.to_string()); + env::set_var(identifier.clone(), body.to_string()); } - } else { - return Err("impossible args to export".to_string()); } + Err(e) => return Err(format!("couldnt eval symbol: {}", e)), } - Err(e) => return Err(format!("couldnt eval symbol: {}", e)), }, // define a function @@ -157,16 +171,10 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result Result { Ctr::Float(f) => string.push_str(&f.to_string()), Ctr::Bool(b) => string.push_str(&b.to_string()), Ctr::Seg(c) => string.push_str(&c.to_string()), + Ctr::Lambda(l) => string.push_str(&l.to_string()), Ctr::None => (), } true @@ -67,6 +68,7 @@ pub fn strlen_callback(ast: &Seg, _syms: &mut SymTable) -> Result { Ctr::Float(f) => Ok(Ctr::Integer(f.to_string().len() as i128)), Ctr::Bool(b) => Ok(Ctr::Integer(b.to_string().len() as i128)), Ctr::Seg(c) => Ok(Ctr::Integer(c.to_string().len() as i128)), + Ctr::Lambda(l) => Ok(Ctr::Integer(l.to_string().len() as i128)), // highly suspicious case below Ctr::None => Ok(Ctr::Integer(0)), } @@ -83,6 +85,7 @@ pub fn strcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result Ctr::Float(f) => Ok(Ctr::String(f.to_string())), Ctr::Bool(b) => Ok(Ctr::String(b.to_string())), Ctr::Seg(c) => Ok(Ctr::String(c.to_string())), + Ctr::Lambda(l) => Ok(Ctr::String(l.to_string())), // highly suspicious case below Ctr::None => Ok(Ctr::String(String::new())), } diff --git a/src/sym.rs b/src/sym.rs index 8f0d16e..45e4c8e 100644 --- a/src/sym.rs +++ b/src/sym.rs @@ -229,11 +229,15 @@ impl fmt::Display for Args { impl fmt::Display for UserFn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "(lambda: ("); - for i in self.arg_syms { - write!(f, "{} ", i); + write!(f, "(lambda (")?; + let mut arg_iter = (&self.arg_syms).into_iter(); + if let Some(elem) = arg_iter.next() { + write!(f, "{}", elem)?; + for i in arg_iter { + write!(f, " {}", i)?; + } } - write!(f, ") {})", self.ast); + write!(f, ") {})", self.ast.car)?; Ok(()) } @@ -265,12 +269,7 @@ impl Symbol { let outer_scope_seg_storage: Seg; let outer_scope_eval: Box; if !self.conditional_branches { - let outer_scope_eval_result = eval(args, syms); - // dont listen to clippy. using ? will move the value. - if let Err(s) = outer_scope_eval_result { - return Err(s); - } - outer_scope_eval = outer_scope_eval_result.unwrap(); + outer_scope_eval = eval(args, syms)?; match *outer_scope_eval { Ctr::Seg(ref segment) => evaluated_args = segment, _ => { @@ -282,6 +281,7 @@ impl Symbol { evaluated_args = args; } + println!("args: {}", evaluated_args); self.args.validate_inputs(evaluated_args)?; match &self.value { ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())), @@ -351,4 +351,64 @@ impl Symbol { } } } + + pub fn from_ast( + id: &String, + doc: &String, + ast: &Seg, + arg_list: Option>, + ) -> Symbol { + let args: Args; + let value: ValueType; + + if let Some(ref arg_syms) = arg_list { + println!("def a func form"); + value = ValueType::FuncForm(UserFn{ + ast: Box::new(ast.clone()), + arg_syms: arg_syms.clone(), + }); + args = Args::Lazy(arg_syms.len() as u128); + } else if let Ctr::Lambda(ref l) = *ast.car { + println!("def a func form (lambda)"); + args = Args::Lazy(l.arg_syms.len() as u128); + value = ValueType::FuncForm(l.clone()); + } else { + println!("def a var form"); + args = Args::None; + value = ValueType::VarForm(ast.car.clone()); + } + + Symbol { + name: id.clone(), + docs: doc.clone(), + conditional_branches: false, + args, value, + } + } +} + +pub fn call_lambda( + lam: &UserFn, + args_ctr: &Box, + syms: &mut SymTable, +) -> Result, String> { + let temp_sym = Symbol { + name: String::from("anonymous"), + conditional_branches: false, + docs: String::from("user defined lambda"), + args: Args::Lazy(lam.arg_syms.len() as u128), + value: ValueType::FuncForm(lam.clone()), + }; + + let args: &Seg; + let outer_scope_maybe_args: Seg; + if let Ctr::Seg(ref args_head) = **args_ctr { + args = args_head; + } else { + outer_scope_maybe_args = Seg::from_mono( + Box::new(*args_ctr.clone())); + args = &outer_scope_maybe_args; + } + + temp_sym.call(args, syms) } diff --git a/tests/test_vars.rs b/tests/test_lib_decl.rs similarity index 79% rename from tests/test_vars.rs rename to tests/test_lib_decl.rs index 47a8d76..9dbba34 100644 --- a/tests/test_vars.rs +++ b/tests/test_lib_decl.rs @@ -1,4 +1,4 @@ -mod var_lib_tests { +mod decl_lib_tests { use relish::ast::{eval, lex, Ctr, SymTable}; use relish::stdlib::{dynamic_stdlib, static_stdlib}; @@ -297,4 +297,86 @@ mod var_lib_tests { result.to_string(), ); } + + #[test] + fn test_lambda_str_equivalency_list() { + let document = "(lambda (x y) (add x y))"; + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + document.to_string(), + ); + } + + #[test] + fn test_lambda_str_equivalency_no_args() { + let document = "(lambda () (add 1 2))"; + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + document.to_string(), + ); + } + + #[test] + fn test_lambda_inline_call() { + let document = "((lambda (x y) (add x y)) 1 2)"; + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + let it = *eval( + &lex(&document.to_string()).unwrap(), + &mut syms).unwrap(); + println!("final return: {}", it); + if let Ctr::Integer(i) = it { + assert_eq!(i, 3) + } else { + panic!() + } + } + + #[test] + fn test_lambda_let_call() { + let document = "(let ((adder (lambda (x y) (add x y)))) + (adder 1 2))"; + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + let it = *eval( + &lex(&document.to_string()).unwrap(), + &mut syms).unwrap(); + println!("{}", it); + if let Ctr::Integer(i) = it { + assert_eq!(i, 3) + } else { + panic!() + } + } + + #[test] + fn test_lambda_var_bound_call() { + let document = "(let (()) + (def adder 'my adder' (lambda (x y) (add x y))) + (adder 1 2))"; + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + let it = *eval( + &lex(&document.to_string()).unwrap(), + &mut syms).unwrap(); + println!("{}", it); + if let Ctr::Integer(i) = it { + assert_eq!(i, 3) + } else { + panic!() + } + } }