oftlisp 0.1.3

A compiler and interpreter for OftLisp, in Rust.
Documentation
//! Macro expansion's implementation.

use either::{Either, Left, Right};
use gc::Gc;

use ast::{ConvertError, convert_fn, Expr};
use collections::GcLinkedList;
use context::InterpreterContext;
use symbol::Symbol;
use util::{as_list, as_shl};
use value::Value;

/// Performs macro-expansion on a module.
pub fn expand_module<C: 'static + InterpreterContext>(
    body: Vec<Gc<Value<C>>>,
    env: &mut GcLinkedList<(Symbol, Gc<Value<C>>)>,
    menv: &mut GcLinkedList<(Symbol, Gc<Value<C>>)>,
) -> Result<Vec<Gc<Value<C>>>, Either<C::RuntimeError, ConvertError<C>>> {
    let mut out = vec![];
    for v in body {
        let v = match expand_expr(v, env, menv) {
            Ok(Some(v)) => v,
            Ok(None) => continue,
            Err(err) => return Err(err),
        };
        out.extend(expand_macro_progns(v));
    }
    Ok(out)
}

/// Performs macro-expansion on a single expression.
pub fn expand_expr<C: 'static + InterpreterContext>(
    value: Gc<Value<C>>,
    env: &mut GcLinkedList<(Symbol, Gc<Value<C>>)>,
    menv: &mut GcLinkedList<(Symbol, Gc<Value<C>>)>,
) -> Result<Option<Gc<Value<C>>>, Either<C::RuntimeError, ConvertError<C>>> {
    if let Some((sym, rest)) = as_shl(value.clone()) {
        if sym.as_str() == "defmacro" {
            if rest.len() > 2 {
                let mut body = expand_list(rest, &mut env.clone(), &mut menv.clone())?;

                let name = if let Value::Symbol(name, _) = *body.remove(0) {
                    name
                } else {
                    unimplemented!("invalid defmacro")
                };
                let args = body.remove(0);
                let lambda = convert_fn(Some(name), args, body).map_err(Right)?;
                let lambda = C::from_expr(lambda);

                let func = C::eval(lambda, env).map_err(Left)?;
                menv.push((name, func));
                Ok(None)
            } else {
                unimplemented!("defmacro")
            }
        } else if let Some(mac) = menv.lookup(sym) {
            let args = rest.into_iter().map(Expr::Literal).map(Gc::new).collect();
            let call = Gc::new(Expr::Call(Gc::new(Expr::Literal(mac)), args));
            let call = C::from_expr(call);
            let val = C::eval(call, env).map_err(Left)?;
            expand_expr(val, env, menv)
        } else {
            expand_list(rest, env, menv)
                .map(|l| {
                    let tl = Value::list(l, Default::default());
                    let hd = Gc::new(Value::Symbol(sym, Default::default()));
                    Gc::new(Value::Cons(hd, tl, Default::default()))
                })
                .map(Some)
        }
    } else if let Some(l) = as_list(value.clone()) {
        expand_list(l, env, menv)
            .map(|l| Value::list(l, Default::default()))
            .map(Some)
    } else if let Value::Vector(ref v, _) = *value.clone() {
        expand_list(v.clone(), env, menv).map(Value::vector).map(
            Some,
        )
    } else {
        Ok(Some(value))
    }
}

/// Performs macro-expansion on each element of a list, properly restoring the
/// list's metadata afterwards.
fn expand_list<C: 'static + InterpreterContext>(
    vals: Vec<Gc<Value<C>>>,
    env: &mut GcLinkedList<(Symbol, Gc<Value<C>>)>,
    menv: &mut GcLinkedList<(Symbol, Gc<Value<C>>)>,
) -> Result<Vec<Gc<Value<C>>>, Either<C::RuntimeError, ConvertError<C>>> {
    let mut menv = menv.clone();
    vals.into_iter()
        .filter_map(|v| match expand_expr(v, env, &mut menv) {
            Ok(Some(v)) => Some(Ok(v)),
            Ok(None) => None,
            Err(err) => Some(Err(err)),
        })
        .collect()
}

/// Recursively expands `macro-progn` forms.
///
/// When a macro returns a `macro-progn`, it is expanded into multiple forms in
/// the *current* scope, rather than creating a new scope. For example,
///
/// ```oftlisp
/// (defmacro m ()
///   '(macro-progn
///     (def x 1)
///     (def y 2)))
///
/// (defn main ()
///   (def x 3)
///   (def y 4)
///   (m)
///   (println x ", " y))
/// ```
///
/// will print `1, 2` rather than `3, 4`.
fn expand_macro_progns<C: 'static + InterpreterContext>(value: Gc<Value<C>>) -> Vec<Gc<Value<C>>> {
    fn helper<C: 'static + InterpreterContext>(value: Gc<Value<C>>, v: &mut Vec<Gc<Value<C>>>) {
        if let Some((sym, rest)) = as_shl(value.clone()) {
            if sym == "macro-progn".into() {
                for value in rest {
                    helper(value, v);
                }
            } else {
                v.push(value);
            }
        } else {
            v.push(value);
        }
    }

    let mut out = vec![];
    helper(value, &mut out);
    out
}