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),
+ },
+ },
+ }
+ }
+}