mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
Step 6
- 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:
59
src/core.rs
59
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::<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
|
||||
}
|
||||
|
||||
17
src/env.rs
17
src/env.rs
@ -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]),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
36
src/eval.rs
36
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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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(_)
|
||||
));
|
||||
}};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
41
src/step6_file.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/types.rs
12
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,
|
||||
|
||||
Reference in New Issue
Block a user