[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:
@@ -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, "}");
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user