/* 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::{Ctr, Seg, Type};
use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType};
use std::env;
use std::rc::Rc;
pub mod append;
pub mod boolean;
pub mod control;
//pub mod str;
/// static_stdlib
/// inserts all stdlib functions that can be inserted without
/// any kind of further configuration data into a symtable
pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
syms.insert(
"append".to_string(),
Symbol {
name: String::from("append"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses any number of arguments collecting them into a list.
If the first argument is a list, all other arguments are added sequentially to the end of the list contained in the first argument.".to_string(),
value: ValueType::Internal(Rc::new(append::append_callback)),
},
);
syms.insert(
"echo".to_string(),
Symbol {
name: String::from("echo"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses any number of arguments. Prints their evaluated values on a new line for each.".to_string(),
value: ValueType::Internal(Rc::new(_echo_callback)),
},
);
syms.insert(
"if".to_string(),
Symbol {
name: String::from("if"),
args: Args::Lazy(3),
conditional_branches: true,
docs: "accepts three bodies, a condition, an unevaluated consequence, and an alternative consequence.
If the condition is evaluated to true, the first consequence is evaluated.
If the condition is evaluated to false, the second consequence is evaluated.
Otherwise, an error is thrown.
example: (if my-state-switch
(do-my-thing)
(else-an-other-thing))".to_string(),
value: ValueType::Internal(Rc::new(control::if_callback)),
},
);
syms.insert(
"let".to_string(),
Symbol {
name: String::from("let"),
args: Args::Infinite,
conditional_branches: true,
docs: "creates a stack of local variables for a sequence of operations.
returns the result of the final operation.
example: (let ((step1 'hello')
(step2 (concat step1 '-'))
(step3 (concat step2 'world')))
(echo step3)
(some-func some-args))
In this example step1, step2, and step3 are created sequentially.
Then, the echo form is evaluated, printing 'hello-world'.
Finally, the some-func form is evaluated.
Since the call to some-func is the final form, its value is returned."
.to_string(),
value: ValueType::Internal(Rc::new(control::let_callback)),
},
);
syms.insert(
"while".to_string(),
Symbol {
name: String::from("while"),
args: Args::Infinite,
conditional_branches: true,
docs: "traverses a list of N un-evaluated forms.
the first form is expected to evaluate to a boolean. if it evaluates to false, while will stop and return. Otherwise, while will evaluate each form in a loop.
example: (while (check-my-state)
(do-thing-1 args)
(do-thing-2 args)
(edit-state my-state))".to_string(),
value: ValueType::Internal(Rc::new(control::while_callback)),
},
);
syms.insert(
"circuit".to_string(),
Symbol {
name: String::from("circuit"),
args: Args::Infinite,
conditional_branches: true,
docs: "traverses a list of N un-evaluated forms.
evaluates each one until it stops. Circuit will stop when a form errors during evaluation.
Circuit will also stop when a form does not evaluate to a boolean, or evaluates to false.
example: (circuit (eq? (do-operation) myresult)
(and state1 state2 (boolean-operation3))
false
(do-another-operation))
in this example, do-another-operation will not be called"
.to_string(),
value: ValueType::Internal(Rc::new(control::circuit_callback)),
},
);
syms.insert(
"and".to_string(),
Symbol {
name: String::from("and"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 AND arg2, and then calculates prev_result AND next_arg.
returns final result."
.to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_and_callback)),
},
);
syms.insert(
"or".to_string(),
Symbol {
name: String::from("or"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 OR arg2, and then calculates prev_result OR next_arg.
returns final result."
.to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_or_callback)),
},
);
syms.insert(
"not".to_string(),
Symbol {
name: String::from("not"),
args: Args::Strict(vec![Type::Bool]),
conditional_branches: false,
docs: "takes a single argument (expects a boolean).
returns false if arg is true or true if arg is false."
.to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_not_callback)),
},
);
syms.insert(
"eq?".to_string(),
Symbol {
name: String::from("eq?"),
args: Args::Infinite,
conditional_branches: false,
docs: "traverses a list of N arguments.
returns true if all arguments hold the same value.
NOTE: 1 and 1.0 are the same, but '1' 'one' or one (symbol) aren't"
.to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_iseq_callback)),
},
);
syms.insert(
"toggle".to_string(),
Symbol {
name: String::from("toggle"),
args: Args::Lazy(1),
conditional_branches: true,
docs: "switches a boolean symbol between true or false.
Takes a single argument (a symbol). Looks it up in the variable table.
Either sets the symbol to true if it is currently false, or vice versa."
.to_string(),
value: ValueType::Internal(Rc::new(boolean::bool_toggle_callback)),
},
);
syms.insert(
"help".to_string(),
Symbol {
name: String::from("help"),
args: Args::Strict(vec![Type::Symbol]),
conditional_branches: true,
docs: "prints help text for a given symbol. Expects only one argument.".to_string(),
value: ValueType::Internal(Rc::new(_help_callback)),
},
);
Ok(())
}
/// dynamic_stdlib
/// takes configuration data and uses it to insert dynamic
/// callbacks with configuration into a symtable
pub fn dynamic_stdlib(syms: &mut SymTable) -> Result<(), String> {
//get CFG_RELISH_ENV from syms
let env_cfg_user_form = syms
.call_symbol(&"CFG_RELISH_ENV".to_string(), &Seg::new(), true)
.unwrap_or_else(|_: String| Box::new(Ctr::None))
.to_string()
.ne("");
syms.insert(
"def".to_string(),
Symbol {
name: String::from("define"),
args: Args::Infinite,
conditional_branches: true,
docs: "allows user to define functions and variables.
A call may take one of three forms:
1. variable declaration:
Takes a name, doc string, and a value.
(def myvar 'my special variable' 'my var value')
2. function declaration:
Takes a name, doc string, list of arguments, and one or more bodies to evaluate.
Result of evaluating the final body is returned.
(def myfunc 'does a thing' (myarg1 myarg2) (dothing myarg1 myarg2) (add myarg1 myarg2))
3. symbol un-definition:
Takes just a name. Removes variable from table.
(def useless-var)"
.to_string(),
value: ValueType::Internal(Rc::new(
move |ast: &Seg, syms: &mut SymTable| -> Result {
_store_callback(ast, syms, env_cfg_user_form)
},
)),
},
);
Ok(())
}
fn _echo_callback(ast: &Seg, _syms: &mut SymTable) -> Result {
if ast.len() == 1 {
println!("{}", ast.car);
} else {
ast.circuit(&mut |arg: &Ctr| print!("{}", arg) == ());
}
Ok(Ctr::None)
}
fn _help_callback(ast: &Seg, syms: &mut SymTable) -> Result {
if ast.len() != 1 {
return Err("help only takes a single argument".to_string());
}
if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(ref sym) = syms.get(symbol) {
let args_str: String;
if let ValueType::VarForm(_) = sym.value {
args_str = "(its a variable)".to_string();
} else {
args_str = sym.args.to_string();
}
println!(
"NAME: {0}\n
ARGS: {1}\n
DOCUMENTATION:\n
{2}\n
CURRENT VALUE AND/OR BODY:
(TODO)",
sym.name, args_str, sym.docs
);
} else {
return Err("undefined symbol".to_string());
}
} else {
return Err("help should only be called on a symbol".to_string());
}
Ok(Ctr::None)
}
fn _store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result {
let is_var = ast.len() == 3;
if let Ctr::Symbol(ref identifier) = *ast.car {
match &*ast.cdr {
// define a symbol
Ctr::Seg(doc_tree) => {
if let Ctr::String(ref doc) = *doc_tree.car {
match &*doc_tree.cdr {
// define a variable
Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), syms) {
Ok(seg) => {
if let Ctr::Seg(ref val) = *seg {
syms.insert(
identifier.clone(),
Symbol {
value: ValueType::VarForm(val.car.clone()),
name: identifier.clone(),
args: Args::None,
docs: doc.to_owned(),
conditional_branches: false,
},
);
if env_cfg {
env::set_var(identifier.clone(), val.car.to_string());
}
} else {
return Err("impossible args to export".to_string());
}
}
Err(e) => return Err(format!("couldnt eval symbol: {}", e)),
},
// define a function
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 if let Ctr::None = c {
// a user cannot type a None
// this case represents no args
true
} else {
false
}
}) {
return Err(
"all arguments defined for function must be of type symbol"
.to_string(),
);
};
if let Ctr::Seg(ref bodies) = *data_tree.cdr {
syms.insert(
identifier.clone(),
Symbol {
value: ValueType::FuncForm(UserFn {
ast: Box::new(bodies.clone()),
arg_syms: arg_list.clone(),
}),
name: identifier.clone(),
args: Args::Lazy(arg_list.len() as u128),
docs: doc.to_owned(),
conditional_branches: false,
},
);
} else {
return Err(
"expected one or more function bodies in function definition"
.to_string(),
);
}
} else {
return Err(
"expected list of arguments in function definition".to_string()
);
}
}
// theres a name and a doc string but nothing else
_ => return Err("have name and doc string, but no body.".to_string()),
}
} else {
return Err("doc string is a required argument".to_string());
}
}
// undefine a symbol
Ctr::None => {
syms.remove(&identifier.to_string());
if env_cfg {
env::remove_var(identifier);
}
}
_ => return Err("arguments not in standard form".to_string()),
}
} else {
return Err("first argument to export must be a symbol".to_string());
}
Ok(Ctr::None)
}