From 03592e828864ff8efd03c8aa3bb6ad852420fd43 Mon Sep 17 00:00:00 2001 From: Will Lillis Date: Fri, 21 Aug 2026 17:46:01 -0400 Subject: [PATCH] perf(generate): build `Seq` and `Repeat` child ranges without temporaries `try_seq` reserves a `Seq`'s child range up front and fills slots as recursion produces each member. The `Repeat` arm builds `Choice(repeat(x), blank)` directly, skipping the `choice` helper. Slightly faster and less memory used. --- crates/generate/src/parse_grammar.rs | 15 +++++++-------- crates/generate/src/rules.rs | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/crates/generate/src/parse_grammar.rs b/crates/generate/src/parse_grammar.rs index ed163d51c..c89657d70 100644 --- a/crates/generate/src/parse_grammar.rs +++ b/crates/generate/src/parse_grammar.rs @@ -402,13 +402,9 @@ impl RulePool { .collect::>>()?; Ok(self.choice(&members)) } - RuleJSON::SEQ { members } => { - let members = members - .into_iter() - .map(|m| self.parse_rule(m, is_token, diagnostics)) - .collect::>>()?; - Ok(self.seq(&members)) - } + RuleJSON::SEQ { members } => self.try_seq(members.into_iter(), |pool, m| { + pool.parse_rule(m, is_token, diagnostics) + }), RuleJSON::FIELD { name, content } => { let content = self.parse_rule(*content, is_token, diagnostics)?; let name = self.intern(&name); @@ -418,7 +414,10 @@ impl RulePool { let content = self.parse_rule(*content, is_token, diagnostics)?; let repeat = self.repeat(content); let blank = self.blank(); - Ok(self.choice(&[repeat, blank])) + // Neither element is a `Choice` or equal to one another, so we + // can skip `self.choice`'s flattening loop. + let range = self.push_children(&[repeat, blank]); + Ok(self.push_node(Rule::Choice(range))) } RuleJSON::REPEAT1 { content } => { let content = self.parse_rule(*content, is_token, diagnostics)?; diff --git a/crates/generate/src/rules.rs b/crates/generate/src/rules.rs index a83e8edf3..0ca0d8527 100644 --- a/crates/generate/src/rules.rs +++ b/crates/generate/src/rules.rs @@ -304,13 +304,34 @@ impl RulePool { self.push_node(Rule::Eof) } + #[cfg_attr(not(test), expect(dead_code))] pub fn seq(&mut self, ids: &[RuleId]) -> RuleId { let range = self.push_children(ids); self.push_node(Rule::Seq(range)) } + pub fn try_seq( + &mut self, + items: impl ExactSizeIterator, + mut f: impl FnMut(&mut Self, T) -> Result, + ) -> Result { + let len = items.len(); + let start = self.children.len(); + self.children.resize(start + len, RuleId(0)); + for (i, item) in items.enumerate() { + let id = f(self, item)?; + self.children[start + i] = id; + } + + let range = RuleIdRange { + start: start as u32, + len: len as u32, + }; + Ok(self.push_node(Rule::Seq(range))) + } + /// Flatten nested choices and de-dup structurally, keeping a `Choice` node - /// event for a single element + /// even for a single element pub fn choice(&mut self, ids: &[RuleId]) -> RuleId { let mut elements: Vec = Vec::with_capacity(ids.len()); let mut stack: Vec = Vec::with_capacity(ids.len());