summaryrefslogtreecommitdiff
path: root/abnf_to_pest
diff options
context:
space:
mode:
Diffstat (limited to 'abnf_to_pest')
-rw-r--r--abnf_to_pest/src/lib.rs207
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
}