diff options
Diffstat (limited to 'abnf_to_pest')
-rw-r--r-- | abnf_to_pest/src/lib.rs | 207 |
1 files changed, 112 insertions, 95 deletions
diff --git a/abnf_to_pest/src/lib.rs b/abnf_to_pest/src/lib.rs index 72746c0..7853773 100644 --- a/abnf_to_pest/src/lib.rs +++ b/abnf_to_pest/src/lib.rs @@ -1,146 +1,163 @@ #![allow(clippy::implicit_hasher, clippy::or_fun_call)] use itertools::Itertools; use std::collections::HashMap; +use pretty::{BoxDoc, Doc}; +pub use abnf::abnf::{Alternation, Concatenation, Repetition, Repeat, Range, Element}; +use abnf::abnf::Rule; -pub struct PestRuleSettings { - pub visible: bool, - pub replace: Option<String>, -} -impl Default for PestRuleSettings { - fn default() -> Self { - PestRuleSettings { - visible: true, - replace: None, - } - } +trait Pretty { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>>; } -pub fn abnf_to_pest( - data: &[u8], - rule_settings: &HashMap<String, PestRuleSettings>, -) -> std::io::Result<String> { - use abnf::abnf::*; - use pretty::{BoxDoc, Doc}; - fn format_rule( - x: Rule, - rule_settings: &HashMap<String, PestRuleSettings>, - ) -> Doc<BoxDoc<()>> { - let rulename = format_rulename(x.name); - let default = Default::default(); - let setting = rule_settings.get(&rulename).unwrap_or(&default); - let visible = if setting.visible { "" } else { "_" }; - let contents = match setting.replace { - None => format_alternation(x.elements), - Some(ref x) => Doc::text(x.clone()), - }; - Doc::nil() - .append(Doc::text(rulename)) - .append(Doc::text(" = ")) - .append(Doc::text(visible)) - .append(Doc::text("{")) - .append(Doc::space().append(contents).nest(2)) - .append(Doc::space()) - .append(Doc::text("}")) - .group() - } - fn format_rulename(x: String) -> String { - let x = x.replace("-", "_"); - if x == "if" - || x == "else" - || x == "as" - || x == "let" - || x == "in" - || x == "fn" - // TODO: remove when https://github.com/pest-parser/pest/pull/375 gets into a release - || x == "whitespace" - { - x + "_" - } else { - x - } - } - fn format_alternation(x: Alternation) -> Doc<'static, BoxDoc<'static, ()>> { +impl Pretty for Alternation { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>> { Doc::intersperse( - x.concatenations - .into_iter() - .map(|x| format_concatenation(x).nest(2).group()), + self.concatenations + .iter() + .map(|x| x.pretty().nest(2).group()), Doc::space().append(Doc::text("| ")), ) } - fn format_concatenation( - x: Concatenation, - ) -> Doc<'static, BoxDoc<'static, ()>> { +} + +impl Pretty for Concatenation { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>> { Doc::intersperse( - x.repetitions.into_iter().map(format_repetition), + self.repetitions.iter().map(Repetition::pretty), Doc::space().append(Doc::text("~ ")), ) } - fn format_repetition(x: Repetition) -> Doc<'static, BoxDoc<'static, ()>> { - format_element(x.element).append( - x.repeat - .map(format_repeat) - .map(Doc::text) +} + +impl Pretty for Repetition { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>> { + self.element.pretty().append( + self.repeat + .as_ref() + .map(Repeat::pretty) .unwrap_or(Doc::nil()), ) } - fn format_repeat(x: Repeat) -> String { - match (x.min.unwrap_or(0), x.max) { +} + +impl Pretty for Repeat { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>> { + Doc::text(match (self.min.unwrap_or(0), self.max) { (0, None) => "*".into(), (1, None) => "+".into(), (0, Some(1)) => "?".into(), (min, None) => format!("{{{},}}", min), (min, Some(max)) if min == max => format!("{{{}}}", min), (min, Some(max)) => format!("{{{},{}}}", min, max), - } + }) } - fn format_element(x: Element) -> Doc<'static, BoxDoc<'static, ()>> { +} + +impl Pretty for Element { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>> { use abnf::abnf::Element::*; - match x { - Rulename(s) => Doc::text(format_rulename(s)), + match self { + Rulename(s) => Doc::text(escape_rulename(s)), Group(g) => Doc::text("(") - .append(format_alternation(g.alternation).nest(4).group()) + .append((g.alternation).pretty().nest(4).group()) .append(Doc::text(")")), Option(o) => Doc::text("(") - .append(format_alternation(o.alternation).nest(4).group()) + .append((o.alternation).pretty().nest(4).group()) .append(Doc::text(")?")), CharVal(s) => Doc::text(format!( "^\"{}\"", s.replace("\"", "\\\"").replace("\\", "\\\\") )), - NumVal(r) => Doc::text(format_range(r)), + NumVal(r) => r.pretty(), ProseVal(_) => unimplemented!(), } } - fn format_range(x: Range) -> String { +} + +impl Pretty for Range { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>> { use abnf::abnf::Range::*; - match x { + Doc::text(match self { Range(x, y) => { format!("'{}'..'{}'", format_char(x), format_char(y)) } OneOf(v) => { - format!("\"{}\"", v.into_iter().map(format_char).join("")) + format!("\"{}\"", v.iter().map(format_char).join("")) } - } + }) + } +} + +pub fn escape_rulename(x: &str) -> String { + let x = x.replace("-", "_"); + if x == "if" + || x == "else" + || x == "as" + || x == "let" + || x == "in" + || x == "fn" + // TODO: remove when https://github.com/pest-parser/pest/pull/375 gets into a release + || x == "whitespace" + { + x + "_" + } else { + x.clone() } - fn format_char(x: u64) -> String { - if x <= u64::from(u8::max_value()) { - let x: u8 = x as u8; - if x.is_ascii_graphic() { - let x: char = x as char; - if x != '"' && x != '\'' && x != '\\' { - return x.to_string(); - } +} + +fn format_char(x: &u64) -> String { + if *x <= u64::from(u8::max_value()) { + let x: u8 = *x as u8; + if x.is_ascii_graphic() { + let x: char = x as char; + if x != '"' && x != '\'' && x != '\\' { + return x.to_string(); } } - format!("\\u{{{:02X}}}", x) } + format!("\\u{{{:02X}}}", x) +} + +/// Allow control over some of the pest properties of the outputted rule +pub struct PestyRule { + pub silent: bool, + pub elements: Alternation, +} + +impl Pretty for (String, PestyRule) { + fn pretty(&self) -> Doc<'static, BoxDoc<'static, ()>> { + Doc::nil() + .append(Doc::text(self.0.clone())) + .append(Doc::text(" = ")) + .append(Doc::text(if self.1.silent { "_" } else { "" })) + .append(Doc::text("{")) + .append(Doc::space().append(self.1.elements.pretty()).nest(2)) + .append(Doc::space()) + .append(Doc::text("}")) + .group() + } +} + + +/// Parse an abnf file. Returns a map of rules. +pub fn parse_abnf(data: &[u8]) -> Result<HashMap<String, PestyRule>, std::io::Error> { let make_err = |e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)); + let rules: Vec<Rule> = abnf::abnf::rulelist_comp(&data).map_err(make_err)?.1; + Ok(rules.into_iter().map(|rule| { + let name = escape_rulename(&rule.name); + (name.clone(), PestyRule { + silent: false, + elements: rule.elements.clone(), + }) + }).collect()) +} - let rules = rulelist_comp(&data).map_err(make_err)?.1; - let formatted_rules = - rules.into_iter().map(|x| format_rule(x, rule_settings)); - let doc: Doc<_> = Doc::intersperse(formatted_rules, Doc::newline()); - Ok(format!("{}", doc.pretty(80))) +pub fn render_rules_to_pest<I>(rules: I) -> Doc<'static, BoxDoc<'static, ()>, ()> +where I: IntoIterator<Item=(String, PestyRule)>, +{ + let pretty_rules = rules.into_iter().map(|x| x.pretty()); + let doc: Doc<_> = Doc::intersperse(pretty_rules, Doc::newline()); + doc } |