LCOV - code coverage report
Current view: top level - dashql-parser/src - keywords.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 96.8 % 63 61
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : //! The keywords of the grammar, generated from `grammar/lists/*.list` (shared with the bison grammar).
       2              : 
       3              : use crate::{symbol_kind, SymbolKind};
       4              : 
       5              : #[rustfmt::skip]
       6              : mod table;
       7              : 
       8              : pub use table::{KEYWORDS, MAX_KEYWORD_LENGTH};
       9              : use table::KEYWORD_TABLE;
      10              : 
      11              : /// The category of a keyword
      12              : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
      13              : pub enum KeywordCategory {
      14              :     SqlColumnName,
      15              :     SqlReserved,
      16              :     SqlTypeFunc,
      17              :     SqlUnreserved,
      18              : }
      19              : 
      20              : /// A keyword
      21              : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
      22              : pub struct Keyword {
      23              :     /// The lower-case keyword text
      24              :     pub name: &'static str,
      25              :     /// The terminal symbol of the keyword
      26              :     pub symbol: SymbolKind,
      27              :     /// The category
      28              :     pub category: KeywordCategory,
      29              : }
      30              : 
      31              : impl Keyword {
      32              :     /// Find a keyword by its lower-case text
      33       191568 :     pub fn find(text: &[u8]) -> Option<&'static Keyword> {
      34       191568 :         if text.len() > MAX_KEYWORD_LENGTH {
      35         5077 :             return None;
      36       186491 :         }
      37       186491 :         let mask = KEYWORD_TABLE.len() - 1;
      38       186491 :         let mut slot = fnv1a(text) as usize & mask;
      39              :         loop {
      40       220999 :             match KEYWORD_TABLE[slot] {
      41        94466 :                 0 => return None,
      42       126533 :                 i => {
      43       126533 :                     let keyword = &KEYWORDS[i as usize - 1];
      44       126533 :                     if keyword.name.as_bytes() == text {
      45        92025 :                         return Some(keyword);
      46        34508 :                     }
      47              :                 }
      48              :             }
      49        34508 :             slot = (slot + 1) & mask;
      50              :         }
      51       191568 :     }
      52              : 
      53              :     /// The text of a keyword or operator symbol
      54      1138303 :     pub fn symbol_text(symbol: SymbolKind) -> Option<&'static str> {
      55      1138303 :         if let Some(name) = Self::name_of(symbol) {
      56      1092529 :             return Some(name);
      57        45774 :         }
      58        45774 :         match symbol {
      59            0 :             symbol_kind::S_EQUALS_GREATER => Some("=>"),
      60          574 :             symbol_kind::S_LESS_EQUALS => Some("<="),
      61          574 :             symbol_kind::S_GREATER_EQUALS => Some(">="),
      62          574 :             symbol_kind::S_NOT_EQUALS => Some("!="),
      63            0 :             symbol_kind::S_COLON_EQUALS => Some(":="),
      64        44052 :             _ => None,
      65              :         }
      66      1138303 :     }
      67              : 
      68              :     /// The keyword text of a terminal symbol, if the symbol is a keyword
      69      1231745 :     pub fn name_of(symbol: SymbolKind) -> Option<&'static str> {
      70      1231745 :         keyword_names().get(symbol as usize).copied().flatten()
      71      1231745 :     }
      72              : }
      73              : 
      74              : /// FNV-1a, the hash of the keyword lookup table (see //grammar/rust:gen_keywords.py)
      75       186491 : fn fnv1a(text: &[u8]) -> u32 {
      76       186491 :     let mut h: u32 = 0x811C_9DC5;
      77      1111854 :     for &b in text {
      78      1111854 :         h ^= b as u32;
      79      1111854 :         h = h.wrapping_mul(0x0100_0193);
      80      1111854 :     }
      81       186491 :     h
      82       186491 : }
      83              : 
      84              : /// Keyword texts indexed by symbol kind
      85      1231745 : fn keyword_names() -> &'static [Option<&'static str>] {
      86              :     static NAMES: std::sync::OnceLock<Vec<Option<&'static str>>> = std::sync::OnceLock::new();
      87      1231745 :     NAMES.get_or_init(|| {
      88           12 :         let mut names = vec![None; crate::tables::YYNTOKENS];
      89         5508 :         for k in KEYWORDS {
      90         5508 :             names[k.symbol as usize] = Some(k.name);
      91         5508 :         }
      92           12 :         names
      93           12 :     })
      94      1231745 : }
      95              : 
      96              : #[cfg(test)]
      97              : mod tests {
      98              :     use super::*;
      99              : 
     100              :     #[test]
     101            1 :     fn finds_keywords() {
     102            1 :         assert_eq!(KEYWORDS.len(), 459);
     103            1 :         let select = Keyword::find(b"select").unwrap();
     104            1 :         assert_eq!(select.symbol, symbol_kind::S_SELECT);
     105            1 :         assert_eq!(select.category, KeywordCategory::SqlReserved);
     106            1 :         assert_eq!(Keyword::find(b"SELECT"), None);
     107            1 :         assert_eq!(Keyword::find(b"selec"), None);
     108            1 :         assert_eq!(Keyword::find(b"a_name_that_is_too_long_to_be_a_keyword"), None);
     109          458 :         assert!(KEYWORDS.windows(2).all(|w| w[0].name < w[1].name));
     110          459 :         assert!(KEYWORDS.iter().all(|k| Keyword::find(k.name.as_bytes()) == Some(k)));
     111            1 :     }
     112              : 
     113              :     #[test]
     114            1 :     fn names_symbols() {
     115            1 :         assert_eq!(Keyword::name_of(symbol_kind::S_SELECT), Some("select"));
     116            1 :         assert_eq!(Keyword::name_of(symbol_kind::S_IDENT), None);
     117            1 :         assert_eq!(Keyword::name_of(symbol_kind::S_NOT_LA), None);
     118            1 :     }
     119              : }
        

Generated by: LCOV version 2.0-1