Fully fledged lambdas, along with efficiency tweaks across the ast

This commit is contained in:
Ava Apples Affine 2023-03-13 15:02:19 -07:00
parent b0bd369c1d
commit 8efa1dbaad
Signed by: affine
GPG key ID: 3A4645B8CF806069
10 changed files with 264 additions and 70 deletions

View file

@ -152,7 +152,34 @@ CURRENT VALUE AND/OR BODY:
#+END_SRC #+END_SRC
*** TODO Quote and Eval *** 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 Defining variables and functions
**** TODO Anatomy **** TODO Anatomy
**** TODO Naming conventions **** 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 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. 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 list contains via circuit
*** TODO Map function *** TODO Map function
- DOCUMENTATION + TEST: - DOCUMENTATION + TEST:

View file

@ -16,13 +16,14 @@
*/ */
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::SymTable; use crate::sym::{SymTable, call_lambda};
/* iterates over a syntax tree /* iterates over a syntax tree
* returns a NEW LIST of values * returns a NEW LIST of values
* representing the simplest possible form of the input * representing the simplest possible form of the input
*/ */
pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> { pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
println!("E: {}", ast);
// data to return // data to return
let mut ret = Box::from(Ctr::None); let mut ret = Box::from(Ctr::None);
let mut first = true; let mut first = true;
@ -39,7 +40,17 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
let mut none = false; let mut none = false;
while !none { while !none {
match &**arg_car { 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) => { Ctr::Symbol(ref tok) => {
let outer_scope_seg_holder: Seg; let outer_scope_seg_holder: Seg;

View file

@ -244,7 +244,7 @@ impl Clone for Ctr {
Ctr::Float(s) => Ctr::Float(*s), Ctr::Float(s) => Ctr::Float(*s),
Ctr::Bool(s) => Ctr::Bool(*s), Ctr::Bool(s) => Ctr::Bool(*s),
Ctr::Seg(s) => Ctr::Seg(s.clone()), 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, Ctr::None => Ctr::None,
} }
} }

View file

@ -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(()) Ok(())
} }

View file

