diff --git a/src/core.rs b/src/core.rs index e942cff..f36f19e 100644 --- a/src/core.rs +++ b/src/core.rs @@ -29,33 +29,42 @@ macro_rules! env_init { }; } -use crate::printer::prt; +use crate::parse_tools::read_file; +use crate::printer::pr_str; +use crate::reader::{read_str, Reader}; use crate::types::MalType::{Bool, Fun, Int, List, Nil, Str}; use crate::types::{mal_assert, mal_assert_eq, mal_comp, MalArgs}; pub fn ns_init() -> Env { - env_init!(None, - "test" => Fun(|_| Ok(Str("This is a test function".to_string())), "Just a test function"), - "exit" => Fun(mal_exit, "Quits the program with specified status"), - "+" => 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, a), "Returns the division of the arguments"), - ">" => Fun(|a| comparison_op(|a, b| a > b, a), "Returns true if the arguments are in strictly descending order, 'nil' otherwise"), - "<" => Fun(|a| comparison_op(|a, b| a < b, a), "Returns true if the arguments are in strictly ascending order, 'nil' otherwise"), - ">=" => Fun(|a| comparison_op(|a, b| a >= b, a), "Returns true if the arguments are in descending order, 'nil' otherwise"), - "<=" => Fun(|a| comparison_op(|a, b| a <= b, a), "Returns true if the arguments are in ascending order, 'nil' otherwise"), - "prn" => Fun(|a| {a.iter().for_each(|a| print!("{} ", prt(a))); 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"), - "empty?" => Fun(|a| Ok(Bool(car(a)?.if_list()?.is_empty())), "Return true if the first parameter is an empty list, false otherwise, returns an error if the element is not a list"), - "count" => Fun(|a| Ok(Int(car(a)?.if_list()?.len() as isize)), "Return the number of elements in the first argument"), - "and" => Fun(|a| Ok(Bool(a.iter().all(|a| !matches!(a, Nil | Bool(false))))), "Returns false if at least one of the arguments is 'false' or 'nil', true otherwise"), - "or" => Fun(|a| Ok(Bool(a.iter().any(|a| !matches!(a, Nil | Bool(false))))), "Returns false if all the arguments are 'false' or 'nil', true otherwise"), - "xor" => Fun(|a| Ok(Bool(a.iter().filter(|a| !matches!(a, Nil | Bool(false))).count() == 1)), "Returns true if one of the arguments is different from 'nil' or 'false', false otherwise"), - "not" => Fun(|a| Ok(Bool(matches!(car(a)?, Nil | Bool(false)))), "Negate 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"), - "assert" => Fun(mal_assert, "Return an error if assertion fails"), - "assert-eq" => Fun(mal_assert_eq, "Return an error if arguments are not the same") - ) + let env_t = env_init!(None, + "test" => Fun(|_| Ok(Str("This is a test function".to_string())), "Just a test function"), + "exit" => Fun(mal_exit, "Quits the program with specified status"), + "+" => 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, a), "Returns the division of the arguments"), + ">" => Fun(|a| comparison_op(|a, b| a > b, a), "Returns true if the arguments are in strictly descending order, 'nil' otherwise"), + "<" => Fun(|a| comparison_op(|a, b| a < b, a), "Returns true if the arguments are in strictly ascending order, 'nil' otherwise"), + ">=" => Fun(|a| comparison_op(|a, b| a >= b, a), "Returns true if the arguments are in descending order, 'nil' otherwise"), + "<=" => Fun(|a| comparison_op(|a, b| a <= b, a), "Returns true if the arguments are in ascending order, 'nil' otherwise"), + "pr-str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, true)).collect::>().join(" "))), "Print readably all arguments"), + "str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::>().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"), + "empty?" => Fun(|a| Ok(Bool(car(a)?.if_list()?.is_empty())), "Return true if the first parameter is an empty list, false otherwise, returns an error if the element is not a list"), + "count" => Fun(|a| Ok(Int(car(a)?.if_list()?.len() as isize)), "Return the number of elements in the first argument"), + "and" => Fun(|a| Ok(Bool(a.iter().all(|a| !matches!(a, Nil | Bool(false))))), "Returns false if at least one of the arguments is 'false' or 'nil', true otherwise"), + "or" => Fun(|a| Ok(Bool(a.iter().any(|a| !matches!(a, Nil | Bool(false))))), "Returns false if all the arguments are 'false' or 'nil', true otherwise"), + "xor" => Fun(|a| Ok(Bool(a.iter().filter(|a| !matches!(a, Nil | Bool(false))).count() == 1)), "Returns true if one of the arguments is different from 'nil' or 'false', false otherwise"), + // "not" defined inside mal itself + // "not" => Fun(|a| Ok(Bool(matches!(car(a)?, Nil | Bool(false)))), "Negate 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"), + "assert" => Fun(mal_assert, "Return an error if assertion fails"), + "assert-eq" => Fun(mal_assert_eq, "Return an error if arguments are not the same"), + "read-string" => Fun(|a| read_str(&Reader::new().push(car(a)?.if_string()?)), "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") + ); + env_t } diff --git a/src/env.rs b/src/env.rs index d3c5672..bbe114b 100644 --- a/src/env.rs +++ b/src/env.rs @@ -6,7 +6,20 @@ use std::rc::Rc; #[derive(Clone, Debug)] pub struct EnvType { data: RefCell, - outer: Option, + pub outer: Option, +} + +impl EnvType { + pub fn keys(&self) -> String { + let mut keys = self + .data + .borrow() + .iter() + .map(|(k, _)| k.clone()) + .collect::>(); + keys.sort_unstable(); + keys.join(" ") + } } pub type Env = Rc; @@ -158,7 +171,7 @@ fn first(list: &[MalType]) -> &[MalType] { fn last(list: &[MalType]) -> Result<&MalType, MalErr> { match list.len() { 0 => Err(MalErr::unrecoverable("Mi sono cacato le mutande")), - _ => Ok(&list[0]), + _ => Ok(&list[list.len() - 1]), } } diff --git a/src/eval.rs b/src/eval.rs index 6854077..0dd4889 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,4 @@ -use crate::env::{call_func, car_cdr, CallFunc, CallRet}; +use crate::env::{self, call_func, car_cdr, CallFunc, CallRet}; use crate::env::{env_get, env_new, env_set}; use crate::env::{first_last, Env}; use crate::printer::prt; @@ -104,16 +104,28 @@ fn fn_star_form(list: &[MalType], env: Env) -> MalRet { use crate::printer::print_malfun; pub fn help_form(list: &[MalType], env: Env) -> MalRet { - let (sym, _) = car_cdr(list)?; - let sym_str = sym.if_symbol()?; - match eval(sym, env.clone())? { - M::Fun(_, desc) => println!("{}\t[builtin]: {}", sym_str, desc), - M::MalFun { params, ast, .. } => print_malfun(sym_str, params, ast), - _ => println!("{}\t[symbol]: {}", sym_str, prt(&env_get(&env, sym_str)?)), + if list.is_empty() { + eprintln!("\t[defined symbols]:\n{}", env.keys()) + } else { + let (sym, _) = car_cdr(list)?; + let sym_str = sym.if_symbol()?; + match eval(sym, env.clone())? { + M::Fun(_, desc) => println!("{}\t[builtin]: {}", sym_str, desc), + M::MalFun { params, ast, .. } => print_malfun(sym_str, params, ast), + _ => eprintln!("{}\t[symbol]: {}", sym_str, prt(&env_get(&env, sym_str)?)), + } } Ok(M::Bool(true)) } +pub fn outermost(env: &Env) -> Env { + let mut env = env; + while let Some(ref e) = env.outer { + env = e; + } + env.clone() +} + /// Intermediate function to discern special forms from defined symbols pub fn eval(ast: &MalType, env: Env) -> MalRet { let mut ast = ast.clone(); @@ -130,11 +142,17 @@ pub fn eval(ast: &MalType, env: Env) -> MalRet { M::Sym(sym) if sym == "if" => ast = if_form(cdr, env.clone())?, 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()), + // Special form, sad + // Bruh, is basically double eval + M::Sym(sym) if sym == "eval" => { + ast = eval(env::car(cdr)?, env.clone())?; + // Climb to the outermost environment (The repl env) + env = outermost(&env); + } // Filter out special forms // "apply"/invoke _ => { - let apply_list = - &eval_ast(&M::List(MalArgs::new(list.to_vec())), env.clone())?; + let apply_list = &eval_ast(&ast, env.clone())?; let eval_ret = eval_func(apply_list)?; match eval_ret { diff --git a/src/main.rs b/src/main.rs index e1544c2..bc6b4e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,16 +8,21 @@ mod mal_tests; mod parse_tools; mod printer; mod reader; -mod step5_tco; +mod step6_file; mod types; use core::ns_init; -use parse_tools::{interactive, load_file}; +use parse_tools::{interactive, load_conf, load_core, load_file}; fn main() { // Initialize ns environment let reply_env = ns_init(); + load_core(&reply_env); + + // Load config files ($MAL_HOME/config.mal, or default $HOME/.config/mal/config.mal) + load_conf(&reply_env); + // load all files passed as arguments args().collect::>()[1..].iter().for_each(|f| { if let Err(e) = load_file(f, &reply_env) { diff --git a/src/mal_tests/mod.rs b/src/mal_tests/mod.rs index 75bc4bb..72e8b73 100644 --- a/src/mal_tests/mod.rs +++ b/src/mal_tests/mod.rs @@ -5,8 +5,11 @@ mod functional { ($file:expr) => {{ use crate::core::ns_init; use crate::load_file; + use crate::parse_tools::load_core; + let env = ns_init(); + load_core(&env); assert!(matches!( - load_file(format!("tests/{}.mal", $file).as_str(), &ns_init()), + load_file(format!("tests/{}.mal", $file).as_str(), &env), Ok(_) )); }}; diff --git a/src/parse_tools.rs b/src/parse_tools.rs index 01f1fa6..779b940 100644 --- a/src/parse_tools.rs +++ b/src/parse_tools.rs @@ -1,19 +1,65 @@ use crate::env::Env; -use crate::reader::Reader; -use crate::step5_tco::rep; +use crate::eval::eval; +use crate::reader::{read_str, Reader}; +use crate::step6_file::rep; use crate::types::{MalErr, MalRet, MalType::Nil}; use regex::Regex; +use std::env; use std::fs::File; -use std::io::{BufRead, BufReader}; +use std::io::{BufRead, BufReader, Read}; +use std::path::Path; use std::process::exit; +pub fn load_core(env: &Env) { + eval_str("(def! not (fn* (x) (if x nil true)))", &env).unwrap(); + eval_str( + "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", + &env, + ) + .unwrap(); +} + +fn eval_str(line: &str, env: &Env) -> MalRet { + eval(&read_str(Reader::new().push(line))?, env.clone()) +} + +pub fn load_conf(work_env: &Env) { + const CONFIG: &str = "config.mal"; + let home = match env::var("MAL_HOME") { + Ok(s) => s, + Err(_) => env::var("HOME").unwrap() + "/.config/mal", + }; + let config = home + "/" + CONFIG; + + if Path::new(&config).exists() { + match load_file(&config, work_env) { + Err(e) => eprintln!("{}", e.message()), + _ => (), + } + } +} + +pub fn read_file(filename: &str) -> Result { + let mut file = File::open(filename).map_err(|_| { + MalErr::unrecoverable(format!("Failed to open file '{}'", filename).as_str()) + })?; + let mut content = String::new(); + + file.read_to_string(&mut content).map_err(|_| { + MalErr::unrecoverable(format!("Failed to read content of '{}'", filename).as_str()) + })?; + + Ok(content) +} + pub fn load_file(filename: &str, env: &Env) -> MalRet { let file_desc = File::open(filename); let file = match file_desc { Ok(file) => file, Err(_) => { - println!("Unable to open file: '{}'", filename); - exit(1) + return Err(MalErr::unrecoverable( + format!("; WARNING: Unable to open file: {}", filename).as_str(), + )); } }; let reader = BufReader::new(file); @@ -103,13 +149,13 @@ pub fn interactive(env: Env) { // Perform rep on whole available input match rep(&parser, &env) { - Ok(output) => output.iter().for_each(|el| println!("[{}]> {}", num, el)), + Ok(output) => output.iter().for_each(|el| eprintln!("[{}]> {}", num, el)), Err(error) => { if error.is_recoverable() { // && line != "\n" { continue; } - println!("; [{}]> Error @ {}", num, error.message()); + eprintln!("; [{}]> Error @ {}", num, error.message()); } } num += 1; diff --git a/src/reader.rs b/src/reader.rs index 2c3c7ac..d6b1b33 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -25,10 +25,11 @@ impl Reader { } } - pub fn push(&self, input: &str) { + pub fn push(&self, input: &str) -> &Self { self.ptr.set(0); // reset the state of the parser and push the additional strings self.tokens.borrow_mut().append(&mut tokenize(input)); + self } pub fn clear(&self) { diff --git a/src/step6_file.rs b/src/step6_file.rs new file mode 100644 index 0000000..c4de798 --- /dev/null +++ b/src/step6_file.rs @@ -0,0 +1,41 @@ +// Structure the main functions of the interpreter +// +// For now just act as an echo, note that each function should not modify the +// input, thus this can be referenced by the previous step without the need +// to allocate more memory + +use crate::env::Env; +use crate::eval::eval; +use crate::printer::pr_str; +use crate::reader::{read_str, Reader}; +use crate::types::{MalErr, MalRet, MalType}; + +#[allow(non_snake_case)] +/// Read input and generate an ast +fn READ(input: &Reader) -> MalRet { + read_str(input).map_err(|err| MalErr::new(format!("READ: {}", err.message()), err.severity())) +} + +#[allow(non_snake_case)] +/// Evaluate the generated ast +fn EVAL(ast: MalType, env: Env) -> MalRet { + eval(&ast, env).map_err(|err| MalErr::new(format!("EVAL: {}", err.message()), err.severity())) +} + +#[allow(non_snake_case)] +/// Print out the result of the evaluation +fn PRINT(output: MalType) -> String { + pr_str(&output, true) +} + +pub fn rep(reader: &Reader, env: &Env) -> Result, MalErr> { + let mut ret_str = Vec::new(); + loop { + let ast = READ(reader)?; + let out = EVAL(ast, env.clone())?; + ret_str.push(PRINT(out)); + if reader.ended() { + break Ok(ret_str); + } + } +} diff --git a/src/types.rs b/src/types.rs index b1f9787..3648279 100644 --- a/src/types.rs +++ b/src/types.rs @@ -50,6 +50,15 @@ impl MalType { )), } } + + pub fn if_string(&self) -> Result<&str, MalErr> { + match self { + Self::Str(sym) => Ok(sym), + _ => Err(MalErr::unrecoverable( + format!("{:?} is not a string", prt(self)).as_str(), + )), + } + } } use crate::types::MalType as M; @@ -120,12 +129,13 @@ pub fn mal_assert_eq(args: &[MalType]) -> MalRet { } } -#[derive(PartialEq, Clone, Copy)] +#[derive(PartialEq, Clone, Copy, Debug)] pub enum Severity { Recoverable, Unrecoverable, } +#[derive(Debug)] pub struct MalErr { message: String, severity: Severity,