use super::env::{env_get, macro_env};
use super::eval::eval;
use super::{Env, Err, Exp};
use crate::{
ensure_length_eq, ensure_length_gt, ensure_list, ensure_string, expected
};
use alloc::format;
use alloc::rc::Rc;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use core::cell::RefCell;
fn is_sym(e: &Exp, name: &str) -> bool {
*e == Exp::Sym(name.to_string())
}
pub fn expand_quasiquote(exp: &Exp) -> Result<Exp, Err> {
match exp {
Exp::List(list) if list.len() > 0 => match &list[0] {
Exp::Sym(s) if s == "unquote" => Ok(list[1].clone()),
Exp::List(l) if l.len() == 2 && is_sym(&l[0], "unquote-splice") => {
Ok(Exp::List(vec![
Exp::Sym("concat".to_string()),
l[1].clone(),
expand_quasiquote(&Exp::List(list[1..].to_vec()))?,
]))
}
_ => Ok(Exp::List(vec![
Exp::Sym("cons".to_string()),
expand_quasiquote(&list[0])?,
expand_quasiquote(&Exp::List(list[1..].to_vec()))?,
])),
},
_ => Ok(Exp::List(vec![Exp::Sym("quote".to_string()), exp.clone()])),
}
}
pub fn expand_list(
list: &[Exp],
env: &mut Rc<RefCell<Env>>
) -> Result<Exp, Err> {
let expanded: Result<Vec<Exp>, Err> = list.iter().map(|item|
expand(item, env)
).collect();
Ok(Exp::List(expanded?))
}
pub fn expand(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
if let Exp::List(list) = exp {
if list.is_empty() {
return Ok(exp.clone());
}
match &list[0] {
Exp::Sym(s) if s == "quote" => {
ensure_length_eq!(list, 2);
Ok(exp.clone())
}
Exp::Sym(s) if s == "quasiquote" => {
ensure_length_eq!(list, 2);
expand_quasiquote(&list[1])
}
Exp::Sym(s) if s == "define-function" || s == "define" => {
let (params, body) = match list.len() {
3 => {
ensure_list!(&list[2]);
(&list[1], &list[2])
}
4 => {
ensure_string!(&list[2]);
ensure_list!(&list[3]);
(&list[1], &list[3])
}
_ => return expected!("3 or 4 arguments"),
};
match params {
Exp::List(args) => {
ensure_length_gt!(args, 0);
let name = args[0].clone();
let args = Exp::List(args[1..].to_vec());
let body = expand(body, env)?;
let mut function = vec![
Exp::Sym("function".to_string()),
args,
body,
];
if list.len() == 4 {
function.insert(2, list[2].clone());
}
Ok(Exp::List(vec![
Exp::Sym("variable".to_string()),
name,
Exp::List(function),
]))
}
Exp::Sym(_) => expand_list(list, env),
_ => expected!("first argument to be a symbol or a list"),
}
}
Exp::Sym(s) if s == "define-macro" => {
ensure_length_eq!(list, 3);
match (&list[1], &list[2]) {
(Exp::List(args), Exp::List(_)) => {
ensure_length_gt!(args, 0);
let name = args[0].clone();
let args = Exp::List(args[1..].to_vec());
let body = expand(&list[2], env)?;
Ok(Exp::List(vec![
Exp::Sym("variable".to_string()),
name,
Exp::List(vec![
Exp::Sym("macro".to_string()),
args,
body
]),
]))
}
(Exp::Sym(_), _) => expand_list(list, env),
_ => expected!("first argument to be a symbol or a list"),
}
}
Exp::Sym(s) if s == "cond" => {
ensure_length_gt!(list, 1);
if let Exp::List(args) = &list[1] {
ensure_length_eq!(args, 2);
let test_exp = expand(&args[0], env)?;
let then_exp = expand(&args[1], env)?;
let mut res = vec![
Exp::Sym("if".to_string()),
test_exp,
then_exp,
];
if list.len() > 2 {
let mut acc = vec![Exp::Sym("cond".to_string())];
acc.extend_from_slice(&list[2..]);
res.push(expand(&Exp::List(acc), env)?);
}
Ok(Exp::List(res))
} else {
expected!("lists of predicate and expression")
}
}
Exp::Sym(s) => {
if let Ok(Exp::Macro(m)) = env_get(s, env) {
let mut m_env = macro_env(&m.params, &list[1..], env)?;
let m_exp = m.body;
expand(&eval(&m_exp, &mut m_env)?, env)
} else {
expand_list(list, env)
}
}
_ => expand_list(list, env),
}
} else {
Ok(exp.clone())
}
}