* fixed and wrote test for lambda and function as arg case

* added license to userlib tests
* added map impl to userlib
* userlib tests now run and pass
* all args are evaluated individually
This commit is contained in:
Ava Apples Affine 2023-03-20 16:22:51 -07:00
parent 8a91560921
commit dcb2969b0a
Signed by: affine
GPG key ID: 3A4645B8CF806069
7 changed files with 148 additions and 40 deletions

View file

@ -490,7 +490,7 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
- Lex function
- Read function (Input + Lex)
- get type function
- Shell module-
- Shell module
- Only loadable via POSIX config var
- Overload Load function to hook into this lib
- arg processor because these are control flow
@ -503,6 +503,7 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
- logging library
- make const all the error messages
- Main shell calls Load function on arg and exits
- Should globals be immutable?
** TODO alpha tasks
- Rename to Flesh
@ -514,7 +515,6 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
- Custom ast pretty print
- Implement Compose for lambdas
Document this in relevant readme sections
- Userlib Map function
- Userlib Reduce function
- File operations
- read-to-string

View file

@ -1,11 +1,30 @@
#!/bin/relish
;; 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/>.
;; USERLIB-TESTS
;; this file implements unit tests for handwritten userlib functions
(def passed
(def passed
'prints if a test has passed'
(test)
(echo (concat "PASSED: " test)))
(def failed
(def failed
'prints if a test has failed'
(test)
(echo (concat "FAILED: " test)))
@ -14,30 +33,32 @@
(('set updates var'
(quote
(let ((test-val 0))
(set test-val 3)
(set (q test-val) 3)
(eq? test-val 3))))
('prepend prepends to list'
(quote
(let ((list (2 3 4))
(list (prepend 1 list))
(list-head (head list)))
(list-head (pop list)))
(eq? (car list-head) 1))))
('map applies function across list'
(quote
(let ((list (1 2 3))
(adder (lambda (x) (add 1 x))))
(eq? (map adder list) (2 3 4)))))
;; add more test cases here
))
(def test-iter 'iterates over test cases'
(head test-cases))
;; run all test cases, print output
(while (gt? (len (cdr test-iter))
0)
(let ((test (car test-iter))
(remaining (cdr test-iter))
(test-name (car test))
(test-body (cdr test)))
(if (eval test-body)
(passed test-name)
(failed test-name))
(def test-iter '' (head remaining))))
(let ((test-iter (pop test-cases)))
(while (gt? (len test-iter) 1)
(let ((test (car test-iter))
(remaining (cdr test-iter))
(test-name (car test))
(test-body (cdr test)))
(if (eval test-body)
(passed test-name)
(failed test-name))
(set (q test-iter) (pop remaining)))))

View file

@ -1,3 +1,5 @@
#!/bin/relish
;; relish: versatile lisp shell
;; Copyright (C) 2021 Aidan Hahn
;;
@ -15,16 +17,50 @@
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;; USERLIB
;; This file contains a plethora of useful features that are not shipped in the STL
;; This file contains useful features that are not shipped in the STL
;; this would be way faster as code in stl
;; but stl already suffers scope creep
(def prepend
'takes a list and appends an element to the back of it.
returns prepended list'
(elem list)
(reverse (append (reverse list) elem)))
(reverse (cons (reverse list) elem)))
;; please dont misuse this tool
(def set
'sets an existing variable without touching its docstring'
'sets an existing variable without touching its docstring.
WARNING: abandon hope all ye who declare and then modify global variables!
If you find yourself struggling to debug a complex error in state access,
or you are having issues re-running commands in the shell consider the
following advice:
It is very much an anti pattern to mutate global variable that contain state
refactor your program: put iterators, counters, procedurally generated code,
and all other mutable state into a let loop.
A zen script in relish is one where each root level form (or eval at repl)
is self contained, and does not permanently modify any other one.
See the userlib tests for an easy to follow example of this.'
(var val)
(let ((doc (get-doc var)))
(def (eval var) doc val)))
(def (eval var) doc val)))
(def map
'Takes two arguments: a function and a list.
for each element in the list, the function is applied and the
result is added to a new list. Returns the new list.'
(func list)
(let ((list-iter (pop list))
(result ()))
(while (gt? (len list-iter) 1)
(let ((current (car list-iter))
(remaining (cdr list-iter))
(current-res (func current)))
(set (q result) (cons result current-res))
(set (q list-iter) (pop remaining))))
result))

