WIP commit:

* Fix up project structures
* combine vars and funcs table
* make a place for old code that may be useful to reference
* singleton pattern for sym table

Commentary:
When this change is finally finished I promise to use feature branches
from here on out
This commit is contained in:
Ava Hahn 2023-02-15 23:27:00 -08:00
parent b680e3ca9a
commit ca4c557d95
Signed by untrusted user who does not match committer: affine
GPG key ID: 3A4645B8CF806069
32 changed files with 1092 additions and 616 deletions

View file

@ -8,4 +8,5 @@ edition = "2018"
[dependencies] [dependencies]
dirs = "3.0" dirs = "3.0"
lazy_static = "1.4.0"
rustyline = "8.2.0" rustyline = "8.2.0"

View file

@ -119,15 +119,16 @@ Note: this section will not show the status of each item unless you are viewing
**** DONE Derive Default for Seg **** DONE Derive Default for Seg
**** WONTDO From/Into for Ctr **** WONTDO From/Into for Ctr
**** DONE Iterator for Seg **** 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 one of these may fix borrow checker insanity
all you have to do is actually learn this shit **** DONE make FTable and VTable singleton modules (global mutable)
just pay attention **** WONTDO make FTable and VTable into private definitions
**** DONE YEET AST EVERYWHERE. PASS AROUND A SEG REF. **** DONE YEET AST EVERYWHERE. PASS AROUND A SEG REF.
*** TODO refactor code into more digestible, better organized pieces *** TODO refactor code into more digestible, better organized pieces
Just need to get core compiling at this point. **** DONE combine var and func table into global sym table
Refactors made include splitting func_call into reasonable segments **** TODO code deduplication and cleanup in sym table
Reorganizing eval **** TODO refactor and reimplement eval
**** TODO (test and get it working)
*** TODO Rudimentary Control Flow *** TODO Rudimentary Control Flow
**** TODO if clause **** TODO if clause
**** TODO loop clause **** TODO loop clause
@ -175,5 +176,6 @@ Will need a concatenate function for func tables
***** TODO HTTP Listener ***** TODO HTTP Listener
***** TODO UDP Listener ***** TODO UDP Listener
*** TODO Ensure full test coverage *** TODO Ensure full test coverage
**** TODO Test for VTable overwrites in func_call
THE GOOD STUFF IS IN Repositories/temprl **** TODO Implement missing VTable, FTable, Lex, Eval tests
**** TODO stdlib tests :)

View file

