meta_language/grammar/emit/
abnf.rs1use crate::grammar::{CharClassItem, Grammar, GrammarExpr, GrammarFormat};
2
3use super::{
4 finish_lines, ordered_rules, render_rule_line, unsupported_error, EmitReport, GrammarEmitError,
5 ABNF_RULE_TEMPLATE,
6};
7
8pub fn emit_abnf(grammar: &Grammar) -> Result<(String, EmitReport), GrammarEmitError> {
19 let mut emitter = AbnfEmitter::default();
20 let mut lines = Vec::new();
21
22 for rule in ordered_rules(grammar) {
23 let body = emitter.emit_expr(rule.expr(), Precedence::Choice)?;
24 lines.push(render_rule_line(ABNF_RULE_TEMPLATE, rule.name(), &body));
25 }
26
27 Ok((finish_lines(&lines), emitter.report))
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
31enum Precedence {
32 Choice = 0,
33 Sequence = 1,
34 Atom = 2,
35}
36
37#[derive(Clone, Debug, Default)]
38struct AbnfEmitter {
39 report: EmitReport,
40}
41
42impl AbnfEmitter {
43 fn emit_expr(
44 &mut self,
45 expr: &GrammarExpr,
46 parent: Precedence,
47 ) -> Result<String, GrammarEmitError> {
48 let (text, precedence) = match expr {
49 GrammarExpr::Empty => ("\"\"".to_string(), Precedence::Atom),
50 GrammarExpr::Terminal(value) => (quote_terminal("%s", value), Precedence::Atom),
51 GrammarExpr::TerminalInsensitive(value) => {
52 (quote_terminal("%i", value), Precedence::Atom)
53 }
54 GrammarExpr::CharRange(start, end) => {
55 (emit_char_range(*start, *end)?, Precedence::Atom)
56 }
57 GrammarExpr::CharClass { negated, items } => {
58 (emit_char_class(*negated, items)?, Precedence::Atom)
59 }
60 GrammarExpr::AnyChar => ("%x00-10FFFF".to_string(), Precedence::Atom),
61 GrammarExpr::NonTerminal(name) => (name.clone(), Precedence::Atom),
62 GrammarExpr::Choice {
63 ordered,
64 alternatives,
65 } => (
66 self.emit_choice(*ordered, alternatives)?,
67 Precedence::Choice,
68 ),
69 GrammarExpr::Sequence(items) => (self.emit_sequence(items)?, Precedence::Sequence),
70 GrammarExpr::Optional(inner) => {
71 let inner = self.emit_expr(inner, Precedence::Choice)?;
72 (format!("[ {inner} ]"), Precedence::Atom)
73 }
74 GrammarExpr::ZeroOrMore(inner) => {
75 let inner = self.emit_expr(inner, Precedence::Choice)?;
76 (format!("*( {inner} )"), Precedence::Atom)
77 }
78 GrammarExpr::OneOrMore(inner) => {
79 let inner = self.emit_expr(inner, Precedence::Choice)?;
80 (format!("1*( {inner} )"), Precedence::Atom)
81 }
82 GrammarExpr::Repeat { expr, min, max } => {
83 (self.emit_repeat(expr, *min, *max)?, Precedence::Atom)
84 }
85 GrammarExpr::And(_) => return Err(unsupported_error(GrammarFormat::Abnf, "And")),
86 GrammarExpr::Not(_) => return Err(unsupported_error(GrammarFormat::Abnf, "Not")),
87 GrammarExpr::Capture { label, expr } => {
88 report_capture_loss(&mut self.report, GrammarFormat::Abnf, label.as_ref());
89 return self.emit_expr(expr, parent);
90 }
91 };
92
93 if precedence < parent {
94 Ok(format!("( {text} )"))
95 } else {
96 Ok(text)
97 }
98 }
99
100 fn emit_choice(
101 &mut self,
102 ordered: bool,
103 alternatives: &[GrammarExpr],
104 ) -> Result<String, GrammarEmitError> {
105 if ordered {
106 self.report
107 .add_lossy("ABNF treats ordered choice as unordered choice");
108 }
109 alternatives
110 .iter()
111 .map(|alternative| self.emit_expr(alternative, Precedence::Choice))
112 .collect::<Result<Vec<_>, _>>()
113 .map(|items| items.join(" / "))
114 }
115
116 fn emit_sequence(&mut self, items: &[GrammarExpr]) -> Result<String, GrammarEmitError> {
117 let mut emitted = Vec::new();
118 for item in items {
119 if matches!(item, GrammarExpr::Empty) {
120 continue;
121 }
122 let text = self.emit_expr(item, Precedence::Sequence)?;
123 if !text.is_empty() {
124 emitted.push(text);
125 }
126 }
127 if emitted.is_empty() {
128 Ok("\"\"".to_string())
129 } else {
130 Ok(emitted.join(" "))
131 }
132 }
133
134 fn emit_repeat(
135 &mut self,
136 expr: &GrammarExpr,
137 min: usize,
138 max: Option<usize>,
139 ) -> Result<String, GrammarEmitError> {
140 if max.is_some_and(|max| max < min) {
141 return Err(unsupported_error(
142 GrammarFormat::Abnf,
143 format!("Repeat with min {min} greater than max {max:?}"),
144 ));
145 }
146
147 let inner = self.emit_expr(expr, Precedence::Choice)?;
148 let prefix = max.map_or_else(
149 || {
150 if min == 0 {
151 "*".to_string()
152 } else {
153 format!("{min}*")
154 }
155 },
156 |max| {
157 if min == 0 {
158 format!("*{max}")
159 } else {
160 format!("{min}*{max}")
161 }
162 },
163 );
164 Ok(format!("{prefix}( {inner} )"))
165 }
166}
167
168fn emit_char_range(start: char, end: char) -> Result<String, GrammarEmitError> {
169 if start > end {
170 return Err(unsupported_error(
171 GrammarFormat::Abnf,
172 format!(
173 "CharRange has descending bounds U+{:04X}..=U+{:04X}",
174 start as u32, end as u32
175 ),
176 ));
177 }
178 Ok(format!("%x{}-{}", hex_char(start), hex_char(end)))
179}
180
181fn emit_char_class(negated: bool, items: &[CharClassItem]) -> Result<String, GrammarEmitError> {
182 if negated {
183 return Err(unsupported_error(GrammarFormat::Abnf, "negated CharClass"));
184 }
185 if items.is_empty() {
186 return Err(unsupported_error(GrammarFormat::Abnf, "empty CharClass"));
187 }
188
189 let items = items
190 .iter()
191 .map(emit_char_class_item)
192 .collect::<Result<Vec<_>, _>>()?
193 .join(" / ");
194 Ok(format!("( {items} )"))
195}
196
197fn emit_char_class_item(item: &CharClassItem) -> Result<String, GrammarEmitError> {
198 match item {
199 CharClassItem::Char(value) => Ok(format!("%x{}", hex_char(*value))),
200 CharClassItem::Range(start, end) => emit_char_range(*start, *end),
201 }
202}
203
204fn report_capture_loss(report: &mut EmitReport, format: GrammarFormat, label: Option<&String>) {
205 if let Some(label) = label {
206 report.add_lossy(format!("{format} dropped capture label {label:?}"));
207 } else {
208 report.add_lossy(format!("{format} dropped anonymous capture"));
209 }
210}
211
212fn quote_terminal(prefix: &str, value: &str) -> String {
213 if value.is_empty() {
214 return "\"\"".to_string();
215 }
216 if abnf_char_value_safe(value) {
217 return format!("{prefix}\"{value}\"");
218 }
219 numeric_terminal(value)
220}
221
222fn abnf_char_value_safe(value: &str) -> bool {
223 value
224 .chars()
225 .all(|character| matches!(character as u32, 0x20 | 0x21 | 0x23..=0x7e))
226}
227
228fn numeric_terminal(value: &str) -> String {
229 let values = value.chars().map(hex_char).collect::<Vec<_>>().join(".");
230 format!("%x{values}")
231}
232
233fn hex_char(value: char) -> String {
234 let code = value as u32;
235 if code <= 0xff {
236 format!("{code:02X}")
237 } else {
238 format!("{code:X}")
239 }
240}