From 72b0d26ee93e2bfb577b6095d6a3cb993c0ef8fb Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Mon, 11 Mar 2019 17:12:03 +0100 Subject: [PATCH] [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. --- cranelift/codegen/meta/src/gen_settings.rs | 8 +- cranelift/codegen/meta/src/srcgen.rs | 107 ++++++++++++++++++--- 2 files changed, 98 insertions(+), 17 deletions(-) diff --git a/cranelift/codegen/meta/src/gen_settings.rs b/cranelift/codegen/meta/src/gen_settings.rs index a3c97ebab8..20fc6d1fc3 100644 --- a/cranelift/codegen/meta/src/gen_settings.rs +++ b/cranelift/codegen/meta/src/gen_settings.rs @@ -152,13 +152,9 @@ fn gen_getter(setting: &Setting, fmt: &mut Formatter) { fmt.indent(|fmt| { let mut m = Match::new(format!("self.bytes[{}]", setting.byte_offset)); for (i, v) in values.iter().enumerate() { - m.arm( - format!("{}", i), - vec![], - format!("{}::{}", ty, camel_case(v)), - ); + m.arm_no_fields(format!("{}", i), format!("{}::{}", ty, camel_case(v))); } - m.arm("_", vec![], "panic!(\"Invalid enum value\")"); + m.arm_no_fields("_", "panic!(\"Invalid enum value\")"); fmt.add_match(m); }); fmtln!(fmt, "}"); diff --git a/cranelift/codegen/meta/src/srcgen.rs b/cranelift/codegen/meta/src/srcgen.rs index dc7f622940..9a6c4af038 100644 --- a/cranelift/codegen/meta/src/srcgen.rs +++ b/cranelift/codegen/meta/src/srcgen.rs @@ -25,6 +25,14 @@ macro_rules! fmtln { ($fmt:ident, $arg:expr) => { $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 { @@ -84,6 +92,11 @@ impl Formatter { 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. pub fn _outdented_line(&mut self, s: &str) { let new_line = format!("{}{}", self._get_outdent(), s); @@ -112,7 +125,7 @@ impl Formatter { } /// 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)); } @@ -141,7 +154,7 @@ impl Formatter { self.indent(|fmt| { for (&(ref fields, ref body), ref names) in m.arms.iter() { // name { fields } | name { fields } => { body } - let conditions: Vec = names + let conditions = names .iter() .map(|name| { if fields.len() > 0 { @@ -150,9 +163,20 @@ impl Formatter { name.clone() } }) - .collect(); - let lhs = conditions.join(" | "); - fmtln!(fmt, "{} => {{", lhs); + .collect::>() + .join(" |\n") + + " => {"; + + 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.line(body); }); @@ -239,27 +263,57 @@ fn parse_multiline(s: &str) -> Vec { pub struct Match { expr: String, arms: BTreeMap<(Vec, String), BTreeSet>, + /// The clause for the placeholder pattern _. + catch_all: Option, } impl Match { /// Create a new match statement on `expr`. - pub fn new>(expr: T) -> Self { + pub fn new(expr: impl Into) -> Self { Self { expr: expr.into(), arms: BTreeMap::new(), + catch_all: None, } } - /// Add an arm to the Match statement. - pub fn arm>(&mut self, name: T, fields: Vec, body: T) { - // let key = (fields, body); + fn set_catch_all(&mut self, clause: String) { + assert!(self.catch_all.is_none()); + self.catch_all = Some(clause); + } + + /// Add an arm that reads fields to the Match statement. + pub fn arm, S: Into>(&mut self, name: T, fields: Vec, 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 fields = fields.into_iter().map(|x| x.into()).collect(); let match_arm = self .arms .entry((fields, body)) .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, body: impl Into) { + 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 } => { different body } - Orange { a, b } | Yellow { a, b } => { + Orange { a, b } | + Yellow { a, b } => { some body } Blue { x, y } => { @@ -308,6 +363,36 @@ match x { 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] fn parse_multiline_works() { let input = "\n hello\n world\n";