logicaffeine_language/parser/lgx_lower/naming.rs
1/// The name of a component's render function: `__render_<Name>`. The VM builds the same name
2/// from a `Mount`, so the two sides agree without a table between them.
3pub fn render_fn_name(component: &str) -> String {
4 format!("__render_{component}")
5}
6
7/// `__View` — the enum a page program's View values are: one variant per `-> View` template,
8/// carrying that template's arguments (`lgx_lower::views`). Registered by the discovery pass, so
9/// every engine is handed it.
10pub fn view_type_name() -> &'static str {
11 "__View"
12}
13
14/// `__View_<f>` — the variant a call of template `f` is.
15pub fn view_variant_name(template: &str) -> String {
16 format!("__View_{template}")
17}
18
19/// `__View_none` — the empty view: what a View prop holds before anything is handed to it.
20pub fn empty_view_variant_name() -> &'static str {
21 "__View_none"
22}
23
24/// `__view_<f>(depth, scope, params…)` — template `f`'s nodes drawn onto the tape, `depth` views
25/// deep.
26pub fn view_draw_fn_name(template: &str) -> String {
27 format!("__view_{template}")
28}
29
30/// `__lgx_place_view(view, scope, depth)` — draw whichever template a View value is a call of.
31pub fn place_view_fn_name() -> &'static str {
32 "__lgx_place_view"
33}
34
35/// The parameter a drawing takes how deep it is nested in its own kind in: a template among
36/// templates (`__view_<f>`), a served component among served components (`__serve_<C>`). Past
37/// `RENDER_DEPTH_LIMIT` the drawing fails in the render-depth words.
38pub fn draw_depth_param() -> &'static str {
39 "__lgx_depth"
40}
41
42/// The parameter a template's drawing takes the placing component's scope class in (`""` when
43/// that component has no `Style:`).
44pub fn view_scope_param() -> &'static str {
45 "__lgx_scope"
46}
47
48/// The parameter [`place_view_fn_name`] takes the View value in.
49pub fn view_value_param() -> &'static str {
50 "__lgx_view"
51}
52
53/// The module binding that remembers WHERE one updatable site's node landed.
54///
55/// The compiled half of Stage B: the render function writes each patchable site's node into its
56/// own global, and a per-turn patcher writes new values into those nodes — so a turn that moves
57/// one field costs one `setText` instead of clearing the mount and rebuilding the subtree.
58///
59/// The name is derived from the site's `SiteDeps::key()` (`[email protected]` → `__SITE_App_text_0_1`), so
60/// the dependency walk, the render walk and the patcher all address a site by ONE string.
61pub fn site_binding(component: &str, key: &str) -> String {
62 let sanitized: String = key
63 .chars()
64 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
65 .collect();
66 format!("__SITE_{component}_{sanitized}")
67}
68
69/// The per-turn patcher: `__patch_<Component>_<turn>`.
70pub fn patch_fn_name(component: &str, turn: &str) -> String {
71 let sanitized: String = turn
72 .chars()
73 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
74 .collect();
75 format!("__patch_{component}_{sanitized}")
76}
77
78/// The module-level binding a component's state field lives in. Prefixed with the component's
79/// name so two components' `n` are two different bindings.
80/// The parameter every instance-aware synthesized function carries: WHICH instance it is acting
81/// for. A component's state is a map from this key to that instance's value, so two placements of
82/// one component keep separate records, each instance addressed by its key path.
83///
84/// The ROOT instance's key is the empty string, so a component placed once — every page that
85/// worked before — is the general case with a constant key rather than a second mechanism.
86pub fn instance_key_param() -> &'static str {
87 "__lgx_key"
88}
89
90pub fn state_binding(component: &str, field: &str) -> String {
91 format!("__S_{component}_{field}")
92}
93
94/// The class a component's `Style:` is scoped to, and which its own elements carry. The server
95/// walk stamps the same class, or the two would render the same page with different class lists.
96/// The module binding holding whether this component's stylesheet has been emitted in the CURRENT
97/// paint. A `Style:` block is one `<style>` per component TYPE per render pass, however many
98/// instances render: the first instance to render claims it, and a new paint clears the claims.
99pub fn styled_binding(component: &str) -> String {
100 format!("__STYLED_{component}")
101}
102
103pub fn scope_class(component: &str) -> String {
104 format!("lgx-{}", component.to_ascii_lowercase())
105}
106
107/// `__html_<Component>()` — the server-rendered markup of a component's view.
108/// The parameter a rendered component reads its `{children}` out of in a BROWSER: the node
109/// holding the caller's children, or `0` for a placement that gave none.
110///
111/// ★ A HANDLE, WHERE THE SERVER PASSES A STRING. Both tiers render the children in the CALLER's
112/// scope and hand the finished thing down — a server can finish markup into text, and a browser
113/// finishes it into nodes, so the same rule takes the shape each tier has for "already drawn".
114pub fn render_slot_param() -> &'static str {
115 "__lgx_slot"
116}
117
118/// The parameter a layout's render reads its `{block <name>}` out of: the node holding what the
119/// extending view drew for that block in its own scope, or `0` when it filled none and the layout
120/// draws its default.
121pub fn block_slot_param(block: &str) -> String {
122 format!("__lgx_block_{block}")
123}
124
125/// `__html_<C>()` — component `C`'s markup, as `html of C` asks for it: `C` drawn by its server
126/// render into a root no host holds, serialized, and the root discarded.
127pub fn html_fn_name(component: &str) -> String {
128 format!("__html_{component}")
129}
130
131/// `__serve_<C>(props…, slot, blocks…)` — component `C` drawn onto the tape to be serialized: the
132/// same walk as its `__render_<C>` over its initial state, writing only the records its markup is
133/// made of (`LowerTrail::serving`).
134pub fn serve_fn_name(component: &str) -> String {
135 format!("__serve_{component}")
136}
137
138/// Is this a name the COMPILER wrote, rather than one the author typed?
139///
140/// ★ AN AUTHOR CANNOT ACT ON A DIAGNOSTIC ABOUT CODE THEY DID NOT WRITE. Every LGX page lowers
141/// into synthesized functions — a dispatcher, a render, a turn — and an analysis that runs over
142/// the lowered program will happily report on those. It did: opening any page with a handler put
143/// two `use-after-move` errors in the editor about `__lgx_sel`, a dispatcher PARAMETER, spanning
144/// the whole file, advising the author to "Use Show instead". A name they have never seen, at a
145/// place they cannot look, about a transfer they did not write.
146///
147/// The rule lives HERE, beside the functions that mint the names, for the same reason
148/// `is_browser_only_lowering` does: a consumer matching strings of its own would be a second
149/// spelling of a decision only this module makes, and it would fall out of step the first time a
150/// new family is added. This is the WIDER question of the two — `__html_` is compiler-written and
151/// is not browser-only — so the two predicates are separate on purpose.
152pub fn is_compiler_written(name: &str) -> bool {
153 // ★ ONE MARK, NOT A LIST OF FAMILIES.
154 //
155 // The first version of this enumerated the prefixes — `__lgx_`, `__render_`, `__act_`, and so
156 // on — and was ALREADY INCOMPLETE when it was written: the lowering also mints `__ARG_`,
157 // `__DUE_`, `__SITE_`, `__STYLED_`, `__Store_`, `__error_page_`, `__middleware_`, and bare
158 // temporaries like `__out` and `__item` that begin no family at all. An enumeration of a set
159 // that grows every time a construct lands is a list that is wrong between the two commits, and
160 // wrong SILENTLY — the symptom is a false error in someone's editor, not a failing test.
161 //
162 // Every name this module mints begins `__`, and nothing an author writes does: the whole
163 // stdlib contains no `__` identifier. So the leading underscores ARE the mark, and a new
164 // construct inherits the answer by spelling its temporaries the way every other one does.
165 name.starts_with("__")
166}
167
168/// ★ WHICH SYNTHESIZED FUNCTIONS NEED A DOCUMENT — the one place the answer lives.
169///
170/// A component lowers into two families. `__html_<Name>` builds a string out of `htmlEscape` and
171/// `+`, which is ordinary code on any tier. `__render_`, `__act_`, `__mount_`, `__bind_`,
172/// `__patch_`, `__dir_`, `__cb_` and the `__lgx_*` dispatcher and page drivers call `createElement` and `appendChild` —
173/// a browser's document, which a server binary does not have.
174///
175/// Emitting the second family into AOT-Rust is what made the component model look unshippable, and
176/// the blanket refusal that followed meant **per-request server rendering did not exist in
177/// production**: markup could only be produced at BUILD time by pre-rendering, so nothing whose
178/// content depends on the request could be server-rendered at all.
179///
180/// The names are MINTED HERE, by the functions directly above, so this is where the question is
181/// answerable without guessing. A backend asking "is this browser-only?" by matching strings of
182/// its own would be a second spelling of a decision only this module makes.
183pub fn is_browser_only_lowering(name: &str) -> bool {
184 // The per-COMPONENT families. Each is `<prefix><Component>[_<member>]`, so a prefix is the
185 // whole rule and it cannot be extended by accident — a new one would need a new prefix here.
186 const NEEDS_A_DOCUMENT: &[&str] = &[
187 "__render_",
188 "__act_",
189 "__mount_",
190 "__bind_",
191 "__patch_",
192 "__dir_",
193 "__cb_",
194 "__join_",
195 "__publish_",
196 "__refresh_",
197 "__sh_",
198 ];
199 if NEEDS_A_DOCUMENT.iter().any(|p| name.starts_with(p)) {
200 return true;
201 }
202 // ★ THE `__lgx_` FAMILY IS ENUMERATED, NOT PREFIX-MATCHED, AND THE DEFAULT IS "EMIT IT".
203 //
204 // This was `name.starts_with("__lgx_")` with ONE exception for `__lgx_route_handler`, and it
205 // broke the moment the router grew: `__lgx_route_for`, `__lgx_route_status_for`,
206 // `__lgx_route_ctype_for`, `__lgx_route_headers_for` and `__lgx_route_response_for` were all
207 // skipped from the server binary while `routeWith` and friends still CALLED them, so **every
208 // program with a `## Route` failed to build** with "cannot find function `__lgx_route_for`".
209 // Nothing caught it on the tree-walker or the VM, which resolve lazily; only rustc says so.
210 //
211 // The two sets are not symmetric, and that is what decides the shape. The browser-only names
212 // are a CLOSED set — the dispatcher, the listener table's answer and retirement, the
213 // page-level drivers, each minted by a named function just below — while the server-side
214 // `__lgx_` names are an
215 // OPEN family that grows whenever the router learns a new question. Enumerating the closed set
216 // and defaulting to EMIT means a new router walk works with no edit here; a new page-level
217 // entry needs one line, next to the function that names it.
218 [
219 dispatch_fn_name(),
220 answer_fn_name(),
221 forget_listener_fn_name(),
222 own_frag_fn_name(),
223 gone_fn_name(),
224 drain_directives_fn_name(),
225 commit_fn_name(),
226 raise_fn_name(),
227 tick_fn_name(),
228 rearm_fn_name(),
229 on_failure_fn_name(),
230 route_changed_fn_name(),
231 repaint_root_fn_name(),
232 shared_arrival_fn_name(),
233 ]
234 .contains(&name)
235}
236
237/// The binding holding where a component last rendered, so a turn can repaint it.
238/// `__bind_<Component>_<field>(v)` — the write-back a bound input performs.
239///
240/// A FUNCTION rather than statements inlined into the listener closure, because a closure
241/// captures the names it touches and a captured state binding is a COPY: the write would land on
242/// the copy and the page would never change. A function resolves the module binding directly,
243/// which is the same reason the dispatcher's `bind_arm` has always worked.
244pub fn bind_fn_name(component: &str, field: &str) -> String {
245 format!("__bind_{component}_{field}")
246}
247
248/// The module binding holding a component's PROP values, per instance.
249///
250/// ★ A REPAINT MUST PASS THE PROPS AGAIN. A turn redraws the instance whose state moved by calling
251/// its render function, and that function takes the props its parent handed it — which the turn does
252/// not have. Recorded here at render time and read back at repaint, per instance, exactly as state
253/// and sites are.
254pub fn prop_binding(component: &str, prop: &str) -> String {
255 format!("__P_{component}_{prop}")
256}
257
258/// The module binding holding a CALLBACK PROP's own identity, per instance: which `(component,
259/// action)` pair to run when the prop is called, as that pair's tag (`events.rs::action_tag`) —
260/// never the pair itself, because the two scalars a map CAN hold are an index and a key, never a
261/// function reference.
262///
263/// ★ NOT [`prop_binding`]'S MAP. `__P_<Component>_<prop>` holds the prop's VALUE, and a callback
264/// prop's value is a closure — the one shape the wasm backend refuses inside a map, measured
265/// (`events.rs::ListenerSite`'s own doc comment). This binding never holds the closure: it holds
266/// which action the closure at THIS instance currently means, so a driving verb — which cannot
267/// call a closure it found at run time — can still resolve and run it.
268pub fn callback_tag_binding(component: &str, prop: &str) -> String {
269 format!("__P_{component}_{prop}_TAG")
270}
271
272/// The twin of [`callback_tag_binding`]: whose own instance the tag's action runs against — the
273/// placing component's key, recorded beside the tag so the two travel together.
274pub fn callback_key_binding(component: &str, prop: &str) -> String {
275 format!("__P_{component}_{prop}_KEY")
276}
277
278/// Where a callback prop's own declared parameters are kept, per instance — the twin of
279/// [`listener_arg_binding`] for a handler whose target varies by placement instead of being fixed
280/// at the component's own declaration. Typed by the prop's own function type, which every action
281/// bound to it is required to match exactly, so one map serves every target the tag can name.
282pub fn callback_arg_binding(component: &str, prop: &str, index: usize) -> String {
283 format!("__ARG_{component}_{prop}_cb{index}")
284}
285
286/// The page's OUTBOX: the element events turns have asked for and not yet had delivered — how many
287/// ([`outbox_count_binding`]) and, at positions `1..=count`, three parallel `Int`-keyed maps of
288/// selector, event and carried value — appended where the asking statement RUNS and emptied by the
289/// flush that follows a turn's write-back.
290pub fn outbox_count_binding() -> &'static str {
291 "__LGX_OUTBOX_N"
292}
293
294pub fn outbox_selector_binding() -> &'static str {
295 "__LGX_OUTBOX_SEL"
296}
297
298pub fn outbox_event_binding() -> &'static str {
299 "__LGX_OUTBOX_EV"
300}
301
302pub fn outbox_value_binding() -> &'static str {
303 "__LGX_OUTBOX_VAL"
304}
305
306/// The reserved event a turn's epilogue dispatches to flush the outbox (`lower_lgx_dispatcher`'s
307/// first arm).
308pub fn outbox_flush_event() -> &'static str {
309 "__lgx_outbox"
310}
311
312/// `__cb_<Component>_<prop>(key, a…)` — the one function every call of a callback prop goes
313/// through: it reads the instance's recorded identity and runs the action it names.
314pub fn callback_trampoline_fn_name(component: &str, prop: &str) -> String {
315 format!("__cb_{component}_{prop}")
316}
317
318/// Where a registered listener's ARGUMENTS are kept, per instance.
319///
320/// ★ AN ARGUMENT IS EVALUATED WHERE THE LISTENER IS ATTACHED, not where it is dispatched.
321/// `on-click={remove(todo's id)}` names a loop item that exists only inside the render — the
322/// dispatcher has no `todo` and never will. So the VALUE is recorded at registration, keyed by the
323/// same element+event, and typed by the action's own declared parameter.
324pub fn listener_arg_binding(component: &str, action: &str, index: usize) -> String {
325 format!("__ARG_{component}_{action}_{index}")
326}
327
328/// `__LISTENER_SPACE` — how many listener entries (event names, and event-and-key pairs) the program
329/// registers. A listener's SLOT is `node * __LISTENER_SPACE + code`, so every (element, entry) pair
330/// has a slot of its own however many entries the program has; declared once every entry is known.
331pub fn listener_space_binding() -> &'static str {
332 "__LISTENER_SPACE"
333}
334
335/// `__LISTENER_FN` — the site each registered listener runs, by its lookup: its slot for the first
336/// listener waiting there, a spilled lookup ([`listener_spill_binding`]) for each one after it.
337pub fn listener_fn_binding() -> &'static str {
338 "__LISTENER_FN"
339}
340
341/// `__LISTENER_MORE` — the lookups of every listener past the first waiting on one slot, in the
342/// order they registered: one element can wait for one event several times over (a directive and a
343/// sentence, two sentences, two instances naming it), and each answers. Empty unless a slot has
344/// several listeners, so the common slot costs nothing here.
345pub fn listener_spill_binding() -> &'static str {
346 "__LISTENER_MORE"
347}
348
349/// `__LISTENER_MORE_AT` — the slot each spilled listener waits on, so retiring it finds its list.
350pub fn listener_spill_slot_binding() -> &'static str {
351 "__LISTENER_MORE_AT"
352}
353
354/// `__LISTENER_SPILLED` — how many listeners have ever spilled. The n-th spilled listener's lookup is
355/// `-1 - n`: below every slot (slots are never negative), never reused, and never
356/// [`UNUSED_LOOKUP`], which the listener maps are seeded with.
357pub fn listener_spill_count_binding() -> &'static str {
358 "__LISTENER_SPILLED"
359}
360
361/// The lookup no listener ever has: the seed entry of every map keyed by lookup, which types the
362/// map's key and is never looked up.
363pub const UNUSED_LOOKUP: i64 = -1;
364
365/// `__LISTENER_HELD` — how many deliveries in flight hold each lookup. A delivery fixes the
366/// listeners it will run before running any, as a browser does, so a listener whose element an
367/// earlier listener's turn redrew still answers: retiring a held lookup waits for its last holder.
368pub fn listener_held_binding() -> &'static str {
369 "__LISTENER_HELD"
370}
371
372/// `__LISTENER_LEFT` — the held lookups retired while held, forgotten when their last holder lets go.
373pub fn listener_left_binding() -> &'static str {
374 "__LISTENER_LEFT"
375}
376
377/// `__lgx_forget_listener(lookup)` — retire one registration: its entry in every map keyed by
378/// lookup and its place in a spilled slot's list; or, while a delivery holds it, mark it to go when
379/// the delivery lets go.
380pub fn forget_listener_fn_name() -> &'static str {
381 "__lgx_forget_listener"
382}
383
384/// `__LGX_ONCE` — the identity of every `.once` listener that has answered, mapped to `true`. An
385/// identity is the listener's author-level address — its instance key, its static site, and its row
386/// in every enclosing `{for each}` — so it survives a repaint that draws the element again with a
387/// fresh node, and a spent listener stays spent however its element is redrawn.
388pub fn once_spent_binding() -> &'static str {
389 "__LGX_ONCE"
390}
391
392/// `__LISTENER_ONCE` — the `.once` identity of each registered listener, keyed like
393/// [`listener_fn_binding`], so the dispatcher can spend the listener it resolved.
394pub fn listener_once_binding() -> &'static str {
395 "__LISTENER_ONCE"
396}
397
398/// `__ONCE_<C>` — every `.once` identity an instance of `C` has spent, per instance key, so the
399/// unmount sweep can forget them when the instance leaves and a remount starts with its listeners
400/// armed.
401pub fn once_record_binding(component: &str) -> String {
402 format!("__ONCE_{component}")
403}
404
405/// The instance key each registered listener belongs to — the twin of [`listener_fn_binding`].
406///
407/// Two maps rather than a map of records because they are written at one site and read at one site,
408/// so they cannot drift, and a record would need a synthesized type for no gain.
409pub fn listener_key_binding() -> &'static str {
410 "__LISTENER_KEY"
411}
412
413/// `__LISTENER_SITES_<C>` — every listener lookup THIS instance registered in its LAST paint.
414///
415/// ★ A REPAINT CREATES FRESH NODE IDS, so its predecessor's listener keys are dead the moment
416/// `clearChildren` runs — but `__LISTENER_FN`/`__LISTENER_KEY` never learned that: they only ever
417/// gain entries, one per element per paint, for the life of the page. A list that repaints its rows
418/// a thousand times leaks a thousand rows of dead listener bookkeeping it will never look up again.
419/// This is the record a render checks at its own top (has THIS instance key painted before?) to
420/// retire what it registered last time before it registers what it is about to.
421pub fn listener_sites_binding(component: &str) -> String {
422 format!("__LISTENER_SITES_{component}")
423}
424
425/// The attribute a pre-rendered document's `<body>` carries: its markup is the page's own first paint,
426/// which the mount adopts (`vdHydrate`) rather than clearing and drawing again. Written by the build's
427/// pre-render, read by the mount, and taken off by the host as the hydration opens.
428pub const PRERENDERED_ATTR: &str = "data-logos-prerendered";
429
430/// Every `vdom.lg` function a lowered page calls — the tape it writes, the commit, the sites and
431/// fragments it finds its nodes by. None of them appears in the author's source, so the prelude
432/// splices vdom.lg for a page and the pruner keeps exactly these as roots (`loader.rs`);
433/// `vdom_calls_lock` holds this list to the calls the lowering writes.
434pub const VDOM_CALLS: &[&str] = &[
435 "vdAbort",
436 "vdAnchor",
437 "vdApply",
438 "vdClearGone",
439 "vdClose",
440 "vdCommit",
441 "vdDiscardRoot",
442 "vdEnsureSite",
443 "vdFragClose",
444 "vdFragNode",
445 "vdFragOpen",
446 "vdFreeSite",
447 "vdGone",
448 "vdGoneCount",
449 "vdHydrate",
450 "vdListen",
451 "vdListenAt",
452 "vdNewFrag",
453 "vdNewRoot",
454 "vdNewSite",
455 "vdNodeOfWire",
456 "vdOpen",
457 "vdOpenKeyed",
458 "vdOpenSoft",
459 "vdPatchChecked",
460 "vdPatchText",
461 "vdPatchValue",
462 "vdQueryText",
463 "vdRangeClose",
464 "vdRangeOpen",
465 "vdReleaseAnchors",
466 "vdSerializeChildren",
467 "vdSetAttribute",
468 "vdSetChecked",
469 "vdSetHtml",
470 "vdSetValue",
471 "vdSite",
472 "vdSiteAt",
473 "vdSiteNode",
474 "vdSiteOf",
475 "vdSlot",
476 "vdTapeLength",
477 "vdTapeSite",
478 "vdTapeStash",
479 "vdTapeUnstash",
480 "vdText",
481 "vdTextSite",
482 "vdWire",
483];
484
485/// A `vdom.lg` path only some pages take: every `vdom.lg` function named with `prefix` runs only on a
486/// tape holding records one of `writers` laid down, so the module's dead-code analysis follows a
487/// call into it once a writer is reached (`vm::reach`) — a page that never calls a writer ships none
488/// of the feature. A new function of the feature is pruned by being named for it.
489#[derive(Debug, Clone, Copy)]
490pub struct VdomFeature {
491 pub prefix: &'static str,
492 pub writers: &'static [&'static str],
493}
494
495/// Every [`VdomFeature`]: firm-key checks, matching by key, range resolution, hydration.
496pub const VDOM_FEATURES: &[VdomFeature] = &[
497 VdomFeature {
498 prefix: "vdKeyed",
499 writers: &["vdOpenKeyed"],
500 },
501 VdomFeature {
502 prefix: "vdKeys",
503 writers: &["vdOpenKeyed", "vdOpenSoft"],
504 },
505 VdomFeature {
506 prefix: "vdSlots",
507 writers: &["vdRangeOpen", "vdSlot"],
508 },
509 VdomFeature {
510 prefix: "vdAdopt",
511 writers: &["vdHydrate"],
512 },
513];
514
515/// The render function's parameter holding the fragment its instance is drawn as — what the sites
516/// its paint hands out are kept against (`vdAnchor`).
517pub fn render_frag_param() -> &'static str {
518 "__lgx_frag"
519}
520
521/// The render function's local holding where its own records start on the tape — what a sentence
522/// asks for its element from.
523pub(crate) fn tape_from_local() -> &'static str {
524 "__lgx_from"
525}
526
527/// `__LGX_FRAGC` — the component (its position among the lowered components, from 1) each
528/// fragment id is an instance of; `0` for an id no instance holds.
529pub fn frag_owner_binding() -> &'static str {
530 "__LGX_FRAGC"
531}
532
533/// `__LGX_FRAGK` — the instance key each fragment id is drawn for.
534pub fn frag_key_binding() -> &'static str {
535 "__LGX_FRAGK"
536}
537
538/// `__lgx_own_frag(frag, who, key)` — records which instance a fresh fragment id is drawn for.
539pub fn own_frag_fn_name() -> &'static str {
540 "__lgx_own_frag"
541}
542
543/// `__lgx_gone()` — the unmount sweep: every instance whose fragment the last commit removed loses
544/// its registries, tells its `When this component disappears:` handler, and hands its sites back.
545pub fn gone_fn_name() -> &'static str {
546 "__lgx_gone"
547}
548
549/// `__lgx_commit(node)` — commits the tape into `node`'s retained children and hands the stream to
550/// the host, or fails the turn with the commit's refusal and the document untouched.
551pub fn commit_fn_name() -> &'static str {
552 "__lgx_commit"
553}
554
555/// `__lgx_drain_directives()` — runs every directive occurrence the committed paint drew.
556pub fn drain_directives_fn_name() -> &'static str {
557 "__lgx_drain_directives"
558}
559
560/// `__DIRQS_<d>` — the sites of the elements directive `d` sits on, queued by the render for the
561/// commit.
562pub fn directive_queue_site_binding(directive: &str) -> String {
563 format!("__DIRQS_{directive}")
564}
565
566/// `__DIRQV_<d>` — the value each queued occurrence of directive `d` is drawn with.
567pub fn directive_queue_value_binding(directive: &str) -> String {
568 format!("__DIRQV_{directive}")
569}
570
571/// `__DIRQK_<d>` — the identity of each queued occurrence of a directive with `When removed:`.
572pub fn directive_queue_key_binding(directive: &str) -> String {
573 format!("__DIRQK_{directive}")
574}
575
576/// `__LGX_HEARD_<C>_<i>` — whether the page already forwards what handler `i` of `C` waits for (a
577/// window event, a relay topic) to its dispatcher; the forwarder is attached once per page.
578pub fn forwarded_binding(component: &str, index: usize) -> String {
579 format!("__LGX_HEARD_{component}_{index}")
580}
581
582/// The render function's own local accumulator: every listener key IT registers this paint,
583/// written back to [`listener_sites_binding`] once, at the end, so next paint knows what to retire.
584///
585/// A local rather than a second write into the per-instance map at registration time: a render
586/// walks its view once and registers as it goes, so collecting into a plain local and writing the
587/// map ONCE is one map write per paint instead of one per listener.
588pub(crate) fn paint_sites_local() -> &'static str {
589 "__lgx_paint_sites"
590}
591
592/// `__SEEN_<C>` — the key of every instance of `C` that has ever rendered, mapped to the PAINT
593/// PASS it last rendered in.
594///
595/// ★ THIS IS THE VDOM SIDE OF THE PAGE, and it is what makes `When this component disappears:`
596/// answerable without asking the host anything. A paint stamps every instance it draws with the
597/// current pass number; after the paint, an instance still carrying an older number was not drawn,
598/// which means it left the page. The DOM says what is on screen and this map says what the program
599/// believes is on screen — the two linked, so a diff between them IS the unmount set.
600pub fn seen_binding(component: &str) -> String {
601 format!("__SEEN_{component}")
602}
603
604/// `__KEYS_<C>` — every instance key of `C` that has ever rendered, in first-render order.
605///
606/// ★ IT EXISTS BECAUSE THE SWEEP MUST BE ITERABLE ON EVERY TIER. The seen-map answers "which pass
607/// was this instance last drawn in" in one lookup, but WALKING a map is not something the wasm
608/// backend lowers ("unsupported iteration over a non-sequence value"), and a sweep that ran on two
609/// tiers and not the third would be the exact shape this campaign exists to prevent. A sequence
610/// walks everywhere, so the keys live in one and the passes in the other.
611pub fn keys_binding(component: &str) -> String {
612 format!("__KEYS_{component}")
613}
614
615/// The key the per-instance maps are SEEDED with, purely to give themselves a type.
616///
617/// ★ IT MUST NOT BE A KEY ANY INSTANCE COULD HAVE, and the root key `""` is. Seeded with `""`, the
618/// timer maps reported the ROOT instance as already known on its first render — so its countdown
619/// was never armed and the page's clock never started, silently. An instance key is a path built
620/// from component names, so a leading control character can never collide with one.
621pub fn seed_key() -> &'static str {
622 "\u{1}"
623}
624
625/// `__DUE_<C>_<i>` — the page time (`__LGX_NOW`'s clock) at which handler `i` of `C` next fires,
626/// per instance. `-1` is "not running", which is what a `Stop the … timer.` writes and what a
627/// one-shot leaves behind.
628pub fn due_binding(component: &str, index: usize) -> String {
629 format!("__DUE_{component}_{index}")
630}
631
632/// `__SEQ_<C>_<i>` — when handler `i` of `C` was armed on each instance, in arming order across the
633/// page: two timers due at one moment fire in the order they were armed.
634pub fn seq_binding(component: &str, index: usize) -> String {
635 format!("__SEQ_{component}_{index}")
636}
637
638/// `__PERIOD_<C>_<i>` — handler `i` of `C`'s period in milliseconds on each instance, as its
639/// sentence read when the instance was drawn: what a repeating timer re-arms by and what
640/// `Start the timer …` restarts it from.
641pub fn period_binding(component: &str, index: usize) -> String {
642 format!("__PERIOD_{component}_{index}")
643}
644
645/// `__LGX_NOW` — the page's own clock, in milliseconds since the page started, moved only by
646/// `__lgx_tick(ms)`.
647pub fn now_binding() -> &'static str {
648 "__LGX_NOW"
649}
650
651/// `__LGX_TIMER_SEQ` — how many timers the page has armed, the next arming's order.
652pub fn timer_seq_binding() -> &'static str {
653 "__LGX_TIMER_SEQ"
654}
655
656/// `__lgx_rearm()` — ask the host to wake the page when its earliest running timer comes due
657/// (`wakeAfter`), or to wake it for none. Called after every arming, stop and start, and at the end
658/// of every tick.
659pub fn rearm_fn_name() -> &'static str {
660 "__lgx_rearm"
661}
662
663/// `__LGX_FAIL_C` / `__LGX_FAIL_K` — WHICH turn is running right now: a component id and an
664/// instance key, written at the top of every turn.
665///
666/// ★ THE HOST CANNOT KNOW THIS, AND DOES NOT HAVE TO. A trap unwinds to the host, which learns
667/// only that something failed; it has no idea which component's action was mid-flight. The module
668/// does, because it just wrote it down — so `When an action fails (e: Text):` is dispatched by
669/// compiled LOGOS exactly like every other trigger, and the host's whole contribution is to say
670/// "that trapped, here is the message".
671pub fn failing_component_binding() -> &'static str {
672 "__LGX_FAIL_C"
673}
674
675pub fn failing_key_binding() -> &'static str {
676 "__LGX_FAIL_K"
677}
678
679/// `__LGX_OPEN_TURNS` — the journal names of the turns in progress, innermost last: a turn pushes
680/// its name first and pops it just before it journals itself, so a trap leaves exactly the turns it
681/// interrupted here, for the failure door to journal innermost-first.
682pub fn open_turns_binding() -> &'static str {
683 "__LGX_OPEN_TURNS"
684}
685
686/// `__LGX_OPEN_KEYS` — the journal instance of each open turn, beside its name in
687/// [`open_turns_binding`].
688pub fn open_keys_binding() -> &'static str {
689 "__LGX_OPEN_KEYS"
690}
691
692/// `__LGX_CASCADE` — how many rounds of store watchers are running inside one another: a round
693/// that has watchers to run enters before they run and leaves after, and a round past
694/// [`crate::ast::component::STORE_CASCADE_LIMIT`] fails the page as one that never settles.
695pub fn cascade_binding() -> &'static str {
696 "__LGX_CASCADE"
697}
698
699/// `__LGX_CONTEXT` — what the page is doing that a trap should be named by (a directive's block),
700/// in the words every tier puts in front of the trap's message; `""` outside any.
701pub fn failure_context_binding() -> &'static str {
702 "__LGX_CONTEXT"
703}
704
705/// `__lgx_failure_context() -> Text` — the context a trap left open, taken (the page is back
706/// outside any once a host has read it).
707pub fn failure_context_fn_name() -> &'static str {
708 "__lgx_failure_context"
709}
710
711/// `__LGX_RENDER_DEPTH` — how many renders of components that can be drawn inside themselves are
712/// running inside one another; one past [`crate::ast::component::RENDER_DEPTH_LIMIT`] fails the
713/// paint as one with no finite rendering.
714pub fn render_depth_binding() -> &'static str {
715 "__LGX_RENDER_DEPTH"
716}
717
718/// `__LGX_APPEAR_WHO` — which component each owed `When this component appears:` belongs to, as its
719/// place (from 1) among the components that have one; beside [`appear_key_binding`], one entry per
720/// instance a paint drew for the first time, in the order it drew them.
721pub fn appear_who_binding() -> &'static str {
722 "__LGX_APPEAR_WHO"
723}
724
725/// `__LGX_APPEAR_KEY` — the instance each owed `When this component appears:` belongs to.
726pub fn appear_key_binding() -> &'static str {
727 "__LGX_APPEAR_KEY"
728}
729
730/// `__LGX_APPEAR_AT` — the next owed entry [`appear_fn_name`] runs.
731pub fn appear_at_binding() -> &'static str {
732 "__LGX_APPEAR_AT"
733}
734
735/// `__lgx_appear()` — every owed `When this component appears:`, first drawn first, each a turn of
736/// its own; run where a paint ends, never inside one.
737pub fn appear_fn_name() -> &'static str {
738 "__lgx_appear"
739}
740
741/// `__MOVED_<Store>_<field>` — set by a store action whose commit moved a field some component
742/// watches, and taken by the next store-watcher round ([`wake_stores_fn_name`]).
743pub fn store_moved_binding(store: &str, field: &str) -> String {
744 format!("__MOVED_{store}_{field}")
745}
746
747/// `__LGX_STORES_DIRTY` — set by every store action's commit and cleared by every repaint from the
748/// page root: whether the page may still show a store as it was before a commit. A turn that moved
749/// a store repaints from the root; one that trapped first never did, and the failure door repaints
750/// for it.
751pub fn stores_dirty_binding() -> &'static str {
752 "__LGX_STORES_DIRTY"
753}
754
755/// `__lgx_wake_stores()` — one store-watcher round: take every moved-field flag, and run each
756/// watcher of a field that moved once per live instance. Every turn ends with one.
757pub fn wake_stores_fn_name() -> &'static str {
758 "__lgx_wake_stores"
759}
760
761/// `__LGX_WATCH_CHANGES` — the changes the watching turn's action committed, as `noteTurn` takes
762/// them: what the failure door journals for that turn when one of its handlers traps.
763pub fn watch_changes_binding() -> &'static str {
764 "__LGX_WATCH_CHANGES"
765}
766
767/// `__LGX_WATCHING` — which component's `When <field> changes:` handlers are running (its id among
768/// the components that have them, `0` for none): its action has committed, so a trap there is
769/// absorbed rather than undoing it. `__LGX_WATCH_K` is that instance's key, and
770/// `__LGX_WATCH_DEPTH` how many turns were open when the handlers began (the watching turn's own
771/// entry the last of them).
772pub fn watching_binding() -> &'static str {
773 "__LGX_WATCHING"
774}
775
776pub fn watch_key_binding() -> &'static str {
777 "__LGX_WATCH_K"
778}
779
780pub fn watch_depth_binding() -> &'static str {
781 "__LGX_WATCH_DEPTH"
782}
783
784/// `__lgx_on_failure(message)` — run the failure handler of whichever component was mid-turn. The
785/// browser host calls it from the one place that catches.
786pub fn on_failure_fn_name() -> &'static str {
787 "__lgx_on_failure"
788}
789
790/// Who enters an [`LGX_ENTRIES`] function by name.
791#[derive(Clone, Copy, Debug, PartialEq, Eq)]
792pub enum EntryHost {
793 /// A page's host — the browser runtime and the in-process page host alike. A page's wasm module
794 /// exports it and the dead-code walk roots at it.
795 Page,
796 /// Only the in-process host of a `## Test`: a verb with no browser counterpart.
797 Test,
798 /// A server's request loop, native or in-process.
799 Server,
800}
801
802/// A function a host enters a compiled program by name.
803#[derive(Clone, Copy, Debug)]
804pub struct HostEntry {
805 /// The function's name, or a family's prefix: every member is the prefix and a non-empty rest.
806 pub name: &'static str,
807 pub family: bool,
808 pub host: EntryHost,
809 /// Why a host enters it.
810 pub why: &'static str,
811}
812
813/// ★ EVERY FUNCTION A HOST ENTERS BY NAME, ONCE. A page's wasm module exports its page entries under
814/// these names and nothing else of the lowering's, the dead-code walk roots at them, `runtime.js`
815/// enters the module only through them and the in-process page host calls only them —
816/// `lgx_entry_abi_lock` reads each of those sides against this table.
817pub const LGX_ENTRIES: &[HostEntry] = &[
818 HostEntry {
819 name: "__lgx_dispatch_node",
820 family: false,
821 host: EntryHost::Page,
822 why: "The one event path: a host fires an event at the node it happened on.",
823 },
824 HostEntry {
825 name: "__lgx_on_failure",
826 family: false,
827 host: EntryHost::Page,
828 why: "A host that caught a trapped turn hands the page the failure's message.",
829 },
830 HostEntry {
831 name: "__lgx_tick",
832 family: false,
833 host: EntryHost::Page,
834 why: "The page clock: a host hands it the milliseconds that passed.",
835 },
836 HostEntry {
837 name: "__lgx_route_changed",
838 family: false,
839 host: EntryHost::Page,
840 why: "A host tells the page its location moved.",
841 },
842 HostEntry {
843 name: "__lgx_owns",
844 family: false,
845 host: EntryHost::Page,
846 why: "A host asks whether a path is this page's, to navigate in place rather than load a document.",
847 },
848 HostEntry {
849 name: "__lgx_failure_context",
850 family: false,
851 host: EntryHost::Page,
852 why: "A host that caught a trap asks what the page was doing when it trapped, to name it in the failure.",
853 },
854 HostEntry {
855 name: "__lgx_shared",
856 family: false,
857 host: EntryHost::Page,
858 why: "A host hands a shared store the frame another replica published on its topic.",
859 },
860 HostEntry {
861 name: "__mount_",
862 family: true,
863 host: EntryHost::Page,
864 why: "A host mounts a component into a parent node.",
865 },
866 HostEntry {
867 name: "__lgx_raise",
868 family: false,
869 host: EntryHost::Test,
870 why: "`Raise` delivers a named event to the page.",
871 },
872 HostEntry {
873 name: "__act_",
874 family: true,
875 host: EntryHost::Test,
876 why: "`Replay these actions:` runs a component's action by name.",
877 },
878 HostEntry {
879 name: "route",
880 family: false,
881 host: EntryHost::Server,
882 why: "A server asks the program's routes for a request's body.",
883 },
884 HostEntry {
885 name: "routeStatus",
886 family: false,
887 host: EntryHost::Server,
888 why: "A server asks the program's routes for a request's status.",
889 },
890 HostEntry {
891 name: "routeContentType",
892 family: false,
893 host: EntryHost::Server,
894 why: "A server asks the program's routes for a response's content type.",
895 },
896];
897
898/// The [`LGX_ENTRIES`] row `name` enters by: its own row, or its family's.
899pub fn host_entry_of(name: &str) -> Option<&'static HostEntry> {
900 LGX_ENTRIES.iter().find(|e| match e.family {
901 true => name.len() > e.name.len() && name.starts_with(e.name),
902 false => name == e.name,
903 })
904}
905
906/// `__lgx_tick(ms)` — the page's clock moves forward by `ms`, and every timer that time makes due
907/// fires, in due order.
908///
909/// ★ ONE IMPLEMENTATION FOR EVERY TIER, WHICH IS THE WHOLE POINT. A browser has real timers and a
910/// test has none, so the obvious build gives the two tiers different code and hopes they agree.
911/// Instead the page keeps its own clock (`__LGX_NOW`) and every due time in compiled LOGOS, and the
912/// tiers differ only in what calls this: a browser hands it the milliseconds that passed — before
913/// every entry, and when `wakeAfter` says the earliest timer is due — and `Advance time by …` calls
914/// it directly. A timer fires at the page time it was armed for, whatever steps the clock took.
915pub fn tick_fn_name() -> &'static str {
916 "__lgx_tick"
917}
918
919/// `__lgx_descends(k, under)` — whether instance key `k` was placed inside the instance keyed by
920/// `under` (a key and its trailing `/`); what a repaint of a placed component sweeps within.
921pub fn descends_fn_name() -> &'static str {
922 "__lgx_descends"
923}
924
925/// The paint-pass counter every instance stamps itself with. One per program.
926pub fn pass_binding() -> &'static str {
927 "__LGX_PASS"
928}
929
930pub fn mount_binding(component: &str) -> String {
931 format!("__M_{component}")
932}
933
934/// The program-wide event dispatcher: `__lgx_dispatch(selector, event)`. A driving verb on the
935/// bytecode tier calls it, and it routes to the action the view attached — decided here, as data,
936/// because a page has no way to call a closure it found at run time.
937pub fn dispatch_fn_name() -> &'static str {
938 "__lgx_dispatch"
939}
940
941/// The dispatcher's node half: `__lgx_dispatch_node(node, event, value)` — a host that already
942/// holds the element (a focus change: the element that lost focus, the one that gained it) raises
943/// an event on it without a selector naming it.
944pub fn dispatch_node_fn_name() -> &'static str {
945 "__lgx_dispatch_node"
946}
947
948/// `__lgx_answer(lookup, value) -> Bool` — run the listener one listener-table entry names, for the
949/// instance that registered it; the node dispatcher asks it for the entry waiting for the pressed
950/// key and for the one waiting for any.
951pub fn answer_fn_name() -> &'static str {
952 "__lgx_answer"
953}
954
955/// `__lgx_route_changed()` — deliver a move of the location to every live instance of every
956/// component with a `When the route changes:` handler. The page registers it with the host once,
957/// at start (`onRoute`), and a turn whose own `Navigate to` moved the location calls it at its
958/// epilogue, so a placed component hears the move exactly as a mounted one does.
959pub fn route_changed_fn_name() -> &'static str {
960 "__lgx_route_changed"
961}
962
963/// `__lgx_owns(path) -> Bool` — whether this module's `## Page` serves `path` (its query and fragment
964/// set aside): the one rule deciding whether a move to `path` stays in the page (`navigate`, a
965/// pushState) or loads another document (`assignLocation`). `Navigate to` branches on it, and a
966/// host's link-following asks it.
967pub fn owns_fn_name() -> &'static str {
968 "__lgx_owns"
969}
970
971/// `__LGX_MOVED` — set when a turn moved the location within the page, and taken by that turn's
972/// epilogue, which delivers the move to the page's route listeners.
973pub fn moved_binding() -> &'static str {
974 "__LGX_MOVED"
975}
976
977/// `__lgx_repaint_root()` — repaint the page from its root (the instance keyed `""`), the answer to
978/// a change any component may show: what a turn that moved a store paints, and what a shared store
979/// runs when another replica's write arrives.
980pub fn repaint_root_fn_name() -> &'static str {
981 "__lgx_repaint_root"
982}
983
984/// The storage area a `shared on` store's fields live in, keyed `"<topic>.<field>"`: the topics
985/// every page on them shares.
986pub const SHARED_AREA: &str = "shared";
987
988/// Whether the shared-store replica `store` has joined its topic.
989pub fn shared_joined_binding(store: &str) -> String {
990 format!("__SJ_{store}")
991}
992
993/// `__join_<S>()` — the replica joins its topic: it adopts what the topic holds and publishes
994/// the rest. Runs before the first statement that reads one of its fields.
995pub fn shared_join_fn_name(store: &str) -> String {
996 format!("__join_{store}")
997}
998
999/// `__publish_<S>()` — publish every field the replica's action moved, then refresh the topic's
1000/// other joined replicas.
1001pub fn shared_publish_fn_name(store: &str) -> String {
1002 format!("__publish_{store}")
1003}
1004
1005/// `__refresh_<S>()` — a joined replica adopts what its topic holds.
1006pub fn shared_refresh_fn_name(store: &str) -> String {
1007 format!("__refresh_{store}")
1008}
1009
1010/// `__lgx_shared(topic, key, frame)` — the relay's entry: a frame another page published.
1011pub fn shared_arrival_fn_name() -> &'static str {
1012 "__lgx_shared"
1013}
1014
1015/// This page's Lamport clock for its shared stores.
1016pub fn shared_clock_binding() -> &'static str {
1017 "__SH_CLOCK"
1018}
1019
1020/// This page's replica id, drawn once per page load by a page with a convergent shared field: the
1021/// part of every tag and node id that no other page's can share.
1022pub fn shared_replica_binding() -> &'static str {
1023 "__SH_REPLICA"
1024}
1025
1026/// The plain value a convergent field of shared store `store` last adopted — what the next
1027/// action's change to `field` is read against.
1028pub fn shared_base_binding(store: &str, field: &str) -> String {
1029 format!("__SB_{store}_{field}")
1030}
1031
1032/// The node id behind each element of shared sequence `field` of `store`, in its order.
1033pub fn shared_ids_binding(store: &str, field: &str) -> String {
1034 format!("__SI_{store}_{field}")
1035}
1036
1037/// `Raise "offline".` on the bytecode tier.
1038pub fn raise_fn_name() -> &'static str {
1039 "__lgx_raise"
1040}
1041
1042/// The record type a `## Store` lowers to. A store is a singleton with named fields, which is
1043/// what a record is — so `Session's name` stays an ORDINARY field access on both tiers and no
1044/// expression anywhere needs rewriting.
1045pub fn store_type_name(store: &str) -> String {
1046 format!("__Store_{store}")
1047}
1048
1049/// The function a `## Directive` lowers to. A directive is behavior attached to an element, and
1050/// on this tier that is exactly what a function call is.
1051pub fn directive_fn_name(directive: &str) -> String {
1052 format!("__dir_{directive}")
1053}
1054
1055/// `__dir_<name>__removed` — a directive's `When removed:` block, run with the element and value
1056/// its occurrence was last attached with, once that occurrence is no longer drawn.
1057pub fn directive_removed_fn_name(directive: &str) -> String {
1058 format!("__dir_{directive}__removed")
1059}
1060
1061/// `__DIRK_<name>` — every attached occurrence of a directive with a `When removed:` block, by its
1062/// identity (the instance key, `\u{1}`, its static site, and its row in every enclosing loop).
1063pub fn directive_keys_binding(directive: &str) -> String {
1064 format!("__DIRK_{directive}")
1065}
1066
1067/// `__DIRS_<name>` — the paint pass each attached occurrence was last drawn in.
1068pub fn directive_stamp_binding(directive: &str) -> String {
1069 format!("__DIRS_{directive}")
1070}
1071
1072/// `__DIRN_<name>` — the element each attached occurrence was last drawn on.
1073pub fn directive_node_binding(directive: &str) -> String {
1074 format!("__DIRN_{directive}")
1075}
1076
1077/// `__DIRV_<name>` — the value each attached occurrence was last drawn with.
1078pub fn directive_value_binding(directive: &str) -> String {
1079 format!("__DIRV_{directive}")
1080}
1081
1082/// The entry point a `Mount` calls: record where the component renders, run its load handlers
1083/// once, then paint. Distinct from the render function, which runs again on every repaint — a
1084/// load handler that ran on each repaint would not be a load handler.
1085pub fn mount_fn_name(component: &str) -> String {
1086 format!("__mount_{component}")
1087}
1088
1089pub fn action_fn_name(component: &str, action: &str) -> String {
1090 format!("__act_{component}_{action}")
1091}
1092
1093/// `__route_<METHOD>_<slug>` — the function a `## Route` becomes.
1094pub fn route_fn_name(method: &str, path: &str) -> String {
1095 let slug: String = path
1096 .chars()
1097 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
1098 .collect();
1099 format!("__route_{}_{}", method.to_ascii_uppercase(), slug)
1100}
1101
1102/// The construct an author wrote that the compiler-written function `name` was lowered from, named
1103/// as the author knows it: `` `## Route GET /agent` ``, `` the `add` action of `## Component
1104/// Counter` ``, `` `## Directive focus` ``, or `` the `## Route` table `` for the walks that serve
1105/// every route. Built on the forward helpers above over the constructs the program declares, so
1106/// there is no inverse mapping to keep in step. `None` for a name no construct owns — a synthesized
1107/// name never becomes text an author reads.
1108pub fn authored_construct(
1109 name: &str,
1110 program: &[crate::ast::stmt::Stmt<'_>],
1111 interner: &logicaffeine_base::Interner,
1112) -> Option<String> {
1113 use super::server::{
1114 ROUTE_BODY_WALK, ROUTE_CTYPE_WALK, ROUTE_HANDLER, ROUTE_HEADERS_WALK, ROUTE_RESPONSE_WALK,
1115 ROUTE_STATUS_WALK,
1116 };
1117 use crate::ast::stmt::Stmt;
1118 const WALKS: [&str; 6] = [
1119 ROUTE_HANDLER,
1120 ROUTE_BODY_WALK,
1121 ROUTE_STATUS_WALK,
1122 ROUTE_CTYPE_WALK,
1123 ROUTE_HEADERS_WALK,
1124 ROUTE_RESPONSE_WALK,
1125 ];
1126 if WALKS.contains(&name) {
1127 return Some("the `## Route` table".to_string());
1128 }
1129 let mut components: Vec<&crate::ast::component::ComponentDef<'_>> = Vec::new();
1130 for s in program {
1131 match s {
1132 Stmt::RouteDef { method, path, .. }
1133 if route_fn_name(interner.resolve(*method), interner.resolve(*path)) == name =>
1134 {
1135 return Some(format!(
1136 "`{}`",
1137 super::gate::route_heading(*method, *path, interner)
1138 ));
1139 }
1140 Stmt::DirectiveDef {
1141 name: directive, ..
1142 } if directive_fn_name(interner.resolve(*directive)) == name => {
1143 return Some(format!("`## Directive {}`", interner.resolve(*directive)));
1144 }
1145 Stmt::ComponentDef(def) => components.push(def),
1146 _ => {}
1147 }
1148 }
1149 // Longest component name first, so a patch function of `Counter_x` is never claimed by `Counter`.
1150 components.sort_by_key(|def| std::cmp::Reverse(interner.resolve(def.name).len()));
1151 for def in components {
1152 let component = interner.resolve(def.name);
1153 let heading = super::gate::construct_heading(def, interner);
1154 for action in &def.actions {
1155 let action_name = interner.resolve(action.name);
1156 if action_fn_name(component, action_name) == name {
1157 return Some(format!("the `{action_name}` action of `{heading}`"));
1158 }
1159 }
1160 let owned = [
1161 render_fn_name(component),
1162 html_fn_name(component),
1163 mount_fn_name(component),
1164 ]
1165 .iter()
1166 .any(|n| n == name)
1167 || name.starts_with(&patch_fn_name(component, ""))
1168 || def
1169 .state
1170 .iter()
1171 .any(|f| bind_fn_name(component, interner.resolve(f.name)) == name);
1172 if owned {
1173 return Some(format!("`{heading}`"));
1174 }
1175 }
1176 None
1177}