use crate::{Error, TulispObject, TulispValue, destruct_bind};
pub(crate) fn classify_free_vars(
body: &TulispObject,
params: &[TulispObject],
) -> Result<Vec<TulispObject>, Error> {
let mut free: Vec<TulispObject> = Vec::new();
let mut scopes: Vec<Vec<TulispObject>> = vec![params.to_vec()];
visit(body, &mut free, &mut scopes, 0)?;
Ok(free)
}
fn is_bound(scopes: &[Vec<TulispObject>], sym: &TulispObject) -> bool {
scopes.iter().any(|scope| scope.iter().any(|b| b.eq(sym)))
}
fn note_free(free: &mut Vec<TulispObject>, sym: TulispObject) {
if !free.iter().any(|x| x.eq(&sym)) {
free.push(sym);
}
}
fn visit(
obj: &TulispObject,
free: &mut Vec<TulispObject>,
scopes: &mut Vec<Vec<TulispObject>>,
quote_depth: u32,
) -> Result<(), Error> {
if !obj.consp() {
let inner = obj.inner_ref();
return match &inner.0 {
TulispValue::LexicalBinding { .. } => {
if quote_depth == 0 && !is_bound(scopes, obj) {
drop(inner);
note_free(free, obj.clone());
}
Ok(())
}
TulispValue::Symbol { .. } => Ok(()),
TulispValue::Backquote { value } => {
let v = value.clone();
drop(inner);
visit(&v, free, scopes, quote_depth + 1)
}
TulispValue::Unquote { value } | TulispValue::Splice { value } => {
let v = value.clone();
drop(inner);
visit(&v, free, scopes, quote_depth.saturating_sub(1))
}
TulispValue::Sharpquote { value } if quote_depth == 0 => {
let v = value.clone();
drop(inner);
visit(&v, free, scopes, quote_depth)
}
_ => Ok(()),
};
}
if quote_depth == 0
&& let Ok(car) = obj.car()
&& let Ok(name) = car.as_symbol()
&& name == "quote"
{
return Ok(());
}
if quote_depth == 0
&& let Ok(car) = obj.car()
&& let Ok(name) = car.as_symbol()
{
match name.as_str() {
"let" | "let*" => return visit_let(obj, free, scopes, quote_depth),
"lambda" => return visit_lambda(obj, free, scopes, quote_depth),
"dolist" | "dotimes" => {
return visit_dolist_dotimes(obj, free, scopes, quote_depth);
}
_ => {}
}
}
for item in obj.base_iter() {
visit(&item, free, scopes, quote_depth)?;
}
Ok(())
}
fn visit_let(
form: &TulispObject,
free: &mut Vec<TulispObject>,
scopes: &mut Vec<Vec<TulispObject>>,
quote_depth: u32,
) -> Result<(), Error> {
destruct_bind!((_let varlist &rest body) = form);
let mut bound: Vec<TulispObject> = Vec::new();
for item in varlist.base_iter() {
if item.symbolp() {
bound.push(item);
} else if item.consp() {
let name = item.car()?;
if name.symbolp() {
bound.push(name.clone());
}
if let Ok(init) = item.cadr() {
visit(&init, free, scopes, quote_depth)?;
}
}
}
scopes.push(bound);
let result = visit(&body, free, scopes, quote_depth);
scopes.pop();
result
}
fn visit_lambda(
form: &TulispObject,
free: &mut Vec<TulispObject>,
scopes: &mut Vec<Vec<TulispObject>>,
quote_depth: u32,
) -> Result<(), Error> {
destruct_bind!((_lambda params &rest body) = form);
let mut bound: Vec<TulispObject> = Vec::new();
for p in params.base_iter() {
if p.symbolp() {
let name = p.as_symbol().unwrap_or_default();
if !name.starts_with('&') {
bound.push(p);
}
}
}
scopes.push(bound);
let result = visit(&body, free, scopes, quote_depth);
scopes.pop();
result
}
fn visit_dolist_dotimes(
form: &TulispObject,
free: &mut Vec<TulispObject>,
scopes: &mut Vec<Vec<TulispObject>>,
quote_depth: u32,
) -> Result<(), Error> {
destruct_bind!((_head spec &rest body) = form);
if spec.consp() {
if let Ok(init) = spec.cadr() {
visit(&init, free, scopes, quote_depth)?;
}
let var = spec.car()?;
let mut bound = Vec::new();
if var.symbolp() {
bound.push(var);
}
scopes.push(bound);
let result = visit(&body, free, scopes, quote_depth);
scopes.pop();
result
} else {
visit(&body, free, scopes, quote_depth)
}
}