Significant improvement to eval routine and tests
This commit is contained in:
parent
9b981921b4
commit
df5cb47cb4
4 changed files with 397 additions and 11 deletions
115
src/eval.rs
115
src/eval.rs
|
|
@ -30,10 +30,107 @@ pub fn eval(
|
||||||
vars: Rc<RefCell<VTable>>,
|
vars: Rc<RefCell<VTable>>,
|
||||||
funcs: Rc<RefCell<FTable>>,
|
funcs: Rc<RefCell<FTable>>,
|
||||||
sym_loose: bool
|
sym_loose: bool
|
||||||
) -> Result<Ast, String> {
|
) -> Result<Ctr, String> {
|
||||||
eval_inner(ast, vars, funcs, sym_loose, true)
|
let mut car = ast.borrow().clone().car;
|
||||||
|
let mut cdr = ast.borrow().clone().cdr;
|
||||||
|
let ret = new_ast(Ctr::None, Ctr::None);
|
||||||
|
let mut iter = ret.clone();
|
||||||
|
|
||||||
|
// doing an initial variable check here allows us
|
||||||
|
// to find functions passed in as variables
|
||||||
|
if let Ctr::Symbol(ref tok) = car {
|
||||||
|
if let Some(val) = vars.borrow().get(tok) {
|
||||||
|
car = (**val).clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// another check to detect if we may have a function call
|
||||||
|
if let Ctr::Symbol(ref tok) = car {
|
||||||
|
if let Ctr::Seg(ast) = cdr {
|
||||||
|
if let Some(func) = funcs.borrow().get(tok) {
|
||||||
|
return func_call(func.clone(), ast.clone(), vars.clone(), funcs.clone())
|
||||||
|
} else {
|
||||||
|
return Err(format!("Couldnt find function: {}.", tok))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(format!("Arguments to function not a list!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut none = false;
|
||||||
|
while !none {
|
||||||
|
match car {
|
||||||
|
// if LIST: call eval inner on it with first_item=true
|
||||||
|
Ctr::Seg(ref inner) => {
|
||||||
|
match eval(inner.clone(), vars.clone(), funcs.clone(), sym_loose) {
|
||||||
|
Ok(res) => (*iter).borrow_mut().car = res,
|
||||||
|
Err(e) => return Err(format!("Evaluation error: {}", e))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// if SYMBOL: unwrap naively
|
||||||
|
Ctr::Symbol(ref tok) => {
|
||||||
|
if let Some(val) = vars.borrow().get(&tok.clone()) {
|
||||||
|
(*iter).borrow_mut().car = (**val).clone();
|
||||||
|
} else if sym_loose {
|
||||||
|
(*iter).borrow_mut().car = Ctr::Symbol(tok.to_string());
|
||||||
|
} else {
|
||||||
|
return Err(format!("Undefined variable: {}", tok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// if OTHER: clone and set
|
||||||
|
_ => {
|
||||||
|
(*iter).borrow_mut().car = car.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match cdr {
|
||||||
|
// if SYMBOL: unwrap naively, then end
|
||||||
|
Ctr::Symbol(ref tok) => {
|
||||||
|
if let Some(val) = vars.borrow().get(&tok.clone()) {
|
||||||
|
(*iter).borrow_mut().car = (**val).clone();
|
||||||
|
} else if sym_loose {
|
||||||
|
(*iter).borrow_mut().car = Ctr::Symbol(tok.to_string());
|
||||||
|
} else {
|
||||||
|
return Err(format!("Undefined variable: {}", tok))
|
||||||
|
}
|
||||||
|
|
||||||
|
none = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
// if LIST:
|
||||||
|
// - iter.cdr = new_ast(None, None)
|
||||||
|
// - iter = iter.cdr
|
||||||
|
// - car = cdr.car
|
||||||
|
// - cdr = cdr.cdr
|
||||||
|
// - LOOP
|
||||||
|
Ctr::Seg(next) => {
|
||||||
|
let n = new_ast(Ctr::None, Ctr::None);
|
||||||
|
iter.borrow_mut().cdr = Ctr::Seg(n.clone());
|
||||||
|
iter = n;
|
||||||
|
car = next.borrow().clone().car;
|
||||||
|
cdr = next.borrow().clone().cdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if OTHER: clone and set, and then end
|
||||||
|
_ => {
|
||||||
|
(*iter).borrow_mut().cdr = cdr.clone();
|
||||||
|
none = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ctr::None = car {
|
||||||
|
if let Ctr::None = cdr {
|
||||||
|
none = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Ctr::Seg(ret))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
fn eval_inner(
|
fn eval_inner(
|
||||||
ast: Ast,
|
ast: Ast,
|
||||||
vars: Rc<RefCell<VTable>>,
|
vars: Rc<RefCell<VTable>>,
|
||||||
|
|
@ -54,6 +151,8 @@ fn eval_inner(
|
||||||
let res_car;
|
let res_car;
|
||||||
let res_cdr;
|
let res_cdr;
|
||||||
|
|
||||||
|
// Handle Function Case
|
||||||
|
|
||||||
match process_ctr(
|
match process_ctr(
|
||||||
ast.borrow().clone().car,
|
ast.borrow().clone().car,
|
||||||
vars.clone(),
|
vars.clone(),
|
||||||
|
|
@ -113,8 +212,10 @@ fn process_ctr(
|
||||||
|
|
||||||
match ctr.clone() {
|
match ctr.clone() {
|
||||||
Ctr::Symbol(token) => {
|
Ctr::Symbol(token) => {
|
||||||
|
print!("[+] Detected Symbol: {}\n", token);
|
||||||
let mut tok = token;
|
let mut tok = token;
|
||||||
if let Some(s) = vars.borrow().get(&tok) {
|
if let Some(s) = vars.borrow().get(&tok) {
|
||||||
|
print!("[+] Found symbol value\n");
|
||||||
|
|
||||||
// is function, or variable alias?
|
// is function, or variable alias?
|
||||||
let mut pass = false;
|
let mut pass = false;
|
||||||
|
|
@ -141,12 +242,15 @@ fn process_ctr(
|
||||||
if first_item {
|
if first_item {
|
||||||
if let Some(f) = funcs.borrow().get(&tok) {
|
if let Some(f) = funcs.borrow().get(&tok) {
|
||||||
if let Ctr::Seg(args) = rest {
|
if let Ctr::Seg(args) = rest {
|
||||||
|
print!("[+] Calling function: {}\n", tok);
|
||||||
match func_call(f.clone(), args, vars.clone(), funcs.clone()) {
|
match func_call(f.clone(), args, vars.clone(), funcs.clone()) {
|
||||||
Ok(a) => return Ok(a.clone()),
|
Ok(a) => {
|
||||||
|
print!("[+] Function Call Result: {:#?}\n", a);
|
||||||
|
return Ok(a.clone())
|
||||||
|
},
|
||||||
Err(s) => return Err(s)
|
Err(s) => return Err(s)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("AC::: {:?}\n", rest);
|
|
||||||
return Err("evaluation: args to function not a list".to_string())
|
return Err("evaluation: args to function not a list".to_string())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -160,7 +264,7 @@ fn process_ctr(
|
||||||
},
|
},
|
||||||
Ctr::Seg(tree) => {
|
Ctr::Seg(tree) => {
|
||||||
// if list is_car then we need to set first_item to true
|
// if list is_car then we need to set first_item to true
|
||||||
match eval_inner(tree, vars, funcs, sym_loose, is_car && first_item) {
|
match eval_inner(tree, vars, funcs, sym_loose, is_car) {
|
||||||
Ok(ast) => {
|
Ok(ast) => {
|
||||||
Ok(Ctr::Seg(ast))
|
Ok(Ctr::Seg(ast))
|
||||||
},
|
},
|
||||||
|
|
@ -172,3 +276,4 @@ fn process_ctr(
|
||||||
_ => Ok(ctr.clone())
|
_ => Ok(ctr.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
||||||
35
src/func.rs
35
src/func.rs
|
|
@ -81,7 +81,13 @@ pub fn func_call(
|
||||||
let mut n_args: Ast = args.clone();
|
let mut n_args: Ast = args.clone();
|
||||||
if !called_func.eval_lazy {
|
if !called_func.eval_lazy {
|
||||||
match eval(args, vars.clone(), funcs.clone(), called_func.loose_syms) {
|
match eval(args, vars.clone(), funcs.clone(), called_func.loose_syms) {
|
||||||
Ok(rc_seg) => n_args = rc_seg.clone(),
|
Ok(rc_seg) => {
|
||||||
|
if let Ctr::Seg(ast) = rc_seg {
|
||||||
|
n_args = ast
|
||||||
|
} else {
|
||||||
|
return Err("Panicking: eval returned not a list for function args.".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
Err(s) => return Err(
|
Err(s) => return Err(
|
||||||
format!(
|
format!(
|
||||||
"error evaluating args to {}: {}",
|
"error evaluating args to {}: {}",
|
||||||
|
|
@ -166,15 +172,32 @@ pub fn func_call(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = eval(f.ast.clone(), vars.clone(), funcs, called_func.loose_syms);
|
let mut result = Ctr::None;
|
||||||
|
let mut iterate = f.ast.clone();
|
||||||
|
loop {
|
||||||
|
if let Ctr::Seg(ast) = iterate.borrow().clone().car {
|
||||||
|
match eval(ast, vars.clone(), funcs.clone(), called_func.loose_syms) {
|
||||||
|
Ok(ctr) => {
|
||||||
|
if let Ctr::Seg(ast) = ctr {
|
||||||
|
result = ast.borrow().clone().car;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => return Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match iterate.clone().borrow().clone().cdr {
|
||||||
|
Ctr::Seg(next) => iterate = next.clone(),
|
||||||
|
Ctr::None => break,
|
||||||
|
_ => panic!("function body not in standard form!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for n in 0..f.arg_syms.len() {
|
for n in 0..f.arg_syms.len() {
|
||||||
vars.borrow_mut().remove(&f.arg_syms[n].clone());
|
vars.borrow_mut().remove(&f.arg_syms[n].clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
match result {
|
return Ok(result)
|
||||||
Ok(r) => Ok(r.borrow().clone().car),
|
|
||||||
Err(e) => Err(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
228
tests/test_eval.rs
Normal file
228
tests/test_eval.rs
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
mod func_tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use relish::ast::{Ctr, Function, Operation, ExternalOperation};
|
||||||
|
use relish::ast::{new_ast, lex, eval, VTable, FTable, ast_to_string};
|
||||||
|
use relish::ast::{func_declare, Args};
|
||||||
|
|
||||||
|
// TODO: write generalized testing routine on top of list of inputs
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_singlet() {
|
||||||
|
let test_doc = "(1)".to_string();
|
||||||
|
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||||
|
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||||
|
|
||||||
|
match lex(test_doc.clone()) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Lexing error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(initial_ast) => {
|
||||||
|
match eval(initial_ast.clone(), vt.clone(), ft.clone(), false) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Evaluation error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(reduced) => {
|
||||||
|
if let Ctr::Seg(reduced_ast) = reduced {
|
||||||
|
assert_eq!(ast_to_string(reduced_ast), test_doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_embedded_lists_no_funcs() {
|
||||||
|
let test_doc = "(1 (1 2 3 4 5) 5)".to_string();
|
||||||
|
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||||
|
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||||
|
|
||||||
|
match lex(test_doc.clone()) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Lexing error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(initial_ast) => {
|
||||||
|
match eval(initial_ast.clone(), vt.clone(), ft.clone(), false) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Evaluation error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(reduced) => {
|
||||||
|
if let Ctr::Seg(reduced_ast) = reduced {
|
||||||
|
assert_eq!(ast_to_string(reduced_ast), test_doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_function_call() {
|
||||||
|
let test_doc = "('one' (echo 'unwrap_me'))".to_string();
|
||||||
|
let output = "('one' 'unwrap_me')";
|
||||||
|
let test_external_func: Function = Function{
|
||||||
|
name: String::from("echo"),
|
||||||
|
loose_syms: false,
|
||||||
|
eval_lazy: false,
|
||||||
|
args: Args::Lazy(1),
|
||||||
|
function: Operation::External(
|
||||||
|
ExternalOperation{
|
||||||
|
arg_syms: vec!["input".to_string()],
|
||||||
|
ast: new_ast(Ctr::Seg(new_ast(Ctr::Symbol("input".to_string()), Ctr::None)), Ctr::None)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||||
|
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||||
|
if let Some(s) = func_declare(ft.clone(),
|
||||||
|
Rc::new(RefCell::new(test_external_func))) {
|
||||||
|
print!("Error declaring external func: {}", s);
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
match lex(test_doc) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Lexing error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(initial_ast) => {
|
||||||
|
match eval(initial_ast.clone(), vt.clone(), ft.clone(), false) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Evaluation error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(reduced) => {
|
||||||
|
if let Ctr::Seg(reduced_ast) = reduced {
|
||||||
|
let out_doc = ast_to_string(reduced_ast);
|
||||||
|
if out_doc != output {
|
||||||
|
print!("Erroneous output: {}\n", out_doc);
|
||||||
|
assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[test]
|
||||||
|
fn eval_embedded_func_calls() {
|
||||||
|
let test_doc = "1".to_string();
|
||||||
|
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||||
|
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||||
|
|
||||||
|
match lex(test_doc) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Lexing error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(initial_ast) => {
|
||||||
|
match eval(initial_ast.clone(), vt.clone(), ft.clone(), false) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Evaluation error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(reduced_ast) => {
|
||||||
|
// write tests here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_bad_vars() {
|
||||||
|
let test_doc = "1".to_string();
|
||||||
|
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||||
|
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||||
|
|
||||||
|
match lex(test_doc) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Lexing error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(initial_ast) => {
|
||||||
|
match eval(initial_ast.clone(), vt.clone(), ft.clone(), false) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Evaluation error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(reduced_ast) => {
|
||||||
|
// write tests here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_bad_func() {
|
||||||
|
let test_doc = "1".to_string();
|
||||||
|
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||||
|
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||||
|
|
||||||
|
match lex(test_doc) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Lexing error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(initial_ast) => {
|
||||||
|
match eval(initial_ast.clone(), vt.clone(), ft.clone(), false) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Evaluation error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(reduced_ast) => {
|
||||||
|
// write tests here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_verify_all_elems_cloned() {
|
||||||
|
let test_doc = "1".to_string();
|
||||||
|
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||||
|
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||||
|
|
||||||
|
match lex(test_doc) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Lexing error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(initial_ast) => {
|
||||||
|
match eval(initial_ast.clone(), vt.clone(), ft.clone(), false) {
|
||||||
|
Err(e) => {
|
||||||
|
println!("Evaluation error: {}\n", e);
|
||||||
|
assert!(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(reduced_ast) => {
|
||||||
|
// write tests here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
@ -352,6 +352,36 @@ mod func_tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_lazy_func_call() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sym_loose_func_call() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn too_many_args() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_enough_args() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bad_eval_arg() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bad_eval_fn_body() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TESTING TODO:
|
/* TESTING TODO:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue