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};
|
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
|
// 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,
|
// 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
|
// 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"),
|
"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 == "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 == "help" => return help_form(cdr, env.clone()),
|
||||||
M::Sym(sym) if sym == "find" => return find_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
|
// Special form, sad
|
||||||
// Bruh, is basically double eval
|
// Bruh, is basically double eval
|
||||||
M::Sym(sym) if sym == "eval" => {
|
M::Sym(sym) if sym == "eval" => {
|
||||||
|
|||||||
@ -18,12 +18,7 @@ mod functional {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_fail() {
|
fn assert_fail() {
|
||||||
use crate::core::ns_init;
|
test!("assert")
|
||||||
use crate::load_file;
|
|
||||||
assert!(matches!(
|
|
||||||
load_file("tests/assert_fail.mal", &ns_init()),
|
|
||||||
Err(_)
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
use crate::env::{env_get, Env};
|
use crate::env::Env;
|
||||||
use crate::eval::eval;
|
use crate::eval::eval;
|
||||||
use crate::reader::{read_str, Reader};
|
use crate::reader::{read_str, Reader};
|
||||||
use crate::step6_file::rep;
|
use crate::step6_file::rep;
|
||||||
use crate::types::{MalErr, MalRet};
|
use crate::types::{MalErr, MalRet};
|
||||||
use std::env;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -14,29 +13,20 @@ fn eval_str(line: &str, env: &Env) -> MalRet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_home_path(env: &Env) {
|
pub fn set_home_path(env: &Env) {
|
||||||
let home = match env::var("MAL_HOME") {
|
eval_str(
|
||||||
Ok(s) => s,
|
"(or (def! MAL_HOME (env \"MAL_HOME\"))
|
||||||
Err(_) => env::var("HOME").unwrap() + "/.config/mal",
|
(def! MAL_HOME (str (env \"HOME\") \"/.config/mal\")))",
|
||||||
};
|
env,
|
||||||
|
)
|
||||||
if !Path::new(&home).exists() {
|
.unwrap();
|
||||||
eprintln!("; WARNING: MAL_HOME: \"{}\" does not exist", home);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add config path to mal
|
|
||||||
eval_str(format!("(def! MAL_HOME \"{home}\")").as_str(), env).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_home_path(env: &Env) -> String {
|
fn get_home_path(env: &Env) -> Result<String, MalErr> {
|
||||||
env_get(env, "MAL_HOME")
|
Ok(eval_str("MAL_HOME", env)?.if_string()?.to_string())
|
||||||
.unwrap()
|
|
||||||
.if_string()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_home_file(filename: &str, env: &Env, warn: bool) {
|
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 Path::new(&full_filename).exists() {
|
||||||
if let Err(e) = load_file(&full_filename, env) {
|
if let Err(e) = load_file(&full_filename, env) {
|
||||||
@ -77,12 +67,13 @@ use rustyline::error::ReadlineError;
|
|||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
|
|
||||||
pub fn interactive(env: Env) {
|
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
|
// 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
|
// TODO: remove unwrap and switch to a better error handling
|
||||||
let mut rl = DefaultEditor::new().unwrap();
|
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");
|
eprintln!("; Failed to load history");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +96,7 @@ pub fn interactive(env: Env) {
|
|||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
// TODO: should handle this in a different way
|
// TODO: should handle this in a different way
|
||||||
rl.add_history_entry(&line).unwrap();
|
rl.add_history_entry(&line).unwrap();
|
||||||
rl.save_history(HISTORY_PATH).unwrap();
|
rl.save_history(HISTORY).unwrap();
|
||||||
|
|
||||||
parser.push(&line);
|
parser.push(&line);
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ fn key_str(val: &str) -> MalType {
|
|||||||
|
|
||||||
pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
|
pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
|
||||||
match ast {
|
match ast {
|
||||||
M::Nil => "nil".to_string(),
|
M::Nil => "NIL".to_string(),
|
||||||
M::Sym(sym) => sym.to_string(),
|
M::Sym(sym) => sym.to_string(),
|
||||||
M::Key(sym) => sym[2..].to_string(),
|
M::Key(sym) => sym[2..].to_string(),
|
||||||
M::Int(val) => val.to_string(),
|
M::Int(val) => val.to_string(),
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
// Specyfy components in "types"
|
// Specyfy components in "types"
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
@ -120,6 +121,14 @@ impl Reader {
|
|||||||
"(" => self.read_list(")"),
|
"(" => self.read_list(")"),
|
||||||
"[" => self.read_list("]"),
|
"[" => 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(),
|
_ => self.read_atom(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,12 +30,10 @@ fn PRINT(output: MalType) -> String {
|
|||||||
|
|
||||||
pub fn rep(reader: &Reader, env: &Env) -> Result<Vec<String>, MalErr> {
|
pub fn rep(reader: &Reader, env: &Env) -> Result<Vec<String>, MalErr> {
|
||||||
let mut ret_str = Vec::new();
|
let mut ret_str = Vec::new();
|
||||||
loop {
|
while !reader.ended() {
|
||||||
let ast = READ(reader)?;
|
let ast = READ(reader)?;
|
||||||
let out = EVAL(ast, env.clone())?;
|
let out = EVAL(ast, env.clone())?;
|
||||||
ret_str.push(PRINT(out));
|
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;
|
use crate::types::MalType as M;
|
||||||
|
|
||||||
|
// That's a quite chonky function
|
||||||
fn mal_eq(a: &MalType, b: &[MalType]) -> MalRet {
|
fn mal_eq(a: &MalType, b: &[MalType]) -> MalRet {
|
||||||
Ok(M::Bool(match a {
|
Ok(M::Bool(match a {
|
||||||
M::Nil => b.iter().all(|el| matches!(el, M::Nil)),
|
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