significant refactor and simplification

This commit is contained in:
Ava Hahn 2023-02-17 21:00:07 -08:00
parent ca4c557d95
commit 7555a90328
Signed by untrusted user who does not match committer: affine
GPG key ID: 3A4645B8CF806069
8 changed files with 501 additions and 422 deletions

View file

@ -126,9 +126,9 @@ one of these may fix borrow checker insanity
**** 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
**** DONE combine var and func table into global sym table **** DONE combine var and func table into global sym table
**** TODO code deduplication and cleanup in sym table **** DONE refactor and reimplement eval
**** TODO refactor and reimplement eval
**** TODO (test and get it working) **** TODO (test and get it working)
**** TODO lex/eval/e2e tests for var that holds callback func
*** TODO Rudimentary Control Flow *** TODO Rudimentary Control Flow
**** TODO if clause **** TODO if clause
**** TODO loop clause **** TODO loop clause
@ -141,8 +141,8 @@ one of these may fix borrow checker insanity
**** DONE manual verification of config defaults **** DONE manual verification of config defaults
*** TODO Help function *** TODO Help function
*** TODO Env function *** TODO Env function
*** TODO User variable declaration *** DONE User variable declaration
*** TODO User function declaration *** DONE User function declaration
*** TODO Load (load a script) function *** TODO Load (load a script) function
Pull/Refactor the logic out of the configure functions. Pull/Refactor the logic out of the configure functions.
Optionally return a list of new variables and/or functions? Optionally return a list of new variables and/or functions?

View file

