meta_language/grammar/emit/
bnf.rs1use crate::grammar::{CharClassItem, Grammar, GrammarExpr, GrammarFormat};
2
3use super::{
4 expanded_chars, finish_lines, ordered_rules, render_rule_line, unsupported_error, EmitReport,
5 GrammarEmitError, HelperRules, BNF_RULE_TEMPLATE,
6};
7
8const MAX_BNF_EXPANSION: u32 = 256;
9
10pub fn emit_bnf(grammar: &Grammar) -> Result<(String, EmitReport), GrammarEmitError> {
22 let mut emitter = BnfEmitter::new(grammar);
23 let mut lines = Vec::new();
24
25 for rule in ordered_rules(grammar) {
26 let body = emitter.emit_expr(rule.expr(), BnfContext::Production)?;
27 lines.push(render_rule_line(BNF_RULE_TEMPLATE, rule.name(), &body));
28 }
29 for helper in emitter.helpers.entries() {
30 lines.push(render_rule_line(
31 BNF_RULE_TEMPLATE,
32 &helper.name,
33 &helper.body,
34 ));
35 }
36
37 Ok((finish_lines(&lines), emitter.report))
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41enum BnfContext {
42 Production,
43 SequenceItem,
44}
45
46#[derive(Clone, Debug)]
47struct BnfEmitter {
48 report: EmitReport,
49 helpers: HelperRules,
50}
51
52impl BnfEmitter {
53 fn new(grammar: &Grammar) -> Self {
54 Self {
55 report: EmitReport::default(),
56 helpers: HelperRules::new(grammar),
57 }
58 }
59
60 fn emit_expr(
61 &mut self,
62 expr: &GrammarExpr,
63 context: BnfContext,
64 ) -> Result<String, GrammarEmitError> {
65 match expr {
66 GrammarExpr::Empty => Ok(String::new()),
67 GrammarExpr::Terminal(value) => Ok(quote_terminal(value)),
68 GrammarExpr::TerminalInsensitive(value) => {
69 self.report.add_lossy(format!(
70 "BNF cannot preserve case-insensitive terminal {value:?}"
71 ));
72 Ok(quote_terminal(value))
73 }
74 GrammarExpr::CharRange(start, end) => self.emit_range_helper(*start, *end),
75 GrammarExpr::CharClass { negated, items } => {
76 self.emit_char_class_helper(*negated, items)
77 }
78 GrammarExpr::AnyChar => Err(unsupported_error(GrammarFormat::Bnf, "AnyChar")),
79 GrammarExpr::NonTerminal(name) => Ok(nonterminal(name)),
80 GrammarExpr::Choice {
81 ordered,
82 alternatives,
83 } => self.emit_choice(*ordered, alternatives, context),
84 GrammarExpr::Sequence(items) => self.emit_sequence(items),
85 GrammarExpr::Optional(inner) => self.emit_optional_helper(inner),
86 GrammarExpr::ZeroOrMore(inner) => self.emit_star_helper(inner),
87 GrammarExpr::OneOrMore(inner) => self.emit_plus_helper(inner),
88 GrammarExpr::Repeat { expr, min, max } => self.emit_repeat(expr, *min, *max),
89 GrammarExpr::And(_) => Err(unsupported_error(GrammarFormat::Bnf, "And")),
90 GrammarExpr::Not(_) => Err(unsupported_error(GrammarFormat::Bnf, "Not")),
91 GrammarExpr::Capture { label, expr } => {
92 report_capture_loss(&mut self.report, GrammarFormat::Bnf, label.as_ref());
93 self.emit_expr(expr, context)
94 }
95 }
96 }
97
98 fn emit_choice(
99 &mut self,
100 ordered: bool,
101 alternatives: &[GrammarExpr],
102 context: BnfContext,
103 ) -> Result<String, GrammarEmitError> {
104 if ordered {
105 self.report
106 .add_lossy("BNF treats ordered choice as unordered choice");
107 }
108 if context == BnfContext::SequenceItem {
109 return self.emit_choice_helper(&GrammarExpr::Choice {
110 ordered,
111 alternatives: alternatives.to_vec(),
112 });
113 }
114
115 alternatives
116 .iter()
117 .map(|alternative| self.emit_expr(alternative, BnfContext::Production))
118 .collect::<Result<Vec<_>, _>>()
119 .map(|items| items.join(" | "))
120 }
121
122 fn emit_sequence(&mut self, items: &[GrammarExpr]) -> Result<String, GrammarEmitError> {
123 let mut emitted = Vec::new();
124 for item in items {
125 let text = self.emit_expr(item, BnfContext::SequenceItem)?;
126 if !text.is_empty() {
127 emitted.push(text);
128 }
129 }
130 Ok(emitted.join(" "))
131 }
132
133 fn emit_repeat(
134 &mut self,
135 expr: &GrammarExpr,
136 min: usize,
137 max: Option<usize>,
138 ) -> Result<String, GrammarEmitError> {
139 if max.is_some_and(|max| max < min) {
140 return Err(unsupported_error(
141 GrammarFormat::Bnf,
142 format!("Repeat with min {min} greater than max {max:?}"),
143 ));
144 }
145
146 let mut parts = Vec::new();
147 for _ in 0..min {
148 parts.push(self.emit_expr(expr, BnfContext::SequenceItem)?);
149 }
150
151 match max {
152 Some(max) => {
153 for _ in min..max {
154 parts.push(self.emit_optional_helper(expr)?);
155 }
156 }
157 None => parts.push(self.emit_star_helper(expr)?),
158 }
159
160 Ok(parts
161 .into_iter()
162 .filter(|part| !part.is_empty())
163 .collect::<Vec<_>>()
164 .join(" "))
165 }
166
167 fn emit_choice_helper(&mut self, expr: &GrammarExpr) -> Result<String, GrammarEmitError> {
168 let (name, is_new) = self.helpers.reserve("choice", format!("{expr:?}"));
169 if is_new {
170 let body = self.emit_expr(expr, BnfContext::Production)?;
171 self.helpers.push(name.clone(), body);
172 }
173 Ok(nonterminal(&name))
174 }
175
176 fn emit_optional_helper(&mut self, expr: &GrammarExpr) -> Result<String, GrammarEmitError> {
177 if matches!(expr, GrammarExpr::Empty) {
178 return Ok(String::new());
179 }
180
181 let (name, is_new) = self.helpers.reserve("opt", format!("{expr:?}"));
182 if is_new {
183 let body = format!("{} |", self.emit_expr(expr, BnfContext::Production)?);
184 self.helpers.push(name.clone(), body);
185 }
186 Ok(nonterminal(&name))
187 }
188
189 fn emit_star_helper(&mut self, expr: &GrammarExpr) -> Result<String, GrammarEmitError> {
190 if matches!(expr, GrammarExpr::Empty) {
191 return Ok(String::new());
192 }
193
194 let (name, is_new) = self.helpers.reserve("star", format!("{expr:?}"));
195 if is_new {
196 let item = self.emit_expr(expr, BnfContext::SequenceItem)?;
197 let body = if item.is_empty() {
198 String::new()
199 } else {
200 format!("{item} {} |", nonterminal(&name))
201 };
202 self.helpers.push(name.clone(), body);
203 }
204 Ok(nonterminal(&name))
205 }
206
207 fn emit_plus_helper(&mut self, expr: &GrammarExpr) -> Result<String, GrammarEmitError> {
208 if matches!(expr, GrammarExpr::Empty) {
209 return Ok(String::new());
210 }
211
212 let (name, is_new) = self.helpers.reserve("plus", format!("{expr:?}"));
213 if is_new {
214 let item = self.emit_expr(expr, BnfContext::SequenceItem)?;
215 let body = if item.is_empty() {
216 String::new()
217 } else {
218 format!("{item} {} | {item}", nonterminal(&name))
219 };
220 self.helpers.push(name.clone(), body);
221 }
222 Ok(nonterminal(&name))
223 }
224
225 fn emit_range_helper(&mut self, start: char, end: char) -> Result<String, GrammarEmitError> {
226 let (name, is_new) = self
227 .helpers
228 .reserve("range", format!("{}:{}", start as u32, end as u32));
229 if is_new {
230 let body = expand_range(start, end)?
231 .into_iter()
232 .map(|character| quote_terminal(&character.to_string()))
233 .collect::<Vec<_>>()
234 .join(" | ");
235 self.helpers.push(name.clone(), body);
236 }
237 Ok(nonterminal(&name))
238 }
239
240 fn emit_char_class_helper(
241 &mut self,
242 negated: bool,
243 items: &[CharClassItem],
244 ) -> Result<String, GrammarEmitError> {
245 if negated {
246 return Err(unsupported_error(GrammarFormat::Bnf, "negated CharClass"));
247 }
248
249 let (name, is_new) = self.helpers.reserve("class", format!("{items:?}"));
250 if is_new {
251 let chars = expand_class_items(items)?;
252 if chars.is_empty() {
253 return Err(unsupported_error(GrammarFormat::Bnf, "empty CharClass"));
254 }
255 let body = chars
256 .into_iter()
257 .map(|character| quote_terminal(&character.to_string()))
258 .collect::<Vec<_>>()
259 .join(" | ");
260 self.helpers.push(name.clone(), body);
261 }
262 Ok(nonterminal(&name))
263 }
264}
265
266fn expand_range(start: char, end: char) -> Result<Vec<char>, GrammarEmitError> {
267 expanded_chars(
268 GrammarFormat::Bnf,
269 "CharRange",
270 start,
271 end,
272 MAX_BNF_EXPANSION,
273 )
274}
275
276fn expand_class_items(items: &[CharClassItem]) -> Result<Vec<char>, GrammarEmitError> {
277 let mut chars = Vec::new();
278 for item in items {
279 match item {
280 CharClassItem::Char(value) => chars.push(*value),
281 CharClassItem::Range(start, end) => chars.extend(expand_range(*start, *end)?),
282 }
283 if chars.len() > MAX_BNF_EXPANSION as usize {
284 return Err(unsupported_error(
285 GrammarFormat::Bnf,
286 format!("CharClass expands to more than {MAX_BNF_EXPANSION} characters"),
287 ));
288 }
289 }
290 Ok(chars)
291}
292
293fn report_capture_loss(report: &mut EmitReport, format: GrammarFormat, label: Option<&String>) {
294 if let Some(label) = label {
295 report.add_lossy(format!("{format} dropped capture label {label:?}"));
296 } else {
297 report.add_lossy(format!("{format} dropped anonymous capture"));
298 }
299}
300
301fn nonterminal(name: &str) -> String {
302 format!("<{name}>")
303}
304
305fn quote_terminal(value: &str) -> String {
306 let mut output = String::with_capacity(value.len() + 2);
307 output.push('"');
308 for character in value.chars() {
309 match character {
310 '"' => output.push_str("\\\""),
311 '\\' => output.push_str("\\\\"),
312 other => output.push(other),
313 }
314 }
315 output.push('"');
316 output
317}