@ -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<Ctr, String> { pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
match &*ast.car { match &*ast.car {
Ctr::Bool(_) => Ok(*ast.car.clone()), Ctr::Bool(_) => Ok(*ast.car.clone()),
Ctr::Symbol(_) => Err("not clear how to cast a symbol to a bool".to_string()),
Ctr::String(s) => { Ctr::String(s) => {
if s == "true" { if s == "true" {
Ok(Ctr::Bool(true)) Ok(Ctr::Bool(true))
@ -139,7 +138,7 @@ pub fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
}, },
Ctr::Integer(i) => Ok(Ctr::Bool(*i == 0)), Ctr::Integer(i) => Ok(Ctr::Bool(*i == 0)),
Ctr::Float(f) => Ok(Ctr::Bool(*f == 0.0)), Ctr::Float(f) => Ok(Ctr::Bool(*f == 0.0)),
Ctr::Seg(_) => Err("cannot convert list to a boolean".to_string()), _ => Err(format!("cannot convert a {} to a boolean",
Ctr::None => Err("Impossible task: cannot convert none to bool".to_string()), ast.car.to_type())),
} }
} }

View file

@ -17,7 +17,7 @@
use crate::eval::eval; use crate::eval::eval;
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::{Args, SymTable, Symbol, ValueType}; use crate::sym::{SymTable, Symbol};
pub const IF_DOCSTRING: &str = pub const IF_DOCSTRING: &str =
"accepts three bodies, a condition, an unevaluated consequence, and an alternative consequence. "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<Ctr, String> {
localsyms.insert( localsyms.insert(
name.clone(), name.clone(),
Symbol { Symbol::from_ast(
name: name.clone(), name, &"variable used in let form".to_string(),
args: Args::None, &Seg::from_mono(Box::new(*var_val_res.unwrap().clone())),
conditional_branches: false, None),
docs: "variable used in let form".to_string(),
value: ValueType::VarForm(Box::new(*var_val_res.unwrap().clone())),
},
); );
} }
} else if let Ctr::None = *var_form.car {
// nothing to declare
return true;
} else { } else {
eprintln!("improper declaration of {}: not a list", var_decl); eprintln!("improper declaration of {}: not a list", var_decl);
return false; return false;
@ -176,6 +176,7 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
let mut result: Box<Ctr> = Box::new(Ctr::None); let mut result: Box<Ctr> = Box::new(Ctr::None);
if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool { if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool {
let res: Result<Box<Ctr>, String>; let res: Result<Box<Ctr>, String>;
println!("let: {}", eval_form);
if let Ctr::Seg(ref eval_tree) = eval_form { if let Ctr::Seg(ref eval_tree) = eval_form {
res = eval(&eval_tree, &mut localsyms); res = eval(&eval_tree, &mut localsyms);
} else { } else {

View file

@ -17,7 +17,7 @@
use crate::eval::eval; use crate::eval::eval;
use crate::segment::{Ctr, Seg}; use crate::segment::{Ctr, Seg};
use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType}; use crate::sym::{SymTable, Symbol, UserFn, ValueType};
use std::env; use std::env;
pub const QUOTE_DOCSTRING: &str = "takes a single unevaluated tree and returns it as it is: unevaluated."; 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)"; (def useless-var)";
pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<Ctr, String> { pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<Ctr, String> {
println!("def: {}", ast);
let is_var = ast.len() == 3; let is_var = ast.len() == 3;
if let Ctr::Symbol(ref identifier) = *ast.car { if let Ctr::Symbol(ref identifier) = *ast.car {
match &*ast.cdr { match &*ast.cdr {
@ -109,27 +110,40 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
if let Ctr::String(ref doc) = *doc_tree.car { if let Ctr::String(ref doc) = *doc_tree.car {
match &*doc_tree.cdr { match &*doc_tree.cdr {
// define a variable // define a variable
Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), syms) { Ctr::Seg(data_tree) if is_var => {
Ok(seg) => { let eval_arg: &Seg;
if let Ctr::Seg(ref val) = *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( syms.insert(
identifier.clone(), identifier.clone(),
Symbol { Symbol::from_ast(
value: ValueType::VarForm(val.car.clone()), identifier, doc,
name: identifier.clone(), &Seg::from_mono(body.clone()), None
args: Args::None, ),
docs: doc.to_owned(),
conditional_branches: false,
},
); );
if env_cfg { 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 // define a function
@ -157,16 +171,10 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
if let Ctr::Seg(ref bodies) = *data_tree.cdr { if let Ctr::Seg(ref bodies) = *data_tree.cdr {
syms.insert( syms.insert(
identifier.clone(), identifier.clone(),
Symbol { Symbol::from_ast(
value: ValueType::FuncForm(UserFn { identifier, doc, bodies,
ast: Box::new(bodies.clone()), Some(arg_list),
arg_syms: arg_list.clone(), ),
}),
name: identifier.clone(),
args: Args::Lazy(arg_list.len() as u128),
docs: doc.to_owned(),
conditional_branches: false,
},
); );
} else { } else {
return Err( return Err(
@ -266,6 +274,9 @@ pub fn lambda_callback(
if let Ctr::Symbol(ref s) = *arg { if let Ctr::Symbol(ref s) = *arg {
args.push(s.clone()); args.push(s.clone());
true true
} else if let Ctr::None = *arg {
// no args case
true
} else { } else {
false false
} }
@ -273,10 +284,14 @@ pub fn lambda_callback(
Err("all elements of first argumnets must be symbols".to_string()) Err("all elements of first argumnets must be symbols".to_string())
} else { } else {
if let Ctr::Seg(ref eval_head) = *ast.cdr { if let Ctr::Seg(ref eval_head) = *ast.cdr {
if let Ctr::Seg(_) = *eval_head.car {
Ok(Ctr::Lambda(UserFn{ Ok(Ctr::Lambda(UserFn{
ast: Box::new(eval_head.clone()), ast: Box::new(eval_head.clone()),
arg_syms: args, arg_syms: args,
})) }))
} else {
Err("function body must be in list form".to_string())
}
} else { } else {
Err("not enough args".to_string()) Err("not enough args".to_string())
} }

View file

@ -46,6 +46,7 @@ pub fn concat_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
Ctr::Float(f) => string.push_str(&f.to_string()), Ctr::Float(f) => string.push_str(&f.to_string()),
Ctr::Bool(b) => string.push_str(&b.to_string()), Ctr::Bool(b) => string.push_str(&b.to_string()),
Ctr::Seg(c) => string.push_str(&c.to_string()), Ctr::Seg(c) => string.push_str(&c.to_string()),
Ctr::Lambda(l) => string.push_str(&l.to_string()),
Ctr::None => (), Ctr::None => (),
} }
true true
@ -67,6 +68,7 @@ pub fn strlen_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
Ctr::Float(f) => Ok(Ctr::Integer(f.to_string().len() as i128)), 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::Bool(b) => Ok(Ctr::Integer(b.to_string().len() as i128)),
Ctr::Seg(c) => Ok(Ctr::Integer(c.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 // highly suspicious case below
Ctr::None => Ok(Ctr::Integer(0)), Ctr::None => Ok(Ctr::Integer(0)),
} }
@ -83,6 +85,7 @@ pub fn strcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String>
Ctr::Float(f) => Ok(Ctr::String(f.to_string())), Ctr::Float(f) => Ok(Ctr::String(f.to_string())),
Ctr::Bool(b) => Ok(Ctr::String(b.to_string())), Ctr::Bool(b) => Ok(Ctr::String(b.to_string())),
Ctr::Seg(c) => Ok(Ctr::String(c.to_string())), Ctr::Seg(c) => Ok(Ctr::String(c.to_string())),
Ctr::Lambda(l) => Ok(Ctr::String(l.to_string())),
// highly suspicious case below // highly suspicious case below
Ctr::None => Ok(Ctr::String(String::new())), Ctr::None => Ok(Ctr::String(String::new())),
} }

View file

@ -229,11 +229,15 @@ impl fmt::Display for Args {
impl fmt::Display for UserFn { impl fmt::Display for UserFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(lambda: ("); write!(f, "(lambda (")?;
for i in self.arg_syms { let mut arg_iter = (&self.arg_syms).into_iter();
write!(f, "{} ", i); 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(()) Ok(())
} }
@ -265,12 +269,7 @@ impl Symbol {
let outer_scope_seg_storage: Seg; let outer_scope_seg_storage: Seg;
let outer_scope_eval: Box<Ctr>; let outer_scope_eval: Box<Ctr>;
if !self.conditional_branches { if !self.conditional_branches {
let outer_scope_eval_result = eval(args, syms); outer_scope_eval = 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();
match *outer_scope_eval { match *outer_scope_eval {
Ctr::Seg(ref segment) => evaluated_args = segment, Ctr::Seg(ref segment) => evaluated_args = segment,
_ => { _ => {
@ -282,6 +281,7 @@ impl Symbol {
evaluated_args = args; evaluated_args = args;
} }
println!("args: {}", evaluated_args);
self.args.validate_inputs(evaluated_args)?; self.args.validate_inputs(evaluated_args)?;
match &self.value { match &self.value {
ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())), 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<Vec<String>>,
) -> 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<Ctr>,
syms: &mut SymTable,
) -> Result<Box<Ctr>, 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)
} }

View file

@ -1,4 +1,4 @@
mod var_lib_tests { mod decl_lib_tests {
use relish::ast::{eval, lex, Ctr, SymTable}; use relish::ast::{eval, lex, Ctr, SymTable};
use relish::stdlib::{dynamic_stdlib, static_stdlib}; use relish::stdlib::{dynamic_stdlib, static_stdlib};
@ -297,4 +297,86 @@ mod var_lib_tests {
result.to_string(), 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!()
}
}
} }