Skip to main content

cljrs_reader/
form.rs

1use std::mem;
2use std::sync::{Arc, Mutex, OnceLock};
3
4use cljrs_types::span::Span;
5
6/// A parsed Clojure form with its source location.
7///
8/// `PartialEq` ignores spans (and the expansion cache) so test assertions can
9/// compare forms without constructing exact span values.
10#[derive(Debug)]
11pub struct Form {
12    pub kind: FormKind,
13    pub span: Span,
14    /// Memoized macro expansion of this form when it is a macro call site.
15    ///
16    /// Owned by the form so it lives exactly as long as the call site does:
17    /// a function body or `loop` that evaluates the same form repeatedly
18    /// expands its macros once, while a freshly read form (a new REPL input)
19    /// always starts empty. See [`ExpansionCache`].
20    pub expansion: ExpansionCache,
21}
22
23impl Clone for Form {
24    fn clone(&self) -> Self {
25        // Only a list can be a macro call site. Its clones share one cache
26        // slot: `(fn ...)` copies its body into every closure it creates, and
27        // an expansion made while running one closure must serve the next.
28        let expansion = if matches!(self.kind, FormKind::List(_)) {
29            self.expansion.share()
30        } else {
31            ExpansionCache::default()
32        };
33        Self {
34            kind: self.kind.clone(),
35            span: self.span.clone(),
36            expansion,
37        }
38    }
39}
40
41impl Form {
42    pub fn new(kind: FormKind, span: Span) -> Self {
43        Self {
44            kind,
45            span,
46            expansion: ExpansionCache::default(),
47        }
48    }
49
50    /// Total heap bytes owned by this form tree (excluding the `Form` itself).
51    pub fn heap_size(&self) -> usize {
52        mem::size_of::<FormKind>() + self.kind.heap_size()
53    }
54
55    /// True when this form, used as a variadic `&` rest pattern, selects
56    /// Clojure's keyword-argument convention.
57    ///
58    /// A map-shaped rest pattern (`[& {:keys [a b]}]`) does not destructure the
59    /// rest *sequence*; the trailing arguments are first normalized into a map
60    /// (`Value::from_kwargs_rest`) and the pattern is applied to that.  Every
61    /// execution tier has to make this call the same way — the tree-walker in
62    /// `bind_fn_params`, and both lowerers on their way to `KnownFn::KwargsMap`
63    /// — so they share this predicate rather than three copies of the match.
64    /// Issue #368 was those copies disagreeing.
65    pub fn is_kwargs_rest_pattern(&self) -> bool {
66        matches!(self.kind, FormKind::Map(_))
67    }
68
69    /// The annotated form with every `^meta` wrapper removed.
70    ///
71    /// Returns `self` when the form carries no metadata. Stacked metadata
72    /// (`^:a ^:b x`) is peeled down to the innermost form.
73    pub fn unmeta(&self) -> &Form {
74        let mut form = self;
75        while let FormKind::Meta(_, inner) = &form.kind {
76            form = inner;
77        }
78        form
79    }
80
81    /// True when the value this form denotes *as data* can carry metadata.
82    ///
83    /// Inside `quote` every form is a literal, so whether an annotation lands
84    /// is a question about the form and needs no runtime test: `'^{:a 1} [1]`
85    /// carries it, `'^{:a 1} 42` cannot. Mirrors `supports_meta` in
86    /// `cljrs-runtime` over the values `form_to_value` produces — a reader
87    /// macro (`'x`, `@x`, `#'x`, `` `x ``) denotes a list, and `#(…)` denotes
88    /// the `fn*` list it expands to.
89    pub fn quoted_value_supports_meta(&self) -> bool {
90        match &self.kind {
91            FormKind::List(_)
92            | FormKind::Vector(_)
93            | FormKind::Map(_)
94            | FormKind::Set(_)
95            | FormKind::Symbol(_)
96            | FormKind::AutoSymbol(_)
97            | FormKind::AnonFn(_)
98            | FormKind::Quote(_)
99            | FormKind::SyntaxQuote(_)
100            | FormKind::Unquote(_)
101            | FormKind::UnquoteSplice(_)
102            | FormKind::Deref(_)
103            | FormKind::Var(_) => true,
104            FormKind::Meta(_, inner) => inner.quoted_value_supports_meta(),
105            _ => false,
106        }
107    }
108
109    /// True when an evaluated-position `^meta` annotation on this form becomes
110    /// *runtime* metadata on the value it produces.
111    ///
112    /// Only a form that constructs an `IObj` qualifies — a collection literal
113    /// (the empty list `()` included) or a function. Every other form (a call,
114    /// a symbol, `quote`, `if`, `do`) takes the annotation as a compile-time
115    /// hint and evaluates to an unannotated value, so `(meta ^{:a 1} (list 1))`
116    /// and `(meta ^{:a 1} x)` are both `nil`.
117    ///
118    /// Every execution tier consults this one predicate: the tree-walker in
119    /// `interp::eval`, and IR lowering in `lower::anf` for the JIT and AOT
120    /// paths. A tier that disagreed would make `meta` depend on how hot the
121    /// code got.
122    ///
123    /// Inside `quote` the rule does not apply: there the annotation is data and
124    /// lands on any value that can carry it (see
125    /// [`Form::quoted_value_supports_meta`]).
126    pub fn takes_runtime_meta(&self) -> bool {
127        match &self.kind {
128            FormKind::Vector(_) | FormKind::Map(_) | FormKind::Set(_) | FormKind::AnonFn(_) => true,
129            // `()` has no head, so it is not a call: it is the empty-list
130            // literal, and attaches exactly as `[]`, `{}` and `#{}` do.
131            FormKind::List(parts) if parts.is_empty() => true,
132            // Any other list is a call, except when it *is* a function form.
133            FormKind::List(_) => self.is_fn_form(),
134            // Metadata stacks: `^:a ^:b [1]` annotates the vector twice.
135            FormKind::Meta(_, inner) => inner.takes_runtime_meta(),
136            _ => false,
137        }
138    }
139
140    /// True when this form is a function form: a list headed by `fn` or `fn*`.
141    pub fn is_fn_form(&self) -> bool {
142        match &self.kind {
143            FormKind::List(parts) => matches!(
144                parts.first().map(|f| &f.kind),
145                Some(FormKind::Symbol(s)) if s == "fn" || s == "fn*"
146            ),
147            _ => false,
148        }
149    }
150
151    /// True when this form, used as a `^meta` annotation or a `defn` attr-map,
152    /// requests `:async`.
153    ///
154    /// Handles the keyword shorthand `^:async` and an explicit map such as
155    /// `^{:async true}` or `{:async true}`; a `false` or `nil` value does not
156    /// request it.
157    pub fn requests_async(&self) -> bool {
158        match &self.kind {
159            FormKind::Keyword(k) => k == "async",
160            FormKind::Map(entries) => entries.chunks(2).any(|kv| {
161                matches!(&kv[0].kind, FormKind::Keyword(k) if k == "async")
162                    && !matches!(
163                        kv.get(1).map(|f| &f.kind),
164                        None | Some(FormKind::Bool(false)) | Some(FormKind::Nil)
165                    )
166            }),
167            _ => false,
168        }
169    }
170
171    /// True when this form is an anonymous function that asks to be `^:async`,
172    /// in any spelling: an annotation on the whole form,
173    /// `^:async (fn [..] ..)`, or one accepted by [`Form::fn_args_request_async`]
174    /// on its arguments. Stacked annotations are searched in full.
175    ///
176    /// Only the tree-walker can build an async closure; IR lowering refuses a
177    /// body containing one of these, so every tier agrees on what calling it
178    /// returns.
179    pub fn is_async_fn_form(&self) -> bool {
180        let (metas, inner) = self.peel_meta();
181        if !inner.is_fn_form() {
182            return false;
183        }
184        if metas.iter().any(|m| m.requests_async()) {
185            return true;
186        }
187        let FormKind::List(parts) = &inner.kind else {
188            return false;
189        };
190        Form::fn_args_request_async(&parts[1..])
191    }
192
193    /// True when the arguments of an `fn`/`fn*` form (everything after the
194    /// head) request `:async` through metadata on the first argument,
195    /// `(fn ^:async [..] ..)` / `(fn ^:async name [..] ..)`, or on a single-arity
196    /// params vector that follows a name, `(fn name ^:async [..] ..)`. The last
197    /// is also what `(defn name ^:async [..] ..)` becomes once `defn` rebuilds
198    /// its `fn` arguments. Stacked annotations are searched in full.
199    pub fn fn_args_request_async(args: &[Form]) -> bool {
200        let requests = |f: &Form| f.peel_meta().0.iter().any(|m| m.requests_async());
201        let Some(first) = args.first() else {
202            return false;
203        };
204        if requests(first) {
205            return true;
206        }
207        first.as_symbol().is_some() && args.get(1).is_some_and(requests)
208    }
209
210    /// The `^meta` forms attached to this form, outermost first, together with
211    /// the annotated form itself.
212    pub fn peel_meta(&self) -> (Vec<&Form>, &Form) {
213        let mut metas = Vec::new();
214        let mut form = self;
215        while let FormKind::Meta(meta, inner) = &form.kind {
216            metas.push(meta.as_ref());
217            form = inner;
218        }
219        (metas, form)
220    }
221
222    // ── Structural views ──────────────────────────────────────────────────────
223    //
224    // Every accessor below reports the shape of [`Form::unmeta`], so an
225    // annotated form has the same structural shape as the form it annotates.
226
227    /// The symbol name, if this form is a symbol.
228    pub fn as_symbol(&self) -> Option<&str> {
229        match &self.unmeta().kind {
230            FormKind::Symbol(s) => Some(s),
231            _ => None,
232        }
233    }
234
235    /// The keyword name (without the leading `:`), if this form is a keyword.
236    pub fn as_keyword(&self) -> Option<&str> {
237        match &self.unmeta().kind {
238            FormKind::Keyword(k) => Some(k),
239            _ => None,
240        }
241    }
242
243    /// The string contents, if this form is a string literal.
244    pub fn as_string(&self) -> Option<&str> {
245        match &self.unmeta().kind {
246            FormKind::Str(s) => Some(s),
247            _ => None,
248        }
249    }
250
251    /// The elements, if this form is a list.
252    pub fn as_list(&self) -> Option<&[Form]> {
253        match &self.unmeta().kind {
254            FormKind::List(v) => Some(v),
255            _ => None,
256        }
257    }
258
259    /// The elements, if this form is a vector.
260    pub fn as_vector(&self) -> Option<&[Form]> {
261        match &self.unmeta().kind {
262            FormKind::Vector(v) => Some(v),
263            _ => None,
264        }
265    }
266
267    /// The flat key/value pairs, if this form is a map literal.
268    pub fn as_map(&self) -> Option<&[Form]> {
269        match &self.unmeta().kind {
270            FormKind::Map(v) => Some(v),
271            _ => None,
272        }
273    }
274}
275
276impl FormKind {
277    /// Heap bytes owned by this node and all children.
278    pub fn heap_size(&self) -> usize {
279        match self {
280            // Inline scalars — no heap.
281            FormKind::Nil
282            | FormKind::Bool(_)
283            | FormKind::Int(_)
284            | FormKind::Float(_)
285            | FormKind::Char(_)
286            | FormKind::Symbolic(_) => 0,
287
288            // String payloads.
289            FormKind::BigInt(s)
290            | FormKind::BigDecimal(s)
291            | FormKind::Ratio(s)
292            | FormKind::Str(s)
293            | FormKind::Regex(s)
294            | FormKind::Symbol(s)
295            | FormKind::Keyword(s)
296            | FormKind::AutoKeyword(s)
297            | FormKind::AutoSymbol(s) => s.capacity(),
298
299            // Vec<Form> — Vec overhead + recursive children.
300            FormKind::List(v)
301            | FormKind::Vector(v)
302            | FormKind::Map(v)
303            | FormKind::Set(v)
304            | FormKind::AnonFn(v) => vec_heap_size(v),
305
306            // Box<Form> — one Form on heap.
307            FormKind::Quote(f)
308            | FormKind::SyntaxQuote(f)
309            | FormKind::Unquote(f)
310            | FormKind::UnquoteSplice(f)
311            | FormKind::Deref(f)
312            | FormKind::Var(f) => mem::size_of::<Form>() + f.heap_size(),
313
314            // Two Box<Form>.
315            FormKind::Meta(a, b) => mem::size_of::<Form>() * 2 + a.heap_size() + b.heap_size(),
316
317            // String + Box<Form>.
318            FormKind::TaggedLiteral(s, f) => s.capacity() + mem::size_of::<Form>() + f.heap_size(),
319
320            FormKind::ReaderCond { clauses, .. } => vec_heap_size(clauses),
321        }
322    }
323}
324
325impl PartialEq for Form {
326    fn eq(&self, other: &Self) -> bool {
327        self.kind == other.kind
328    }
329}
330
331// ── Macro-expansion cache ─────────────────────────────────────────────────────
332
333/// One memoized macro expansion: valid while the call site resolves to the
334/// same macro (`macro_id`) and is evaluated in the same namespace (`ns`).
335#[derive(Debug)]
336pub struct CachedExpansion {
337    /// Identity of the macro function that produced `form` (its first arity's
338    /// process-unique id). Redefining the macro mints a new id, so a stale
339    /// expansion is never reused.
340    pub macro_id: u64,
341    /// Namespace the call site was expanded in; `::kw` and syntax-quote
342    /// resolution depend on it.
343    pub ns: Arc<str>,
344    /// The expansion. Held in an `Arc` so its own nested call sites keep a
345    /// stable address and therefore their own caches across evaluations.
346    pub form: Arc<Form>,
347}
348
349/// Per-call-site slot holding the last macro expansion of a [`Form`].
350///
351/// The tree-walking evaluator otherwise re-expands every macro call on every
352/// evaluation (converting the forms to values, running the macro, converting
353/// the result back), which dominated the cost of e.g. a `when` inside a hot
354/// `loop`. Clojure itself expands each call site once, at compile time, so
355/// reusing the expansion matches its semantics.
356///
357/// Clones of a list form share the slot (see `Form::clone`), so a function
358/// body copied into many closures expands each call site once in total. The
359/// shared cell is allocated lazily, on first store or first clone, so a form
360/// that is parsed and evaluated once pays nothing beyond an empty word.
361///
362/// A form whose `kind` is mutated in place after cloning must not be
363/// evaluated as a macro call afterwards, since it would share its source's
364/// expansion; no evaluator path does this (rewrites build fresh forms).
365#[derive(Default)]
366pub struct ExpansionCache(OnceLock<Arc<ExpansionSlot>>);
367
368type ExpansionSlot = Mutex<Option<Arc<CachedExpansion>>>;
369
370impl ExpansionCache {
371    /// The cached expansion, if one was made by `macro_id` in `ns`.
372    pub fn get(&self, macro_id: u64, ns: &str) -> Option<Arc<Form>> {
373        let slot = self.0.get()?.lock().unwrap_or_else(|e| e.into_inner());
374        match slot.as_ref() {
375            Some(c) if c.macro_id == macro_id && c.ns.as_ref() == ns => Some(c.form.clone()),
376            _ => None,
377        }
378    }
379
380    /// Record `form` as the expansion made by `macro_id` in `ns`, replacing
381    /// any previous entry. Returns the stored `Arc` so the caller evaluates
382    /// the very form the cache holds.
383    pub fn store(&self, macro_id: u64, ns: Arc<str>, form: Form) -> Arc<Form> {
384        let form = Arc::new(form);
385        let entry = Arc::new(CachedExpansion {
386            macro_id,
387            ns,
388            form: form.clone(),
389        });
390        *self.slot().lock().unwrap_or_else(|e| e.into_inner()) = Some(entry);
391        form
392    }
393
394    /// Drop any cached expansion (for every form sharing this slot).
395    pub fn clear(&self) {
396        if let Some(slot) = self.0.get() {
397            *slot.lock().unwrap_or_else(|e| e.into_inner()) = None;
398        }
399    }
400
401    /// A handle to the same slot, for a clone of the owning form.
402    fn share(&self) -> Self {
403        Self(OnceLock::from(self.slot().clone()))
404    }
405
406    fn slot(&self) -> &Arc<ExpansionSlot> {
407        self.0.get_or_init(Default::default)
408    }
409}
410
411impl std::fmt::Debug for ExpansionCache {
412    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413        f.write_str("ExpansionCache")
414    }
415}
416
417/// The payload of a `Form` node.
418#[derive(Debug, Clone, PartialEq)]
419pub enum FormKind {
420    // ── Atoms ─────────────────────────────────────────────────────────────────
421    Nil,
422    Bool(bool),
423    Int(i64),
424    BigInt(String),
425    Float(f64), // NaN != NaN per IEEE 754 — acceptable for AST equality
426    BigDecimal(String),
427    Ratio(String),
428    Char(char),
429    Str(String),
430    Regex(String),
431    /// `##Inf` → `INFINITY`, `##-Inf` → `NEG_INFINITY`, `##NaN` → `NAN`
432    Symbolic(f64),
433
434    // ── Identifiers ───────────────────────────────────────────────────────────
435    Symbol(String),
436    Keyword(String),
437    /// `::kw` / `::alias/kw` - the namespace is resolved against the reading
438    /// namespace by the evaluator, not by the reader.
439    AutoKeyword(String),
440    /// A symbol whose namespace is auto-resolved the same way. There is no
441    /// surface syntax for one; the reader produces it for a bare symbol key in
442    /// an auto-resolved namespaced map (`#::{a 1}`, `#::alias{a 1}`).
443    AutoSymbol(String),
444
445    // ── Collections ───────────────────────────────────────────────────────────
446    List(Vec<Form>),
447    Vector(Vec<Form>),
448    /// Flat key/value pairs; length is always even.
449    Map(Vec<Form>),
450    Set(Vec<Form>),
451
452    // ── Wrapping reader macros ────────────────────────────────────────────────
453    Quote(Box<Form>),
454    SyntaxQuote(Box<Form>),
455    Unquote(Box<Form>),
456    UnquoteSplice(Box<Form>),
457    Deref(Box<Form>),
458    /// `#'symbol`
459    Var(Box<Form>),
460    /// `^meta-form annotated-form` — raw meta form kept as-is; evaluator
461    /// expands shorthand (`:kw` → `{:kw true}`, `Sym` → `{:tag Sym}`).
462    Meta(Box<Form>, Box<Form>),
463
464    // ── Dispatch forms ────────────────────────────────────────────────────────
465    /// `#(…)` anonymous function literal
466    AnonFn(Vec<Form>),
467    /// `#tag form` tagged literal
468    TaggedLiteral(String, Box<Form>),
469
470    // ── Reader conditionals ───────────────────────────────────────────────────
471    /// All branches are kept; the runtime picks one with
472    /// `cljrs_runtime::builtins::form::select_reader_cond` (feature keys
473    /// `:cljrs`/legacy `:rust`, then `:default`, first match in clause order).
474    /// Never compare the feature keyword by hand.
475    /// `clauses` is flat: `[keyword, form, keyword, form, …]`.
476    ReaderCond {
477        splicing: bool,
478        clauses: Vec<Form>,
479    },
480}
481
482fn vec_heap_size(forms: &[Form]) -> usize {
483    mem::size_of_val(forms) + forms.iter().map(|f| f.heap_size()).sum::<usize>()
484}
485
486#[cfg(test)]
487mod expansion_cache_tests {
488    use super::*;
489
490    fn span() -> Span {
491        Span::new(Arc::new("<t>".to_string()), 0, 0, 1, 1)
492    }
493
494    fn list() -> Form {
495        Form::new(
496            FormKind::List(vec![Form::new(FormKind::Symbol("m".into()), span())]),
497            span(),
498        )
499    }
500
501    #[test]
502    fn empty_until_stored() {
503        assert!(list().expansion.get(1, "user").is_none());
504    }
505
506    #[test]
507    fn hit_requires_same_macro_and_namespace() {
508        let site = list();
509        site.expansion
510            .store(7, Arc::from("user"), Form::new(FormKind::Int(1), span()));
511        assert!(site.expansion.get(7, "user").is_some());
512        assert!(site.expansion.get(8, "user").is_none());
513        assert!(site.expansion.get(7, "other").is_none());
514    }
515
516    #[test]
517    fn clones_of_a_list_share_the_slot() {
518        let site = list();
519        let copy = site.clone();
520        copy.expansion
521            .store(7, Arc::from("user"), Form::new(FormKind::Int(1), span()));
522        assert!(site.expansion.get(7, "user").is_some());
523        site.expansion.clear();
524        assert!(copy.expansion.get(7, "user").is_none());
525    }
526
527    #[test]
528    fn non_list_clones_do_not_allocate_a_slot() {
529        let sym = Form::new(FormKind::Symbol("x".into()), span());
530        let copy = sym.clone();
531        assert!(sym.expansion.0.get().is_none());
532        assert!(copy.expansion.0.get().is_none());
533    }
534}