@ -15,22 +15,21 @@
* 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::sym::{SYM_TABLE, Symbol, ValueType};
use crate::segment::{Seg, Ctr}; use crate::segment::{Seg, Ctr};
use crate::sym::SYM_TABLE;
/* 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: &Seg, ast: &Seg,
sym_loose: bool, expect_all_symbols_defined: bool,
call_lazy: bool, simplify_function_branches: bool,
) -> Result<Box<Ctr>, String> { ) -> Result<Box<Ctr>, String> {
// data to return // data to return
let mut ret = Box::from(Ctr::None); let mut ret = Box::from(Ctr::None);
let mut first = true;
// to be assigned from cloned/evaled data // to be assigned from cloned/evaled data
let mut car; let mut car;
@ -40,60 +39,69 @@ pub fn eval(
let mut arg_car = &ast.car; let mut arg_car = &ast.car;
let mut arg_cdr = &ast.cdr; let mut arg_cdr = &ast.cdr;
// theres probably a better way to do this let table_handle = SYM_TABLE.lock().unwrap();
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(tok) = &**arg_car {
binding_for_vtable_get = vars.get(tok.clone());
if let Some(ref val) = binding_for_vtable_get {
arg_car = &val;
}
}
// 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(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!")),
}
}
}
// iterate over ast and build out ret // iterate over ast and build out ret
let mut none = false; let mut none = false;
while !none { while !none {
match &**arg_car { let mut prefetched_function: Option<&Symbol> = None;
Ctr::Seg(ref inner) => { while let Ctr::Symbol(ref tok) = **arg_car {
match eval(inner, vars, funcs, sym_loose, call_lazy) { prefetched_function = table_handle.get(tok);
Ok(res) => car = res, if let Some(sym_ref) = prefetched_function {
Err(e) => return Err(format!("Evaluation error: {}", e)), if let ValueType::VarForm(ref value) = sym_ref.value {
arg_car = value;
} else {
break;
}
} else if !expect_all_symbols_defined {
return Err(format!("evaluation error: undefined symbol {}", tok))
} else {
break
} }
} }
Ctr::Symbol(ref tok) => { match **arg_car {
binding_for_vtable_get = vars.get(tok.clone()); Ctr::Seg(ref inner) => {
if let Some(ref val) = binding_for_vtable_get { match eval(inner, expect_all_symbols_defined, simplify_function_branches) {
car = val.clone(); Ok(res) => car = res,
} else if sym_loose { Err(e) => return Err(format!("evaluation error: {}", e)),
car = arg_car.clone() }
}
// im tired please simplify this
Ctr::Symbol(_) => {
if simplify_function_branches && first {
if let Some(func) = prefetched_function {
if let Ctr::Seg(ref candidates) = **arg_cdr {
let fc: Result<Box<Ctr>, String>;
match eval(candidates, expect_all_symbols_defined, false) {
Ok(res) => {
match *res {
Ctr::Seg(ref args) => {
fc = func.call(args);
},
_ => {
fc = func.call(&Seg::from_mono(res.clone()))
}
}
match fc {
Ok(datum) => car = datum,
Err(e) => return Err(format!("call to {} failed: {}", func.name, e))
}
}
Err(e) => return Err(format!("evaluation error: {}", e))
}
} else { } else {
return Err(format!("Undefined variable: {}", tok.clone())); match func.call(&Seg::new()) {
Ok(res) => car = res,
Err(e) => return Err(format!("call to {} failed: {}", func.name, e))
}
}
} else {
car = arg_car.clone();
}
} else {
car = arg_car.clone();
} }
} }
@ -102,16 +110,25 @@ pub fn eval(
} }
} }
match &**arg_cdr { // weird tree but okay
Ctr::Symbol(ref tok) => { while let Ctr::Symbol(ref tok) = **arg_cdr {
if let Some(val) = vars.get(tok.clone()) { prefetched_function = table_handle.get(tok);
cdr = val.clone(); if let Some(sym_ref) = prefetched_function {
} else if sym_loose { if let ValueType::VarForm(ref value) = sym_ref.value {
cdr = ast.cdr.clone() arg_cdr = value;
} else { } else {
return Err(format!("Undefined variable: {}", tok.clone())); break;
}
} else if !expect_all_symbols_defined {
return Err(format!("evaluation error: undefined symbol {}", tok))
} else {
break
}
} }
match **arg_cdr {
Ctr::Symbol(_) => {
cdr = arg_cdr.clone();
none = true; none = true;
} }
@ -125,7 +142,6 @@ pub fn eval(
arg_cdr = &next.cdr arg_cdr = &next.cdr
} }
// if OTHER: clone and set, and then end
_ => { _ => {
cdr = ast.cdr.clone(); cdr = ast.cdr.clone();
none = true; none = true;
@ -137,7 +153,10 @@ pub fn eval(
none = true; none = true;
} }
} }
first = false;
} }
return Ok(ret); Ok(ret)
} }

View file

@ -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(document: &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: &'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(document: &String) -> Result<Box<Seg>, String> {
let doc_len = document.len(); let doc_len = document.len();
if doc_len == 0 { if doc_len == 0 {
@ -87,7 +87,7 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
// set alloc_list // set alloc_list
if delim == ')' { if delim == ')' {
alloc_list = true; alloc_list = true;
if ref_stack.len() < 1 { if ref_stack.is_empty() {
return Err("too many end parens".to_string()); return Err("too many end parens".to_string());
} }
} }
@ -101,7 +101,7 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
// try to generalize all whitespace // try to generalize all whitespace
if !needs_alloc && char::is_whitespace(c) && !is_str { if !needs_alloc && char::is_whitespace(c) && !is_str {
// dont make empty tokens just because the document has consecutive whitespace // dont make empty tokens just because the document has consecutive whitespace
if token.len() == 0 { if token.is_empty() {
continue; continue;
} }
needs_alloc = true; needs_alloc = true;
@ -116,7 +116,7 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
continue; continue;
} }
if token != "" { if !token.is_empty() {
return Err("list started in middle of another token".to_string()); return Err("list started in middle of another token".to_string());
} }
@ -147,7 +147,7 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
* 2. Handle expansion of current list ref * 2. Handle expansion of current list ref
*/ */
} else { } else {
if token.len() == 0 && !is_str && !alloc_list { if token.is_empty() && !is_str && !alloc_list {
return Err("Empty token".to_string()); return Err("Empty token".to_string());
} }
@ -158,7 +158,7 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
is_str = false; is_str = false;
token = String::new(); token = String::new();
current_seg.append(obj); current_seg.append(obj);
} else if token.len() > 0 { } else if !token.is_empty() {
if token == "true" { if token == "true" {
obj = Box::from(Ctr::Bool(true)); obj = Box::from(Ctr::Bool(true));
} else if token == "false" { } else if token == "false" {
@ -179,7 +179,7 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
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.is_empty() {
return Ok(Box::new(current_seg)); return Ok(Box::new(current_seg));
} }
@ -199,9 +199,10 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
} }
if is_str { if is_str {
return Err(UNMATCHED_STR_DELIM.to_string()); Err(UNMATCHED_STR_DELIM.to_string())
} else {
Err(UNMATCHED_LIST_DELIM.to_string())
} }
return Err(UNMATCHED_LIST_DELIM.to_string());
} }
/* Returns true if token /* Returns true if token
@ -209,13 +210,12 @@ fn process<'a>(document: &'a String) -> Result<Box<Seg>, String> {
* *
* else returns false * else returns false
*/ */
fn tok_is_symbol(token: &String) -> Option<String> { fn tok_is_symbol(token: &str) -> Option<String> {
let tok = token.as_str(); for t in token.chars() {
for t in tok.chars() { if !t.is_alphanumeric() && t != '-' && t != '_' {
if !t.is_alphabetic() && !t.is_digit(10) && !(t == '-') && !(t == '_') {
return None; return None;
} }
} }
return Some(String::from(tok)); Some(String::from(token))
} }

