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

            Line data    Source code
       1              : //! Attribute lookups over the children of an AST object.
       2              : //!
       3              : //! [`attrs!`](crate::attrs) fixes the keys at compile time, rejects duplicate keys during constant
       4              : //! evaluation, and then scans the children against the constant key array. LLVM vectorizes that
       5              : //! comparison, which measured faster than a static perfect hash table and than a direct-index table
       6              : //! for every object size of the grammar.
       7              : //!
       8              : //! Duplicate keys resolve differently by key count (see docs/design/core_quirks.md):
       9              : //! - with a single key, the first child with that key is returned,
      10              : //! - with several keys, the last child with each key is returned.
      11              : 
      12              : pub use dashql_buffers::parser::AttributeKey;
      13              : use dashql_buffers::parser::Node;
      14              : 
      15              : /// Copy constant keys into an array, rejecting duplicates (evaluated at compile time)
      16            0 : pub const fn key_array<const N: usize>(keys: &[AttributeKey]) -> [AttributeKey; N] {
      17            0 :     assert!(keys.len() == N);
      18            0 :     let mut out = [AttributeKey::NONE; N];
      19            0 :     let mut i = 0;
      20            0 :     while i < N {
      21            0 :         let mut j = i + 1;
      22            0 :         while j < N {
      23            0 :             assert!(keys[i].0 != keys[j].0, "duplicate attribute key");
      24            0 :             j += 1;
      25              :         }
      26            0 :         out[i] = keys[i];
      27            0 :         i += 1;
      28              :     }
      29            0 :     out
      30            0 : }
      31              : 
      32              : /// Find the last child with each key
      33              : #[inline(always)]
      34       155744 : pub fn lookup_last<'a, const N: usize>(children: &'a [Node], keys: &[AttributeKey; N]) -> [Option<&'a Node>; N] {
      35       155744 :     let mut out = [None; N];
      36       276690 :     for child in children {
      37       276690 :         let key = child.attribute_key();
      38      1520809 :         for (slot, k) in out.iter_mut().zip(keys.iter()) {
      39      1520809 :             if key == *k {
      40       262467 :                 *slot = Some(child);
      41      1258342 :             }
      42              :         }
      43              :     }
      44       155744 :     out
      45       155744 : }
      46              : 
      47              : /// Find the first child with a key
      48              : #[inline(always)]
      49        69019 : pub fn lookup_first(children: &[Node], key: AttributeKey) -> [Option<&Node>; 1] {
      50        76975 :     [children.iter().find(|c| c.attribute_key() == key)]
      51        69019 : }
      52              : 
      53              : /// Look up attributes in the children of an object: `attrs!(children, [KEY_A, KEY_B])` returns
      54              : /// `[Option<&Node>; 2]`.
      55              : ///
      56              : /// A single key returns the first child with that key, several keys the last child with each key.
      57              : /// The keys must be constant expressions.
      58              : #[macro_export]
      59              : macro_rules! attrs {
      60              :     ($children:expr, [$key:expr $(,)?] $(,)?) => {
      61              :         $crate::attributes::lookup_first($children, $key)
      62              :     };
      63              :     ($children:expr, [$($key:expr),+ $(,)?] $(,)?) => {{
      64              :         const KEY_LIST: &[$crate::attributes::AttributeKey] = &[$($key),+];
      65              :         const KEYS: [$crate::attributes::AttributeKey; KEY_LIST.len()] = $crate::attributes::key_array(KEY_LIST);
      66              :         $crate::attributes::lookup_last($children, &KEYS)
      67              :     }};
      68              : }
      69              : 
      70              : #[cfg(test)]
      71              : mod tests {
      72              :     use dashql_buffers::parser::{AttributeKey, Node, NodeType, SymbolSpan};
      73              : 
      74            6 :     fn node(key: AttributeKey, value: u32) -> Node {
      75            6 :         Node::new(&SymbolSpan::new(0, 0), NodeType::NONE, key, 0, value, 0)
      76            6 :     }
      77              : 
      78              :     #[test]
      79            1 :     fn single_key_finds_first() {
      80            1 :         let children = [node(AttributeKey::SQL_SELECT_FROM, 1), node(AttributeKey::SQL_SELECT_FROM, 2)];
      81            1 :         let [from] = attrs!(&children, [AttributeKey::SQL_SELECT_FROM]);
      82            1 :         assert_eq!(from.unwrap().children_begin_or_value(), 1);
      83            1 :     }
      84              : 
      85              :     #[test]
      86            1 :     fn several_keys_find_last() {
      87            1 :         let children = [
      88            1 :             node(AttributeKey::SQL_SELECT_FROM, 1),
      89            1 :             node(AttributeKey::SQL_SELECT_WHERE, 2),
      90            1 :             node(AttributeKey::SQL_SELECT_FROM, 3),
      91            1 :             node(AttributeKey::NONE, 4),
      92            1 :         ];
      93            1 :         let [from, where_, limit] = attrs!(
      94            1 :             &children,
      95              :             [AttributeKey::SQL_SELECT_FROM, AttributeKey::SQL_SELECT_WHERE, AttributeKey::SQL_SELECT_LIMIT]
      96              :         );
      97            1 :         assert_eq!(from.unwrap().children_begin_or_value(), 3);
      98            1 :         assert_eq!(where_.unwrap().children_begin_or_value(), 2);
      99            1 :         assert!(limit.is_none());
     100            1 :     }
     101              : }
        

Generated by: LCOV version 2.0-1