diff --git a/Cargo.toml b/Cargo.toml index 7214a92..db0c100 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ edition = "2018" [dependencies] dirs = "3.0" -rustyline = "8.2.0" \ No newline at end of file +lazy_static = "1.4.0" +rustyline = "8.2.0" diff --git a/Readme.org b/Readme.org index 81cddc5..078f957 100644 --- a/Readme.org +++ b/Readme.org @@ -119,15 +119,16 @@ Note: this section will not show the status of each item unless you are viewing **** DONE Derive Default for Seg **** WONTDO From/Into for Ctr **** DONE Iterator for Seg -**** TODO Consider RefCell for tables, or using 'static lifetime +**** WONTDO Consider RefCell for tables, or using 'static lifetime one of these may fix borrow checker insanity -all you have to do is actually learn this shit -just pay attention +**** DONE make FTable and VTable singleton modules (global mutable) +**** WONTDO make FTable and VTable into private definitions **** DONE YEET AST EVERYWHERE. PASS AROUND A SEG REF. *** TODO refactor code into more digestible, better organized pieces -Just need to get core compiling at this point. -Refactors made include splitting func_call into reasonable segments -Reorganizing eval +**** DONE combine var and func table into global sym table +**** TODO code deduplication and cleanup in sym table +**** TODO refactor and reimplement eval +**** TODO (test and get it working) *** TODO Rudimentary Control Flow **** TODO if clause **** TODO loop clause @@ -175,5 +176,6 @@ Will need a concatenate function for func tables ***** TODO HTTP Listener ***** TODO UDP Listener *** TODO Ensure full test coverage - -THE GOOD STUFF IS IN Repositories/temprl +**** TODO Test for VTable overwrites in func_call +**** TODO Implement missing VTable, FTable, Lex, Eval tests +**** TODO stdlib tests :) diff --git a/the_rewrite/src/eval.rs b/legacy_snippets/eval_draft2.rs similarity index 98% rename from the_rewrite/src/eval.rs rename to legacy_snippets/eval_draft2.rs index 1cd953c..02b94f0 100644 --- a/the_rewrite/src/eval.rs +++ b/legacy_snippets/eval_draft2.rs @@ -72,7 +72,7 @@ pub fn eval<'a, 'b>( } Ctr::None => { if let Some(ref func) = funcs.get(tok.clone()) { - return func.func_call(&Seg::new(), vars, funcs); + return (*func).func_call(&Seg::new(), vars, funcs); } else if !sym_loose { return Err(format!("Couldnt find definition of {}.", tok.clone())); } diff --git a/src/append.rs b/legacy_snippets/initial_rough_spaghetti_implementation/append.rs similarity index 100% rename from src/append.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/append.rs diff --git a/src/bin/main.rs b/legacy_snippets/initial_rough_spaghetti_implementation/bin/main.rs similarity index 100% rename from src/bin/main.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/bin/main.rs diff --git a/src/config.rs b/legacy_snippets/initial_rough_spaghetti_implementation/config.rs similarity index 100% rename from src/config.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/config.rs diff --git a/src/control.rs b/legacy_snippets/initial_rough_spaghetti_implementation/control.rs similarity index 100% rename from src/control.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/control.rs diff --git a/legacy_snippets/initial_rough_spaghetti_implementation/eval.rs b/legacy_snippets/initial_rough_spaghetti_implementation/eval.rs new file mode 100644 index 0000000..23fbfc7 --- /dev/null +++ b/legacy_snippets/initial_rough_spaghetti_implementation/eval.rs @@ -0,0 +1,144 @@ +/* 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::{func_call, FTable}; +use crate::segment::{new_ast, Ast, Ctr}; +use crate::vars::VTable; +use std::cell::RefCell; +use std::rc::Rc; + +/* iterates over a syntax tree + * returns a NEW LIST of values + * representing the simplest possible form of the input + */ +pub fn eval( + ast: Ast, + vars: Rc>, + funcs: Rc>, + sym_loose: bool, +) -> Result { + 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 { + match cdr.clone() { + Ctr::Seg(ast) => { + if let Some(func) = funcs.borrow().get(tok) { + return func_call(func.clone(), ast.clone(), vars.clone(), funcs.clone()); + } else if !sym_loose { + return Err(format!("Couldnt find definition of {}.", tok)); + } + } + Ctr::None => { + if let Some(func) = funcs.borrow().get(tok) { + return func_call( + func.clone(), + new_ast(Ctr::None, Ctr::None), + vars.clone(), + funcs.clone(), + ); + } else if !sym_loose { + return Err(format!("Couldnt find definition of {}.", tok)); + } + } + _ => 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)); +} diff --git a/src/func.rs b/legacy_snippets/initial_rough_spaghetti_implementation/func.rs similarity index 100% rename from src/func.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/func.rs diff --git a/the_rewrite/src/lex.rs b/legacy_snippets/initial_rough_spaghetti_implementation/lex.rs similarity index 82% rename from the_rewrite/src/lex.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/lex.rs index 337849b..87700c8 100644 --- a/the_rewrite/src/lex.rs +++ b/legacy_snippets/initial_rough_spaghetti_implementation/lex.rs @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -use crate::segment::{Ctr, Seg}; +use crate::segment::{list_append, Ctr, Seg}; const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input"; const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input"; @@ -23,7 +23,7 @@ const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input"; /* takes a line of user input * returns an unsimplified tree of tokens. */ -pub fn lex<'a>(document: &'a String) -> Result, String> { +pub fn lex<'a>(document: String) -> Result>, String> { if !document.is_ascii() { return Err("document may only contain ascii characters".to_string()); } @@ -42,7 +42,7 @@ pub fn lex<'a>(document: &'a String) -> Result, String> { * Returns Ok(Rc) if lexing passes * Returns Err(String) if an error occurs */ -fn process<'a>(document: &'a String) -> Result, String> { +fn process<'a>(document: &'a String) -> Result>, String> { let doc_len = document.len(); if doc_len == 0 { @@ -120,7 +120,8 @@ fn process<'a>(document: &'a String) -> Result, String> { return Err("list started in middle of another token".to_string()); } - ref_stack.push(Seg::new()); + ref_stack.push(Box::new(Seg::new())); + delim_stack.push(')'); } // begin parsing a string @@ -151,47 +152,42 @@ fn process<'a>(document: &'a String) -> Result, String> { return Err("Empty token".to_string()); } - let mut current_seg = ref_stack.pop().unwrap(); - let obj; + let mut current_seg = ref_stack.pop(); + let mut obj; if is_str { - obj = Box::from(Ctr::String(token)); + obj = Ctr::String(token); is_str = false; token = String::new(); - current_seg.append(obj); } else if token.len() > 0 { if token == "true" { - obj = Box::from(Ctr::Bool(true)); + obj = Ctr::Bool(true); } else if token == "false" { - obj = Box::from(Ctr::Bool(false)); + obj = Ctr::Bool(false); } else if let Ok(i) = token.parse::() { - obj = Box::from(Ctr::Integer(i)); + obj = Ctr::Integer(i); } else if let Ok(f) = token.parse::() { - obj = Box::from(Ctr::Float(f)); + obj = Ctr::Float(f); } else if let Some(s) = tok_is_symbol(&token) { - obj = Box::from(Ctr::Symbol(s)); + obj = Ctr::Symbol(s); } else { return Err(format!("Unparsable token: {}", token)); } token = String::new(); - current_seg.append(obj.clone()); } + list_append(current_seg, obj); + if alloc_list { // return if we have finished the document if ref_stack.len() == 0 { - return Ok(Box::new(current_seg)); + return Ok(current_seg); } - let t = current_seg; - current_seg = ref_stack.pop().unwrap(); - /* TODO: is there a way to do this that doesnt - * involve needlessly copying heap data? I am - * not sure what optimizations rustc performs - * but I assume this should not end up copying - * contained segments around. - */ - current_seg.append(Box::from(Ctr::Seg(t))); + // shortening this will lead to naught but pain + obj = Ctr::Seg(current_seg.into_raw()); + current_seg = ref_stack.pop(); + list_append(current_seg, obj); } ref_stack.push(current_seg); diff --git a/the_rewrite/src/lib.rs b/legacy_snippets/initial_rough_spaghetti_implementation/lib.rs similarity index 80% rename from the_rewrite/src/lib.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/lib.rs index 534f3e5..6a48eef 100644 --- a/the_rewrite/src/lib.rs +++ b/legacy_snippets/initial_rough_spaghetti_implementation/lib.rs @@ -15,32 +15,35 @@ * along with this program. If not, see . */ -/*mod append; +#![feature(derive_default_enum)] + +mod append; mod config; - */ mod eval; mod func; mod lex; mod segment; +mod stl; +mod str; mod vars; -/*mod stl; -mod str;*/ pub mod ast { pub use crate::eval::eval; - pub use crate::func::{Args, ExternalOperation, FTable, Function, Operation}; + pub use crate::func::{ + func_call, func_declare, Args, ExternalOperation, FTable, Function, Operation, + }; pub use crate::lex::lex; pub use crate::segment::{Ctr, Seg, Type}; - pub use crate::vars::VTable; + pub use crate::vars::{define, VTable}; } -/*pub mod stdlib { +pub mod stdlib { pub use crate::append::get_append; pub use crate::stl::get_stdlib; pub use crate::str::{get_concat, get_echo}; pub use crate::vars::get_export; -}*/ +} -/*pub mod aux { +pub mod aux { pub use crate::config::configure; -}*/ +} diff --git a/legacy_snippets/initial_rough_spaghetti_implementation/segment.rs b/legacy_snippets/initial_rough_spaghetti_implementation/segment.rs new file mode 100644 index 0000000..3535aa0 --- /dev/null +++ b/legacy_snippets/initial_rough_spaghetti_implementation/segment.rs @@ -0,0 +1,210 @@ +/* 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 std::fmt; + + +// Container +#[derive(Debug, Clone, Default)] +pub enum Ctr <'a> { + Symbol(String), + String(String), + Integer(i128), + Float(f64), + Bool(bool), + Seg(Seg<'a>), + #[default] + None, +} + +// Type of Container +#[derive(PartialEq, Clone)] +pub enum Type { + Symbol, + String, + Integer, + Float, + Bool, + Seg, + None, +} + +/* Segment + * Holds two Containers. + * Basic building block for more complex data structures. + */ +#[derive(Clone, Debug, Default)] +pub struct Seg <'a> { + /* "Contents of Address Register" + * Historical way of referring to the first value in a cell. + */ + pub car: &mut Ctr<'a>, + + /* "Contents of Decrement Register" + * Historical way of referring to the second value in a cell. + */ + pub cdr: &mut Ctr<'a>, +} + +impl Ctr<'_> { + pub fn to_type(&self) -> Type { + match self { + Ctr::Symbol(_s) => Type::Symbol, + Ctr::String(_s) => Type::String, + Ctr::Integer(_s) => Type::Integer, + Ctr::Float(_s) => Type::Float, + Ctr::Bool(_s) => Type::Bool, + Ctr::Seg(_s) => Type::Seg, + Ctr::None => Type::None, + } + } + +} + +fn seg_to_string(s: &Seg, parens: bool) -> String { + let mut string = String::new(); + match s.car { + Ctr::None => string.push_str(""), + _ => string.push_str(s.car), + } + string.push(' '); + match s.cdr { + Ctr::Seg(inner) => string.push_str(seg_to_string(inner, false)), + Ctr::None => {}, + _ => string.push_str(s.cdr), + } + + if parens { + String::from("(" + string + ")") + } +} + +impl fmt::Display for Ctr <'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Ctr::Symbol(s) => write!(f, "{}", s), + Ctr::String(s) => write!(f, "\'{}\'", s), + Ctr::Integer(s) => write!(f, "{}", s), + Ctr::Float(s) => write!(f, "{}", s), + Ctr::Bool(s) => { + if s { + write!(f, "T") + } else { + write!(f, "F") + } + }, + Ctr::Seg(s) => write!(f, "{}", s), + Ctr::None => Ok(), + } + } +} + +impl fmt::Display for Seg<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", seg_to_string(self, true)) + } +} + +impl Iterator for Seg<'_> { + fn next(&self) -> Option<&Seg> { + if let Ctr::Seg(s) = self.cdr { + Ok(s) + } else { + None() + } + } +} + +impl Type { + pub fn to_string(&self) -> String { + let ret: &str; + match self { + Type::Symbol => ret = "symbol", + Type::String => ret = "string", + Type::Integer => ret = "integer", + Type::Float => ret = "float", + Type::Bool => ret = "bool", + Type::Seg => ret = "segment", + Type::None => ret = "none", + } + + ret.to_owned() + } +} + +/* 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(list: &Seg, func: &mut F) -> bool { + if func(&list.car) { + match list.cdr { + Ctr::None => true, + Ctr::Seg(l) => circuit(l, func), + _ => false, + } + } else { + false + } +} + +/* recurs over ast assumed to be list in standard form + * returns length + */ +pub fn list_len(list: &Seg) -> u128 { + let mut len = 0; + circuit(list, &circuit(&mut |c: &Ctr| -> bool { len += 1; true })) +} + +/* recurs over tree assumed to be list in standard form + * returns clone of ctr at index provided + * + * TODO: return result (or option?) + */ +pub fn list_idx<'a>(list: &Seg, idx: u128) -> Ctr<'a> { + if idx > 0 { + if let Ctr::Seg(s) = list.car { + list_idx(s, idx - 1) + } else if idx == 1 { + list.cdr + } else { + Ctr::None + } + } else { + list.car + } +} + +/* recurs over tree assumed to be list in standard form + * appends object to end of list + * + * TODO: return result + */ +pub fn list_append<'a>(list: &Seg, obj: Ctr) { + if let Ctr::None = list.car { + list.car = obj; + return + } + + if let Ctr::Seg(s) = list.cdr { + list_append(s, obj) + } + + if let Ctr::None = list.cdr { + list.cdr = Ctr::Seg(&Seg{car:obj, cdr:Ctr::None}) + } +} diff --git a/the_rewrite/tests/test_eval.rs b/legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_eval.rs similarity index 100% rename from the_rewrite/tests/test_eval.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_eval.rs diff --git a/the_rewrite/tests/test_func.rs b/legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_func.rs similarity index 100% rename from the_rewrite/tests/test_func.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_func.rs diff --git a/the_rewrite/tests/test_lex.rs b/legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lex.rs similarity index 64% rename from the_rewrite/tests/test_lex.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lex.rs index 3469b4a..ff3d946 100644 --- a/the_rewrite/tests/test_lex.rs +++ b/legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lex.rs @@ -3,10 +3,10 @@ mod lex_tests { #[test] fn test_lex_basic_pair() { - let document = String::from("(hello 'world')"); - match lex(&document) { + let document: &str = "(hello 'world')"; + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), document); + assert_eq!(tree, document); } Err(s) => { print!("{}\n", s); @@ -17,10 +17,10 @@ mod lex_tests { #[test] fn test_lex_basic_list() { - let document = String::from("(hello 'world' 1 2 3)"); - match lex(&document) { + let document: &str = "(hello 'world' 1 2 3)"; + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), document); + assert_eq!(tree, document); } Err(s) => { print!("{}\n", s); @@ -31,10 +31,10 @@ mod lex_tests { #[test] fn test_lex_complex_list() { - let document = String::from("(hello 'world' (1 2 (1 2 3)) 1 2 3)"); - match lex(&document) { + let document: &str = "(hello 'world' (1 2 (1 2 3)) 1 2 3)"; + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), document); + assert_eq!(tree, document); } Err(s) => { print!("{}\n", s); @@ -45,11 +45,11 @@ mod lex_tests { #[test] fn test_bad_symbol() { - let document = String::from("(as;dd)"); + let document: &str = "(as;dd)"; let output: &str = "Problem lexing document: \"Unparsable token: as;dd\""; - match lex(&document) { + match lex(document) { Ok(tree) => { - print!("Bad token yielded: {}\n", tree.to_string()); + print!("Bad token yielded: {}\n", tree); assert!(false); } Err(s) => { @@ -60,10 +60,10 @@ mod lex_tests { #[test] fn test_list_delim_in_str() { - let document = String::from("('(')"); - match lex(&document) { + let document: &str = "('(')"; + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), document); + assert_eq!(tree, document); } Err(s) => { print!("{}\n", s); @@ -74,10 +74,10 @@ mod lex_tests { #[test] fn test_empty_string() { - let document = String::from("('')"); - match lex(&document) { + let document: &str = "('')"; + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), document); + assert_eq!(tree, document); } Err(s) => { print!("{}\n", s); @@ -88,11 +88,11 @@ mod lex_tests { #[test] fn test_unmatched_list_delim_flat() { - let document = String::from("(one two"); + let document: &str = "(one two"; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; - match lex(&document) { + match lex(document) { Ok(tree) => { - print!("Bad token yielded: {}\n", tree.to_string()); + print!("Bad token yielded: {}\n", tree); assert!(false); } Err(s) => { @@ -103,9 +103,9 @@ mod lex_tests { #[test] fn test_unmatched_list_delim_complex() { - let document = String::from("(one two (three)"); + let document: &str = "(one two (three)"; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; - match lex(&document) { + match lex(document) { Ok(tree) => { print!("Bad token yielded: {}\n", tree); assert!(false); @@ -118,11 +118,11 @@ mod lex_tests { #[test] fn test_comment() { - let document = String::from("#!/bin/relish\n(one two)"); + let document: &str = "#!/bin/relish\n(one two)"; let output: &str = "(one two)"; - match lex(&document) { + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), output); + assert_eq!(tree, output); } Err(s) => { print!("{}\n", s); @@ -133,11 +133,11 @@ mod lex_tests { #[test] fn test_postline_comment() { - let document = String::from("#!/bin/relish\n((one two)# another doc comment\n(three four))"); + let document: &str = "#!/bin/relish\n((one two)# another doc comment\n(three four))"; let output: &str = "((one two) (three four))"; - match lex(&document) { + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), output.to_string()); + assert_eq!(tree, output.to_string()); } Err(s) => { print!("{}\n", s); @@ -148,11 +148,11 @@ mod lex_tests { #[test] fn test_inline_comment() { - let document = String::from("#!/bin/relish\n((one two)\n# another doc comment\nthree)"); + let document: &str = "#!/bin/relish\n((one two)\n# another doc comment\nthree)"; let output: &str = "((one two) three)"; - match lex(&document) { + match lex(document) { Ok(tree) => { - assert_eq!(tree.to_string(), output.to_string()); + assert_eq!(tree, output); } Err(s) => { print!("{}\n", s); @@ -163,9 +163,9 @@ mod lex_tests { #[test] fn test_bad_token_list() { - let document = String::from("(one t(wo)"); + let document: &str = "(one t(wo)"; let output: &str = "Problem lexing document: \"list started in middle of another token\""; - match lex(&document) { + match lex(document) { Ok(tree) => { print!("Bad token yielded: {}\n", tree); assert!(false); diff --git a/tests/test_lib_append.rs b/legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lib_append.rs similarity index 100% rename from tests/test_lib_append.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lib_append.rs diff --git a/tests/test_lib_str.rs b/legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lib_str.rs similarity index 100% rename from tests/test_lib_str.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lib_str.rs diff --git a/tests/test_lib_vars.rs b/legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lib_vars.rs similarity index 100% rename from tests/test_lib_vars.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/spaghetti_tests/test_lib_vars.rs diff --git a/src/stl.rs b/legacy_snippets/initial_rough_spaghetti_implementation/stl.rs similarity index 100% rename from src/stl.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/stl.rs diff --git a/src/str.rs b/legacy_snippets/initial_rough_spaghetti_implementation/str.rs similarity index 100% rename from src/str.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/str.rs diff --git a/src/vars.rs b/legacy_snippets/initial_rough_spaghetti_implementation/vars.rs similarity index 100% rename from src/vars.rs rename to legacy_snippets/initial_rough_spaghetti_implementation/vars.rs diff --git a/the_rewrite/src/func.rs b/legacy_snippets/seperate_tables/func.rs similarity index 82% rename from the_rewrite/src/func.rs rename to legacy_snippets/seperate_tables/func.rs index 38811b7..c38156e 100644 --- a/the_rewrite/src/func.rs +++ b/legacy_snippets/seperate_tables/func.rs @@ -17,14 +17,24 @@ use crate::eval::eval; use crate::segment::{Seg, Ctr, Type}; -use crate::vars::VTable; +use crate::vars::{VAR_TABLE, VTable, LIB_EXPORT}; use std::collections::HashMap; use std::convert::TryInto; +use lazy_static::lazy_static; -pub struct FTable<'a>(HashMap>>); +lazy_static! { + pub static ref FUNC_TABLE: FTable<'static> = { + let mut tab = FTable::new(); + tab.declare(LIB_EXPORT); + tab + }; +} -impl<'a> FTable<'a> { - pub fn declare(&mut self, func: Box>) -> Option { + +pub struct FTable<'a> (HashMap); + +impl FTable<'_> { + 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() { @@ -42,7 +52,7 @@ impl<'a> FTable<'a> { None } - pub fn get(&self, id: String) -> Option<&Box>> { + pub fn get(&self, id: String) -> Option<&Box> { self.0.get(&id) } @@ -50,7 +60,7 @@ impl<'a> FTable<'a> { self.0.remove(&id); } - pub fn new() -> FTable<'a> { + pub fn new() -> FTable<'static> { FTable{0: HashMap::>::new()} } } @@ -74,7 +84,7 @@ pub struct ExternalOperation<'a> { * or a syntax tree to eval with the arguments */ pub enum Operation<'a> { - Internal(Box Ctr<'a>>), + Internal(Box Ctr<'a>>), External(ExternalOperation<'a>), } @@ -113,11 +123,9 @@ impl Args { 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; @@ -140,7 +148,6 @@ impl Args { arg_types[idx].to_string() )); } - if idx == (arg_types.len() - 1) { return Err("too many arguments".to_string()); } @@ -152,8 +159,8 @@ impl Args { } } -pub struct Function<'a> { - pub function: Operation<'a>, +pub struct Function { + pub function: Operation, pub name: String, pub args: Args, @@ -164,19 +171,17 @@ pub struct Function<'a> { pub eval_lazy: bool, } -impl<'a, 'b, 'c> Function<'a> { +impl<'b, 'c> Function { /* call * routine is called by eval when a function call is detected */ pub fn func_call( &self, args: &'b Seg<'b>, - vars: &'a mut VTable<'a>, - funcs: &'a mut FTable<'a>, ) -> Result>, String> { // put args in simplest desired form let evaluated_args; - match eval(args, vars, funcs, self.loose_syms, self.eval_lazy) { + match eval(args, self.loose_syms, self.eval_lazy) { Ok(arg_data) => { if let Ctr::Seg(ast) = *arg_data { evaluated_args = * @@ -201,18 +206,26 @@ impl<'a, 'b, 'c> Function<'a> { * result of the final body is returned. */ match &self.function { - Operation::Internal(ref f) => return Ok(Box::new(f(evaluated_args, vars, funcs))), + Operation::Internal(ref f) => return Ok(Box::new(f(evaluated_args))), Operation::External(ref f) => { + let mut holding_table = VTable::new(); + + // Prep var table for function execution 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)); + if let Some(val) = VAR_TABLE.get(f.arg_syms[n]) { + holding_table.insert(f.arg_syms[n].clone(), val); + } + + VAR_TABLE.insert(f.arg_syms[n].clone(), Box::new(iter_arg)); } + // execute function 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, true) { + match eval(data, self.loose_syms, true) { Ok(ctr) => result = ctr, Err(e) => return Err(e), } @@ -226,8 +239,13 @@ impl<'a, 'b, 'c> Function<'a> { _ => panic!("function body not in standard form!"), } } + + // clear local vars and restore previous values for n in 0..f.arg_syms.len() { - vars.remove(f.arg_syms[n]); + VAR_TABLE.remove(f.arg_syms[n]); + if let Some(val) = holding_table.get(f.arg_syms[n]) { + VAR_TABLE.insert(f.arg_syms[n].clone(), val); + } } return Ok(result); diff --git a/the_rewrite/src/vars.rs b/legacy_snippets/seperate_tables/vars.rs similarity index 70% rename from the_rewrite/src/vars.rs rename to legacy_snippets/seperate_tables/vars.rs index f143566..47196d5 100644 --- a/the_rewrite/src/vars.rs +++ b/legacy_snippets/seperate_tables/vars.rs @@ -16,10 +16,18 @@ */ use crate::eval::eval; -use crate::func::{Args, FTable, Function, Operation}; +use crate::func::{Args, Function, Operation}; use crate::segment::{Seg, Ctr}; use std::collections::HashMap; use std::env; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref VAR_TABLE: VTable<'static> = { + VTable::new() + }; +} + /* 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 @@ -50,29 +58,45 @@ impl<'a, 'b> VTable<'a> { } } -// 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 { +// the stdlib var export function with env_sync on +lazy_static! { + pub static ref LIB_EXPORT_ENV: Function = Function { name: String::from("export"), loose_syms: true, eval_lazy: true, args: Args::Lazy(2), - function: Operation::Internal(Box::new( - move |ast: &Seg, vars: &mut VTable, funcs: &mut FTable| -> Ctr { - _export_callback(ast, vars, funcs, env_cfg) + function: Operation::Internal(Box::new( move |ast: &Seg| -> Ctr { + _export_callback(ast, true) + }, + )), + }; +} + +// the stdlib var export function with env_sync off +lazy_static! { + pub static ref LIB_EXPORT_NO_ENV: Function = Function { + name: String::from("export"), + loose_syms: true, + eval_lazy: true, + args: Args::Lazy(2), + function: Operation::Internal(Box::new( move |ast: &Seg| -> Ctr { + _export_callback(ast, false) }, )), }; } -fn _export_callback<'a> (ast: &Seg, vars: &mut VTable, funcs: &mut FTable, env_cfg: bool) -> Ctr<'a> { +fn _export_callback<'a> (ast: &Seg, env_cfg: bool) -> Ctr<'a> { if let Ctr::Symbol(ref identifier) = *ast.car { match &*ast.cdr { - Ctr::Seg(data_tree) => match eval(&Box::new(data_tree), vars, funcs, false, true) { + Ctr::Seg(data_tree) => match eval(&Box::new(data_tree), false, true) { Ok(seg) => match *seg { Ctr::Seg(val) => { - vars.insert(identifier.clone(), val.car); + SYM_TABLE.declare(Symbol { + value: UserVar(val.car), + name: identifier.clone(), + }); if env_cfg { env::set_var(identifier.clone(), val.car.to_string()) } @@ -82,7 +106,7 @@ fn _export_callback<'a> (ast: &Seg, vars: &mut VTable, funcs: &mut FTable, env_c Err(e) => eprintln!("couldnt eval symbol: {}", e), }, Ctr::None => { - vars.remove(identifier.to_string()); + VAR_TABLE.remove(identifier.to_string()); if env_cfg { env::remove_var(identifier.to_string()); } diff --git a/src/eval.rs b/src/eval.rs index 23fbfc7..c09b6b8 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -15,130 +15,129 @@ * along with this program. If not, see . */ -use crate::func::{func_call, FTable}; -use crate::segment::{new_ast, Ast, Ctr}; -use crate::vars::VTable; -use std::cell::RefCell; -use std::rc::Rc; + +use crate::segment::{Seg, Ctr}; +use crate::sym::SYM_TABLE; /* iterates over a syntax tree * returns a NEW LIST of values * representing the simplest possible form of the input */ + pub fn eval( - ast: Ast, - vars: Rc>, - funcs: Rc>, + ast: &Seg, sym_loose: bool, -) -> Result { - 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(); + call_lazy: bool, +) -> Result, String> { + // data to return + let mut ret = Box::from(Ctr::None); + + // to be assigned from cloned/evaled data + let mut car; + let mut cdr = Box::from(Ctr::None); + + // lets me redirect the input + let mut arg_car = &ast.car; + let mut arg_cdr = &ast.cdr; + + // theres probably a better way to do this + let mut binding_for_vtable_get; // 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(); + if let Ctr::Symbol(tok) = &**arg_car { + binding_for_vtable_get = vars.get(tok.clone()); + if let Some(ref val) = binding_for_vtable_get { + arg_car = &val; } } - // another check to detect if we may have a function call - if let Ctr::Symbol(ref tok) = car { - match cdr.clone() { - Ctr::Seg(ast) => { - if let Some(func) = funcs.borrow().get(tok) { - return func_call(func.clone(), ast.clone(), vars.clone(), funcs.clone()); - } else if !sym_loose { - return Err(format!("Couldnt find definition of {}.", tok)); + // Is ast a function call? + if !call_lazy { + if let Ctr::Symbol(ref tok) = &**arg_car { + match *ast.cdr { + Ctr::Seg(ref ast) => { + if let Some(ref 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.borrow().get(tok) { - return func_call( - func.clone(), - new_ast(Ctr::None, Ctr::None), - vars.clone(), - funcs.clone(), - ); - } else if !sym_loose { - return Err(format!("Couldnt find definition of {}.", tok)); + Ctr::None => { + if let Some(ref 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!")), } - _ => return Err(format!("Arguments to function not a list!")), } } + // iterate over ast and build out ret let mut none = false; while !none { - match car { - // if LIST: call eval inner on it with first_item=true + match &**arg_car { Ctr::Seg(ref inner) => { - match eval(inner.clone(), vars.clone(), funcs.clone(), sym_loose) { - Ok(res) => (*iter).borrow_mut().car = res, + match eval(inner, vars, funcs, sym_loose, call_lazy) { + Ok(res) => 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(); + binding_for_vtable_get = vars.get(tok.clone()); + if let Some(ref val) = binding_for_vtable_get { + car = val.clone(); } else if sym_loose { - (*iter).borrow_mut().car = Ctr::Symbol(tok.to_string()); + car = arg_car.clone() } else { - return Err(format!("Undefined variable: {}", tok)); + return Err(format!("Undefined variable: {}", tok.clone())); } } - // if OTHER: clone and set _ => { - (*iter).borrow_mut().car = car.clone(); + car = arg_car.clone(); } } - match cdr { - // if SYMBOL: unwrap naively, then end + match &**arg_cdr { Ctr::Symbol(ref tok) => { - if let Some(val) = vars.borrow().get(&tok.clone()) { - (*iter).borrow_mut().car = (**val).clone(); + if let Some(val) = vars.get(tok.clone()) { + cdr = val.clone(); } else if sym_loose { - (*iter).borrow_mut().car = Ctr::Symbol(tok.to_string()); + cdr = ast.cdr.clone() } else { - return Err(format!("Undefined variable: {}", tok)); + 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) => { - 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; + Ctr::Seg(ref next) => { + if let Ctr::None = *ret { + *ret = Ctr::Seg(Seg::from(car, cdr.clone())) + } else if let Ctr::Seg(ref mut s) = *ret { + s.append(Box::from(Ctr::Seg(Seg::from(car, cdr.clone())))) + } + arg_car = &next.car; + arg_cdr = &next.cdr } // if OTHER: clone and set, and then end _ => { - (*iter).borrow_mut().cdr = cdr.clone(); + cdr = ast.cdr.clone(); none = true; } } - if let Ctr::None = car { - if let Ctr::None = cdr { + if let Ctr::None = **arg_car { + if let Ctr::None = **arg_cdr { none = true; } } } - return Ok(Ctr::Seg(ret)); + return Ok(ret); } diff --git a/src/lex.rs b/src/lex.rs index 87700c8..337849b 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -use crate::segment::{list_append, Ctr, Seg}; +use crate::segment::{Ctr, Seg}; const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input"; const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input"; @@ -23,7 +23,7 @@ const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input"; /* takes a line of user input * returns an unsimplified tree of tokens. */ -pub fn lex<'a>(document: String) -> Result>, String> { +pub fn lex<'a>(document: &'a String) -> Result, String> { if !document.is_ascii() { return Err("document may only contain ascii characters".to_string()); } @@ -42,7 +42,7 @@ pub fn lex<'a>(document: String) -> Result>, String> { * Returns Ok(Rc) if lexing passes * Returns Err(String) if an error occurs */ -fn process<'a>(document: &'a String) -> Result>, String> { +fn process<'a>(document: &'a String) -> Result, String> { let doc_len = document.len(); if doc_len == 0 { @@ -120,8 +120,7 @@ fn process<'a>(document: &'a String) -> Result>, String> { return Err("list started in middle of another token".to_string()); } - ref_stack.push(Box::new(Seg::new())); - + ref_stack.push(Seg::new()); delim_stack.push(')'); } // begin parsing a string @@ -152,42 +151,47 @@ fn process<'a>(document: &'a String) -> Result>, String> { return Err("Empty token".to_string()); } - let mut current_seg = ref_stack.pop(); - let mut obj; + let mut current_seg = ref_stack.pop().unwrap(); + let obj; if is_str { - obj = Ctr::String(token); + obj = Box::from(Ctr::String(token)); is_str = false; token = String::new(); + current_seg.append(obj); } else if token.len() > 0 { if token == "true" { - obj = Ctr::Bool(true); + obj = Box::from(Ctr::Bool(true)); } else if token == "false" { - obj = Ctr::Bool(false); + obj = Box::from(Ctr::Bool(false)); } else if let Ok(i) = token.parse::() { - obj = Ctr::Integer(i); + obj = Box::from(Ctr::Integer(i)); } else if let Ok(f) = token.parse::() { - obj = Ctr::Float(f); + obj = Box::from(Ctr::Float(f)); } else if let Some(s) = tok_is_symbol(&token) { - obj = Ctr::Symbol(s); + obj = Box::from(Ctr::Symbol(s)); } else { return Err(format!("Unparsable token: {}", token)); } token = String::new(); + current_seg.append(obj.clone()); } - list_append(current_seg, obj); - if alloc_list { // return if we have finished the document if ref_stack.len() == 0 { - return Ok(current_seg); + return Ok(Box::new(current_seg)); } - // shortening this will lead to naught but pain - obj = Ctr::Seg(current_seg.into_raw()); - current_seg = ref_stack.pop(); - list_append(current_seg, obj); + let t = current_seg; + current_seg = ref_stack.pop().unwrap(); + /* TODO: is there a way to do this that doesnt + * involve needlessly copying heap data? I am + * not sure what optimizations rustc performs + * but I assume this should not end up copying + * contained segments around. + */ + current_seg.append(Box::from(Ctr::Seg(t))); } ref_stack.push(current_seg); diff --git a/src/lib.rs b/src/lib.rs index 6a48eef..f2f1862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,35 +15,37 @@ * along with this program. If not, see . */ -#![feature(derive_default_enum)] - -mod append; +/*mod append; mod config; + */ mod eval; mod func; mod lex; mod segment; -mod stl; -mod str; mod vars; +/*mod stl; +mod str;*/ + +extern crate lazy_static; pub mod ast { pub use crate::eval::eval; - pub use crate::func::{ - func_call, func_declare, Args, ExternalOperation, FTable, Function, Operation, - }; pub use crate::lex::lex; pub use crate::segment::{Ctr, Seg, Type}; - pub use crate::vars::{define, VTable}; + pub use crate::sym::{ + SYM_TABLE, SymTable, Symbol, + UserFn, ValueType, Args, + LIB_EXPORT_ENV, LIB_EXPORT_NO_ENV + }; } -pub mod stdlib { +/*pub mod stdlib { pub use crate::append::get_append; pub use crate::stl::get_stdlib; pub use crate::str::{get_concat, get_echo}; pub use crate::vars::get_export; -} +}*/ -pub mod aux { +/*pub mod aux { pub use crate::config::configure; -} +}*/ diff --git a/src/segment.rs b/src/segment.rs index 3535aa0..1b463ae 100644 --- a/src/segment.rs +++ b/src/segment.rs @@ -15,11 +15,12 @@ * along with this program. If not, see . */ use std::fmt; - +use std::marker::PhantomData; +use std::ops::Index; // Container -#[derive(Debug, Clone, Default)] -pub enum Ctr <'a> { +#[derive(Debug, Default)] +pub enum Ctr<'a> { Symbol(String), String(String), Integer(i128), @@ -45,20 +46,31 @@ pub enum Type { /* Segment * Holds two Containers. * Basic building block for more complex data structures. + * I was going to call it Cell and then I learned about + * how important RefCells were in Rust */ -#[derive(Clone, Debug, Default)] -pub struct Seg <'a> { +#[derive(Debug)] +pub struct Seg<'a> { /* "Contents of Address Register" * Historical way of referring to the first value in a cell. */ - pub car: &mut Ctr<'a>, + pub car: Box>, /* "Contents of Decrement Register" * Historical way of referring to the second value in a cell. */ - pub cdr: &mut Ctr<'a>, + pub cdr: Box>, + + /* Stupid hack that makes rust look foolish. + * Needed to determine variance of lifetime. + * How this is an acceptable solution I have + * not a single clue. + */ + _lifetime_variance_determinant: PhantomData<&'a ()> } +static NOTHING: Ctr = Ctr::None; + impl Ctr<'_> { pub fn to_type(&self) -> Type { match self { @@ -74,25 +86,140 @@ impl Ctr<'_> { } -fn seg_to_string(s: &Seg, parens: bool) -> String { - let mut string = String::new(); - match s.car { - Ctr::None => string.push_str(""), - _ => string.push_str(s.car), - } - string.push(' '); - match s.cdr { - Ctr::Seg(inner) => string.push_str(seg_to_string(inner, false)), - Ctr::None => {}, - _ => string.push_str(s.cdr), +impl<'a> Seg<'a> { + /* recurs over tree assumed to be list in standard form + * appends object to end of list + * + * TODO: figure out how not to call CLONE on a CTR via obj arg + * TODO: return result + */ + pub fn append<'b>(&mut self, obj: Box>) { + if let Ctr::None = &*(self.car) { + self.car = obj; + return + } + + if let Ctr::Seg(s) = &mut *(self.cdr) { + s.append(obj); + return + } + + if let Ctr::None = &mut *(self.cdr) { + self.cdr = Box::new(Ctr::Seg(Seg::from_mono(obj))); + // pray for memory lost to the void + } } - if parens { - String::from("(" + string + ")") + /* 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_mono(arg: Box>) -> Seg<'a> { + return Seg{ + car: arg, + cdr: Box::new(Ctr::None), + _lifetime_variance_determinant: PhantomData, + } + } + + pub fn from(car: Box>, cdr: Box>) -> Seg<'a> { + return Seg{ + car, + cdr, + _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, + } } } -impl fmt::Display for Ctr <'_> { +fn seg_to_string(s: &Seg, parens: bool) -> String { + let mut string = String::new(); + if parens { string.push('('); } + match *(s.car) { + Ctr::None => string.push_str(""), + _ => string.push_str(&s.car.to_string()), + } + string.push(' '); + match &*(s.cdr) { + Ctr::Seg(inner) => string.push_str(&seg_to_string(&inner, false)), + Ctr::None => {string.pop();}, + _ => string.push_str(&s.cdr.to_string()), + } + if parens { string.push(')'); } + + string +} + +impl<'a> Clone for Seg<'a> { + fn clone(&self) -> Seg<'a> { + return Seg{ + car: self.car.clone(), + cdr: self.cdr.clone(), + _lifetime_variance_determinant: PhantomData, + } + } +} + +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 { + Ctr::Symbol(s) => Ctr::Symbol(s.clone()), + Ctr::String(s) => Ctr::String(s.clone()), + Ctr::Integer(s) => Ctr::Integer(s.clone()), + Ctr::Float(s) => Ctr::Float(s.clone()), + Ctr::Bool(s) => Ctr::Bool(s.clone()), + Ctr::Seg(s) => Ctr::Seg(s.clone()), + Ctr::None => Ctr::None, + } + } +} + +impl fmt::Display for Ctr<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Ctr::Symbol(s) => write!(f, "{}", s), @@ -100,14 +227,14 @@ impl fmt::Display for Ctr <'_> { Ctr::Integer(s) => write!(f, "{}", s), Ctr::Float(s) => write!(f, "{}", s), Ctr::Bool(s) => { - if s { + if *s { write!(f, "T") } else { write!(f, "F") } }, Ctr::Seg(s) => write!(f, "{}", s), - Ctr::None => Ok(), + Ctr::None => Ok(()), } } } @@ -118,16 +245,6 @@ impl fmt::Display for Seg<'_> { } } -impl Iterator for Seg<'_> { - fn next(&self) -> Option<&Seg> { - if let Ctr::Seg(s) = self.cdr { - Ok(s) - } else { - None() - } - } -} - impl Type { pub fn to_string(&self) -> String { let ret: &str; @@ -144,67 +261,3 @@ impl Type { ret.to_owned() } } - -/* 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(list: &Seg, func: &mut F) -> bool { - if func(&list.car) { - match list.cdr { - Ctr::None => true, - Ctr::Seg(l) => circuit(l, func), - _ => false, - } - } else { - false - } -} - -/* recurs over ast assumed to be list in standard form - * returns length - */ -pub fn list_len(list: &Seg) -> u128 { - let mut len = 0; - circuit(list, &circuit(&mut |c: &Ctr| -> bool { len += 1; true })) -} - -/* recurs over tree assumed to be list in standard form - * returns clone of ctr at index provided - * - * TODO: return result (or option?) - */ -pub fn list_idx<'a>(list: &Seg, idx: u128) -> Ctr<'a> { - if idx > 0 { - if let Ctr::Seg(s) = list.car { - list_idx(s, idx - 1) - } else if idx == 1 { - list.cdr - } else { - Ctr::None - } - } else { - list.car - } -} - -/* recurs over tree assumed to be list in standard form - * appends object to end of list - * - * TODO: return result - */ -pub fn list_append<'a>(list: &Seg, obj: Ctr) { - if let Ctr::None = list.car { - list.car = obj; - return - } - - if let Ctr::Seg(s) = list.cdr { - list_append(s, obj) - } - - if let Ctr::None = list.cdr { - list.cdr = Ctr::Seg(&Seg{car:obj, cdr:Ctr::None}) - } -} diff --git a/sym.rs b/sym.rs new file mode 100644 index 0000000..fc6a442 --- /dev/null +++ b/sym.rs @@ -0,0 +1,294 @@ +/* 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::{VAR_TABLE, VTable, LIB_EXPORT}; +use std::collections::HashMap; +use std::convert::TryInto; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref SYM_TABLE: SymTable<'static> = { + let mut tab = SymTable::new(); + tab.declare(LIB_EXPORT); + tab + }; +} + + +pub struct SymTable<'a> (HashMap); + +#[derive(Debug)] +pub struct UserFn<'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 symbol may either be a pointer to a function + * or a syntax tree to eval with the arguments or + * a simple variable declaration (which can also + * be a macro) + */ +pub enum ValueType<'a> { + Internal(Box Ctr<'a>>), + UserFn(ExternalOperation<'a>), + UserVar(Box>), +} + + +/* 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), +} + +impl Args { + fn validate_inputs(&self, args: &Seg) -> Result<(), String> { + match self { + Args::Lazy(ref num) => { + let called_arg_count = args.len() as i128; + if *num == 0 { + if let Ctr::None = *args.car { + //pass + } else { + return Err("Expected 0 args. Got one or more.".to_string()); + } + } else if *num > -1 && (*num != called_arg_count) { + return Err(format!( + "Expected {} args. Got {}.", + num, called_arg_count + )); + } + } + + Args::Strict(ref arg_types) => { + let mut idx: usize = 0; + let passes = 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 few arguments", + arg_types.len() - (idx + 1) + )); + } + + if !passes { + if idx < (arg_types.len() - 1) { + return Err(format!( + "argument {} is of wrong type (expected {})", + idx + 1, + arg_types[idx].to_string() + )); + } + if idx == (arg_types.len() - 1) { + return Err("too many arguments".to_string()); + } + } + } + } + + Ok(()) + } +} + + +pub struct Symbol { + pub value: ValueType, + pub name: String, + + // has no meaning for UserVar values + pub args: Args, + + // dont fail on undefined symbol (passed to eval) + // has no meaning for UserVar values + pub loose_syms: bool, + + // dont evaluate args at all. leave that to the function + // has no meaning for UserVar values + pub eval_lazy: bool, +} + +impl<'a, 'b> Symbol { + /* call + * routine is called by eval when a function call is detected + */ + pub fn func_call( + &self, + args: &'b Seg<'b>, + ) -> Result>, String> { + // put args in simplest desired form + let evaluated_args; + match eval(args, self.loose_syms, self.eval_lazy) { + 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 + )) + } + } + + if let UserVar(_s) = self { + return evaluated_args; + } + + if let Err(msg) = self.args.validate_inputs(evaluated_args) { + return Err(format!("failure to call {}: {}", self.name, msg)); + } + + /* 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))), + Operation::External(ref f) => { + let mut holding_table = VTable::new(); + + // Prep var table for function execution + for n in 0..f.arg_syms.len() { + let iter_arg = evaluated_args[n]; + if let Some(val) = VAR_TABLE.get(f.arg_syms[n]) { + holding_table.insert(f.arg_syms[n].clone(), val); + } + + VAR_TABLE.insert(f.arg_syms[n].clone(), Box::new(iter_arg)); + } + + // execute function + let mut result: Box; + let iterate = &*(f.ast); + loop { + if let Ctr::Seg(ref data) = *iterate.car { + match eval(data, self.loose_syms, true) { + 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!"), + } + } + + // clear local vars and restore previous values + for n in 0..f.arg_syms.len() { + VAR_TABLE.remove(f.arg_syms[n]); + if let Some(val) = holding_table.get(f.arg_syms[n]) { + VAR_TABLE.insert(f.arg_syms[n].clone(), val); + } + } + + return Ok(result); + } + } + } +} + + +// the stdlib var export function with env_sync on +lazy_static! { + pub static ref LIB_EXPORT_ENV: Function = Function { + name: String::from("export"), + loose_syms: true, + eval_lazy: true, + args: Args::Lazy(2), + function: Operation::Internal(Box::new( move |ast: &Seg| -> Ctr { + _export_callback(ast, true) + }, + )), + }; +} + +// the stdlib var export function with env_sync off +lazy_static! { + pub static ref LIB_EXPORT_NO_ENV: Function = Function { + name: String::from("export"), + loose_syms: true, + eval_lazy: true, + args: Args::Lazy(2), + function: Operation::Internal(Box::new( move |ast: &Seg| -> Ctr { + _export_callback(ast, false) + }, + )), + }; +} + + +fn _export_callback<'a> (ast: &Seg, env_cfg: bool) -> Ctr<'a> { + if let Ctr::Symbol(ref identifier) = *ast.car { + match &*ast.cdr { + Ctr::Seg(data_tree) => match eval(&Box::new(data_tree), false, true) { + Ok(seg) => match *seg { + Ctr::Seg(val) => { + SYM_TABLE.declare(Symbol { + value: UserVar(val.car), + name: identifier.clone(), + }); + if env_cfg { + env::set_var(identifier.clone(), val.car.to_string()) + } + }, + _ => eprintln!("impossible args to export"), + }, + Err(e) => eprintln!("couldnt eval symbol: {}", e), + }, + Ctr::None => { + VAR_TABLE.remove(identifier.to_string()); + if env_cfg { + env::remove_var(identifier.to_string()); + } + }, + _ => eprintln!("args not in standard form"), + } + } else { + eprintln!("first argument to export must be a symbol"); + } + return Ctr::None; +} diff --git a/tests/test_lex.rs b/tests/test_lex.rs index ff3d946..3469b4a 100644 --- a/tests/test_lex.rs +++ b/tests/test_lex.rs @@ -3,10 +3,10 @@ mod lex_tests { #[test] fn test_lex_basic_pair() { - let document: &str = "(hello 'world')"; - match lex(document) { + let document = String::from("(hello 'world')"); + match lex(&document) { Ok(tree) => { - assert_eq!(tree, document); + assert_eq!(tree.to_string(), document); } Err(s) => { print!("{}\n", s); @@ -17,10 +17,10 @@ mod lex_tests { #[test] fn test_lex_basic_list() { - let document: &str = "(hello 'world' 1 2 3)"; - match lex(document) { + let document = String::from("(hello 'world' 1 2 3)"); + match lex(&document) { Ok(tree) => { - assert_eq!(tree, document); + assert_eq!(tree.to_string(), document); } Err(s) => { print!("{}\n", s); @@ -31,10 +31,10 @@ mod lex_tests { #[test] fn test_lex_complex_list() { - let document: &str = "(hello 'world' (1 2 (1 2 3)) 1 2 3)"; - match lex(document) { + let document = String::from("(hello 'world' (1 2 (1 2 3)) 1 2 3)"); + match lex(&document) { Ok(tree) => { - assert_eq!(tree, document); + assert_eq!(tree.to_string(), document); } Err(s) => { print!("{}\n", s); @@ -45,11 +45,11 @@ mod lex_tests { #[test] fn test_bad_symbol() { - let document: &str = "(as;dd)"; + let document = String::from("(as;dd)"); let output: &str = "Problem lexing document: \"Unparsable token: as;dd\""; - match lex(document) { + match lex(&document) { Ok(tree) => { - print!("Bad token yielded: {}\n", tree); + print!("Bad token yielded: {}\n", tree.to_string()); assert!(false); } Err(s) => { @@ -60,10 +60,10 @@ mod lex_tests { #[test] fn test_list_delim_in_str() { - let document: &str = "('(')"; - match lex(document) { + let document = String::from("('(')"); + match lex(&document) { Ok(tree) => { - assert_eq!(tree, document); + assert_eq!(tree.to_string(), document); } Err(s) => { print!("{}\n", s); @@ -74,10 +74,10 @@ mod lex_tests { #[test] fn test_empty_string() { - let document: &str = "('')"; - match lex(document) { + let document = String::from("('')"); + match lex(&document) { Ok(tree) => { - assert_eq!(tree, document); + assert_eq!(tree.to_string(), document); } Err(s) => { print!("{}\n", s); @@ -88,11 +88,11 @@ mod lex_tests { #[test] fn test_unmatched_list_delim_flat() { - let document: &str = "(one two"; + let document = String::from("(one two"); let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; - match lex(document) { + match lex(&document) { Ok(tree) => { - print!("Bad token yielded: {}\n", tree); + print!("Bad token yielded: {}\n", tree.to_string()); assert!(false); } Err(s) => { @@ -103,9 +103,9 @@ mod lex_tests { #[test] fn test_unmatched_list_delim_complex() { - let document: &str = "(one two (three)"; + let document = String::from("(one two (three)"); let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\""; - match lex(document) { + match lex(&document) { Ok(tree) => { print!("Bad token yielded: {}\n", tree); assert!(false); @@ -118,11 +118,11 @@ mod lex_tests { #[test] fn test_comment() { - let document: &str = "#!/bin/relish\n(one two)"; + let document = String::from("#!/bin/relish\n(one two)"); let output: &str = "(one two)"; - match lex(document) { + match lex(&document) { Ok(tree) => { - assert_eq!(tree, output); + assert_eq!(tree.to_string(), output); } Err(s) => { print!("{}\n", s); @@ -133,11 +133,11 @@ mod lex_tests { #[test] fn test_postline_comment() { - let document: &str = "#!/bin/relish\n((one two)# another doc comment\n(three four))"; + let document = String::from("#!/bin/relish\n((one two)# another doc comment\n(three four))"); let output: &str = "((one two) (three four))"; - match lex(document) { + match lex(&document) { Ok(tree) => { - assert_eq!(tree, output.to_string()); + assert_eq!(tree.to_string(), output.to_string()); } Err(s) => { print!("{}\n", s); @@ -148,11 +148,11 @@ mod lex_tests { #[test] fn test_inline_comment() { - let document: &str = "#!/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)"; - match lex(document) { + match lex(&document) { Ok(tree) => { - assert_eq!(tree, output); + assert_eq!(tree.to_string(), output.to_string()); } Err(s) => { print!("{}\n", s); @@ -163,9 +163,9 @@ mod lex_tests { #[test] fn test_bad_token_list() { - let document: &str = "(one t(wo)"; + let document = String::from("(one t(wo)"); let output: &str = "Problem lexing document: \"list started in middle of another token\""; - match lex(document) { + match lex(&document) { Ok(tree) => { print!("Bad token yielded: {}\n", tree); assert!(false); diff --git a/the_rewrite/tests/test_vars.rs b/tests/test_vars.rs similarity index 100% rename from the_rewrite/tests/test_vars.rs rename to tests/test_vars.rs diff --git a/the_rewrite/Cargo.toml b/the_rewrite/Cargo.toml deleted file mode 100644 index 7214a92..0000000 --- a/the_rewrite/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "relish" -version = "0.1.0" -authors = ["Aidan "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -dirs = "3.0" -rustyline = "8.2.0" \ No newline at end of file diff --git a/the_rewrite/src/segment.rs b/the_rewrite/src/segment.rs deleted file mode 100644 index 1b463ae..0000000 --- a/the_rewrite/src/segment.rs +++ /dev/null @@ -1,263 +0,0 @@ -/* 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 std::fmt; -use std::marker::PhantomData; -use std::ops::Index; - -// Container -#[derive(Debug, Default)] -pub enum Ctr<'a> { - Symbol(String), - String(String), - Integer(i128), - Float(f64), - Bool(bool), - Seg(Seg<'a>), - #[default] - None, -} - -// Type of Container -#[derive(PartialEq, Clone)] -pub enum Type { - Symbol, - String, - Integer, - Float, - Bool, - Seg, - None, -} - -/* Segment - * Holds two Containers. - * Basic building block for more complex data structures. - * I was going to call it Cell and then I learned about - * how important RefCells were in Rust - */ -#[derive(Debug)] -pub struct Seg<'a> { - /* "Contents of Address Register" - * Historical way of referring to the first value in a cell. - */ - pub car: Box>, - - /* "Contents of Decrement Register" - * Historical way of referring to the second value in a cell. - */ - pub cdr: Box>, - - /* Stupid hack that makes rust look foolish. - * Needed to determine variance of lifetime. - * How this is an acceptable solution I have - * not a single clue. - */ - _lifetime_variance_determinant: PhantomData<&'a ()> -} - -static NOTHING: Ctr = Ctr::None; - -impl Ctr<'_> { - pub fn to_type(&self) -> Type { - match self { - Ctr::Symbol(_s) => Type::Symbol, - Ctr::String(_s) => Type::String, - Ctr::Integer(_s) => Type::Integer, - Ctr::Float(_s) => Type::Float, - Ctr::Bool(_s) => Type::Bool, - Ctr::Seg(_s) => Type::Seg, - Ctr::None => Type::None, - } - } - -} - -impl<'a> Seg<'a> { - /* recurs over tree assumed to be list in standard form - * appends object to end of list - * - * TODO: figure out how not to call CLONE on a CTR via obj arg - * TODO: return result - */ - pub fn append<'b>(&mut self, obj: Box>) { - if let Ctr::None = &*(self.car) { - self.car = obj; - return - } - - if let Ctr::Seg(s) = &mut *(self.cdr) { - s.append(obj); - return - } - - if let Ctr::None = &mut *(self.cdr) { - self.cdr = Box::new(Ctr::Seg(Seg::from_mono(obj))); - // 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_mono(arg: Box>) -> Seg<'a> { - return Seg{ - car: arg, - cdr: Box::new(Ctr::None), - _lifetime_variance_determinant: PhantomData, - } - } - - pub fn from(car: Box>, cdr: Box>) -> Seg<'a> { - return Seg{ - car, - cdr, - _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 { - let mut string = String::new(); - if parens { string.push('('); } - match *(s.car) { - Ctr::None => string.push_str(""), - _ => string.push_str(&s.car.to_string()), - } - string.push(' '); - match &*(s.cdr) { - Ctr::Seg(inner) => string.push_str(&seg_to_string(&inner, false)), - Ctr::None => {string.pop();}, - _ => string.push_str(&s.cdr.to_string()), - } - if parens { string.push(')'); } - - string -} - -impl<'a> Clone for Seg<'a> { - fn clone(&self) -> Seg<'a> { - return Seg{ - car: self.car.clone(), - cdr: self.cdr.clone(), - _lifetime_variance_determinant: PhantomData, - } - } -} - -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 { - Ctr::Symbol(s) => Ctr::Symbol(s.clone()), - Ctr::String(s) => Ctr::String(s.clone()), - Ctr::Integer(s) => Ctr::Integer(s.clone()), - Ctr::Float(s) => Ctr::Float(s.clone()), - Ctr::Bool(s) => Ctr::Bool(s.clone()), - Ctr::Seg(s) => Ctr::Seg(s.clone()), - Ctr::None => Ctr::None, - } - } -} - -impl fmt::Display for Ctr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Ctr::Symbol(s) => write!(f, "{}", s), - Ctr::String(s) => write!(f, "\'{}\'", s), - Ctr::Integer(s) => write!(f, "{}", s), - Ctr::Float(s) => write!(f, "{}", s), - Ctr::Bool(s) => { - if *s { - write!(f, "T") - } else { - write!(f, "F") - } - }, - Ctr::Seg(s) => write!(f, "{}", s), - Ctr::None => Ok(()), - } - } -} - -impl fmt::Display for Seg<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", seg_to_string(self, true)) - } -} - -impl Type { - pub fn to_string(&self) -> String { - let ret: &str; - match self { - Type::Symbol => ret = "symbol", - Type::String => ret = "string", - Type::Integer => ret = "integer", - Type::Float => ret = "float", - Type::Bool => ret = "bool", - Type::Seg => ret = "segment", - Type::None => ret = "none", - } - - ret.to_owned() - } -}