Skip to main content

frazaro_core/
kernel.rs

1//! The kernel's boundary, and the five seams through which everything else
2//! enters (`KERNEL.1`, 2026-10-06; the design is `web/CALLOSUM.md` ยง14).
3//!
4//! The kernel holds mechanism only: forms and their expansion, the emitters,
5//! the sheet model, recalculation over a declared subset once it lands, the
6//! relation set, and the ABI. It never holds English, message text, a
7//! default, chrome, a format beyond a trait, or a door. Each of those is
8//! data the kernel reads (the four tables under `core/data/`, a phrasebook, a
9//! library) or an implementation of one of the seams below. The rule that
10//! follows: the kernel grows a seam, never a feature.
11//!
12//! `tools/check_kernel_boundary.ps1` pins the rule over `core/src/`. This
13//! module writes the seams down as data, so that a test can hold their
14//! counts and `frazaro describe` (`KERNEL.18`) can print them, and names the
15//! two traits whose first implementations later items land: [`Engine`]
16//! (`PORT.9`) and [`Projection`] (`KERNEL.4`).
17
18use crate::messages::Refusal;
19use crate::sheet::{A1Range, Workbook};
20
21/// One of the five entrances into the kernel.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct Seam {
24    /// The seam's name, as `CONTRIBUTING.md` heads its row.
25    pub name: &'static str,
26    /// What a contribution through it is: a data file, or an implementation
27    /// of a trait in this crate.
28    pub entrance: &'static str,
29    /// The oracle a change through it must pass before it lands.
30    pub oracle: &'static str,
31    /// The implementations the kernel ships today, by name; empty where the
32    /// first is deferred to the roadmap item in `deferred_to`.
33    pub implementations: &'static [&'static str],
34    /// The roadmap item that lands the first implementation, or empty.
35    pub deferred_to: &'static str,
36}
37
38/// The five seams, in the order `CONTRIBUTING.md` lists them.
39pub const SEAMS: [Seam; 5] = [
40    Seam {
41        name: "sentences",
42        entrance: "a phrasebook file (.vla) with its test: proofs, loaded through api::vocab_gate",
43        oracle: "frazaro prove passes every proof; the translate and refusal goldens hold",
44        implementations: &["the phrasebooks a door loads, english.vla first"],
45        deferred_to: "",
46    },
47    Seam {
48        name: "paragraphs",
49        entrance: "a library of sentences with named slots, imported by a Use sentence",
50        oracle: "the build golden, and the stamp hashing every file used",
51        implementations: &[],
52        deferred_to: "G-USE",
53    },
54    Seam {
55        name: "engines",
56        entrance: "an implementation of kernel::Engine under a head-table symbol",
57        oracle: "a proof file of its kind, with clingo beside it where it applies",
58        implementations: &[],
59        deferred_to: "PORT.9",
60    },
61    Seam {
62        name: "formats and hosts",
63        entrance: "an implementation of reflect::Source to read, a writer beside it, and a kernel::HostProfile per door",
64        oracle: "the reflect, diff and audit goldens for a reader; the build golden for a writer",
65        implementations: &["xlsx (OOXML), read and written", "ods (OpenDocument), read"],
66        deferred_to: "",
67    },
68    Seam {
69        name: "projections",
70        entrance: "an implementation of kernel::Projection",
71        oracle: "a view golden per projection",
72        implementations: &[],
73        deferred_to: "KERNEL.4",
74    },
75];
76
77/// The seams, as data.
78pub fn seams() -> &'static [Seam] {
79    &SEAMS
80}
81
82/// A file format the kernel reads, writes, or both.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct Format {
85    pub name: &'static str,
86    pub extension: &'static str,
87    pub reads: bool,
88    pub writes: bool,
89}
90
91/// The formats seam's registry. `reflect::open` picks the reader by the
92/// bytes it is handed; the one writer is `sheet::ooxml::workbook_bytes`.
93pub const FORMATS: [Format; 2] = [
94    Format {
95        name: "OOXML",
96        extension: "xlsx",
97        reads: true,
98        writes: true,
99    },
100    Format {
101        name: "OpenDocument",
102        extension: "ods",
103        reads: true,
104        writes: false,
105    },
106];
107
108/// An engine: tables in, a table out, under a symbol of the head table,
109/// held by proof files of one kind. The relation type is the implementor's,
110/// since the port of `VLA_Relation` (`PORT.9`) defines it; the first
111/// implementations are SQL, DATALOG, PROLOG and OPTIMIZE, and a fifth proves
112/// the seam takes additions.
113pub trait Engine {
114    /// The relation an engine takes and gives.
115    type Table;
116    /// The head-table symbol the engine answers to, such as `datalog`.
117    fn head(&self) -> &str;
118    /// The proof-file kind that holds it, such as `test-datalog`.
119    fn proof_kind(&self) -> &str;
120    /// The tables in, the table out, or a refusal from the catalogue.
121    fn run(&self, tables: &[Self::Table]) -> Result<Self::Table, Refusal>;
122}
123
124/// A window onto the model: one sheet by name, one rectangle of it.
125#[derive(Clone, Debug, PartialEq, Eq)]
126pub struct Window {
127    pub sheet: String,
128    pub range: A1Range,
129}
130
131/// A projection: a pure function from the model and a window to lines, the
132/// shape every door prints (`SD-23`). The grid is the first (`KERNEL.4`),
133/// the sentence pane the second, the dependency cone drawn as a diagram the
134/// third (`KERNEL.12`). A projection never edits the model.
135pub trait Projection {
136    /// The projection's name, as `frazaro view` selects it.
137    fn name(&self) -> &str;
138    /// The window's lines, in a fixed order, to the sink.
139    fn project(&self, model: &Workbook, window: &Window, out: &mut dyn FnMut(&str));
140}
141
142/// A door's host profile: the statement heads it carries out with nothing
143/// running, and the catalogue id that refuses the rest by name.
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub struct HostProfile {
146    pub door: &'static str,
147    /// The core forms, by head-table symbol, the door holds.
148    pub forms: &'static [&'static str],
149    /// The scaffold's own calls the door steps over without effect.
150    pub scaffold: &'static [&'static str],
151    /// The catalogue id of the refusal for a sentence outside the profile.
152    pub refuses_with: &'static str,
153}
154
155/// The file door: `frazaro build`, and the page's Workbook strip. What the
156/// walker in `build.rs` holds, read off its arms: a value or a formula into
157/// a range, `Work on sheet` and `Go to sheet`, a condition that folds, and
158/// a call of one of the program's own steps with no values, inlined. The
159/// rest is refused with the sentence quoted.
160pub const FILE_DOOR: HostProfile = HostProfile {
161    door: "file",
162    forms: &[
163        "at-line",
164        "begin",
165        "dim",
166        "on-error",
167        "label",
168        "exit-sub",
169        "exit-function",
170        "set!",
171        "if",
172        ".",
173    ],
174    scaffold: &["vla-report-error", "vlaensuresheet"],
175    refuses_with: "build-not-representable",
176};
177
178/// The host profiles the kernel knows. The Excel add-in needs none, since
179/// it holds every form; the page's viewport and the task pane add theirs
180/// with `KERNEL.19`.
181pub const HOST_PROFILES: [HostProfile; 1] = [FILE_DOOR];
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use crate::headtable;
187    use crate::reflect;
188
189    const FIXTURE_XLSX: &[u8] = include_bytes!("../../scripts/reflect/fixture.xlsx");
190    const FIXTURE_ODS: &[u8] = include_bytes!("../../scripts/reflect/opendocument.ods");
191
192    #[test]
193    fn five_seams_each_with_an_entrance_and_an_oracle() {
194        assert_eq!(seams().len(), 5);
195        let names: Vec<&str> = seams().iter().map(|s| s.name).collect();
196        assert_eq!(
197            names,
198            [
199                "sentences",
200                "paragraphs",
201                "engines",
202                "formats and hosts",
203                "projections"
204            ]
205        );
206        for s in seams() {
207            assert!(!s.entrance.is_empty(), "{} names no entrance", s.name);
208            assert!(!s.oracle.is_empty(), "{} names no oracle", s.name);
209            assert!(
210                !s.implementations.is_empty() || !s.deferred_to.is_empty(),
211                "{} has neither an implementation nor the item that lands one",
212                s.name
213            );
214        }
215    }
216
217    #[test]
218    fn the_engines_and_projections_seams_are_named_and_empty_today() {
219        let engines = seams().iter().find(|s| s.name == "engines").unwrap();
220        assert!(engines.implementations.is_empty());
221        assert_eq!(engines.deferred_to, "PORT.9");
222        let projections = seams().iter().find(|s| s.name == "projections").unwrap();
223        assert!(projections.implementations.is_empty());
224        assert_eq!(projections.deferred_to, "KERNEL.4");
225        let paragraphs = seams().iter().find(|s| s.name == "paragraphs").unwrap();
226        assert_eq!(paragraphs.deferred_to, "G-USE");
227    }
228
229    #[test]
230    fn the_formats_registry_matches_the_readers_and_the_writer() {
231        assert_eq!(FORMATS.len(), 2);
232        assert!(reflect::open(FIXTURE_XLSX, "fixture.xlsx").is_ok());
233        assert!(reflect::open(FIXTURE_ODS, "opendocument.ods").is_ok());
234        let readers: Vec<&str> = FORMATS
235            .iter()
236            .filter(|f| f.reads)
237            .map(|f| f.extension)
238            .collect();
239        assert_eq!(readers, ["xlsx", "ods"]);
240        let writers: Vec<&str> = FORMATS
241            .iter()
242            .filter(|f| f.writes)
243            .map(|f| f.extension)
244            .collect();
245        assert_eq!(writers, ["xlsx"], "the one writer is the OOXML writer");
246        let formats = seams()
247            .iter()
248            .find(|s| s.name == "formats and hosts")
249            .unwrap();
250        assert_eq!(formats.implementations.len(), FORMATS.len());
251    }
252
253    #[test]
254    fn the_file_door_holds_only_core_forms_and_refuses_from_the_catalogue() {
255        assert_eq!(HOST_PROFILES.len(), 1);
256        assert_eq!(FILE_DOOR.door, "file");
257        for sym in FILE_DOOR.forms {
258            let canonical = headtable::resolve_head_alias(sym);
259            assert!(
260                headtable::row(&canonical).is_some(),
261                "{sym} is not a form of the head table"
262            );
263        }
264        for call in FILE_DOOR.scaffold {
265            assert!(
266                headtable::row(call).is_none(),
267                "{call} is a scaffold call, not a core form"
268            );
269        }
270        assert_eq!(FILE_DOOR.refuses_with, "build-not-representable");
271    }
272
273    #[test]
274    fn a_window_is_one_sheet_and_one_rectangle() {
275        let w = Window {
276            sheet: "Model".to_string(),
277            range: A1Range {
278                top: 1,
279                left: 1,
280                bottom: 20,
281                right: 6,
282            },
283        };
284        assert_eq!(w.range.cells(), 120);
285        assert_eq!(w.sheet, "Model");
286    }
287}