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 = "codec-lisp", feature = "shape"))]
256    LoaderSource(&'a sim_run_loaders::SourceTemplateMacro),
257    #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
258    Native(&'a sim_run_loaders::NativeAbiMacro),
259}
260
261impl<'a> ResolvedMacro<'a> {
262    fn from_value(value: &'a Value) -> Option<Self> {
263        if let Some(mac) = value.object().downcast_ref::<MacroObject>() {
264            return Some(Self::Sdk(mac));
265        }
266        #[cfg(all(feature = "codec-lisp", feature = "shape"))]
267        if let Some(mac) = value
268            .object()
269            .downcast_ref::<sim_run_loaders::SourceTemplateMacro>()
270        {
271            return Some(Self::LoaderSource(mac));
272        }
273        #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
274        if let Some(mac) = value
275            .object()
276            .downcast_ref::<sim_run_loaders::NativeAbiMacro>()
277        {
278            return Some(Self::Native(mac));
279        }
280        None
281    }
282
283    fn symbol(&self) -> Symbol {
284        match self {
285            Self::Sdk(mac) => mac.macro_ref().symbol(),
286            #[cfg(all(feature = "codec-lisp", feature = "shape"))]
287            Self::LoaderSource(mac) => mac.symbol(),
288            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
289            Self::Native(mac) => mac.symbol(),
290        }
291    }
292
293    fn syntax_shape(&self) -> Arc<dyn Shape> {
294        match self {
295            Self::Sdk(mac) => mac.syntax_shape(),
296            #[cfg(all(feature = "codec-lisp", feature = "shape"))]
297            Self::LoaderSource(mac) => mac.syntax_shape(),
298            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
299            Self::Native(mac) => mac.syntax_shape(),
300        }
301    }
302
303    fn parser_trusted(&self) -> bool {
304        match self {
305            Self::Sdk(mac) => mac.parser_trusted(),
306            #[cfg(all(feature = "codec-lisp", feature = "shape"))]
307            Self::LoaderSource(mac) => mac.parser_trusted(),
308            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
309            Self::Native(mac) => mac.parser_trusted(),
310        }
311    }
312
313    fn expand(
314        &self,
315        cx: &mut MacroCx<'_>,
316        input: Expr,
317        captures: sim_shape::Bindings,
318    ) -> Result<Expr> {
319        match self {
320            Self::Sdk(mac) => mac.macro_ref().expand(cx, input, captures),
321            #[cfg(all(feature = "codec-lisp", feature = "shape"))]
322            Self::LoaderSource(mac) => {
323                let _ = cx;
324                mac.expand(input, captures)
325            }
326            #[cfg(all(feature = "dynamic-native", not(target_arch = "wasm32")))]
327            Self::Native(mac) => {
328                let _ = captures;
329                mac.expand(input)
330            }
331        }
332    }
333}
334
335fn macro_head_symbol(input: &Expr) -> Option<Symbol> {
336    match input {
337        Expr::List(items) => items.first().and_then(|item| match item {
338            Expr::Symbol(symbol) => Some(symbol.clone()),
339            _ => None,
340        }),
341        _ => None,
342    }
343}
344
345fn retarget_macro_head(input: Expr, target: Symbol) -> Expr {
346    match input {
347        Expr::List(mut items) if !items.is_empty() => {
348            items[0] = Expr::Symbol(target);
349            Expr::List(items)
350        }
351        other => other,
352    }
353}
354
355fn wrong_macro_shape(
356    cx: &mut MacroCx<'_>,
357    macro_symbol: &Symbol,
358    shape: &dyn Shape,
359    matched: sim_shape::ShapeMatch,
360) -> Error {
361    let mut diagnostics = vec![Diagnostic::error(format!(
362        "macro {} rejected syntax during {:?}",
363        macro_symbol,
364        cx.phase(),
365    ))];
366    if let Ok(doc) = shape.describe(cx.cx) {
367        diagnostics.push(Diagnostic::error(format!("syntax shape: {}", doc.name)));
368        diagnostics.extend(
369            doc.details
370                .into_iter()
371                .map(|detail| Diagnostic::error(format!("syntax detail: {detail}"))),
372        );
373    }
374    diagnostics.extend(matched.diagnostics);
375    Error::WrongShape {
376        expected: shape.id().unwrap_or(sim_kernel::ShapeId(0)),
377        diagnostics,
378    }
379}
380
381fn macro_phase_capability(phase: Phase) -> sim_kernel::CapabilityName {
382    match phase {
383        Phase::Read => sim_kernel::macro_expand_read_capability(),
384        Phase::Expand => sim_kernel::macro_expand_capability(),
385        Phase::Compile => sim_kernel::macro_expand_compile_capability(),
386        Phase::Eval => sim_kernel::macro_expand_eval_capability(),
387    }
388}
389
390fn expand_many(cx: &mut MacroCx<'_>, items: Vec<Expr>, depth: usize) -> Result<Vec<Expr>> {
391    items
392        .into_iter()
393        .map(|item| expand_expr_with_depth(cx, item, depth))
394        .collect()
395}
396
397/// Builds the `macroexpand` function object that expands a quoted expression.
398pub fn macroexpand_function(
399    case_id: sim_kernel::CaseId,
400    function_id: sim_kernel::FunctionId,
401    symbol: Symbol,
402) -> FunctionObject {
403    FunctionObject::new(
404        function_id,
405        symbol.clone(),
406        vec![FunctionCase {
407            id: case_id,
408            name: Symbol::qualified(symbol.to_string(), "expr"),
409            args: Arc::new(ListShape::new(vec![Arc::new(CaptureShape::new(
410                Symbol::new("expr"),
411                Arc::new(AnyShape),
412            ))])),
413            result: Some(Arc::new(AnyShape)),
414            demand: vec![Demand::Value],
415            priority: 10,
416            implementation: macroexpand_impl,
417        }],
418    )
419}
420
421fn macroexpand_impl(
422    cx: &mut Cx,
423    prepared: &PreparedArgs,
424    _bindings: sim_shape::Bindings,
425) -> Result<Value> {
426    let expr = prepared
427        .get(0)
428        .ok_or_else(|| Error::Eval("macroexpand expects one expression".to_owned()))?
429        .object()
430        .as_expr(cx)?;
431    let expanded = cx.expand_macros(Phase::Expand, expr)?;
432    cx.factory().expr(expanded)
433}
434
435/// Builds a shape matching an exact head symbol, for macro syntax.
436pub fn literal_head_shape(symbol: Symbol) -> Arc<dyn Shape> {
437    Arc::new(ExactExprShape::new(Expr::Symbol(symbol)))
438}
439
440/// Builds a list shape with a fixed head symbol followed by the given tail.
441pub fn list_macro_shape(head: Symbol, tail: Vec<Arc<dyn Shape>>) -> Arc<dyn Shape> {
442    let items = std::iter::once(literal_head_shape(head))
443        .chain(tail)
444        .collect::<Vec<_>>();
445    Arc::new(ListShape::new(items))
446}
447
448/// Builds a list shape with a fixed head and tail plus a trailing rest shape.
449pub fn list_macro_shape_with_rest(
450    head: Symbol,
451    fixed_tail: Vec<Arc<dyn Shape>>,
452    rest: Arc<dyn Shape>,
453) -> Arc<dyn Shape> {
454    let items = std::iter::once(literal_head_shape(head))
455        .chain(fixed_tail)
456        .collect::<Vec<_>>();
457    Arc::new(ListShape::with_rest(items, rest))
458}
459
460/// Builds a macro syntax shape that captures named positional parameters and an
461/// optional rest parameter after the head symbol.
462pub fn positional_macro_shape(
463    head: Symbol,
464    fixed: &[Symbol],
465    rest: Option<&Symbol>,
466) -> Arc<dyn Shape> {
467    let fixed_tail = fixed
468        .iter()
469        .cloned()
470        .map(|name| Arc::new(CaptureShape::new(name, Arc::new(AnyShape))) as Arc<dyn Shape>)
471        .collect::<Vec<_>>();
472    match rest {
473        Some(rest) => list_macro_shape_with_rest(
474            head,
475            fixed_tail,
476            Arc::new(CaptureShape::new(rest.clone(), Arc::new(AnyShape))),
477        ),
478        None => list_macro_shape(head, fixed_tail),
479    }
480}