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
*** 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:

View file

@ -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<Box<Ctr>, 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<Box<Ctr>, 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;

View file

@ -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,
}
}

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

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> {
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, String>
},
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())),
}
}

View file

@ -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<Ctr, String> {
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<Ctr, String> {
let mut result: Box<Ctr> = Box::new(Ctr::None);
if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool {
let res: Result<Box<Ctr>, String>;
println!("let: {}", eval_form);
if let Ctr::Seg(ref eval_tree) = eval_form {
res = eval(&eval_tree, &mut localsyms);
} else {

View file

@ -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<Ctr, String> {
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<C
if let Ctr::String(ref doc) = *doc_tree.car {
match &*doc_tree.cdr {
// define a variable
Ctr::Seg(data_tree) if is_var => 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());
}
} else {
return Err("impossible args to export".to_string());
env::set_var(identifier.clone(), body.to_string());
}
}
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<C
if let Ctr::Seg(ref bodies) = *data_tree.cdr {
syms.insert(
identifier.clone(),
Symbol {
value: ValueType::FuncForm(UserFn {
ast: Box::new(bodies.clone()),
arg_syms: arg_list.clone(),
}),
name: identifier.clone(),
args: Args::Lazy(arg_list.len() as u128),
docs: doc.to_owned(),
conditional_branches: false,
},
Symbol::from_ast(
identifier, doc, bodies,
Some(arg_list),
),
);
} else {
return Err(
@ -266,6 +274,9 @@ pub fn lambda_callback(
if let Ctr::Symbol(ref s) = *arg {
args.push(s.clone());
true
} else if let Ctr::None = *arg {
// no args case
true
} else {
false
}
@ -273,10 +284,14 @@ pub fn lambda_callback(
Err("all elements of first argumnets must be symbols".to_string())
} else {
if let Ctr::Seg(ref eval_head) = *ast.cdr {
if let Ctr::Seg(_) = *eval_head.car {
Ok(Ctr::Lambda(UserFn{
ast: Box::new(eval_head.clone()),
arg_syms: args,
}))
} else {
Err("function body must be in list form".to_string())
}
} else {
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::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, String> {
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, String>
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())),
}

View file

@ -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<Ctr>;
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<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::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!()
}
}
}