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
10pub type NativeMacroImpl = fn(&mut MacroCx<'_>, Expr, Bindings) -> Result<Expr>;
12
13pub trait LispMacro: Send + Sync {
16 fn symbol(&self) -> Symbol;
18 fn syntax_shape(&self) -> Arc<dyn Shape>;
20 fn expand(&self, cx: &mut MacroCx<'_>, input: Expr, captures: Bindings) -> Result<Expr>;
22}
23
24pub 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct MacroExpansionLimits {
38 pub max_depth: usize,
40 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 pub fn new(cx: &'a mut Cx, phase: Phase) -> Self {
68 Self::with_limits(cx, phase, MacroExpansionLimits::default())
69 }
70
71 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 pub fn phase(&self) -> Phase {
86 self.phase
87 }
88
89 pub fn env(&self) -> &Env {
91 &self.env
92 }
93
94 pub fn factory(&self) -> &dyn Factory {
96 self.cx.factory()
97 }
98
99 pub fn quote_expr(&self, expr: Expr) -> Result<Value> {
101 self.cx.factory().expr(expr)
102 }
103
104 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}