Skip to main content

rudb_parse/
rules.rs

1//! The shape of the generated rule table, and the filter that reads it.
2//!
3//! `generated::rules` is data. This is the handful of types that give it meaning, and they are
4//! written by hand because they are an interface: the generator in `xtask` writes discriminants
5//! that have to mean the same thing here, and there is a test on each side that says so.
6//!
7//! The one idea worth stating on its own is the FIRST filter. A choice in this grammar can have
8//! forty alternatives, `Statement` has thirty six, and upstream tries them in order, descending
9//! into each one far enough to fail. Every node here carries a 64 bit set of the token keys it can
10//! begin with, and a token maps to exactly one of those keys, so an alternative that cannot
11//! possibly match is skipped on one AND rather than on a subtree walk. The set is a superset by
12//! construction: keywords share 58 buckets, so a bit that is set may still fail, and a bit that is
13//! clear can never match. Being wrong in that direction costs a wasted attempt and never changes
14//! what the parser accepts, which is what makes it safe to put in front of a dialect we are
15//! copying rather than defining.
16//!
17//! `spec/20-the-grammar.md` sections 3 and 5.
18
19use crate::token::{Kind, NOT_A_KEYWORD, Token};
20
21/// What a node is.
22///
23/// The discriminants are written into `generated::rules` as `Op::Name`, so they are not load
24/// bearing on their own, but `xtask`'s copy of this enum has to have the same variants in the same
25/// order for the generator to be able to name them. `the_ops_match_the_generator` is that check.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[repr(u8)]
28pub enum Op {
29    /// A word. `a` indexes `generated::keywords::KEYWORDS`, and the comparison is an index compare
30    /// because the tokenizer already resolved the token's word to the same table.
31    Keyword = 0,
32    /// Punctuation or an operator. `a` indexes `SYMBOLS` and the comparison is on text.
33    Symbol = 1,
34    /// A reference to a rule. `a` is the rule index.
35    Rule = 2,
36    /// `a` is a start index into `CHILDREN`, `b` is how many. All of them, in order.
37    Sequence = 3,
38    /// Same layout. Ordered choice, first success wins, no backtracking into a taken alternative.
39    Choice = 4,
40    /// `a` is the child node. Matches it or matches nothing.
41    Optional = 5,
42    /// `a` is the child node. One or more. `X*` is `Optional(Repeat(X))` in the table, because
43    /// that is what upstream builds and a separate zero or more node would be a second thing to
44    /// keep in step for no gain.
45    Repeat = 6,
46    /// An identifier matcher. `a` is a `Suggestion`, `flags` bit 0 is `RESERVED`.
47    Identifier = 7,
48    /// A numeric literal.
49    Number = 8,
50    /// A string literal, including its adjacent continuations.
51    String = 9,
52    /// An operator token, subject to the exclusions in `OperatorMatcher`.
53    Operator = 10,
54    /// The end of the input.
55    EndOfInput = 11,
56    /// A word in one of the five keyword classes. `a` is the class mask.
57    ///
58    /// The grammar spells these as an ordered choice of two hundred literals, because a PEG has no
59    /// way to say "a word in this set". Upstream compiles that to two hundred `KeywordMatcher`
60    /// objects and tries them in turn. The words in a list are distinct and a `KeywordMatcher` is a
61    /// case insensitive text compare, so membership in the list is exactly a mask test on the class
62    /// the tokenizer already resolved, and the two are the same predicate.
63    KeywordClass = 12,
64}
65
66/// One node. Twenty four bytes, and everything the matcher needs to decide what to do with it.
67///
68/// The FIRST set and the nullable bit live in here rather than in two arrays beside it. They used
69/// to be parallel tables, on the theory that the filter could read eight bytes of `FIRST` and skip
70/// the node entirely, and that theory was wrong in the case that matters. A node that survives the
71/// filter is loaded immediately afterwards, and surviving is the common case: the filter is there
72/// to cut the thirty six alternatives of `Statement` down, and the one that matches still has to be
73/// walked. So the old layout paid three cache lines on every node it did not reject and saved two
74/// on every node it did, and the walk visits far more of the first kind.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct Node {
77    /// What this node can start with, as a set of token keys. A superset, always.
78    pub first: u64,
79    pub a: u32,
80    pub b: u32,
81    pub op: Op,
82    /// Per op, plus `NULLABLE`, which every op can carry.
83    pub flags: u8,
84}
85
86impl Node {
87    /// On an `Identifier` node: the keyword check is dropped, so any word matches.
88    ///
89    /// This is the whole of `ReservedIdentifierMatcher`. It is worth knowing that upstream applies
90    /// it to the rule named `ReservedKeyword`, so a grammar rule that reads
91    /// `ColLabel <- ReservedKeyword / ...` does not test for a reserved word, it accepts any word
92    /// at all. Reading the grammar text alone would get that backwards.
93    pub const RESERVED: u8 = 1 << 0;
94
95    /// This node can match without consuming a token, so its FIRST set says nothing about whether
96    /// it applies and the filter has to let it through.
97    pub const NULLABLE: u8 = 1 << 1;
98
99    /// Whether a node could possibly begin with this token.
100    ///
101    /// False means it cannot, and that is the only answer the caller may act on. True means try it.
102    /// A nullable node always answers true.
103    pub fn can_start(self, key: u64) -> bool {
104        self.flags & Self::NULLABLE != 0 || self.first & key != 0
105    }
106
107    /// The children of a sequence or a choice.
108    pub fn children(self) -> &'static [u32] {
109        &crate::generated::rules::CHILDREN[self.a as usize..(self.a + self.b) as usize]
110    }
111}
112
113/// One rule.
114#[derive(Debug, Clone, Copy)]
115pub struct Rule {
116    pub name: &'static str,
117    /// The node its body compiled to. The matcher does not read this. A `Rule` node carries the
118    /// same number in its `b`, so entering a rule is a field of a node already in a register rather
119    /// than an index into a second table. This is here for the name lookup and for the tests.
120    pub root: u32,
121    /// Whether upstream memoizes it. Twenty two rules do, and they are the ones deep in the
122    /// expression grammar that a failing alternative re-enters at the same position over and over.
123    pub memoized: bool,
124}
125
126/// What an identifier matcher was built to suggest.
127///
128/// Kept rather than reduced to the two answers it implies, because upstream derives both from it
129/// and keeping the derivation in one place is how the two stay comparable. `identifier_matcher.hpp`
130/// is the source.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132#[repr(u32)]
133pub enum Suggestion {
134    Variable = 0,
135    CatalogName = 1,
136    SchemaName = 2,
137    TableName = 3,
138    ColumnName = 4,
139    ScalarFunctionName = 5,
140    TableFunctionName = 6,
141    TypeName = 7,
142    PragmaName = 8,
143    SettingName = 9,
144    FileName = 10,
145}
146
147impl Suggestion {
148    /// Which keyword class may be used as a bare word here, on top of unreserved.
149    ///
150    /// Type name positions allow the type name class, both function name positions allow the
151    /// combined type and function class, and everything else allows the column name class. That
152    /// `TypeFuncKeyword <- TypeNameKeyword / FuncNameKeyword` is a rule in the grammar and also a
153    /// category in the matcher is not a coincidence, it is the same union written once.
154    /// `ParsedGrammarKeywordHelper`'s constructor holds a table of five rule names against five
155    /// keyword sets, and the entry for `typefunc_keyword_map` names that rule, which it then walks
156    /// through its references to collect the words. So the category is not a sixth list somebody
157    /// has to keep in step with the other five, it is what that one line of the grammar says.
158    pub const fn allowed_class(self) -> u8 {
159        use crate::generated::keywords::{COLUMN_NAME, FUNC_NAME, TYPE_NAME};
160        match self {
161            Suggestion::TypeName => TYPE_NAME,
162            Suggestion::ScalarFunctionName | Suggestion::TableFunctionName => TYPE_NAME | FUNC_NAME,
163            _ => COLUMN_NAME,
164        }
165    }
166
167    /// Whether a single quoted string is accepted where this name is expected.
168    ///
169    /// Two positions only. `FROM 'file.parquet'` is the reason, and `SELECT 'x' FROM t` staying a
170    /// string literal rather than becoming a column reference is the reason it is only two.
171    pub const fn supports_string_literal(self) -> bool {
172        matches!(self, Suggestion::TableName | Suggestion::FileName)
173    }
174}
175
176/// How many bits of a FIRST set go to token kinds before the keyword buckets start.
177pub const KIND_BITS: u32 = 6;
178/// The keyword buckets, being the rest of the 64.
179pub const BUCKETS: u32 = 64 - KIND_BITS;
180
181/// A bare or quoted name.
182pub const FIRST_IDENT: u64 = 1 << 0;
183/// A numeric literal.
184pub const FIRST_NUMBER: u64 = 1 << 1;
185/// A string literal.
186pub const FIRST_STRING: u64 = 1 << 2;
187/// An operator or a piece of punctuation.
188pub const FIRST_OPERATOR: u64 = 1 << 3;
189/// A `;`.
190pub const FIRST_TERMINATOR: u64 = 1 << 4;
191/// The end of the input.
192pub const FIRST_END: u64 = 1 << 5;
193/// Every keyword bucket at once, which is what an identifier matcher accepts, because which words
194/// it takes depends on the class and on the position and the filter is not the place to decide it.
195pub const FIRST_ANY_KEYWORD: u64 = !((1 << KIND_BITS) - 1);
196
197/// Which bucket a keyword falls in.
198pub const fn bucket(index: u32) -> u64 {
199    1 << (KIND_BITS + index % BUCKETS)
200}
201
202/// The one FIRST bit this token sets.
203///
204/// Exactly one bit, so the filter is one AND against the node's set, with no loop and no branch.
205pub fn token_key(token: Token) -> u64 {
206    match token.kind {
207        Kind::Identifier | Kind::QuotedIdentifier => FIRST_IDENT,
208        // A word the tokenizer resolved to the table. A word in no class arrives as `Identifier`,
209        // so this branch always has a real index and the fallback is unreachable in practice.
210        Kind::Keyword => {
211            if token.keyword == NOT_A_KEYWORD {
212                FIRST_IDENT
213            } else {
214                bucket(u32::from(token.keyword))
215            }
216        }
217        Kind::Number => FIRST_NUMBER,
218        Kind::String => FIRST_STRING,
219        Kind::Operator => FIRST_OPERATOR,
220        Kind::Terminator => FIRST_TERMINATOR,
221        Kind::EndOfInput => FIRST_END,
222    }
223}
224
225/// The rule with this name, if there is one.
226pub fn rule(name: &str) -> Option<&'static Rule> {
227    crate::generated::rules::RULES
228        .binary_search_by(|candidate| candidate.name.cmp(name))
229        .ok()
230        .map(|index| &crate::generated::rules::RULES[index])
231}
232
233/// The rules a rule chooses between, in the order the matcher tries them.
234///
235/// Written for `Statement`, whose thirty six alternatives are the denominator of the statement
236/// coverage number in `spec/sql/duckdb/01-what-compatible-means.md` section 1.1. Reading them off
237/// the table rather than writing them down is the whole point: the list moves when the vendored
238/// grammar moves, so a statement upstream adds is a statement the denominator grew by, and nobody
239/// has to remember to edit a constant.
240///
241/// Empty when there is no such rule, when its body is not a choice, and when any alternative of the
242/// choice is something other than a reference to a rule. The last one is not a fussy guard. A choice
243/// of a rule and a bare keyword has no name for that second alternative, so a count over what came
244/// back would be a count with a hole in it, and handing back nothing says that more clearly than
245/// handing back a list one short.
246pub fn alternatives(name: &str) -> Vec<&'static str> {
247    let Some(found) = rule(name) else {
248        return Vec::new();
249    };
250    let body = crate::generated::rules::NODES[found.root as usize];
251    if body.op != Op::Choice {
252        return Vec::new();
253    }
254    let mut names = Vec::with_capacity(body.b as usize);
255    for &child in body.children() {
256        let node = crate::generated::rules::NODES[child as usize];
257        if node.op != Op::Rule {
258            return Vec::new();
259        }
260        names.push(crate::generated::rules::RULES[node.a as usize].name);
261    }
262    names
263}
264
265#[cfg(test)]
266mod tests {
267    use super::{Node, Op, Suggestion, alternatives, bucket, rule, token_key};
268    use crate::generated::rules::{CHILDREN, NODES, PROGRAM, RULES, SYMBOLS};
269    use crate::token::{Flags, Kind, Token};
270
271    fn token(kind: Kind, keyword: u16) -> Token {
272        Token { kind, flags: Flags::default(), keyword, start: 0, end: 1 }
273    }
274
275    #[test]
276    fn a_node_is_twenty_four_bytes() {
277        assert_eq!(size_of::<Node>(), 24);
278    }
279
280    #[test]
281    fn the_tables_are_in_range() {
282        for node in &NODES {
283            match node.op {
284                Op::Sequence | Op::Choice => {
285                    assert!(node.b > 0, "an empty sequence or choice matches nothing");
286                    let end = (node.a + node.b) as usize;
287                    assert!(end <= CHILDREN.len());
288                    for child in &CHILDREN[node.a as usize..end] {
289                        assert!((*child as usize) < NODES.len());
290                    }
291                }
292                Op::Optional | Op::Repeat => assert!((node.a as usize) < NODES.len()),
293                Op::Rule => {
294                    assert!((node.a as usize) < RULES.len());
295                    // The body index the matcher actually jumps to, which is the one thing in the
296                    // table that is written twice and so is the one thing that can disagree.
297                    assert_eq!(node.b, RULES[node.a as usize].root);
298                }
299                Op::Symbol => assert!((node.a as usize) < SYMBOLS.len()),
300                Op::Keyword => {
301                    assert!((node.a as usize) < crate::generated::keywords::KEYWORDS.len())
302                }
303                _ => {}
304            }
305        }
306        for entry in &RULES {
307            assert!((entry.root as usize) < NODES.len());
308        }
309    }
310
311    #[test]
312    fn the_roots_are_there_and_named() {
313        assert_eq!(RULES[PROGRAM as usize].name, "Program");
314        assert!(rule("Program").is_some());
315        assert!(rule("SelectStatement").is_some());
316        // Overridden by a matcher, so its written body is dead, but the rule itself is very much
317        // reachable and has to be in the table.
318        assert!(rule("Identifier").is_some());
319        // Not reachable from Program once `Identifier` is overridden, so it should be gone.
320        assert!(rule("PlainIdentifier").is_none());
321    }
322
323    #[test]
324    fn a_repeat_never_wraps_something_that_matches_nothing() {
325        // `RepeatMatchProcess` upstream loops while the child succeeds and has no guard for a
326        // child that succeeds without consuming, so this is the difference between a table that
327        // terminates and one that does not.
328        for node in &NODES {
329            if node.op == Op::Repeat {
330                assert_eq!(NODES[node.a as usize].flags & Node::NULLABLE, 0);
331            }
332        }
333    }
334
335    #[test]
336    fn the_filter_only_ever_says_no_to_things_that_could_not_match() {
337        // `SELECT` starts a statement, so the root has to admit it.
338        let select = crate::generated::keywords::KEYWORDS
339            .binary_search_by(|(word, _)| (*word).cmp("select"))
340            .expect("select is a keyword");
341        let key = token_key(token(Kind::Keyword, select as u16));
342        assert!(NODES[RULES[PROGRAM as usize].root as usize].can_start(key));
343
344        // A number does not start a statement, and the root is nullable through
345        // `Statement? (';'+ / EndOfInput)`, so this is about the FIRST set and not about whether
346        // the parse eventually succeeds on an empty script.
347        let number = token_key(token(Kind::Number, u16::MAX));
348        let select_rule = rule("SelectStatement").expect("SelectStatement is a rule");
349        assert!(!NODES[select_rule.root as usize].can_start(number));
350    }
351
352    #[test]
353    fn a_token_maps_to_exactly_one_bit() {
354        for kind in [
355            Kind::Identifier,
356            Kind::QuotedIdentifier,
357            Kind::Number,
358            Kind::String,
359            Kind::Operator,
360            Kind::Terminator,
361            Kind::EndOfInput,
362        ] {
363            assert_eq!(token_key(token(kind, u16::MAX)).count_ones(), 1, "{kind:?}");
364        }
365        assert_eq!(token_key(token(Kind::Keyword, 3)).count_ones(), 1);
366        assert_eq!(bucket(0).count_ones(), 1);
367    }
368
369    #[test]
370    fn the_statement_rule_chooses_between_thirty_six_named_rules() {
371        let names = alternatives("Statement");
372        assert_eq!(names.len(), 36);
373        assert_eq!(names[0], "ExternalResourceStatement");
374        assert_eq!(names[3], "SelectStatement");
375        assert_eq!(names[35], "ExpressionStatement");
376        for name in &names {
377            assert!(rule(name).is_some(), "{name} is not a rule");
378        }
379    }
380
381    #[test]
382    fn the_order_the_alternatives_come_back_in_is_the_order_the_matcher_tries_them() {
383        let names = alternatives("Statement");
384        let body = NODES[rule("Statement").expect("Statement").root as usize];
385        let tried: Vec<&str> =
386            body.children().iter().map(|&at| RULES[NODES[at as usize].a as usize].name).collect();
387        assert_eq!(names, tried);
388    }
389
390    #[test]
391    fn a_rule_that_is_not_a_choice_has_no_alternatives_rather_than_one() {
392        let body = NODES[rule("Program").expect("Program").root as usize];
393        assert_ne!(body.op, Op::Choice);
394        assert!(alternatives("Program").is_empty());
395    }
396
397    #[test]
398    fn asking_for_the_alternatives_of_a_rule_that_is_not_there_is_not_an_error() {
399        assert!(alternatives("NoSuchRule").is_empty());
400    }
401
402    #[test]
403    fn the_two_derived_answers_match_the_matcher_header() {
404        use crate::generated::keywords::{COLUMN_NAME, FUNC_NAME, TYPE_NAME};
405        assert_eq!(Suggestion::TypeName.allowed_class(), TYPE_NAME);
406        assert_eq!(Suggestion::ScalarFunctionName.allowed_class(), TYPE_NAME | FUNC_NAME);
407        assert_eq!(Suggestion::TableFunctionName.allowed_class(), TYPE_NAME | FUNC_NAME);
408        assert_eq!(Suggestion::Variable.allowed_class(), COLUMN_NAME);
409        assert!(Suggestion::TableName.supports_string_literal());
410        assert!(Suggestion::FileName.supports_string_literal());
411        assert!(!Suggestion::ColumnName.supports_string_literal());
412    }
413}