1pub mod kw {
13 #![allow(missing_docs)] pub const DOMAIN: &str = "DOMAIN";
15 pub const IMPORT: &str = "IMPORT";
16 pub const AS: &str = "AS";
17 pub const FACT: &str = "FACT";
18 pub const NOT: &str = "NOT";
19 pub const ASSUME: &str = "ASSUME";
20 pub const PREMISE: &str = "PREMISE";
21 pub const RULE: &str = "RULE";
22 pub const CHECK: &str = "CHECK";
23 pub const BIDIRECTIONAL: &str = "BIDIRECTIONAL";
24 pub const WHEN: &str = "WHEN";
25 pub const THEN: &str = "THEN";
26 pub const AND: &str = "AND";
27 pub const OR: &str = "OR";
28 pub const EXCLUSIVE: &str = "EXCLUSIVE";
29 pub const FORBIDS: &str = "FORBIDS";
30 pub const ONEOF: &str = "ONEOF";
31 pub const ATLEAST: &str = "ATLEAST";
32 pub const SET: &str = "SET";
33 pub const FOR: &str = "FOR";
34 pub const EACH: &str = "EACH";
35 pub const IN: &str = "IN";
36 pub const CLOSE: &str = "CLOSE";
37 pub const TRANSITIVE: &str = "TRANSITIVE";
38 pub const VAR: &str = "VAR";
39 pub const DEFAULT: &str = "DEFAULT";
40 pub const PROVIDE: &str = "PROVIDE";
41}
42
43#[derive(Clone, Copy, PartialEq, Eq)]
47pub struct Card {
48 pub form: &'static str,
50 pub gloss: &'static str,
52 pub example: &'static str,
54}
55
56pub struct Keyword {
59 pub text: &'static str,
61 pub top_level: bool,
64 pub card: Card,
66}
67
68const fn card(form: &'static str, gloss: &'static str, example: &'static str) -> Card {
70 Card {
71 form,
72 gloss,
73 example,
74 }
75}
76
77pub const KEYWORDS: &[Keyword] = &[
81 Keyword {
82 text: kw::DOMAIN,
83 top_level: true,
84 card: card(
85 "DOMAIN <name>",
86 "declare this file's domain (required, once, first) — the namespace bare atoms fall into",
87 "DOMAIN physics",
88 ),
89 },
90 Keyword {
91 text: kw::FACT,
92 top_level: true,
93 card: card(
94 "FACT [<domain>.]<Subject> <predicate> [<object>]",
95 "assert an atom is TRUE",
96 "FACT socrates is human",
97 ),
98 },
99 Keyword {
100 text: kw::NOT,
101 top_level: true,
102 card: card(
103 "NOT <Subject> <predicate> [<object>]",
104 "assert an atom is FALSE",
105 "NOT socrates is immortal",
106 ),
107 },
108 Keyword {
109 text: kw::ASSUME,
110 top_level: true,
111 card: card(
112 "ASSUME [NOT] <Subject> <predicate> [<object>]",
113 "a soft, retractable hypothesis (the solver may ask you to drop it)",
114 "ASSUME release is_ready",
115 ),
116 },
117 Keyword {
118 text: kw::PREMISE,
119 top_level: true,
120 card: card(
121 "PREMISE <name>: then a list body or a WHEN ... THEN implication",
122 "a checked first principle (a constraint that must hold)",
123 "PREMISE wings:\n WHEN bird has feathers\n THEN bird can_fly",
124 ),
125 },
126 Keyword {
127 text: kw::RULE,
128 top_level: true,
129 card: card(
130 "RULE <name>: then a WHEN ... THEN implication",
131 "an inference rule: when the WHEN holds, it derives the THEN as a fact",
132 "RULE mortal:\n WHEN x is human\n THEN x is mortal",
133 ),
134 },
135 Keyword {
136 text: kw::CHECK,
137 top_level: true,
138 card: card(
139 "CHECK [<subject>] [BIDIRECTIONAL]",
140 "run the query; an optional subject narrows the report",
141 "CHECK socrates",
142 ),
143 },
144 Keyword {
145 text: kw::SET,
146 top_level: true,
147 card: card(
148 "SET <name> then one element per line (>= 1)",
149 "declare a finite set of elements to quantify a PREMISE/RULE over (FOR EACH ... IN)",
150 "SET tasks\n deploy\n backup",
151 ),
152 },
153 Keyword {
154 text: kw::CLOSE,
155 top_level: true,
156 card: card(
157 "CLOSE <relation> TRANSITIVE",
158 "make a relation transitive at compile time (a->b, b->c implies a->c); a cycle is an error",
159 "CLOSE depends_on TRANSITIVE",
160 ),
161 },
162 Keyword {
163 text: kw::VAR,
164 top_level: true,
165 card: card(
166 "VAR <name> [DEFAULT true|false]",
167 "declare an external boolean port (a bare proposition); its value comes from CLI/API/data, else DEFAULT, else UNKNOWN",
168 "VAR db_ready DEFAULT false",
169 ),
170 },
171 Keyword {
172 text: kw::PROVIDE,
173 top_level: true,
174 card: card(
175 "PROVIDE [<domain>.]<port|atom>: true|false",
176 "bind an external value (a VAR port, or an atom); a domain. prefix disambiguates across imports. In a --data file or alongside the program",
177 "PROVIDE db_ready: true",
178 ),
179 },
180 Keyword {
181 text: kw::IMPORT,
182 top_level: true,
183 card: card(
184 "IMPORT \"<path>\" [AS <alias>]",
185 "pull in another .vrf source for reuse; its atoms are named <domain>.<atom>",
186 "IMPORT \"physics.vrf\"",
187 ),
188 },
189 Keyword {
190 text: kw::AS,
191 top_level: false,
192 card: card(
193 "IMPORT \"<path>\" AS <alias>",
194 "give the imported domain a local name to reference it by",
195 "IMPORT \"physics.vrf\" AS phys",
196 ),
197 },
198 Keyword {
199 text: kw::FOR,
200 top_level: false,
201 card: card(
202 "PREMISE <name> FOR EACH <binder> IN <set>: then the usual body",
203 "quantify a PREMISE/RULE: instantiate its body once per element of <set>, binding <binder>",
204 "PREMISE colored FOR EACH n IN nodes:\n ONEOF\n n is red\n n is blue",
205 ),
206 },
207 Keyword {
208 text: kw::EACH,
209 top_level: false,
210 card: card(
211 "FOR EACH <binder> IN <set>",
212 "part of the FOR EACH ... IN quantifier on a PREMISE/RULE header",
213 "FOR EACH t IN tasks",
214 ),
215 },
216 Keyword {
217 text: kw::IN,
218 top_level: false,
219 card: card(
220 "FOR EACH <binder> IN <set>",
221 "names the declared SET a FOR EACH quantifier ranges over",
222 "FOR EACH t IN tasks",
223 ),
224 },
225 Keyword {
226 text: kw::WHEN,
227 top_level: false,
228 card: card(
229 "WHEN <literal> (literal = [NOT] <Subject> <predicate> [<object>])",
230 "the condition (if-part) of an implication; continue with AND/OR",
231 "WHEN motor over_100",
232 ),
233 },
234 Keyword {
235 text: kw::THEN,
236 top_level: false,
237 card: card(
238 "THEN <literal>",
239 "the conclusion (then-part) of an implication; continue with AND/OR",
240 "THEN motor uses fast_path",
241 ),
242 },
243 Keyword {
244 text: kw::AND,
245 top_level: false,
246 card: card(
247 "AND <literal>",
248 "extend the current WHEN or THEN group (all must hold); do not mix with OR",
249 "AND motor is reviewed",
250 ),
251 },
252 Keyword {
253 text: kw::OR,
254 top_level: false,
255 card: card(
256 "OR <literal>",
257 "extend the current WHEN or THEN group (at least one holds); do not mix with AND",
258 "OR motor is hotfixed",
259 ),
260 },
261 Keyword {
262 text: kw::EXCLUSIVE,
263 top_level: false,
264 card: card(
265 "EXCLUSIVE then one atom per line (>= 2 atoms)",
266 "at most one of the listed atoms may be TRUE",
267 "EXCLUSIVE\n light is on\n light is off",
268 ),
269 },
270 Keyword {
271 text: kw::FORBIDS,
272 top_level: false,
273 card: card(
274 "FORBIDS then one atom per line (>= 2 atoms)",
275 "at most one may be TRUE (a synonym of EXCLUSIVE)",
276 "FORBIDS\n door is open\n door is locked",
277 ),
278 },
279 Keyword {
280 text: kw::ONEOF,
281 top_level: false,
282 card: card(
283 "ONEOF then one atom per line (>= 2 atoms)",
284 "exactly one of the listed atoms is TRUE",
285 "ONEOF\n mode is idle\n mode is run",
286 ),
287 },
288 Keyword {
289 text: kw::ATLEAST,
290 top_level: false,
291 card: card(
292 "ATLEAST then one atom per line (>= 2 atoms)",
293 "at least one of the listed atoms is TRUE",
294 "ATLEAST\n plan has tests\n plan has review",
295 ),
296 },
297 Keyword {
298 text: kw::BIDIRECTIONAL,
299 top_level: false,
300 card: card(
301 "CHECK [<subject>] BIDIRECTIONAL",
302 "a CHECK modifier: also run the backward pass (finds UNDERDETERMINED)",
303 "CHECK BIDIRECTIONAL",
304 ),
305 },
306 Keyword {
307 text: kw::TRANSITIVE,
308 top_level: false,
309 card: card(
310 "CLOSE <relation> TRANSITIVE",
311 "the closure kind for CLOSE: a->b and b->c implies a->c",
312 "CLOSE depends_on TRANSITIVE",
313 ),
314 },
315 Keyword {
316 text: kw::DEFAULT,
317 top_level: false,
318 card: card(
319 "VAR <name> DEFAULT true|false",
320 "the fallback value of a VAR port when no external value is supplied",
321 "VAR db_ready DEFAULT false",
322 ),
323 },
324];
325
326pub fn is_reserved(word: &str) -> bool {
328 KEYWORDS.iter().any(|k| k.text == word)
329}
330
331pub fn is_top_level(word: &str) -> bool {
333 KEYWORDS.iter().any(|k| k.top_level && k.text == word)
334}
335
336pub fn card_for(keyword: &str) -> Option<&'static Card> {
338 KEYWORDS.iter().find(|k| k.text == keyword).map(|k| &k.card)
339}
340
341pub fn top_level_forms() -> impl Iterator<Item = &'static Keyword> {
344 KEYWORDS.iter().filter(|k| k.top_level)
345}
346
347pub fn top_level_menu() -> alloc::string::String {
351 use alloc::string::String;
352 let words: alloc::vec::Vec<&'static str> = top_level_forms().map(|k| k.text).collect();
353 let mut out = String::new();
354 for (i, w) in words.iter().enumerate() {
355 if i > 0 {
356 out.push_str(", ");
357 if i + 1 == words.len() {
358 out.push_str("or ");
359 }
360 }
361 out.push_str(w);
362 }
363 out
364}
365
366pub fn keyword_in(message: &str) -> Option<&'static str> {
370 message
371 .split(|c: char| !c.is_ascii_alphabetic())
372 .find_map(|w| KEYWORDS.iter().map(|k| k.text).find(|t| *t == w))
373}