Adding some info functions and tests

This commit is contained in:
teo3300
2024-04-16 09:35:35 +09:00
parent 5c9aa39750
commit 0ec8923abc
10 changed files with 108 additions and 9 deletions

View File

@ -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=="
"<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"
"#alias: h"
"#returns: NIL"))
(def! find (fn* [substring...] "==SPECIAL FORM=="
"print all the known symbols partially matching <substring> in"
"the current environment"
"#alias f"
"#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"
"; (find [pattern...]) : list symbols matching all patterns\n"
"; (help <symbol>) : print information about a symbol\n"
";\n"
"; enjoy ^.^\n"))
(println BANNER)

View File

@ -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),

View File

@ -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()),

View File

@ -45,4 +45,14 @@ mod functional {
fn forms() {
test!("forms")
}
#[test]
fn lists() {
test!("lists")
}
#[test]
fn atoms() {
test!("atoms")
}
}

View File

@ -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" {

View File

@ -53,6 +53,7 @@ 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, print_readably)),
}
}
@ -61,7 +62,7 @@ pub fn prt(ast: &MalType) -> String {
}
pub fn print_malfun(sym: &str, params: Rc<MalType>, ast: Rc<MalType>) {
println!("{}\t[function]: {}", sym, prt(&params));
println!("; {}\t[function]: {}", sym, prt(&params));
ast.as_ref()
.if_list()
.unwrap_or(&[])

View File

@ -25,6 +25,7 @@ pub enum MalType {
Str(MalStr),
Int(isize),
Bool(bool),
Atom(Rc<MalType>),
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())
}

6
tests/atoms.mal Normal file
View File

@ -0,0 +1,6 @@
; atom?
(assert (atom? (atom 1)))
(assert (not (atom? 1)))
; deref
(assert-eq (deref (atom 1)) 1)

View File

@ -31,7 +31,6 @@
(assert-eq (do) nil)
(println :ififififififififif)
; if
(assert (if true 1))
(assert (not (if false 1)))

View File

@ -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))))