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

@ -136,13 +136,12 @@ 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 Clean up tests, simplify, convert some to unit tests, mention tests in Readme as docs
*** TODO Document all internal/builtin functions in the rustiest way possible
*** TODO Custom ast pretty print *** TODO Custom ast pretty print
*** TODO Help function *** TODO Help function
**** TODO add doc string to function form **** DONE add doc string to function form
**** TODO write doc strings to all symbols **** DONE write doc strings to all symbols
**** TODO help function outputs name and help doc **** DONE pretty printer for help doc
**** DONE help function outputs name, args, and help doc
**** TODO help function outputs current value or function form pretty printed **** TODO help function outputs current value or function form pretty printed
*** TODO Eval function *** TODO Eval function
*** TODO Input function *** TODO Input function
@ -152,7 +151,7 @@ Pull/Refactor the logic out of the configure functions.
Optionally return a list of new variables and/or functions? Optionally return a list of new variables and/or functions?
Will need a concatenate function for tables Will need a concatenate function for tables
*** TODO Main shell calls Load function on arg and exits *** TODO Main shell calls Load function on arg and exits
*** TODO Wrapping errors as they return from eval and call_sym *** TODO Can enter multiple lines of text, with formatting in repl
*** TODO arithmetic operations *** TODO arithmetic operations
**** TODO typecast (int) **** TODO typecast (int)
**** TODO typecast (float) **** TODO typecast (float)
@ -182,6 +181,7 @@ Will need a concatenate function for tables
**** TODO Optional form of process which allows fd redirecting **** TODO Optional form of process which allows fd redirecting
**** TODO Foreground process TTY **** TODO Foreground process TTY
**** TODO Background processes **** TODO Background processes
**** TODO Update config env var docstring with functions loaded or unloaded
*** TODO list operations *** TODO list operations
**** DONE append **** DONE append
**** DONE expand **** DONE expand

View file

@ -0,0 +1,7 @@
pub fn expand_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(_) = *ast.car {
Ok(*ast.car.clone())
} else {
Err("non list passed to expand!".to_string())
}
}

View file

@ -29,15 +29,19 @@ fn prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
/* loads defaults, evaluates config script */ /* loads defaults, evaluates config script */
pub fn configure(filename: String, syms: &mut SymTable) -> Result<(), String> { pub fn configure(filename: String, syms: &mut SymTable) -> Result<(), String> {
syms.insert( /*syms.insert(
"CFG_RELISH_POSIX".to_string(), "CFG_RELISH_POSIX".to_string(),
Symbol { Symbol {
name: String::from("CFG_RELISH_POSIX"), name: String::from("CFG_RELISH_POSIX"),
args: Args::None, args: Args::None,
conditional_branches: false, conditional_branches: false,
docs: "variable holding whether or not POSIX job control functions are to be loaded.
checked at shell startup by configuration daemon. not used afterwards.
default value: not set".to_string(),
value: ValueType::VarForm(Box::new(Ctr::String("0".to_string()))), value: ValueType::VarForm(Box::new(Ctr::String("0".to_string()))),
}, },
); );*/
syms.insert( syms.insert(
"CFG_RELISH_ENV".to_string(), "CFG_RELISH_ENV".to_string(),
@ -45,6 +49,12 @@ pub fn configure(filename: String, syms: &mut SymTable) -> Result<(), String> {
name: String::from("CFG_RELISH_ENV"), name: String::from("CFG_RELISH_ENV"),
args: Args::None, args: Args::None,
conditional_branches: false, conditional_branches: false,
docs: "variable holding whether or not vars and other symbols should be linked to process environment variables.
If set/defined all calls to def will result in additions or subtractions from user environment variables.
checked at shell startup by configuration daemon. not used afterwards.
default value: 1 (set)
".to_string(),
value: ValueType::VarForm(Box::new(Ctr::String("1".to_string()))), value: ValueType::VarForm(Box::new(Ctr::String("1".to_string()))),
}, },
); );
@ -55,6 +65,8 @@ pub fn configure(filename: String, syms: &mut SymTable) -> Result<(), String> {
name: String::from("default relish prompt"), name: String::from("default relish prompt"),
args: Args::None, args: Args::None,
conditional_branches: false, conditional_branches: false,
docs: "function called to output prompt. this function is called with no arguments.
default value (<lambda>)".to_string(),
value: ValueType::Internal(Rc::new(prompt_default_callback)), value: ValueType::Internal(Rc::new(prompt_default_callback)),
}, },
); );

View file