View file

@ -19,24 +19,23 @@
mod config; mod config;
*/ */
mod eval; mod eval;
mod func;
mod lex; mod lex;
mod segment; mod segment;
mod vars; mod sym;
mod stl;
/*mod stl; /*mod stl;
mod str;*/ mod str;*/
extern crate lazy_static; extern crate lazy_static;
pub mod ast { pub mod ast {
pub use crate::eval::eval; // pub use crate::eval::eval;
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::sym::{ /* pub use crate::sym::{
SYM_TABLE, SymTable, Symbol, SYM_TABLE, SymTable, Symbol,
UserFn, ValueType, Args, UserFn, ValueType, Args
LIB_EXPORT_ENV, LIB_EXPORT_NO_ENV };*/
};
} }
/*pub mod stdlib { /*pub mod stdlib {

View file

@ -20,13 +20,13 @@ use std::ops::Index;
// Container // Container
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub enum Ctr<'a> { pub enum Ctr {
Symbol(String), Symbol(String),
String(String), String(String),
Integer(i128), Integer(i128),
Float(f64), Float(f64),
Bool(bool), Bool(bool),
Seg(Seg<'a>), Seg(Seg),
#[default] #[default]
None, None,
} }
@ -49,29 +49,29 @@ pub enum Type {
* I was going to call it Cell and then I learned about * I was going to call it Cell and then I learned about
* how important RefCells were in Rust * how important RefCells were in Rust
*/ */
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Seg<'a> { pub struct Seg {
/* "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: Box<Ctr<'a>>, pub car: Box<Ctr>,
/* "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: Box<Ctr<'a>>, pub cdr: Box<Ctr>,
/* Stupid hack that makes rust look foolish. /* Stupid hack that makes rust look foolish.
* Needed to determine variance of lifetime. * Needed to determine variance of lifetime.
* How this is an acceptable solution I have * How this is an acceptable solution I have
* not a single clue. * not a single clue.
*/ */
_lifetime_variance_determinant: PhantomData<&'a ()> _lifetime_variance_determinant: PhantomData<()>
} }
static NOTHING: Ctr = Ctr::None; 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 {
Ctr::Symbol(_s) => Type::Symbol, Ctr::Symbol(_s) => Type::Symbol,
@ -86,14 +86,14 @@ impl Ctr<'_> {
} }
impl<'a> Seg<'a> { impl Seg {
/* recurs over tree assumed to be list in standard form /* recurs over tree assumed to be list in standard form
* appends object to end of list * appends object to end of list
* *
* TODO: figure out how not to call CLONE on a CTR via obj arg * TODO: figure out how not to call CLONE on a CTR via obj arg
* TODO: return result * TODO: return result
*/ */
pub fn append<'b>(&mut self, obj: Box<Ctr<'a>>) { pub fn append(&mut self, obj: Box<Ctr>) {
if let Ctr::None = &*(self.car) { if let Ctr::None = &*(self.car) {
self.car = obj; self.car = obj;
return return
@ -127,16 +127,16 @@ impl<'a> Seg<'a> {
} }
} }
pub fn from_mono(arg: Box<Ctr<'a>>) -> Seg<'a> { pub fn from_mono(arg: Box<Ctr>) -> Seg {
return Seg{ Seg {
car: arg, car: arg,
cdr: Box::new(Ctr::None), cdr: Box::new(Ctr::None),
_lifetime_variance_determinant: PhantomData, _lifetime_variance_determinant: PhantomData,
} }
} }
pub fn from(car: Box<Ctr<'a>>, cdr: Box<Ctr<'a>>) -> Seg<'a> { pub fn from(car: Box<Ctr>, cdr: Box<Ctr>) -> Seg {
return Seg{ Seg {
car, car,
cdr, cdr,
_lifetime_variance_determinant: PhantomData, _lifetime_variance_determinant: PhantomData,
@ -152,8 +152,12 @@ impl<'a> Seg<'a> {
len len
} }
pub fn new() -> Seg<'a> { pub fn is_empty(&self) -> bool {
return Seg{ self.len() == 0
}
pub fn new() -> Seg {
Seg {
car: Box::new(Ctr::None), car: Box::new(Ctr::None),
cdr: Box::new(Ctr::None), cdr: Box::new(Ctr::None),
_lifetime_variance_determinant: PhantomData, _lifetime_variance_determinant: PhantomData,
@ -170,7 +174,7 @@ fn seg_to_string(s: &Seg, parens: bool) -> String {
} }
string.push(' '); string.push(' ');
match &*(s.cdr) { match &*(s.cdr) {
Ctr::Seg(inner) => string.push_str(&seg_to_string(&inner, false)), Ctr::Seg(inner) => string.push_str(&seg_to_string(inner, false)),
Ctr::None => {string.pop();}, Ctr::None => {string.pop();},
_ => string.push_str(&s.cdr.to_string()), _ => string.push_str(&s.cdr.to_string()),
} }
@ -179,9 +183,9 @@ fn seg_to_string(s: &Seg, parens: bool) -> String {
string string
} }
impl<'a> Clone for Seg<'a> { impl Clone for Seg {
fn clone(&self) -> Seg<'a> { fn clone(&self) -> Seg {
return Seg{ Seg {
car: self.car.clone(), car: self.car.clone(),
cdr: self.cdr.clone(), cdr: self.cdr.clone(),
_lifetime_variance_determinant: PhantomData, _lifetime_variance_determinant: PhantomData,
@ -189,8 +193,8 @@ impl<'a> Clone for Seg<'a> {
} }
} }
impl<'a> Index<usize> for Seg<'a> { impl Index<usize> for Seg {
type Output = Ctr<'a>; type Output = Ctr;
fn index(&self, idx: usize) -> &Self::Output { fn index(&self, idx: usize) -> &Self::Output {
if idx == 0 { if idx == 0 {
@ -201,25 +205,25 @@ impl<'a> Index<usize> for Seg<'a> {
return s.index(idx - 1) return s.index(idx - 1)
} }
return &NOTHING; &NOTHING
} }
} }
impl<'a> Clone for Ctr<'a> { impl Clone for Ctr {
fn clone(&self) -> Ctr<'a> { fn clone(&self) -> Ctr {
match self { match self {
Ctr::Symbol(s) => Ctr::Symbol(s.clone()), Ctr::Symbol(s) => Ctr::Symbol(s.clone()),
Ctr::String(s) => Ctr::String(s.clone()), Ctr::String(s) => Ctr::String(s.clone()),
Ctr::Integer(s) => Ctr::Integer(s.clone()), Ctr::Integer(s) => Ctr::Integer(*s),
Ctr::Float(s) => Ctr::Float(s.clone()), Ctr::Float(s) => Ctr::Float(*s),
Ctr::Bool(s) => Ctr::Bool(s.clone()), Ctr::Bool(s) => Ctr::Bool(*s),
Ctr::Seg(s) => Ctr::Seg(s.clone()), Ctr::Seg(s) => Ctr::Seg(s.clone()),
Ctr::None => Ctr::None, Ctr::None => Ctr::None,
} }
} }
} }
impl fmt::Display for Ctr<'_> { 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),
@ -239,25 +243,38 @@ impl fmt::Display for Ctr<'_> {
} }
} }
impl fmt::Display for Seg<'_> { impl fmt::Display for Seg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", seg_to_string(self, true)) write!(f, "{}", seg_to_string(self, true))
} }
} }
impl Type { impl fmt::Display for Type {
pub fn to_string(&self) -> String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ret: &str; let ret: &str = match self {
match self { Type::Symbol => "symbol",
Type::Symbol => ret = "symbol", Type::String => "string",
Type::String => ret = "string", Type::Integer => "integer",
Type::Integer => ret = "integer", Type::Float => "float",
Type::Float => ret = "float", Type::Bool => "bool",
Type::Bool => ret = "bool", Type::Seg => "segment",
Type::Seg => ret = "segment", Type::None => "none",
Type::None => ret = "none", };
}
ret.to_owned() write!(f, "{}", ret)
}
}
impl std::convert::From<String> for Type {
fn from(value: String) -> Self {
match value.as_str() {
"symbol" => Type::Symbol,
"string" => Type::String,
"integer" => Type::Integer,
"float" => Type::Float,
"bool" => Type::Bool,
"segment" => Type::Seg,
_ => Type::None,
}
} }
} }

