Eval enhancements. Rewrote store to be significantly better
This commit is contained in:
parent
20821057f2
commit
3848d3bcfa
6 changed files with 363 additions and 327 deletions
70
Readme.org
70
Readme.org
|
|
@ -317,9 +317,10 @@ It is sort of like a lint roller.
|
|||
It also contains considerably subpar implementations of Relish's internals that are kept around for historical reasons.
|
||||
|
||||
** Easy patterns
|
||||
This section can serve as a sort of cookbook for a user who is new to leveraging LISP languages or unsure of where to start with ~relish~.
|
||||
This section contains examples of common composites of control flow that can be used to build more complex or effective applications
|
||||
More ideas may be explored in the file:snippets directory of this project.
|
||||
The author encourages any users to contribute their own personal favorites not already in this section either by adding them to the file:snippets folder, or to extend the documentation here.
|
||||
|
||||
*** while-let combo
|
||||
#+BEGIN_SRC lisp
|
||||
;; myiter = (1 (2 3 4 5 6))
|
||||
|
|
@ -334,15 +335,7 @@ The author encourages any users to contribute their own personal favorites not a
|
|||
#+END_SRC
|
||||
|
||||
The while-let pattern can be used for many purposes. Above it is used to iterate over elements in a list. It can also be used to receive connections to a socket and write data to them.
|
||||
*** TODO main loop application
|
||||
- state switch (while-toggle)
|
||||
- state calculation
|
||||
*** TODO callback model via eval and passed-in functions
|
||||
*** TODO quote/eval for pseudo-anonymous pseudo-functions
|
||||
*** TODO short-circuit guard
|
||||
- circuit example
|
||||
- while-not-circuit-do-more-work
|
||||
*** TODO shell capabilities check
|
||||
|
||||
*** let destructuring
|
||||
~let~ is very useful for destructuring complex return types. If you have a function that may return a whole list of values you can then call it from ~let~ to consume the result data.
|
||||
In this example a let form is used to destructure a call to ~head~. ~head~ returns a list consisting of ~(first-element rest-of-list)~ (for more information see ~(help head)~).
|
||||
|
|
@ -356,6 +349,7 @@ Finally, the bodies evaluated in the ~let~ form are able to operate on the head
|
|||
(echo "this is 1: " first)
|
||||
(echo "this is 2, 3: " rest))
|
||||
#+END_SRC
|
||||
|
||||
*** if-set?
|
||||
One common pattern seen in bash scripts and makefiles is the set-variable-if-not-set pattern.
|
||||
#+BEGIN_SRC shell
|
||||
|
|
@ -421,11 +415,10 @@ Variables and functions defined in an external script loaded by your interpreter
|
|||
cargo run src/bin/main.rs
|
||||
#+END_SRC
|
||||
|
||||
* Guide to codebase
|
||||
* The codebase
|
||||
** file:tests directory
|
||||
Start here if you are new.
|
||||
Most of these files have unimplemented tests commented out in them.
|
||||
Contributions that help fill out all of these tests
|
||||
|
||||
*** Eval tests: file:tests/test_eval.rs
|
||||
These are particularly easy to read and write tests.
|
||||
|
||||
|
|
@ -436,23 +429,24 @@ You can consider these to extend the eval tests to cover the co-recursive nature
|
|||
These tests verify the handling of syntax.
|
||||
|
||||
*** Lib tests: (tests/test_lib*)
|
||||
These tests are unique per stdlib module.
|
||||
These tests are unique per stdlib module and work to prove the functionality of builtin functions in the language.
|
||||
|
||||
** file:src directory
|
||||
This directory contains all of the user facing code in relish.
|
||||
|
||||
Just a few entries of note:
|
||||
*** segment: file:src/segment.rs
|
||||
This file lays out the +spiritual+ +theological+ +ideological+ +theoretical+ mechanical underpinnings of the entire interpreter.
|
||||
The entire LISP machine centers around a singlet or pairing of datum.
|
||||
The ~Ctr~ datatype provides an abstraction for which any type of data, including a ~Seg~ can be a datum.
|
||||
The ~Seg~ datatype provides a mechanism to hold a single datum or a pair of datum. It is implemented as two ~Ctr~s: ~car~ and ~cdr~.
|
||||
A primitive type system is provided through the Rust Enum datatype. A number of utility functions follow.
|
||||
This file lays out the data structures that the interpreter operates on.
|
||||
Representation of code trees, traversals, and type annotations all live here.
|
||||
|
||||
*** lib: file:src/lib.rs
|
||||
This defines a library that can be included to provide an interpreter interface within any Rust project.
|
||||
It includes the core interpreter mechanisms, full stdlib, and the configuration system.
|
||||
Your project can use or not use any number of these components. They can certainly be used to support language development for other LISP machines,
|
||||
or even other languages.
|
||||
The components defined here can certainly be used to support language development for other LISP (or non LISP) langauges.
|
||||
Your project can use or not use any number of these components.
|
||||
|
||||
*** sym: file:src/sym.rs
|
||||
This file contains all code related to symbol expansion and function calling.
|
||||
The types defined in this file include SymTable, Args, Symbol, and more.
|
||||
|
||||
*** config: file:src/config.rs
|
||||
This file contains default configuration values as well as functions which load and run the configuration file script.
|
||||
|
|
@ -464,8 +458,8 @@ The ~static_stdlib~ function loads all symbols in the standard library which do
|
|||
The ~dyanmic_stdlib~ function loads all symbols in the standard library which *do* need configuration into the symbol table.
|
||||
The ~dynamic_stdlib~ function uses variables saved in the symbol table to configure the functions and variables it loads.
|
||||
For more information see file:src/config.
|
||||
Any new addition to the stdlib must make its way here to be included in the main shell (and any other shell using the included ~get_stdlib~ function).
|
||||
You may choose to override these functions if you would like to include your own special functions in your own special interpreter, or if you would like to pare down the stdlib to a small minimal subet of what it is.
|
||||
Any new addition to the stdlib must make its way here to be included in the main shell (and any other shell using the included stdlib functions).
|
||||
You may choose to override these functions if you would like to include your own special functions in your own special interpreter, or if you would like to pare down the stdlib to a lighter subet of what it is.
|
||||
|
||||
You can view the code for standard library functions in file:src/stl/.
|
||||
|
||||
|
|
@ -476,20 +470,20 @@ This contains any executable target of this project. Notably the main shell file
|
|||
Note: this section will not show the status of each item unless you are viewing it with a proper orgmode viewer.
|
||||
Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered.
|
||||
|
||||
*** TODO set function
|
||||
*** TODO list contains via circuit
|
||||
*** TODO Input function
|
||||
*** TODO Lex function
|
||||
*** TODO Read function (Input + Lex)
|
||||
*** TODO Caps function (list functions in table)
|
||||
*** TODO Default prompt says "<minimum shell> LAMBDA:"
|
||||
*** TODO Symbols defined within let by def statements must be pulled out and made global
|
||||
*** TODO Def must eval args for symbol and doc
|
||||
*** TODO Shell prompt is fully configurable (L, R, and Delim)
|
||||
*** TODO Load (load a script) function
|
||||
Pull/Refactor the logic out of the configure functions.
|
||||
Optionally return a list of new variables and/or functions?
|
||||
Will need a concatenate function for tables
|
||||
*** TODO not-builtin set function
|
||||
*** TODO not-builtin list contains
|
||||
*** TODO Main shell calls Load function on arg and exits
|
||||
*** TODO Ship a relish-based stdlib
|
||||
*** TODO Map library written in relish ("slowmap")
|
||||
*** TODO Input function
|
||||
*** TODO Lex function
|
||||
*** TODO Read function (Input + Lex)
|
||||
*** TODO get type function
|
||||
*** TODO FINISH DOCUMENTATION
|
||||
*** TODO Shell module-
|
||||
**** TODO only loadable via POSIX config var
|
||||
|
|
@ -501,16 +495,16 @@ Overload Load function to call a binary too
|
|||
**** TODO Foreground process TTY (fg function)
|
||||
**** TODO list jobs (j function)
|
||||
**** TODO ESSENTIAL: DOCUMENT POSIX MODULE
|
||||
*** TODO Configurable RPROMPT
|
||||
*** TODO logging library
|
||||
*** TODO make const all the error messages
|
||||
*** TODO Rename to Flesh
|
||||
*** TODO Create a dedicated community channel on matrix.sunnypup.io
|
||||
*** TODO Post to relevant channels
|
||||
*** TODO Custom ast pretty print
|
||||
*** TODO Implement Compose for lambdas
|
||||
*** TODO Map function
|
||||
- DOCUMENTATION + TEST:
|
||||
apply a lambda to a list
|
||||
*** TODO Reduce function
|
||||
DOCUMENT AS WELL
|
||||
*** TODO non built in Map function
|
||||
*** TODO non built in Reduce function
|
||||
*** TODO file operations
|
||||
**** TODO read-to-string
|
||||
**** TODO write-to-file
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
;; comment out to load USERLIB
|
||||
;; (load (concat HOME "/.relish/userlib.rls"))
|
||||
|
||||
(def CFG_RELISH_POSIX
|
||||
"my job control preference"
|
||||
"1")
|
||||
|
||||
(echo "Welcome back pupper")
|
||||
(echo "Welcome back to relish.")
|
||||
(echo "Enjoy your computing.")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/bin/relish
|
||||
|
||||
(def prepend 'takes a list and appends an element to the back of it, returns prepended list'
|
||||
(def prepend
|
||||
'takes a list and appends an element to the back of it.
|
||||
returns prepended list'
|
||||
(elem list)
|
||||
(reverse (append (reverse list) elem)))
|
||||
22
src/stl.rs
22
src/stl.rs
|
|
@ -503,6 +503,28 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
|
|||
}
|
||||
);
|
||||
|
||||
syms.insert(
|
||||
"get-doc".to_string(),
|
||||
Symbol {
|
||||
name: String::from("get-doc"),
|
||||
args: Args::Lazy(1),
|
||||
conditional_branches: true,
|
||||
docs: decl::GETDOC_DOCSTRING.to_string(),
|
||||
value: ValueType::Internal(Rc::new(decl::getdoc_callback)),
|
||||
}
|
||||
);
|
||||
|
||||
syms.insert(
|
||||
"set-doc".to_string(),
|
||||
Symbol {
|
||||
name: String::from("get-doc"),
|
||||
args: Args::Lazy(2),
|
||||
conditional_branches: true,
|
||||
docs: decl::SETDOC_DOCSTRING.to_string(),
|
||||
value: ValueType::Internal(Rc::new(decl::setdoc_callback)),
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
332
src/stl/decl.rs
332
src/stl/decl.rs
|
|
@ -39,7 +39,20 @@ pub fn eval_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
if ast.len() > 1 {
|
||||
Err("do not eval more than one thing at a time".to_string())
|
||||
} else {
|
||||
let arguments: Ctr;
|
||||
match *ast.car {
|
||||
Ctr::Seg(ref s) => arguments = *eval(s, syms)?.clone(),
|
||||
Ctr::Symbol(ref sym) => {
|
||||
let intermediate = syms.call_symbol(sym, &Seg::new(), true)?;
|
||||
if let Ctr::Seg(ref s) = *intermediate {
|
||||
arguments = *eval(s, syms)?.clone()
|
||||
} else {
|
||||
arguments = *intermediate
|
||||
}
|
||||
},
|
||||
_ => arguments = *ast.car.clone()
|
||||
}
|
||||
match arguments {
|
||||
Ctr::Seg(ref s) => Ok(*eval(s, syms)?.clone()),
|
||||
Ctr::Symbol(ref sym) => {
|
||||
let intermediate = syms.call_symbol(sym, &Seg::new(), true)?;
|
||||
|
|
@ -49,7 +62,7 @@ pub fn eval_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
|||
Ok(*intermediate)
|
||||
}
|
||||
},
|
||||
_ => Ok(*ast.car.clone())
|
||||
_ => Ok(arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,131 +100,6 @@ CURRENT VALUE AND/OR BODY:
|
|||
Ok(Ctr::None)
|
||||
}
|
||||
|
||||
pub const STORE_DOCSTRING: &str = "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)";
|
||||
|
||||
pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<Ctr, String> {
|
||||
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 => {
|
||||
let eval_arg: &Seg;
|
||||
let outer_maybe_eval_seg: Seg;
|
||||
let mut expand = false;
|
||||
if let Ctr::Seg(ref eval_me) = *data_tree.car {
|
||||
eval_arg = eval_me;
|
||||
} else {
|
||||
outer_maybe_eval_seg = Seg::from_mono(data_tree.car.clone());
|
||||
eval_arg = &outer_maybe_eval_seg;
|
||||
expand = true;
|
||||
}
|
||||
match eval(eval_arg, syms) {
|
||||
Ok(ctr) => {
|
||||
let mut body = ctr;
|
||||
if expand {
|
||||
if let Ctr::Seg(ref s) = *body {
|
||||
body = s.car.clone();
|
||||
} else {
|
||||
return Err("impossible expansion".to_string())
|
||||
}
|
||||
}
|
||||
syms.insert(
|
||||
identifier.clone(),
|
||||
Symbol::from_ast(
|
||||
identifier, doc,
|
||||
&Seg::from_mono(body.clone()), None
|
||||
),
|
||||
);
|
||||
if env_cfg {
|
||||
env::set_var(identifier.clone(), body.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::from_ast(
|
||||
identifier, doc, bodies,
|
||||
Some(arg_list),
|
||||
),
|
||||
);
|
||||
} 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)
|
||||
}
|
||||
|
||||
pub const ISSET_DOCSTRING: &str = "accepts a single argument: a symbol.
|
||||
returns true or false according to whether or not the symbol is found in the symbol table.";
|
||||
|
||||
|
|
@ -299,3 +187,195 @@ pub fn lambda_callback(
|
|||
Err("first argument should be a list of symbols".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub const GETDOC_DOCSTRING: &str = "accepts an unevaluated symbol, returns the doc string.
|
||||
Returns an error if symbol is undefined";
|
||||
|
||||
pub fn getdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
||||
if ast.len() != 1 {
|
||||
Err("get-doc only takes a single argument".to_string())
|
||||
} else {
|
||||
if let Ctr::Symbol(ref symbol) = *ast.car {
|
||||
if let Some(sym) = syms.get(symbol) {
|
||||
Ok(Ctr::String(sym.docs.clone()))
|
||||
} else {
|
||||
Err("undefined symbol".to_string())
|
||||
}
|
||||
|
||||
} else {
|
||||
Err("get-doc should only be called on a symbol".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const SETDOC_DOCSTRING: &str = "accepts a symbol and a doc string.
|
||||
Returns an error if symbol is undefined, otherwise sets the symbols docstring to the argument.";
|
||||
|
||||
pub fn setdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
|
||||
if ast.len() != 2 {
|
||||
Err("set-doc only takes two arguments".to_string())
|
||||
} else {
|
||||
if let Ctr::Symbol(ref symbol) = *ast.car {
|
||||
if let Some(mut sym) = syms.remove(symbol) {
|
||||
if let Ctr::Seg(ref doc_node) = *ast.cdr {
|
||||
if let Ctr::String(ref doc) = *doc_node.car {
|
||||
sym.docs = doc.clone();
|
||||
syms.insert(sym.name.clone(), sym);
|
||||
Ok(Ctr::None)
|
||||
} else {
|
||||
syms.insert(sym.name.clone(), sym);
|
||||
Err("second arg must be a string".to_string())
|
||||
}
|
||||
} else {
|
||||
Err("impossible: not a second arg".to_string())
|
||||
}
|
||||
} else {
|
||||
Err("undefined symbol".to_string())
|
||||
}
|
||||
|
||||
} else {
|
||||
Err("first argument must be a symbol".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const STORE_DOCSTRING: &str = "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)
|
||||
|
||||
Additionally, passing a tree as a name will trigger def to evaluate the tree and try to derive
|
||||
a value from it. If it does not return a ";
|
||||
|
||||
|
||||
pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<Ctr, String> {
|
||||
let is_var = ast.len() == 3;
|
||||
let name: String;
|
||||
let docs: String;
|
||||
|
||||
match *ast.car {
|
||||
Ctr::String(ref s) => name = s.clone(),
|
||||
Ctr::Symbol(ref s) => name = s.clone(),
|
||||
Ctr::Seg(ref s) => match *eval(s, syms)? {
|
||||
Ctr::String(ref s) => name = s.clone(),
|
||||
Ctr::Symbol(ref s) => name = s.clone(),
|
||||
_ => return Err("new symbol name doesnt make sense".to_string()),
|
||||
},
|
||||
_ => return Err("new symbol name doesnt make sense".to_string()),
|
||||
}
|
||||
|
||||
// remove var case
|
||||
if ast.len() == 1 {
|
||||
syms.remove(&name);
|
||||
if env_cfg {
|
||||
env::remove_var(name);
|
||||
}
|
||||
|
||||
return Ok(Ctr::None)
|
||||
} else {
|
||||
if ast.len() < 3 || ast.len() > 4 {
|
||||
return Err("expected 3 or 4 args".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
let mut iter: &Seg;
|
||||
if let Ctr::Seg(ref s) = *ast.cdr {
|
||||
iter = s;
|
||||
} else {
|
||||
return Err("not enough args".to_string())
|
||||
}
|
||||
|
||||
match *iter.car {
|
||||
Ctr::String(ref s) => docs = s.clone(),
|
||||
Ctr::Symbol(ref s) => {
|
||||
if let Ctr::String(doc) = *syms.call_symbol(&s, &Seg::new(), true)? {
|
||||
docs = doc.clone();
|
||||
} else {
|
||||
return Err("docs argument does not evaluate to a string".to_string())
|
||||
}
|
||||
},
|
||||
_ => return Err("docs argument does not evaluate to a string".to_string())
|
||||
}
|
||||
|
||||
if let Ctr::Seg(ref s) = *iter.cdr {
|
||||
iter = s;
|
||||
} else {
|
||||
return Err("not enough args".to_string())
|
||||
}
|
||||
|
||||
let mut outer_scope_val: Seg = Seg::new();
|
||||
let noseg = Seg::new(); // similarly, rust shouldnt need this either
|
||||
let mut args = &noseg;
|
||||
let mut var_val_form: &Seg = &outer_scope_val;
|
||||
let mut expand = false;
|
||||
match *iter.car {
|
||||
Ctr::Seg(ref s) if !is_var => args = s,
|
||||
Ctr::Seg(ref s) if is_var => var_val_form = s,
|
||||
_ if is_var => {
|
||||
expand = true;
|
||||
outer_scope_val = Seg::from_mono(Box::new(*iter.car.clone()));
|
||||
var_val_form = &outer_scope_val;
|
||||
},
|
||||
_ if !is_var => return Err("arg list must at least be a list".to_string()),
|
||||
_ => unimplemented!(), // rustc is haunted
|
||||
}
|
||||
|
||||
if is_var {
|
||||
let var_val: Ctr;
|
||||
let var_eval_result = *eval(var_val_form, syms)?;
|
||||
match var_eval_result {
|
||||
Ctr::Seg(ref s) if expand => var_val = *s.car.clone(),
|
||||
Ctr::Seg(ref s) if !expand => var_val = Ctr::Seg(s.clone()),
|
||||
_ => var_val = var_eval_result,
|
||||
}
|
||||
|
||||
let outer_seg = Seg::from_mono(Box::new(var_val.clone()));
|
||||
syms.insert(
|
||||
name.clone(),
|
||||
Symbol::from_ast(&name, &docs, &outer_seg, None),
|
||||
);
|
||||
if env_cfg {
|
||||
env::set_var(name.clone(), var_val.to_string());
|
||||
}
|
||||
return Ok(Ctr::None)
|
||||
}
|
||||
|
||||
println!("{}", args);
|
||||
|
||||
let mut arg_list = vec![];
|
||||
if !args.circuit(&mut |c: &Ctr| -> bool {
|
||||
if let Ctr::Symbol(s) = c {
|
||||
arg_list.push(s.clone());
|
||||
true
|
||||
} else if let Ctr::None = c {
|
||||
// no args case
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return Err("all arguments defined for function must be of type symbol".to_string())
|
||||
}
|
||||
|
||||
if let Ctr::Seg(ref eval_bodies) = *iter.cdr {
|
||||
syms.insert(
|
||||
name.clone(),
|
||||
Symbol::from_ast(
|
||||
&name, &docs,
|
||||
eval_bodies,
|
||||
Some(arg_list),
|
||||
),
|
||||
);
|
||||
Ok(Ctr::None)
|
||||
} else {
|
||||
Err("expected one or more bodies to evaluate in function".to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,76 +5,46 @@ mod decl_lib_tests {
|
|||
#[test]
|
||||
fn test_variable_def_and_lookup() {
|
||||
let doc1 = "(def test 'my test var' 1)";
|
||||
let doc2 = "(test)";
|
||||
let doc2 = "test";
|
||||
let result = "(1)";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
|
||||
if let Ok(tree) = lex(&doc1.to_string()) {
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::None = eval_result {
|
||||
// pass
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc1");
|
||||
assert!(false);
|
||||
}
|
||||
eval(&lex(&doc1.to_string()).unwrap(), &mut syms).unwrap();
|
||||
let res = *eval(&lex(&doc2.to_string()).unwrap(), &mut syms).unwrap();
|
||||
assert_eq!(res.to_string(), result);
|
||||
}
|
||||
|
||||
if let Ok(tree) = lex(&doc2.to_string()) {
|
||||
println!("tree: {tree}");
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::Seg(ref i) = eval_result {
|
||||
assert_eq!(i.to_string(), result);
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc2");
|
||||
assert!(false);
|
||||
}
|
||||
#[test]
|
||||
fn test_variable_def_and_lookup_list() {
|
||||
let doc1 = "(def test 'my test var' (1))";
|
||||
let doc2 = "test";
|
||||
let result = "((1))";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
|
||||
eval(&lex(&doc1.to_string()).unwrap(), &mut syms).unwrap();
|
||||
let res = *eval(&lex(&doc2.to_string()).unwrap(), &mut syms).unwrap();
|
||||
assert_eq!(res.to_string(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_func_def_and_lookup() {
|
||||
let doc1 = "(def test 'my test func' (hello) hello)";
|
||||
let doc2 = "(test '1')";
|
||||
let doc2 = "(test 1)";
|
||||
let result = "1";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
|
||||
if let Ok(tree) = lex(&doc1.to_string()) {
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::None = eval_result {
|
||||
// pass
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc1");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
if let Ok(tree) = lex(&doc2.to_string()) {
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::String(ref i) = eval_result {
|
||||
assert_eq!(i.to_string(), result);
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc2");
|
||||
assert!(false);
|
||||
}
|
||||
eval(&lex(&doc1.to_string()).unwrap(), &mut syms).unwrap();
|
||||
let res = *eval(&lex(&doc2.to_string()).unwrap(), &mut syms).unwrap();
|
||||
assert_eq!(res.to_string(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -88,46 +58,10 @@ mod decl_lib_tests {
|
|||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
|
||||
if let Ok(tree) = lex(&doc1.to_string()) {
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::None = eval_result {
|
||||
// pass
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc1");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
if let Ok(tree) = lex(&doc2.to_string()) {
|
||||
println!("tree: {tree}");
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::None = eval_result {
|
||||
// pass
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc2");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
if let Ok(tree) = lex(&doc3.to_string()) {
|
||||
println!("tree: {tree}");
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::Seg(ref i) = eval_result {
|
||||
assert_eq!(i.to_string(), result);
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc3");
|
||||
assert!(false);
|
||||
}
|
||||
eval(&lex(&doc1.to_string()).unwrap(), &mut syms).unwrap();
|
||||
eval(&lex(&doc2.to_string()).unwrap(), &mut syms).unwrap();
|
||||
let res = *eval(&lex(&doc3.to_string()).unwrap(), &mut syms).unwrap();
|
||||
assert_eq!(res.to_string(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -140,55 +74,23 @@ mod decl_lib_tests {
|
|||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
|
||||
if let Ok(tree) = lex(&doc1.to_string()) {
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::None = eval_result {
|
||||
// pass
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc1");
|
||||
assert!(false);
|
||||
}
|
||||
eval(&lex(&doc1.to_string()).unwrap(), &mut syms).unwrap();
|
||||
eval(&lex(&doc2.to_string()).unwrap(), &mut syms).unwrap();
|
||||
|
||||
if let Ok(tree) = lex(&doc2.to_string()) {
|
||||
println!("tree: {tree}");
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::None = eval_result {
|
||||
// pass
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
let eval_result = eval(&lex(&doc3.to_string()).unwrap(), &mut syms);
|
||||
if let Err(s) = eval_result {
|
||||
assert_eq!(
|
||||
s.to_string(),
|
||||
"error in call to test: undefined symbol: test".to_string()
|
||||
);
|
||||
} else {
|
||||
eprintln!("couldn't lex doc2");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
if let Ok(tree) = lex(&doc3.to_string()) {
|
||||
println!("tree: {tree}");
|
||||
let eval_result = eval(&tree, &mut syms);
|
||||
if let Err(s) = eval_result {
|
||||
assert_eq!(
|
||||
s.to_string(),
|
||||
"error in call to test: undefined symbol: test".to_string()
|
||||
);
|
||||
} else {
|
||||
let res = eval_result.unwrap();
|
||||
eprintln!("shouldn't have suceeded: {res}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc3");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_func_def_no_args() {
|
||||
let doc1 = "(def test 'my test func' () '1')";
|
||||
let doc1 = "(def test 'my test func' () 1)";
|
||||
let doc2 = "(test)";
|
||||
let result = "1";
|
||||
|
||||
|
|
@ -196,40 +98,20 @@ mod decl_lib_tests {
|
|||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
|
||||
if let Ok(tree) = lex(&doc1.to_string()) {
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::None = eval_result {
|
||||
// pass
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc1");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
if let Ok(tree) = lex(&doc2.to_string()) {
|
||||
let eval_result = *eval(&tree, &mut syms).unwrap();
|
||||
if let Ctr::String(ref i) = eval_result {
|
||||
assert_eq!(i.to_string(), result);
|
||||
} else {
|
||||
eprintln!("bad: {eval_result}");
|
||||
assert!(false);
|
||||
}
|
||||
} else {
|
||||
eprintln!("couldn't lex doc2");
|
||||
assert!(false);
|
||||
}
|
||||
eval(&lex(&doc1.to_string()).unwrap(), &mut syms).unwrap();
|
||||
let res = *eval(&lex(&doc2.to_string()).unwrap(), &mut syms).unwrap();
|
||||
assert_eq!(res.to_string(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_isset_true() {
|
||||
let doc1 = "(def test '' 1)";
|
||||
let doc2 = "(set? test)";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
|
||||
let def_tree = lex(&doc1.to_string()).unwrap();
|
||||
let set_tree = lex(&doc2.to_string()).unwrap();
|
||||
eval(&def_tree, &mut syms).unwrap();
|
||||
|
|
@ -298,6 +180,40 @@ mod decl_lib_tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_basic() {
|
||||
let document = "(eval (1 2 3))";
|
||||
let result = "(1 2 3)";
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
assert_eq!(
|
||||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
result.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_var_deref() {
|
||||
let def1 = "(def one '' 1)";
|
||||
let def2 = "(def two '' (quote one))";
|
||||
let document = "(eval two)";
|
||||
let result = "1";
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
eval(&lex(&def1.to_string()).unwrap(), &mut syms).unwrap();
|
||||
eval(&lex(&def2.to_string()).unwrap(), &mut syms).unwrap();
|
||||
assert_eq!(
|
||||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
result.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_str_equivalency_list() {
|
||||
let document = "(lambda (x y) (add x y))";
|
||||
|
|
@ -335,7 +251,6 @@ mod decl_lib_tests {
|
|||
let it = *eval(
|
||||
&lex(&document.to_string()).unwrap(),
|
||||
&mut syms).unwrap();
|
||||
println!("final return: {}", it);
|
||||
if let Ctr::Integer(i) = it {
|
||||
assert_eq!(i, 3)
|
||||
} else {
|
||||
|
|
@ -353,7 +268,6 @@ mod decl_lib_tests {
|
|||
let it = *eval(
|
||||
&lex(&document.to_string()).unwrap(),
|
||||
&mut syms).unwrap();
|
||||
println!("{}", it);
|
||||
if let Ctr::Integer(i) = it {
|
||||
assert_eq!(i, 3)
|
||||
} else {
|
||||
|
|
@ -372,11 +286,31 @@ mod decl_lib_tests {
|
|||
let it = *eval(
|
||||
&lex(&document.to_string()).unwrap(),
|
||||
&mut syms).unwrap();
|
||||
println!("{}", it);
|
||||
if let Ctr::Integer(i) = it {
|
||||
assert_eq!(i, 3)
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setget_doc_string() {
|
||||
let highly_inadvisable = "(set-doc help 'help')";
|
||||
let document = "(get-doc help)";
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms).unwrap();
|
||||
dynamic_stdlib(&mut syms).unwrap();
|
||||
let _ = *eval(
|
||||
&lex(&highly_inadvisable.to_string()).unwrap(),
|
||||
&mut syms).unwrap();
|
||||
let it = *eval(
|
||||
&lex(&document.to_string()).unwrap(),
|
||||
&mut syms).unwrap();
|
||||
if let Ctr::String(i) = it {
|
||||
assert_eq!(i, "help".to_string())
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue