Skip to main content

caixa_theme/
style.rs

1//! The small semantic-style enum every caixa tool agrees on.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(
6    Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, gen_platform::IsVariant,
7)]
8pub enum Semantic {
9    /// Language keywords — `defcaixa`, `defteia`, `let`, `lambda`, etc.
10    Keyword,
11    /// Non-keyword symbols — identifiers, function names, variant names.
12    Symbol,
13    /// `:keyword-positioned` atoms.
14    KeywordArg,
15    /// `"string literals"`.
16    String,
17    /// `42`, `3.14`.
18    Number,
19    /// `#t`, `#f`, `nil`.
20    Literal,
21    /// `; comments`.
22    Comment,
23    /// Primary accent — useful for highlights, carets, focused tokens.
24    Accent,
25    /// Dim text — metadata, line numbers, help text.
26    Muted,
27
28    // Diagnostic severities.
29    Error,
30    Warning,
31    Info,
32    Hint,
33
34    // Diff decorations — used by formatter preview and lint output.
35    Added,
36    Removed,
37    Unchanged,
38}
39
40impl Semantic {
41    /// Every variant of [`Semantic`] in declaration order.
42    ///
43    /// The single canonical arm-list every substrate consumer that has
44    /// to walk the closed 15-arm semantic-style partition (the two
45    /// theme overlays' exhaustive-match resolver functions
46    /// `blackmatter_dark_color` / `blackmatter_light_color` in
47    /// [`crate::blackmatter`], the future LSP-side per-Semantic
48    /// `SemanticTokenType` dispatch at
49    /// `caixa-lsp/src/main.rs`, a future
50    /// `feira lint --list-styles` operator-facing enumeration verb)
51    /// reads for. Peer of the sibling closed-set fieldless typed
52    /// enums' `ALL` slices already carried by
53    /// [`caixa_core::CaixaKind`] /
54    /// [`caixa_core::supervisor::RestartStrategy`] /
55    /// [`caixa_core::supervisor::RestartPolicy`] /
56    /// [`caixa_core::aplicacao::PlacementStrategy`] /
57    /// [`caixa_core::upgrade::UpgradeInstruction`] /
58    /// `caixa_lint::diagnostic::Severity` /
59    /// `caixa_lint::diagnostic::FixSafety` /
60    /// `caixa_arch::InvariantKind` / `caixa_arch::ArchVerdict` /
61    /// `caixa_provedor::FerriteRuntime` closed-set typed-enum
62    /// discriminator axes.
63    pub const ALL: &'static [Self] = &[
64        Self::Keyword,
65        Self::Symbol,
66        Self::KeywordArg,
67        Self::String,
68        Self::Number,
69        Self::Literal,
70        Self::Comment,
71        Self::Accent,
72        Self::Muted,
73        Self::Error,
74        Self::Warning,
75        Self::Info,
76        Self::Hint,
77        Self::Added,
78        Self::Removed,
79        Self::Unchanged,
80    ];
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn semantic_all_enumerates_every_variant_in_declaration_order() {
89        // Fail-before-pass-after pin on the [`Semantic::ALL`] slice:
90        // the slice must list every one of the 15 variants in
91        // declaration order (Keyword → Symbol → KeywordArg → String →
92        // Number → Literal → Comment → Accent → Muted → Error →
93        // Warning → Info → Hint → Added → Removed → Unchanged). Peer
94        // of the sibling ALL slices on the closed-set typed-enum
95        // discriminator axes ([`caixa_core::CaixaKind::ALL`],
96        // [`caixa_core::supervisor::RestartStrategy::ALL`],
97        // [`caixa_core::supervisor::RestartPolicy::ALL`],
98        // [`caixa_core::aplicacao::PlacementStrategy::ALL`],
99        // [`caixa_core::upgrade::UpgradeInstruction::ALL`]). A future
100        // arm addition (a `Namespace` tier between `Symbol` and
101        // `KeywordArg` for the M4 tatara-lisp module system's
102        // qualified-name semantic-token dispatch, a `Deleted` tier
103        // for a hard-delete-mark distinct from `Removed` the future
104        // 3-way diff surface grows) that lands the arm on the enum
105        // but forgets to extend `ALL` must trip this pin rather than
106        // surface as a downstream consumer's silently-partial
107        // iteration.
108        assert_eq!(
109            Semantic::ALL,
110            &[
111                Semantic::Keyword,
112                Semantic::Symbol,
113                Semantic::KeywordArg,
114                Semantic::String,
115                Semantic::Number,
116                Semantic::Literal,
117                Semantic::Comment,
118                Semantic::Accent,
119                Semantic::Muted,
120                Semantic::Error,
121                Semantic::Warning,
122                Semantic::Info,
123                Semantic::Hint,
124                Semantic::Added,
125                Semantic::Removed,
126                Semantic::Unchanged,
127            ],
128        );
129        // Also pin the per-arm `IsVariant`-derived partition: every
130        // arm in `ALL` must satisfy exactly one of the 15 generated
131        // arm-discriminator predicates.
132        for variant in Semantic::ALL {
133            let row = [
134                variant.is_keyword(),
135                variant.is_symbol(),
136                variant.is_keyword_arg(),
137                variant.is_string(),
138                variant.is_number(),
139                variant.is_literal(),
140                variant.is_comment(),
141                variant.is_accent(),
142                variant.is_muted(),
143                variant.is_error(),
144                variant.is_warning(),
145                variant.is_info(),
146                variant.is_hint(),
147                variant.is_added(),
148                variant.is_removed(),
149                variant.is_unchanged(),
150            ];
151            let hits = row.iter().filter(|b| **b).count();
152            assert_eq!(
153                hits, 1,
154                "Semantic::{variant:?} must satisfy exactly one of the \
155                 15 is_* arm-discriminator predicates; got {row:?}",
156            );
157        }
158    }
159
160    #[test]
161    fn semantic_is_variant_predicates_partition_the_arm_set() {
162        // Fail-before-pass-after pin on the [`gen_platform::IsVariant`]
163        // derive: for each of the 15 variants, exactly one of the
164        // generated is_* predicates returns `true` and the other 14
165        // return `false`. Pre-derive the closed 15-arm partition
166        // lived only inside the two theme overlays' 15-arm match
167        // resolvers; a future rebrand (a `#[is_variant(name = "…")]`
168        // drift, a manual hand-rolled `impl` that shadows the
169        // derive-generated method, an arm rename) trips this pin at
170        // caixa-theme build time rather than surfacing far from the
171        // derive declaration. Peer of the sibling
172        // [`caixa_core::CaixaKind`] `IsVariant` partition pin.
173        // A copy-paste flip that reroutes one arm through the wrong
174        // predicate lane trips at the identity-diagonal assertion,
175        // since each variant's row is generated live from `ALL`'s
176        // declaration order rather than transcribed by hand.
177        for (idx, variant) in Semantic::ALL.iter().enumerate() {
178            let observed: [bool; 16] = [
179                variant.is_keyword(),
180                variant.is_symbol(),
181                variant.is_keyword_arg(),
182                variant.is_string(),
183                variant.is_number(),
184                variant.is_literal(),
185                variant.is_comment(),
186                variant.is_accent(),
187                variant.is_muted(),
188                variant.is_error(),
189                variant.is_warning(),
190                variant.is_info(),
191                variant.is_hint(),
192                variant.is_added(),
193                variant.is_removed(),
194                variant.is_unchanged(),
195            ];
196            let mut expected = [false; 16];
197            expected[idx] = true;
198            assert_eq!(
199                observed, expected,
200                "Semantic::{variant:?} at ALL[{idx}] is_* predicates \
201                 must fire only on their own arm lane (identity \
202                 diagonal); got {observed:?}",
203            );
204        }
205    }
206
207    #[test]
208    fn semantic_is_variant_predicates_are_const_fn() {
209        // The [`gen_platform::IsVariant`] derive emits `const fn`
210        // predicates on the peer [`caixa_core::CaixaKind`] /
211        // [`caixa_core::upgrade::UpgradeInstruction`] /
212        // [`caixa_core::supervisor::RestartStrategy`] /
213        // [`caixa_core::supervisor::RestartPolicy`] closed-set typed
214        // enums — pin the same posture on [`Semantic`] so a future
215        // accidental downgrade to non-`const` (an added runtime helper
216        // reachable only from a non-`const` context, a manual hand-
217        // rolled `impl` that shadows the derive-generated method)
218        // trips at caixa-theme build time rather than surfacing as a
219        // downstream `const`-context regression far from the derive
220        // declaration.
221        const { assert!(Semantic::Keyword.is_keyword()) };
222        const { assert!(Semantic::Error.is_error()) };
223        const { assert!(Semantic::Added.is_added()) };
224        const { assert!(Semantic::Unchanged.is_unchanged()) };
225    }
226}