Adding collect function

- Collect function as fn:(collector, x) -> new_collector for collections
- Moving some definitions to "libs" folder
- Map and filter defined in mal
This commit is contained in:
teo3300
2024-06-19 15:51:20 +09:00
parent 35716afee9
commit c5406458de
8 changed files with 80 additions and 44 deletions

View File

@ -9,6 +9,8 @@ test:
conf:
mkdir -p ${HOME}/.config/mal
cp -f core/core.mal ${HOME}/.config/mal/
mkdir -p ${HOME}/.config/mal/libs
cp -f libs/* ${HOME}/.config/mal/libs/
install: build-release conf
sudo cp target/release/rust-mal /usr/local/bin/mal

View File

@ -18,14 +18,6 @@
b)))
; Arithmetic
(def! abs (fn* [a]
(if (> a 0)
a
(- 0 a))))
(def! mod (fn* [a b]
(- a (* (/ a b) b))))
(def! > (fn* [a b]
(< b a)))
@ -51,13 +43,19 @@
(def! empty? (fn* [l]
(= (count l) 0)))
(def! assert-msg (fn* [e m]
(if (not e)
(raise m))))
(def! assert (fn* [e] (assert-msg e "Assertion failed")))
(def! assert-eq (fn* [a b]
"returns an error if arguments are not equals, NIL otherwise"
(assert (= a b))))
(assert-msg (= a b) (str "Expected " b (type b) ", found " a (type a)))))
(def! assert-fail (fn* [x]
"returns NIL if evaluation of argument fails, error otherwise"
(assert (not (ok? (eval x))))))
(assert-msg (not (ok? (eval x))) (str "Expected failure, but " x " is correct"))))
;; Since thread safety is not required, I will leave it like this,
;; to make thread safe just guard the function
@ -152,22 +150,35 @@
(def! map (fn* [f l]
"Apply function f to all elements of l"
(def! l (reverse l))
(def! map-r (fn* [l p]
(if (empty? l)
p
(map-r (cdr l) (cons (f (car l)) p)))))
(map-r l '())))
(reverse (map-r l '()))))
(def! filter (fn* [f l]
"Remove all elements that don't satisfy f"
(def! l (reverse l))
"Remove all elements of l that don't satisfy f"
(def! filter-r (fn* [l p]
(if (empty? l)
p
(do
(def! t (car l))
(if (f t)
(filter-r (cdr l) (cons (car l) p))
(filter-r (cdr l) (cons t p))
(filter-r (cdr l) p))))))
(filter-r l '())))
(reverse (filter-r l '()))))
(def! map-if (fn* [c f l]
"Apply function f to all elements of l that satisfy c"
(map (fn* [x] (if (c x) (f x) x)) l)))
(def! collect (fn* [f i l]
"Apply collector function f to list l"
"Collector function must accept two parameters:"
"collector and current element, result is the collector in the next iteration"
"Collector is initialized as i"
(def! collect-r (fn* [c l]
(if (empty? l)
c
(collect-r (f c (car l)) (cdr l)))))
(collect-r i l)))

22
libs/lists.mal Normal file
View File

@ -0,0 +1,22 @@
(def! concat (fn* [x y] (def! concat-r (fn* [x y t]
"Concatenate arguments, keeping their order"
(if (car x)
(concat-r (cdr x) y (cons (car x) t))
(if (car y)
(concat-r '() (cdr y) (cons (car y) t))
t))))
(concat-r (reverse y) (reverse x) '())))
(def! distribute (fn* [x] (def! distribute-r (fn* [p n t]
(if (empty? n)
t
(let* [c (car n) n (cdr n)]
(distribute-r (cons c p) n (cons (cons c (concat p n)) t))))))
(distribute-r '() x '())))
(def! len (fn* [l] (def! len-r (fn* [l c]
(if (empty? l)
c
(len-r (cdr l) (+ c 1)))))
(len-r l 0)))

23
libs/math.mal Normal file
View File

@ -0,0 +1,23 @@
(def! abs (fn* [a]
(if (> a 0)
a
(- 0 a))))
(def! mod (fn* [a b]
(- a (* (/ a b) b))))
(def! max (fn* [a b]
(if (> a b)
a
b)))
(def! min (fn* [a b]
(if (< a b)
a
b)))
(def! fact (fn* [a]
(def! fact-r (fn* [a b]
(if (not (> a 1)) b
(fact-r (- a 1) (* a b)))))
(fact-r a 1)))

View File

@ -38,7 +38,7 @@ use crate::parse_tools::read_file;
use crate::printer::{pr_str, prt};
use crate::reader::{read_str, Reader};
use crate::types::MalType::{Atom, Fun, Int, List, Nil, Str};
use crate::types::{mal_assert, mal_equals, reset_bang, MalErr};
use crate::types::{mal_equals, reset_bang, MalErr};
macro_rules! if_atom {
($val:expr) => {{
@ -65,17 +65,16 @@ pub fn ns_init() -> Env {
">" => Fun(|a| comparison_op( |a, b| a > b, a), "Returns true if the first argument is strictly greater than the second one, nil otherwise"),
"<=" => Fun(|a| comparison_op( |a, b| a <= b, a), "Returns true if the first argument is smaller than or equal to the second one, nil otherwise"),
">=" => Fun(|a| comparison_op( |a, b| a >= b, a), "Returns true if the first argument is greater than or equal to the second one, nil otherwise"),
"pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::<Vec<String>>().join(" ").into())), "Print readably all arguments"),
"pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::<Vec<String>>().join("").into())), "Print readably all arguments"),
"str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::<Vec<String>>().join("").into())), "Print non readably all arguments"),
"prn" => Fun(|a| {a.iter().for_each(|a| print!("{} ", pr_str(a, false))); Ok(Nil) }, "Print readably all the arguments"),
"println" => Fun(|a| {a.iter().for_each(|a| print!("{} ", pr_str(a, false))); println!(); Ok(Nil) }, "Print readably all the arguments"),
"prn" => Fun(|a| {a.iter().for_each(|a| print!("{}", pr_str(a, false))); Ok(Nil) }, "Print readably all the arguments"),
"println" => Fun(|a| {a.iter().for_each(|a| print!("{}", pr_str(a, false))); println!(); Ok(Nil) }, "Print readably all the arguments"),
"list" => Fun(|a| Ok(List(a.into())), "Return the arguments as a list"),
"type" => Fun(|a| Ok(car(a)?.label_type()), "Returns a label indicating the type of it's argument"),
"count" => Fun(|a| Ok(Int(car(a)?.if_list()?.len() as isize)), "Return the number of elements in the first argument"),
"=" => Fun(mal_equals, "Return true if the first two parameters are the same type and content, in case of lists propagate to all elements (NOT IMPLEMENTED for 'Map', 'Fun' and 'MalFun')"),
"car" => Fun(|a| mal_car(car(a)?), "Returns the first element of the list, NIL if its empty"),
"cdr" => Fun(|a| mal_cdr(car(a)?), "Returns all the list but the first element"),
"assert" => Fun(mal_assert, "Return an error if assertion fails"),
"read-string" => Fun(|a| read_str(Reader::new().push(car(a)?.if_string()?)).map_err(MalErr::severe), "Tokenize and read the first argument"),
"slurp" => Fun(|a| Ok(Str(read_file(car(a)?.if_string()?)?)), "Read a file and return the content as a string"),
"atom" => Fun(|a| Ok(Atom(Rc::new(RefCell::new(car(a).unwrap_or_default().clone())))), "Return an atom pointing to the given arg"),

View File

@ -126,13 +126,6 @@ pub fn mal_equals(args: &[MalType]) -> MalRet {
}))
}
pub fn mal_assert(args: &[MalType]) -> MalRet {
if args.iter().any(|i| matches!(i, M::Nil | M::Bool(false))) {
return Err(MalErr::unrecoverable("Assertion failed"));
}
Ok(M::Nil)
}
pub fn reset_bang(args: &[MalType]) -> MalRet {
if args.len() < 2 {
return Err(MalErr::unrecoverable("reset requires two arguments"));
@ -241,16 +234,6 @@ pub fn unescape_str(s: &str) -> String {
#[cfg(test)]
mod tests {
use crate::types::mal_assert;
use crate::types::MalType as M;
#[test]
fn _mal_assert() {
assert!(matches!(mal_assert(&[M::Nil]), Err(_)));
assert!(matches!(mal_assert(&[M::Bool(false)]), Err(_)));
assert!(matches!(mal_assert(&[M::Bool(true)]), Ok(_)));
assert!(matches!(mal_assert(&[M::Int(1)]), Ok(_)));
}
#[test]
fn _escape_str() {

View File

@ -18,10 +18,6 @@
(assert (= (/ -10 -2) 5))
(assert (not (ok? (/ 12 0))))
; mod
(assert (= (mod 10 4) 2))
(assert (= (mod 4 10) 4))
; >
(assert (> 3 2))
(assert (not (> 1 2)))
@ -40,4 +36,4 @@
; <=
(assert (<= 1 3))
(assert (not (<= 3 2)))
(assert (<= 1 1))
(assert (<= 1 1))

View File

@ -42,7 +42,7 @@
(assert-fail '(if))
; let*
(assert (let* [let-a 1
(assert-eq (let* [let-a 1
let-b 2]
(assert-eq let-a 1)
(assert-eq let-b 2)