118
src/stl.rs Normal file
View file

@ -0,0 +1,118 @@
/* 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::segment::{Ctr, Seg, Type};
use crate::eval::eval;
use crate::sym::{SYM_TABLE, Symbol, ValueType, Args, UserFn};
use std::env;
/*
// the stdlib var export function with env_sync on
static LIB_STORE_ENV: Symbol = Symbol {
name: String::from("export"),
args: Args::Lazy(2),
value: ValueType::Internal(Box::new( |ast: &Seg| -> Ctr {
_store_callback(ast, true)
},
)),
has_undefined_symbols: false,
};
// the stdlib var export function with env_sync off
pub static LIB_STORE_NO_ENV: Symbol = Symbol {
name: String::from("export"),
args: Args::Lazy(2),
value: ValueType::Internal(Box::new( |ast: &Seg| -> Ctr {
_store_callback(ast, false)
},
)),
has_undefined_symbols: false,
};*/
// TODO : declare function if arg list is long enough
fn _store_callback (ast: &Seg, env_cfg: bool) -> Ctr {
let mut table_handle = SYM_TABLE.lock().unwrap();
let is_var = ast.len() == 2;
if let Ctr::Symbol(ref identifier) = *ast.car {
match &*ast.cdr {
Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), true, true) {
Ok(seg) => if let Ctr::Seg(ref val) = *seg {
table_handle.insert(identifier.clone(), Symbol{
value: ValueType::VarForm(val.car.clone()),
name: identifier.clone(),
args: Args::None,
has_undefined_symbols: false,
});
if env_cfg {
env::set_var(identifier.clone(), val.car.to_string());
}
} else {
eprintln!("impossible args to export")
},
Err(e) => eprintln!("couldnt eval symbol: {}", e),
},
Ctr::Seg(data_tree) if !is_var => {
if let Ctr::Seg(ref args) = *data_tree.car {
let mut arg_list = vec![];
if !args.circuit(&mut |c: &Ctr| -> bool {
if let Ctr::Symbol(ref arg) = c {
arg_list.push(arg.clone());
true
} else {
false
}
}) {
eprintln!("all arguments defined for function must be of type symbol");
return Ctr::None;
};
if let Ctr::Seg(ref bodies) = *data_tree.cdr {
table_handle.insert(identifier.clone(), Symbol{
value: ValueType::FuncForm(UserFn{
ast: Box::new(bodies.clone()),
arg_syms: arg_list.clone(),
}),
name: identifier.clone(),
args: Args::Strict(arg_list
.into_iter()
.map(Type::from)
.collect()),
has_undefined_symbols: false,
});
} else {
eprintln!("expected one or more function bodies in function definition");
return Ctr::None;
}
} else {
eprintln!("expected list of arguments in function definition");
return Ctr::None;
}
}
Ctr::None => {
table_handle.remove(&identifier.to_string());
if env_cfg {
env::remove_var(identifier);
}
},
_ => eprintln!("args not in standard form"),
}
} else {
eprintln!("first argument to export must be a symbol");
}
Ctr::None
}

