use crate::token::{Kind, NOT_A_KEYWORD, Token};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Op {
Keyword = 0,
Symbol = 1,
Rule = 2,
Sequence = 3,
Choice = 4,
Optional = 5,
Repeat = 6,
Identifier = 7,
Number = 8,
String = 9,
Operator = 10,
EndOfInput = 11,
KeywordClass = 12,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Node {
pub op: Op,
pub flags: u8,
pub a: u32,
pub b: u32,
}
impl Node {
pub const RESERVED: u8 = 1 << 0;
pub fn children(self) -> &'static [u32] {
&crate::generated::rules::CHILDREN[self.a as usize..(self.a + self.b) as usize]
}
}
#[derive(Debug, Clone, Copy)]
pub struct Rule {
pub name: &'static str,
pub root: u32,
pub memoized: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Suggestion {
Variable = 0,
CatalogName = 1,
SchemaName = 2,
TableName = 3,
ColumnName = 4,
ScalarFunctionName = 5,
TableFunctionName = 6,
TypeName = 7,
PragmaName = 8,
SettingName = 9,
FileName = 10,
}
impl Suggestion {
pub const fn allowed_class(self) -> u8 {
use crate::generated::keywords::{COLUMN_NAME, FUNC_NAME, TYPE_NAME};
match self {
Suggestion::TypeName => TYPE_NAME,
Suggestion::ScalarFunctionName | Suggestion::TableFunctionName => TYPE_NAME | FUNC_NAME,
_ => COLUMN_NAME,
}
}
pub const fn supports_string_literal(self) -> bool {
matches!(self, Suggestion::TableName | Suggestion::FileName)
}
}
pub const KIND_BITS: u32 = 6;
pub const BUCKETS: u32 = 64 - KIND_BITS;
pub const FIRST_IDENT: u64 = 1 << 0;
pub const FIRST_NUMBER: u64 = 1 << 1;
pub const FIRST_STRING: u64 = 1 << 2;
pub const FIRST_OPERATOR: u64 = 1 << 3;
pub const FIRST_TERMINATOR: u64 = 1 << 4;
pub const FIRST_END: u64 = 1 << 5;
pub const FIRST_ANY_KEYWORD: u64 = !((1 << KIND_BITS) - 1);
pub const fn bucket(index: u32) -> u64 {
1 << (KIND_BITS + index % BUCKETS)
}
pub fn token_key(token: Token) -> u64 {
match token.kind {
Kind::Identifier | Kind::QuotedIdentifier => FIRST_IDENT,
Kind::Keyword => {
if token.keyword == NOT_A_KEYWORD {
FIRST_IDENT
} else {
bucket(u32::from(token.keyword))
}
}
Kind::Number => FIRST_NUMBER,
Kind::String => FIRST_STRING,
Kind::Operator => FIRST_OPERATOR,
Kind::Terminator => FIRST_TERMINATOR,
Kind::EndOfInput => FIRST_END,
}
}
pub fn can_start(node: u32, key: u64) -> bool {
crate::generated::rules::FIRST[node as usize] & key != 0
}
pub fn rule(name: &str) -> Option<&'static Rule> {
crate::generated::rules::RULES
.binary_search_by(|candidate| candidate.name.cmp(name))
.ok()
.map(|index| &crate::generated::rules::RULES[index])
}
#[cfg(test)]
mod tests {
use super::{Node, Op, Suggestion, bucket, can_start, rule, token_key};
use crate::generated::rules::{CHILDREN, FIRST, NODES, NULLABLE, PROGRAM, RULES, SYMBOLS};
use crate::token::{Flags, Kind, Token};
fn token(kind: Kind, keyword: u16) -> Token {
Token { kind, flags: Flags::default(), keyword, start: 0, end: 1 }
}
#[test]
fn a_node_is_twelve_bytes() {
assert_eq!(size_of::<Node>(), 12);
}
#[test]
fn the_tables_are_parallel_and_in_range() {
assert_eq!(NODES.len(), FIRST.len());
assert_eq!(NODES.len(), NULLABLE.len());
for node in &NODES {
match node.op {
Op::Sequence | Op::Choice => {
assert!(node.b > 0, "an empty sequence or choice matches nothing");
let end = (node.a + node.b) as usize;
assert!(end <= CHILDREN.len());
for child in &CHILDREN[node.a as usize..end] {
assert!((*child as usize) < NODES.len());
}
}
Op::Optional | Op::Repeat => assert!((node.a as usize) < NODES.len()),
Op::Rule => assert!((node.a as usize) < RULES.len()),
Op::Symbol => assert!((node.a as usize) < SYMBOLS.len()),
Op::Keyword => {
assert!((node.a as usize) < crate::generated::keywords::KEYWORDS.len())
}
_ => {}
}
}
for entry in &RULES {
assert!((entry.root as usize) < NODES.len());
}
}
#[test]
fn the_roots_are_there_and_named() {
assert_eq!(RULES[PROGRAM as usize].name, "Program");
assert!(rule("Program").is_some());
assert!(rule("SelectStatement").is_some());
assert!(rule("Identifier").is_some());
assert!(rule("PlainIdentifier").is_none());
}
#[test]
fn a_repeat_never_wraps_something_that_matches_nothing() {
for node in &NODES {
if node.op == Op::Repeat {
assert!(!NULLABLE[node.a as usize]);
}
}
}
#[test]
fn the_filter_only_ever_says_no_to_things_that_could_not_match() {
let select = crate::generated::keywords::KEYWORDS
.binary_search_by(|(word, _)| (*word).cmp("select"))
.expect("select is a keyword");
let key = token_key(token(Kind::Keyword, select as u16));
assert!(can_start(RULES[PROGRAM as usize].root, key));
let number = token_key(token(Kind::Number, u16::MAX));
let select_rule = rule("SelectStatement").expect("SelectStatement is a rule");
assert!(!can_start(select_rule.root, number));
}
#[test]
fn a_token_maps_to_exactly_one_bit() {
for kind in [
Kind::Identifier,
Kind::QuotedIdentifier,
Kind::Number,
Kind::String,
Kind::Operator,
Kind::Terminator,
Kind::EndOfInput,
] {
assert_eq!(token_key(token(kind, u16::MAX)).count_ones(), 1, "{kind:?}");
}
assert_eq!(token_key(token(Kind::Keyword, 3)).count_ones(), 1);
assert_eq!(bucket(0).count_ones(), 1);
}
#[test]
fn the_two_derived_answers_match_the_matcher_header() {
use crate::generated::keywords::{COLUMN_NAME, FUNC_NAME, TYPE_NAME};
assert_eq!(Suggestion::TypeName.allowed_class(), TYPE_NAME);
assert_eq!(Suggestion::ScalarFunctionName.allowed_class(), TYPE_NAME | FUNC_NAME);
assert_eq!(Suggestion::TableFunctionName.allowed_class(), TYPE_NAME | FUNC_NAME);
assert_eq!(Suggestion::Variable.allowed_class(), COLUMN_NAME);
assert!(Suggestion::TableName.supports_string_literal());
assert!(Suggestion::FileName.supports_string_literal());
assert!(!Suggestion::ColumnName.supports_string_literal());
}
}