From fad53463992f760925e109b2789dcf98db2ba8dd Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 1 May 2026 21:59:39 +0000 Subject: [PATCH] Yeast: Index rules by root node kind for fast lookup Rules are now indexed by their root query kind into a BTreeMap. When processing a node, only rules matching that node's kind (plus wildcard rules) are tried, instead of scanning all rules linearly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- shared/yeast/src/lib.rs | 45 +++++++++++++++++++++++++++++++++------ shared/yeast/src/query.rs | 13 +++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 04b5e6ad327..08fc366e7e7 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -558,11 +558,42 @@ impl Rule { const MAX_REWRITE_DEPTH: usize = 100; -fn apply_rules(rules: &Vec, ast: &mut Ast, id: Id, fresh: &tree_builder::FreshScope) -> Result, String> { - apply_rules_inner(rules, ast, id, fresh, 0) +/// Index of rules by their root query kind for fast lookup. +struct RuleIndex<'a> { + /// Rules indexed by root node kind name. + by_kind: BTreeMap<&'static str, Vec<&'a Rule>>, + /// Rules with wildcard queries (Any) that apply to all nodes. + wildcard: Vec<&'a Rule>, } -fn apply_rules_inner(rules: &Vec, ast: &mut Ast, id: Id, fresh: &tree_builder::FreshScope, rewrite_depth: usize) -> Result, String> { +impl<'a> RuleIndex<'a> { + fn new(rules: &'a [Rule]) -> Self { + let mut by_kind: BTreeMap<&'static str, Vec<&'a Rule>> = BTreeMap::new(); + let mut wildcard = Vec::new(); + for rule in rules { + match rule.query.root_kind() { + Some(kind) => by_kind.entry(kind).or_default().push(rule), + None => wildcard.push(rule), + } + } + Self { by_kind, wildcard } + } + + fn rules_for_kind(&self, kind: &str) -> impl Iterator { + self.by_kind + .get(kind) + .into_iter() + .flat_map(|v| v.iter()) + .chain(self.wildcard.iter()) + } +} + +fn apply_rules(rules: &[Rule], ast: &mut Ast, id: Id, fresh: &tree_builder::FreshScope) -> Result, String> { + let index = RuleIndex::new(rules); + apply_rules_inner(&index, ast, id, fresh, 0) +} + +fn apply_rules_inner(index: &RuleIndex, ast: &mut Ast, id: Id, fresh: &tree_builder::FreshScope, rewrite_depth: usize) -> Result, String> { if rewrite_depth > MAX_REWRITE_DEPTH { return Err(format!( "Desugaring exceeded maximum rewrite depth ({MAX_REWRITE_DEPTH}). \ @@ -570,12 +601,12 @@ fn apply_rules_inner(rules: &Vec, ast: &mut Ast, id: Id, fresh: &tree_buil )); } - for rule in rules { + let node_kind = ast.get_node(id).map(|n| n.kind()).unwrap_or(""); + for rule in index.rules_for_kind(node_kind) { if let Some(result_node) = rule.try_rule(ast, id, fresh)? { let mut results = Vec::new(); for node in result_node { - // Increment depth only when re-processing rule output - results.extend(apply_rules_inner(rules, ast, node, fresh, rewrite_depth + 1)?); + results.extend(apply_rules_inner(index, ast, node, fresh, rewrite_depth + 1)?); } return Ok(results); } @@ -589,7 +620,7 @@ fn apply_rules_inner(rules: &Vec, ast: &mut Ast, id: Id, fresh: &tree_buil let old = std::mem::take(vec); let mut new = Vec::new(); for child_id in old { - new.extend(apply_rules_inner(rules, ast, child_id, fresh, rewrite_depth)?); + new.extend(apply_rules_inner(index, ast, child_id, fresh, rewrite_depth)?); } *vec = new; } diff --git a/shared/yeast/src/query.rs b/shared/yeast/src/query.rs index 87535940809..b09b7d9b89b 100644 --- a/shared/yeast/src/query.rs +++ b/shared/yeast/src/query.rs @@ -17,6 +17,19 @@ pub enum QueryNode { }, } +impl QueryNode { + /// Returns the root node kind this query matches, if it's specific. + /// Returns None for wildcards (Any) and captures wrapping wildcards. + pub fn root_kind(&self) -> Option<&'static str> { + match self { + QueryNode::Node { kind, .. } => Some(kind), + QueryNode::UnnamedNode { kind } => Some(kind), + QueryNode::Capture { node, .. } => node.root_kind(), + QueryNode::Any() => None, + } + } +} + #[derive(Debug, Clone)] pub enum QueryListElem { Repeated { children: Vec, rep: Rep },