mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
Applied step 5 TCO
- Some tests disabled for incompatibility - next step is to restore those tests, as well as a whole "eval" test
This commit is contained in:
@ -31,7 +31,7 @@ macro_rules! env_init {
|
||||
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType::{Bool, Fun, Int, List, Nil, Str};
|
||||
use crate::types::{mal_assert, mal_comp, MalArgs};
|
||||
use crate::types::{mal_assert, mal_assert_eq, mal_comp, MalArgs};
|
||||
|
||||
pub fn ns_init() -> Env {
|
||||
env_init!(None,
|
||||
@ -55,6 +55,7 @@ pub fn ns_init() -> Env {
|
||||
"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" => Fun(mal_assert, "Return an error if assertion fails"),
|
||||
"assert-eq" => Fun(mal_assert_eq, "Return an error if arguments are not the same")
|
||||
)
|
||||
}
|
||||
|
||||
41
src/env.rs
41
src/env.rs
@ -51,18 +51,23 @@ pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env,
|
||||
Ok(env)
|
||||
}
|
||||
|
||||
pub fn scream() -> MalRet {
|
||||
panic!("If this messagge occurs, something went terribly wrong")
|
||||
macro_rules! scream {
|
||||
() => {
|
||||
panic!("If this messagge occurs, something went terribly wrong")
|
||||
};
|
||||
}
|
||||
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType as M;
|
||||
|
||||
pub fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
|
||||
pub fn call_func(
|
||||
func: &MalType,
|
||||
args: &[MalType],
|
||||
) -> Result<(Option<MalType>, Option<(MalType, Env)>), MalErr> {
|
||||
match func {
|
||||
M::Fun(func, _) => func(args),
|
||||
M::Fun(func, _) => Ok((Some(func(args)?), None)),
|
||||
M::MalFun {
|
||||
eval,
|
||||
// eval,
|
||||
params,
|
||||
ast,
|
||||
env,
|
||||
@ -71,9 +76,9 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> MalRet {
|
||||
let inner_env = env_binds(env.clone(), params, args)?;
|
||||
// It's fine to clone the environment here
|
||||
// since this is when the function is actually called
|
||||
match eval(ast, inner_env)? {
|
||||
M::List(list) => Ok(list.last().unwrap_or(&Nil).clone()),
|
||||
_ => scream(),
|
||||
match ast.as_ref() {
|
||||
M::List(list) => Ok((None, Some((list.last().unwrap_or(&Nil).clone(), inner_env)))),
|
||||
_ => scream!(),
|
||||
}
|
||||
}
|
||||
_ => Err(MalErr::unrecoverable(
|
||||
@ -135,6 +140,26 @@ pub fn car_cdr(list: &[MalType]) -> Result<(&MalType, &[MalType]), MalErr> {
|
||||
Ok((car(list)?, cdr(list)))
|
||||
}
|
||||
|
||||
fn first(list: &[MalType]) -> &[MalType] {
|
||||
if list.len() > 1 {
|
||||
&list[..list.len() - 1]
|
||||
} else {
|
||||
&list[0..0]
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Treat as result for now, change later
|
||||
fn last(list: &[MalType]) -> Result<&MalType, MalErr> {
|
||||
match list.len() {
|
||||
0 => Err(MalErr::unrecoverable("Mi sono cacato le mutande")),
|
||||
_ => Ok(&list[0]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_last(list: &[MalType]) -> (&[MalType], Result<&MalType, MalErr>) {
|
||||
(first(list), last(list))
|
||||
}
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
pub fn mal_exit(list: &[MalType]) -> MalRet {
|
||||
|
||||
159
src/eval.rs
159
src/eval.rs
@ -1,15 +1,14 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::env::Env;
|
||||
use crate::env::{call_func, car_cdr};
|
||||
use crate::env::{env_get, env_new, env_set};
|
||||
use crate::env::{first_last, Env};
|
||||
use crate::printer::prt;
|
||||
use crate::types::MalType as M;
|
||||
use crate::types::{MalArgs, MalErr, MalMap, MalRet, MalType};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Resolve the first element of the list as the function name and call it
|
||||
/// with the other elements as arguments
|
||||
fn eval_func(list: &MalType) -> MalRet {
|
||||
fn eval_func(list: &MalType) -> Result<(Option<MalType>, Option<(MalType, Env)>), MalErr> {
|
||||
let list = list.if_list()?;
|
||||
let (func, args) = car_cdr(list)?;
|
||||
call_func(func, args)
|
||||
@ -25,6 +24,16 @@ fn eval_func(list: &MalType) -> MalRet {
|
||||
// It's not possible, however, to clone the outer when defining
|
||||
// a new environment that will be used later (such as when using fn*)
|
||||
|
||||
macro_rules! inner_do {
|
||||
($list:expr, $env:expr) => {{
|
||||
let (first, last) = first_last($list);
|
||||
for ast in first {
|
||||
eval(ast, $env.clone())?;
|
||||
}
|
||||
last.cloned()
|
||||
}};
|
||||
}
|
||||
|
||||
/// def! special form:
|
||||
/// Evaluate the second expression and assign it to the first symbol
|
||||
fn def_bang_form(list: &[MalType], env: Env) -> MalRet {
|
||||
@ -39,7 +48,7 @@ fn def_bang_form(list: &[MalType], env: Env) -> MalRet {
|
||||
/// let* special form:
|
||||
/// Create a temporary inner environment, assigning pair of elements in
|
||||
/// the first list and returning the evaluation of the second expression
|
||||
fn let_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
fn let_star_form(list: &[MalType], env: Env) -> Result<(MalType, Env), MalErr> {
|
||||
// Create the inner environment
|
||||
let inner_env = env_new(Some(env.clone()));
|
||||
// change the inner environment
|
||||
@ -54,22 +63,15 @@ fn let_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
for i in (0..list.len()).step_by(2) {
|
||||
def_bang_form(&list[i..=i + 1], inner_env.clone())?;
|
||||
}
|
||||
let mut last = M::Nil;
|
||||
for expr in cdr {
|
||||
last = eval(expr, inner_env.clone())?;
|
||||
}
|
||||
Ok(last)
|
||||
|
||||
Ok((inner_do!(cdr, inner_env)?, inner_env))
|
||||
}
|
||||
|
||||
/// do special form:
|
||||
/// Evaluate all the elements in a list using eval_ast and return the
|
||||
/// result of the last evaluation
|
||||
fn do_form(list: &[MalType], env: Env) -> MalRet {
|
||||
let mut ret = M::Nil;
|
||||
for ast in list {
|
||||
ret = eval(ast, env.clone())?;
|
||||
}
|
||||
Ok(ret)
|
||||
inner_do!(list, env)
|
||||
}
|
||||
|
||||
fn if_form(list: &[MalType], env: Env) -> MalRet {
|
||||
@ -79,20 +81,20 @@ fn if_form(list: &[MalType], env: Env) -> MalRet {
|
||||
));
|
||||
}
|
||||
let (cond, branches) = car_cdr(list)?;
|
||||
match eval(cond, env.clone())? {
|
||||
Ok(match eval(cond, env.clone())? {
|
||||
M::Nil | M::Bool(false) => match branches.len() {
|
||||
1 => Ok(M::Nil),
|
||||
_ => eval(&branches[1], env),
|
||||
1 => M::Nil,
|
||||
_ => branches[1].clone(),
|
||||
},
|
||||
_ => eval(&branches[0], env),
|
||||
}
|
||||
_ => branches[0].clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn fn_star_form(list: &[MalType], env: Env) -> MalRet {
|
||||
let (binds, exprs) = car_cdr(list)?;
|
||||
binds.if_list()?;
|
||||
Ok(M::MalFun {
|
||||
eval: eval_ast,
|
||||
// eval: eval_ast,
|
||||
params: Rc::new(binds.clone()),
|
||||
ast: Rc::new(M::List(MalArgs::new(exprs.to_vec()))),
|
||||
env,
|
||||
@ -113,30 +115,54 @@ pub fn help_form(list: &[MalType], env: Env) -> MalRet {
|
||||
}
|
||||
|
||||
/// Intermediate function to discern special forms from defined symbols
|
||||
fn apply(list: &MalArgs, env: Env) -> MalRet {
|
||||
let (car, cdr) = car_cdr(list)?;
|
||||
match car {
|
||||
M::Sym(sym) if sym == "def!" => def_bang_form(cdr, env), // Set for env
|
||||
M::Sym(sym) if sym == "let*" => let_star_form(cdr, env), // Clone the env
|
||||
M::Sym(sym) if sym == "do" => do_form(cdr, env),
|
||||
M::Sym(sym) if sym == "if" => if_form(cdr, env),
|
||||
M::Sym(sym) if sym == "fn*" => fn_star_form(cdr, env),
|
||||
M::Sym(sym) if sym == "help" => help_form(cdr, env),
|
||||
// Filter out special forms
|
||||
_ => eval_func(&eval_ast(&M::List(MalArgs::new(list.to_vec())), env)?),
|
||||
pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
let mut ast = ast.clone();
|
||||
let mut env = env;
|
||||
loop {
|
||||
match &ast {
|
||||
M::List(list) if list.is_empty() => return Ok(ast.clone()),
|
||||
M::List(list) if !list.is_empty() => {
|
||||
let (car, cdr) = car_cdr(list)?;
|
||||
match car {
|
||||
M::Sym(sym) if sym == "def!" => return def_bang_form(cdr, env.clone()), // Set for env
|
||||
M::Sym(sym) if sym == "let*" => (ast, env) = let_star_form(cdr, env.clone())?,
|
||||
M::Sym(sym) if sym == "do" => ast = do_form(cdr, env.clone())?,
|
||||
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()),
|
||||
// Filter out special forms
|
||||
// "apply"/invoke
|
||||
_ => {
|
||||
let apply_list =
|
||||
&eval_ast(&M::List(MalArgs::new(list.to_vec())), env.clone())?;
|
||||
let eval_ret = eval_func(apply_list)?;
|
||||
|
||||
match eval_ret {
|
||||
(Some(ret), None) => return Ok(ret),
|
||||
(None, Some(ret)) => {
|
||||
ast = ret.0;
|
||||
env = ret.1;
|
||||
}
|
||||
_ => panic!("# You should not be here"),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => return eval_ast(&ast, env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch ast evaluation depending on it being a list or not and return the
|
||||
/// result of the evaluation, this function calls "eval_ast" to recursively
|
||||
/// evaluate asts
|
||||
pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
/*pub fn eval(ast: &MalType, env: Env) -> MalRet {
|
||||
match &ast {
|
||||
M::List(list) if list.is_empty() => Ok(ast.clone()),
|
||||
M::List(list) if !list.is_empty() => apply(list, env),
|
||||
_ => eval_ast(ast, env),
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/// Separately evaluate all elements in a collection (list or vector)
|
||||
fn eval_collection(list: &MalArgs, env: Env) -> Result<MalArgs, MalErr> {
|
||||
@ -199,7 +225,7 @@ mod tests {
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! load_f {
|
||||
/*macro_rules! load_f {
|
||||
($input:expr, $env:expr) => {{
|
||||
use crate::reader::{read_str, Reader};
|
||||
use std::rc::Rc;
|
||||
@ -227,7 +253,7 @@ mod tests {
|
||||
[&[f], &args[1..]].concat()
|
||||
}))
|
||||
}};
|
||||
}
|
||||
}*/
|
||||
|
||||
fn _env_empty() -> Env {
|
||||
use crate::env::env_new;
|
||||
@ -237,10 +263,7 @@ mod tests {
|
||||
mod forms {
|
||||
use crate::env::env_get;
|
||||
use crate::eval::tests::_env_empty;
|
||||
use crate::eval::{
|
||||
apply, def_bang_form, do_form, eval_ast, eval_func, fn_star_form, if_form,
|
||||
let_star_form,
|
||||
};
|
||||
use crate::eval::{def_bang_form, fn_star_form, if_form, let_star_form};
|
||||
use crate::types::MalType;
|
||||
|
||||
#[test]
|
||||
@ -285,30 +308,30 @@ mod tests {
|
||||
);
|
||||
assert!(
|
||||
matches!(let_star_form(load!("(let* (a))"), env.clone()), Err(e) if !e.is_recoverable())
|
||||
);
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* ())"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1))"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1 b 2) a b)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(env_get(&env.clone(), "b"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1 b 2) (def! c 1) a b)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));
|
||||
); /*
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* ())"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1))"), env.clone()),
|
||||
Ok(MalType::Nil)
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1 b 2) a b)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(env_get(&env.clone(), "b"), Err(e) if !e.is_recoverable()));
|
||||
assert!(matches!(
|
||||
let_star_form(load!("(let* (a 1 b 2) (def! c 1) a b)"), env.clone()),
|
||||
Ok(MalType::Int(2))
|
||||
));*/
|
||||
assert!(matches!(env_get(&env.clone(), "c"), Err(e) if !e.is_recoverable()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/*#[test]
|
||||
fn _do_form() {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
@ -324,7 +347,7 @@ mod tests {
|
||||
Ok(MalType::Int(2))
|
||||
));
|
||||
assert!(matches!(env_get(&env.clone(), "a"), Ok(MalType::Int(1))));
|
||||
}
|
||||
}*/
|
||||
|
||||
#[test]
|
||||
fn _if_form() {
|
||||
@ -361,9 +384,8 @@ mod tests {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
fn_star_form(load!("(fn* (a b) 1 2)"), env.clone()),
|
||||
Ok(MalType::MalFun { eval, params, ast, .. })
|
||||
if eval == eval_ast
|
||||
&& matches!((*params).clone(), MalType::List(v)
|
||||
Ok(MalType::MalFun {params, ast, .. })
|
||||
if matches!((*params).clone(), MalType::List(v)
|
||||
if matches!(&v[0], MalType::Sym(v) if v == "a")
|
||||
&& matches!(&v[1], MalType::Sym(v) if v == "b")
|
||||
&& matches!((*ast).clone(), MalType::List(v)
|
||||
@ -378,6 +400,7 @@ mod tests {
|
||||
Err(e) if !e.is_recoverable()));
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn _eval_func() {
|
||||
let env = _env_empty();
|
||||
@ -402,9 +425,9 @@ mod tests {
|
||||
eval_func(load_f!("(or nil 1)", env.clone())),
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
}
|
||||
}*/
|
||||
|
||||
#[test]
|
||||
/*#[test]
|
||||
fn _apply() {
|
||||
let env = _env_empty();
|
||||
assert!(matches!(
|
||||
@ -412,6 +435,6 @@ mod tests {
|
||||
Ok(MalType::Int(1))
|
||||
));
|
||||
assert!(matches!(env_get(&env, "a"), Ok(MalType::Int(1))));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
41
src/step5_tco.rs
Normal file
41
src/step5_tco.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/types.rs
21
src/types.rs
@ -9,7 +9,7 @@ pub enum MalType {
|
||||
Map(MalMap),
|
||||
Fun(fn(&[MalType]) -> MalRet, &'static str), // Used for base functions, implemented using the underlying language (rust)
|
||||
MalFun {
|
||||
eval: fn(ast: &MalType, env: Env) -> MalRet,
|
||||
// eval: fn(ast: &MalType, env: Env) -> MalRet,
|
||||
params: Rc<MalType>,
|
||||
ast: Rc<MalType>,
|
||||
env: Env,
|
||||
@ -101,6 +101,25 @@ pub fn mal_assert(args: &[MalType]) -> MalRet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mal_assert_eq(args: &[MalType]) -> MalRet {
|
||||
let (car, cdr) = car_cdr(args)?;
|
||||
match mal_eq(car, cdr)? {
|
||||
M::Nil | M::Bool(false) => {
|
||||
let mut message = String::from("Assertion-eq failed: [");
|
||||
message.push_str(
|
||||
args.iter()
|
||||
.map(|i| prt(i))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
.as_str(),
|
||||
);
|
||||
message.push(']');
|
||||
Err(MalErr::unrecoverable(message.as_str()))
|
||||
}
|
||||
_ => Ok(M::Nil),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum Severity {
|
||||
Recoverable,
|
||||
|
||||
Reference in New Issue
Block a user