Adding quotation, semiquote macro and env access

Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
teo3300
2024-01-17 16:36:18 +09:00
parent 0b9e535b7c
commit 18ca7ddbdf
10 changed files with 49 additions and 36 deletions

View File

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

View File

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

View File

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

View File

@ -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<String, MalErr> {
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);

View File

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

View File

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

View File

@ -30,12 +30,10 @@ fn PRINT(output: MalType) -> String {
pub fn rep(reader: &Reader, env: &Env) -> Result<Vec<String>, 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)
}

View File

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

5
tests/assert.mal Normal file
View File

@ -0,0 +1,5 @@
(assert true)
(assert (ok? 1))
(assert-eq nil (ok? (1)))
(ok? (assert true))
(not (ok? (assert nil)))

View File

@ -1 +0,0 @@
(assert nil)