Skip to main content

nibli_render/
overlay.rs

1//! Per-example domain-gloss overlay: an OPTIONAL display layer that renders a
2//! curated corpus's proxy predicates and opaque constants in real domain terms
3//! (`prevents` -> "inhibits", `flukonazol` -> "fluconazole"), scoped to a loaded
4//! example.
5//!
6//! The engine dictionary (`nibli_lexicon`) is always the FALLBACK: an example
7//! with no entry for a relation, and every Custom (user-authored) KB, render with
8//! the literal glosses. The overlay is installed only for the duration of one
9//! render call via [`with_overlay`] and read back through [`active`], so it never
10//! leaks into the back-translation ("What Nibli Understood") tab — that surface
11//! calls the renderer WITHOUT an overlay and stays deliberately literal as the
12//! firewall's verification view.
13
14use std::cell::Cell;
15
16/// A domain-term overlay for one curated example. The tables are tiny (a handful
17/// of entries each), so a linear scan is fine.
18#[derive(Clone, Copy, Debug)]
19pub struct DomainGloss {
20    /// Place-frame template overrides keyed by bare gismu. Placeholders `{x1}`..
21    /// may reorder, e.g. `se cuts` -> "{x2} is metabolized by {x1}" (the IR has
22    /// already swapped the `se` args, so the template keys on the bare gismu).
23    pub templates: &'static [(&'static str, &'static str)],
24    /// Single-word noun-gloss overrides keyed by gismu (e.g. the "a <noun>" of an
25    /// existence clause).
26    pub glosses: &'static [(&'static str, &'static str)],
27    /// Display-name overrides keyed by constant (cmevla), e.g. `varfarin` ->
28    /// "warfarin", `siptucin` -> "CYP2C9".
29    pub names: &'static [(&'static str, &'static str)],
30}
31
32impl DomainGloss {
33    /// Overlay place-frame template for `relation`, if any.
34    pub fn template(&self, relation: &str) -> Option<&'static str> {
35        lookup(self.templates, relation)
36    }
37
38    /// Overlay noun gloss for `relation`, if any.
39    pub fn gloss(&self, relation: &str) -> Option<&'static str> {
40        lookup(self.glosses, relation)
41    }
42
43    /// Overlay display name for a constant, if any.
44    pub fn name(&self, constant: &str) -> Option<&'static str> {
45        lookup(self.names, constant)
46    }
47}
48
49fn lookup(table: &[(&'static str, &'static str)], key: &str) -> Option<&'static str> {
50    table.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
51}
52
53thread_local! {
54    /// The overlay active for the current render call (`None` = dictionary
55    /// fallback). A `&'static` reference, so the cell is `Copy`.
56    static OVERLAY: Cell<Option<&'static DomainGloss>> = const { Cell::new(None) };
57}
58
59/// Run `f` with `overlay` installed as the active render overlay, restoring the
60/// previous value afterward (panic-safe via the drop guard). Intended for the
61/// single-threaded UI render path; other threads see `None`.
62pub(crate) fn with_overlay<T>(overlay: Option<&'static DomainGloss>, f: impl FnOnce() -> T) -> T {
63    struct Guard(Option<&'static DomainGloss>);
64    impl Drop for Guard {
65        fn drop(&mut self) {
66            OVERLAY.with(|o| o.set(self.0));
67        }
68    }
69    let _guard = OVERLAY.with(|o| Guard(o.replace(overlay)));
70    f()
71}
72
73/// The overlay active for this render call, if any.
74pub(crate) fn active() -> Option<&'static DomainGloss> {
75    OVERLAY.with(Cell::get)
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    static SAMPLE: DomainGloss = DomainGloss {
83        templates: &[("dangerous", "{x1} is at toxicity risk")],
84        glosses: &[("chemical", "drug")],
85        names: &[("varfarin", "warfarin")],
86    };
87
88    #[test]
89    fn lookups_resolve() {
90        assert_eq!(
91            SAMPLE.template("dangerous"),
92            Some("{x1} is at toxicity risk")
93        );
94        assert_eq!(SAMPLE.template("increases"), None);
95        assert_eq!(SAMPLE.gloss("chemical"), Some("drug"));
96        assert_eq!(SAMPLE.name("varfarin"), Some("warfarin"));
97        assert_eq!(SAMPLE.name("adam"), None);
98    }
99
100    #[test]
101    fn overlay_is_scoped_and_restored() {
102        assert!(active().is_none());
103        with_overlay(Some(&SAMPLE), || {
104            assert!(std::ptr::eq(active().unwrap(), &SAMPLE));
105            // Nested override + restore.
106            with_overlay(None, || assert!(active().is_none()));
107            assert!(std::ptr::eq(active().unwrap(), &SAMPLE));
108        });
109        assert!(active().is_none());
110    }
111}