Skip to main content

sim/macros/
expand.rs

1use std::sync::Arc;
2
3use sim_kernel::{
4    Cx, Demand, Diagnostic, Error, Expr, MacroExpander, MacroId, Phase, PreparedArgs, Result,
5    Symbol, Value,
6};
7use sim_shape::{AnyShape, CaptureShape, ExactExprShape, ListShape, Shape};
8
9use crate::{
10    functions::{FunctionCase, FunctionObject},
11    macros::{
12        LispMacro, MacroCx, MacroExpansionLimits, MacroObject, macro_value_with_parser_trust,
13    },
14};
15
16/// Registers a macro in the context, trusting its parser, and returns its id.
17pub fn register_macro(cx: &mut Cx, mac: Arc<dyn LispMacro>) -> Result<MacroId> {
18    register_macro_with_parser_trust(cx, mac, true)
19}
20
21/// Registers a macro with an explicit parser-trust flag and returns its id.
22pub fn register_macro_with_parser_trust(
23    cx: &mut Cx,
24    mac: Arc<dyn LispMacro>,
25    parser_trusted: bool,
26) -> Result<MacroId> {
27    let symbol = mac.symbol();
28    cx.registry_mut()
29        .register_macro_value(symbol, macro_value_with_parser_trust(mac, parser_trusted))
30}
31
32/// Macro expander that resolves macros through the context registry, bounded by
33/// configurable expansion limits.
34pub struct RegistryMacroExpander {
35    limits: MacroExpansionLimits,
36}
37
38impl RegistryMacroExpander {
39    /// Creates an expander with the default expansion limits.
40    pub fn new() -> Self {
41        Self {
42            limits: MacroExpansionLimits::default(),
43        }
44    }
45
46    /// Creates an expander with the given expansion limits.
47    pub fn with_limits(limits: MacroExpansionLimits) -> Self {
48        Self { limits }
49    }
50}
51
52impl Default for RegistryMacroExpander {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl MacroExpander for RegistryMacroExpander {
59    fn expand_expr(&self, cx: &mut Cx, phase: Phase, expr: Expr) -> Result<Expr> {
60        let mut macro_cx = MacroCx::with_limits(cx, phase, self.limits);
61        expand_expr(&mut macro_cx, expr)
62    }
63}
64
65/// Fully expands macros in an expression within a macro context.
66pub fn expand_expr(cx: &mut MacroCx<'_>, expr: Expr) -> Result<Expr> {
67    expand_expr_with_depth(cx, expr, 0)
68}
69
70fn expand_expr_with_depth(cx: &mut MacroCx<'_>, expr: Expr, depth: usize) -> Result<Expr> {
71    cx.charge(1)?;
72    if depth > cx.max_depth() {
73        return Err(cx.budget_error(format!(
74            "macro expansion exceeded depth limit of {}",
75            cx.max_depth()
76        )));
77    }
78
79    match expr {
80        Expr::List(items) => expand_list(cx, items, depth),
81        Expr::Call { operator, args } => expand_call(cx, *operator, args, depth),
82        Expr::Vector(items) => Ok(Expr::Vector(expand_many(cx, items, depth)?)),
83        Expr::Map(entries) => Ok(Expr::Map(
84            entries
85                .into_iter()
86                .map(|(key, value)| {
87                    Ok((
88                        expand_expr_with_depth(cx, key, depth)?,
89                        expand_expr_with_depth(cx, value, depth)?,
90                    ))
91                })
92                .collect::<Result<Vec<_>>>()?,
93        )),
94        Expr::Set(items) => Ok(Expr::Set(expand_many(cx, items, depth)?)),
95        Expr::Infix {
96            operator,
97            left,
98            right,
99        } => Ok(Expr::Infix {
100            operator,
101            left: Box::new(expand_expr_with_depth(cx, *left, depth)?),
102            right: Box::new(expand_expr_with_depth(cx, *right, depth)?),
103        }),
104        Expr::Prefix { operator, arg } => Ok(Expr::Prefix {
105            operator,
106            arg: Box::new(expand_expr_with_depth(cx, *arg, depth)?),
107        }),
108        Expr::Postfix { operator, arg } => Ok(Expr::Postfix {
109            operator,
110            arg: Box::new(expand_expr_with_depth(cx, *arg, depth)?),
111        }),
112        Expr::Block(items) => Ok(Expr::Block(expand_many(cx, items, depth)?)),
113        Expr::Annotated { expr, annotations } => Ok(Expr::Annotated {
114            expr: Box::new(expand_expr_with_depth(cx, *expr, depth)?),
115            annotations: annotations
116                .into_iter()
117                .map(|(symbol, value)| Ok((symbol, expand_expr_with_depth(cx, value, depth)?)))
118                .collect::<Result<Vec<_>>>()?,
119        }),
120        Expr::Extension { tag, payload } => Ok(Expr::Extension {
121            tag,
122            payload: Box::new(expand_expr_with_depth(cx, *payload, depth)?),
123        }),
124        Expr::Quote { .. }
125        | Expr::Nil
126        | Expr::Bool(_)
127        | Expr::Number(_)
128        | Expr::Symbol(_)
129        | Expr::Local(_)
130        | Expr::String(_)
131        | Expr::Bytes(_) => Ok(expr),
132    }
133}
134
135fn expand_list(cx: &mut MacroCx<'_>, items: Vec<Expr>, depth: usize) -> Result<Expr> {
136    let Some(Expr::Symbol(symbol)) = items.first() else {
137        return Ok(Expr::List(expand_many(cx, items, depth)?));
138    };
139    let Some(value) = cx.cx.registry().macro_by_symbol(symbol).cloned() else {
140        return Ok(Expr::List(expand_many(cx, items, depth)?));
141    };
142    expand_macro_form(cx, value, Expr::List(items), depth)
143}
144
145fn expand_call(
146    cx: &mut MacroCx<'_>,
147    operator: Expr,
148    args: Vec<Expr>,
149    depth: usize,
150) -> Result<Expr> {
151    if let Expr::Symbol(symbol) = &operator
152        && let Some(value) = cx.cx.registry().macro_by_symbol(symbol).cloned()
153    {
154        let input = Expr::List(std::iter::once(operator).chain(args).collect::<Vec<_>>());
155        return expand_macro_form(cx, value, input, depth);
156    }
157
158    Ok(Expr::Call {
159        operator: Box::new(expand_expr_with_depth(cx, operator, depth)?),
160        args: expand_many(cx, args, depth)?,
161    })
162}
163
164fn expand_macro_form(
165    cx: &mut MacroCx<'_>,
166    value: Value,
167    input: Expr,
168    depth: usize,
169) -> Result<Expr> {
170    let symbol = macro_head_symbol(&input);
171    if !cx.cx.eval_policy().allow_macro_expansion(cx.phase()) {
172        let name = symbol
173            .as_ref()
174            .map(Symbol::to_string)
175            .unwrap_or_else(|| "<unknown>".to_owned());
176        return Err(Error::Eval(format!(
177            "macro expansion for {name} is not allowed during {:?} by {} eval policy",
178            cx.phase(),
179            cx.cx.eval_policy_name()
180        )));
181    }
182    cx.cx.require(&macro_phase_capability(cx.phase()))?;
183
184    let mac = ResolvedMacro::from_value(&value).ok_or(Error::TypeMismatch {
185        expected: "macro object",
186        found: "non-macro object",
187    })?;
188    let shape = mac.syntax_shape();
189    if shape.is_effectful() && !mac.parser_trusted() {
190        return Err(Error::Eval(format!(
191            "macro {} uses an effectful syntax shape in an untrusted parse position",
192            mac.symbol()
193        )));
194    }
195    let macro_symbol = mac.symbol();
196    let (matched, expansion_input) = match shape.check_expr(cx.cx, &input)? {
197        matched if matched.accepted => (matched, input),
198        rejected => {
199            let Some(alias) = symbol.as_ref() else {
200                return Err(wrong_macro_shape(
201                    cx,
202                    &macro_symbol,
203                    shape.as_ref(),
204                    rejected,
205                ));
206            };
207            if alias == &macro_symbol {
208                return Err(wrong_macro_shape(
209                    cx,
210                    &macro_symbol,
211                    shape.as_ref(),
212                    rejected,
213                ));
214            }
215            let normalized = retarget_macro_head(input.clone(), macro_symbol.clone());
216            match shape.check_expr(cx.cx, &normalized)? {
217                matched if matched.accepted => (matched, normalized),
218                _ => {
219                    return Err(wrong_macro_shape(
220                        cx,
221                        &macro_symbol,
222                        shape.as_ref(),
223                        rejected,
224                    ));
225                }
226            }
227        }
228    };
229    if !matched.accepted {
230        return Err(wrong_macro_shape(
231            cx,
232            &macro_symbol,
233            shape.as_ref(),
234            matched,
235        ));
236    }
237
238    let symbol = symbol.unwrap_or(macro_symbol);
239    cx.stack.push(symbol);
240    let expanded = mac.expand(cx, expansion_input, matched.captures);
241    let expanded = match expanded {
242        Ok(expanded) => expanded,
243        Err(error) => {
244            cx.stack.pop();
245            return Err(error);
246        }
247    };
248    let result = expand_expr_with_depth(cx, expanded, depth + 1);
249    cx.stack.pop();
250    result
251}
252
253enum ResolvedMacro<'a> {
254    Sdk(&'a MacroObject),
255    #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
256    Native(&'a sim_run_loaders::NativeAbiMacro),
257}
258
259impl<'a> ResolvedMacro<'a> {
260    fn from_value(value: &'a Value) -> Option<Self> {
261        if let Some(mac) = value.object().downcast_ref::<MacroObject>() {
262            return Some(Self::Sdk(mac));
263        }
264        #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
265        if let Some(mac) = value
266            .object()
267            .downcast_ref::<sim_run_loaders::NativeAbiMacro>()
268        {
269            return Some(Self::Native(mac));
270        }
271        None
272    }
273
274    fn symbol(&self) -> Symbol {
275        match self {
276            Self::Sdk(mac) => mac.macro_ref().symbol(),
277            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
278            Self::Native(mac) => mac.symbol(),
279        }
280    }
281
282    fn syntax_shape(&self) -> Arc<dyn Shape> {
283        match self {
284            Self::Sdk(mac) => mac.syntax_shape(),
285            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
286            Self::Native(mac) => mac.syntax_shape(),
287        }
288    }
289
290    fn parser_trusted(&self) -> bool {
291        match self {
292            Self::Sdk(mac) => mac.parser_trusted(),
293            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
294            Self::Native(mac) => mac.parser_trusted(),
295        }
296    }
297
298    fn expand(
299        &self,
300        cx: &mut MacroCx<'_>,
301        input: Expr,
302        captures: sim_shape::Bindings,
303    ) -> Result<Expr> {
304        match self {
305            Self::Sdk(mac) => mac.macro_ref().expand(cx, input, captures),
306            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
307            Self::Native(mac) => {
308                let _ = captures;
309                mac.expand(input)
310            }
311        }
312    }
313}
314
315fn macro_head_symbol(input: &Expr) -> Option<Symbol> {
316    match input {
317        Expr::List(items) => items.first().and_then(|item| match item {
318            Expr::Symbol(symbol) => Some(symbol.clone()),
319            _ => None,
320        }),
321        _ => None,
322    }
323}
324
325fn retarget_macro_head(input: Expr, target: Symbol) -> Expr {
326    match input {
327        Expr::List(mut items) if !items.is_empty() => {
328            items[0] = Expr::Symbol(target);
329            Expr::List(items)
330        }
331        other => other,
332    }
333}
334
335fn wrong_macro_shape(
336    cx: &mut MacroCx<'_>,
337    macro_symbol: &Symbol,
338    shape: &dyn Shape,
339    matched: sim_shape::ShapeMatch,
340) -> Error {
341    let mut diagnostics = vec![Diagnostic::error(format!(
342        "macro {} rejected syntax during {:?}",
343        macro_symbol,
344        cx.phase(),
345    ))];
346    if let Ok(doc) = shape.describe(cx.cx) {
347        diagnostics.push(Diagnostic::error(format!("syntax shape: {}", doc.name)));
348        diagnostics.extend(
349            doc.details
350                .into_iter()
351                .map(|detail| Diagnostic::error(format!("syntax detail: {detail}"))),
352        );
353    }
354    diagnostics.extend(matched.diagnostics);
355    Error::WrongShape {
356        expected: shape.id().unwrap_or(sim_kernel::ShapeId(0)),
357        diagnostics,
358    }
359}
360
361fn macro_phase_capability(phase: Phase) -> sim_kernel::CapabilityName {
362    match phase {
363        Phase::Read => sim_kernel::macro_expand_read_capability(),
364        Phase::Expand => sim_kernel::macro_expand_capability(),
365        Phase::Compile => sim_kernel::macro_expand_compile_capability(),
366        Phase::Eval => sim_kernel::macro_expand_eval_capability(),
367    }
368}
369
370fn expand_many(cx: &mut MacroCx<'_>, items: Vec<Expr>, depth: usize) -> Result<Vec<Expr>> {
371    items
372        .into_iter()
373        .map(|item| expand_expr_with_depth(cx, item, depth))
374        .collect()
375}
376
377/// Builds the `macroexpand` function object that expands a quoted expression.
378pub fn macroexpand_function(
379    case_id: sim_kernel::CaseId,
380    function_id: sim_kernel::FunctionId,
381    symbol: Symbol,
382) -> FunctionObject {
383    FunctionObject::new(
384        function_id,
385        symbol.clone(),
386        vec![FunctionCase {
387            id: case_id,
388            name: Symbol::qualified(symbol.to_string(), "expr"),
389            args: Arc::new(ListShape::new(vec![Arc::new(CaptureShape::new(
390                Symbol::new("expr"),
391                Arc::new(AnyShape),
392            ))])),
393            result: Some(Arc::new(AnyShape)),
394            demand: vec![Demand::Value],
395            priority: 10,
396            implementation: macroexpand_impl,
397        }],
398    )
399}
400
401fn macroexpand_impl(
402    cx: &mut Cx,
403    prepared: &PreparedArgs,
404    _bindings: sim_shape::Bindings,
405) -> Result<Value> {
406    let expr = prepared
407        .get(0)
408        .ok_or_else(|| Error::Eval("macroexpand expects one expression".to_owned()))?
409        .object()
410        .as_expr(cx)?;
411    let expanded = cx.expand_macros(Phase::Expand, expr)?;
412    cx.factory().expr(expanded)
413}
414
415/// Builds a shape matching an exact head symbol, for macro syntax.
416pub fn literal_head_shape(symbol: Symbol) -> Arc<dyn Shape> {
417    Arc::new(ExactExprShape::new(Expr::Symbol(symbol)))
418}
419
420/// Builds a list shape with a fixed head symbol followed by the given tail.
421pub fn list_macro_shape(head: Symbol, tail: Vec<Arc<dyn Shape>>) -> Arc<dyn Shape> {
422    let items = std::iter::once(literal_head_shape(head))
423        .chain(tail)
424        .collect::<Vec<_>>();
425    Arc::new(ListShape::new(items))
426}
427
428/// Builds a list shape with a fixed head and tail plus a trailing rest shape.
429pub fn list_macro_shape_with_rest(
430    head: Symbol,
431    fixed_tail: Vec<Arc<dyn Shape>>,
432    rest: Arc<dyn Shape>,
433) -> Arc<dyn Shape> {
434    let items = std::iter::once(literal_head_shape(head))
435        .chain(fixed_tail)
436        .collect::<Vec<_>>();
437    Arc::new(ListShape::with_rest(items, rest))
438}
439
440/// Builds a macro syntax shape that captures named positional parameters and an
441/// optional rest parameter after the head symbol.
442pub fn positional_macro_shape(
443    head: Symbol,
444    fixed: &[Symbol],
445    rest: Option<&Symbol>,
446) -> Arc<dyn Shape> {
447    let fixed_tail = fixed
448        .iter()
449        .cloned()
450        .map(|name| Arc::new(CaptureShape::new(name, Arc::new(AnyShape))) as Arc<dyn Shape>)
451        .collect::<Vec<_>>();
452    match rest {
453        Some(rest) => list_macro_shape_with_rest(
454            head,
455            fixed_tail,
456            Arc::new(CaptureShape::new(rest.clone(), Arc::new(AnyShape))),
457        ),
458        None => list_macro_shape(head, fixed_tail),
459    }
460}