mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 01:05:32 +01:00
Merge branch 'dev'
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -7,4 +7,7 @@ src/#*
|
||||
src/*~
|
||||
|
||||
# history file
|
||||
.mal-history
|
||||
.mal-history
|
||||
|
||||
# stupid macos
|
||||
.DS_Store
|
||||
|
||||
23
Makefile
Normal file
23
Makefile
Normal file
@ -0,0 +1,23 @@
|
||||
default: build-release
|
||||
|
||||
build-release:
|
||||
@echo "Build release"
|
||||
@cargo build --release
|
||||
|
||||
test:
|
||||
@echo "Test release"
|
||||
@cargo test --release
|
||||
|
||||
conf:
|
||||
@echo "Copy core and libraries"
|
||||
@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 test conf
|
||||
@echo "Install mal"
|
||||
sudo cp target/release/rust-mal /usr/local/bin/mal
|
||||
@sudo chown ${USER} /usr/local/bin/mal
|
||||
@echo "To start mal run:"
|
||||
@printf "\tmal [path/module.mal ...]\n\n"
|
||||
191
core/core.mal
191
core/core.mal
@ -1,21 +1,25 @@
|
||||
;; Previously in core.rs
|
||||
; Logic
|
||||
|
||||
; Identity function
|
||||
(def! I (fn* [x] x))
|
||||
|
||||
(def! not (fn* [x]
|
||||
(if x nil true)))
|
||||
|
||||
(def! and (fn* [a b]
|
||||
(if a
|
||||
(if b true))))
|
||||
b)))
|
||||
|
||||
(def! or (fn* [a b]
|
||||
(if a
|
||||
true
|
||||
(if b true))))
|
||||
a
|
||||
b)))
|
||||
|
||||
(def! xor (fn* [a b]
|
||||
(if a
|
||||
(not b)
|
||||
(if b true))))
|
||||
b)))
|
||||
|
||||
; Arithmetic
|
||||
(def! > (fn* [a b]
|
||||
@ -27,57 +31,158 @@
|
||||
(def! <= (fn* [a b]
|
||||
(>= b a)))
|
||||
|
||||
(def! + (fn* [a b]
|
||||
(- a (- 0 b))))
|
||||
|
||||
(def! abs (fn* [a]
|
||||
(if (> a 0)
|
||||
a
|
||||
(- 0 a))))
|
||||
|
||||
(def! sign-product (fn* [op a b acc]
|
||||
(let* [res (op (abs a) (abs b) acc)]
|
||||
(if (xor (< 0 a) (< 0 b))
|
||||
(- 0 res)
|
||||
res))))
|
||||
|
||||
(def! * (fn* [a b]
|
||||
"Recursive product based on sum"
|
||||
(sign-product *-rec a b 0)))
|
||||
|
||||
(def! *-rec (fn* [a b acc]
|
||||
(if (= b 0)
|
||||
acc
|
||||
(*-rec a (- b 1) (+ acc a)))))
|
||||
|
||||
(def! / (fn* [a b]
|
||||
"recursive division based on subtraction"
|
||||
(if (= b 0)
|
||||
(raise "Attempting division by 0")
|
||||
(sign-product /-rec a b 0))))
|
||||
|
||||
(def! /-rec (fn* [a b acc]
|
||||
(if (>= (- a b) 0)
|
||||
(/-rec (- a b) b (+ acc 1))
|
||||
acc)))
|
||||
|
||||
(def! mod (fn* [a b]
|
||||
(- a (* (/ a b) b))))
|
||||
|
||||
; Other functions in core.rs
|
||||
(def! assert-eq (fn* [a b]
|
||||
(assert (= a b))))
|
||||
(def! int? (fn* [a]
|
||||
(= (type a) :int)))
|
||||
|
||||
(def! sym? (fn* [a]
|
||||
(= (type a) :symbol)))
|
||||
|
||||
(def! list? (fn* [a]
|
||||
(= (type a) :list)))
|
||||
|
||||
(def! atom? (fn* [a]
|
||||
(= (type a) :atom)))
|
||||
|
||||
(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-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-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
|
||||
(def! swap! (fn* [a f]
|
||||
(reset! a (f @a))))
|
||||
|
||||
;; File-interaction functions
|
||||
(def! load-file (fn* [f]
|
||||
"open a mal file and evaluate its content"
|
||||
(eval (read-string (str "(do " (slurp f) "\nnil)")))))
|
||||
|
||||
(def! conf-reload (fn* []
|
||||
"reload mal config file"
|
||||
(load-file (str MAL_HOME "/" "config.mal"))))
|
||||
|
||||
;; Shorthand
|
||||
(def! quit (fn* []
|
||||
"Quit the program with status '0'"
|
||||
(exit 0)))
|
||||
|
||||
;; variables
|
||||
(def! MAL_HISTORY (str MAL_HOME "/" ".mal-history"))
|
||||
|
||||
;; helper functions
|
||||
; these functions are never called, their symbols should always resolve
|
||||
; in a special form, they are here only to provide informations through
|
||||
; the "find" and "help" functions
|
||||
(def! def! (fn* [symbol value] "==SPECIAL FORM=="
|
||||
"<symbol>: Sym"
|
||||
"assign <value> to <symbol> in the current environment"
|
||||
"#returns: <value>"))
|
||||
|
||||
(def! let* (fn* [binding statement...] "==SPECIAL FORM=="
|
||||
"<bindings>: Vec"
|
||||
"create a new environment and assign values to symbols according"
|
||||
"to the <binding> vector then evaluate each <statement>"
|
||||
"#returns: result of the last evaluation"))
|
||||
|
||||
(def! do (fn* [statement...] "==SPECIAL FORM=="
|
||||
"evaluate each <statement> in the current environment"
|
||||
"#returns: result of the last evalutaion"))
|
||||
|
||||
(def! if (fn* [condition if-true if-false] "==SPECIAL FORM=="
|
||||
"first evaluate <condition>, based on the result of evaluation"
|
||||
"evaluates one of the two conditional branches, a missing branch"
|
||||
"evaluates to NIL"
|
||||
"#returns: result of the last evaluation"))
|
||||
|
||||
(def! fn* (fn* [arguments statement...] "==SPECIAL FORM=="
|
||||
"arguments: Vec"
|
||||
"#alias: λ" ; >:3
|
||||
"#returns: new lambda that accepts <arguments>, evaluates each"
|
||||
" : <statement> and returns the last evaluation's result"))
|
||||
|
||||
(def! help (fn* [symbol] "==SPECIAL FORM=="
|
||||
"symbol: Sym"
|
||||
"display an helper f or the specified symbol"
|
||||
"#returns: NIL"))
|
||||
|
||||
(def! find (fn* [substring...] "==SPECIAL FORM=="
|
||||
"print all the known symbols partially matching <substring> in"
|
||||
"the current environment"
|
||||
"#returns: NIL"))
|
||||
|
||||
(def! quote (fn* [statement] "==SPECIAL FORM=="
|
||||
"prevents <statement> from being evaluated, it's possible to use"
|
||||
"the ' symbol: 'sym is equivalent to (quote sym)"))
|
||||
|
||||
(def! ok? (fn* [statement] "==SPECIAL FORM=="
|
||||
"evaluate <statement>"
|
||||
"#returns: true if evaluation succeeds, NIL otherwise"))
|
||||
|
||||
(def! eval (fn* [statement] "==SPECIAL FORM=="
|
||||
"evaluate <statement>"
|
||||
"#returns: the result of the evaluation"))
|
||||
|
||||
(def! BANNER
|
||||
(str
|
||||
"; rust-mal: a toy lisp interpreter written in rust\n"
|
||||
"; $ mal [filename ...] : load specified modules at start\n"
|
||||
"; (load-file <name>) : load specified module while mal is running\n"
|
||||
"; (find [pattern...]) : list symbols matching all patterns\n"
|
||||
"; (help <symbol>) : print information about a symbol\n"
|
||||
";\n"
|
||||
"; enjoy ^.^\n"))
|
||||
|
||||
(def! reverse (fn* [x]
|
||||
"Reverses order of elements in the arg"
|
||||
(def! reverse-r (fn* [x t]
|
||||
(if (empty? x)
|
||||
t
|
||||
(reverse-r (cdr x) (cons (car x) t)))))
|
||||
(reverse-r x '())))
|
||||
|
||||
(def! map (fn* [f l]
|
||||
"Apply function f to all elements of l"
|
||||
(def! map-r (fn* [l p]
|
||||
(if (empty? l)
|
||||
p
|
||||
(map-r (cdr l) (cons (f (car l)) p)))))
|
||||
(reverse (map-r l '()))))
|
||||
|
||||
(def! filter (fn* [f l]
|
||||
"Remove all elements of l that don't satisfy f"
|
||||
(def! filter-r (fn* [l p]
|
||||
(if (empty? l)
|
||||
p
|
||||
(if (f (def! t (car l)))
|
||||
(filter-r (cdr l) (cons t p))
|
||||
(filter-r (cdr l) p)))))
|
||||
(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)))
|
||||
|
||||
19
libs/lists.mal
Normal file
19
libs/lists.mal
Normal file
@ -0,0 +1,19 @@
|
||||
(def! map-nil (fn* [v l]
|
||||
"Map nil values of l to the specified value"
|
||||
(map-if v nil l)))
|
||||
|
||||
(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 '())))
|
||||
23
libs/math.mal
Normal file
23
libs/math.mal
Normal 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)))
|
||||
40
libs/string.mal
Normal file
40
libs/string.mal
Normal file
@ -0,0 +1,40 @@
|
||||
(def! char (fn* [l]
|
||||
(car (boom l))))
|
||||
|
||||
(def! char? (fn* [a]
|
||||
(= (type a) :char)))
|
||||
|
||||
(def! string? (fn* [a]
|
||||
(= (type a) :string)))
|
||||
|
||||
(def! strc (fn* [l]
|
||||
(def! strc-r (fn* [l s]
|
||||
(if (empty? l)
|
||||
s
|
||||
(strc-r (cdr l) (str s (car l))))))
|
||||
(strc-r l "")))
|
||||
|
||||
(def! split (fn* [s c]
|
||||
"Split the string at every occurrence of character sc"
|
||||
(if (and (string? s)
|
||||
(char? c))
|
||||
(def! split-r (fn* [l t r]
|
||||
(if (empty? l)
|
||||
(cons t r)
|
||||
(do (def! cc (car l))
|
||||
(if (= cc c)
|
||||
(split-r (cdr l) "" (cons t r))
|
||||
(split-r (cdr l) (str t cc) r))))))
|
||||
(raise "split: accepts a string and a char as arguments"))
|
||||
(reverse (split-r (boom s) "" '()))))
|
||||
|
||||
(def! join (fn* [l s]
|
||||
"Join element of list l to a stiring, using s as separator"
|
||||
(def! join-r (fn* [l t]
|
||||
(if (empty? l)
|
||||
t
|
||||
(join-r (cdr l) (str t s (car l))))))
|
||||
(join-r (cdr l) (car l))))
|
||||
|
||||
(def! chsub (fn* [s c1 c2]
|
||||
(strc (map-if (fn* [x] (= x c1)) (fn* [x] c2) (boom s)))))
|
||||
61
src/core.rs
61
src/core.rs
@ -1,6 +1,9 @@
|
||||
use std::env;
|
||||
use std::{cell::RefCell, env, rc::Rc};
|
||||
|
||||
use crate::env::{car, env_new, env_set, mal_exit, num_op, Env};
|
||||
use crate::env::{
|
||||
any_zero, arithmetic_op, car, comparison_op, env_new, env_set, mal_boom, mal_car, mal_cdr,
|
||||
mal_cons, mal_exit, Env,
|
||||
};
|
||||
|
||||
// This is the first time I implement a macro, and I'm copying it
|
||||
// so I will comment this a LOT
|
||||
@ -32,32 +35,56 @@ macro_rules! env_init {
|
||||
}
|
||||
|
||||
use crate::parse_tools::read_file;
|
||||
use crate::printer::pr_str;
|
||||
use crate::printer::{pr_str, prt};
|
||||
use crate::reader::{read_str, Reader};
|
||||
use crate::types::MalType::{Bool, Fun, Int, List, Nil, Str};
|
||||
use crate::types::{mal_assert, mal_comp, MalArgs, MalErr};
|
||||
use crate::types::MalType::{Atom, Fun, Int, List, Nil, Str};
|
||||
use crate::types::{mal_equals, reset_bang, MalErr};
|
||||
|
||||
macro_rules! if_atom {
|
||||
($val:expr) => {{
|
||||
match $val {
|
||||
Atom(a) => Ok(a.borrow().clone()),
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!("{:?} is not an atom", prt($val)).as_str(),
|
||||
)),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn ns_init() -> Env {
|
||||
env_init!(None,
|
||||
// That's it, you are all going to be simpler functions
|
||||
"exit" => Fun(mal_exit, "Quits the program with specified status"),
|
||||
"raise" => Fun(|a| Err(MalErr::unrecoverable(car(a)?.if_string()?)), "Raise an unrecoverable error with the specified message"),
|
||||
"-" => Fun(|a| num_op(|a, b| Int(a - b), a), "Returns the difference of the arguments"),
|
||||
"*" => Fun(|a| num_op(|a, b| Int(a * b), a), "Returns the product of the arguments"),
|
||||
"<" => Fun(|a| num_op(|a, b| Bool(a < b), a), "Returns true if the first argument is strictly smallerthan the second one, nil otherwise"),
|
||||
"pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::<Vec<String>>().join(" "))), "Print readably all arguments"),
|
||||
"str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::<Vec<String>>().join(""))), "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"),
|
||||
"list" => Fun(|a| Ok(List(MalArgs::new(a.to_vec()))), "Return the arguments as a list"),
|
||||
"list?" => Fun(|a| Ok(Bool(a.iter().all(|el| matches!(el, List(_))))), "Return true if the first argument is a list, false otherwise"),
|
||||
// Ok, keep * and / here because computing basic math operators recursively is fun but not too convenient
|
||||
"+" => Fun(|a| arithmetic_op(0, |a, b| a + b, a), "Returns the sum of the arguments"),
|
||||
"-" => Fun(|a| arithmetic_op(0, |a, b| a - b, a), "Returns the difference of the arguments"),
|
||||
"*" => Fun(|a| arithmetic_op(1, |a, b| a * b, a), "Returns the product of the arguments"),
|
||||
"/" => Fun(|a| arithmetic_op(1, |a, b| a / b, any_zero(a)?), "Returns the quotient of the arguments (not checking for division by 0)"),
|
||||
"<" => Fun(|a| comparison_op( |a, b| a < b, a), "Returns true if the first argument is strictly smaller than the second one, nil otherwise"),
|
||||
">" => 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"),
|
||||
"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"),
|
||||
"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_comp, "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')"),
|
||||
"assert" => Fun(mal_assert, "Return an error if assertion fails"),
|
||||
"=" => 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"),
|
||||
// A tribute to PHP's explode (PHP, a language I never used)
|
||||
"boom" => Fun(mal_boom, "Split a string into a list of string\n; BE CAREFUL WHEN USING"),
|
||||
"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"),
|
||||
"deref" => Fun(|a| if_atom!(car(a)?), "Return the content of the atom argumet"),
|
||||
"reset!" => Fun(reset_bang, "Change the value of the Atom (frist argument) to the second argument"),
|
||||
"cons" => Fun(mal_cons, "Push to front if second element is a list"),
|
||||
"env" => Fun(|a| match env::var(car(a)?.if_string()?) {
|
||||
Ok(s) => Ok(Str(s)),
|
||||
Ok(s) => Ok(Str(s.into())),
|
||||
_ => Ok(Nil),
|
||||
}, "Retrieve the specified environment variable, returns NIL if that variable does not exist")
|
||||
)
|
||||
|
||||
149
src/env.rs
149
src/env.rs
@ -1,3 +1,4 @@
|
||||
use crate::eval::eval;
|
||||
use crate::types::MalErr;
|
||||
use crate::types::{MalMap, MalRet, MalType};
|
||||
use std::cell::RefCell;
|
||||
@ -15,7 +16,7 @@ impl EnvType {
|
||||
.data
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|(k, _)| k.clone())
|
||||
.map(|(k, _)| k.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
keys.sort_unstable();
|
||||
keys
|
||||
@ -26,27 +27,31 @@ pub type Env = Rc<EnvType>;
|
||||
// Following rust implementation, using shorthand to always pas Reference count
|
||||
|
||||
pub fn env_new(outer: Option<Env>) -> Env {
|
||||
Rc::new(EnvType {
|
||||
Env::new(EnvType {
|
||||
data: RefCell::new(MalMap::new()),
|
||||
outer,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn env_set(env: &Env, sym: &str, val: &MalType) -> MalType {
|
||||
env.data.borrow_mut().insert(sym.to_string(), val.clone());
|
||||
val.clone()
|
||||
pub fn env_set(env: &Env, sym: &str, val: &MalType) {
|
||||
env.data.borrow_mut().insert(sym.into(), val.clone());
|
||||
}
|
||||
|
||||
pub fn env_get(env: &Env, sym: &str) -> MalRet {
|
||||
match env.data.borrow().get(sym) {
|
||||
Some(val) => Ok(val.clone()),
|
||||
None => match env.outer.clone() {
|
||||
Some(outer) => env_get(&outer, sym),
|
||||
None => Err(MalErr::unrecoverable(
|
||||
format!("symbol {:?} not defined", sym).as_str(),
|
||||
)),
|
||||
},
|
||||
let mut iter_env = env;
|
||||
loop {
|
||||
if let Some(val) = iter_env.data.borrow().get(sym) {
|
||||
return Ok(val.clone());
|
||||
}
|
||||
if let Some(outer) = &iter_env.outer {
|
||||
iter_env = &outer;
|
||||
continue;
|
||||
}
|
||||
return Err(MalErr::unrecoverable(
|
||||
format!("symbol {:?} not defined", sym).as_str(),
|
||||
));
|
||||
}
|
||||
// Recursive was prettier, but we hate recursion
|
||||
}
|
||||
|
||||
pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env, MalErr> {
|
||||
@ -93,34 +98,88 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> CallRet {
|
||||
// It's fine to clone the environment here
|
||||
// since this is when the function is actually called
|
||||
match ast.as_ref() {
|
||||
M::List(list) => Ok(CallFunc::MalFun(
|
||||
list.last().unwrap_or(&Nil).clone(),
|
||||
inner_env,
|
||||
)),
|
||||
M::List(list) => {
|
||||
for x in &list[0..list.len() - 1] {
|
||||
eval(x, inner_env.clone())?;
|
||||
}
|
||||
Ok(CallFunc::MalFun(
|
||||
list.last().unwrap_or_default().clone(),
|
||||
inner_env,
|
||||
))
|
||||
}
|
||||
_ => scream!(),
|
||||
}
|
||||
}
|
||||
M::Map(m) => {
|
||||
if args.is_empty() {
|
||||
return Err(MalErr::unrecoverable("No key provided to Map construct"));
|
||||
}
|
||||
match &args[0] {
|
||||
M::Str(s) | M::Key(s) => {
|
||||
Ok(CallFunc::Builtin(m.get(s).unwrap_or_default().clone()))
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable("Map argument must be string or key")),
|
||||
}
|
||||
}
|
||||
M::Vector(v) => {
|
||||
if args.is_empty() {
|
||||
return Err(MalErr::unrecoverable("No key provided to Vector construct"));
|
||||
}
|
||||
match &args[0] {
|
||||
M::Int(i) => {
|
||||
if { 0..v.len() as isize }.contains(i) {
|
||||
Ok(CallFunc::Builtin(v[*i as usize].clone()))
|
||||
} else {
|
||||
Ok(CallFunc::Builtin(M::Nil))
|
||||
}
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable("Map argument must be string or key")),
|
||||
}
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!("{:?} is not a function", prt(func)).as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bin_unpack(list: &[MalType]) -> Result<(&MalType, &MalType), MalErr> {
|
||||
if list.len() != 2 {
|
||||
return Err(MalErr::unrecoverable("Two arguments required"));
|
||||
pub fn any_zero(list: &[MalType]) -> Result<&[MalType], MalErr> {
|
||||
if list.iter().any(|x| matches!(x, M::Int(0))) {
|
||||
return Err(MalErr::unrecoverable("Attempting division by 0"));
|
||||
}
|
||||
Ok((&list[0], &list[1]))
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub fn num_op(f: fn(isize, isize) -> MalType, args: &[MalType]) -> MalRet {
|
||||
let (car, cdr) = bin_unpack(args)?;
|
||||
let car = car.if_number()?;
|
||||
let cdr = cdr.if_number()?;
|
||||
Ok(f(car, cdr))
|
||||
pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet {
|
||||
Ok(M::Int(match args.len() {
|
||||
0 => set,
|
||||
1 => f(set, args[0].if_number()?),
|
||||
_ => {
|
||||
// TODO: Maybe an accumulator
|
||||
let mut left = args[0].if_number()?;
|
||||
for el in &args[1..] {
|
||||
left = f(left, el.if_number()?);
|
||||
}
|
||||
left
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
use MalType::Nil;
|
||||
use MalType::{Bool, Nil};
|
||||
pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet {
|
||||
if args.is_empty() {
|
||||
return Ok(Nil);
|
||||
}
|
||||
let (left, rights) = car_cdr(args)?;
|
||||
let mut left = left.if_number()?;
|
||||
for right in rights {
|
||||
let right = right.if_number()?;
|
||||
if !f(left, right) {
|
||||
return Ok(Nil);
|
||||
}
|
||||
left = right;
|
||||
}
|
||||
Ok(Bool(true))
|
||||
}
|
||||
|
||||
pub fn car(list: &[MalType]) -> Result<&MalType, MalErr> {
|
||||
match list.len() {
|
||||
@ -137,11 +196,39 @@ pub fn cdr(list: &[MalType]) -> &[MalType] {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mal_cdr(arg: &MalType) -> MalRet {
|
||||
let list = arg.if_list()?;
|
||||
Ok(MalType::List(cdr(list).into()))
|
||||
}
|
||||
|
||||
pub fn mal_car(arg: &MalType) -> MalRet {
|
||||
let list = arg.if_list()?;
|
||||
if list.is_empty() {
|
||||
Ok(Nil)
|
||||
} else {
|
||||
Ok(list[0].clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the car and cdr from a list
|
||||
pub fn car_cdr(list: &[MalType]) -> Result<(&MalType, &[MalType]), MalErr> {
|
||||
Ok((car(list)?, cdr(list)))
|
||||
}
|
||||
|
||||
// TODO: fix these chonky functions
|
||||
|
||||
pub fn mal_cons(args: &[MalType]) -> MalRet {
|
||||
match args.len() {
|
||||
2 => {
|
||||
let mut car = vec![args[0].clone()];
|
||||
let cdr = args[1].if_list()?;
|
||||
car.extend_from_slice(cdr);
|
||||
Ok(M::List(car.into()))
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable("cons: requires 2 arguments")),
|
||||
}
|
||||
}
|
||||
|
||||
fn first(list: &[MalType]) -> &[MalType] {
|
||||
if list.len() > 1 {
|
||||
&list[..list.len() - 1]
|
||||
@ -153,7 +240,7 @@ fn first(list: &[MalType]) -> &[MalType] {
|
||||
// FIXME: Treat as result for now, change later
|
||||
fn last(list: &[MalType]) -> Result<&MalType, MalErr> {
|
||||
match list.len() {
|
||||
0 => Err(MalErr::unrecoverable("Mi sono cacato le mutande")),
|
||||
0 => Ok(&MalType::Nil),
|
||||
_ => Ok(&list[list.len() - 1]),
|
||||
}
|
||||
}
|
||||
@ -170,3 +257,9 @@ pub fn mal_exit(list: &[MalType]) -> MalRet {
|
||||
_ => exit(-1),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: find another way to process strings
|
||||
pub fn mal_boom(args: &[MalType]) -> MalRet {
|
||||
let string = car(args)?.if_string()?;
|
||||
Ok(M::List(string.chars().map(M::Ch).collect()))
|
||||
}
|
||||
|
||||
365
src/eval.rs
365
src/eval.rs
@ -4,8 +4,28 @@ use crate::env::{first_last, Env};
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType as M;
|
||||
use crate::types::{MalArgs, MalErr, MalMap, MalRet, MalType};
|
||||
use std::borrow::Borrow;
|
||||
use std::rc::Rc;
|
||||
|
||||
macro_rules! forms {
|
||||
($($name:ident : $value:expr),*) => {
|
||||
$(
|
||||
const $name: &'static str = $value;
|
||||
)*
|
||||
};
|
||||
}
|
||||
forms!(NAME_DEF : "def!",
|
||||
NAME_LET : "let*",
|
||||
NAME_DO : "do",
|
||||
NAME_IF : "if",
|
||||
NAME_FN : "fn*",
|
||||
NAME_FN_ALT : "λ",
|
||||
NAME_HELP : "help",
|
||||
NAME_FIND : "find",
|
||||
NAME_QUOTE : "quote",
|
||||
NAME_OK : "ok?",
|
||||
NAME_EVAL : "eval");
|
||||
|
||||
/// Resolve the first element of the list as the function name and call it
|
||||
/// with the other elements as arguments
|
||||
fn eval_func(list: &MalType) -> CallRet {
|
||||
@ -42,7 +62,9 @@ fn def_bang_form(list: &[MalType], env: Env) -> MalRet {
|
||||
}
|
||||
let (car, _) = car_cdr(list)?;
|
||||
let sym = car.if_symbol()?;
|
||||
Ok(env_set(&env, sym, &eval(&list[1], env.clone())?))
|
||||
let val = &eval(&list[1], env.clone())?;
|
||||
env_set(&env, sym, val);
|
||||
Ok(val.clone())
|
||||
}
|
||||
|
||||
/// let* special form:
|
||||
@ -96,7 +118,7 @@ fn fn_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
Ok(M::MalFun {
|
||||
// eval: eval_ast,
|
||||
params: Rc::new(binds.clone()),
|
||||
ast: Rc::new(M::List(MalArgs::new(exprs.to_vec()))),
|
||||
ast: Rc::new(M::List(exprs.into())),
|
||||
env,
|
||||
})
|
||||
}
|
||||
@ -132,6 +154,21 @@ pub fn outermost(env: &Env) -> Env {
|
||||
env.clone()
|
||||
}
|
||||
|
||||
macro_rules! apply {
|
||||
($ast:expr, $env:expr) => {{
|
||||
let apply_list = &eval_ast(&$ast, $env.clone())?;
|
||||
let eval_ret = eval_func(apply_list)?;
|
||||
|
||||
match eval_ret {
|
||||
CallFunc::Builtin(ret) => return Ok(ret),
|
||||
CallFunc::MalFun(fun_ast, fun_env) => {
|
||||
$ast = fun_ast;
|
||||
$env = fun_env;
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Intermediate function to discern special forms from defined symbols
|
||||
pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
let mut ast = ast.clone();
|
||||
@ -141,75 +178,59 @@ pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
M::List(list) if list.is_empty() => return Ok(ast.clone()),
|
||||
M::List(list) => {
|
||||
let (symbol, args) = car_cdr(list)?;
|
||||
match symbol {
|
||||
M::Sym(sym) if sym == "def!" => return def_bang_form(args, env.clone()), // Set for env
|
||||
M::Sym(sym) if sym == "let*" => (ast, env) = let_star_form(args, env.clone())?,
|
||||
M::Sym(sym) if sym == "do" => ast = do_form(args, env.clone())?,
|
||||
M::Sym(sym) if sym == "if" => ast = if_form(args, env.clone())?,
|
||||
M::Sym(sym) if sym == "fn*" => return fn_star_form(args, env.clone()),
|
||||
M::Sym(sym) if sym == "help" => return help_form(args, env.clone()),
|
||||
M::Sym(sym) if sym == "find" => return find_form(args, env.clone()),
|
||||
// Oh God, what have I done
|
||||
M::Sym(sym) if sym == "quote" => return Ok(car(args)?.clone()),
|
||||
M::Sym(sym) if sym == "ok?" => {
|
||||
return match eval(car(args)?, env.clone()) {
|
||||
Err(_) => Ok(M::Nil),
|
||||
_ => Ok(M::Bool(true)),
|
||||
if let M::Sym(sym) = symbol {
|
||||
match sym.borrow() {
|
||||
// I don't like to borrow tho
|
||||
NAME_DEF => return def_bang_form(args, env.clone()), // Set for env
|
||||
NAME_LET => {(ast, env) = let_star_form(args, env.clone())?; continue;},
|
||||
NAME_DO => {ast = do_form(args, env.clone())?; continue;},
|
||||
NAME_IF => {ast = if_form(args, env.clone())?; continue;},
|
||||
NAME_FN | NAME_FN_ALT /* :) */ => {
|
||||
return fn_star_form(args, env.clone())
|
||||
}
|
||||
}
|
||||
// Special form, sad
|
||||
// Bruh, is basically double eval
|
||||
M::Sym(sym) if sym == "eval" => {
|
||||
ast = eval(env::car(args)?, env.clone())?;
|
||||
// Climb to the outermost environment (The repl env)
|
||||
env = outermost(&env);
|
||||
}
|
||||
// Filter out special forms
|
||||
// "apply"/invoke
|
||||
_ => {
|
||||
let apply_list = &eval_ast(&ast, env.clone())?;
|
||||
let eval_ret = eval_func(apply_list)?;
|
||||
|
||||
match eval_ret {
|
||||
CallFunc::Builtin(ret) => return Ok(ret),
|
||||
CallFunc::MalFun(fun_ast, fun_env) => {
|
||||
ast = fun_ast;
|
||||
env = fun_env;
|
||||
NAME_HELP => return help_form(args, env.clone()),
|
||||
NAME_FIND => return find_form(args, env.clone()),
|
||||
// Oh God, what have I done
|
||||
NAME_QUOTE => return Ok(car(args)?.clone()),
|
||||
NAME_OK => {
|
||||
return match eval(car(args)?, env.clone()) {
|
||||
Err(_) => Ok(M::Nil),
|
||||
_ => Ok(M::Bool(true)),
|
||||
}
|
||||
}
|
||||
// Special form, sad
|
||||
// Bruh, is basically double eval
|
||||
NAME_EVAL => {
|
||||
ast = eval(env::car(args)?, env.clone())?;
|
||||
// Climb to the outermost environment (The repl env)
|
||||
env = outermost(&env);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
// "apply"/invoke
|
||||
apply!(ast, env)
|
||||
}
|
||||
_ => return eval_ast(&ast, env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch ast evaluation depending on it being a list or not and return the
|
||||
/// result of the evaluation, this function calls "eval_ast" to recursively
|
||||
/// evaluate asts
|
||||
/*pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
match &ast {
|
||||
M::List(list) if list.is_empty() => Ok(ast.clone()),
|
||||
M::List(list) if !list.is_empty() => apply(list, env),
|
||||
_ => eval_ast(ast, env),
|
||||
}
|
||||
}*/
|
||||
|
||||
/// Separately evaluate all elements in a collection (list or vector)
|
||||
fn eval_collection(list: &MalArgs, env: Env) -> Result<MalArgs, MalErr> {
|
||||
let mut ret = Vec::new();
|
||||
for el in list.as_ref() {
|
||||
ret.push(eval(el, env.clone())?);
|
||||
}
|
||||
Ok(MalArgs::new(ret))
|
||||
Ok(ret.into())
|
||||
}
|
||||
|
||||
/// Evaluate the values of a map
|
||||
fn eval_map(map: &MalMap, env: Env) -> MalRet {
|
||||
let mut ret = MalMap::new();
|
||||
for (k, v) in map {
|
||||
ret.insert(k.to_string(), eval(v, env.clone())?);
|
||||
ret.insert(k.clone(), eval(v, env.clone())?);
|
||||
}
|
||||
Ok(M::Map(ret))
|
||||
}
|
||||
@ -225,248 +246,4 @@ fn eval_ast(ast: &MalType, env: Env) -> MalRet {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Tests //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::env::Env;
|
||||
|
||||
macro_rules! load2 {
|
||||
($input:expr) => {{
|
||||
use crate::reader::{read_str, Reader};
|
||||
|
||||
let r = Reader::new();
|
||||
r.push($input);
|
||||
&match read_str(&r) {
|
||||
Ok(v) => match v {
|
||||
MalType::List(v) => v,
|
||||
_ => panic!("Not a list"),
|
||||
},
|
||||
_ => panic!("Bad command"),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! load {
|
||||
($input:expr) => {{
|
||||
use crate::env::cdr;
|
||||
cdr(load2!($input))
|
||||
}};
|
||||
}
|
||||
|
||||
/*macro_rules! load_f {
|
||||
($input:expr, $env:expr) => {{
|
||||
use crate::reader::{read_str, Reader};
|
||||
use std::rc::Rc;
|
||||
|
||||
let r = Reader::new();
|
||||
r.push($input);
|
||||
let args = match read_str(&r) {
|
||||
Ok(v) => match v {
|
||||
MalType::List(v) => v,
|
||||
_ => panic!("Bad command"),
|
||||
},
|
||||
_ => panic!("Bad command"),
|
||||
};
|
||||
&MalType::List(Rc::new(if args.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let f_str = match &args[0] {
|
||||
MalType::Sym(s) => s.as_str(),
|
||||
_ => panic!("Can't solve function"),
|
||||
};
|
||||
let f = match env_get(&$env.clone(), f_str) {
|
||||
Ok(v) => v,
|
||||
_ => panic!("No such function in env"),
|
||||
};
|
||||
[&[f], &args[1..]].concat()
|
||||
}))
|
||||
}};
|
||||
}*/
|
||||
|
||||
fn _env_empty() -> Env {
|
||||
use crate::env::env_new;
|
||||
env_new(None)
|
||||
}
|
||||
|
||||
mod forms {
|
||||
use crate::env::env_get;
|
||||
use crate::eval::tests::_env_empty;
|
||||
use crate::eval::{def_bang_form, fn_star_form, if_form, let_star_form};
|
||||
use crate::types::MalType;
|
||||
|
||||
#[test]
|
||||
fn def_bang() {
|
||||
let env = _env_empty();
|
||||
|
||||
assert!(matches!( //x empty
|
||||
def_bang_form(
|
||||
load!("(def!) ; empty"),
|
||||
env.clone()),
|
||||
Err(e)
|
||||
if !e.is_recoverable()));
|
||||
assert!(matches!( //x 1 arg
|
||||
def_bang_form(
|
||||
load!("(def! a) ; 1 arg"),
|
||||
env.clone()),
|
||||
Err(e)
|
||||
if !e.is_recoverable()));
|
||||
assert!(matches!( //x 3 args
|
||||
def_bang_form(
|
||||
load!("(def! a 1 2) ; 3 args"),
|
||||
env.clone()),
|
||||
Err(e)
|
||||
if !e.is_recoverable()));
|
||||
|
||||
assert!(matches!(
|
||||
//v 2 args
|
||||
def_bang_form(load!("(def! a 1) ; correct a = 1"), env.clone()),
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
assert!(matches!(env_get(&env, "a"), Ok(MalType::Int(1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_star() {
|
||||
let env = _env_empty();
|
||||
assert!(
|
||||
matches!(let_star_form(load!("(let*)"), env.clone()), Err(e) if !e.is_recoverable())
|
||||
);
|
||||
assert!(
|
||||
matches!(let_star_form(load!("(let* 1)"), env.clone()), Err(e) if !e.is_recoverable())
|
||||
);
|
||||
assert!(
|
||||
matches!(let_star_form(load!("(let* [a])"), env.clone()), Err(e) if !e.is_recoverable())
|
||||
); /*
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* ())"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1))"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1 b 2) a b)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(env_get(&env.clone(), "b"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1 b 2) (def! c 1) a b)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));*/
|
||||
assert!(matches!(env_get(&env.clone(), "c"), Err(e) if !e.is_recoverable()));
|
||||
}
|
||||
|
||||
/*#[test]
|
||||
fn _do_form() {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
do_form(load!("(do)"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(
|
||||
do_form(load!("(do true)"), env.clone()),
|
||||
Ok(MalType::Bool(true))
|
||||
));
|
||||
assert!(matches!(
|
||||
do_form(load!("(do (def! a 1) 2)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Ok(MalType::Int(1))));
|
||||
}*/
|
||||
|
||||
#[test]
|
||||
fn _if_form() {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
if_form(load!("(if)"), env.clone()),
|
||||
Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
if_form(load!("(if 1)"), env.clone()),
|
||||
Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
if_form(load!("(if 1 2 3 4)"), env.clone()),
|
||||
Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
if_form(load!("(if nil 1)"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(
|
||||
if_form(load!("(if nil 1 2)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));
|
||||
assert!(matches!(
|
||||
if_form(load!("(if true 1)"), env.clone()),
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
assert!(matches!(
|
||||
if_form(load!("(if true 1 2)"), env.clone()),
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_star() {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
fn_star_form(load!("(fn* [a b] 1 2)"), env.clone()),
|
||||
Ok(MalType::MalFun {params, ast, .. })
|
||||
if matches!((*params).clone(), MalType::Vector(v)
|
||||
if matches!(&v[0], MalType::Sym(v) if v == "a")
|
||||
&& matches!(&v[1], MalType::Sym(v) if v == "b")
|
||||
&& matches!((*ast).clone(), MalType::List(v)
|
||||
if matches!(&v[0], MalType::Int(1))
|
||||
&& matches!(&v[1], MalType::Int(2))))));
|
||||
// We trust the fact that the env does not do silly stuff
|
||||
assert!(matches!(
|
||||
fn_star_form(load!("(fn*)"), env.clone()),
|
||||
Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
fn_star_form(load!("(fn* 1)"), env.clone()),
|
||||
Err(e) if !e.is_recoverable()));
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn _eval_func() {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
def_bang_form(load!("(def! or (fn* (a b) (if a a b)))"), env.clone()),
|
||||
Ok(_)
|
||||
));
|
||||
assert!(matches!(
|
||||
eval_func(&MalType::Int(1)),
|
||||
Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
eval_func(load_f!("()", env.clone())),
|
||||
Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
eval_func(load_f!("(or nil nil)", env.clone())),
|
||||
Ok(v) if matches!(v, MalType::Nil)));
|
||||
assert!(matches!(
|
||||
eval_func(load_f!("(or 1 nil)", env.clone())),
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
assert!(matches!(
|
||||
eval_func(load_f!("(or nil 1)", env.clone())),
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
}*/
|
||||
|
||||
/*#[test]
|
||||
fn _apply() {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
apply(load2!("(def! a 1)"), env.clone()),
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
assert!(matches!(env_get(&env, "a"), Ok(MalType::Int(1))));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
// all tests moved to mal
|
||||
|
||||
@ -12,7 +12,7 @@ mod step6_file;
|
||||
mod types;
|
||||
|
||||
use core::ns_init;
|
||||
use parse_tools::{interactive, load_file, load_home_file, set_home_path};
|
||||
use parse_tools::{interactive, load_file, load_home_file, print_banner, set_home_path};
|
||||
|
||||
fn main() {
|
||||
// Initialize ns environment
|
||||
@ -33,5 +33,7 @@ fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
print_banner(&reply_env);
|
||||
|
||||
interactive(reply_env);
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ mod functional {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_fail() {
|
||||
fn assert() {
|
||||
test!("assert")
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ mod functional {
|
||||
|
||||
#[test]
|
||||
fn builtin_equals() {
|
||||
test!("equals");
|
||||
test!("equals")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -38,6 +38,36 @@ mod functional {
|
||||
|
||||
#[test]
|
||||
fn fibonacci() {
|
||||
test!("fibonacci");
|
||||
test!("fibonacci")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forms() {
|
||||
test!("forms")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lists() {
|
||||
test!("lists")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atoms() {
|
||||
test!("atoms")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn car_cdr() {
|
||||
test!("car-cdr")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map() {
|
||||
test!("map")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fil() {
|
||||
test!("fil")
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::env::Env;
|
||||
use crate::eval::eval;
|
||||
use crate::reader::{read_str, Reader};
|
||||
use crate::step6_file::rep;
|
||||
use crate::types::{MalErr, MalRet};
|
||||
use crate::types::{MalErr, MalRet, MalStr};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
@ -22,6 +22,10 @@ pub fn set_home_path(env: &Env) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn print_banner(env: &Env) {
|
||||
let _ = eval_str("(prn BANNER)", env);
|
||||
}
|
||||
|
||||
fn get_home_path(env: &Env) -> Result<String, MalErr> {
|
||||
Ok(eval_str("MAL_HOME", env)?.if_string()?.to_string())
|
||||
}
|
||||
@ -39,7 +43,7 @@ pub fn load_home_file(filename: &str, env: &Env, warn: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_file(filename: &str) -> Result<String, MalErr> {
|
||||
pub fn read_file(filename: &str) -> Result<MalStr, MalErr> {
|
||||
let mut file = File::open(filename).map_err(|_| {
|
||||
MalErr::unrecoverable(format!("Failed to open file '{}'", filename).as_str())
|
||||
})?;
|
||||
@ -49,7 +53,7 @@ pub fn read_file(filename: &str) -> Result<String, MalErr> {
|
||||
MalErr::unrecoverable(format!("Failed to read content of '{}'", filename).as_str())
|
||||
})?;
|
||||
|
||||
Ok(content)
|
||||
Ok(content.into())
|
||||
}
|
||||
|
||||
pub fn load_file(filename: &str, env: &Env) -> MalRet {
|
||||
@ -63,7 +67,6 @@ pub fn load_file(filename: &str, env: &Env) -> MalRet {
|
||||
)
|
||||
} // WTF this is becoming ever less like rust and more like lisp, did I really completely skip the file reading?
|
||||
|
||||
extern crate rustyline;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::DefaultEditor;
|
||||
|
||||
@ -71,6 +74,7 @@ pub fn interactive(env: Env) {
|
||||
const HISTORY: &str = ".mal-history";
|
||||
let home = get_home_path(&env).unwrap();
|
||||
let history = home + "/" + HISTORY;
|
||||
eval_str(format!("(def! MAL_HISTORY \"{}\")", history).as_str(), &env).unwrap();
|
||||
|
||||
// Using "Editor" instead of the standard I/O because I hate myself but not this much
|
||||
// TODO: remove unwrap and switch to a better error handling
|
||||
@ -92,7 +96,7 @@ pub fn interactive(env: Env) {
|
||||
// // Read line to compose program input
|
||||
// let mut line = String::new();
|
||||
// io::stdin().read_line(&mut line).unwrap();
|
||||
let line = rl.readline("user> ");
|
||||
let line = rl.readline("; mal> ");
|
||||
|
||||
match line {
|
||||
Ok(line) => {
|
||||
@ -105,7 +109,7 @@ pub fn interactive(env: Env) {
|
||||
|
||||
// Perform rep on whole available input
|
||||
match rep(&parser, &env) {
|
||||
Ok(output) => output.iter().for_each(|el| eprintln!("[{}]> {}", num, el)),
|
||||
Ok(output) => output.iter().for_each(|el| println!("; [{}]> {}", num, el)),
|
||||
Err(error) => {
|
||||
if error.is_recoverable() {
|
||||
// && line != "\n" {
|
||||
|
||||
@ -5,9 +5,9 @@ use crate::types::{escape_str, MalType};
|
||||
|
||||
fn key_str(val: &str) -> MalType {
|
||||
if val.starts_with('ʞ') {
|
||||
M::Key(val.to_string())
|
||||
M::Key(val.into())
|
||||
} else {
|
||||
M::Str(val.to_string())
|
||||
M::Str(val.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,6 +53,14 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
|
||||
),
|
||||
M::Fun(..) => "#<builtin>".to_string(),
|
||||
M::MalFun { .. } => "#<function>".to_string(),
|
||||
M::Atom(sub) => format!("Atom({})", pr_str(&sub.borrow(), print_readably)),
|
||||
M::Ch(c) => {
|
||||
if print_readably {
|
||||
format!("#\\{}", c)
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +69,7 @@ pub fn prt(ast: &MalType) -> String {
|
||||
}
|
||||
|
||||
pub fn print_malfun(sym: &str, params: Rc<MalType>, ast: Rc<MalType>) {
|
||||
println!("{}\t[function]: {}", sym, prt(¶ms));
|
||||
println!("; {}\t[function]: {}", sym, prt(¶ms));
|
||||
ast.as_ref()
|
||||
.if_list()
|
||||
.unwrap_or(&[])
|
||||
|
||||
@ -79,9 +79,9 @@ impl Reader {
|
||||
self.next()?;
|
||||
|
||||
match terminator {
|
||||
")" => Ok(List(MalArgs::new(vector))),
|
||||
"]" => Ok(Vector(MalArgs::new(vector))),
|
||||
"}" => make_map(MalArgs::new(vector)),
|
||||
")" => Ok(List(vector.into())),
|
||||
"]" => Ok(Vector(vector.into())),
|
||||
"}" => make_map(vector.into()),
|
||||
t => Err(MalErr::unrecoverable(
|
||||
format!("Unknown collection terminator: {}", t).as_str(),
|
||||
)),
|
||||
@ -100,15 +100,15 @@ impl Reader {
|
||||
return Ok(Int(tk.parse::<isize>().unwrap()));
|
||||
} else if tk.starts_with('\"') {
|
||||
if tk.len() > 1 && tk.ends_with('\"') {
|
||||
return Ok(Str(unescape_str(tk)));
|
||||
return Ok(Str(unescape_str(tk).into()));
|
||||
}
|
||||
return Err(MalErr::unrecoverable(
|
||||
"End of line reached without closing string",
|
||||
));
|
||||
} else if tk.starts_with(':') {
|
||||
return Ok(Key(format!("ʞ{}", tk)));
|
||||
return Ok(Key(format!("ʞ{}", tk).into()));
|
||||
}
|
||||
Ok(Sym(tk.to_string()))
|
||||
Ok(Sym(tk.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,8 +128,15 @@ impl Reader {
|
||||
// Ugly quote transformation for quote expansion
|
||||
"'" => {
|
||||
self.next()?;
|
||||
Ok(List(Rc::new(vec![
|
||||
MalType::Sym("quote".to_string()),
|
||||
Ok(List(Rc::new([
|
||||
MalType::Sym("quote".into()),
|
||||
self.read_form()?,
|
||||
])))
|
||||
}
|
||||
"@" => {
|
||||
self.next()?;
|
||||
Ok(List(Rc::new([
|
||||
MalType::Sym("deref".into()),
|
||||
self.read_form()?,
|
||||
])))
|
||||
}
|
||||
@ -168,6 +175,8 @@ fn tokenize(input: &str) -> Tokens {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use crate::{
|
||||
reader::read_str,
|
||||
types::{MalMap, MalType as M},
|
||||
@ -262,9 +271,15 @@ mod tests {
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Nil)));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Int(1))));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Bool(true))));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Sym(v) if v == "a")));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Str(v) if v == "s")));
|
||||
assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Key(v) if v == "ʞ:a")));
|
||||
assert!(
|
||||
matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Sym(v) if matches!(v.borrow(), "a")))
|
||||
);
|
||||
assert!(
|
||||
matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Str(v) if matches!(v.borrow(), "s")))
|
||||
);
|
||||
assert!(
|
||||
matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Key(v) if matches!(v.borrow(), "ʞ:a")))
|
||||
);
|
||||
assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable()));
|
||||
@ -311,10 +326,14 @@ mod tests {
|
||||
MalMap::new()
|
||||
}
|
||||
};
|
||||
assert!(matches!(t.get("n"), Some(x) if matches!(x.clone(), M::Nil)));
|
||||
assert!(matches!(t.get("t"), Some(x) if matches!(x.clone(), M::Bool(v) if v)));
|
||||
assert!(matches!(t.get("i"), Some(x) if matches!(x.clone(), M::Int(v) if v == 1)));
|
||||
assert!(matches!(t.get("s"), Some(x) if matches!(x.clone(), M::Str(v) if v == "str")));
|
||||
assert!(matches!(t.get("ʞ:s"), Some(x) if matches!(x.clone(), M::Key(v) if v == "ʞ:sym")));
|
||||
assert!(matches!(t.get("n"), Some(x) if matches!(&x, M::Nil)));
|
||||
assert!(matches!(t.get("t"), Some(x) if matches!(&x, M::Bool(v) if *v)));
|
||||
assert!(matches!(t.get("i"), Some(x) if matches!(&x, M::Int(v) if *v == 1)));
|
||||
assert!(
|
||||
matches!(t.get("s"), Some(x) if matches!(&x, M::Str(v) if matches!(v.borrow(), "str")))
|
||||
);
|
||||
assert!(
|
||||
matches!(t.get("ʞ:s"), Some(x) if matches!(&x, M::Key(v) if matches!(v.borrow(), "ʞ:sym")))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
// input, thus this can be referenced by the previous step without the need
|
||||
// to allocate more memory
|
||||
|
||||
// FIXME: (?) multiple sentences per line, only last is kept
|
||||
|
||||
use crate::env::Env;
|
||||
use crate::eval::eval;
|
||||
use crate::printer::pr_str;
|
||||
|
||||
93
src/types.rs
93
src/types.rs
@ -1,5 +1,10 @@
|
||||
use crate::env::{bin_unpack, Env};
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
use crate::env::{car_cdr, Env};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
pub type MalStr = Rc<str>;
|
||||
pub type MalArgs = Rc<[MalType]>;
|
||||
pub type MalMap = HashMap<MalStr, MalType>;
|
||||
pub type MalRet = Result<MalType, MalErr>;
|
||||
|
||||
// All Mal types should inherit from this
|
||||
#[derive(Clone)]
|
||||
@ -15,14 +20,22 @@ pub enum MalType {
|
||||
env: Env,
|
||||
}, // Used for functions defined within mal
|
||||
// Use Rc so I can now clone like there's no tomorrow
|
||||
Sym(String),
|
||||
Key(String),
|
||||
Str(String),
|
||||
Sym(MalStr),
|
||||
Key(MalStr),
|
||||
Str(MalStr),
|
||||
Ch(char),
|
||||
Int(isize),
|
||||
Bool(bool),
|
||||
Atom(Rc<RefCell<MalType>>),
|
||||
Nil,
|
||||
}
|
||||
|
||||
impl Default for &MalType {
|
||||
fn default() -> Self {
|
||||
&MalType::Nil
|
||||
}
|
||||
}
|
||||
|
||||
impl MalType {
|
||||
pub fn if_number(&self) -> Result<isize, MalErr> {
|
||||
match self {
|
||||
@ -68,36 +81,68 @@ impl MalType {
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label_type(&self) -> MalType {
|
||||
Key(("ʞ:".to_owned()
|
||||
+ match self {
|
||||
M::Nil => "nil",
|
||||
M::Bool(_) => "bool",
|
||||
M::Int(_) => "int",
|
||||
M::Fun(_, _) | M::MalFun { .. } => "lambda",
|
||||
M::Key(_) => "key",
|
||||
M::Str(_) => "string",
|
||||
M::Sym(_) => "symbol",
|
||||
M::List(_) => "list",
|
||||
M::Vector(_) => "vector",
|
||||
M::Map(_) => "map",
|
||||
M::Atom(_) => "atom",
|
||||
M::Ch(_) => "char",
|
||||
})
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
use crate::types::MalType as M;
|
||||
|
||||
// That's a quite chonky function
|
||||
fn mal_eq(args: (&MalType, &MalType)) -> bool {
|
||||
fn mal_compare(args: (&MalType, &MalType)) -> bool {
|
||||
match (args.0, args.1) {
|
||||
(M::Nil, M::Nil) => true,
|
||||
(M::Bool(a), M::Bool(b)) => a == b,
|
||||
(M::Int(a), M::Int(b)) => a == b,
|
||||
(M::Ch(a), M::Ch(b)) => a == b,
|
||||
(M::Key(a), M::Key(b)) | (M::Str(a), M::Str(b)) | (M::Sym(a), M::Sym(b)) => a == b,
|
||||
(M::List(a), M::List(b)) | (M::Vector(a), M::Vector(b)) => {
|
||||
a.len() == b.len() && a.iter().zip(b.iter()).all(mal_eq)
|
||||
a.len() == b.len() && a.iter().zip(b.iter()).all(mal_compare)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mal_comp(args: &[MalType]) -> MalRet {
|
||||
if args.len() != 2 {
|
||||
return Err(MalErr::unrecoverable("Expected 2 arguments"));
|
||||
}
|
||||
Ok(M::Bool(mal_eq(bin_unpack(args)?)))
|
||||
pub fn mal_equals(args: &[MalType]) -> MalRet {
|
||||
Ok(M::Bool(match args.len() {
|
||||
0 => true,
|
||||
_ => {
|
||||
let (car, cdr) = car_cdr(args)?;
|
||||
cdr.iter().all(|x| mal_compare((car, x)))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
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"));
|
||||
pub fn reset_bang(args: &[MalType]) -> MalRet {
|
||||
if args.len() < 2 {
|
||||
return Err(MalErr::unrecoverable("reset requires two arguments"));
|
||||
}
|
||||
let val = &args[1];
|
||||
match &args[0] {
|
||||
M::Atom(sym) => {
|
||||
*std::cell::RefCell::<_>::borrow_mut(sym) = val.clone();
|
||||
Ok(val.clone())
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
format!("{:?} is not an atom", prt(&args[1])).as_str(),
|
||||
)),
|
||||
}
|
||||
Ok(M::Nil)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
@ -143,10 +188,6 @@ impl MalErr {
|
||||
}
|
||||
}
|
||||
|
||||
pub type MalArgs = Rc<Vec<MalType>>;
|
||||
pub type MalMap = HashMap<String, MalType>;
|
||||
pub type MalRet = Result<MalType, MalErr>;
|
||||
|
||||
use crate::printer::prt;
|
||||
use MalType::{Key, Map, Str};
|
||||
|
||||
@ -161,7 +202,7 @@ pub fn make_map(list: MalArgs) -> MalRet {
|
||||
match &list[i] {
|
||||
Key(k) | Str(k) => {
|
||||
let v = &list[i + 1];
|
||||
map.insert(k.to_string(), v.clone());
|
||||
map.insert(k.clone(), v.clone());
|
||||
}
|
||||
_ => {
|
||||
return Err(MalErr::unrecoverable(
|
||||
@ -196,16 +237,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() {
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -2,5 +2,7 @@
|
||||
(assert (ok? 1))
|
||||
(assert-eq nil (ok? (1)))
|
||||
(assert-eq true (ok? 1))
|
||||
(ok? (assert true))
|
||||
(not (ok? (assert nil)))
|
||||
(assert (ok? (assert true)))
|
||||
(assert (not (ok? (assert nil))))
|
||||
(assert (not (ok? (assert-fail '1))))
|
||||
(assert-fail '(1))
|
||||
|
||||
19
tests/atoms.mal
Normal file
19
tests/atoms.mal
Normal file
@ -0,0 +1,19 @@
|
||||
; atom?
|
||||
(assert (atom? (atom 1)))
|
||||
(assert (not (atom? 1)))
|
||||
|
||||
; deref
|
||||
(assert-eq (deref (atom 1)) 1)
|
||||
|
||||
; @ macro
|
||||
(assert-eq @(atom 1) 1)
|
||||
|
||||
(def! ATOM (atom 1))
|
||||
|
||||
; reset!
|
||||
(reset! ATOM 2)
|
||||
(assert-eq @ATOM 2)
|
||||
|
||||
; swap!
|
||||
(swap! ATOM (fn* [x] (* x 2)))
|
||||
(assert-eq @ATOM 4)
|
||||
7
tests/car-cdr.mal
Normal file
7
tests/car-cdr.mal
Normal file
@ -0,0 +1,7 @@
|
||||
;; Test car
|
||||
(assert-eq (car '(1 2 3)) 1)
|
||||
(assert-eq (car '()) nil)
|
||||
|
||||
;; Test cdr
|
||||
(assert-eq (cdr '(1 2 3)) '(2 3))
|
||||
(assert-eq (cdr '()) '())
|
||||
4
tests/fil.mal
Normal file
4
tests/fil.mal
Normal file
@ -0,0 +1,4 @@
|
||||
; filter with builtin function
|
||||
(assert-eq (list '(1) '(2) '(3)) (filter car (list '(1) '() '(2) '() '(3))))
|
||||
; filter with lambda function
|
||||
(assert-eq (list 1 2 3) (filter int? (list 1 "string" 2 'symbol 3 :label)))
|
||||
58
tests/forms.mal
Normal file
58
tests/forms.mal
Normal file
@ -0,0 +1,58 @@
|
||||
; def!
|
||||
(assert (not (def! FALSE nil)))
|
||||
(assert (def! TRUE true))
|
||||
|
||||
(println :def)
|
||||
(def! value-0 0)
|
||||
(assert-eq value-0 0)
|
||||
(assert-eq
|
||||
((fn* [x]
|
||||
(def! value-1 1) (+ x value-1))
|
||||
2)
|
||||
3)
|
||||
|
||||
(assert-fail '(def!))
|
||||
(assert-fail '(def! a))
|
||||
|
||||
(println :do)
|
||||
; do
|
||||
(assert-eq
|
||||
(do 1 2 3)
|
||||
3)
|
||||
(assert-eq
|
||||
(do
|
||||
(def! do-test-1 1)
|
||||
(def! do-test-2 2))
|
||||
2)
|
||||
(assert-eq 2 do-test-2)
|
||||
(let* [a 1 b 2]
|
||||
(def! do-test-3 3)
|
||||
(assert-eq do-test-3 3))
|
||||
|
||||
(assert-eq (do) nil)
|
||||
|
||||
; if
|
||||
(assert (if true 1))
|
||||
(assert (not (if false 1)))
|
||||
(assert (not (if false nil)))
|
||||
(assert (if false nil 1))
|
||||
(assert (if true 1 nil))
|
||||
(assert-fail '(if true))
|
||||
(assert-fail '(if false))
|
||||
(assert-fail '(if))
|
||||
|
||||
; let*
|
||||
(assert-eq (let* [let-a 1
|
||||
let-b 2]
|
||||
(assert-eq let-a 1)
|
||||
(assert-eq let-b 2)
|
||||
4) 4)
|
||||
(assert-fail 'let-a)
|
||||
|
||||
; fn*
|
||||
(def! f (fn* [x] (def! func-var x) (+ func-var 1)))
|
||||
(assert-fail 'func-var)
|
||||
(assert-eq (f 1) 2)
|
||||
(assert-fail '(f 1 2))
|
||||
(assert-fail '(1 2 3))
|
||||
|
||||
@ -5,5 +5,5 @@
|
||||
; empty?
|
||||
(assert (empty? ()))
|
||||
(assert (empty? (list)))
|
||||
(assert (not (empty? ())))
|
||||
(assert (not (empty? (list (1 2 3)))))
|
||||
(assert (not (empty? '(1 2 3))))
|
||||
(assert (not (empty? (list 1 2 3))))
|
||||
|
||||
5
tests/map.mal
Normal file
5
tests/map.mal
Normal file
@ -0,0 +1,5 @@
|
||||
; builtin map
|
||||
(assert-eq '((1) (2) (3)) (map list '(1 2 3)))
|
||||
|
||||
; lambda map
|
||||
(assert-eq '(2 3 4) (map (fn* [x] (+ x 1)) '(1 2 3)))
|
||||
Reference in New Issue
Block a user