ridl_syntax/keywords.rs
1//! The RIDL family reserved-word registry (typl reference §1.4).
2//!
3//! The family maintains one reserved-word registry across every profile: every
4//! family keyword is reserved in every profile, including keywords a profile
5//! does not accept. This module is the single source of truth the lexer maps
6//! identifiers against, and the source a governance test can read.
7//!
8//! An identifier that matches a keyword the active [`Profile`] uses lexes to
9//! that keyword's [`SyntaxKind`]; one that matches any other registry word lexes
10//! to [`SyntaxKind::ReservedWord`]; anything else stays an
11//! [`Ident`](SyntaxKind::Ident).
12
13use crate::syntax_kind::SyntaxKind;
14
15/// The profile a source file is lexed and parsed under (ridl reference §2,
16/// ADR-0008): which member of the family the file is written in. The profile
17/// selects which registry words are active keywords — every other registry
18/// word stays [`SyntaxKind::ReservedWord`] — and where the parser draws its
19/// profile-boundary diagnostics (TYPL-304 / RIDL-403).
20///
21/// `ridl-core` derives the profile from the file extension
22/// (`profile_of_path`): `.ridl` selects [`Profile::Ridl`], `.rsdl` selects
23/// [`Profile::Rsdl`], everything else parses as typl.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum Profile {
26 /// `.typl` — the vocabulary layer. Behavior is identical to the E1
27 /// toolchain: only the typl keywords are active.
28 Typl,
29 /// `.ridl` — the interface-description layer. The typl keywords plus the
30 /// nine ridl words are active; durations and `@` are ordinary tokens.
31 Ridl,
32 /// `.rsdl` — the system-description layer (rsdl reference §2). The typl
33 /// keywords plus the eight rsdl words are active; durations and `@` are
34 /// ordinary tokens, as under [`Profile::Ridl`].
35 Rsdl,
36}
37
38/// The keywords the typl profile uses (typl reference §1.4), paired with the
39/// token kind each lexes to. Every entry here is also present in
40/// [`FAMILY_RESERVED`]; the `family_registry_is_consistent` test guards that.
41const TYPL_KEYWORDS: &[(&str, SyntaxKind)] = &[
42 ("package", SyntaxKind::PackageKw),
43 ("import", SyntaxKind::ImportKw),
44 ("as", SyntaxKind::AsKw),
45 ("internal", SyntaxKind::InternalKw),
46 ("type", SyntaxKind::TypeKw),
47 ("const", SyntaxKind::ConstKw),
48 ("struct", SyntaxKind::StructKw),
49 ("enum", SyntaxKind::EnumKw),
50 ("enumset", SyntaxKind::EnumsetKw),
51 ("union", SyntaxKind::UnionKw),
52 ("boolean", SyntaxKind::BooleanKw),
53 ("integer", SyntaxKind::IntegerKw),
54 ("float", SyntaxKind::FloatKw),
55 ("string", SyntaxKind::StringKw),
56 ("bytes", SyntaxKind::BytesKw),
57 ("true", SyntaxKind::TrueKw),
58 ("false", SyntaxKind::FalseKw),
59 ("step", SyntaxKind::StepKw),
60 ("match", SyntaxKind::MatchKw),
61 ("reserved", SyntaxKind::ReservedKw),
62 ("error", SyntaxKind::ErrorKw),
63];
64
65/// The nine words the ridl profile activates beyond typl's set (ridl reference
66/// §2.3): the interaction and container keywords plus the two expr-core words
67/// (`require`/`ensure`). Every entry here is also present in
68/// [`FAMILY_RESERVED`]; the `family_registry_is_consistent` test guards that.
69const RIDL_KEYWORDS: &[(&str, SyntaxKind)] = &[
70 ("interface", SyntaxKind::InterfaceKw),
71 ("service", SyntaxKind::ServiceKw),
72 ("signal", SyntaxKind::SignalKw),
73 ("event", SyntaxKind::EventKw),
74 ("command", SyntaxKind::CommandKw),
75 ("query", SyntaxKind::QueryKw),
76 ("fixed", SyntaxKind::FixedKw),
77 ("require", SyntaxKind::RequireKw),
78 ("ensure", SyntaxKind::EnsureKw),
79];
80
81/// The eight words the rsdl profile activates beyond typl's set (rsdl reference
82/// §2): the five declaration keywords, the two component-line keywords, and the
83/// `for` of a deployment's relation clause. Every entry here is also present in
84/// [`FAMILY_RESERVED`]; the `family_registry_is_consistent` test guards that.
85const RSDL_KEYWORDS: &[(&str, SyntaxKind)] = &[
86 ("system", SyntaxKind::SystemKw),
87 ("component", SyntaxKind::ComponentKw),
88 ("distribution", SyntaxKind::DistributionKw),
89 ("deployment", SyntaxKind::DeploymentKw),
90 ("machine", SyntaxKind::MachineKw),
91 ("offers", SyntaxKind::OffersKw),
92 ("requires", SyntaxKind::RequiresKw),
93 ("for", SyntaxKind::ForKw),
94];
95
96/// The full family reserved-word registry (typl reference §1.4): the words the
97/// typl profile uses, followed by the words reserved family-wide for the other
98/// profiles but rejected by `.typl`. This is the governance source of truth —
99/// every word here is reserved in every profile and may not be an identifier.
100///
101/// The registry grows as the other profiles land; the per-profile keyword
102/// sections of each language reference enumerate their own additions, and the
103/// union of those sections is this list until the platform spec extracts it as a
104/// standalone normative list.
105pub const FAMILY_RESERVED: &[&str] = &[
106 // typl (used) — typl reference §1.4.
107 "package",
108 "import",
109 "as",
110 "internal",
111 "type",
112 "const",
113 "struct",
114 "enum",
115 "enumset",
116 "union",
117 "boolean",
118 "integer",
119 "float",
120 "string",
121 "bytes",
122 "true",
123 "false",
124 "step",
125 "match",
126 "reserved",
127 "error",
128 // ridl.
129 "interface",
130 "service",
131 "signal",
132 "event",
133 "command",
134 "query",
135 // Shared by ridl and uxdl — one registry entry per concept (typl §1.4).
136 "fixed",
137 // uxdl.
138 "view",
139 "display",
140 "input",
141 "action",
142 "activate",
143 "toggle",
144 "select",
145 "adjust",
146 "dismiss",
147 "fetch",
148 "states",
149 "during",
150 "navigate",
151 "scroll",
152 "drag",
153 "observe",
154 "surface",
155 "agent",
156 // expr core.
157 "require",
158 "ensure",
159 // rmdl.
160 "model",
161 "function",
162 "let",
163 "init",
164 "last",
165 "case",
166 "if",
167 "then",
168 "else",
169 "when",
170 "emit",
171 "merge",
172 "current",
173 "state",
174 "transition",
175 "automaton",
176 // rsdl — rsdl reference §2. The v0.1 words (`provides`, `instance`,
177 // `assurance`, `target`, `place`, `on`, `transport`, `bundle`, `time`,
178 // `base`, `redundant`, `supervise`, `degraded`) left the registry with
179 // rsdl v0.2: no other profile uses one, so each is an identifier again.
180 "system",
181 "component",
182 "distribution",
183 "deployment",
184 "machine",
185 "offers",
186 "requires",
187 "for",
188];
189
190/// The [`SyntaxKind`] a typl keyword lexes to, or `None` if `text` is not a
191/// keyword the typl profile uses (it may still be a reserved word — see
192/// [`is_reserved`]).
193pub fn typl_keyword(text: &str) -> Option<SyntaxKind> {
194 TYPL_KEYWORDS
195 .iter()
196 .find(|(word, _)| *word == text)
197 .map(|(_, kind)| *kind)
198}
199
200/// The [`SyntaxKind`] one of the nine ridl words lexes to under
201/// [`Profile::Ridl`], or `None` if `text` is not one of them. The parser also
202/// uses this table to spot an interaction keyword in a typl parse (TYPL-304).
203pub fn ridl_keyword(text: &str) -> Option<SyntaxKind> {
204 RIDL_KEYWORDS
205 .iter()
206 .find(|(word, _)| *word == text)
207 .map(|(_, kind)| *kind)
208}
209
210/// The [`SyntaxKind`] one of the eight rsdl words lexes to under
211/// [`Profile::Rsdl`], or `None` if `text` is not one of them.
212pub fn rsdl_keyword(text: &str) -> Option<SyntaxKind> {
213 RSDL_KEYWORDS
214 .iter()
215 .find(|(word, _)| *word == text)
216 .map(|(_, kind)| *kind)
217}
218
219/// The [`SyntaxKind`] `text` lexes to as an active keyword of `profile`, or
220/// `None` when the profile does not use it — it may still be a reserved word
221/// of another profile ([`is_reserved`]) or an ordinary identifier.
222pub fn keyword_in(profile: Profile, text: &str) -> Option<SyntaxKind> {
223 match profile {
224 Profile::Typl => typl_keyword(text),
225 Profile::Ridl => typl_keyword(text).or_else(|| ridl_keyword(text)),
226 Profile::Rsdl => typl_keyword(text).or_else(|| rsdl_keyword(text)),
227 }
228}
229
230/// Whether `text` is reserved anywhere in the family — a keyword the typl
231/// profile uses or a word reserved for another profile. A reserved word may not
232/// be used as an identifier in any profile.
233pub fn is_reserved(text: &str) -> bool {
234 FAMILY_RESERVED.contains(&text)
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn family_registry_is_consistent() {
243 // Every keyword the typl, ridl or rsdl profile uses is in the family
244 // registry.
245 for (word, _) in TYPL_KEYWORDS
246 .iter()
247 .chain(RIDL_KEYWORDS)
248 .chain(RSDL_KEYWORDS)
249 {
250 assert!(
251 FAMILY_RESERVED.contains(word),
252 "used keyword `{word}` is missing from FAMILY_RESERVED",
253 );
254 }
255 // No word is claimed by two profiles' own tables.
256 for (word, _) in RIDL_KEYWORDS {
257 assert!(
258 typl_keyword(word).is_none(),
259 "`{word}` is in both TYPL_KEYWORDS and RIDL_KEYWORDS",
260 );
261 }
262 for (word, _) in RSDL_KEYWORDS {
263 assert!(
264 typl_keyword(word).is_none() && ridl_keyword(word).is_none(),
265 "`{word}` is in RSDL_KEYWORDS and in the typl or ridl table",
266 );
267 }
268 // The registry has no duplicate entries.
269 for (i, word) in FAMILY_RESERVED.iter().enumerate() {
270 assert!(
271 !FAMILY_RESERVED[..i].contains(word),
272 "duplicate registry word `{word}`",
273 );
274 }
275 }
276
277 #[test]
278 fn keyword_in_maps_per_profile() {
279 // A typl keyword is active in both profiles.
280 assert_eq!(keyword_in(Profile::Typl, "type"), Some(SyntaxKind::TypeKw));
281 assert_eq!(keyword_in(Profile::Ridl, "type"), Some(SyntaxKind::TypeKw));
282 // A ridl word is active under Ridl only.
283 assert_eq!(keyword_in(Profile::Typl, "signal"), None);
284 assert_eq!(
285 keyword_in(Profile::Ridl, "signal"),
286 Some(SyntaxKind::SignalKw)
287 );
288 assert_eq!(
289 keyword_in(Profile::Ridl, "ensure"),
290 Some(SyntaxKind::EnsureKw)
291 );
292 // A word of another profile is active in neither.
293 assert_eq!(keyword_in(Profile::Typl, "model"), None);
294 assert_eq!(keyword_in(Profile::Ridl, "model"), None);
295 // An ordinary identifier is never a keyword.
296 assert_eq!(keyword_in(Profile::Ridl, "Speed"), None);
297 }
298
299 #[test]
300 fn used_keywords_map_to_their_kind() {
301 assert_eq!(typl_keyword("package"), Some(SyntaxKind::PackageKw));
302 assert_eq!(typl_keyword("enumset"), Some(SyntaxKind::EnumsetKw));
303 assert_eq!(typl_keyword("error"), Some(SyntaxKind::ErrorKw));
304 // A reserved-but-unused word is not a typl keyword.
305 assert_eq!(typl_keyword("signal"), None);
306 // A plain identifier is neither.
307 assert_eq!(typl_keyword("Speed"), None);
308 }
309
310 #[test]
311 fn reserved_covers_used_and_unused() {
312 assert!(is_reserved("type")); // used
313 assert!(is_reserved("signal")); // reserved for ridl
314 assert!(is_reserved("model")); // reserved for rmdl
315 assert!(!is_reserved("Speed")); // ordinary identifier
316 assert!(!is_reserved("node")); // considered and rejected — never reserved
317 }
318
319 #[test]
320 fn fixed_is_an_active_ridl_keyword_and_final_is_not_reserved() {
321 assert_eq!(
322 keyword_in(Profile::Ridl, "fixed"),
323 Some(SyntaxKind::FixedKw),
324 "`fixed` is the provisioned-constant keyword in the ridl profile"
325 );
326 assert!(
327 !FAMILY_RESERVED.contains(&"final"),
328 "`final` was retired from the registry, the way `default` was"
329 );
330 }
331}