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:
teo3300
2023-12-21 19:25:24 +09:00
parent c6eeb225df
commit 0b47444836
6 changed files with 191 additions and 83 deletions

View File

@ -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")
)
}

View File

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

View File

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

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

View File

@ -1,10 +1,9 @@
(def! n-fib (fn* (n)
(if (<= n 0) 0 ; 0 base case
(if (= n 1) 1 ; 1 base case
(+ (n-fib (- n 1)) (n-fib (- n 2))))))) ; recursive
(if (< n 2) n ; base
(+ (n-fib (- n 1)) (n-fib (- n 2)))))) ; recursive
(def! assert-fib (fn* (n expected) ; check fibonacci result
(if (= (n-fib n) expected) nil
(if (not (= (n-fib n) expected))
(do (prn (list
"Expected"
expected