/* relish: versatile lisp shell
* Copyright (C) 2021 Aidan Hahn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
use crate::eval::eval;
use crate::segment::{Seg, Ctr, Type};
use crate::vars::{VAR_TABLE, VTable, LIB_EXPORT};
use std::collections::HashMap;
use std::convert::TryInto;
use lazy_static::lazy_static;
lazy_static! {
pub static ref SYM_TABLE: SymTable<'static> = {
let mut tab = SymTable::new();
tab.declare(LIB_EXPORT);
tab
};
}
pub struct SymTable<'a> (HashMap);
#[derive(Debug)]
pub struct UserFn<'a> {
// Un-evaluated abstract syntax tree
// TODO: Intermediate evaluation to simplify branches with no argument in them
// Simplified branches must not have side effects.
// TODO: Apply Memoization?
pub ast: Box>,
// list of argument string tokens
pub arg_syms: Vec,
}
/* A symbol may either be a pointer to a function
* or a syntax tree to eval with the arguments or
* a simple variable declaration (which can also
* be a macro)
*/
pub enum ValueType<'a> {
Internal(Box Ctr<'a>>),
UserFn(ExternalOperation<'a>),
UserVar(Box>),
}
/* Function Args
* If Lazy, is an integer denoting number of args
* If Strict, is a list of type tags denoting argument type.
*/
pub enum Args {
// signed: -1 denotes infinite args
Lazy(i128),
Strict(Vec),
}
impl Args {
fn validate_inputs(&self, args: &Seg) -> Result<(), String> {
match self {
Args::Lazy(ref num) => {
let called_arg_count = args.len() as i128;
if *num == 0 {
if let Ctr::None = *args.car {
//pass
} else {
return Err("Expected 0 args. Got one or more.".to_string());
}
} else if *num > -1 && (*num != called_arg_count) {
return Err(format!(
"Expected {} args. Got {}.",
num, called_arg_count
));
}
}
Args::Strict(ref arg_types) => {
let mut idx: usize = 0;
let passes = args.circuit(&mut |c: &Ctr| -> bool {
if idx >= arg_types.len() {
return false;
}
if let Ctr::None = c {
return false;
}
if arg_types[idx] == c.to_type() {
idx += 1;
return true;
}
return false;
});
if passes && idx < (arg_types.len() - 1) {
return Err(format!(
"{} too few arguments",
arg_types.len() - (idx + 1)
));
}
if !passes {
if idx < (arg_types.len() - 1) {
return Err(format!(
"argument {} is of wrong type (expected {})",
idx + 1,
arg_types[idx].to_string()
));
}
if idx == (arg_types.len() - 1) {
return Err("too many arguments".to_string());
}
}
}
}
Ok(())
}
}
pub struct Symbol {
pub value: ValueType,
pub name: String,
// has no meaning for UserVar values
pub args: Args,
// dont fail on undefined symbol (passed to eval)
// has no meaning for UserVar values
pub loose_syms: bool,
// dont evaluate args at all. leave that to the function
// has no meaning for UserVar values
pub eval_lazy: bool,
}
impl<'a, 'b> Symbol {
/* call
* routine is called by eval when a function call is detected
*/
pub fn func_call(
&self,
args: &'b Seg<'b>,
) -> Result>, String> {
// put args in simplest desired form
let evaluated_args;
match eval(args, self.loose_syms, self.eval_lazy) {
Ok(arg_data) => {
if let Ctr::Seg(ast) = *arg_data {
evaluated_args = *
} else {
return Err("Panicking: eval returned not a list for function args.".to_string());
}
}
Err(s) => {
return Err(format!(
"error evaluating args to {}: {}",
self.name, s
))
}
}
if let UserVar(_s) = self {
return evaluated_args;
}
if let Err(msg) = self.args.validate_inputs(evaluated_args) {
return Err(format!("failure to call {}: {}", self.name, msg));
}
/* corecursive with eval.
* essentially calls eval on each body in the function.
* result of the final body is returned.
*/
match &self.function {
Operation::Internal(ref f) => return Ok(Box::new(f(evaluated_args))),
Operation::External(ref f) => {
let mut holding_table = VTable::new();
// Prep var table for function execution
for n in 0..f.arg_syms.len() {
let iter_arg = evaluated_args[n];
if let Some(val) = VAR_TABLE.get(f.arg_syms[n]) {
holding_table.insert(f.arg_syms[n].clone(), val);
}
VAR_TABLE.insert(f.arg_syms[n].clone(), Box::new(iter_arg));
}
// execute function
let mut result: Box;
let iterate = &*(f.ast);
loop {
if let Ctr::Seg(ref data) = *iterate.car {
match eval(data, self.loose_syms, true) {
Ok(ctr) => result = ctr,
Err(e) => return Err(e),
}
} else {
panic!("function body not in standard form!")
}
match *iterate.cdr {
Ctr::Seg(ref next) => iterate = next,
Ctr::None => break,
_ => panic!("function body not in standard form!"),
}
}
// clear local vars and restore previous values
for n in 0..f.arg_syms.len() {
VAR_TABLE.remove(f.arg_syms[n]);
if let Some(val) = holding_table.get(f.arg_syms[n]) {
VAR_TABLE.insert(f.arg_syms[n].clone(), val);
}
}
return Ok(result);
}
}
}
}
// the stdlib var export function with env_sync on
lazy_static! {
pub static ref LIB_EXPORT_ENV: Function = Function {
name: String::from("export"),
loose_syms: true,
eval_lazy: true,
args: Args::Lazy(2),
function: Operation::Internal(Box::new( move |ast: &Seg| -> Ctr {
_export_callback(ast, true)
},
)),
};
}
// the stdlib var export function with env_sync off
lazy_static! {
pub static ref LIB_EXPORT_NO_ENV: Function = Function {
name: String::from("export"),
loose_syms: true,
eval_lazy: true,
args: Args::Lazy(2),
function: Operation::Internal(Box::new( move |ast: &Seg| -> Ctr {
_export_callback(ast, false)
},
)),
};
}
fn _export_callback<'a> (ast: &Seg, env_cfg: bool) -> Ctr<'a> {
if let Ctr::Symbol(ref identifier) = *ast.car {
match &*ast.cdr {
Ctr::Seg(data_tree) => match eval(&Box::new(data_tree), false, true) {
Ok(seg) => match *seg {
Ctr::Seg(val) => {
SYM_TABLE.declare(Symbol {
value: UserVar(val.car),
name: identifier.clone(),
});
if env_cfg {
env::set_var(identifier.clone(), val.car.to_string())
}
},
_ => eprintln!("impossible args to export"),
},
Err(e) => eprintln!("couldnt eval symbol: {}", e),
},
Ctr::None => {
VAR_TABLE.remove(identifier.to_string());
if env_cfg {
env::remove_var(identifier.to_string());
}
},
_ => eprintln!("args not in standard form"),
}
} else {
eprintln!("first argument to export must be a symbol");
}
return Ctr::None;
}