Skip to main content

sim/macros/
model.rs

1use std::sync::Arc;
2
3use sim_kernel::{Cx, Env, Error, Expr, Factory, Phase, Result, Symbol, Value};
4use sim_shape::{Bindings, Shape};
5
6const MAX_MACRO_EXPANSION_DEPTH: usize = 128;
7const MAX_MACRO_EXPANSION_STEPS: usize = 16_384;
8const MAX_MACRO_STACK_DISPLAY: usize = 8;
9
10/// Signature of a native macro expansion function.
11pub type NativeMacroImpl = fn(&mut MacroCx<'_>, Expr, Bindings) -> Result<Expr>;
12
13/// Contract for an expandable macro: its name, its syntax shape, and the
14/// expansion that rewrites a matched form into a new expression.
15pub trait LispMacro: Send + Sync {
16    /// Returns the symbol the macro is registered under.
17    fn symbol(&self) -> Symbol;
18    /// Returns the shape the macro's call syntax must match.
19    fn syntax_shape(&self) -> Arc<dyn Shape>;
20    /// Expands a matched input form into a replacement expression.
21    fn expand(&self, cx: &mut MacroCx<'_>, input: Expr, captures: Bindings) -> Result<Expr>;
22}
23
24/// Expansion context passed to macros: the evaluation context, current phase,
25/// environment, expansion budget, and the active macro stack.
26pub struct MacroCx<'a> {
27    pub(crate) cx: &'a mut Cx,
28    pub(crate) phase: Phase,
29    env: Env,
30    budget: MacroExpansionBudget,
31    gensym_counter: u64,
32    pub(crate) stack: Vec<Symbol>,
33}
34
35/// Bounds that stop runaway macro expansion.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct MacroExpansionLimits {
38    /// Maximum recursive expansion depth.
39    pub max_depth: usize,
40    /// Maximum number of expansion steps charged against the budget.
41    pub max_steps: usize,
42}
43
44impl Default for MacroExpansionLimits {
45    fn default() -> Self {
46        Self {
47            max_depth: MAX_MACRO_EXPANSION_DEPTH,
48            max_steps: MAX_MACRO_EXPANSION_STEPS,
49        }
50    }
51}
52
53#[derive(Clone, Debug)]
54struct MacroExpansionBudget {
55    limits: MacroExpansionLimits,
56    steps: usize,
57}
58
59impl MacroExpansionBudget {
60    fn new(limits: MacroExpansionLimits) -> Self {
61        Self { limits, steps: 0 }
62    }
63}
64
65impl<'a> MacroCx<'a> {
66    /// Creates an expansion context with the default expansion limits.
67    pub fn new(cx: &'a mut Cx, phase: Phase) -> Self {
68        Self::with_limits(cx, phase, MacroExpansionLimits::default())
69    }
70
71    /// Creates an expansion context with explicit expansion limits.
72    pub fn with_limits(cx: &'a mut Cx, phase: Phase, limits: MacroExpansionLimits) -> Self {
73        let env = cx.env().clone();
74        Self {
75            cx,
76            phase,
77            env,
78            budget: MacroExpansionBudget::new(limits),
79            gensym_counter: 0,
80            stack: Vec::new(),
81        }
82    }
83
84    /// Returns the phase the macro is being expanded in.
85    pub fn phase(&self) -> Phase {
86        self.phase
87    }
88
89    /// Returns the environment captured for this expansion.
90    pub fn env(&self) -> &Env {
91        &self.env
92    }
93
94    /// Returns the object factory of the underlying context.
95    pub fn factory(&self) -> &dyn Factory {
96        self.cx.factory()
97    }
98
99    /// Quotes an expression into a value through the factory.
100    pub fn quote_expr(&self, expr: Expr) -> Result<Value> {
101        self.cx.factory().expr(expr)
102    }
103
104    /// Generates a fresh hygienic symbol scoped to the active macro.
105    pub fn hygienic_symbol(&mut self, prefix: impl AsRef<str>) -> Symbol {
106        self.gensym_counter += 1;
107        let macro_name = self
108            .stack
109            .last()
110            .map(Symbol::as_qualified_str)
111            .unwrap_or_else(|| "anonymous".to_owned());
112        Symbol::qualified(
113            format!("macro/{macro_name}"),
114            format!("{}${}", prefix.as_ref(), self.gensym_counter),
115        )
116    }
117
118    pub(crate) fn charge(&mut self, amount: usize) -> Result<()> {
119        self.budget.steps = self
120            .budget
121            .steps
122            .checked_add(amount)
123            .ok_or_else(|| self.budget_error("macro expansion step counter overflowed"))?;
124        if self.budget.steps > self.budget.limits.max_steps {
125            return Err(self.budget_error(format!(
126                "macro expansion exceeded step limit of {}",
127                self.budget.limits.max_steps
128            )));
129        }
130        Ok(())
131    }
132
133    pub(crate) fn max_depth(&self) -> usize {
134        self.budget.limits.max_depth
135    }
136
137    pub(crate) fn budget_error(&self, message: impl Into<String>) -> Error {
138        Error::Eval(format!("{}{}", message.into(), self.stack_suffix()))
139    }
140
141    fn stack_suffix(&self) -> String {
142        if self.stack.is_empty() {
143            return String::new();
144        }
145        let skip = self.stack.len().saturating_sub(MAX_MACRO_STACK_DISPLAY);
146        let mut names = self
147            .stack
148            .iter()
149            .skip(skip)
150            .map(Symbol::to_string)
151            .collect::<Vec<_>>();
152        if skip > 0 {
153            names.insert(0, "...".to_owned());
154        }
155        format!(" while expanding {}", names.join(" -> "))
156    }
157}