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

            Line data    Source code
       1              : //! The names of a script: identifiers and keywords that may be used as names.
       2              : 
       3              : use crate::span::TextSpan;
       4              : 
       5              : /// The id of a registered name, unique within a script
       6              : pub type NameId = u32;
       7              : 
       8              : /// A registered name
       9              : #[derive(Clone, Debug, PartialEq, Eq)]
      10              : pub struct RegisteredName {
      11              :     /// The name id
      12              :     pub name_id: NameId,
      13              :     /// The text (lower-cased unless the name was quoted)
      14              :     pub text: String,
      15              :     /// The location of the first occurrence (empty for names without one)
      16              :     pub location: TextSpan,
      17              :     /// The number of occurrences
      18              :     pub occurrences: u32,
      19              : }
      20              : 
      21              : /// The names of a script, ids are assigned in order of first registration.
      22              : ///
      23              : /// An open-addressing table over the names (name id + 1 per slot, 0 = empty), so the texts are
      24              : /// stored once.
      25              : #[derive(Clone, Debug, Default)]
      26              : pub struct NameRegistry {
      27              :     /// The names indexed by name id
      28              :     names: Vec<RegisteredName>,
      29              :     /// The hash slots holding name id + 1, 0 for an empty slot, the length is a power of two
      30              :     table: Vec<u32>,
      31              : }
      32              : 
      33              : /// A fast hash for short names (FxHash over 8-byte words)
      34       126293 : fn hash(text: &[u8]) -> u64 {
      35              :     const K: u64 = 0x517c_c1b7_2722_0a95;
      36       126293 :     let mut h: u64 = text.len() as u64;
      37       126293 :     let mut chunks = text.chunks_exact(8);
      38       126293 :     for chunk in &mut chunks {
      39        79664 :         let word = u64::from_le_bytes(chunk.try_into().unwrap());
      40        79664 :         h = (h.rotate_left(5) ^ word).wrapping_mul(K);
      41        79664 :     }
      42       410810 :     for &b in chunks.remainder() {
      43       410794 :         h = (h.rotate_left(5) ^ b as u64).wrapping_mul(K);
      44       410794 :     }
      45       126293 :     h
      46       126293 : }
      47              : 
      48              : impl NameRegistry {
      49              :     /// The number of names
      50         3736 :     pub fn len(&self) -> usize {
      51         3736 :         self.names.len()
      52         3736 :     }
      53              : 
      54              :     /// Are there no names?
      55            0 :     pub fn is_empty(&self) -> bool {
      56            0 :         self.names.is_empty()
      57            0 :     }
      58              : 
      59              :     /// Get a name
      60         4488 :     pub fn get(&self, id: NameId) -> &RegisteredName {
      61         4488 :         &self.names[id as usize]
      62         4488 :     }
      63              : 
      64              :     /// All names in id order
      65         7577 :     pub fn iter(&self) -> impl Iterator<Item = &RegisteredName> {
      66         7577 :         self.names.iter()
      67         7577 :     }
      68              : 
      69              :     /// The slot of a name text: either the slot holding it or the empty slot where it belongs
      70       108213 :     fn slot(&self, text: &str) -> usize {
      71       108213 :         let mask = self.table.len() - 1;
      72       108213 :         let mut slot = hash(text.as_bytes()) as usize & mask;
      73              :         loop {
      74       135753 :             match self.table[slot] {
      75        55472 :                 0 => return slot,
      76        80281 :                 id if self.names[id as usize - 1].text == text => return slot,
      77        27540 :                 _ => slot = (slot + 1) & mask,
      78              :             }
      79              :         }
      80       108213 :     }
      81              : 
      82              :     /// Find a name by its text
      83         1007 :     pub fn find(&self, text: &str) -> Option<NameId> {
      84         1007 :         if self.table.is_empty() {
      85            0 :             return None;
      86         1007 :         }
      87         1007 :         match self.table[self.slot(text)] {
      88            1 :             0 => None,
      89         1006 :             id => Some(id - 1),
      90              :         }
      91         1007 :     }
      92              : 
      93         6664 :     fn grow(&mut self) {
      94         6664 :         let capacity = (self.table.len() * 2).max(64);
      95         6664 :         self.table = vec![0; capacity];
      96         6664 :         let mask = capacity - 1;
      97        20329 :         for (i, name) in self.names.iter().enumerate() {
      98        18080 :             let mut slot = hash(name.text.as_bytes()) as usize & mask;
      99        21567 :             while self.table[slot] != 0 {
     100         3487 :                 slot = (slot + 1) & mask;
     101         3487 :             }
     102        18080 :             self.table[slot] = i as u32 + 1;
     103              :         }
     104         6664 :     }
     105              : 
     106              :     /// Register a name. A known name counts another occurrence and keeps its first location.
     107       107206 :     pub fn register(&mut self, text: &str, location: TextSpan) -> NameId {
     108       107206 :         if (self.names.len() + 1) * 2 > self.table.len() {
     109         6664 :             self.grow();
     110       100600 :         }
     111       107206 :         let slot = self.slot(text);
     112       107206 :         match self.table[slot] {
     113              :             0 => {
     114        55471 :                 let id = self.names.len() as NameId;
     115        55471 :                 self.names.push(RegisteredName {
     116        55471 :                     name_id: id,
     117        55471 :                     text: text.to_string(),
     118        55471 :                     location,
     119        55471 :                     occurrences: 1,
     120        55471 :                 });
     121        55471 :                 self.table[slot] = id + 1;
     122        55471 :                 id
     123              :             }
     124        51735 :             id => {
     125        51735 :                 self.names[id as usize - 1].occurrences += 1;
     126        51735 :                 id - 1
     127              :             }
     128              :         }
     129       107206 :     }
     130              : }
     131              : 
     132              : #[cfg(test)]
     133              : mod tests {
     134              :     use super::*;
     135              : 
     136              :     #[test]
     137            1 :     fn registers_names_once() {
     138            1 :         let mut names = NameRegistry::default();
     139            1 :         assert_eq!(names.register("foo", TextSpan::new(0, 3)), 0);
     140            1 :         assert_eq!(names.register("bar", TextSpan::new(4, 3)), 1);
     141            1 :         assert_eq!(names.register("foo", TextSpan::new(8, 3)), 0);
     142            1 :         assert_eq!(names.len(), 2);
     143            1 :         assert_eq!(names.get(0).occurrences, 2);
     144            1 :         assert_eq!(names.get(0).location, TextSpan::new(0, 3));
     145            1 :         assert_eq!(names.find("bar"), Some(1));
     146            1 :         assert_eq!(names.find("baz"), None);
     147            1 :     }
     148              : 
     149              :     #[test]
     150            1 :     fn grows() {
     151            1 :         let mut names = NameRegistry::default();
     152         1000 :         for i in 0..1000 {
     153         1000 :             assert_eq!(names.register(&format!("name{i}"), TextSpan::default()), i);
     154              :         }
     155         1000 :         for i in 0..1000 {
     156         1000 :             assert_eq!(names.find(&format!("name{i}")), Some(i));
     157         1000 :             assert_eq!(names.register(&format!("name{i}"), TextSpan::default()), i);
     158              :         }
     159            1 :         assert_eq!(names.len(), 1000);
     160            1 :     }
     161              : }
        

Generated by: LCOV version 2.0-1