View file

@ -51,6 +51,8 @@ pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
}
},
Ctr::Lambda(ref l) => return Ok(call_lambda(l, arg_cdr, syms)?.clone()),
Ctr::Symbol(ref tok) => {
let outer_scope_seg_holder: Seg;
let args: &Seg;

View file

@ -110,7 +110,16 @@ impl SymTable {
let cond_args: &Seg;
let outer_scope_seg_holder: Seg;
if let ValueType::VarForm(ref val) = symbol.value {
return Ok(val.clone());
match **val {
Ctr::Lambda(ref l) if call_func => {
return call_lambda(
l,
&Box::new(Ctr::Seg(args.clone())),
self
)
},
_ => return Ok(val.clone()),
}
} else if call_func {
cond_args = args
} else {
@ -122,8 +131,12 @@ impl SymTable {
}
pub fn is_function_type(&self, name: &String) -> Option<bool> {
if let ValueType::VarForm(_) = self.get(name)?.value {
Some(false)
if let ValueType::VarForm(ref val) = self.get(name)?.value {
if let Ctr::Lambda(_) = **val {
Some(true)
} else {
Some(false)
}
} else {
Some(true)
}
@ -270,17 +283,36 @@ impl Symbol {
*/
pub fn call(&self, args: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
let evaluated_args: &Seg;
let outer_scope_seg_storage: Seg;
let outer_scope_eval: Box<Ctr>;
let mut outer_scope_seg_storage = Seg::new();
let mut errcon: String = String::new();
if !self.conditional_branches {
outer_scope_eval = eval(args, syms)?;
match *outer_scope_eval {
Ctr::Seg(ref segment) => evaluated_args = segment,
_ => {
outer_scope_seg_storage = Seg::from_mono(outer_scope_eval);
evaluated_args = &outer_scope_seg_storage;
if !args.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Seg(ref s) = arg {
let eval_res = eval(s, syms);
if eval_res.is_err() {
errcon = eval_res.err().unwrap();
return false
}
outer_scope_seg_storage.append(eval_res.unwrap().clone());
} else if let Ctr::Symbol(ref s) = arg {
let eval_res = syms.call_symbol(
s,
&outer_scope_seg_storage,
false
);
if eval_res.is_err() {
errcon = eval_res.err().unwrap();
return false
}
outer_scope_seg_storage.append(eval_res.unwrap().clone());
} else {
outer_scope_seg_storage.append(Box::new(arg.clone()));
}
true
}) {
return Err(format!("error evaluating args: {}", errcon))
}
evaluated_args = &outer_scope_seg_storage;
} else {
evaluated_args = args;
}
@ -294,7 +326,7 @@ impl Symbol {
// 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();
let mut holding_table = SymTable::new();
// Prep var table for function execution
for n in 0..f.arg_syms.len() {
@ -370,9 +402,6 @@ impl Symbol {
arg_syms: arg_syms.clone(),
});
args = Args::Lazy(arg_syms.len() as u128);
} else if let Ctr::Lambda(ref l) = *ast.car {
args = Args::Lazy(l.arg_syms.len() as u128);
value = ValueType::FuncForm(l.clone());
} else {
args = Args::None;
value = ValueType::VarForm(ast.car.clone());

View file

@ -252,7 +252,7 @@ mod func_tests {
syms.call_symbol(&"test_func_in".to_string(), &args, true)
.err()
.unwrap(),
"error in call to undefined-symbol: undefined symbol: undefined-symbol".to_string(),
"error evaluating args: undefined symbol: undefined-symbol".to_string(),
);
}
}

View file

@ -335,6 +335,26 @@ mod decl_lib_tests {
}
}
#[test]
fn test_lambda_arg_call() {
let document = "(let (())
(def appl '' (func item) (func item))
(def adder 'my adder' (lambda (x) (add x 1)))
(appl adder 2))";
let mut syms = SymTable::new();
static_stdlib(&mut syms).unwrap();
dynamic_stdlib(&mut syms).unwrap();
let it = *eval(
&lex(&document.to_string()).unwrap(),
&mut syms).unwrap();
if let Ctr::Integer(i) = it {
assert_eq!(i, 3)
} else {
println!("bad result: {}", it);
panic!()
}
}
#[test]
fn test_setget_doc_string() {
let highly_inadvisable = "(set-doc (q help) 'help')";