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}