- added 'eval', 'slurp', 'read-string'
- 'not' and 'load-file' defined through mal
- added some missing print functions
- listing all defined repl symbols when calling 'help' with no argumens

Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
teo3300
2023-12-27 16:26:46 +09:00
parent 7a5ebe5fa1
commit 6bc8735af6
9 changed files with 194 additions and 48 deletions

View File

@ -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::<Vec<String>>().join(" "))), "Print readably all arguments"),
"str" => Fun(|a| Ok(Str(a.iter().map(|i| pr_str(i, false)).collect::<Vec<String>>().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
}

View File

@ -6,7 +6,20 @@ use std::rc::Rc;
#[derive(Clone, Debug)]
pub struct EnvType {
data: RefCell<MalMap>,
outer: Option<Env>,
pub outer: Option<Env>,
}
impl EnvType {
pub fn keys(&self) -> String {
let mut keys = self
.data
.borrow()
.iter()
.map(|(k, _)| k.clone())
.collect::<Vec<String>>();
keys.sort_unstable();
keys.join(" ")
}
}
pub type Env = Rc<EnvType>;
@ -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]),
}
}

View File

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

View File

@ -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::<Vec<String>>()[1..].iter().for_each(|f| {
if let Err(e) = load_file(f, &reply_env) {

View File

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

View File

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

View File

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

41
src/step6_file.rs Normal file
View File

@ -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<Vec<String>, 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);
}
}
}

View File

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