meta_language/grammar/emit/
abnf.rs1use crate::grammar::{CharClassItem, Grammar, GrammarExpr, GrammarFormat};
2
3use super::{
4 ABNF_RULE_TEMPLATE, EmitReport, GrammarEmitError, finish_lines, ordered_rules,
5 render_rule_line, unsupported_error,
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::Feature(feature) => {
88 return Err(unsupported_error(GrammarFormat::Abnf, feature.head()));
89 }
90 GrammarExpr::Capture { label, expr } => {
91 report_capture_loss(&mut self.report, GrammarFormat::Abnf, label.as_ref());
92 return self.emit_expr(expr, parent);
93 }
94 };
95
96 if precedence < parent {
97 Ok(format!("( {text} )"))
98 } else {
99 Ok(text)
100 }
101 }
102
103 fn emit_choice(
104 &mut self,
105 ordered: bool,
106 alternatives: &[GrammarExpr],
107 ) -> Result<String, GrammarEmitError> {
108 if ordered {
109 self.report
110 .add_lossy("ABNF treats ordered choice as unordered choice");
111 }
112 alternatives
113 .iter()
114 .map(|alternative| self.emit_expr(alternative, Precedence::Choice))
115 .collect::<Result<Vec<_>, _>>()
116 .map(|items| items.join(" / "))
117 }
118
119 fn emit_sequence(&mut self, items: &[GrammarExpr]) -> Result<String, GrammarEmitError> {
120 let mut emitted = Vec::new();
121 for item in items {
122 if matches!(item, GrammarExpr::Empty) {
123 continue;
124 }
125 let text = self.emit_expr(item, Precedence::Sequence)?;
126 if !text.is_empty() {
127 emitted.push(text);
128 }
129 }
130 if emitted.is_empty() {
131 Ok("\"\"".to_string())
132 } else {
133 Ok(emitted.join(" "))
134 }
135 }
136
137 fn emit_repeat(
138 &mut self,
139 expr: &GrammarExpr,
140 min: usize,
141 max: Option<usize>,
142 ) -> Result<String, GrammarEmitError> {
143 if max.is_some_and(|max| max < min) {
144 return Err(unsupported_error(
145 GrammarFormat::Abnf,
146 format!("Repeat with min {min} greater than max {max:?}"),
147 ));
148 }
149
150 let inner = self.emit_expr(expr, Precedence::Choice)?;
151 let prefix = max.map_or_else(
152 || {
153 if min == 0 {
154 "*".to_string()
155 } else {
156 format!("{min}*")
157 }
158 },
159 |max| {
160 if min == 0 {
161 format!("*{max}")
162 } else {
163 format!("{min}*{max}")
164 }
165 },
166 );
167 Ok(format!("{prefix}( {inner} )"))
168 }
169}
170
171fn emit_char_range(start: char, end: char) -> Result<String, GrammarEmitError> {
172 if start > end {
173 return Err(unsupported_error(
174 GrammarFormat::Abnf,
175 format!(
176 "CharRange has descending bounds U+{:04X}..=U+{:04X}",
177 start as u32, end as u32
178 ),
179 ));
180 }
181 Ok(format!("%x{}-{}", hex_char(start), hex_char(end)))
182}
183
184fn emit_char_class(negated: bool, items: &[CharClassItem]) -> Result<String, GrammarEmitError> {
185 if negated {
186 return Err(unsupported_error(GrammarFormat::Abnf, "negated CharClass"));
187 }
188 if items.is_empty() {
189 return Err(unsupported_error(GrammarFormat::Abnf, "empty CharClass"));
190 }
191
192 let items = items
193 .iter()
194 .map(emit_char_class_item)
195 .collect::<Result<Vec<_>, _>>()?
196 .join(" / ");
197 Ok(format!("( {items} )"))
198}
199
200fn emit_char_class_item(item: &CharClassItem) -> Result<String, GrammarEmitError> {
201 match item {
202 CharClassItem::Char(value) => Ok(format!("%x{}", hex_char(*value))),
203 CharClassItem::Range(start, end) => emit_char_range(*start, *end),
204 }
205}
206
207fn report_capture_loss(report: &mut EmitReport, format: GrammarFormat, label: Option<&String>) {
208 if let Some(label) = label {
209 report.add_lossy(format!("{format} dropped capture label {label:?}"));
210 } else {
211 report.add_lossy(format!("{format} dropped anonymous capture"));
212 }
213}
214
215fn quote_terminal(prefix: &str, value: &str) -> String {
216 if value.is_empty() {
217 return "\"\"".to_string();
218 }
219 if abnf_char_value_safe(value) {
220 return format!("{prefix}\"{value}\"");
221 }
222 numeric_terminal(value)
223}
224
225fn abnf_char_value_safe(value: &str) -> bool {
226 value
227 .chars()
228 .all(|character| matches!(character as u32, 0x20 | 0x21 | 0x23..=0x7e))
229}
230
231fn numeric_terminal(value: &str) -> String {
232 let values = value.chars().map(hex_char).collect::<Vec<_>>().join(".");
233 format!("%x{values}")
234}
235
236fn hex_char(value: char) -> String {
237 let code = value as u32;
238 if code <= 0xff {
239 format!("{code:02X}")
240 } else {
241 format!("{code:X}")
242 }
243}