Lines
58.14 %
Functions
40 %
Branches
100 %
//! Quasiquote / unquote / unquote-splicing expansion.
//!
//! `expand_quasiquote` walks an expression in quasiquote context,
//! resolving each `,form` (Unquote) against the symbol table and
//! splicing each `,@form` (UnquoteSplicing) into its parent list.
//! The result wraps the fully resolved tree in a single outer
//! `Quote` so downstream callers see a constant-foldable value.
use crate::ast::Expr;
use crate::error::{Error, Result};
use crate::runtime::SymbolTable;
use super::eval::resolve_arg;
use super::format::format_expr;
pub(in crate::compiler) fn expand_quasiquote(
symbols: &mut SymbolTable,
expr: &Expr,
) -> Result<Expr> {
fn splice_elements(resolved: Expr) -> Result<Vec<Expr>> {
match resolved {
Expr::Nil => Ok(Vec::new()),
Expr::List(elems) => Ok(elems),
Expr::Quote(inner) => match *inner {
other => Err(Error::Compile(format!(
"unquote-splicing requires a list, got {}",
format_expr(&other)
))),
},
}
fn quasiquote_data(symbols: &mut SymbolTable, expr: &Expr) -> Result<Expr> {
match expr {
Expr::Unquote(inner) => resolve_arg(symbols, inner),
Expr::UnquoteSplicing(_) => Err(Error::Compile(
"unquote-splicing outside of list in quasiquote".to_string(),
)),
Expr::List(elems) => {
let mut resolved: Vec<Expr> = Vec::new();
for elem in elems {
match elem {
Expr::UnquoteSplicing(inner) => {
let spliced = resolve_arg(symbols, inner)?;
resolved.extend(splice_elements(spliced)?);
_ => resolved.push(quasiquote_data(symbols, elem)?),
Ok(Expr::List(resolved))
Expr::Cons(car, cdr) => {
let car = quasiquote_data(symbols, car)?;
let cdr = quasiquote_data(symbols, cdr)?;
Ok(Expr::cons(car, cdr))
_ => Ok(expr.clone()),
Ok(Expr::Quote(Box::new(quasiquote_data(symbols, expr)?)))