mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
Adding quotation, semiquote macro and env access
Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
@ -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")
|
||||
)
|
||||
}
|
||||
|
||||
@ -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" => {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
5
tests/assert.mal
Normal file
@ -0,0 +1,5 @@
|
||||
(assert true)
|
||||
(assert (ok? 1))
|
||||
(assert-eq nil (ok? (1)))
|
||||
(ok? (assert true))
|
||||
(not (ok? (assert nil)))
|
||||
@ -1 +0,0 @@
|
||||
(assert nil)
|
||||
Reference in New Issue
Block a user