1
//! Quasiquote / unquote / unquote-splicing expansion.
2
//!
3
//! `expand_quasiquote` walks an expression in quasiquote context,
4
//! resolving each `,form` (Unquote) against the symbol table and
5
//! splicing each `,@form` (UnquoteSplicing) into its parent list.
6
//! The result wraps the fully resolved tree in a single outer
7
//! `Quote` so downstream callers see a constant-foldable value.
8

            
9
use crate::ast::Expr;
10
use crate::error::{Error, Result};
11
use crate::runtime::SymbolTable;
12

            
13
use super::eval::resolve_arg;
14
use super::format::format_expr;
15

            
16
/// `,name` substitutes the name's bound value verbatim — for a macro parameter
17
/// its argument FORM — without chasing that form through the caller's bindings
18
/// (which would bake e.g. a let-bound closure's local into the expansion).
19
18260
fn unquoted_value(symbols: &mut SymbolTable, inner: &Expr) -> Result<Expr> {
20
18260
    if let Expr::Symbol(name) = inner
21
18177
        && let Some(value) = symbols.lookup(name).and_then(|sym| sym.value().cloned())
22
    {
23
18177
        return Ok(value);
24
83
    }
25
83
    resolve_arg(symbols, inner)
26
18260
}
27

            
28
9130
pub(in crate::compiler) fn expand_quasiquote(
29
9130
    symbols: &mut SymbolTable,
30
9130
    expr: &Expr,
31
9130
) -> Result<Expr> {
32
7636
    fn splice_elements(resolved: Expr) -> Result<Vec<Expr>> {
33
7636
        match resolved {
34
1411
            Expr::Nil => Ok(Vec::new()),
35
            Expr::List(elems) => Ok(elems),
36
6225
            Expr::Quote(inner) => match *inner {
37
                Expr::Nil => Ok(Vec::new()),
38
6225
                Expr::List(elems) => Ok(elems),
39
                other => Err(Error::Compile(format!(
40
                    "unquote-splicing requires a list, got {}",
41
                    format_expr(&other)
42
                ))),
43
            },
44
            other => Err(Error::Compile(format!(
45
                "unquote-splicing requires a list, got {}",
46
                format_expr(&other)
47
            ))),
48
        }
49
7636
    }
50

            
51
47476
    fn quasiquote_data(symbols: &mut SymbolTable, expr: &Expr) -> Result<Expr> {
52
47476
        match expr {
53
10624
            Expr::Unquote(inner) => unquoted_value(symbols, inner),
54
            Expr::UnquoteSplicing(_) => Err(Error::Compile(
55
                "unquote-splicing outside of list in quasiquote".to_string(),
56
            )),
57
14940
            Expr::List(elems) => {
58
14940
                let mut resolved: Vec<Expr> = Vec::new();
59
45982
                for elem in elems {
60
45982
                    match elem {
61
7636
                        Expr::UnquoteSplicing(inner) => {
62
7636
                            let spliced = unquoted_value(symbols, inner)?;
63
7636
                            resolved.extend(splice_elements(spliced)?);
64
                        }
65
38346
                        _ => resolved.push(quasiquote_data(symbols, elem)?),
66
                    }
67
                }
68
14940
                Ok(Expr::List(resolved))
69
            }
70
            Expr::Cons(car, cdr) => {
71
                let car = quasiquote_data(symbols, car)?;
72
                let cdr = quasiquote_data(symbols, cdr)?;
73
                Ok(Expr::cons(car, cdr))
74
            }
75
21912
            _ => Ok(expr.clone()),
76
        }
77
47476
    }
78

            
79
9130
    Ok(Expr::Quote(Box::new(quasiquote_data(symbols, expr)?)))
80
9130
}