diff options
author | Nadrieril | 2019-03-09 20:28:18 +0100 |
---|---|---|
committer | Nadrieril | 2019-03-09 20:28:18 +0100 |
commit | b44cbca3a3996b3c47ec0f883bd1dc087971f2d1 (patch) | |
tree | 3fa171309ef1eb218cd77aae9418e17d1fec8731 | |
parent | cb9dfc1b9421e9814dac3ba6c78bad0d271f9d67 (diff) |
Refactor abnf_to_pest to make rule editing possible
Obsoletes #16
-rw-r--r-- | abnf_to_pest/src/lib.rs | 207 | ||||
-rw-r--r-- | dhall_parser/build.rs | 50 |
2 files changed, 128 insertions, 129 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 } diff --git a/dhall_parser/build.rs b/dhall_parser/build.rs index d30e31e..2bafba8 100644 --- a/dhall_parser/build.rs +++ b/dhall_parser/build.rs @@ -1,10 +1,9 @@ -use std::collections::HashMap; use std::env; use std::fs::File; use std::io::{BufRead, BufReader, Read, Write}; use std::path::Path; -use abnf_to_pest::{abnf_to_pest, PestRuleSettings}; +use abnf_to_pest::render_rules_to_pest; fn main() -> std::io::Result<()> { // TODO: upstream changes to grammar @@ -20,49 +19,32 @@ fn main() -> std::io::Result<()> { file.read_to_end(&mut data)?; data.push('\n' as u8); - let mut rule_settings: HashMap<String, PestRuleSettings> = HashMap::new(); + let mut rules = abnf_to_pest::parse_abnf(&data)?; for line in BufReader::new(File::open(visibility_path)?).lines() { let line = line?; if line.len() >= 2 && &line[0..2] == "# " { - rule_settings.insert( - line[2..].into(), - PestRuleSettings { - visible: false, - ..Default::default() - }, - ); - } else { - rule_settings.insert( - line, - PestRuleSettings { - visible: true, - ..Default::default() - }, - ); + rules.get_mut(&line[2..]).map(|x| x.silent = true); } } - rule_settings.insert( - "simple_label".to_owned(), - PestRuleSettings { - visible: true, - replace: Some( - " - keyword_raw ~ simple_label_next_char+ - | !keyword_raw ~ simple_label_first_char ~ simple_label_next_char* - " - .to_owned(), - ), - }, - ); + rules.remove("simple_label"); let mut file = File::create(pest_path)?; writeln!(&mut file, "// AUTO-GENERATED FILE. See build.rs.")?; - writeln!(&mut file, "{}", abnf_to_pest(&data, &rule_settings)?)?; + writeln!(&mut file, "{}", render_rules_to_pest(rules).pretty(80))?; + + writeln!(&mut file)?; + writeln!( + &mut file, + "simple_label = _{{ + keyword_raw ~ simple_label_next_char+ + | !keyword_raw ~ simple_label_first_char ~ simple_label_next_char* + }}" + )?; writeln!( &mut file, "keyword_raw = _{{ - let_raw | in_raw | if_raw | then_raw - | else_raw | Infinity_raw | NaN_raw + let_raw | in_raw | if_raw | then_raw + | else_raw | Infinity_raw | NaN_raw }}" )?; writeln!( |