@ -72,7 +72,7 @@ pub fn eval<'a, 'b>(
} }
Ctr::None => { Ctr::None => {
if let Some(ref func) = funcs.get(tok.clone()) { 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 { } else if !sym_loose {
return Err(format!("Couldnt find definition of {}.", tok.clone())); return Err(format!("Couldnt find definition of {}.", tok.clone()));
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<RefCell<VTable>>,
funcs: Rc<RefCell<FTable>>,
sym_loose: bool,
) -> Result<Ctr, String> {
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));
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::segment::{Ctr, Seg}; use crate::segment::{list_append, Ctr, Seg};
const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input"; const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input";
const UNMATCHED_LIST_DELIM: &str = "Unmatched list 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 /* takes a line of user input
* returns an unsimplified tree of tokens. * returns an unsimplified tree of tokens.
*/ */
pub fn lex<'a>(document: &'a String) -> Result<Box<Seg>, String> { pub fn lex<'a>(document: String) -> Result<Box<Seg<'a>>, String> {
if !document.is_ascii() { if !document.is_ascii() {
return Err("document may only contain ascii characters".to_string()); return Err("document may only contain ascii characters".to_string());
} }
@ -42,7 +42,7 @@ pub fn lex<'a>(document: &'a String) -> Result<Box<Seg>, String> {
* Returns Ok(Rc<Seg>) if lexing passes * Returns Ok(Rc<Seg>) if lexing passes
* Returns Err(String) if an error occurs * Returns Err(String) if an error occurs
*/ */
fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> { fn process<'a>(document: &'a String) -> Result<Box<Seg<'a>>, String> {
let doc_len = document.len(); let doc_len = document.len();
if doc_len == 0 { if doc_len == 0 {
@ -120,7 +120,8 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
return Err("list started in middle of another token".to_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(')'); delim_stack.push(')');
} }
// begin parsing a string // begin parsing a string
@ -151,47 +152,42 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
return Err("Empty token".to_string()); return Err("Empty token".to_string());
} }
let mut current_seg = ref_stack.pop().unwrap(); let mut current_seg = ref_stack.pop();
let obj; let mut obj;
if is_str { if is_str {
obj = Box::from(Ctr::String(token)); obj = Ctr::String(token);
is_str = false; is_str = false;
token = String::new(); token = String::new();
current_seg.append(obj);
} else if token.len() > 0 { } else if token.len() > 0 {
if token == "true" { if token == "true" {
obj = Box::from(Ctr::Bool(true)); obj = Ctr::Bool(true);
} else if token == "false" { } else if token == "false" {
obj = Box::from(Ctr::Bool(false)); obj = Ctr::Bool(false);
} else if let Ok(i) = token.parse::<i128>() { } else if let Ok(i) = token.parse::<i128>() {
obj = Box::from(Ctr::Integer(i)); obj = Ctr::Integer(i);
} else if let Ok(f) = token.parse::<f64>() { } else if let Ok(f) = token.parse::<f64>() {
obj = Box::from(Ctr::Float(f)); obj = Ctr::Float(f);
} else if let Some(s) = tok_is_symbol(&token) { } else if let Some(s) = tok_is_symbol(&token) {
obj = Box::from(Ctr::Symbol(s)); obj = Ctr::Symbol(s);
} else { } else {
return Err(format!("Unparsable token: {}", token)); return Err(format!("Unparsable token: {}", token));
} }
token = String::new(); token = String::new();
current_seg.append(obj.clone());
} }
list_append(current_seg, obj);
if alloc_list { if alloc_list {
// return if we have finished the document // return if we have finished the document
if ref_stack.len() == 0 { if ref_stack.len() == 0 {
return Ok(Box::new(current_seg)); return Ok(current_seg);
} }
let t = current_seg; // shortening this will lead to naught but pain
current_seg = ref_stack.pop().unwrap(); obj = Ctr::Seg(current_seg.into_raw());
/* TODO: is there a way to do this that doesnt current_seg = ref_stack.pop();
* involve needlessly copying heap data? I am list_append(current_seg, obj);
* 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); ref_stack.push(current_seg);

View file

@ -15,32 +15,35 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*mod append; #![feature(derive_default_enum)]
mod append;
mod config; mod config;
*/
mod eval; mod eval;
mod func; mod func;
mod lex; mod lex;
mod segment; mod segment;
mod stl;
mod str;
mod vars; mod vars;
/*mod stl;
mod str;*/
pub mod ast { pub mod ast {
pub use crate::eval::eval; 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::lex::lex;
pub use crate::segment::{Ctr, Seg, Type}; 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::append::get_append;
pub use crate::stl::get_stdlib; pub use crate::stl::get_stdlib;
pub use crate::str::{get_concat, get_echo}; pub use crate::str::{get_concat, get_echo};
pub use crate::vars::get_export; pub use crate::vars::get_export;
}*/ }
/*pub mod aux { pub mod aux {
pub use crate::config::configure; pub use crate::config::configure;
}*/ }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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("<nil>"),
_ => 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<F: Fn(&Ctr)>(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})
}
}

View file

