metrale_grammar/grammar/functor/
lookahead.rs1use crate::grammar::data::GrammarData;
19use crate::grammar::expr::GrammarExprType;
20
21pub struct LookaheadAssertionAnalyzer;
24
25#[derive(Debug, Clone, Default)]
28struct RuleLookaheadInfo {
29 is_triggered_by_dispatch: bool,
31 appears_as_last_in_other_rule: bool,
34 non_last_occurrence_count: i32,
37 suffix_after_first_occurrence: Vec<i32>,
39}
40
41impl LookaheadAssertionAnalyzer {
42 pub fn apply(grammar: GrammarData) -> GrammarData {
44 let root = grammar.root_rule();
45 if grammar.expr(root.body_expr_id).kind == GrammarExprType::TagDispatch {
46 return grammar;
47 }
48 let mut g = grammar;
49 let root_id = g.root_rule_id();
50 let infos = build_rule_lookahead_info(&g);
51 for i in 0..g.num_rules() {
52 if i == root_id {
53 continue;
54 }
55 if g.rule(i).lookahead_assertion_id != -1 {
56 g.rule_mut(i).is_exact_lookahead = can_use_derived_lookahead(&infos, i);
58 continue;
59 }
60 if can_use_derived_lookahead(&infos, i) {
61 let seq = infos[i as usize].suffix_after_first_occurrence.clone();
62 let seq_id = g.append_expr(GrammarExprType::Sequence, &seq);
63 g.rule_mut(i).lookahead_assertion_id = seq_id;
64 g.rule_mut(i).is_exact_lookahead = true;
65 }
66 }
67 g
68 }
69}
70
71fn can_use_derived_lookahead(infos: &[RuleLookaheadInfo], rule_id: i32) -> bool {
76 let info = &infos[rule_id as usize];
77 !info.is_triggered_by_dispatch
78 && !info.appears_as_last_in_other_rule
79 && info.non_last_occurrence_count == 1
80}
81
82fn build_rule_lookahead_info(grammar: &GrammarData) -> Vec<RuleLookaheadInfo> {
85 let mut infos: Vec<RuleLookaheadInfo> =
86 vec![RuleLookaheadInfo::default(); grammar.num_rules() as usize];
87 for i in 0..grammar.num_rules() {
88 let body_id = grammar.rule(i).body_expr_id;
89 let body = grammar.expr(body_id);
90 if body.kind == GrammarExprType::TagDispatch {
91 for (_, rid) in grammar.tag_dispatch(body_id).tag_rule_pairs {
92 infos[rid as usize].is_triggered_by_dispatch = true;
93 }
94 continue;
95 }
96 debug_assert_eq!(body.kind, GrammarExprType::Choices);
97 let choices: Vec<i32> = body.data.to_vec();
98 for seq_id in choices {
99 let seq = grammar.expr(seq_id);
100 if seq.kind != GrammarExprType::Sequence || seq.data.is_empty() {
101 continue;
102 }
103 let seq_data: Vec<i32> = seq.data.to_vec();
104 if let Some(&last) = seq_data.last() {
107 let last_e = grammar.expr(last);
108 if last_e.kind == GrammarExprType::RuleRef && i != last_e.data[0] {
109 infos[last_e.data[0] as usize].appears_as_last_in_other_rule = true;
110 }
111 }
112 for j in 0..seq_data.len().saturating_sub(1) {
116 let elem = grammar.expr(seq_data[j]);
117 if elem.kind != GrammarExprType::RuleRef {
118 continue;
119 }
120 let target = elem.data[0] as usize;
121 let info = &mut infos[target];
122 if info.non_last_occurrence_count == 0 {
123 info.suffix_after_first_occurrence = seq_data[j + 1..].to_vec();
124 }
125 info.non_last_occurrence_count += 1;
126 }
127 }
128 }
129 infos
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::grammar::functor::normalizer::GrammarNormalizer;
136 use crate::grammar::parse_ebnf_default;
137
138 fn normed(ebnf: &str) -> GrammarData {
139 GrammarNormalizer::apply(parse_ebnf_default(ebnf).expect("parse"))
140 }
141
142 #[test]
143 fn detects_suffix_lookahead() {
144 let g = normed("root ::= sub \"z\"\nsub ::= \"a\"\n");
146 let analyzed = LookaheadAssertionAnalyzer::apply(g);
147 let sub_id = (0..analyzed.num_rules())
148 .find(|&i| analyzed.rule(i).name == "sub")
149 .unwrap();
150 assert_ne!(analyzed.rule(sub_id).lookahead_assertion_id, -1);
151 assert!(analyzed.rule(sub_id).is_exact_lookahead);
152 }
153
154 #[test]
155 fn tail_reference_no_lookahead() {
156 let g = normed("root ::= \"z\" sub\nsub ::= \"a\"\n");
158 let analyzed = LookaheadAssertionAnalyzer::apply(g);
159 let sub_id = (0..analyzed.num_rules())
160 .find(|&i| analyzed.rule(i).name == "sub")
161 .unwrap();
162 assert_eq!(analyzed.rule(sub_id).lookahead_assertion_id, -1);
163 }
164
165 #[test]
166 fn tag_dispatch_root_passthrough() {
167 let g = normed("root ::= TagDispatch((\"a\", sub))\nsub ::= \"x\"\n");
168 let analyzed = LookaheadAssertionAnalyzer::apply(g.clone());
169 assert_eq!(analyzed.num_rules(), g.num_rules());
170 }
171
172 #[test]
173 fn multiple_references_no_lookahead() {
174 let g = normed("root ::= sub \"y\" | sub \"z\"\nsub ::= \"a\"\n");
175 let analyzed = LookaheadAssertionAnalyzer::apply(g);
176 let sub_id = (0..analyzed.num_rules())
177 .find(|&i| analyzed.rule(i).name == "sub")
178 .unwrap();
179 assert_eq!(analyzed.rule(sub_id).lookahead_assertion_id, -1);
180 }
181
182 #[test]
183 fn precompute_pass_matches_predicates() {
184 let g = normed("root ::= sub \"z\"\nsub ::= \"a\"\n");
186 let infos = build_rule_lookahead_info(&g);
187 let sub_id = (0..g.num_rules())
188 .find(|&i| g.rule(i).name == "sub")
189 .unwrap();
190 assert!(can_use_derived_lookahead(&infos, sub_id));
191 assert_eq!(infos[sub_id as usize].non_last_occurrence_count, 1);
192 assert!(!infos[sub_id as usize].appears_as_last_in_other_rule);
193 assert!(!infos[sub_id as usize].is_triggered_by_dispatch);
194 assert!(
195 !infos[sub_id as usize]
196 .suffix_after_first_occurrence
197 .is_empty()
198 );
199 }
200
201 #[test]
202 fn precompute_pass_counts_dispatch_trigger() {
203 let g = normed("root ::= TagDispatch((\"a\", sub))\nsub ::= \"x\"\n");
204 let infos = build_rule_lookahead_info(&g);
205 let sub_id = (0..g.num_rules())
206 .find(|&i| g.rule(i).name == "sub")
207 .unwrap();
208 assert!(infos[sub_id as usize].is_triggered_by_dispatch);
209 assert!(!can_use_derived_lookahead(&infos, sub_id));
210 }
211}