1#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct Keyword(u8);
50
51macro_rules! keywords {
53 ($($index:expr => $constant:ident, $text:literal, $fallback:literal;)*) => {
54 impl Keyword {
55 $(
56 #[doc = concat!("The `", stringify!($constant), "` keyword.")]
57 pub const $constant: Keyword = Keyword($index);
58 )*
59
60 pub fn text(self) -> &'static [u8] {
62 match self.0 {
63 $($index => $text,)*
64 _ => b"",
65 }
66 }
67
68 pub fn may_fall_back(self) -> bool {
74 match self.0 {
75 $($index => $fallback,)*
76 _ => false,
77 }
78 }
79 }
80
81 pub const KEYWORDS: &[Keyword] = &[$(Keyword::$constant),*];
83
84 pub fn lookup(word: &[u8]) -> Option<Keyword> {
86 if word.len() > MAX_KEYWORD_LEN {
87 return None;
88 }
89 let mut upper = [0u8; MAX_KEYWORD_LEN];
90 let slot = upper.get_mut(..word.len())?;
91 for (target, byte) in slot.iter_mut().zip(word.iter()) {
92 *target = byte.to_ascii_uppercase();
93 }
94 let upper = upper.get(..word.len())?;
95 match upper {
96 $($text => Some(Keyword::$constant),)*
97 _ => None,
98 }
99 }
100 };
101}
102
103pub const MAX_KEYWORD_LEN: usize = 17;
105
106keywords! {
107 0 => ABORT, b"ABORT", true;
108 1 => ACTION, b"ACTION", true;
109 2 => ADD, b"ADD", false;
110 3 => AFTER, b"AFTER", true;
111 4 => ALL, b"ALL", false;
112 5 => ALTER, b"ALTER", false;
113 6 => ALWAYS, b"ALWAYS", true;
114 7 => ANALYZE, b"ANALYZE", true;
115 8 => AND, b"AND", false;
116 9 => AS, b"AS", false;
117 10 => ASC, b"ASC", true;
118 11 => ATTACH, b"ATTACH", true;
119 12 => AUTOINCREMENT, b"AUTOINCREMENT", false;
120 13 => BEFORE, b"BEFORE", true;
121 14 => BEGIN, b"BEGIN", true;
122 15 => BETWEEN, b"BETWEEN", false;
123 16 => BY, b"BY", true;
124 17 => CASCADE, b"CASCADE", true;
125 18 => CASE, b"CASE", false;
126 19 => CAST, b"CAST", true;
127 20 => CHECK, b"CHECK", false;
128 21 => COLLATE, b"COLLATE", false;
129 22 => COLUMN, b"COLUMN", true;
130 23 => COMMIT, b"COMMIT", false;
131 24 => CONFLICT, b"CONFLICT", true;
132 25 => CONSTRAINT, b"CONSTRAINT", false;
133 26 => CREATE, b"CREATE", false;
134 27 => CROSS, b"CROSS", false;
135 28 => CURRENT, b"CURRENT", true;
136 29 => CURRENT_DATE, b"CURRENT_DATE", false;
137 30 => CURRENT_TIME, b"CURRENT_TIME", false;
138 31 => CURRENT_TIMESTAMP, b"CURRENT_TIMESTAMP", false;
139 32 => DATABASE, b"DATABASE", true;
140 33 => DEFAULT, b"DEFAULT", false;
141 34 => DEFERRABLE, b"DEFERRABLE", false;
142 35 => DEFERRED, b"DEFERRED", true;
143 36 => DELETE, b"DELETE", false;
144 37 => DESC, b"DESC", true;
145 38 => DETACH, b"DETACH", true;
146 39 => DISTINCT, b"DISTINCT", false;
147 40 => DO, b"DO", true;
148 41 => DROP, b"DROP", false;
149 42 => EACH, b"EACH", true;
150 43 => ELSE, b"ELSE", false;
151 44 => END, b"END", true;
152 45 => ESCAPE, b"ESCAPE", false;
153 46 => EXCEPT, b"EXCEPT", false;
154 47 => EXCLUDE, b"EXCLUDE", true;
155 48 => EXCLUSIVE, b"EXCLUSIVE", true;
156 49 => EXISTS, b"EXISTS", false;
157 50 => EXPLAIN, b"EXPLAIN", true;
158 51 => FAIL, b"FAIL", true;
159 52 => FILTER, b"FILTER", true;
160 53 => FIRST, b"FIRST", true;
161 54 => FOLLOWING, b"FOLLOWING", true;
162 55 => FOR, b"FOR", true;
163 56 => FOREIGN, b"FOREIGN", false;
164 57 => FROM, b"FROM", false;
165 58 => FULL, b"FULL", false;
166 59 => GENERATED, b"GENERATED", true;
167 60 => GLOB, b"GLOB", false;
168 61 => GROUP, b"GROUP", false;
169 62 => GROUPS, b"GROUPS", true;
170 63 => HAVING, b"HAVING", false;
171 64 => IF, b"IF", true;
172 65 => IGNORE, b"IGNORE", true;
173 66 => IMMEDIATE, b"IMMEDIATE", true;
174 67 => IN, b"IN", false;
175 68 => INDEX, b"INDEX", false;
176 69 => INDEXED, b"INDEXED", false;
177 70 => INITIALLY, b"INITIALLY", true;
178 71 => INNER, b"INNER", false;
179 72 => INSERT, b"INSERT", false;
180 73 => INSTEAD, b"INSTEAD", true;
181 74 => INTERSECT, b"INTERSECT", false;
182 75 => INTO, b"INTO", false;
183 76 => IS, b"IS", false;
184 77 => ISNULL, b"ISNULL", false;
185 78 => JOIN, b"JOIN", false;
186 79 => KEY, b"KEY", true;
187 80 => LAST, b"LAST", true;
188 81 => LEFT, b"LEFT", false;
189 82 => LIKE, b"LIKE", false;
190 83 => LIMIT, b"LIMIT", false;
191 84 => MATCH, b"MATCH", true;
192 85 => MATERIALIZED, b"MATERIALIZED", true;
193 86 => NATURAL, b"NATURAL", false;
194 87 => NO, b"NO", true;
195 88 => NOT, b"NOT", false;
196 89 => NOTHING, b"NOTHING", false;
197 90 => NOTNULL, b"NOTNULL", false;
198 91 => NULL, b"NULL", false;
199 92 => NULLS, b"NULLS", true;
200 93 => OF, b"OF", true;
201 94 => OFFSET, b"OFFSET", true;
202 95 => ON, b"ON", false;
203 96 => OR, b"OR", false;
204 97 => ORDER, b"ORDER", false;
205 98 => OTHERS, b"OTHERS", true;
206 99 => OUTER, b"OUTER", false;
207 100 => OVER, b"OVER", true;
208 101 => PARTITION, b"PARTITION", true;
209 102 => PLAN, b"PLAN", true;
210 103 => PRAGMA, b"PRAGMA", true;
211 104 => PRECEDING, b"PRECEDING", true;
212 105 => PRIMARY, b"PRIMARY", false;
213 106 => QUERY, b"QUERY", true;
214 107 => RAISE, b"RAISE", true;
215 108 => RANGE, b"RANGE", true;
216 109 => RECURSIVE, b"RECURSIVE", true;
217 110 => REFERENCES, b"REFERENCES", false;
218 111 => REGEXP, b"REGEXP", false;
219 112 => REINDEX, b"REINDEX", true;
220 113 => RELEASE, b"RELEASE", true;
221 114 => RENAME, b"RENAME", true;
222 115 => REPLACE, b"REPLACE", true;
223 116 => RESTRICT, b"RESTRICT", true;
224 117 => RETURNING, b"RETURNING", false;
225 118 => RIGHT, b"RIGHT", false;
226 119 => ROLLBACK, b"ROLLBACK", true;
227 120 => ROW, b"ROW", true;
228 121 => ROWS, b"ROWS", true;
229 122 => SAVEPOINT, b"SAVEPOINT", true;
230 123 => SELECT, b"SELECT", false;
231 124 => SET, b"SET", false;
232 125 => TABLE, b"TABLE", false;
233 126 => TEMP, b"TEMP", true;
234 127 => TEMPORARY, b"TEMPORARY", true;
235 128 => THEN, b"THEN", false;
236 129 => TIES, b"TIES", true;
237 130 => TO, b"TO", false;
238 131 => TRANSACTION, b"TRANSACTION", false;
239 132 => TRIGGER, b"TRIGGER", true;
240 133 => UNBOUNDED, b"UNBOUNDED", true;
241 134 => UNION, b"UNION", false;
242 135 => UNIQUE, b"UNIQUE", false;
243 136 => UPDATE, b"UPDATE", false;
244 137 => USING, b"USING", false;
245 138 => VACUUM, b"VACUUM", true;
246 139 => VALUES, b"VALUES", false;
247 140 => VIEW, b"VIEW", true;
248 141 => VIRTUAL, b"VIRTUAL", true;
249 142 => WHEN, b"WHEN", false;
250 143 => WHERE, b"WHERE", false;
251 144 => WINDOW, b"WINDOW", true;
252 145 => WITH, b"WITH", true;
253 146 => WITHOUT, b"WITHOUT", true;
254}
255
256impl Keyword {
257 pub fn as_str(self) -> &'static str {
259 core::str::from_utf8(self.text()).unwrap_or("")
260 }
261
262 pub const JOIN_KEYWORDS: &'static [Keyword] = &[
269 Keyword::CROSS,
270 Keyword::FULL,
271 Keyword::INNER,
272 Keyword::LEFT,
273 Keyword::NATURAL,
274 Keyword::OUTER,
275 Keyword::RIGHT,
276 ];
277
278 pub fn is_join_keyword(self) -> bool {
280 Keyword::JOIN_KEYWORDS.contains(&self)
281 }
282
283 pub fn may_be_name(self) -> bool {
296 self.may_fall_back() || self.is_join_keyword() || self == Keyword::INDEXED
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
307 fn the_table_holds_every_documented_keyword() {
308 assert_eq!(KEYWORDS.len(), 147);
309 }
310
311 #[test]
313 fn lookup_ignores_case() {
314 assert_eq!(lookup(b"select"), Some(Keyword::SELECT));
315 assert_eq!(lookup(b"SeLeCt"), Some(Keyword::SELECT));
316 assert_eq!(lookup(b"SELECT"), Some(Keyword::SELECT));
317 assert_eq!(lookup(b"selectx"), None);
318 }
319
320 #[test]
323 fn an_overlong_word_is_not_a_keyword() {
324 assert_eq!(lookup(&[b'A'; 512]), None);
325 }
326
327 #[test]
329 fn every_keyword_round_trips() {
330 for keyword in KEYWORDS {
331 let text = keyword.text();
332 assert_eq!(lookup(text), Some(*keyword), "{}", keyword.as_str());
333 }
334 }
335
336 #[test]
339 fn the_fallback_set_matches_the_grammar() {
340 assert!(Keyword::KEY.may_fall_back());
341 assert!(Keyword::MATCH.may_fall_back());
342 assert!(Keyword::ROWS.may_fall_back());
343 assert!(!Keyword::SELECT.may_fall_back());
344 assert!(!Keyword::FROM.may_fall_back());
345 assert!(!Keyword::WHERE.may_fall_back());
346 }
347
348 #[test]
351 fn a_join_keyword_is_a_name_without_being_a_fallback() {
352 for keyword in Keyword::JOIN_KEYWORDS {
353 assert!(!keyword.may_fall_back(), "{}", keyword.as_str());
354 assert!(keyword.may_be_name(), "{}", keyword.as_str());
355 }
356 assert!(!Keyword::INDEXED.may_fall_back());
357 assert!(Keyword::INDEXED.may_be_name());
358 }
359
360 #[test]
365 fn the_name_set_is_the_fallback_set_plus_the_token_class() {
366 let extra: Vec<&str> = KEYWORDS
367 .iter()
368 .filter(|keyword| keyword.may_be_name() && !keyword.may_fall_back())
369 .map(|keyword| keyword.as_str())
370 .collect();
371 assert_eq!(
372 extra,
373 vec!["CROSS", "FULL", "INDEXED", "INNER", "LEFT", "NATURAL", "OUTER", "RIGHT"]
374 );
375 for keyword in KEYWORDS {
376 if keyword.may_fall_back() {
377 assert!(keyword.may_be_name(), "{}", keyword.as_str());
378 }
379 }
380 }
381
382 #[test]
384 fn a_hard_keyword_is_never_a_name() {
385 assert!(!Keyword::SELECT.may_be_name());
386 assert!(!Keyword::FROM.may_be_name());
387 assert!(!Keyword::JOIN.may_be_name());
388 assert!(!Keyword::ON.may_be_name());
389 }
390}