220
src/sym.rs Normal file
View file

@ -0,0 +1,220 @@
/* 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 std::collections::HashMap;
use std::sync::Mutex;
use lazy_static::lazy_static;
pub type SymTable = HashMap<String, Symbol>;
lazy_static! {
pub static ref SYM_TABLE: Mutex<SymTable> = {
Mutex::new(SymTable::new())
};
}
#[derive(Debug, Clone)]
pub struct UserFn {
// 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>,
// 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)
*/
#[derive(Clone)]
pub enum ValueType {
Internal(Box<fn(&Seg) -> Ctr>),
FuncForm(UserFn),
VarForm(Box<Ctr>)
}
/* Function Args
* If Lazy, is an integer denoting number of args
* If Strict, is a list of type tags denoting argument type.
*/
#[derive(Clone)]
pub enum Args {
Lazy(u128),
Strict(Vec<Type>),
Infinite,
None
}
#[derive(Clone)]
pub struct Symbol {
pub value: ValueType,
pub name: String,
pub args: Args,
pub has_undefined_symbols: bool,
}
impl Args {
fn validate_inputs(&self, args: &Seg) -> Result<(), String> {
match self {
Args::None => {
if args.is_empty() {
return Ok(())
} else {
return Err("expected no args".to_string())
}
},
Args::Infinite => {
if !args.is_empty() {
return Ok(())
} else {
return Err("expected args but none were provided".to_string())
}
},
Args::Lazy(ref num) => {
let called_arg_count = args.len();
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 != 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;
}
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]
));
}
if idx == (arg_types.len() - 1) {
return Err("too many arguments".to_string());
}
}
}
}
Ok(())
}
}
impl Symbol {
/* call
* routine is called by eval when a symbol is expanded
*/
pub fn call(
&self,
args: &Seg,
) -> Result<Box<Ctr>, String> {
if let Err(msg) = self.args.validate_inputs(args) {
return Err(format!("failure to call {}: {}", self.name, msg));
}
match &self.value {
ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())),
ValueType::Internal(ref f) => Ok(Box::new(f(args))),
ValueType::FuncForm(ref f) => {
// stores any value overwritten by local state
// If this ever becomes ASYNC this will need to
// become a more traditional stack design, and the
// global table will need to be released
let mut holding_table = SymTable::new();
// Prep var table for function execution
for n in 0..f.arg_syms.len() {
if let Some(old) = SYM_TABLE.lock().unwrap()
.insert(f.arg_syms[n].clone(), Symbol{
name: f.arg_syms[n].clone(),
value: ValueType::VarForm(Box::new(args[n].clone())),
args: Args::None,
has_undefined_symbols: false,
})
{
holding_table.insert(f.arg_syms[n].clone(), old);
}
}
// execute function
let mut result: Box<Ctr>;
let mut iterate = &*(f.ast);
loop {
if let Ctr::Seg(ref data) = *iterate.car {
match eval(data, !self.has_undefined_symbols, 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() {
SYM_TABLE.lock().unwrap().remove(&f.arg_syms[n]);
if let Some(val) = holding_table.remove(&f.arg_syms[n]) {
SYM_TABLE.lock().unwrap().insert(f.arg_syms[n].clone(), val);
}
}
Ok(result)
}
}
}
}

294
sym.rs
View file

@ -1,294 +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 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;
}