[meta] Add features to srcgen;

- Adds a compiler warning when the fmtln! macro isn't correctly used.
- Allow to add an empty line.
- Make the output of generated matches more beautiful, by having one
struct per line on the clause.
- Add a method to add match with doesn't read any data from fields.
- Make sure that the placeholder clause of a match is put at the end.
This commit is contained in:
Benjamin Bouvier
2019-03-11 17:12:03 +01:00
parent 68bda3a42d
commit 72b0d26ee9
2 changed files with 98 additions and 17 deletions

View File

@@ -152,13 +152,9 @@ fn gen_getter(setting: &Setting, fmt: &mut Formatter) {
fmt.indent(|fmt| { fmt.indent(|fmt| {
let mut m = Match::new(format!("self.bytes[{}]", setting.byte_offset)); let mut m = Match::new(format!("self.bytes[{}]", setting.byte_offset));
for (i, v) in values.iter().enumerate() { for (i, v) in values.iter().enumerate() {
m.arm( m.arm_no_fields(format!("{}", i), format!("{}::{}", ty, camel_case(v)));
format!("{}", i),
vec![],
format!("{}::{}", ty, camel_case(v)),
);
} }
m.arm("_", vec![], "panic!(\"Invalid enum value\")"); m.arm_no_fields("_", "panic!(\"Invalid enum value\")");
fmt.add_match(m); fmt.add_match(m);
}); });
fmtln!(fmt, "}"); fmtln!(fmt, "}");

View File

@@ -25,6 +25,14 @@ macro_rules! fmtln {
($fmt:ident, $arg:expr) => { ($fmt:ident, $arg:expr) => {
$fmt.line($arg); $fmt.line($arg);
}; };
($_:tt, $($args:expr),+) => {
compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.");
};
($_:tt) => {
compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.");
};
} }
pub struct Formatter { pub struct Formatter {
@@ -84,6 +92,11 @@ impl Formatter {
self.lines.push(indented_line); self.lines.push(indented_line);
} }
/// Pushes an empty line.
pub fn empty_line(&mut self) {
self.lines.push("\n".to_string());
}
/// Emit a line outdented one level. /// Emit a line outdented one level.
pub fn _outdented_line(&mut self, s: &str) { pub fn _outdented_line(&mut self, s: &str) {
let new_line = format!("{}{}", self._get_outdent(), s); let new_line = format!("{}{}", self._get_outdent(), s);
@@ -112,7 +125,7 @@ impl Formatter {
} }
/// Add one or more lines after stripping common indentation. /// Add one or more lines after stripping common indentation.
pub fn _multi_line(&mut self, s: &str) { pub fn multi_line(&mut self, s: &str) {
parse_multiline(s).into_iter().for_each(|l| self.line(&l)); parse_multiline(s).into_iter().for_each(|l| self.line(&l));
} }
@@ -141,7 +154,7 @@ impl Formatter {
self.indent(|fmt| { self.indent(|fmt| {
for (&(ref fields, ref body), ref names) in m.arms.iter() { for (&(ref fields, ref body), ref names) in m.arms.iter() {
// name { fields } | name { fields } => { body } // name { fields } | name { fields } => { body }
let conditions: Vec<String> = names let conditions = names
.iter() .iter()
.map(|name| { .map(|name| {
if fields.len() > 0 { if fields.len() > 0 {
@@ -150,9 +163,20 @@ impl Formatter {
name.clone() name.clone()
} }
}) })
.collect(); .collect::<Vec<_>>()
let lhs = conditions.join(" | "); .join(" |\n")
fmtln!(fmt, "{} => {{", lhs); + " => {";
fmt.multi_line(&conditions);
fmt.indent(|fmt| {
fmt.line(body);
});
fmt.line("}");
}
// Make sure to include the catch all clause last.
if let Some(body) = m.catch_all {
fmt.line("_ => {");
fmt.indent(|fmt| { fmt.indent(|fmt| {
fmt.line(body); fmt.line(body);
}); });
@@ -239,27 +263,57 @@ fn parse_multiline(s: &str) -> Vec<String> {
pub struct Match { pub struct Match {
expr: String, expr: String,
arms: BTreeMap<(Vec<String>, String), BTreeSet<String>>, arms: BTreeMap<(Vec<String>, String), BTreeSet<String>>,
/// The clause for the placeholder pattern _.
catch_all: Option<String>,
} }
impl Match { impl Match {
/// Create a new match statement on `expr`. /// Create a new match statement on `expr`.
pub fn new<T: Into<String>>(expr: T) -> Self { pub fn new(expr: impl Into<String>) -> Self {
Self { Self {
expr: expr.into(), expr: expr.into(),
arms: BTreeMap::new(), arms: BTreeMap::new(),
catch_all: None,
} }
} }
/// Add an arm to the Match statement. fn set_catch_all(&mut self, clause: String) {
pub fn arm<T: Into<String>>(&mut self, name: T, fields: Vec<T>, body: T) { assert!(self.catch_all.is_none());
// let key = (fields, body); self.catch_all = Some(clause);
}
/// Add an arm that reads fields to the Match statement.
pub fn arm<T: Into<String>, S: Into<String>>(&mut self, name: T, fields: Vec<S>, body: T) {
let name = name.into();
assert!(
name != "_",
"catch all clause can't extract fields, use arm_no_fields instead."
);
let body = body.into(); let body = body.into();
let fields = fields.into_iter().map(|x| x.into()).collect(); let fields = fields.into_iter().map(|x| x.into()).collect();
let match_arm = self let match_arm = self
.arms .arms
.entry((fields, body)) .entry((fields, body))
.or_insert_with(BTreeSet::new); .or_insert_with(BTreeSet::new);
match_arm.insert(name.into()); match_arm.insert(name);
}
/// Adds an arm that doesn't read anythings from the fields to the Match statement.
pub fn arm_no_fields(&mut self, name: impl Into<String>, body: impl Into<String>) {
let body = body.into();
let name = name.into();
if name == "_" {
self.set_catch_all(body);
return;
}
let match_arm = self
.arms
.entry((Vec::new(), body))
.or_insert_with(BTreeSet::new);
match_arm.insert(name);
} }
} }
@@ -296,7 +350,8 @@ match x {
Green { a, b } => { Green { a, b } => {
different body different body
} }
Orange { a, b } | Yellow { a, b } => { Orange { a, b } |
Yellow { a, b } => {
some body some body
} }
Blue { x, y } => { Blue { x, y } => {
@@ -308,6 +363,36 @@ match x {
assert_eq!(fmt.lines, expected_lines); assert_eq!(fmt.lines, expected_lines);
} }
#[test]
fn match_with_catchall_order() {
// The catchall placeholder must be placed after other clauses.
let mut m = Match::new("x");
m.arm("Orange", vec!["a", "b"], "some body");
m.arm("Green", vec!["a", "b"], "different body");
m.arm_no_fields("_", "unreachable!()");
assert_eq!(m.arms.len(), 2); // catchall is not counted
let mut fmt = Formatter::new();
fmt.add_match(m);
let expected_lines = from_raw_string(
r#"
match x {
Green { a, b } => {
different body
}
Orange { a, b } => {
some body
}
_ => {
unreachable!()
}
}
"#,
);
assert_eq!(fmt.lines, expected_lines);
}
#[test] #[test]
fn parse_multiline_works() { fn parse_multiline_works() {
let input = "\n hello\n world\n"; let input = "\n hello\n world\n";