Skip to main content

harn_builtin_registry/
lib.rs

1//! Process-global registry of builtin signatures.
2//!
3//! `harn-vm` owns the implementations and emits one `&'static BuiltinDef<H>`
4//! per `#[harn_builtin]`-annotated function via the `harn-builtin-macros`
5//! crate. At startup the driver (CLI, LSP, lint, serve, dap, tests) installs
6//! the full slice of signatures here; the parser/typechecker then reads them
7//! through [`installed_signatures`].
8//!
9//! This decouples `harn-parser` (which needs to see signatures to typecheck)
10//! from `harn-vm` (which owns the impls) without a dependency cycle —
11//! `harn-parser` depends only on this crate plus `harn-builtin-meta`, never
12//! on the vm.
13
14use std::sync::OnceLock;
15
16use harn_builtin_meta::BuiltinSignature;
17
18/// A complete description of one builtin: its signature, its aliases, the
19/// runtime handler (typed by the consumer via `H`), and optional metadata.
20///
21/// `H` is parametric so this crate stays free of any handler-type
22/// dependency. `harn-vm` instantiates it as
23/// `BuiltinDef<VmBuiltinHandler>`; parser-only consumers ignore the handler
24/// and read just the [`Self::sig`] field.
25#[derive(Debug, Clone, Copy)]
26pub struct BuiltinDef<H: 'static> {
27    /// Static signature consumed by the parser/typechecker.
28    pub sig: BuiltinSignature,
29    /// Additional names that share this impl + signature. Each alias gets
30    /// its own [`BuiltinSignature`] entry at install time (with the same
31    /// param/return types) so the typechecker accepts both.
32    pub aliases: &'static [&'static str],
33    /// Runtime handler (sync fn, async fn, or `None` for parser-only
34    /// builtins). Type is opaque to this crate.
35    pub handler: H,
36    /// Free-form category label used for metadata/observability.
37    pub category: Option<&'static str>,
38    /// Human-readable doc, typically the leading `///` block from the impl
39    /// function. Surfaced to LSP hover and `harn explain`.
40    pub doc: Option<&'static str>,
41    /// Free-form Harn-style signature text (e.g. `"foo(a: dict) -> dict"`).
42    /// Populated by `#[harn_builtin]` from the `sig = "..."` literal so the
43    /// runtime metadata layer can surface the original source spelling
44    /// without re-rendering [`Self::sig`]. The DSL builder shape used to
45    /// store this via `.signature(...)`; the macro shape replaces it.
46    pub signature_text: Option<&'static str>,
47    /// Set to `true` for builtins that exist in the parser registry but
48    /// have no runtime entry (`len`, `split`, … — see
49    /// `PARSER_ONLY_EXCEPTIONS` in the alignment test). The registry
50    /// skips runtime registration for these.
51    pub parser_only: bool,
52    /// Set to `true` for compiler-synthesized runtime helpers (sigil
53    /// prefix `__`, opcode keywords, enum constructors) that exist as VM
54    /// builtins but should NOT show up in the parser signature table.
55    /// The registry skips signature publishing for these.
56    pub runtime_only: bool,
57}
58
59impl<H: 'static> BuiltinDef<H> {
60    /// Compact constructor for the common case: one signature, no aliases,
61    /// no metadata flags.
62    pub const fn new(sig: BuiltinSignature, handler: H) -> Self {
63        Self {
64            sig,
65            aliases: &[],
66            handler,
67            category: None,
68            doc: None,
69            signature_text: None,
70            parser_only: false,
71            runtime_only: false,
72        }
73    }
74}
75
76/// Process-global slice of installed signatures, populated once by the
77/// driver. Reads via [`installed_signatures`] are O(1); writes via
78/// [`install_builtin_signatures`] are one-shot.
79static INSTALLED: OnceLock<&'static [&'static BuiltinSignature]> = OnceLock::new();
80
81/// Install the process-global signature registry. Called once by the driver
82/// (CLI, LSP, lint, serve, dap) at startup. Test harnesses that build a Vm
83/// via `harn_vm::stdlib::stdlib_probe_vm()` inherit the install through that
84/// helper.
85///
86/// # Panics
87/// Panics if called more than once with different slices. Repeat calls with
88/// the same pointer are tolerated (CLI + test harness can both call it).
89pub fn install_builtin_signatures(sigs: &'static [&'static BuiltinSignature]) {
90    if INSTALLED.set(sigs).is_err() {
91        assert!(
92            INSTALLED.get().copied().map(<[_]>::as_ptr) == Some(sigs.as_ptr()),
93            "install_builtin_signatures called twice with different slices — \
94             drivers should install once at startup"
95        );
96    }
97}
98
99/// Reset the installed slice — only callable from tests (`#[cfg(test)]`
100/// guarded). Avoids leaking state across in-process unit tests that need
101/// to swap the installed slice. **Not** for production use.
102#[doc(hidden)]
103pub fn _test_only_reinstall(sigs: &'static [&'static BuiltinSignature]) {
104    // OnceLock has no public reset, but we can't avoid OnceLock semantics
105    // here. Tests that need to reinstall should call this exactly once
106    // before any other test calls `install_builtin_signatures`.
107    let _ = INSTALLED.set(sigs);
108}
109
110/// Read the installed signature slice. Returns an empty slice before the
111/// first call to [`install_builtin_signatures`] (e.g. in pure-parser unit
112/// tests that don't need a registry).
113pub fn installed_signatures() -> &'static [&'static BuiltinSignature] {
114    INSTALLED.get().copied().unwrap_or(&[])
115}
116
117/// True when the registry has been populated. Useful for guards in parser
118/// code that wants to assert it's running in a configured driver context.
119pub fn is_installed() -> bool {
120    INSTALLED.get().is_some()
121}