Several changes, see commit msg

* clean up all tests
* bugfix for zero value functions, and test
* removed expand function, put in snippets
* added doc strings to Symbol type
* added doc strings to symbol declarations
* implemented display for Args type
* wrote a help function
* wrote docstrings for all builtins and config vars
This commit is contained in:
Ava Hahn 2023-03-05 22:18:49 -08:00
parent 4b587f11ab
commit dc6342bc74
Signed by untrusted user who does not match committer: affine
GPG key ID: 3A4645B8CF806069
16 changed files with 575 additions and 677 deletions

View file

@ -36,26 +36,19 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("append"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses any number of arguments collecting them into a list.
If the first argument is a list, all other arguments are added sequentially to the end of the list contained in the first argument.".to_string(),
value: ValueType::Internal(Rc::new(append::append_callback)),
},
);
syms.insert(
"expand".to_string(),
Symbol {
name: String::from("expand"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
value: ValueType::Internal(Rc::new(append::expand_callback)),
},
);
syms.insert(
"echo".to_string(),
Symbol {
name: String::from("echo"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses any number of arguments. Prints their evaluated values on a new line for each.".to_string(),
value: ValueType::Internal(Rc::new(_echo_callback)),
},
);
@ -66,6 +59,14 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("if"),
args: Args::Lazy(3),
conditional_branches: true,
docs: "accepts three bodies, a condition, an unevaluated consequence, and an alternative consequence.
If the condition is evaluated to true, the first consequence is evaluated.
If the condition is evaluated to false, the second consequence is evaluated.
Otherwise, an error is thrown.
example: (if my-state-switch
(do-my-thing)
(else-an-other-thing))".to_string(),
value: ValueType::Internal(Rc::new(control::if_callback)),
},
);
@ -76,6 +77,19 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("let"),
args: Args::Infinite,
conditional_branches: true,
docs: "creates a stack of local variables for a sequence of operations.
returns the result of the final operation.
example: (let ((step1 'hello')
(step2 (concat step1 '-'))
(step3 (concat step2 'world')))
(echo step3)
(some-func some-args))
In this example step1, step2, and step3 are created sequentially.
Then, the echo form is evaluated, printing 'hello-world'.
Finally, the some-func form is evaluated.
Since the call to some-func is the final form, its value is returned.".to_string(),
value: ValueType::Internal(Rc::new(control::let_callback)),
},
);
@ -86,6 +100,13 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("while"),
args: Args::Infinite,
conditional_branches: true,
docs: "traverses a list of N un-evaluated forms.
the first form is expected to evaluate to a boolean. if it evaluates to false, while will stop and return. Otherwise, while will evaluate each form in a loop.
example: (while (check-my-state)
(do-thing-1 args)
(do-thing-2 args)
(edit-state my-state))".to_string(),
value: ValueType::Internal(Rc::new(control::while_callback)),
},
);
@ -96,6 +117,16 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("circuit"),
args: Args::Infinite,
conditional_branches: true,
docs: "traverses a list of N un-evaluated forms.
evaluates each one until it stops. Circuit will stop when a form errors during evaluation.
Circuit will also stop when a form does not evaluate to a boolean, or evaluates to false.
example: (circuit (eq? (do-operation) myresult)
(and state1 state2 (boolean-operation3))
false
(do-another-operation))
in this example, do-another-operation will not be called".to_string(),
value: ValueType::Internal(Rc::new(control::circuit_callback)),
},
);
@ -106,6 +137,9 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("and"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 AND arg2, and then calculates prev_result AND next_arg.
returns final result.".to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_and_callback)),
},
);
@ -116,6 +150,9 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("or"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 OR arg2, and then calculates prev_result OR next_arg.
returns final result.".to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_or_callback)),
},
);
@ -126,6 +163,8 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("not"),
args: Args::Strict(vec![Type::Bool]),
conditional_branches: false,
docs: "takes a single argument (expects a boolean).
returns false if arg is true or true if arg is false.".to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_not_callback)),
},
);
@ -136,6 +175,9 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("eq?"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses a list of N arguments.
returns true if all arguments hold the same value.
NOTE: 1 and 1.0 are the same, but '1' 'one' or one (symbol) aren't".to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_iseq_callback)),
},
);
@ -146,10 +188,24 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("toggle"),
args: Args::Lazy(1),
conditional_branches: true,
docs: "switches a boolean symbol between true or false.
Takes a single argument (a symbol). Looks it up in the variable table.
Either sets the symbol to true if it is currently false, or vice versa.".to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_toggle_callback)),
},
);
syms.insert(
"help".to_string(),
Symbol {
name: String::from("help"),
args: Args::Strict(vec![Type::Symbol]),
conditional_branches: true,
docs: "prints help text for a given symbol. Expects only one argument.".to_string(),
value: ValueType::Internal(Rc::new(_help_callback)),
},
);
Ok(())
}
@ -170,6 +226,18 @@ pub fn dynamic_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("define"),
args: Args::Infinite,
conditional_branches: true,
docs: "allows user to define functions and variables.
A call may take one of three forms:
1. variable declaration:
Takes a name, doc string, and a value.
(def myvar 'my special variable' 'my var value')
2. function declaration:
Takes a name, doc string, list of arguments, and one or more bodies to evaluate.
Result of evaluating the final body is returned.
(def myfunc 'does a thing' (myarg1 myarg2) (dothing myarg1 myarg2) (add myarg1 myarg2))
3. symbol un-definition:
Takes just a name. Removes variable from table.
(def useless-var)".to_string(),
value: ValueType::Internal(Rc::new(
move |ast: &Seg, syms: &mut SymTable| -> Result<Ctr, String> {
_store_callback(ast, syms, env_cfg_user_form)
@ -191,80 +259,132 @@ fn _echo_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::None)
}
fn _help_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
if ast.len() != 1 {
return Err("help only takes a single argument".to_string())
}
if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(ref sym) = syms.get(symbol) {
let args_str: String;
if let ValueType::VarForm(_) = sym.value {
args_str = "(its a variable)".to_string();
} else {
args_str = sym.args.to_string();
}
println!("NAME: {0}\n
ARGS: {1}\n
DOCUMENTATION:\n
{2}\n
CURRENT VALUE AND/OR BODY:
(TODO)", sym.name, args_str, sym.docs);
} else {
return Err("undefined symbol".to_string())
}
} else {
return Err("help should only be called on a symbol".to_string())
}
Ok(Ctr::None)
}
fn _store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<Ctr, String> {
let is_var = ast.len() == 2;
let is_var = ast.len() == 3;
if let Ctr::Symbol(ref identifier) = *ast.car {
match &*ast.cdr {
Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), syms) {
Ok(seg) => {
if let Ctr::Seg(ref val) = *seg {
syms.insert(
identifier.clone(),
Symbol {
value: ValueType::VarForm(val.car.clone()),
name: identifier.clone(),
args: Args::None,
conditional_branches: false,
},
);
if env_cfg {
env::set_var(identifier.clone(), val.car.to_string());
}
} else {
return Err("impossible args to export".to_string());
}
}
Err(e) => return Err(format!("couldnt eval symbol: {}", e)),
},
Ctr::Seg(data_tree) if !is_var => {
if let Ctr::Seg(ref args) = *data_tree.car {
let mut arg_list = vec![];
if !args.circuit(&mut |c: &Ctr| -> bool {
if let Ctr::Symbol(ref arg) = c {
arg_list.push(arg.clone());
true
} else {
false
}
}) {
return Err(
"all arguments defined for function must be of type symbol".to_string()
);
};
// define a symbol
Ctr::Seg(doc_tree) => {
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 {
syms.insert(
identifier.clone(),
Symbol {
value: ValueType::VarForm(val.car.clone()),
name: identifier.clone(),
args: Args::None,
docs: doc.to_owned(),
conditional_branches: false,
},
);
if env_cfg {
env::set_var(identifier.clone(), val.car.to_string());
}
} else {
return Err("impossible args to export".to_string());
}
}
Err(e) => return Err(format!("couldnt eval symbol: {}", e)),
},
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),
conditional_branches: false,
},
);
} else {
return Err(
"expected one or more function bodies in function definition"
.to_string(),
);
// define a function
Ctr::Seg(data_tree) if !is_var => {
if let Ctr::Seg(ref args) = *data_tree.car {
let mut arg_list = vec![];
if !args.circuit(&mut |c: &Ctr| -> bool {
if let Ctr::Symbol(ref arg) = c {
arg_list.push(arg.clone());
true
} else if let Ctr::None = c {
// a user cannot type a None
// this case represents no args
true
} else {
false
}
}) {
return Err(
"all arguments defined for function must be of type symbol".to_string()
);
};
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,
},
);
} else {
return Err(
"expected one or more function bodies in function definition"
.to_string(),
);
}
} else {
return Err("expected list of arguments in function definition".to_string());
}
},
// theres a name and a doc string but nothing else
_ => return Err("have name and doc string, but no body.".to_string())
}
} else {
return Err("expected list of arguments in function definition".to_string());
return Err("doc string is a required argument".to_string())
}
}
},
// undefine a symbol
Ctr::None => {
syms.remove(&identifier.to_string());
if env_cfg {
env::remove_var(identifier);
}
}
_ => return Err("args not in standard form".to_string()),
},
_ => return Err("arguments not in standard form".to_string()),
}
} else {
return Err("first argument to export must be a symbol".to_string());
return Err("first argument to export must be a symbol".to_string())
}
Ok(Ctr::None)
}