1use core::fmt;
21
22use crate::ast::{CharVal, DefinedAs, Definition, Element, Grammar, NumVal, Repeat};
23use crate::check::CheckedGrammar;
24
25impl fmt::Display for Grammar {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 if self.definitions().is_empty() {
32 return f.write_str("\r\n");
37 }
38 for definition in self.definitions() {
39 write!(f, "{definition}")?;
40 f.write_str("\r\n")?;
43 }
44 Ok(())
45 }
46}
47
48impl fmt::Display for CheckedGrammar {
49 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 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#[derive(Clone, Copy, PartialEq, Eq)]
86enum Context {
87 Free,
90 Concat,
92 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
148fn 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 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
190fn 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 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 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 #[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 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 assert_eq!(canon("*(\"a\" \"b\")"), "*(\"a\" \"b\")");
296 assert_eq!(canon("*(2*\"a\")"), "*(2*\"a\")");
298 assert_eq!(canon("\"a\" \"b\" / \"c\""), "\"a\" \"b\" / \"c\"");
300 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 #[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 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 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}