diff --git a/core/core.mal b/core/core.mal index c654c78..82258e5 100644 --- a/core/core.mal +++ b/core/core.mal @@ -39,6 +39,9 @@ (def! list? (fn* [a] (= (type a) :list))) +(def! atom? (fn* [a] + (= (type a) :atom))) + (def! empty? (fn* [l] (= (count l) 0))) @@ -61,3 +64,68 @@ ;; 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==" + ": Sym" + "assign to in the current environment" + "#returns: ")) + +(def! let* (fn* [binding statement...] "==SPECIAL FORM==" + ": Vec" + "create a new environment and assign values to symbols according" + "to the vector then evaluate each " + "#returns: result of the last evaluation")) + +(def! do (fn* [statement...] "==SPECIAL FORM==" + "evaluate each in the current environment" + "#returns: result of the last evalutaion")) + +(def! if (fn* [condition if-true if-false] "==SPECIAL FORM==" + "first evaluate , 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 , evaluates each" + " : and returns the last evaluation's result")) + +(def! help (fn* [symbol] "==SPECIAL FORM==" + "symbol: Sym" + "display an helper f or the specified symbol" + "#alias: h" + "#returns: NIL")) + +(def! find (fn* [substring...] "==SPECIAL FORM==" + "print all the known symbols partially matching in" + "the current environment" + "#alias f" + "#returns: NIL")) + +(def! quote (fn* [statement] "==SPECIAL FORM==" + "prevents from being evaluated, it's possible to use" + "the ' symbol: 'sym is equivalent to (quote sym)")) + +(def! ok? (fn* [statement] "==SPECIAL FORM==" + "evaluate " + "#returns: true if evaluation succeeds, NIL otherwise")) + +(def! eval (fn* [statement] "==SPECIAL FORM==" + "evaluate " + "#returns: the result of the evaluation")) + +(def! BANNER + (str + "; rust-mal: a toy lisp interpreter written in rust\n" + "; (find [pattern...]) : list symbols matching all patterns\n" + "; (help ) : print information about a symbol\n" + ";\n" + "; enjoy ^.^\n")) + +(println BANNER) diff --git a/src/core.rs b/src/core.rs index 4f88fce..9a5a871 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,5 @@ use std::env; +use std::rc::Rc; use crate::env::{any_zero, arithmetic_op, car, comparison_op, env_new, env_set, mal_exit, Env}; @@ -34,7 +35,7 @@ macro_rules! env_init { use crate::parse_tools::read_file; use crate::printer::pr_str; use crate::reader::{read_str, Reader}; -use crate::types::MalType::{Fun, Int, List, Nil, Str}; +use crate::types::MalType::{Atom, Fun, Int, List, Nil, Str}; use crate::types::{mal_assert, mal_equals, MalErr}; pub fn ns_init() -> Env { @@ -62,6 +63,8 @@ pub fn ns_init() -> Env { "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(car(a)?.clone()))), "Return an atom pointing to the given arg"), + "deref" => Fun(|a| Ok(car(a)?.if_atom()?.clone()), "Return the content of the atom argumet"), "env" => Fun(|a| match env::var(car(a)?.if_string()?) { Ok(s) => Ok(Str(s.into())), _ => Ok(Nil), diff --git a/src/eval.rs b/src/eval.rs index b1feba5..3f77ca2 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -21,6 +21,7 @@ forms!(NAME_DEF : "def!", NAME_FN : "fn*", NAME_FN_ALT : "λ", NAME_HELP : "help", + NAME_HELP_ALT: "h", NAME_FIND : "find", NAME_QUOTE : "quote", NAME_OK : "ok?", @@ -188,7 +189,7 @@ pub fn eval(ast: &MalType, env: Env) -> MalRet { NAME_FN | NAME_FN_ALT /* :) */ => { return fn_star_form(args, env.clone()) } - NAME_HELP => return help_form(args, env.clone()), + NAME_HELP | NAME_HELP_ALT => 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()), diff --git a/src/mal_tests/mod.rs b/src/mal_tests/mod.rs index 29decb8..62d8b2d 100644 --- a/src/mal_tests/mod.rs +++ b/src/mal_tests/mod.rs @@ -45,4 +45,14 @@ mod functional { fn forms() { test!("forms") } + + #[test] + fn lists() { + test!("lists") + } + + #[test] + fn atoms() { + test!("atoms") + } } diff --git a/src/parse_tools.rs b/src/parse_tools.rs index 87058a9..129649a 100644 --- a/src/parse_tools.rs +++ b/src/parse_tools.rs @@ -92,7 +92,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("; user> "); match line { Ok(line) => { @@ -105,7 +105,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" { diff --git a/src/printer.rs b/src/printer.rs index 9247b3a..459c128 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -53,6 +53,7 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String { ), M::Fun(..) => "#".to_string(), M::MalFun { .. } => "#".to_string(), + M::Atom(sub) => format!("Atom({})", pr_str(sub, print_readably)), } } @@ -61,7 +62,7 @@ pub fn prt(ast: &MalType) -> String { } pub fn print_malfun(sym: &str, params: Rc, ast: Rc) { - println!("{}\t[function]: {}", sym, prt(¶ms)); + println!("; {}\t[function]: {}", sym, prt(¶ms)); ast.as_ref() .if_list() .unwrap_or(&[]) diff --git a/src/types.rs b/src/types.rs index ccbfc53..f93952e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -25,6 +25,7 @@ pub enum MalType { Str(MalStr), Int(isize), Bool(bool), + Atom(Rc), Nil, } @@ -74,6 +75,15 @@ impl MalType { } } + pub fn if_atom(&self) -> Result<&MalType, MalErr> { + match self { + Self::Atom(sym) => Ok(sym), + _ => Err(MalErr::unrecoverable( + format!("{:?} is not an atom", prt(self)).as_str(), + )), + } + } + pub fn label_type(&self) -> MalType { Key(match self { M::Nil => "ʞ:nil", @@ -83,9 +93,10 @@ impl MalType { M::Key(_) => "ʞ:key", M::Str(_) => "ʞ:string", M::Sym(_) => "ʞ:symbol", - M::List(_) => "ʞ:ist", + M::List(_) => "ʞ:list", M::Vector(_) => "ʞ:vector", M::Map(_) => "ʞ:map", + M::Atom(_) => "ʞ:atom", } .into()) } diff --git a/tests/atoms.mal b/tests/atoms.mal new file mode 100644 index 0000000..63031de --- /dev/null +++ b/tests/atoms.mal @@ -0,0 +1,6 @@ +; atom? +(assert (atom? (atom 1))) +(assert (not (atom? 1))) + +; deref +(assert-eq (deref (atom 1)) 1) diff --git a/tests/forms.mal b/tests/forms.mal index ae8cdf1..48ba3a2 100644 --- a/tests/forms.mal +++ b/tests/forms.mal @@ -31,7 +31,6 @@ (assert-eq (do) nil) -(println :ififififififififif) ; if (assert (if true 1)) (assert (not (if false 1))) diff --git a/tests/lists.mal b/tests/lists.mal index 39cf24e..21cade9 100644 --- a/tests/lists.mal +++ b/tests/lists.mal @@ -5,5 +5,5 @@ ; empty? (assert (empty? ())) (assert (empty? (list))) -(assert (not (empty? ()))) -(assert (not (empty? (list (1 2 3))))) \ No newline at end of file +(assert (not (empty? '(1 2 3)))) +(assert (not (empty? (list 1 2 3))))