rudb_parse/token.rs
1//! What the tokenizer produces and what the matcher consumes.
2//!
3//! A token is twelve bytes and holds no string. The text stays in the query, the token holds a
4//! span into it, and nothing is decoded here: a string keeps its quotes and its escapes, a number
5//! keeps its underscores, an identifier keeps its case. Decoding is the transformer's job, and
6//! leaving it there is what keeps the whole token vector in cache for a query of any sane size.
7//!
8//! `spec/20-the-grammar.md` section 7 is the behaviour this has to match and why matching it is
9//! the largest single compatibility risk in the front end.
10
11use rudb_common::Span;
12
13/// A word is not a keyword.
14///
15/// `u16::MAX` rather than an `Option<u16>`, which would be four bytes and would put a branch on
16/// the path that reads the keyword out. There are 514 keywords and there is no prospect of 65,535.
17pub const NOT_A_KEYWORD: u16 = u16::MAX;
18
19/// What a token is.
20///
21/// DuckDB has one `IDENTIFIER` type covering both a bare word and a quoted one, and recovers the
22/// difference by looking at the first byte where it matters. We split them, because the grammar
23/// has a `QuotedIdentifier` rule and asking the token is cheaper and harder to get wrong than
24/// asking the source text. The two are otherwise treated alike everywhere upstream treats them
25/// alike, which is nearly everywhere.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27#[repr(u8)]
28pub enum Kind {
29 /// A bare word that is in no keyword class. `foo`, and also `ascending`, which is spelled by
30 /// a rule and is in no class, so it is a perfectly good column name.
31 Identifier,
32 /// A word in double quotes. Still an identifier, never a keyword, and case preserving in
33 /// exactly the same way a bare one is.
34 QuotedIdentifier,
35 /// A bare word that is in at least one keyword class. `keyword` on this token says which.
36 Keyword,
37 /// A numeric literal, with its underscores, its exponent and its decimal point as written.
38 Number,
39 /// A string literal, with its quotes. Single quoted, dollar quoted, or one of the four
40 /// prefixed forms, all of which arrive here as one token.
41 String,
42 /// Punctuation or an operator run. `(`, `,`, `::`, `!~~*` and `+` are all this.
43 Operator,
44 /// A `;`. Its own kind because `Program <- TopLevelStatement*` consumes it as one, and
45 /// because a statement boundary that the tokenizer decided would be a statement boundary the
46 /// grammar cannot change.
47 Terminator,
48 /// The sentinel at the end. Always present, always last, always exactly one.
49 EndOfInput,
50}
51
52impl Kind {
53 /// Whether this is a name, either spelling.
54 pub const fn is_identifier(self) -> bool {
55 matches!(self, Kind::Identifier | Kind::QuotedIdentifier)
56 }
57}
58
59/// Facts about the gap before a token, which the grammar cannot see and two rules need.
60///
61/// A bitfield rather than three `bool`s so that a token stays twelve bytes.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63pub struct Flags(u8);
64
65impl Flags {
66 /// There was a line break between the end of the previous token and the start of this one.
67 pub const NEWLINE: Flags = Flags(1 << 0);
68 /// A block comment ended in the gap before this token.
69 pub const BLOCK_COMMENT: Flags = Flags(1 << 1);
70 /// The token ran to the end of the input without its closing delimiter. Only a dollar quoted
71 /// string reaches the matcher this way; the other unterminated forms are errors.
72 pub const UNTERMINATED: Flags = Flags(1 << 2);
73
74 /// Whether every flag in `other` is set here.
75 pub const fn has(self, other: Flags) -> bool {
76 self.0 & other.0 == other.0
77 }
78
79 /// This set with `other` added.
80 pub const fn with(self, other: Flags) -> Flags {
81 Flags(self.0 | other.0)
82 }
83}
84
85/// One token. Twelve bytes, no allocation, no owned text.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct Token {
88 /// What it is.
89 pub kind: Kind,
90 /// What was in the gap before it.
91 pub flags: Flags,
92 /// The index into `generated::keywords::KEYWORDS`, or `NOT_A_KEYWORD`.
93 ///
94 /// Resolved once here rather than at every position the matcher considers the token, because
95 /// the matcher considers most tokens many times and the fold plus binary search is the
96 /// expensive part of asking.
97 pub keyword: u16,
98 /// Where it starts, as a byte offset into the query.
99 pub start: u32,
100 /// One past where it ends.
101 pub end: u32,
102}
103
104impl Token {
105 /// The span this token covers.
106 pub const fn span(self) -> Span {
107 Span::new(self.start, self.end)
108 }
109
110 /// The text of this token, given the query it came from.
111 ///
112 /// Both bounds came from a scan over the same string and always land on a character boundary,
113 /// so this cannot panic on well formed input. It is written as a slice rather than a checked
114 /// `get` because a token whose bounds are not on a boundary is a bug in the tokenizer and
115 /// silently returning nothing would hide it.
116 pub fn text(self, query: &str) -> &str {
117 &query[self.start as usize..self.end as usize]
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::{Flags, Kind, Token};
124
125 #[test]
126 fn a_token_is_twelve_bytes() {
127 // Not a style preference. A hundred token query is one cache line per ten tokens, and the
128 // matcher walks the vector many times over. If this ever fails, something grew a field
129 // that should have been computed instead.
130 assert_eq!(size_of::<Token>(), 12);
131 }
132
133 #[test]
134 fn the_flags_combine_and_read_back() {
135 let flags = Flags::default().with(Flags::NEWLINE).with(Flags::BLOCK_COMMENT);
136 assert!(flags.has(Flags::NEWLINE));
137 assert!(flags.has(Flags::BLOCK_COMMENT));
138 assert!(!flags.has(Flags::UNTERMINATED));
139 assert!(!Flags::default().has(Flags::NEWLINE));
140 }
141
142 #[test]
143 fn both_spellings_of_a_name_are_identifiers() {
144 assert!(Kind::Identifier.is_identifier());
145 assert!(Kind::QuotedIdentifier.is_identifier());
146 assert!(!Kind::Keyword.is_identifier());
147 assert!(!Kind::String.is_identifier());
148 }
149}