diff --git a/the_rewrite/src/eval.rs b/the_rewrite/src/eval.rs new file mode 100644 index 0000000..e6d4f1f --- /dev/null +++ b/the_rewrite/src/eval.rs @@ -0,0 +1,139 @@ +/* relish: versatile lisp shell + * Copyright (C) 2021 Aidan Hahn + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +use crate::func::FTable; +use crate::segment::{Seg, Ctr}; +use crate::vars::VTable; + +/* iterates over a syntax tree + * returns a NEW LIST of values + * representing the simplest possible form of the input + */ +pub fn eval<'a>( + ast: &'a Seg<'a>, + vars: &'a mut VTable<'a>, + funcs: &'a mut FTable<'a>, + sym_loose: bool, +) -> Result>, String> { + let mut ret = Box::new(Ctr::Seg(Seg::new())); + let mut iter: &mut Seg; + if let Ctr::Seg(ref mut s) = *ret { + iter = s; + } else { + return Err("Critfail: no fuckin clue whats up here".to_string()) + } + + let mut car = &ast.car; + + // doing an initial variable check here allows us + // to find functions passed in as variables + if let Ctr::Symbol(tok) = **car { + if let Some(val) = vars.get(tok.clone()) { + car = &(val.clone()); + } + } + + // another check to detect if we may have a function call + if let Ctr::Symbol(ref tok) = **car { + match *ast.cdr { + Ctr::Seg(ref ast) => { + if let Some(func) = funcs.get(tok.clone()) { + return func.func_call(ast, vars, funcs); + } else if !sym_loose { + return Err(format!("Couldnt find definition of {}.", tok)); + } + } + Ctr::None => { + if let Some(func) = funcs.get(tok.clone()) { + return func.func_call(&Seg::new(), vars, funcs); + } else if !sym_loose { + return Err(format!("Couldnt find definition of {}.", tok.clone())); + } + } + _ => 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, vars, funcs, sym_loose) { + Ok(res) => (*iter).car = res, + Err(e) => return Err(format!("Evaluation error: {}", e)), + } + } + + // if SYMBOL: unwrap naively + Ctr::Symbol(ref tok) => { + if let Some(val) = vars.get(tok.clone()) { + iter.car = val.clone(); + } else if sym_loose { + iter.car = car.clone() + } else { + return Err(format!("Undefined variable: {}", tok.clone())); + } + } + + // if OTHER: clone and set + _ => { + iter.car = car.clone(); + } + } + + match *ast.cdr { + // if SYMBOL: unwrap naively, then end + Ctr::Symbol(ref tok) => { + if let Some(val) = vars.get(tok.clone()) { + iter.cdr = val.clone(); + } else if sym_loose { + iter.cdr = ast.cdr.clone() + } else { + return Err(format!("Undefined variable: {}", tok.clone())); + } + + none = true; + } + + // if LIST: + // - iter.cdr = new_ast(None, None) + // - iter = iter.cdr + // - car = cdr.car + // - cdr = cdr.cdr + // - LOOP + Ctr::Seg(next) => { + iter.cdr = Box::new(Ctr::Seg(Seg::new())); + ast = &next; + } + + // if OTHER: clone and set, and then end + _ => { + iter.cdr = ast.cdr.clone(); + none = true; + } + } + + if let Ctr::None = **car { + if let Ctr::None = *ast.cdr { + none = true; + } + } + } + + return Ok(ret); +} diff --git a/the_rewrite/src/func.rs b/the_rewrite/src/func.rs new file mode 100644 index 0000000..9365168 --- /dev/null +++ b/the_rewrite/src/func.rs @@ -0,0 +1,233 @@ +/* relish: versatile lisp shell + * Copyright (C) 2021 Aidan Hahn + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +use crate::eval::eval; +use crate::segment::{Seg, Ctr, Type}; +use crate::vars::VTable; +use std::collections::HashMap; +use std::convert::TryInto; + +pub struct FTable<'a>(HashMap>>); + +impl<'a> FTable<'a> { + pub fn declare(&mut self, func: Box>) -> Option { + if let Operation::External(ref fun) = &func.function { + if let Args::Lazy(ref i) = func.args { + if fun.arg_syms.len() != i.clone().try_into().unwrap() { + return Some( + "external function must have lazy args equal to declared arg_syms length" + .to_string(), + ); + } + } else { + return Some("external function must have lazy args".to_string()); + } + } + + self.0.insert(func.name, func); + None + } + + pub fn get(&self, id: String) -> Option<&Box>> { + self.0.get(&id) + } + + pub fn remove(&self, id: String) { + self.0.remove(&id); + } +} + + +// Standardized function signature for stdlib functions +//pub type InternalOperation = impl Fn(Box, Box, Box) -> Box; + +#[derive(Debug)] +pub struct ExternalOperation<'a> { + // Un-evaluated abstract syntax tree + // TODO: Intermediate evaluation to simplify branches with no argument in them + // Simplified branches must not have side effects. + // TODO: Apply Memoization? + pub ast: Box>, + // list of argument string tokens + pub arg_syms: Vec, +} + +/* A stored function may either be a pointer to a function + * or a syntax tree to eval with the arguments + */ +pub enum Operation<'a> { + Internal(Box Fn(&'b Seg, &'b mut VTable, &'b mut FTable) -> Ctr<'b>>), + External(ExternalOperation<'a>), +} + +/* Function Args + * If Lazy, is an integer denoting number of args + * If Strict, is a list of type tags denoting argument type. + */ +pub enum Args { + // signed: -1 denotes infinite args + Lazy(i128), + Strict(Vec), +} + +// function which does not need args checked +pub struct Function<'a> { + pub function: Operation<'a>, + pub name: String, + pub args: Args, + + // dont fail on undefined symbol (passed to eval) + pub loose_syms: bool, + + // dont evaluate args at all. leave that to the function + pub eval_lazy: bool, +} + +impl<'a> Function<'a> { + /* call + * routine is called by eval when a function call is detected + */ + pub fn func_call( + &self, + args: &'a Seg<'a>, + vars: &'a mut VTable<'a>, + funcs: &'a mut FTable<'a>, + ) -> Result>, String> { + let mut evaluated_args = args; + if !self.eval_lazy { + match eval(args, vars, funcs, self.loose_syms) { + Ok(arg_data) => { + if let Ctr::Seg(ast) = *arg_data { + evaluated_args = * + } else { + return Err("Panicking: eval returned not a list for function args.".to_string()); + } + } + Err(s) => { + return Err(format!( + "error evaluating args to {}: {}", + self.name, s + )) + } + } + } + + match self.args { + Args::Lazy(ref num) => { + let called_arg_count = evaluated_args.len() as i128; + if *num == 0 { + if let Ctr::None = *evaluated_args.car { + //pass + } else { + return Err(format!( + "expected 0 args in call to {}. Got one or more.", + self.name, + )); + } + } else if *num > -1 && (*num != called_arg_count) { + return Err(format!( + "expected {} args in call to {}. Got {}.", + num, self.name, called_arg_count + )); + } + } + + Args::Strict(ref arg_types) => { + let mut idx: usize = 0; + let passes = evaluated_args.circuit(&mut |c: &Ctr| -> bool { + if idx >= arg_types.len() { + return false; + } + + if let Ctr::None = c { + return false; + } + + if arg_types[idx] == c.to_type() { + idx += 1; + return true; + } + return false; + }); + + if passes && idx < (arg_types.len() - 1) { + return Err(format!( + "{} too little arguments in call to {}", + arg_types.len() - (idx + 1), + self.name + )); + } + + if !passes { + if idx < (arg_types.len() - 1) { + return Err(format!( + "argument {} in call to {} is of wrong type (expected {})", + idx + 1, + self.name, + arg_types[idx].to_string() + )); + } + + if idx == (arg_types.len() - 1) { + return Err(format!( + "too many arguments in call to {}", + self.name + )); + } + } + } + } + + /* corecursive with eval. + * essentially calls eval on each body in the function. + * result of the final body is returned. + */ + match &self.function { + Operation::Internal(ref f) => return Ok(Box::new(f(evaluated_args, vars, funcs))), + Operation::External(ref f) => { + for n in 0..f.arg_syms.len() { + let iter_arg = evaluated_args[n]; + vars.insert(f.arg_syms[n].clone(), Box::new(iter_arg)); + } + + let mut result: Box; + let iterate = &*(f.ast); + loop { + if let Ctr::Seg(ref data) = *iterate.car { + match eval(data, vars, funcs, self.loose_syms) { + Ok(ctr) => result = ctr, + Err(e) => return Err(e), + } + } else { + panic!("function body not in standard form!") + } + + match *iterate.cdr { + Ctr::Seg(ref next) => iterate = next, + Ctr::None => break, + _ => panic!("function body not in standard form!"), + } + } + for n in 0..f.arg_syms.len() { + vars.remove(f.arg_syms[n]); + } + + return Ok(result); + } + } + } +} diff --git a/the_rewrite/src/lib.rs b/the_rewrite/src/lib.rs index 7347455..6520d89 100644 --- a/the_rewrite/src/lib.rs +++ b/the_rewrite/src/lib.rs @@ -20,26 +20,21 @@ /*mod append; mod config; + */ mod eval; -mod func;*/ +mod func; mod lex; mod segment; +mod vars; /*mod stl; -mod str; -mod vars;*/ +mod str;*/ pub mod ast { -// pub use crate::eval::eval; -// pub use crate::func::{ -// func_call, func_declare, Args, ExternalOperation, FTable, Function, Operation, -// }; + pub use crate::eval::eval; + pub use crate::func::{Args, ExternalOperation, FTable, Function, Operation}; pub use crate::lex::lex; pub use crate::segment::{Ctr, Seg, Type}; -// pub use crate::vars::{define, VTable}; -} - -mod test { - + pub use crate::vars::VTable; } /*pub mod stdlib { diff --git a/the_rewrite/src/segment.rs b/the_rewrite/src/segment.rs index 5bbdc1f..d369275 100644 --- a/the_rewrite/src/segment.rs +++ b/the_rewrite/src/segment.rs @@ -16,6 +16,7 @@ */ use std::fmt; use std::marker::PhantomData; +use std::ops::Index; // Container #[derive(Debug, Default)] @@ -68,6 +69,8 @@ pub struct Seg<'a> { _lifetime_variance_determinant: PhantomData<&'a ()> } +static NOTHING: Ctr = Ctr::None; + impl Ctr<'_> { pub fn to_type(&self) -> Type { match self { @@ -84,48 +87,6 @@ impl Ctr<'_> { } impl<'a> Seg<'a> { - pub fn new() -> Seg<'a> { - return Seg{ - car: Box::new(Ctr::None), - cdr: Box::new(Ctr::None), - _lifetime_variance_determinant: PhantomData, - } - } - - pub fn from(arg: Box>) -> Seg<'a> { - return Seg{ - car: arg, - cdr: Box::new(Ctr::None), - _lifetime_variance_determinant: PhantomData, - } - } - - /* applies a function across a list in standard form - * function must take a Ctr and return a bool - * short circuits on the first false returned. - * also returns false on a non standard form list - */ - pub fn circuit bool>(&self, func: &mut F) -> bool { - if func(&self.car) { - match &*(self.cdr) { - Ctr::None => true, - Ctr::Seg(l) => l.circuit(func), - _ => false, - } - } else { - false - } - } - - /* recurs over ast assumed to be list in standard form - * returns length - */ - pub fn len(&self) -> u128 { - let mut len = 0; - self.circuit(&mut |_c: &Ctr| -> bool { len += 1; true }); - len - } - /* recurs over tree assumed to be list in standard form * appends object to end of list * @@ -148,6 +109,48 @@ impl<'a> Seg<'a> { // pray for memory lost to the void } } + + /* applies a function across a list in standard form + * function must take a Ctr and return a bool + * short circuits on the first false returned. + * also returns false on a non standard form list + */ + pub fn circuit bool>(&self, func: &mut F) -> bool { + if func(&self.car) { + match &*(self.cdr) { + Ctr::None => true, + Ctr::Seg(l) => l.circuit(func), + _ => false, + } + } else { + false + } + } + + pub fn from(arg: Box>) -> Seg<'a> { + return Seg{ + car: arg, + cdr: Box::new(Ctr::None), + _lifetime_variance_determinant: PhantomData, + } + } + + /* recurs over ast assumed to be list in standard form + * returns length + */ + pub fn len(&self) -> u128 { + let mut len = 0; + self.circuit(&mut |_c: &Ctr| -> bool { len += 1; true }); + len + } + + pub fn new() -> Seg<'a> { + return Seg{ + car: Box::new(Ctr::None), + cdr: Box::new(Ctr::None), + _lifetime_variance_determinant: PhantomData, + } + } } fn seg_to_string(s: &Seg, parens: bool) -> String { @@ -178,6 +181,22 @@ impl<'a> Clone for Seg<'a> { } } +impl<'a> Index for Seg<'a> { + type Output = Ctr<'a>; + + fn index(&self, idx: usize) -> &Self::Output { + if idx == 0 { + return &self.car; + } + + if let Ctr::Seg(ref s) = *self.cdr { + return s.index(idx - 1) + } + + return &NOTHING; + } +} + impl<'a> Clone for Ctr<'a> { fn clone(&self) -> Ctr<'a> { match self { diff --git a/the_rewrite/src/vars.rs b/the_rewrite/src/vars.rs new file mode 100644 index 0000000..008d99c --- /dev/null +++ b/the_rewrite/src/vars.rs @@ -0,0 +1,85 @@ +/* relish: versatile lisp shell + * Copyright (C) 2021 Aidan Hahn + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +use crate::eval::eval; +use crate::func::{Args, FTable, Function, Operation}; +use crate::segment::{Seg, Ctr}; +use std::collections::HashMap; +use std::env; +/* Mapping between a string token and a tree of Segments + * The string token can be found in any Ctr::Symbol value + * it is expected that the trees stored are already evaluated + */ +pub struct VTable<'a>(HashMap>>); + +impl<'a> VTable<'a> { + // WARNING: make sure var_tree is properly evaluated before storing + pub fn insert(&'a mut self, identifier: String, data: Box>) { + if let Some(datum) = self.0.insert(identifier, data) { + drop(datum); + } + } + + pub fn get(&'a self, id: String) -> Option>> { + return self.0.get(&id); + } + + pub fn remove(&self, id: String) { + self.0.remove(&id); + } +} + +// returns a callback for the stdlib var export function with env_sync on or off +pub fn get_export<'a> (env_cfg: bool) -> Function<'a> { + return Function { + name: String::from("export"), + loose_syms: true, + eval_lazy: true, + args: Args::Lazy(2), + function: Operation::Internal(Box::new( + |ast: &Seg, vars: &mut VTable, funcs: &mut FTable| -> Ctr { + if let Ctr::Symbol(identifier) = *ast.car { + match *ast.cdr { + Ctr::Seg(ref data_tree) => match eval(&Box::new(data_tree), vars, funcs, false) { + Ok(seg) => match *seg { + Ctr::Seg(val) => { + vars.insert(identifier, val.car); + if env_cfg { + env::set_var(identifier, val.car.to_string()) + } + }, + _ => eprintln!("impossible args to export"), + }, + Err(e) => eprintln!("couldnt eval symbol: {}", e), + }, + Ctr::None => { + vars.remove(identifier); + if env_cfg { + env::remove_var(identifier); + } + }, + _ => eprintln!("args not in standard form"), + } + } else { + eprintln!("first argument to export must be a symbol"); + } + return Ctr::None; + }, + )), + }; +} + diff --git a/the_rewrite/tests/test_eval.rs b/the_rewrite/tests/test_eval.rs new file mode 100644 index 0000000..22fb64c --- /dev/null +++ b/the_rewrite/tests/test_eval.rs @@ -0,0 +1,245 @@ +mod eval_tests { + use relish::ast::{ast_to_string, eval, lex, new_ast, FTable, VTable}; + use relish::ast::{func_declare, Args}; + use relish::ast::{Ctr, ExternalOperation, Function, Operation}; + use std::cell::RefCell; + use std::rc::Rc; + + // 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 = "('one' (echo (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_bad_vars() { + let test_doc = "".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 = "".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 = "".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 + } + } + } + } + }*/ +} diff --git a/the_rewrite/tests/test_func.rs b/the_rewrite/tests/test_func.rs new file mode 100644 index 0000000..540006b --- /dev/null +++ b/the_rewrite/tests/test_func.rs @@ -0,0 +1,356 @@ +mod func_tests { + use relish::ast::VTable; + use relish::ast::{ + func_call, func_declare, lex, Args, ExternalOperation, FTable, Function, Operation, + }; + use relish::ast::{new_ast, Ast, Ctr, Type}; + use std::cell::RefCell; + use std::rc::Rc; + + #[test] + fn decl_and_call_internal_func() { + let test_internal_func: Function = Function { + name: String::from("test_func_in"), + loose_syms: false, + eval_lazy: false, + args: Args::Strict(vec![Type::Bool]), + function: Operation::Internal(Box::new( + |a: Ast, _b: Rc>, _c: Rc>| -> Ctr { + let inner = a.borrow(); + let mut is_bool = false; + if let Ctr::Bool(_) = &inner.car { + is_bool = true; + } + + Ctr::Bool(is_bool) + }, + )), + }; + let ft = Rc::new(RefCell::new(FTable::new())); + let vt = Rc::new(RefCell::new(VTable::new())); + let args = new_ast(Ctr::Bool(true), Ctr::None); + if let Some(s) = func_declare(ft.clone(), Rc::new(RefCell::new(test_internal_func))) { + print!("Error declaring internal func: {}", s); + assert!(false); + } + + let func: Rc>; + if let Some(f) = ft.borrow().get(&"test_func_in".to_string()) { + func = f.clone(); + } else { + print!("failed to retrieve function!"); + assert!(false); + return; + } + + if let Ok(ret) = func_call(func, args, vt, ft) { + match ret { + Ctr::Bool(b) => assert!(b), + _ => { + print!("invalid return from func!"); + assert!(false); + } + } + } else { + print!("call to function failed!"); + assert!(false); + } + } + + #[test] + fn decl_and_call_external_func_singlet() { + match lex("((input))".to_string()) { + Err(e) => panic!("{}", e), + Ok(finner) => { + 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: finner, + }), + }; + let ft = Rc::new(RefCell::new(FTable::new())); + let vt = Rc::new(RefCell::new(VTable::new())); + let args = new_ast(Ctr::String("test".to_string()), Ctr::None); + if let Some(s) = func_declare(ft.clone(), Rc::new(RefCell::new(test_external_func))) + { + print!("Error declaring external func: {}", s); + assert!(false); + } + + let func: Rc>; + if let Some(f) = ft.borrow().get(&"echo".to_string()) { + func = f.clone(); + } else { + print!("failed to retrieve function!"); + assert!(false); + return; + } + + match func_call(func, args, vt, ft) { + 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] + fn decl_and_call_external_func_multi_body() { + match lex("((input) (input))".to_string()) { + Err(e) => panic!("{}", e), + Ok(finner) => { + let test_external_func: Function = Function { + name: String::from("echo_2"), + loose_syms: false, + eval_lazy: false, + args: Args::Lazy(1), + function: Operation::External(ExternalOperation { + arg_syms: vec!["input".to_string()], + ast: finner, + }), + }; + let ft = Rc::new(RefCell::new(FTable::new())); + let vt = Rc::new(RefCell::new(VTable::new())); + let args = new_ast(Ctr::String("test".to_string()), Ctr::None); + if let Some(s) = func_declare(ft.clone(), Rc::new(RefCell::new(test_external_func))) + { + print!("Error declaring external func: {}", s); + assert!(false); + } + + let func: Rc>; + if let Some(f) = ft.borrow().get(&"echo_2".to_string()) { + func = f.clone(); + } else { + print!("failed to retrieve function!"); + assert!(false); + return; + } + + match func_call(func, args, vt, ft) { + Ok(ret) => match ret { + Ctr::String(s) => { + assert!(s == "test"); + } + _ => { + print!("Invalid return from function {:#?}. Should have recieved single string", ret); + assert!(false); + return; + } + }, + Err(e) => { + print!("Call to function failed: {}\n", e); + assert!(false); + } + } + } + } + } + + #[test] + fn decl_and_call_func_with_nested_call() { + let inner_func: Function = Function { + name: String::from("test_inner"), + loose_syms: false, + eval_lazy: false, + args: Args::Strict(vec![Type::Bool]), + function: Operation::Internal(Box::new( + |a: Ast, _b: Rc>, _c: Rc>| -> Ctr { + let inner = a.borrow(); + if let Ctr::Bool(b) = &inner.car { + if *b { + Ctr::String("test".to_string()) + } else { + Ctr::None + } + } else { + Ctr::None + } + }, + )), + }; + + match lex("((test_inner true))".to_string()) { + Err(e) => panic!("{}", e), + Ok(finner) => { + let outer_func: Function = Function { + name: String::from("test_outer"), + loose_syms: false, + eval_lazy: false, + args: Args::Lazy(1), + function: Operation::External(ExternalOperation { + arg_syms: vec!["input".to_string()], + ast: finner, + }), + }; + + let ft = Rc::new(RefCell::new(FTable::new())); + let vt = Rc::new(RefCell::new(VTable::new())); + let args = new_ast(Ctr::Bool(true), Ctr::None); + + if let Some(s) = func_declare(ft.clone(), Rc::new(RefCell::new(inner_func))) { + print!("Error declaring inner func: {}", s); + assert!(false); + } + if let Some(s) = func_declare(ft.clone(), Rc::new(RefCell::new(outer_func))) { + print!("Error declaring outer func: {}", s); + assert!(false); + } + + let func: Rc>; + if let Some(f) = ft.borrow().get(&"test_outer".to_string()) { + func = f.clone(); + } else { + print!("failed to retrieve function!"); + assert!(false); + return; + } + + match func_call(func, args, vt, ft) { + 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); + } + } + } + } + } + + /* This test removed because it would make more sense to test this functionality in eval tests + * this function tested the evaluation of a complex argument ast that could be reduced in eval + + #[test] + fn decl_and_call_func_eval_arg() { + let is_true_func: Function = Function{ + name: String::from("is_true?"), + loose_syms: false, + eval_lazy: false, + args: Args::Strict(vec![Type::Bool]), + function: Operation::Internal( + |a: Ast, _b: Rc>, _c: Rc>| -> Ctr { + let inner = a.borrow(); + if let Ctr::Bool(b) = &inner.car { + if *b { + Ctr::String("test".to_string()) + } else { + Ctr::None + } + } else { + Ctr::None + } + } + ) + }; + + let echo_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::Symbol("input".to_string()), Ctr::None) + } + ) + }; + + let ft = Rc::new(RefCell::new(FTable::new())); + let vt = Rc::new(RefCell::new(VTable::new())); + let args = new_ast( + Ctr::Seg(new_ast( + Ctr::Symbol("is_true?".to_string()), + Ctr::Seg(new_ast( + Ctr::Bool(true), + Ctr::None)))), + Ctr::None); + + if let Some(s) = func_declare(ft.clone(), + Rc::new(RefCell::new(is_true_func))) { + print!("Error declaring inner func: {}", s); + assert!(false); + } + if let Some(s) = func_declare(ft.clone(), + Rc::new(RefCell::new(echo_func))) { + print!("Error declaring outer func: {}", s); + assert!(false); + } + + let func: Rc>; + if let Some(f) = ft.borrow().get(&"echo".to_string()) { + func = f.clone(); + } else { + print!("failed to retrieve function!"); + assert!(false); + return; + } + + match func_call(func, args, vt, ft) { + 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); + } + } + }*/ + + /* + // TODO: These tests need completion! + #[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() { + + }*/ +} diff --git a/the_rewrite/tests/test_vars.rs b/the_rewrite/tests/test_vars.rs new file mode 100644 index 0000000..a63663b --- /dev/null +++ b/the_rewrite/tests/test_vars.rs @@ -0,0 +1,64 @@ +mod var_lib_tests { + use relish::ast::{eval, lex, Ctr, FTable, VTable}; + use relish::stdlib::get_stdlib; + use std::cell::RefCell; + use std::rc::Rc; + + #[test] + fn test_variable_export_and_lookup() { + let doc1 = "(export test 1)"; + let doc2 = "(concat test)"; + let result = "1"; + let vt = Rc::new(RefCell::new(VTable::new())); + let ft: Rc>; + match get_stdlib(vt.clone()) { + Ok(f) => ft = f, + Err(s) => { + ft = Rc::new(RefCell::new(FTable::new())); + println!("Couldnt get stdlib: {}!", s); + assert!(false); + } + } + + match lex(doc1.to_string()) { + Err(s) => { + println!("Couldnt lex {}: {}", doc1, s); + assert!(false); + } + + Ok(tree) => match eval(tree, vt.clone(), ft.clone(), false) { + Err(s) => { + println!("Couldnt eval {}: {}", doc2, s); + assert!(false); + } + + Ok(ctr) => { + println!("{:#?}", vt); + match ctr { + Ctr::None => assert!(true), + _ => assert!(false), + } + } + }, + } + + match lex(doc2.to_string()) { + Err(s) => { + println!("Couldnt lex {}: {}", doc2, s); + assert!(false); + } + + Ok(tree) => match eval(tree, vt.clone(), ft.clone(), false) { + Err(s) => { + println!("Couldnt eval {}: {}", doc2, s); + assert!(false); + } + + Ok(ctr) => match ctr { + Ctr::String(s) => assert_eq!(s, result), + _ => assert!(false), + }, + }, + } + } +}