diff --git a/src/core.rs b/src/core.rs index 87c4c3a..94c1752 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,3 +1,5 @@ +use std::env; + use crate::env::{arithmetic_op, car, comparison_op, env_new, env_set, mal_exit, Env}; // This is the first time I implement a macro, and I'm copying it @@ -65,6 +67,11 @@ pub fn ns_init() -> Env { // Since the READ errors are Recoverable, when encountering one the reader continues appending to the previous status, // making unreasonable output, to solve this "upgrade" any kind of error in the inner "read_str" to an unrecoverable one, so the reader breaks the cycle "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") + "slurp" => Fun(|a| Ok(Str(read_file(car(a)?.if_string()?)?)), "Read a file and return the content as a string"), + // Retrieve environment variables, because why not + "env" => Fun(|a| match env::var(car(a)?.if_string()?) { + Ok(s) => Ok(Str(s)), + _ => Ok(Nil), + }, "Retrieve environment variable") ) } diff --git a/src/eval.rs b/src/eval.rs index d1377df..bf23459 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -149,6 +149,14 @@ pub fn eval(ast: &MalType, env: Env) -> MalRet { M::Sym(sym) if sym == "fn*" => return fn_star_form(cdr, env.clone()), M::Sym(sym) if sym == "help" => return help_form(cdr, env.clone()), M::Sym(sym) if sym == "find" => return find_form(cdr, env.clone()), + // Oh God, what have I done + M::Sym(sym) if sym == "quote" => return Ok(car_cdr(cdr)?.0.clone()), + M::Sym(sym) if sym == "ok?" => { + return match eval(car_cdr(cdr)?.0, env.clone()) { + Err(_) => Ok(M::Nil), + _ => Ok(M::Bool(true)), + } + } // Special form, sad // Bruh, is basically double eval M::Sym(sym) if sym == "eval" => { diff --git a/src/mal_tests/mod.rs b/src/mal_tests/mod.rs index 0b980ad..5b12ee4 100644 --- a/src/mal_tests/mod.rs +++ b/src/mal_tests/mod.rs @@ -18,12 +18,7 @@ mod functional { #[test] fn assert_fail() { - use crate::core::ns_init; - use crate::load_file; - assert!(matches!( - load_file("tests/assert_fail.mal", &ns_init()), - Err(_) - )) + test!("assert") } #[test] diff --git a/src/parse_tools.rs b/src/parse_tools.rs index b93f241..c8d33b5 100644 --- a/src/parse_tools.rs +++ b/src/parse_tools.rs @@ -1,9 +1,8 @@ -use crate::env::{env_get, Env}; +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 std::env; use std::fs::File; use std::io::Read; use std::path::Path; @@ -14,29 +13,20 @@ fn eval_str(line: &str, env: &Env) -> MalRet { } pub fn set_home_path(env: &Env) { - let home = match env::var("MAL_HOME") { - Ok(s) => s, - Err(_) => env::var("HOME").unwrap() + "/.config/mal", - }; - - if !Path::new(&home).exists() { - eprintln!("; WARNING: MAL_HOME: \"{}\" does not exist", home); - } - - // Add config path to mal - eval_str(format!("(def! MAL_HOME \"{home}\")").as_str(), env).unwrap(); + eval_str( + "(or (def! MAL_HOME (env \"MAL_HOME\")) + (def! MAL_HOME (str (env \"HOME\") \"/.config/mal\")))", + env, + ) + .unwrap(); } -fn get_home_path(env: &Env) -> String { - env_get(env, "MAL_HOME") - .unwrap() - .if_string() - .unwrap() - .to_string() +fn get_home_path(env: &Env) -> Result { + Ok(eval_str("MAL_HOME", env)?.if_string()?.to_string()) } pub fn load_home_file(filename: &str, env: &Env, warn: bool) { - let full_filename = get_home_path(env) + "/" + filename; + let full_filename = get_home_path(env).unwrap_or_else(|_| "".to_string()) + "/" + filename; if Path::new(&full_filename).exists() { if let Err(e) = load_file(&full_filename, env) { @@ -77,12 +67,13 @@ use rustyline::error::ReadlineError; use rustyline::DefaultEditor; pub fn interactive(env: Env) { - const HISTORY_PATH: &str = ".mal-history"; + const HISTORY: &str = ".mal-history"; + let home = get_home_path(&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 let mut rl = DefaultEditor::new().unwrap(); - if rl.load_history(HISTORY_PATH).is_err() { + if rl.load_history(&(home + "/" + HISTORY)).is_err() { eprintln!("; Failed to load history"); } @@ -105,7 +96,7 @@ pub fn interactive(env: Env) { Ok(line) => { // TODO: should handle this in a different way rl.add_history_entry(&line).unwrap(); - rl.save_history(HISTORY_PATH).unwrap(); + rl.save_history(HISTORY).unwrap(); parser.push(&line); diff --git a/src/printer.rs b/src/printer.rs index 90b80a9..e546ff5 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -13,7 +13,7 @@ fn key_str(val: &str) -> MalType { pub fn pr_str(ast: &MalType, print_readably: bool) -> String { match ast { - M::Nil => "nil".to_string(), + M::Nil => "NIL".to_string(), M::Sym(sym) => sym.to_string(), M::Key(sym) => sym[2..].to_string(), M::Int(val) => val.to_string(), diff --git a/src/reader.rs b/src/reader.rs index 5f6b7b3..29287a5 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,4 +1,5 @@ use std::cell::{Cell, RefCell}; +use std::rc::Rc; // Specyfy components in "types" use crate::types::*; @@ -120,6 +121,14 @@ impl Reader { "(" => self.read_list(")"), "[" => self.read_list("]"), "{" => self.read_list("}"), + // Ugly quote transformation for quote expansion + "'" => { + self.next()?; + Ok(List(Rc::new(vec![ + MalType::Sym("quote".to_string()), + self.read_form()?, + ]))) + } _ => self.read_atom(), } } diff --git a/src/step6_file.rs b/src/step6_file.rs index c4de798..86a5e47 100644 --- a/src/step6_file.rs +++ b/src/step6_file.rs @@ -30,12 +30,10 @@ fn PRINT(output: MalType) -> String { pub fn rep(reader: &Reader, env: &Env) -> Result, MalErr> { let mut ret_str = Vec::new(); - loop { + while !reader.ended() { let ast = READ(reader)?; let out = EVAL(ast, env.clone())?; ret_str.push(PRINT(out)); - if reader.ended() { - break Ok(ret_str); - } } + Ok(ret_str) } diff --git a/src/types.rs b/src/types.rs index fdd185f..68ddf5c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -72,6 +72,7 @@ impl MalType { use crate::types::MalType as M; +// That's a quite chonky function fn mal_eq(a: &MalType, b: &[MalType]) -> MalRet { Ok(M::Bool(match a { M::Nil => b.iter().all(|el| matches!(el, M::Nil)), diff --git a/tests/assert.mal b/tests/assert.mal new file mode 100644 index 0000000..1421cbf --- /dev/null +++ b/tests/assert.mal @@ -0,0 +1,5 @@ +(assert true) +(assert (ok? 1)) +(assert-eq nil (ok? (1))) +(ok? (assert true)) +(not (ok? (assert nil))) \ No newline at end of file diff --git a/tests/assert_fail.mal b/tests/assert_fail.mal deleted file mode 100644 index ec3f2d1..0000000 --- a/tests/assert_fail.mal +++ /dev/null @@ -1 +0,0 @@ -(assert nil) \ No newline at end of file