Lines
72.81 %
Functions
20 %
Branches
100 %
use crate::ast::Expr;
use crate::error::{Error, Result};
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
use super::super::context::CompileContext;
use super::super::emit::FunctionEmitter;
use super::super::expr::{compile_expr, resolve_arg};
use super::binding::parse_lambda_params;
pub(super) fn compile_defmacro_form(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<()> {
let result = defmacro(symbols, args)?;
compile_expr(ctx, emit, symbols, &result)
}
pub(super) fn compile_macroexpand_1_form(
let result = macroexpand_1(symbols, args)?;
pub(super) fn compile_macroexpand_form(
let result = macroexpand(symbols, args)?;
pub(super) fn defmacro(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.len() < 3 {
return Err(Error::Compile(
"DEFMACRO requires a name, parameter list, and body".to_string(),
));
let name = match &args[0] {
Expr::Symbol(s) => s.clone(),
other => {
return Err(Error::Compile(format!(
"DEFMACRO: expected symbol name, got {other:?}"
)));
};
let params = parse_lambda_params("DEFMACRO", &args[1])?;
if !params.aux.is_empty() {
"DEFMACRO: &aux is not yet supported".to_string(),
let (doc, body_idx) = match args.get(2) {
Some(Expr::String(s)) if args.len() > 3 => (Some(s.clone()), 3),
_ => (None, 2),
if body_idx >= args.len() {
return Err(Error::Compile("DEFMACRO: missing body".to_string()));
let body = if args.len() == body_idx + 1 {
args[body_idx].clone()
} else {
let mut forms = Vec::with_capacity(args.len() - body_idx + 1);
forms.push(Expr::Symbol("BEGIN".to_string()));
forms.extend_from_slice(&args[body_idx..]);
Expr::List(forms)
let lambda = Expr::Lambda(params, Box::new(body));
let mut sym = Symbol::new(&name, SymbolKind::Macro).with_function(lambda);
if let Some(d) = doc {
sym = sym.with_doc(d);
symbols.define(sym);
Ok(Expr::Quote(Box::new(Expr::Symbol(name))))
pub(super) fn macroexpand_1(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if args.len() != 1 {
return Err(Error::Arity {
name: "MACROEXPAND-1".to_string(),
expected: 1,
actual: args.len(),
});
let form = resolve_arg(symbols, &args[0])?;
macroexpand_1_impl(symbols, &form)
pub(super) fn macroexpand(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
name: "MACROEXPAND".to_string(),
let mut form = resolve_arg(symbols, &args[0])?;
loop {
let expanded = macroexpand_1_impl(symbols, &form)?;
if expanded == form {
break;
form = expanded;
Ok(form)
fn macroexpand_1_impl(symbols: &mut SymbolTable, form: &Expr) -> Result<Expr> {
match form {
Expr::Quote(inner) => match inner.as_ref() {
Expr::List(elems) if !elems.is_empty() => {
if let Some(name) = macro_head_name(&elems[0])
&& let Some(sym) = symbols.lookup(name)
&& sym.kind() == SymbolKind::Macro
&& let Some(Expr::Lambda(params, body)) = sym.function().cloned()
{
return super::super::expr::expand_macro(symbols, ¶ms, &body, &elems[1..]);
Ok(form.clone())
_ => Ok(form.clone()),
},
fn macro_head_name(expr: &Expr) -> Option<&str> {
match expr {
Expr::Symbol(name) => Some(name),
_ => None,