mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-12 09:15:32 +01:00
170 lines
5.6 KiB
Rust
170 lines
5.6 KiB
Rust
use std::rc::Rc;
|
|
|
|
use crate::core::{call_func, car_cdr};
|
|
use crate::env::Env;
|
|
use crate::env::{env_get, env_new, env_set};
|
|
use crate::printer::prt;
|
|
use crate::types::MalType::*;
|
|
use crate::types::{MalArgs, MalErr, MalMap, MalRet, MalType};
|
|
|
|
/// 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 {
|
|
let list = list.if_list()?;
|
|
let (func, args) = car_cdr(list)?;
|
|
call_func(func, args)
|
|
}
|
|
|
|
// When evaluating an expression it's possible
|
|
// (actually the only option I'm aware until now)
|
|
// to clone the environment "env.clone()", performances aside
|
|
// [env.clone()]
|
|
// NOTE: Ok, so cloning an Env now is not bad, since it's an Rc
|
|
// (Just pointing this out because I know I will try to change this later)
|
|
//
|
|
// It's not possible, however, to clone the outer when defining
|
|
// a new environment that will be used later (such as when using fn*)
|
|
|
|
/// def! special form:
|
|
/// Evaluate the second expression and assign it to the first symbol
|
|
fn def_bang_form(list: &[MalType], env: Env) -> MalRet {
|
|
if list.len() != 2 {
|
|
return Err(MalErr::unrecoverable("def! form: needs 2 arguments"));
|
|
}
|
|
let (car, _) = car_cdr(list)?;
|
|
let sym = car.if_symbol()?;
|
|
Ok(env_set(&env, sym, &eval(&list[1], env.clone())?))
|
|
}
|
|
|
|
/// 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 {
|
|
// Create the inner environment
|
|
let inner_env = env_new(Some(env.clone()));
|
|
// change the inner environment
|
|
let (car, cdr) = car_cdr(list)?;
|
|
let list = car.if_list()?;
|
|
if list.len() % 2 == 0 {
|
|
// TODO: Find a way to avoid index looping that is ugly
|
|
for i in (0..list.len()).step_by(2) {
|
|
def_bang_form(&list[i..=i + 1], inner_env.clone())?;
|
|
}
|
|
if cdr.is_empty() {
|
|
// TODO: check if it exists a better way to do this
|
|
Ok(Nil)
|
|
} else {
|
|
eval(&cdr[0], inner_env)
|
|
}
|
|
} else {
|
|
Err(MalErr::unrecoverable("let* form, number of arguments must be even"))
|
|
}
|
|
}
|
|
|
|
/// 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 {
|
|
if list.is_empty() {
|
|
return Err(MalErr::unrecoverable("do form: provide a list as argument"));
|
|
}
|
|
match eval_ast(&list[0], env)? {
|
|
List(list) => Ok(list.last().unwrap_or(&Nil).clone()),
|
|
_ => Err(MalErr::unrecoverable("do form: argument must be a list")),
|
|
}
|
|
}
|
|
|
|
fn if_form(list: &[MalType], env: Env) -> MalRet {
|
|
if !(2..=3).contains(&list.len()) {
|
|
return Err(MalErr::unrecoverable(
|
|
"if form: number of arguments is wrong",
|
|
));
|
|
}
|
|
let (cond, branches) = car_cdr(list)?;
|
|
match eval(cond, env.clone())? {
|
|
Nil | Bool(false) => match branches.len() {
|
|
1 => Ok(Nil),
|
|
_ => eval(&branches[1], env),
|
|
},
|
|
_ => eval(&branches[0], env),
|
|
}
|
|
}
|
|
|
|
fn fn_star_form(list: &[MalType], env: Env) -> MalRet {
|
|
let (binds, exprs) = car_cdr(list)?;
|
|
Ok(MalFun {
|
|
eval: eval_ast,
|
|
params: Rc::new(binds.clone()),
|
|
ast: Rc::new(List(exprs.to_vec())),
|
|
env,
|
|
})
|
|
}
|
|
|
|
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())? {
|
|
Fun(_, desc) => println!("{}\t[builtin]: {}", sym_str, desc),
|
|
MalFun { params, ast, .. } => print_malfun(sym_str, params, ast),
|
|
_ => println!("{}\t[symbol]: {}", sym_str, prt(&env_get(&env, sym_str)?)),
|
|
}
|
|
Ok(Bool(true))
|
|
}
|
|
|
|
/// Intermediate function to discern special forms from defined symbols
|
|
fn apply(list: &MalArgs, env: Env) -> MalRet {
|
|
let (car, cdr) = car_cdr(list)?;
|
|
match car {
|
|
Sym(sym) if sym == "def!" => def_bang_form(cdr, env), // Set for env
|
|
Sym(sym) if sym == "let*" => let_star_form(cdr, env), // Clone the env
|
|
Sym(sym) if sym == "do" => do_form(cdr, env),
|
|
Sym(sym) if sym == "if" => if_form(cdr, env),
|
|
Sym(sym) if sym == "fn*" => fn_star_form(cdr, env),
|
|
Sym(sym) if sym == "help" => help_form(cdr, env),
|
|
// Filter out special forms
|
|
_ => eval_func(&eval_ast(&List(list.to_vec()), 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 {
|
|
match &ast {
|
|
List(list) if list.is_empty() => Ok(ast.clone()),
|
|
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> {
|
|
let mut ret = MalArgs::new();
|
|
for el in list {
|
|
ret.push(eval(el, env.clone())?);
|
|
}
|
|
Ok(ret)
|
|
}
|
|
|
|
/// Evaluate the values of a map
|
|
fn eval_map(map: &MalMap, env: Env) -> MalRet {
|
|
let mut ret = MalMap::new();
|
|
for (k, v) in map {
|
|
ret.insert(k.to_string(), eval(v, env.clone())?);
|
|
}
|
|
Ok(Map(ret))
|
|
}
|
|
|
|
/// Eval the provided ast
|
|
fn eval_ast(ast: &MalType, env: Env) -> MalRet {
|
|
match ast {
|
|
Sym(sym) => env_get(&env, sym),
|
|
List(list) => Ok(List(eval_collection(list, env)?)),
|
|
Vector(vec) => Ok(Vector(eval_collection(vec, env)?)),
|
|
Map(map) => eval_map(map, env),
|
|
_ => Ok(ast.clone()),
|
|
}
|
|
}
|