@ -3,10 +3,10 @@ mod lex_tests {
#[test] #[test]
fn test_lex_basic_pair() { fn test_lex_basic_pair() {
let document = String::from("(hello 'world')"); let document: &str = "(hello 'world')";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), document); assert_eq!(tree, document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -17,10 +17,10 @@ mod lex_tests {
#[test] #[test]
fn test_lex_basic_list() { fn test_lex_basic_list() {
let document = String::from("(hello 'world' 1 2 3)"); let document: &str = "(hello 'world' 1 2 3)";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), document); assert_eq!(tree, document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -31,10 +31,10 @@ mod lex_tests {
#[test] #[test]
fn test_lex_complex_list() { fn test_lex_complex_list() {
let document = String::from("(hello 'world' (1 2 (1 2 3)) 1 2 3)"); let document: &str = "(hello 'world' (1 2 (1 2 3)) 1 2 3)";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), document); assert_eq!(tree, document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -45,11 +45,11 @@ mod lex_tests {
#[test] #[test]
fn test_bad_symbol() { 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\""; let output: &str = "Problem lexing document: \"Unparsable token: as;dd\"";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree.to_string()); print!("Bad token yielded: {}\n", tree);
assert!(false); assert!(false);
} }
Err(s) => { Err(s) => {
@ -60,10 +60,10 @@ mod lex_tests {
#[test] #[test]
fn test_list_delim_in_str() { fn test_list_delim_in_str() {
let document = String::from("('(')"); let document: &str = "('(')";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), document); assert_eq!(tree, document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -74,10 +74,10 @@ mod lex_tests {
#[test] #[test]
fn test_empty_string() { fn test_empty_string() {
let document = String::from("('')"); let document: &str = "('')";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), document); assert_eq!(tree, document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -88,11 +88,11 @@ mod lex_tests {
#[test] #[test]
fn test_unmatched_list_delim_flat() { 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\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree.to_string()); print!("Bad token yielded: {}\n", tree);
assert!(false); assert!(false);
} }
Err(s) => { Err(s) => {
@ -103,9 +103,9 @@ mod lex_tests {
#[test] #[test]
fn test_unmatched_list_delim_complex() { 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\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree); print!("Bad token yielded: {}\n", tree);
assert!(false); assert!(false);
@ -118,11 +118,11 @@ mod lex_tests {
#[test] #[test]
fn test_comment() { 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)"; let output: &str = "(one two)";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), output); assert_eq!(tree, output);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -133,11 +133,11 @@ mod lex_tests {
#[test] #[test]
fn test_postline_comment() { 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))"; let output: &str = "((one two) (three four))";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), output.to_string()); assert_eq!(tree, output.to_string());
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -148,11 +148,11 @@ mod lex_tests {
#[test] #[test]
fn test_inline_comment() { fn test_inline_comment() {
let document = String::from("#!/bin/relish\n((one two)\n# another doc comment\nthree)"); let document: &str = "#!/bin/relish\n((one two)\n# another doc comment\nthree)";
let output: &str = "((one two) three)"; let output: &str = "((one two) three)";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree.to_string(), output.to_string()); assert_eq!(tree, output);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -163,9 +163,9 @@ mod lex_tests {
#[test] #[test]
fn test_bad_token_list() { 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\""; let output: &str = "Problem lexing document: \"list started in middle of another token\"";
match lex(&document) { match lex(document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree); print!("Bad token yielded: {}\n", tree);
assert!(false); assert!(false);

View file

@ -17,14 +17,24 @@
use crate::eval::eval; use crate::eval::eval;
use crate::segment::{Seg, Ctr, Type}; use crate::segment::{Seg, Ctr, Type};
use crate::vars::VTable; use crate::vars::{VAR_TABLE, VTable, LIB_EXPORT};
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use lazy_static::lazy_static;
pub struct FTable<'a>(HashMap<String, Box<Function<'a>>>); 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<Function<'a>>) -> Option<String> { pub struct FTable<'a> (HashMap<String, &'a Function>);
impl FTable<'_> {
pub fn declare(&mut self, func: Box<Function>) -> Option<String> {
if let Operation::External(ref fun) = &func.function { if let Operation::External(ref fun) = &func.function {
if let Args::Lazy(ref i) = func.args { if let Args::Lazy(ref i) = func.args {
if fun.arg_syms.len() != i.clone().try_into().unwrap() { if fun.arg_syms.len() != i.clone().try_into().unwrap() {
@ -42,7 +52,7 @@ impl<'a> FTable<'a> {
None None
} }
pub fn get(&self, id: String) -> Option<&Box<Function<'a>>> { pub fn get(&self, id: String) -> Option<&Box<Function>> {
self.0.get(&id) self.0.get(&id)
} }
@ -50,7 +60,7 @@ impl<'a> FTable<'a> {
self.0.remove(&id); self.0.remove(&id);
} }
pub fn new() -> FTable<'a> { pub fn new() -> FTable<'static> {
FTable{0: HashMap::<String, Box<Function>>::new()} FTable{0: HashMap::<String, Box<Function>>::new()}
} }
} }
@ -74,7 +84,7 @@ pub struct ExternalOperation<'a> {
* or a syntax tree to eval with the arguments * or a syntax tree to eval with the arguments
*/ */
pub enum Operation<'a> { pub enum Operation<'a> {
Internal(Box<dyn Fn(&'a Seg, &'a mut VTable, &'a mut FTable) -> Ctr<'a>>), Internal(Box<dyn Fn(&'a Seg) -> Ctr<'a>>),
External(ExternalOperation<'a>), External(ExternalOperation<'a>),
} }
@ -113,11 +123,9 @@ impl Args {
if idx >= arg_types.len() { if idx >= arg_types.len() {
return false; return false;
} }
if let Ctr::None = c { if let Ctr::None = c {
return false; return false;
} }
if arg_types[idx] == c.to_type() { if arg_types[idx] == c.to_type() {
idx += 1; idx += 1;
return true; return true;
@ -140,7 +148,6 @@ impl Args {
arg_types[idx].to_string() arg_types[idx].to_string()
)); ));
} }
if idx == (arg_types.len() - 1) { if idx == (arg_types.len() - 1) {
return Err("too many arguments".to_string()); return Err("too many arguments".to_string());
} }
@ -152,8 +159,8 @@ impl Args {
} }
} }
pub struct Function<'a> { pub struct Function {
pub function: Operation<'a>, pub function: Operation,
pub name: String, pub name: String,
pub args: Args, pub args: Args,
@ -164,19 +171,17 @@ pub struct Function<'a> {
pub eval_lazy: bool, pub eval_lazy: bool,
} }
impl<'a, 'b, 'c> Function<'a> { impl<'b, 'c> Function {
/* call /* call
* routine is called by eval when a function call is detected * routine is called by eval when a function call is detected
*/ */
pub fn func_call( pub fn func_call(
&self, &self,
args: &'b Seg<'b>, args: &'b Seg<'b>,
vars: &'a mut VTable<'a>,
funcs: &'a mut FTable<'a>,
) -> Result<Box<Ctr<'c>>, String> { ) -> Result<Box<Ctr<'c>>, String> {
// put args in simplest desired form // put args in simplest desired form
let evaluated_args; 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) => { Ok(arg_data) => {
if let Ctr::Seg(ast) = *arg_data { if let Ctr::Seg(ast) = *arg_data {
evaluated_args = &ast; evaluated_args = &ast;
@ -201,18 +206,26 @@ impl<'a, 'b, 'c> Function<'a> {
* result of the final body is returned. * result of the final body is returned.
*/ */
match &self.function { 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) => { Operation::External(ref f) => {
let mut holding_table = VTable::new();
// Prep var table for function execution
for n in 0..f.arg_syms.len() { for n in 0..f.arg_syms.len() {
let iter_arg = evaluated_args[n]; 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<Ctr>; let mut result: Box<Ctr>;
let iterate = &*(f.ast); let iterate = &*(f.ast);
loop { loop {
if let Ctr::Seg(ref data) = *iterate.car { 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, Ok(ctr) => result = ctr,
Err(e) => return Err(e), Err(e) => return Err(e),
} }
@ -226,8 +239,13 @@ impl<'a, 'b, 'c> Function<'a> {
_ => panic!("function body not in standard form!"), _ => panic!("function body not in standard form!"),
} }
} }
// clear local vars and restore previous values
for n in 0..f.arg_syms.len() { 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); return Ok(result);

View file

@ -16,10 +16,18 @@
*/ */
use crate::eval::eval; use crate::eval::eval;
use crate::func::{Args, FTable, Function, Operation}; use crate::func::{Args, Function, Operation};
use crate::segment::{Seg, Ctr}; use crate::segment::{Seg, Ctr};
use std::collections::HashMap; use std::collections::HashMap;
use std::env; 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 /* Mapping between a string token and a tree of Segments
* The string token can be found in any Ctr::Symbol value * The string token can be found in any Ctr::Symbol value
* it is expected that the trees stored are already evaluated * 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 // the stdlib var export function with env_sync on
pub fn get_export<'a>(env_cfg: bool) -> Function<'a>{ lazy_static! {
return Function { pub static ref LIB_EXPORT_ENV: Function = Function {
name: String::from("export"), name: String::from("export"),
loose_syms: true, loose_syms: true,
eval_lazy: true, eval_lazy: true,
args: Args::Lazy(2), args: Args::Lazy(2),
function: Operation::Internal(Box::new( function: Operation::Internal(Box::new( move |ast: &Seg| -> Ctr {
move |ast: &Seg, vars: &mut VTable, funcs: &mut FTable| -> Ctr { _export_callback(ast, true)
_export_callback(ast, vars, funcs, env_cfg) },
)),
};
}
// 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 { if let Ctr::Symbol(ref identifier) = *ast.car {
match &*ast.cdr { 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 { Ok(seg) => match *seg {
Ctr::Seg(val) => { Ctr::Seg(val) => {
vars.insert(identifier.clone(), val.car); SYM_TABLE.declare(Symbol {
value: UserVar(val.car),
name: identifier.clone(),
});
if env_cfg { if env_cfg {
env::set_var(identifier.clone(), val.car.to_string()) 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), Err(e) => eprintln!("couldnt eval symbol: {}", e),
}, },
Ctr::None => { Ctr::None => {
vars.remove(identifier.to_string()); VAR_TABLE.remove(identifier.to_string());
if env_cfg { if env_cfg {
env::remove_var(identifier.to_string()); env::remove_var(identifier.to_string());
} }

View file

@ -15,130 +15,129 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::func::{func_call, FTable};
use crate::segment::{new_ast, Ast, Ctr}; use crate::segment::{Seg, Ctr};
use crate::vars::VTable; use crate::sym::SYM_TABLE;
use std::cell::RefCell;
use std::rc::Rc;
/* iterates over a syntax tree /* iterates over a syntax tree
* returns a NEW LIST of values * returns a NEW LIST of values
* representing the simplest possible form of the input * representing the simplest possible form of the input
*/ */
pub fn eval( pub fn eval(
ast: Ast, ast: &Seg,
vars: Rc<RefCell<VTable>>,
funcs: Rc<RefCell<FTable>>,
sym_loose: bool, sym_loose: bool,
) -> Result<Ctr, String> { call_lazy: bool,
let mut car = ast.borrow().clone().car; ) -> Result<Box<Ctr>, String> {
let mut cdr = ast.borrow().clone().cdr; // data to return
let ret = new_ast(Ctr::None, Ctr::None); let mut ret = Box::from(Ctr::None);
let mut iter = ret.clone();
// 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 // doing an initial variable check here allows us
// to find functions passed in as variables // to find functions passed in as variables
if let Ctr::Symbol(ref tok) = car { if let Ctr::Symbol(tok) = &**arg_car {
if let Some(val) = vars.borrow().get(tok) { binding_for_vtable_get = vars.get(tok.clone());
car = (**val).clone(); if let Some(ref val) = binding_for_vtable_get {
arg_car = &val;
} }
} }
// another check to detect if we may have a function call // Is ast a function call?
if let Ctr::Symbol(ref tok) = car { if !call_lazy {
match cdr.clone() { if let Ctr::Symbol(ref tok) = &**arg_car {
Ctr::Seg(ast) => { match *ast.cdr {
if let Some(func) = funcs.borrow().get(tok) { Ctr::Seg(ref ast) => {
return func_call(func.clone(), ast.clone(), vars.clone(), funcs.clone()); if let Some(ref func) = funcs.get(tok.clone()) {
return func.func_call(ast, vars, funcs);
} else if !sym_loose { } else if !sym_loose {
return Err(format!("Couldnt find definition of {}.", tok)); return Err(format!("Couldnt find definition of {}.", tok));
} }
} }
Ctr::None => { Ctr::None => {
if let Some(func) = funcs.borrow().get(tok) { if let Some(ref func) = funcs.get(tok.clone()) {
return func_call( return (*func).func_call(&Seg::new(), vars, funcs);
func.clone(),
new_ast(Ctr::None, Ctr::None),
vars.clone(),
funcs.clone(),
);
} else if !sym_loose { } else if !sym_loose {
return Err(format!("Couldnt find definition of {}.", tok)); 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; let mut none = false;
while !none { while !none {
match car { match &**arg_car {
// if LIST: call eval inner on it with first_item=true
Ctr::Seg(ref inner) => { Ctr::Seg(ref inner) => {
match eval(inner.clone(), vars.clone(), funcs.clone(), sym_loose) { match eval(inner, vars, funcs, sym_loose, call_lazy) {
Ok(res) => (*iter).borrow_mut().car = res, Ok(res) => car = res,
Err(e) => return Err(format!("Evaluation error: {}", e)), Err(e) => return Err(format!("Evaluation error: {}", e)),
} }
} }
// if SYMBOL: unwrap naively
Ctr::Symbol(ref tok) => { Ctr::Symbol(ref tok) => {
if let Some(val) = vars.borrow().get(&tok.clone()) { binding_for_vtable_get = vars.get(tok.clone());
(*iter).borrow_mut().car = (**val).clone(); if let Some(ref val) = binding_for_vtable_get {
car = val.clone();
} else if sym_loose { } else if sym_loose {
(*iter).borrow_mut().car = Ctr::Symbol(tok.to_string()); car = arg_car.clone()
} else { } 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 { match &**arg_cdr {
// if SYMBOL: unwrap naively, then end
Ctr::Symbol(ref tok) => { Ctr::Symbol(ref tok) => {
if let Some(val) = vars.borrow().get(&tok.clone()) { if let Some(val) = vars.get(tok.clone()) {
(*iter).borrow_mut().car = (**val).clone(); cdr = val.clone();
} else if sym_loose { } else if sym_loose {
(*iter).borrow_mut().car = Ctr::Symbol(tok.to_string()); cdr = ast.cdr.clone()
} else { } else {
return Err(format!("Undefined variable: {}", tok)); return Err(format!("Undefined variable: {}", tok.clone()));
} }
none = true; none = true;
} }
// if LIST: Ctr::Seg(ref next) => {
// - iter.cdr = new_ast(None, None) if let Ctr::None = *ret {
// - iter = iter.cdr *ret = Ctr::Seg(Seg::from(car, cdr.clone()))
// - car = cdr.car } else if let Ctr::Seg(ref mut s) = *ret {
// - cdr = cdr.cdr s.append(Box::from(Ctr::Seg(Seg::from(car, cdr.clone()))))
// - LOOP }
Ctr::Seg(next) => { arg_car = &next.car;
let n = new_ast(Ctr::None, Ctr::None); arg_cdr = &next.cdr
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 // if OTHER: clone and set, and then end
_ => { _ => {
(*iter).borrow_mut().cdr = cdr.clone(); cdr = ast.cdr.clone();
none = true; none = true;
} }
} }
if let Ctr::None = car { if let Ctr::None = **arg_car {
if let Ctr::None = cdr { if let Ctr::None = **arg_cdr {
none = true; none = true;
} }
} }
} }
return Ok(Ctr::Seg(ret)); return Ok(ret);
} }

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::segment::{list_append, Ctr, Seg}; use crate::segment::{Ctr, Seg};
const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input"; const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input";
const UNMATCHED_LIST_DELIM: &str = "Unmatched list 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 /* takes a line of user input
* returns an unsimplified tree of tokens. * returns an unsimplified tree of tokens.
*/ */
pub fn lex<'a>(document: String) -> Result<Box<Seg<'a>>, String> { pub fn lex<'a>(document: &'a String) -> Result<Box<Seg>, String> {
if !document.is_ascii() { if !document.is_ascii() {
return Err("document may only contain ascii characters".to_string()); return Err("document may only contain ascii characters".to_string());
} }
@ -42,7 +42,7 @@ pub fn lex<'a>(document: String) -> Result<Box<Seg<'a>>, String> {
* Returns Ok(Rc<Seg>) if lexing passes * Returns Ok(Rc<Seg>) if lexing passes
* Returns Err(String) if an error occurs * Returns Err(String) if an error occurs
*/ */
fn process<'a>(document: &'a String) -> Result<Box<Seg<'a>>, String> { fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
let doc_len = document.len(); let doc_len = document.len();
if doc_len == 0 { if doc_len == 0 {
@ -120,8 +120,7 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg<'a>>, String> {
return Err("list started in middle of another token".to_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(')'); delim_stack.push(')');
} }
// begin parsing a string // begin parsing a string
@ -152,42 +151,47 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg<'a>>, String> {
return Err("Empty token".to_string()); return Err("Empty token".to_string());
} }
let mut current_seg = ref_stack.pop(); let mut current_seg = ref_stack.pop().unwrap();
let mut obj; let obj;
if is_str { if is_str {
obj = Ctr::String(token); obj = Box::from(Ctr::String(token));
is_str = false; is_str = false;
token = String::new(); token = String::new();
current_seg.append(obj);
} else if token.len() > 0 { } else if token.len() > 0 {
if token == "true" { if token == "true" {
obj = Ctr::Bool(true); obj = Box::from(Ctr::Bool(true));
} else if token == "false" { } else if token == "false" {
obj = Ctr::Bool(false); obj = Box::from(Ctr::Bool(false));
} else if let Ok(i) = token.parse::<i128>() { } else if let Ok(i) = token.parse::<i128>() {
obj = Ctr::Integer(i); obj = Box::from(Ctr::Integer(i));
} else if let Ok(f) = token.parse::<f64>() { } else if let Ok(f) = token.parse::<f64>() {
obj = Ctr::Float(f); obj = Box::from(Ctr::Float(f));
} else if let Some(s) = tok_is_symbol(&token) { } else if let Some(s) = tok_is_symbol(&token) {
obj = Ctr::Symbol(s); obj = Box::from(Ctr::Symbol(s));
} else { } else {
return Err(format!("Unparsable token: {}", token)); return Err(format!("Unparsable token: {}", token));
} }
token = String::new(); token = String::new();
current_seg.append(obj.clone());
} }
list_append(current_seg, obj);
if alloc_list { if alloc_list {
// return if we have finished the document // return if we have finished the document
if ref_stack.len() == 0 { if ref_stack.len() == 0 {
return Ok(current_seg); return Ok(Box::new(current_seg));
} }
// shortening this will lead to naught but pain let t = current_seg;
obj = Ctr::Seg(current_seg.into_raw()); current_seg = ref_stack.pop().unwrap();
current_seg = ref_stack.pop(); /* TODO: is there a way to do this that doesnt
list_append(current_seg, obj); * 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); ref_stack.push(current_seg);

View file

@ -15,35 +15,37 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#![feature(derive_default_enum)] /*mod append;
mod append;
mod config; mod config;
*/
mod eval; mod eval;
mod func; mod func;
mod lex; mod lex;
mod segment; mod segment;
mod stl;
mod str;
mod vars; mod vars;
/*mod stl;
mod str;*/
extern crate lazy_static;
pub mod ast { pub mod ast {
pub use crate::eval::eval; 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::lex::lex;
pub use crate::segment::{Ctr, Seg, Type}; 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::append::get_append;
pub use crate::stl::get_stdlib; pub use crate::stl::get_stdlib;
pub use crate::str::{get_concat, get_echo}; pub use crate::str::{get_concat, get_echo};
pub use crate::vars::get_export; pub use crate::vars::get_export;
} }*/
pub mod aux { /*pub mod aux {
pub use crate::config::configure; pub use crate::config::configure;
} }*/

View file

@ -15,11 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::fmt; use std::fmt;
use std::marker::PhantomData;
use std::ops::Index;
// Container // Container
#[derive(Debug, Clone, Default)] #[derive(Debug, Default)]
pub enum Ctr <'a> { pub enum Ctr<'a> {
Symbol(String), Symbol(String),
String(String), String(String),
Integer(i128), Integer(i128),
@ -45,20 +46,31 @@ pub enum Type {
/* Segment /* Segment
* Holds two Containers. * Holds two Containers.
* Basic building block for more complex data structures. * 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)] #[derive(Debug)]
pub struct Seg <'a> { pub struct Seg<'a> {
/* "Contents of Address Register" /* "Contents of Address Register"
* Historical way of referring to the first value in a cell. * Historical way of referring to the first value in a cell.
*/ */
pub car: &mut Ctr<'a>, pub car: Box<Ctr<'a>>,
/* "Contents of Decrement Register" /* "Contents of Decrement Register"
* Historical way of referring to the second value in a cell. * Historical way of referring to the second value in a cell.
*/ */
pub cdr: &mut Ctr<'a>, pub cdr: Box<Ctr<'a>>,
/* 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<'_> { impl Ctr<'_> {
pub fn to_type(&self) -> Type { pub fn to_type(&self) -> Type {
match self { match self {
@ -74,25 +86,140 @@ impl Ctr<'_> {
} }
fn seg_to_string(s: &Seg, parens: bool) -> String { impl<'a> Seg<'a> {
let mut string = String::new(); /* recurs over tree assumed to be list in standard form
match s.car { * appends object to end of list
Ctr::None => string.push_str("<nil>"), *
_ => string.push_str(s.car), * TODO: figure out how not to call CLONE on a CTR via obj arg
} * TODO: return result
string.push(' '); */
match s.cdr { pub fn append<'b>(&mut self, obj: Box<Ctr<'a>>) {
Ctr::Seg(inner) => string.push_str(seg_to_string(inner, false)), if let Ctr::None = &*(self.car) {
Ctr::None => {}, self.car = obj;
_ => string.push_str(s.cdr), return
} }
if parens { if let Ctr::Seg(s) = &mut *(self.cdr) {
String::from("(" + string + ")") 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<F: FnMut(&Ctr) -> 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<Ctr<'a>>) -> Seg<'a> {
return Seg{
car: arg,
cdr: Box::new(Ctr::None),
_lifetime_variance_determinant: PhantomData,
}
}
pub fn from(car: Box<Ctr<'a>>, cdr: Box<Ctr<'a>>) -> 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("<nil>"),
_ => 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<usize> 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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Ctr::Symbol(s) => write!(f, "{}", s), Ctr::Symbol(s) => write!(f, "{}", s),
@ -100,14 +227,14 @@ impl fmt::Display for Ctr <'_> {
Ctr::Integer(s) => write!(f, "{}", s), Ctr::Integer(s) => write!(f, "{}", s),
Ctr::Float(s) => write!(f, "{}", s), Ctr::Float(s) => write!(f, "{}", s),
Ctr::Bool(s) => { Ctr::Bool(s) => {
if s { if *s {
write!(f, "T") write!(f, "T")
} else { } else {
write!(f, "F") write!(f, "F")
} }
}, },
Ctr::Seg(s) => write!(f, "{}", s), 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 { impl Type {
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
let ret: &str; let ret: &str;
@ -144,67 +261,3 @@ impl Type {
ret.to_owned() 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<F: Fn(&Ctr)>(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})
}
}

294
sym.rs Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, &'a Symbol>);
#[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<Seg<'a>>,
// list of argument string tokens
pub arg_syms: Vec<String>,
}
/* 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<dyn Fn(&'a Seg) -> Ctr<'a>>),
UserFn(ExternalOperation<'a>),
UserVar(Box<Ctr<'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<Type>),
}
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<Box<Ctr<'c>>, 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 = &ast;
} 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<Ctr>;
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;
}

View file

@ -3,10 +3,10 @@ mod lex_tests {
#[test] #[test]
fn test_lex_basic_pair() { fn test_lex_basic_pair() {
let document: &str = "(hello 'world')"; let document = String::from("(hello 'world')");
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, document); assert_eq!(tree.to_string(), document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -17,10 +17,10 @@ mod lex_tests {
#[test] #[test]
fn test_lex_basic_list() { fn test_lex_basic_list() {
let document: &str = "(hello 'world' 1 2 3)"; let document = String::from("(hello 'world' 1 2 3)");
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, document); assert_eq!(tree.to_string(), document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -31,10 +31,10 @@ mod lex_tests {
#[test] #[test]
fn test_lex_complex_list() { fn test_lex_complex_list() {
let document: &str = "(hello 'world' (1 2 (1 2 3)) 1 2 3)"; let document = String::from("(hello 'world' (1 2 (1 2 3)) 1 2 3)");
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, document); assert_eq!(tree.to_string(), document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -45,11 +45,11 @@ mod lex_tests {
#[test] #[test]
fn test_bad_symbol() { 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\""; let output: &str = "Problem lexing document: \"Unparsable token: as;dd\"";
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree); print!("Bad token yielded: {}\n", tree.to_string());
assert!(false); assert!(false);
} }
Err(s) => { Err(s) => {
@ -60,10 +60,10 @@ mod lex_tests {
#[test] #[test]
fn test_list_delim_in_str() { fn test_list_delim_in_str() {
let document: &str = "('(')"; let document = String::from("('(')");
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, document); assert_eq!(tree.to_string(), document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -74,10 +74,10 @@ mod lex_tests {
#[test] #[test]
fn test_empty_string() { fn test_empty_string() {
let document: &str = "('')"; let document = String::from("('')");
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, document); assert_eq!(tree.to_string(), document);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -88,11 +88,11 @@ mod lex_tests {
#[test] #[test]
fn test_unmatched_list_delim_flat() { 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\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree); print!("Bad token yielded: {}\n", tree.to_string());
assert!(false); assert!(false);
} }
Err(s) => { Err(s) => {
@ -103,9 +103,9 @@ mod lex_tests {
#[test] #[test]
fn test_unmatched_list_delim_complex() { 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\""; let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree); print!("Bad token yielded: {}\n", tree);
assert!(false); assert!(false);
@ -118,11 +118,11 @@ mod lex_tests {
#[test] #[test]
fn test_comment() { 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)"; let output: &str = "(one two)";
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, output); assert_eq!(tree.to_string(), output);
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -133,11 +133,11 @@ mod lex_tests {
#[test] #[test]
fn test_postline_comment() { 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))"; let output: &str = "((one two) (three four))";
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, output.to_string()); assert_eq!(tree.to_string(), output.to_string());
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -148,11 +148,11 @@ mod lex_tests {
#[test] #[test]
fn test_inline_comment() { 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)"; let output: &str = "((one two) three)";
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
assert_eq!(tree, output); assert_eq!(tree.to_string(), output.to_string());
} }
Err(s) => { Err(s) => {
print!("{}\n", s); print!("{}\n", s);
@ -163,9 +163,9 @@ mod lex_tests {
#[test] #[test]
fn test_bad_token_list() { 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\""; let output: &str = "Problem lexing document: \"list started in middle of another token\"";
match lex(document) { match lex(&document) {
Ok(tree) => { Ok(tree) => {
print!("Bad token yielded: {}\n", tree); print!("Bad token yielded: {}\n", tree);
assert!(false); assert!(false);

View file

@ -1,11 +0,0 @@
[package]
name = "relish"
version = "0.1.0"
authors = ["Aidan <aidan@aidanis.online>"]
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"

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Ctr<'a>>,
/* "Contents of Decrement Register"
* Historical way of referring to the second value in a cell.
*/
pub cdr: Box<Ctr<'a>>,
/* 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<Ctr<'a>>) {
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<F: FnMut(&Ctr) -> 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<Ctr<'a>>) -> Seg<'a> {
return Seg{
car: arg,
cdr: Box::new(Ctr::None),
_lifetime_variance_determinant: PhantomData,
}
}
pub fn from(car: Box<Ctr<'a>>, cdr: Box<Ctr<'a>>) -> 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("<nil>"),
_ => 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<usize> 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()
}
}