* 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 - Lex function
- Read function (Input + Lex) - Read function (Input + Lex)
- get type function - get type function
- Shell module- - Shell module
- Only loadable via POSIX config var - Only loadable via POSIX config var
- Overload Load function to hook into this lib - Overload Load function to hook into this lib
- arg processor because these are control flow - 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 - logging library
- make const all the error messages - make const all the error messages
- Main shell calls Load function on arg and exits - Main shell calls Load function on arg and exits
- Should globals be immutable?
** TODO alpha tasks ** TODO alpha tasks
- Rename to Flesh - Rename to Flesh
@ -514,7 +515,6 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
- Custom ast pretty print - Custom ast pretty print
- Implement Compose for lambdas - Implement Compose for lambdas
Document this in relevant readme sections Document this in relevant readme sections
- Userlib Map function
- Userlib Reduce function - Userlib Reduce function
- File operations - File operations
- read-to-string - 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 ;; this file implements unit tests for handwritten userlib functions
(def passed (def passed
'prints if a test has passed' 'prints if a test has passed'
(test) (test)
(echo (concat "PASSED: " test))) (echo (concat "PASSED: " test)))
(def failed (def failed
'prints if a test has failed' 'prints if a test has failed'
(test) (test)
(echo (concat "FAILED: " test))) (echo (concat "FAILED: " test)))
@ -14,30 +33,32 @@
(('set updates var' (('set updates var'
(quote (quote
(let ((test-val 0)) (let ((test-val 0))
(set test-val 3) (set (q test-val) 3)
(eq? test-val 3)))) (eq? test-val 3))))
('prepend prepends to list' ('prepend prepends to list'
(quote (quote
(let ((list (2 3 4)) (let ((list (2 3 4))
(list (prepend 1 list)) (list (prepend 1 list))
(list-head (head list))) (list-head (pop list)))
(eq? (car list-head) 1)))) (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 ;; add more test cases here
)) ))
(def test-iter 'iterates over test cases'
(head test-cases))
;; run all test cases, print output ;; run all test cases, print output
(while (gt? (len (cdr test-iter)) (let ((test-iter (pop test-cases)))
0) (while (gt? (len test-iter) 1)
(let ((test (car test-iter)) (let ((test (car test-iter))
(remaining (cdr test-iter)) (remaining (cdr test-iter))
(test-name (car test)) (test-name (car test))
(test-body (cdr test))) (test-body (cdr test)))
(if (eval test-body) (if (eval test-body)
(passed test-name) (passed test-name)
(failed test-name)) (failed test-name))
(def test-iter '' (head remaining)))) (set (q test-iter) (pop remaining)))))

View file

@ -1,3 +1,5 @@
#!/bin/relish
;; relish: versatile lisp shell ;; relish: versatile lisp shell
;; Copyright (C) 2021 Aidan Hahn ;; Copyright (C) 2021 Aidan Hahn
;; ;;
@ -15,16 +17,50 @@
;; along with this program. If not, see <http://www.gnu.org/licenses/>. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;; USERLIB ;; 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 (def prepend
'takes a list and appends an element to the back of it. 'takes a list and appends an element to the back of it.
returns prepended list' returns prepended list'
(elem list) (elem list)
(reverse (append (reverse list) elem))) (reverse (cons (reverse list) elem)))
;; please dont misuse this tool
(def set (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) (var val)
(let ((doc (get-doc var))) (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) => { Ctr::Symbol(ref tok) => {
let outer_scope_seg_holder: Seg; let outer_scope_seg_holder: Seg;
let args: &Seg; let args: &Seg;

View file

@ -110,7 +110,16 @@ impl SymTable {
let cond_args: &Seg; let cond_args: &Seg;
let outer_scope_seg_holder: Seg; let outer_scope_seg_holder: Seg;
if let ValueType::VarForm(ref val) = symbol.value { 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 { } else if call_func {
cond_args = args cond_args = args
} else { } else {
@ -122,8 +131,12 @@ impl SymTable {
} }
pub fn is_function_type(&self, name: &String) -> Option<bool> { pub fn is_function_type(&self, name: &String) -> Option<bool> {
if let ValueType::VarForm(_) = self.get(name)?.value { if let ValueType::VarForm(ref val) = self.get(name)?.value {
Some(false) if let Ctr::Lambda(_) = **val {
Some(true)
} else {
Some(false)
}
} else { } else {
Some(true) Some(true)
} }
@ -270,17 +283,36 @@ impl Symbol {
*/ */
pub fn call(&self, args: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> { pub fn call(&self, args: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, String> {
let evaluated_args: &Seg; let evaluated_args: &Seg;
let outer_scope_seg_storage: Seg; let mut outer_scope_seg_storage = Seg::new();
let outer_scope_eval: Box<Ctr>; let mut errcon: String = String::new();
if !self.conditional_branches { if !self.conditional_branches {
outer_scope_eval = eval(args, syms)?; if !args.circuit(&mut |arg: &Ctr| -> bool {
match *outer_scope_eval { if let Ctr::Seg(ref s) = arg {
Ctr::Seg(ref segment) => evaluated_args = segment, let eval_res = eval(s, syms);
_ => { if eval_res.is_err() {
outer_scope_seg_storage = Seg::from_mono(outer_scope_eval); errcon = eval_res.err().unwrap();
evaluated_args = &outer_scope_seg_storage; 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 { } else {
evaluated_args = args; evaluated_args = args;
} }
@ -294,7 +326,7 @@ impl Symbol {
// If this ever becomes ASYNC this will need to // If this ever becomes ASYNC this will need to
// become a more traditional stack design, and the // become a more traditional stack design, and the
// global table will need to be released // 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 // Prep var table for function execution
for n in 0..f.arg_syms.len() { for n in 0..f.arg_syms.len() {
@ -370,9 +402,6 @@ impl Symbol {
arg_syms: arg_syms.clone(), arg_syms: arg_syms.clone(),
}); });
args = Args::Lazy(arg_syms.len() as u128); 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 { } else {
args = Args::None; args = Args::None;
value = ValueType::VarForm(ast.car.clone()); 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) syms.call_symbol(&"test_func_in".to_string(), &args, true)
.err() .err()
.unwrap(), .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] #[test]
fn test_setget_doc_string() { fn test_setget_doc_string() {
let highly_inadvisable = "(set-doc (q help) 'help')"; let highly_inadvisable = "(set-doc (q help) 'help')";