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}