#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Keyword(u8);
macro_rules! keywords {
($($index:expr => $constant:ident, $text:literal, $fallback:literal;)*) => {
impl Keyword {
$(
#[doc = concat!("The `", stringify!($constant), "` keyword.")]
pub const $constant: Keyword = Keyword($index);
)*
pub fn text(self) -> &'static [u8] {
match self.0 {
$($index => $text,)*
_ => b"",
}
}
pub fn may_fall_back(self) -> bool {
match self.0 {
$($index => $fallback,)*
_ => false,
}
}
}
pub const KEYWORDS: &[Keyword] = &[$(Keyword::$constant),*];
pub fn lookup(word: &[u8]) -> Option<Keyword> {
if word.len() > MAX_KEYWORD_LEN {
return None;
}
let mut upper = [0u8; MAX_KEYWORD_LEN];
let slot = upper.get_mut(..word.len())?;
for (target, byte) in slot.iter_mut().zip(word.iter()) {
*target = byte.to_ascii_uppercase();
}
let upper = upper.get(..word.len())?;
match upper {
$($text => Some(Keyword::$constant),)*
_ => None,
}
}
};
}
pub const MAX_KEYWORD_LEN: usize = 17;
keywords! {
0 => ABORT, b"ABORT", true;
1 => ACTION, b"ACTION", true;
2 => ADD, b"ADD", false;
3 => AFTER, b"AFTER", true;
4 => ALL, b"ALL", false;
5 => ALTER, b"ALTER", false;
6 => ALWAYS, b"ALWAYS", true;
7 => ANALYZE, b"ANALYZE", true;
8 => AND, b"AND", false;
9 => AS, b"AS", false;
10 => ASC, b"ASC", true;
11 => ATTACH, b"ATTACH", true;
12 => AUTOINCREMENT, b"AUTOINCREMENT", false;
13 => BEFORE, b"BEFORE", true;
14 => BEGIN, b"BEGIN", true;
15 => BETWEEN, b"BETWEEN", false;
16 => BY, b"BY", true;
17 => CASCADE, b"CASCADE", true;
18 => CASE, b"CASE", false;
19 => CAST, b"CAST", true;
20 => CHECK, b"CHECK", false;
21 => COLLATE, b"COLLATE", false;
22 => COLUMN, b"COLUMN", true;
23 => COMMIT, b"COMMIT", false;
24 => CONFLICT, b"CONFLICT", true;
25 => CONSTRAINT, b"CONSTRAINT", false;
26 => CREATE, b"CREATE", false;
27 => CROSS, b"CROSS", false;
28 => CURRENT, b"CURRENT", true;
29 => CURRENT_DATE, b"CURRENT_DATE", false;
30 => CURRENT_TIME, b"CURRENT_TIME", false;
31 => CURRENT_TIMESTAMP, b"CURRENT_TIMESTAMP", false;
32 => DATABASE, b"DATABASE", true;
33 => DEFAULT, b"DEFAULT", false;
34 => DEFERRABLE, b"DEFERRABLE", false;
35 => DEFERRED, b"DEFERRED", true;
36 => DELETE, b"DELETE", false;
37 => DESC, b"DESC", true;
38 => DETACH, b"DETACH", true;
39 => DISTINCT, b"DISTINCT", false;
40 => DO, b"DO", true;
41 => DROP, b"DROP", false;
42 => EACH, b"EACH", true;
43 => ELSE, b"ELSE", false;
44 => END, b"END", true;
45 => ESCAPE, b"ESCAPE", false;
46 => EXCEPT, b"EXCEPT", false;
47 => EXCLUDE, b"EXCLUDE", true;
48 => EXCLUSIVE, b"EXCLUSIVE", true;
49 => EXISTS, b"EXISTS", false;
50 => EXPLAIN, b"EXPLAIN", true;
51 => FAIL, b"FAIL", true;
52 => FILTER, b"FILTER", true;
53 => FIRST, b"FIRST", true;
54 => FOLLOWING, b"FOLLOWING", true;
55 => FOR, b"FOR", true;
56 => FOREIGN, b"FOREIGN", false;
57 => FROM, b"FROM", false;
58 => FULL, b"FULL", false;
59 => GENERATED, b"GENERATED", true;
60 => GLOB, b"GLOB", false;
61 => GROUP, b"GROUP", false;
62 => GROUPS, b"GROUPS", true;
63 => HAVING, b"HAVING", false;
64 => IF, b"IF", true;
65 => IGNORE, b"IGNORE", true;
66 => IMMEDIATE, b"IMMEDIATE", true;
67 => IN, b"IN", false;
68 => INDEX, b"INDEX", false;
69 => INDEXED, b"INDEXED", false;
70 => INITIALLY, b"INITIALLY", true;
71 => INNER, b"INNER", false;
72 => INSERT, b"INSERT", false;
73 => INSTEAD, b"INSTEAD", true;
74 => INTERSECT, b"INTERSECT", false;
75 => INTO, b"INTO", false;
76 => IS, b"IS", false;
77 => ISNULL, b"ISNULL", false;
78 => JOIN, b"JOIN", false;
79 => KEY, b"KEY", true;
80 => LAST, b"LAST", true;
81 => LEFT, b"LEFT", false;
82 => LIKE, b"LIKE", false;
83 => LIMIT, b"LIMIT", false;
84 => MATCH, b"MATCH", true;
85 => MATERIALIZED, b"MATERIALIZED", true;
86 => NATURAL, b"NATURAL", false;
87 => NO, b"NO", true;
88 => NOT, b"NOT", false;
89 => NOTHING, b"NOTHING", false;
90 => NOTNULL, b"NOTNULL", false;
91 => NULL, b"NULL", false;
92 => NULLS, b"NULLS", true;
93 => OF, b"OF", true;
94 => OFFSET, b"OFFSET", true;
95 => ON, b"ON", false;
96 => OR, b"OR", false;
97 => ORDER, b"ORDER", false;
98 => OTHERS, b"OTHERS", true;
99 => OUTER, b"OUTER", false;
100 => OVER, b"OVER", true;
101 => PARTITION, b"PARTITION", true;
102 => PLAN, b"PLAN", true;
103 => PRAGMA, b"PRAGMA", true;
104 => PRECEDING, b"PRECEDING", true;
105 => PRIMARY, b"PRIMARY", false;
106 => QUERY, b"QUERY", true;
107 => RAISE, b"RAISE", true;
108 => RANGE, b"RANGE", true;
109 => RECURSIVE, b"RECURSIVE", true;
110 => REFERENCES, b"REFERENCES", false;
111 => REGEXP, b"REGEXP", false;
112 => REINDEX, b"REINDEX", true;
113 => RELEASE, b"RELEASE", true;
114 => RENAME, b"RENAME", true;
115 => REPLACE, b"REPLACE", true;
116 => RESTRICT, b"RESTRICT", true;
117 => RETURNING, b"RETURNING", false;
118 => RIGHT, b"RIGHT", false;
119 => ROLLBACK, b"ROLLBACK", true;
120 => ROW, b"ROW", true;
121 => ROWS, b"ROWS", true;
122 => SAVEPOINT, b"SAVEPOINT", true;
123 => SELECT, b"SELECT", false;
124 => SET, b"SET", false;
125 => TABLE, b"TABLE", false;
126 => TEMP, b"TEMP", true;
127 => TEMPORARY, b"TEMPORARY", true;
128 => THEN, b"THEN", false;
129 => TIES, b"TIES", true;
130 => TO, b"TO", false;
131 => TRANSACTION, b"TRANSACTION", false;
132 => TRIGGER, b"TRIGGER", true;
133 => UNBOUNDED, b"UNBOUNDED", true;
134 => UNION, b"UNION", false;
135 => UNIQUE, b"UNIQUE", false;
136 => UPDATE, b"UPDATE", false;
137 => USING, b"USING", false;
138 => VACUUM, b"VACUUM", true;
139 => VALUES, b"VALUES", false;
140 => VIEW, b"VIEW", true;
141 => VIRTUAL, b"VIRTUAL", true;
142 => WHEN, b"WHEN", false;
143 => WHERE, b"WHERE", false;
144 => WINDOW, b"WINDOW", true;
145 => WITH, b"WITH", true;
146 => WITHOUT, b"WITHOUT", true;
}
impl Keyword {
pub fn as_str(self) -> &'static str {
core::str::from_utf8(self.text()).unwrap_or("")
}
pub const JOIN_KEYWORDS: &'static [Keyword] = &[
Keyword::CROSS,
Keyword::FULL,
Keyword::INNER,
Keyword::LEFT,
Keyword::NATURAL,
Keyword::OUTER,
Keyword::RIGHT,
];
pub fn is_join_keyword(self) -> bool {
Keyword::JOIN_KEYWORDS.contains(&self)
}
pub fn may_be_name(self) -> bool {
self.may_fall_back() || self.is_join_keyword() || self == Keyword::INDEXED
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_table_holds_every_documented_keyword() {
assert_eq!(KEYWORDS.len(), 147);
}
#[test]
fn lookup_ignores_case() {
assert_eq!(lookup(b"select"), Some(Keyword::SELECT));
assert_eq!(lookup(b"SeLeCt"), Some(Keyword::SELECT));
assert_eq!(lookup(b"SELECT"), Some(Keyword::SELECT));
assert_eq!(lookup(b"selectx"), None);
}
#[test]
fn an_overlong_word_is_not_a_keyword() {
assert_eq!(lookup(&[b'A'; 512]), None);
}
#[test]
fn every_keyword_round_trips() {
for keyword in KEYWORDS {
let text = keyword.text();
assert_eq!(lookup(text), Some(*keyword), "{}", keyword.as_str());
}
}
#[test]
fn the_fallback_set_matches_the_grammar() {
assert!(Keyword::KEY.may_fall_back());
assert!(Keyword::MATCH.may_fall_back());
assert!(Keyword::ROWS.may_fall_back());
assert!(!Keyword::SELECT.may_fall_back());
assert!(!Keyword::FROM.may_fall_back());
assert!(!Keyword::WHERE.may_fall_back());
}
#[test]
fn a_join_keyword_is_a_name_without_being_a_fallback() {
for keyword in Keyword::JOIN_KEYWORDS {
assert!(!keyword.may_fall_back(), "{}", keyword.as_str());
assert!(keyword.may_be_name(), "{}", keyword.as_str());
}
assert!(!Keyword::INDEXED.may_fall_back());
assert!(Keyword::INDEXED.may_be_name());
}
#[test]
fn the_name_set_is_the_fallback_set_plus_the_token_class() {
let extra: Vec<&str> = KEYWORDS
.iter()
.filter(|keyword| keyword.may_be_name() && !keyword.may_fall_back())
.map(|keyword| keyword.as_str())
.collect();
assert_eq!(
extra,
vec!["CROSS", "FULL", "INDEXED", "INNER", "LEFT", "NATURAL", "OUTER", "RIGHT"]
);
for keyword in KEYWORDS {
if keyword.may_fall_back() {
assert!(keyword.may_be_name(), "{}", keyword.as_str());
}
}
}
#[test]
fn a_hard_keyword_is_never_a_name() {
assert!(!Keyword::SELECT.may_be_name());
assert!(!Keyword::FROM.may_be_name());
assert!(!Keyword::JOIN.may_be_name());
assert!(!Keyword::ON.may_be_name());
}
}