Skip to main content

abnf_oracle/
display.rs

1//! Canonical serialization (SCOPE.md 6.7).
2//!
3//! One definition per line, single spaces, CRLF endings, `%x` for every numeric value. The
4//! point is not tidiness: `Grammar::parse(g.to_string()) == g` is an M1 acceptance criterion,
5//! so this module and [`crate::Grammar`]'s `PartialEq` have to agree about what a grammar *is*.
6//! Anything the printer drops must be something equality ignores, and the round-trip tests are
7//! what hold the two together.
8//!
9//! Comments are not preserved, at either layer: the model is semantic (SCOPE.md 12, item 2).
10//!
11//! # Parentheses
12//!
13//! The printer emits a group exactly when dropping it would change the parse, which is the
14//! other half of "redundant groups are dropped" in the canonicalization pass. An alternation
15//! needs parentheses inside a concatenation or a repetition; a concatenation needs them inside
16//! a repetition, since `*a b` is `(*a) b`; a repetition needs them inside a repetition, since
17//! ABNF has no `**a`. Everything else — a concatenation in an alternation branch, anything
18//! inside `[...]` — stands bare.
19
20use core::fmt;
21
22use crate::ast::{CharVal, DefinedAs, Definition, Element, Grammar, NumVal, Repeat};
23use crate::check::CheckedGrammar;
24
25impl fmt::Display for Grammar {
26    /// Writes the grammar in canonical form: the definitions as written, one per line.
27    ///
28    /// `=/` is *not* merged here. That happens in `check`, and `CheckedGrammar` has its own
29    /// canonical form (D12).
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        if self.definitions().is_empty() {
32            // A grammar with no rules is legal — `rulelist` is satisfied by blank lines alone —
33            // but the empty string is not, because `rulelist` requires at least one item. A
34            // single blank line is the only text ABNF has for "no rules", and printing nothing
35            // here would break the round-trip on the one grammar that has nothing to print.
36            return f.write_str("\r\n");
37        }
38        for definition in self.definitions() {
39            write!(f, "{definition}")?;
40            // Every line is terminated, including the last: `rule` ends in `c-nl`, so a grammar
41            // without a final line ending would not parse back.
42            f.write_str("\r\n")?;
43        }
44        Ok(())
45    }
46}
47
48impl fmt::Display for CheckedGrammar {
49    /// Writes the merged rule table: one rule per line, `=/` folded in, in first-definition
50    /// order. Implicit core rules are never printed — they are the resolution environment, not
51    /// part of the grammar (D12).
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        if self.rules().is_empty() {
54            return f.write_str("\r\n");
55        }
56        for rule in self.rules() {
57            // Rebuilt from the arena rather than printed by a second, arena-shaped printer:
58            // one canonical form should have exactly one implementation (PLAN.md 3.10).
59            write!(f, "{} = ", rule.name)?;
60            write_element(f, &self.element_at(rule.body), Context::Free)?;
61            f.write_str("\r\n")?;
62        }
63        Ok(())
64    }
65}
66
67impl fmt::Display for Definition {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        let operator = match self.defined_as {
70            DefinedAs::Base => "=",
71            DefinedAs::Incremental => "=/",
72        };
73        write!(f, "{} {operator} ", self.name)?;
74        write_element(f, &self.body, Context::Free)
75    }
76}
77
78impl fmt::Display for Element {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write_element(f, self, Context::Free)
81    }
82}
83
84/// Where an element sits, which decides whether it needs parentheses.
85#[derive(Clone, Copy, PartialEq, Eq)]
86enum Context {
87    /// A rule body, an alternation branch, or inside `(...)` or `[...]`: nothing needs
88    /// parentheses, because the surrounding construct already delimits it.
89    Free,
90    /// An item of a concatenation.
91    Concat,
92    /// The body of a repetition, which the grammar restricts to a single `element`.
93    Repeat,
94}
95
96fn needs_parens(element: &Element, context: Context) -> bool {
97    match context {
98        Context::Free => false,
99        Context::Concat => matches!(element, Element::Alt(_)),
100        Context::Repeat => matches!(
101            element,
102            Element::Alt(_) | Element::Concat(_) | Element::Repeat { .. }
103        ),
104    }
105}
106
107fn write_element(f: &mut fmt::Formatter<'_>, element: &Element, context: Context) -> fmt::Result {
108    if needs_parens(element, context) {
109        f.write_str("(")?;
110        write_element(f, element, Context::Free)?;
111        return f.write_str(")");
112    }
113
114    match element {
115        Element::Alt(branches) => write_joined(f, branches, " / ", Context::Free),
116        Element::Concat(items) => write_joined(f, items, " ", Context::Concat),
117        Element::Repeat { repeat, body, .. } => {
118            write_repeat(f, *repeat)?;
119            write_element(f, body, Context::Repeat)
120        }
121        Element::Optional(body) => {
122            f.write_str("[")?;
123            write_element(f, body, Context::Free)?;
124            f.write_str("]")
125        }
126        Element::RuleRef { name, .. } => write!(f, "{name}"),
127        Element::CharVal(char_val) => write_char_val(f, char_val),
128        Element::NumVal { value, .. } => write_num_val(f, value),
129        Element::ProseVal { text, .. } => write!(f, "<{text}>"),
130    }
131}
132
133fn write_joined(
134    f: &mut fmt::Formatter<'_>,
135    elements: &[Element],
136    separator: &str,
137    context: Context,
138) -> fmt::Result {
139    for (index, element) in elements.iter().enumerate() {
140        if index > 0 {
141            f.write_str(separator)?;
142        }
143        write_element(f, element, context)?;
144    }
145    Ok(())
146}
147
148/// Writes repetition bounds, never emitting a form canonicalization has ruled out: not `0*n`,
149/// not `*1a` (that is `[a]`), not `1*1a` (that is `a`).
150fn write_repeat(f: &mut fmt::Formatter<'_>, repeat: Repeat) -> fmt::Result {
151    match (repeat.min, repeat.max) {
152        (0, None) => f.write_str("*"),
153        (min, None) => write!(f, "{min}*"),
154        (0, Some(max)) => write!(f, "*{max}"),
155        (min, Some(max)) if min == max => write!(f, "{min}"),
156        (min, Some(max)) => write!(f, "{min}*{max}"),
157    }
158}
159
160fn write_char_val(f: &mut fmt::Formatter<'_>, char_val: &CharVal) -> fmt::Result {
161    // `%i"abc"` is never emitted: the bare form means the same thing and is what RFC 5234
162    // grammars actually use.
163    if char_val.case_sensitive {
164        f.write_str("%s")?;
165    }
166    write!(f, "\"{}\"", char_val.value)
167}
168
169fn write_num_val(f: &mut fmt::Formatter<'_>, num_val: &NumVal) -> fmt::Result {
170    f.write_str("%x")?;
171    match num_val {
172        NumVal::Scalar(value) => write_hex(f, *value),
173        NumVal::Range { lo, hi } => {
174            write_hex(f, *lo)?;
175            f.write_str("-")?;
176            write_hex(f, *hi)
177        }
178        NumVal::Concat(values) => {
179            for (index, value) in values.iter().enumerate() {
180                if index > 0 {
181                    f.write_str(".")?;
182                }
183                write_hex(f, *value)?;
184            }
185            Ok(())
186        }
187    }
188}
189
190/// Uppercase hex, zero-padded to an even number of digits: `%x0D`, `%x41`, `%x0100`.
191fn write_hex(f: &mut fmt::Formatter<'_>, value: u64) -> fmt::Result {
192    let digits = format!("{value:X}");
193    if digits.len() % 2 == 1 {
194        f.write_str("0")?;
195    }
196    f.write_str(&digits)
197}
198
199#[cfg(test)]
200mod tests {
201    use crate::ast::{Grammar, ParseOptions};
202
203    /// The canonical form of a rule body, with the `x = ` and the line ending stripped.
204    fn canon(elements: &str) -> String {
205        let grammar = Grammar::parse(&format!("x = {elements}\r\n"))
206            .unwrap_or_else(|error| panic!("{elements:?} should parse: {error}"));
207        let printed = grammar.to_string();
208        printed
209            .strip_prefix("x = ")
210            .and_then(|rest| rest.strip_suffix("\r\n"))
211            .unwrap_or_else(|| panic!("unexpected shape: {printed:?}"))
212            .to_owned()
213    }
214
215    /// Asserts both halves of the M1 round-trip criterion: reparsing the canonical form gives
216    /// an equal grammar, and printing it again gives identical text.
217    fn round_trip(src: &str) {
218        let original = Grammar::parse(src).unwrap_or_else(|e| panic!("{src:?}: {e}"));
219        let printed = original.to_string();
220        let reparsed = Grammar::parse(&printed).unwrap_or_else(|e| {
221            panic!("canonical form of {src:?} did not parse: {e}\n{printed:?}")
222        });
223
224        assert_eq!(
225            original, reparsed,
226            "round-trip changed {src:?} via {printed:?}"
227        );
228        assert_eq!(
229            printed,
230            reparsed.to_string(),
231            "canonical form is not a fixpoint for {src:?}"
232        );
233    }
234
235    // -- the 6.7 spelling table, row by row -------------------------------------------------
236
237    #[test]
238    fn numeric_values_print_as_even_padded_uppercase_hex() {
239        assert_eq!(canon("%d13"), "%x0D");
240        assert_eq!(canon("%x41"), "%x41");
241        assert_eq!(canon("%x100"), "%x0100");
242        assert_eq!(canon("%x10FFFF"), "%x10FFFF");
243        assert_eq!(
244            canon("%b1000001"),
245            "%x41",
246            "the radix as written is not retained"
247        );
248        assert_eq!(canon("%x0"), "%x00");
249    }
250
251    #[test]
252    fn numeric_ranges_and_concatenations() {
253        assert_eq!(canon("%x41-5A"), "%x41-5A");
254        assert_eq!(canon("%d65-90"), "%x41-5A");
255        assert_eq!(canon("%d65.66.67"), "%x41.42.43");
256        assert_eq!(canon("%d13.10"), "%x0D.0A");
257    }
258
259    #[test]
260    fn quoted_strings() {
261        assert_eq!(canon("\"abc\""), "\"abc\"");
262        assert_eq!(canon("%i\"abc\""), "\"abc\"", "%i is never emitted");
263        assert_eq!(canon("%s\"abc\""), "%s\"abc\"");
264        assert_eq!(canon("%S\"abc\""), "%s\"abc\"");
265        assert_eq!(canon("\"\""), "\"\"");
266    }
267
268    #[test]
269    fn repetition_bounds() {
270        assert_eq!(canon("*\"a\""), "*\"a\"");
271        assert_eq!(canon("3\"a\""), "3\"a\"");
272        assert_eq!(canon("2*5\"a\""), "2*5\"a\"");
273        assert_eq!(canon("*3\"a\""), "*3\"a\"");
274        assert_eq!(canon("3*\"a\""), "3*\"a\"");
275        assert_eq!(canon("0*3\"a\""), "*3\"a\"", "never 0*");
276        assert_eq!(canon("0*\"a\""), "*\"a\"");
277    }
278
279    #[test]
280    fn optional_spellings_collapse_to_one() {
281        assert_eq!(canon("[\"a\"]"), "[\"a\"]");
282        assert_eq!(canon("*1\"a\""), "[\"a\"]");
283        assert_eq!(canon("0*1\"a\""), "[\"a\"]");
284        assert_eq!(canon("1*1\"a\""), "\"a\"");
285        assert_eq!(canon("1\"a\""), "\"a\"", "1a is a bare element");
286    }
287
288    #[test]
289    fn parentheses_appear_exactly_where_they_change_the_parse() {
290        // Needed: an alternation inside a concatenation or a repetition.
291        assert_eq!(canon("(\"a\" / \"b\") \"c\""), "(\"a\" / \"b\") \"c\"");
292        assert_eq!(canon("\"a\" (\"b\" / \"c\")"), "\"a\" (\"b\" / \"c\")");
293        assert_eq!(canon("*(\"a\" / \"b\")"), "*(\"a\" / \"b\")");
294        // Needed: a concatenation inside a repetition, since `*a b` is `(*a) b`.
295        assert_eq!(canon("*(\"a\" \"b\")"), "*(\"a\" \"b\")");
296        // Needed: a repetition inside a repetition, since ABNF has no `**a`.
297        assert_eq!(canon("*(2*\"a\")"), "*(2*\"a\")");
298        // Not needed: a concatenation in an alternation branch.
299        assert_eq!(canon("\"a\" \"b\" / \"c\""), "\"a\" \"b\" / \"c\"");
300        // Not needed: anything inside brackets.
301        assert_eq!(canon("[\"a\" / \"b\"]"), "[\"a\" / \"b\"]");
302        assert_eq!(canon("*[\"a\" \"b\"]"), "*[\"a\" \"b\"]");
303    }
304
305    #[test]
306    fn redundant_groups_are_dropped() {
307        assert_eq!(canon("(\"a\")"), "\"a\"");
308        assert_eq!(canon("((\"a\"))"), "\"a\"");
309        assert_eq!(canon("\"a\" (\"b\" \"c\")"), "\"a\" \"b\" \"c\"");
310        assert_eq!(canon("\"a\" / (\"b\" / \"c\")"), "\"a\" / \"b\" / \"c\"");
311        assert_eq!(canon("(\"a\" \"b\") \"c\""), "\"a\" \"b\" \"c\"");
312    }
313
314    #[test]
315    fn whitespace_is_normalized() {
316        assert_eq!(canon("\"a\"     /\t\"b\""), "\"a\" / \"b\"");
317        assert_eq!(canon("\"a\"  \"b\""), "\"a\" \"b\"");
318    }
319
320    #[test]
321    fn definition_operators_and_line_endings() {
322        let grammar = Grammar::parse("foo=\"a\"\nfoo=/\"b\"\n").expect("parses");
323        assert_eq!(grammar.to_string(), "foo = \"a\"\r\nfoo =/ \"b\"\r\n");
324    }
325
326    #[test]
327    fn comments_are_not_preserved() {
328        let grammar = Grammar::parse("; header\r\nfoo = \"a\" ; why\r\n").expect("parses");
329        assert_eq!(grammar.to_string(), "foo = \"a\"\r\n");
330    }
331
332    #[test]
333    fn prose_and_rule_references_survive_verbatim() {
334        assert_eq!(canon("<any token>"), "<any token>");
335        assert_eq!(canon("ALPHA"), "ALPHA");
336        assert_eq!(canon("a1-b2"), "a1-b2", "rule names print as first written");
337    }
338
339    // -- round-trip --------------------------------------------------------------------------
340
341    #[test]
342    fn round_trips_through_the_canonical_form() {
343        for src in [
344            "foo = \"a\"\r\n",
345            "foo = \"a\" / \"b\" / \"c\"\r\n",
346            "foo = \"a\" \"b\"\r\n",
347            "foo = (\"a\" / \"b\") \"c\"\r\n",
348            "foo = [\"a\"]\r\n",
349            "foo = *\"a\"\r\n",
350            "foo = 2*5(\"a\" / \"b\")\r\n",
351            "foo = %x41-5A\r\n",
352            "foo = %d13.10\r\n",
353            "foo = %s\"aBc\"\r\n",
354            "foo = <prose>\r\n",
355            "foo = ALPHA DIGIT\r\n",
356            "foo = \"a\"\r\nfoo =/ \"b\"\r\n",
357            "; comment\r\nfoo = \"a\"  ; trailing\r\n\r\nbar = foo\r\n",
358            "foo = \"a\"\r\n      \"b\"\r\n",
359            "foo = *(2*3\"a\")\r\n",
360            "\r\n",
361        ] {
362            round_trip(src);
363        }
364    }
365
366    #[test]
367    fn the_empty_grammar_prints_as_a_blank_line() {
368        // `rulelist` needs at least one item, so the empty string is not a grammar even though
369        // a grammar with no rules is perfectly legal.
370        let empty = Grammar::parse("\r\n").expect("parses");
371        assert!(empty.definitions().is_empty());
372        assert_eq!(empty.to_string(), "\r\n");
373        assert_eq!(Grammar::parse(&empty.to_string()).expect("reparses"), empty);
374    }
375
376    #[test]
377    fn round_trips_under_strict_crlf() {
378        // Parse options are provenance and do not affect equality (D21); the canonical form is
379        // CRLF, so it parses back under the stricter setting too.
380        let strict = ParseOptions { strict_crlf: true };
381        let src = "foo = \"a\" / [\"b\"]\r\nfoo =/ %x41\r\n";
382        let original = Grammar::parse_with(src, strict.clone()).expect("parses");
383        let reparsed = Grammar::parse_with(&original.to_string(), strict).expect("reparses");
384        assert_eq!(original, reparsed);
385    }
386
387    #[test]
388    fn lenient_and_strict_parses_of_the_same_grammar_are_equal() {
389        let lenient = Grammar::parse("foo = \"a\"\n").expect("parses");
390        let strict = Grammar::parse_with("foo = \"a\"\r\n", ParseOptions { strict_crlf: true })
391            .expect("parses");
392        assert_eq!(lenient, strict);
393        assert_eq!(lenient.to_string(), strict.to_string());
394    }
395
396    #[test]
397    fn spellings_that_differ_only_syntactically_are_equal() {
398        for (left, right) in [
399            ("foo = *1\"a\"\r\n", "foo = [\"a\"]\r\n"),
400            ("foo = 1*1\"a\"\r\n", "foo = \"a\"\r\n"),
401            ("foo = %d65\r\n", "foo = %x41\r\n"),
402            ("foo = %i\"a\"\r\n", "foo = \"a\"\r\n"),
403            ("foo = (\"a\")\r\n", "foo = \"a\"\r\n"),
404            (
405                "foo = \"a\" / (\"b\" / \"c\")\r\n",
406                "foo = \"a\" / \"b\" / \"c\"\r\n",
407            ),
408            ("foo = \"a\"\r\n", "FOO = \"a\"\r\n"),
409        ] {
410            let left_grammar = Grammar::parse(left).expect("parses");
411            let right_grammar = Grammar::parse(right).expect("parses");
412            assert_eq!(left_grammar, right_grammar, "{left:?} vs {right:?}");
413        }
414    }
415
416    #[test]
417    fn spellings_that_differ_semantically_are_not_equal() {
418        for (left, right) in [
419            ("foo = \"a\" \"b\"\r\n", "foo = \"a\" / \"b\"\r\n"),
420            ("foo = %s\"a\"\r\n", "foo = \"a\"\r\n"),
421            ("foo = *\"a\"\r\n", "foo = 1*\"a\"\r\n"),
422            ("foo = \"a\"\r\n", "foo =/ \"a\"\r\n"),
423            ("foo = \"a\"\r\n", "bar = \"a\"\r\n"),
424            ("foo = \"a\"\r\n", "foo = \"A\"\r\n"),
425        ] {
426            let left_grammar = Grammar::parse(left).expect("parses");
427            let right_grammar = Grammar::parse(right).expect("parses");
428            assert_ne!(left_grammar, right_grammar, "{left:?} vs {right:?}");
429        }
430    }
431}