@ -39,10 +39,7 @@ 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) => match eval(inner, syms) { Ctr::Seg(ref inner) => car = eval(inner, syms)?,
Ok(res) => car = res,
Err(e) => return Err(format!("evaluation error: {}", e)),
},
Ctr::Symbol(ref tok) => { Ctr::Symbol(ref tok) => {
let outer_scope_seg_holder: Seg; let outer_scope_seg_holder: Seg;

View file

@ -240,9 +240,9 @@ impl fmt::Display for Ctr {
Ctr::Float(s) => write!(f, "{}", s), Ctr::Float(s) => write!(f, "{}", s),
Ctr::Bool(s) => { Ctr::Bool(s) => {
if *s { if *s {
write!(f, "T") write!(f, "true")
} else { } else {
write!(f, "F") write!(f, "false")
} }
} }
Ctr::Seg(s) => write!(f, "{}", s), Ctr::Seg(s) => write!(f, "{}", s),

View file

@ -36,26 +36,19 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("append"), name: String::from("append"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: false, 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)), 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( syms.insert(
"echo".to_string(), "echo".to_string(),
Symbol { Symbol {
name: String::from("echo"), name: String::from("echo"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: false, 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)), value: ValueType::Internal(Rc::new(_echo_callback)),
}, },
); );
@ -66,6 +59,14 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("if"), name: String::from("if"),
args: Args::Lazy(3), args: Args::Lazy(3),
conditional_branches: true, 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)), 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"), name: String::from("let"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: true, 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)), 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"), name: String::from("while"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: true, 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)), 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"), name: String::from("circuit"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: true, 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)), 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"), name: String::from("and"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: false, 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)), 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"), name: String::from("or"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: false, 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)), 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"), name: String::from("not"),
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
conditional_branches: false, 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)), 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?"), name: String::from("eq?"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: false, 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)), 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"), name: String::from("toggle"),
args: Args::Lazy(1), args: Args::Lazy(1),
conditional_branches: true, 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)), 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(()) Ok(())
} }
@ -170,6 +226,18 @@ pub fn dynamic_stdlib(syms: &mut SymTable) -> Result<(), String> {
name: String::from("define"), name: String::from("define"),
args: Args::Infinite, args: Args::Infinite,
conditional_branches: true, 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( value: ValueType::Internal(Rc::new(
move |ast: &Seg, syms: &mut SymTable| -> Result<Ctr, String> { move |ast: &Seg, syms: &mut SymTable| -> Result<Ctr, String> {
_store_callback(ast, syms, env_cfg_user_form) _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) 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> { 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 { if let Ctr::Symbol(ref identifier) = *ast.car {
match &*ast.cdr { match &*ast.cdr {
Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), syms) { // define a symbol
Ok(seg) => { Ctr::Seg(doc_tree) => {
if let Ctr::Seg(ref val) = *seg { if let Ctr::String(ref doc) = *doc_tree.car {
syms.insert( match &*doc_tree.cdr {
identifier.clone(), // define a variable
Symbol { Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), syms) {
value: ValueType::VarForm(val.car.clone()), Ok(seg) => {
name: identifier.clone(), if let Ctr::Seg(ref val) = *seg {
args: Args::None, syms.insert(
conditional_branches: false, identifier.clone(),
}, Symbol {
); value: ValueType::VarForm(val.car.clone()),
if env_cfg { name: identifier.clone(),
env::set_var(identifier.clone(), val.car.to_string()); args: Args::None,
} docs: doc.to_owned(),
} else { conditional_branches: false,
return Err("impossible args to export".to_string()); },
} );
} if env_cfg {
Err(e) => return Err(format!("couldnt eval symbol: {}", e)), env::set_var(identifier.clone(), val.car.to_string());
}, }
Ctr::Seg(data_tree) if !is_var => { } else {
if let Ctr::Seg(ref args) = *data_tree.car { return Err("impossible args to export".to_string());
let mut arg_list = vec![]; }
if !args.circuit(&mut |c: &Ctr| -> bool { }
if let Ctr::Symbol(ref arg) = c { Err(e) => return Err(format!("couldnt eval symbol: {}", e)),
arg_list.push(arg.clone()); },
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 { // define a function
syms.insert( Ctr::Seg(data_tree) if !is_var => {
identifier.clone(), if let Ctr::Seg(ref args) = *data_tree.car {
Symbol { let mut arg_list = vec![];
value: ValueType::FuncForm(UserFn { if !args.circuit(&mut |c: &Ctr| -> bool {
ast: Box::new(bodies.clone()), if let Ctr::Symbol(ref arg) = c {
arg_syms: arg_list.clone(), arg_list.push(arg.clone());
}), true
name: identifier.clone(), } else if let Ctr::None = c {
args: Args::Lazy(arg_list.len() as u128), // a user cannot type a None
conditional_branches: false, // this case represents no args
}, true
); } else {
} else { false
return Err( }
"expected one or more function bodies in function definition" }) {
.to_string(), 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 { } 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 => { Ctr::None => {
syms.remove(&identifier.to_string()); syms.remove(&identifier.to_string());
if env_cfg { if env_cfg {
env::remove_var(identifier); env::remove_var(identifier);
} }
} },
_ => return Err("args not in standard form".to_string()),
_ => return Err("arguments not in standard form".to_string()),
} }
} else { } 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) Ok(Ctr::None)
} }

View file

@ -46,11 +46,3 @@ pub fn append_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::Seg(temp)) Ok(Ctr::Seg(temp))
} }
} }
pub fn expand_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, String> {
if let Ctr::Seg(_) = *ast.car {
Ok(*ast.car.clone())
} else {
Err("non list passed to expand!".to_string())
}
}

View file

@ -131,6 +131,7 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
name: name.clone(), name: name.clone(),
args: Args::None, args: Args::None,
conditional_branches: false, conditional_branches: false,
docs: "variable used in let form".to_string(),
value: ValueType::VarForm(Box::new(*var_val_res.unwrap().clone())), value: ValueType::VarForm(Box::new(*var_val_res.unwrap().clone())),
}, },
); );
@ -166,7 +167,7 @@ pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
} }
if let Err(e) = res { if let Err(e) = res {
eprintln!("error evaluating let form: {}", e); eprintln!("{}", e);
return false; return false;
} }

View file

@ -19,6 +19,7 @@ use crate::eval::eval;
use crate::segment::{Ctr, Seg, Type}; use crate::segment::{Ctr, Seg, Type};
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::fmt;
#[derive(Clone)] #[derive(Clone)]
pub struct SymTable(HashMap<String, Symbol>); pub struct SymTable(HashMap<String, Symbol>);
@ -66,6 +67,7 @@ pub struct Symbol {
// for internal control flow constructs // for internal control flow constructs
// eval() will not eval the args // eval() will not eval the args
pub conditional_branches: bool, pub conditional_branches: bool,
pub docs: String,
} }
impl SymTable { impl SymTable {
@ -203,6 +205,23 @@ impl Args {
} }
} }
impl fmt::Display for Args {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Args::None => write!(f, "none"),
Args::Infinite => write!(f, "infinite, untyped"),
Args::Lazy(n) => write!(f, "{} args of any type", n),
Args::Strict(s) => {
write!(f, "types: ")?;
for arg in s {
write!(f, "{} ", arg)?
}
Ok(())
}
}
}
}
impl Symbol { impl Symbol {
/* call /* call
* routine is called by eval when a symbol is expanded * routine is called by eval when a symbol is expanded
@ -229,10 +248,7 @@ impl Symbol {
evaluated_args = args; evaluated_args = args;
} }
if let Err(msg) = self.args.validate_inputs(evaluated_args) { self.args.validate_inputs(evaluated_args)?;
return Err(format!("failure to call {}: {}", self.name, msg));
}
match &self.value { match &self.value {
ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())), ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())),
ValueType::Internal(ref f) => Ok(Box::new(f(evaluated_args, syms)?)), ValueType::Internal(ref f) => Ok(Box::new(f(evaluated_args, syms)?)),
@ -251,6 +267,7 @@ impl Symbol {
name: f.arg_syms[n].clone(), name: f.arg_syms[n].clone(),
value: ValueType::VarForm(Box::new(evaluated_args[n].clone())), value: ValueType::VarForm(Box::new(evaluated_args[n].clone())),
args: Args::None, args: Args::None,
docs: format!("local argument to {}", f.arg_syms[n].clone()),
conditional_branches: false, conditional_branches: false,
}, },
) { ) {
@ -265,7 +282,7 @@ impl Symbol {
if let Ctr::Seg(ref data) = *iterate.car { if let Ctr::Seg(ref data) = *iterate.car {
match eval(data, syms) { match eval(data, syms) {
Ok(ctr) => result = ctr, Ok(ctr) => result = ctr,
Err(e) => return Err(e), Err(e) => return Err(format!("evaluation failure\n{}", e)),
} }
} else { } else {
let temp = Seg::from_mono(iterate.car.clone()); let temp = Seg::from_mono(iterate.car.clone());
@ -277,7 +294,7 @@ impl Symbol {
result = ctr; result = ctr;
} }
} }
Err(e) => return Err(e), Err(e) => return Err(format!("evaluation failure\n{}", e)),
} }
} }

View file

@ -6,46 +6,19 @@ mod eval_tests {
fn eval_simple() { fn eval_simple() {
let test_doc = "(1 2)".to_string(); let test_doc = "(1 2)".to_string();
let mut syms = SymTable::new(); let mut syms = SymTable::new();
match lex(&test_doc) { let doc_tree = lex(&test_doc).unwrap();
Err(e) => { let reduced = *eval(&doc_tree, &mut syms).unwrap();
println!("Lexing error: {}\n", e); assert_eq!(reduced.to_string(), test_doc);
assert!(false)
}
Ok(initial_ast) => match eval(&initial_ast, &mut syms) {
Err(e) => {
println!("Evaluation error: {}\n", e);
assert!(false)
}
Ok(reduced) => {
assert_eq!(reduced.to_string(), test_doc)
}
},
}
} }
#[test] #[test]
fn eval_embedded_lists_no_funcs() { fn eval_embedded_lists_no_funcs() {
let test_doc = "(1 (1 2 3 4 5) 5)".to_string(); let test_doc = "(1 (1 2 3 4 5) 5)".to_string();
let mut syms = SymTable::new(); let mut syms = SymTable::new();
match lex(&test_doc) { let doc_tree = lex(&test_doc).unwrap();
Err(e) => { let reduced = *eval(&doc_tree, &mut syms).unwrap();
println!("Lexing error: {}\n", e); assert_eq!(reduced.to_string(), test_doc);
assert!(false)
}
Ok(initial_ast) => match eval(&initial_ast, &mut syms) {
Err(e) => {
println!("Evaluation error: {}\n", e);
assert!(false)
}
Ok(reduced) => {
assert_eq!(reduced.to_string(), test_doc)
}
},
}
} }
#[test] #[test]
@ -58,6 +31,7 @@ mod eval_tests {
name: String::from("echo"), name: String::from("echo"),
args: Args::Lazy(1), args: Args::Lazy(1),
conditional_branches: false, conditional_branches: false,
docs: String::new(),
value: ValueType::FuncForm(UserFn { value: ValueType::FuncForm(UserFn {
arg_syms: vec!["input".to_string()], arg_syms: vec!["input".to_string()],
ast: Box::new(Seg::from( ast: Box::new(Seg::from(
@ -68,24 +42,9 @@ mod eval_tests {
}; };
syms.insert(String::from("echo"), test_external_func); syms.insert(String::from("echo"), test_external_func);
let doc_tree = lex(&test_doc).unwrap();
match lex(&test_doc) { let reduced = *eval(&doc_tree, &mut syms).unwrap();
Err(e) => { assert_eq!(reduced.to_string(), output);
println!("Lexing error: {}\n", e);
assert!(false)
}
Ok(initial_ast) => match eval(&initial_ast, &mut syms) {
Err(e) => {
println!("Evaluation error: {}\n", e);
assert!(false)
}
Ok(reduced) => {
assert_eq!(reduced.to_string(), output)
}
},
}
} }
#[test] #[test]
@ -98,6 +57,7 @@ mod eval_tests {
name: String::from("echo"), name: String::from("echo"),
args: Args::Lazy(1), args: Args::Lazy(1),
conditional_branches: false, conditional_branches: false,
docs: String::new(),
value: ValueType::FuncForm(UserFn { value: ValueType::FuncForm(UserFn {
arg_syms: vec!["input".to_string()], arg_syms: vec!["input".to_string()],
ast: Box::new(Seg::from( ast: Box::new(Seg::from(
@ -108,46 +68,26 @@ mod eval_tests {
}; };
syms.insert(String::from("echo"), test_external_func); syms.insert(String::from("echo"), test_external_func);
match lex(&test_doc) { let doc_tree = lex(&test_doc).unwrap();
Err(e) => { let reduced = *eval(&doc_tree, &mut syms).unwrap();
println!("Lexing error: {}\n", e); assert_eq!(reduced.to_string(), output);
assert!(false)
}
Ok(initial_ast) => match eval(&initial_ast, &mut syms) {
Err(e) => {
println!("Evaluation error: {}\n", e);
assert!(false)
}
Ok(reduced) => {
assert_eq!(reduced.to_string(), output)
}
},
}
} }
#[test] #[test]
fn eval_bad_syms() { fn eval_bad_syms() {
let test_doc = "(undefined)".to_string(); let test_doc = "(undefined)".to_string();
let mut syms = SymTable::new(); let mut syms = SymTable::new();
match lex(&test_doc) { let doc_tree = lex(&test_doc).unwrap();
match eval(&doc_tree, &mut syms) {
Err(e) => { Err(e) => {
println!("Lexing error: {}\n", e); assert_eq!(e, "error in call to undefined: undefined symbol: undefined")
assert!(false)
} }
Ok(initial_ast) => match eval(&initial_ast, &mut syms) { Ok(reduced) => {
Err(e) => { println!("Eval succeeded when it shouldnt have");
assert_eq!(e, "error in call to undefined: undefined symbol: undefined") println!("see: {}", reduced);
} assert!(false)
}
Ok(reduced) => {
println!("Eval succeeded when it shouldnt have");
println!("see: {}", reduced);
assert!(false)
}
},
} }
} }
} }

View file

@ -10,6 +10,7 @@ mod func_tests {
let test_internal_func: Symbol = Symbol { let test_internal_func: Symbol = Symbol {
name: String::from("test_func_in"), name: String::from("test_func_in"),
conditional_branches: false, conditional_branches: false,
docs: String::new(),
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
@ -23,95 +24,63 @@ mod func_tests {
)), )),
}; };
let args = Seg::from(Box::new(Ctr::Bool(true)), Box::new(Ctr::None)); let args = Seg::from(Box::new(Ctr::Bool(true)), Box::new(Ctr::None));
syms.insert(String::from("test_func_in"), test_internal_func); syms.insert(String::from("test_func_in"), test_internal_func);
if let Ctr::Bool(b) = *syms.call_symbol(&"test_func_in".to_string(), &args, true).unwrap() {
if let Ok(ret) = syms.call_symbol(&"test_func_in".to_string(), &args, true) { assert!(b)
match *ret {
Ctr::Bool(b) => assert!(b),
_ => {
print!("invalid return from func!");
assert!(false);
}
}
} else {
print!("call to function failed!");
assert!(false);
} }
} }
#[test] #[test]
fn decl_and_call_external_func_singlet() { fn decl_and_call_external_func_singlet() {
let mut syms = SymTable::new(); let mut syms = SymTable::new();
match lex(&"input".to_string()) { let finner = lex(&"input".to_string()).unwrap();
Err(e) => panic!("{}", e), let test_external_func: Symbol = Symbol {
Ok(finner) => { name: String::from("echo"),
let test_external_func: Symbol = Symbol { conditional_branches: false,
name: String::from("echo"), args: Args::Lazy(1),
conditional_branches: false, docs: String::new(),
args: Args::Lazy(1), value: ValueType::FuncForm(UserFn {
value: ValueType::FuncForm(UserFn { arg_syms: vec!["input".to_string()],
arg_syms: vec!["input".to_string()], ast: finner,
ast: finner, }),
}), };
};
let args = Seg::from( let args = Seg::from(
Box::new(Ctr::String("test".to_string())), Box::new(Ctr::String("test".to_string())),
Box::new(Ctr::None), Box::new(Ctr::None),
); );
syms.insert(String::from("test_func_in"), test_external_func); syms.insert(String::from("test_func_in"), test_external_func);
if let Ctr::Bool(b) = *syms.call_symbol(&"test_func_in".to_string(), &args, true).unwrap() {
match syms.call_symbol(&"test_func_in".to_string(), &args, true) { assert!(b)
Ok(ret) => match *ret {
Ctr::String(b) => assert!(b == "test"),
_ => {
print!("Invalid return from func. Got {:?}\n", ret);
assert!(false);
}
},
Err(e) => {
print!("Call to function failed: {}\n", e);
assert!(false);
}
}
}
} }
} }
#[test] #[test]
fn decl_and_call_external_func_multi_body() { fn decl_and_call_external_func_multi_body() {
let mut syms = SymTable::new(); let mut syms = SymTable::new();
match lex(&"(input input)".to_string()) { let finner = lex(&"input".to_string()).unwrap();
Err(e) => panic!("{}", e), let test_external_func: Symbol = Symbol {
Ok(finner) => { name: String::from("echo_2"),
let test_external_func: Symbol = Symbol { conditional_branches: false,
name: String::from("echo_2"), args: Args::Lazy(1),
conditional_branches: false, docs: String::new(),
args: Args::Lazy(1), value: ValueType::FuncForm(UserFn {
value: ValueType::FuncForm(UserFn { arg_syms: vec!["input".to_string()],
arg_syms: vec!["input".to_string()], ast: finner,
ast: finner, }),
}), };
};
let args = Seg::from( let args = Seg::from(
Box::new(Ctr::String("test".to_string())), Box::new(Ctr::String("test".to_string())),
Box::new(Ctr::None), Box::new(Ctr::None),
); );
syms.insert(String::from("echo_2"), test_external_func); syms.insert(String::from("echo_2"), test_external_func);
assert_eq!(*syms.call_symbol(&"echo_2".to_string(), &args, true)
match syms.call_symbol(&"echo_2".to_string(), &args, true) { .unwrap()
Ok(ret) => assert_eq!(ret.to_string(), "'test'"), .to_string(),
Err(e) => { "'test'".to_string());
print!("Call to function failed: {}\n", e);
assert!(false);
}
}
}
}
} }
#[test] #[test]
@ -121,6 +90,7 @@ mod func_tests {
name: String::from("test_inner"), name: String::from("test_inner"),
conditional_branches: false, conditional_branches: false,
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
docs: String::new(),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
let inner = a; let inner = a;
@ -137,39 +107,25 @@ mod func_tests {
)), )),
}; };
match lex(&"((test_inner true))".to_string()) { let finner = lex(&"((test_inner true))".to_string()).unwrap();
Err(e) => panic!("{}", e), let outer_func: Symbol = Symbol {
Ok(finner) => { name: String::from("test_outer"),
let outer_func: Symbol = Symbol { conditional_branches: false,
name: String::from("test_outer"), args: Args::Lazy(1),
conditional_branches: false, docs: String::new(),
args: Args::Lazy(1), value: ValueType::FuncForm(UserFn {
value: ValueType::FuncForm(UserFn { arg_syms: vec!["input".to_string()],
arg_syms: vec!["input".to_string()], ast: finner,
ast: finner, }),
}), };
};
let args = Seg::from(Box::new(Ctr::Bool(true)), Box::new(Ctr::None)); let args = Seg::from(Box::new(Ctr::Bool(true)), Box::new(Ctr::None));
syms.insert(String::from("test_inner"), inner_func);
syms.insert(String::from("test_inner"), inner_func); syms.insert(String::from("test_outer"), outer_func);
syms.insert(String::from("test_outer"), outer_func); assert_eq!(syms.call_symbol(&"test_outer".to_string(), &args, true)
.unwrap()
match syms.call_symbol(&"test_outer".to_string(), &args, true) { .to_string(),
Ok(ret) => match *ret { "'test'".to_string());
Ctr::String(b) => assert!(b == "test"),
_ => {
print!("Invalid return from func. Got {:?}\n", ret);
assert!(false);
}
},
Err(e) => {
print!("Call to function failed: {}\n", e);
assert!(false);
}
}
}
}
} }
#[test] #[test]
@ -179,6 +135,7 @@ mod func_tests {
name: String::from("test_func_in"), name: String::from("test_func_in"),
conditional_branches: false, conditional_branches: false,
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
docs: String::new(),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
let inner = a; let inner = a;
@ -193,75 +150,66 @@ mod func_tests {
let args = Seg::from(Box::new(Ctr::Integer(1)), Box::new(Ctr::None)); let args = Seg::from(Box::new(Ctr::Integer(1)), Box::new(Ctr::None));
syms.insert(String::from("test_func_in"), test_internal_func); syms.insert(String::from("test_func_in"), test_internal_func);
assert_eq!(
if let Err(s) = syms.call_symbol(&"test_func_in".to_string(), &args, true) { syms.call_symbol(&"test_func_in".to_string(), &args, true)
assert_eq!(s, "failure to call test_func_in: arg 1 expected to be bool"); .err()
} else { .unwrap(),
print!("call to function succeeded (shouldnt have)"); "arg 1 expected to be bool".to_string(),
assert!(false); );
}
} }
#[test] #[test]
fn too_many_args() { fn too_many_args() {
let mut syms = SymTable::new(); let mut syms = SymTable::new();
match lex(&"(input)".to_string()) { let finner = lex(&"(input)".to_string()).unwrap();
Err(e) => panic!("{}", e), let test_external_func: Symbol = Symbol {
Ok(finner) => { name: String::from("echo"),
let test_external_func: Symbol = Symbol { conditional_branches: false,
name: String::from("echo"), args: Args::Lazy(1),
conditional_branches: false, docs: String::new(),
args: Args::Lazy(1), value: ValueType::FuncForm(UserFn {
value: ValueType::FuncForm(UserFn { arg_syms: vec!["input".to_string()],
arg_syms: vec!["input".to_string()], ast: finner,
ast: finner, }),
}), };
};
let args = Seg::from( let args = Seg::from(
Box::new(Ctr::String("test".to_string())), Box::new(Ctr::String("test".to_string())),
Box::new(Ctr::Seg(Seg::from_mono(Box::new(Ctr::Integer(1))))), Box::new(Ctr::Seg(Seg::from_mono(Box::new(Ctr::Integer(1))))),
); );
syms.insert(String::from("test_func_in"), test_external_func); syms.insert(String::from("test_func_in"), test_external_func);
assert_eq!(
if let Err(s) = syms.call_symbol(&"test_func_in".to_string(), &args, true) { syms.call_symbol(&"test_func_in".to_string(), &args, true)
assert_eq!(s, "failure to call echo: expected 1 args. Got 2."); .err()
} else { .unwrap(),
print!("call to function succeeded (shouldnt have)"); "expected 1 args. Got 2.".to_string(),
assert!(false); );
}
}
}
} }
#[test] #[test]
fn too_few_args() { fn too_few_args() {
let mut syms = SymTable::new(); let mut syms = SymTable::new();
match lex(&"(input)".to_string()) { let finner = lex(&"(input)".to_string()).unwrap();
Err(e) => panic!("{}", e), let test_external_func: Symbol = Symbol {
Ok(finner) => { name: String::from("echo"),
let test_external_func: Symbol = Symbol { conditional_branches: false,
name: String::from("echo"), args: Args::Lazy(1),
conditional_branches: false, docs: String::new(),
args: Args::Lazy(1), value: ValueType::FuncForm(UserFn {
value: ValueType::FuncForm(UserFn { arg_syms: vec!["input".to_string()],
arg_syms: vec!["input".to_string()], ast: finner,
ast: finner, }),
}), };
};
let args = Seg::new(); let args = Seg::new();
syms.insert(String::from("test_func_in"), test_external_func); syms.insert(String::from("test_func_in"), test_external_func);
assert_eq!(
if let Err(s) = syms.call_symbol(&"test_func_in".to_string(), &args, true) { syms.call_symbol(&"test_func_in".to_string(), &args, true)
assert_eq!(s, "failure to call echo: expected 1 args. Got 0."); .err()
} else { .unwrap(),
print!("call to function succeeded (shouldnt have)"); "expected 1 args. Got 0.".to_string(),
assert!(false); );
}
}
}
} }
#[test] #[test]
@ -271,6 +219,7 @@ mod func_tests {
name: String::from("test_func_in"), name: String::from("test_func_in"),
conditional_branches: false, conditional_branches: false,
args: Args::Strict(vec![Type::Bool]), args: Args::Strict(vec![Type::Bool]),
docs: String::new(),
value: ValueType::Internal(Rc::new( value: ValueType::Internal(Rc::new(
|a: &Seg, _: &mut SymTable| -> Result<Ctr, String> { |a: &Seg, _: &mut SymTable| -> Result<Ctr, String> {
let inner = a; let inner = a;
@ -288,15 +237,12 @@ mod func_tests {
); );
syms.insert(String::from("test_func_in"), test_internal_func); syms.insert(String::from("test_func_in"), test_internal_func);
assert_eq!(
if let Err(s) = syms.call_symbol(&"test_func_in".to_string(), &args, true) { syms.call_symbol(&"test_func_in".to_string(), &args, true)
assert_eq!( .err()
s, .unwrap(),
"error in call to undefined-symbol: undefined symbol: undefined-symbol" "error in call to undefined-symbol: undefined symbol: undefined-symbol"
); .to_string(),
} else { );
print!("call to function succeeded (shouldnt have)");
assert!(false);
}
} }
} }

View file

@ -4,131 +4,86 @@ mod lex_tests {
#[test] #[test]
fn test_lex_basic_pair() { fn test_lex_basic_pair() {
let document = String::from("(hello 'world')"); let document = String::from("(hello 'world')");
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), document); document
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
fn test_lex_basic_list() { fn test_lex_basic_list() {
let document = String::from("(hello 'world' 1 2 3)"); let document = String::from("(hello 'world' 1 2 3)");
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), document); document
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
fn test_lex_complex_list() { fn test_lex_complex_list() {
let document = String::from("(hello 'world' (1 2 (1 2 3)) 1 2 3)"); let document = String::from("(hello 'world' (1 2 (1 2 3)) 1 2 3)");
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), document); document
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
fn test_bad_symbol() { fn test_bad_symbol() {
let document = String::from("(as;dd)"); let document = String::from("(as;dd)");
let output: &str = "Problem lexing document: \"Unparsable token: as;dd\""; let output: &str = "Problem lexing document: \"Unparsable token: as;dd\"";
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).err().unwrap(),
print!("Bad token yielded: {}\n", tree.to_string()); output.to_string(),
assert!(false); );
}
Err(s) => {
assert_eq!(s, output);
}
}
} }
#[test] #[test]
fn test_list_delim_in_str() { fn test_list_delim_in_str() {
let document = String::from("('(')"); let document = String::from("('(')");
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), document); document
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
fn test_empty_string() { fn test_empty_string() {
let document = String::from("('')"); let document = String::from("('')");
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), document); document
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
fn test_unmatched_list_delim_flat() { fn test_unmatched_list_delim_flat() {
let document = String::from("(one two"); let document = String::from("(one two");
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).err().unwrap(),
print!("Bad token yielded: {}\n", tree.to_string()); output.to_string(),
assert!(false); );
}
Err(s) => {
assert_eq!(s, output);
}
}
} }
#[test] #[test]
fn test_unmatched_list_delim_complex() { fn test_unmatched_list_delim_complex() {
let document = String::from("(one two (three)"); let document = String::from("(one two (three)");
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).err().unwrap(),
print!("Bad token yielded: {}\n", tree); output.to_string(),
assert!(false); );
}
Err(s) => {
assert_eq!(s, output);
}
}
} }
#[test] #[test]
fn test_comment() { fn test_comment() {
let document = String::from("#!/bin/relish\n(one two)"); let document = String::from("#!/bin/relish\n(one two)");
let output: &str = "(one two)"; let output: &str = "(one two)";
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), output); output.to_string(),
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
@ -136,44 +91,30 @@ mod lex_tests {
let document = let document =
String::from("#!/bin/relish\n((one two)# another doc comment\n(three four))"); String::from("#!/bin/relish\n((one two)# another doc comment\n(three four))");
let output: &str = "((one two) (three four))"; let output: &str = "((one two) (three four))";
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), output.to_string()); output.to_string(),
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
fn test_inline_comment() { fn test_inline_comment() {
let document = String::from("#!/bin/relish\n((one two)\n# another doc comment\nthree)"); let document = String::from("#!/bin/relish\n((one two)\n# another doc comment\nthree)");
let output: &str = "((one two) three)"; let output: &str = "((one two) three)";
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).unwrap().to_string(),
assert_eq!(tree.to_string(), output.to_string()); output.to_string(),
} );
Err(s) => {
print!("{}\n", s);
assert!(false);
}
}
} }
#[test] #[test]
fn test_bad_token_list() { fn test_bad_token_list() {
let document = String::from("(one t(wo)"); let document = String::from("(one t(wo)");
let output: &str = "Problem lexing document: \"list started in middle of another token\""; let output: &str = "Problem lexing document: \"list started in middle of another token\"";
match lex(&document) { assert_eq!(
Ok(tree) => { lex(&document).err().unwrap(),
print!("Bad token yielded: {}\n", tree); output.to_string(),
assert!(false); );
}
Err(s) => {
assert_eq!(s, output);
}
}
} }
} }

View file

@ -1,5 +1,5 @@
mod append_lib_tests { mod append_lib_tests {
use relish::ast::{eval, lex, Ctr, SymTable}; use relish::ast::{eval, lex, SymTable};
use relish::stdlib::{dynamic_stdlib, static_stdlib}; use relish::stdlib::{dynamic_stdlib, static_stdlib};
#[test] #[test]
@ -10,14 +10,10 @@ mod append_lib_tests {
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Seg(ref s) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(s.to_string(), result); );
}
} else {
assert!(false)
}
} }
#[test] #[test]
@ -29,13 +25,10 @@ mod append_lib_tests {
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
if let Ok(tree) = lex(&document.to_string()) { assert_eq!(
if let Ctr::Seg(ref s) = *eval(&tree, &mut syms).unwrap() { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
assert_eq!(s.to_string(), result); result.to_string(),
} );
} else {
assert!(false)
}
} }
#[test] #[test]
@ -47,13 +40,10 @@ mod append_lib_tests {
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
if let Ok(tree) = lex(&document.to_string()) { assert_eq!(
if let Ctr::Seg(ref s) = *eval(&tree, &mut syms).unwrap() { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
assert_eq!(s.to_string(), result); result.to_string(),
} );
} else {
assert!(false);
}
} }
#[test] #[test]
@ -65,13 +55,10 @@ mod append_lib_tests {
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
if let Ok(tree) = lex(&document.to_string()) { assert_eq!(
if let Ctr::Seg(ref s) = *eval(&tree, &mut syms).unwrap() { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
assert_eq!(s.to_string(), result); result.to_string(),
} );
} else {
assert!(false);
}
} }
#[test] #[test]
@ -83,12 +70,9 @@ mod append_lib_tests {
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
if let Ok(tree) = lex(&document.to_string()) { assert_eq!(
if let Ctr::Seg(ref s) = *eval(&tree, &mut syms).unwrap() { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
assert_eq!(s.to_string(), result); result.to_string(),
} );
} else {
assert!(false);
}
} }
} }

View file

@ -5,146 +5,99 @@ mod bool_lib_tests {
#[test] #[test]
fn test_and_true_chain() { fn test_and_true_chain() {
let document = "(and true true true true true)"; let document = "(and true true true true true)";
let result = true; let result = "true";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Bool(b) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(b, result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_and_true_chain_with_false() { fn test_and_true_chain_with_false() {
let document = "(and true true false true true)"; let document = "(and true true false true true)";
let result = false; let result = "false";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Bool(i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i, result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_and_false_chain() { fn test_and_false_chain() {
let document = "(and false false false false false)"; let document = "(and false false false false false)";
let result = false; let result = "false";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Bool(i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i, result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_or_true_chain() { fn test_or_true_chain() {
let document = "(or true true true true true)"; let document = "(or true true true true true)";
let result = true; let result = "true";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
*eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
result.to_string(),
);
if let Ok(tree) = lex(&document.to_string()) {
if let Ctr::Bool(b) = *eval(&tree, &mut syms).unwrap() {
assert_eq!(b, result);
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_or_true_chain_with_false() { fn test_or_true_chain_with_false() {
let document = "(or true true false true true)"; let document = "(or true true false true true)";
let result = true; let result = "true";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
*eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
result.to_string(),
);
if let Ok(tree) = lex(&document.to_string()) {
if let Ctr::Bool(i) = *eval(&tree, &mut syms).unwrap() {
assert_eq!(i, result);
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_or_false_chain() { fn test_or_false_chain() {
let document = "(or false false false false)"; let document = "(or false false false false false)";
let result = false; let result = "false";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Bool(i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i, result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_not() { fn test_not() {
let document = "(not true)"; let document = "(not true)";
let result = false; let result = "false";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Bool(i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i, result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_toggle_a_bool() { fn test_toggle_a_bool() {
let document = "(def tester true)"; let document = "(def tester '' true)";
let change = "(toggle tester)"; let change = "(toggle tester)";
let check = "(tester)"; let check = "(tester)";
@ -183,7 +136,7 @@ mod bool_lib_tests {
#[test] #[test]
fn test_toggle_errors_dont_lose_vars() { fn test_toggle_errors_dont_lose_vars() {
let document = "(def tester 'oops')"; let document = "(def tester '' 'oops')";
let change = "(toggle tester)"; let change = "(toggle tester)";
let check = "(tester)"; let check = "(tester)";
@ -216,7 +169,7 @@ mod bool_lib_tests {
#[test] #[test]
fn test_toggle_errors_dont_lose_funcs() { fn test_toggle_errors_dont_lose_funcs() {
let document = "(def tester (oops) oops)"; let document = "(def tester '' (oops) oops)";
let change = "(toggle tester)"; let change = "(toggle tester)";
let check = "(tester '1')"; let check = "(tester '1')";

View file

@ -1,66 +1,44 @@
mod control_lib_tests { mod control_lib_tests {
use relish::ast::{eval, lex, Ctr, SymTable}; use relish::ast::{eval, lex, SymTable};
use relish::stdlib::{dynamic_stdlib, static_stdlib}; use relish::stdlib::{dynamic_stdlib, static_stdlib};
#[test] #[test]
fn test_if_first_case_singlet() { fn test_if_first_case_singlet() {
let document = "(if true 1 2)"; let document = "(if true 1 2)";
let result = 1; let result = 1;
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Integer(i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i, result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_if_second_case_singlet() { fn test_if_second_case_singlet() {
let document = "(if false 1 2)"; let document = "(if false 1 2)";
let result = 2; let result = 2;
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Integer(i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i, result); );
} else {
eprintln!("{}", *eval(&tree, &mut syms).unwrap());
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_complex_case_call() { fn test_complex_case_call() {
let document = "(if true (append () 1) 2)"; let document = "(if true (append () 1) 2)";
let result = "(1)"; let result = "(1)";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Seg(ref i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i.to_string(), result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
@ -70,45 +48,30 @@ mod control_lib_tests {
(temp (append () temp '2'))) (temp (append () temp '2')))
temp)"; temp)";
let result = "('1' '2')"; let result = "('1' '2')";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Seg(ref i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i.to_string(), result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_let_multibody_evals() { fn test_let_multibody_evals() {
let document = "(let ((temp '1')) temp (append () temp '2'))"; let document = "(let ((temp '1')) temp (append () temp '2'))";
let result = "('1' '2')"; let result = "('1' '2')";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Seg(ref i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i.to_string(), result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_let_multiphase_local_multibody_evals() { fn test_let_multiphase_local_multibody_evals() {
// prints 'first body' and then returns ('1' '2' '3')
let document = "(let ( let document = "(let (
(temp '1') (temp '1')
(temp (append () temp '2'))) (temp (append () temp '2')))
@ -116,33 +79,26 @@ mod control_lib_tests {
(append temp '3'))"; (append temp '3'))";
let result = "('1' '2' '3')"; let result = "('1' '2' '3')";
let mut syms = SymTable::new(); let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap(); static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
assert_eq!(
if let Ok(tree) = lex(&document.to_string()) { *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap().to_string(),
if let Ctr::Seg(ref i) = *eval(&tree, &mut syms).unwrap() { result.to_string(),
assert_eq!(i.to_string(), result); );
} else {
assert!(false);
}
} else {
assert!(false);
}
} }
#[test] #[test]
fn test_while_basic() { fn test_while_basic() {
let switch_dec = "(def switch true)"; let switch_dec = "(def switch '' true)";
// if prev is true, switch looped once and only once // if prev is true, switch looped once and only once
// else prev will have a problematic type // else prev will have a problematic type
let while_loop = " let while_loop = "
(while switch (while switch
(def prev switch) (def prev '' switch)
(toggle switch) (toggle switch)
(if switch (if switch
(def prev) (def '' prev)
()))"; ()))";
let test_check = "prev"; let test_check = "prev";
@ -161,15 +117,15 @@ mod control_lib_tests {
#[test] #[test]
fn test_while_eval_cond() { fn test_while_eval_cond() {
let switch_dec = "(def switch true)"; let switch_dec = "(def switch '' true)";
// if prev is true, switch looped once and only once // if prev is true, switch looped once and only once
// else prev will have a problematic type // else prev will have a problematic type
let while_loop = " let while_loop = "
(while (or switch switch) (while (or switch switch)
(def prev switch) (def prev '' switch)
(toggle switch) (toggle switch)
(if switch (if switch
(def prev) (def '' prev)
()))"; ()))";
let test_check = "prev"; let test_check = "prev";
@ -188,15 +144,15 @@ mod control_lib_tests {
#[test] #[test]
fn test_while_2_iter() { fn test_while_2_iter() {
let additional = "(def sw1 true)"; let additional = "(def sw1 '' true)";
let switch_dec = "(def sw2 true)"; let switch_dec = "(def sw2 '' true)";
// while should loop twice and define result // while should loop twice and define result
let while_loop = " let while_loop = "
(while sw1 (while sw1
(toggle sw2) (toggle sw2)
(if (and sw1 sw2) (if (and sw1 sw2)
(def sw1 false) (def sw1 '' false)
(def result 'yay')))"; (def result '' 'yay')))";
let test_check = "result"; let test_check = "result";
let another_tree = lex(&additional.to_string()).unwrap(); let another_tree = lex(&additional.to_string()).unwrap();
@ -216,7 +172,7 @@ mod control_lib_tests {
#[test] #[test]
fn test_circuit_basic() { fn test_circuit_basic() {
let document = "(if (circuit true (and true true) true) (def result 'passed') ())"; let document = "(if (circuit true (and true true) true) (def result '' 'passed') ())";
let test = "result"; let test = "result";
let doc_tree = lex(&document.to_string()).unwrap(); let doc_tree = lex(&document.to_string()).unwrap();
@ -228,13 +184,12 @@ mod control_lib_tests {
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
let res = eval(&test_tree, &mut syms); let res = eval(&test_tree, &mut syms);
println!("{:#?}", res);
res.unwrap(); res.unwrap();
} }
#[test] #[test]
fn test_circuit_fail() { fn test_circuit_fail() {
let document = "(if (circuit true (and false true) true) (def result 'passed') ())"; let document = "(if (circuit true (and false true) true) (def result '' 'passed') ())";
let test = "result"; let test = "result";
let doc_tree = lex(&document.to_string()).unwrap(); let doc_tree = lex(&document.to_string()).unwrap();
@ -245,13 +200,9 @@ mod control_lib_tests {
dynamic_stdlib(&mut syms).unwrap(); dynamic_stdlib(&mut syms).unwrap();
eval(&doc_tree, &mut syms).unwrap(); eval(&doc_tree, &mut syms).unwrap();
if let Err(s) = eval(&test_tree, &mut syms) { assert_eq!(
assert_eq!( eval(&test_tree, &mut syms).err().unwrap(),
s, "error in call to result: undefined symbol: result".to_string()
"error in call to result: undefined symbol: result".to_string() );
);
} else {
panic!();
}
} }
} }

View file

@ -4,7 +4,7 @@ mod var_lib_tests {
#[test] #[test]
fn test_variable_def_and_lookup() { fn test_variable_def_and_lookup() {
let doc1 = "(def test 1)"; let doc1 = "(def test 'my test var' 1)";
let doc2 = "(test)"; let doc2 = "(test)";
let result = "(1)"; let result = "(1)";
@ -42,7 +42,7 @@ mod var_lib_tests {
#[test] #[test]
fn test_func_def_and_lookup() { fn test_func_def_and_lookup() {
let doc1 = "(def test (hello) hello)"; let doc1 = "(def test 'my test func' (hello) hello)";
let doc2 = "(test '1')"; let doc2 = "(test '1')";
let result = "1"; let result = "1";
@ -79,8 +79,8 @@ mod var_lib_tests {
#[test] #[test]
fn test_variable_def_redef_and_lookup() { fn test_variable_def_redef_and_lookup() {
let doc1 = "(def test 1)"; let doc1 = "(def test 'my test var' 1)";
let doc2 = "(def test '2')"; let doc2 = "(def test 'my test var' '2')";
let doc3 = "(test)"; let doc3 = "(test)";
let result = "('2')"; let result = "('2')";
@ -132,7 +132,7 @@ mod var_lib_tests {
#[test] #[test]
fn test_variable_def_undef_and_lookup_fail() { fn test_variable_def_undef_and_lookup_fail() {
let doc1 = "(def test 1)"; let doc1 = "(def test 'my test var' 1)";
let doc2 = "(def test)"; let doc2 = "(def test)";
let doc3 = "(test)"; let doc3 = "(test)";
@ -185,4 +185,41 @@ mod var_lib_tests {
assert!(false); assert!(false);
} }
} }
#[test]
fn test_func_def_no_args() {
let doc1 = "(def test 'my test func' () '1')";
let doc2 = "(test)";
let result = "1";
let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap();
if let Ok(tree) = lex(&doc1.to_string()) {
let eval_result = *eval(&tree, &mut syms).unwrap();
if let Ctr::None = eval_result {
// pass
} else {
eprintln!("bad: {eval_result}");
assert!(false);
}
} else {
eprintln!("couldn't lex doc1");
assert!(false);
}
if let Ok(tree) = lex(&doc2.to_string()) {
let eval_result = *eval(&tree, &mut syms).unwrap();
if let Ctr::String(ref i) = eval_result {
assert_eq!(i.to_string(), result);
} else {
eprintln!("bad: {eval_result}");
assert!(false);
}
} else {
eprintln!("couldn't lex doc2");
assert!(false);
}
}
} }