diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/cranelift-codegen-meta/src/srcgen.rs | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cranelift-codegen-meta/src/srcgen.rs')
-rw-r--r-- | third_party/rust/cranelift-codegen-meta/src/srcgen.rs | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/third_party/rust/cranelift-codegen-meta/src/srcgen.rs b/third_party/rust/cranelift-codegen-meta/src/srcgen.rs new file mode 100644 index 0000000000..ad8db175d7 --- /dev/null +++ b/third_party/rust/cranelift-codegen-meta/src/srcgen.rs @@ -0,0 +1,484 @@ +//! Source code generator. +//! +//! The `srcgen` module contains generic helper routines and classes for +//! generating source code. + +#![macro_use] + +use std::cmp; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs; +use std::io::Write; +use std::path; + +use crate::error; + +static SHIFTWIDTH: usize = 4; + +/// A macro that simplifies the usage of the Formatter by allowing format +/// strings. +macro_rules! fmtln { + ($fmt:ident, $fmtstring:expr, $($fmtargs:expr),*) => { + $fmt.line(format!($fmtstring, $($fmtargs),*)); + }; + + ($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(crate) struct Formatter { + indent: usize, + lines: Vec<String>, +} + +impl Formatter { + /// Source code formatter class. Used to collect source code to be written + /// to a file, and keep track of indentation. + pub fn new() -> Self { + Self { + indent: 0, + lines: Vec::new(), + } + } + + /// Increase current indentation level by one. + pub fn indent_push(&mut self) { + self.indent += 1; + } + + /// Decrease indentation by one level. + pub fn indent_pop(&mut self) { + assert!(self.indent > 0, "Already at top level indentation"); + self.indent -= 1; + } + + pub fn indent<T, F: FnOnce(&mut Formatter) -> T>(&mut self, f: F) -> T { + self.indent_push(); + let ret = f(self); + self.indent_pop(); + ret + } + + /// Get the current whitespace indentation in the form of a String. + fn get_indent(&self) -> String { + if self.indent == 0 { + String::new() + } else { + format!("{:-1$}", " ", self.indent * SHIFTWIDTH) + } + } + + /// Get a string containing whitespace outdented one level. Used for + /// lines of code that are inside a single indented block. + fn get_outdent(&mut self) -> String { + self.indent_pop(); + let s = self.get_indent(); + self.indent_push(); + s + } + + /// Add an indented line. + pub fn line(&mut self, contents: impl AsRef<str>) { + let indented_line = format!("{}{}\n", self.get_indent(), contents.as_ref()); + 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!("{}{}\n", self.get_outdent(), s); + self.lines.push(new_line); + } + + /// Write `self.lines` to a file. + pub fn update_file( + &self, + filename: impl AsRef<str>, + directory: &str, + ) -> Result<(), error::Error> { + #[cfg(target_family = "windows")] + let path_str = format!("{}\\{}", directory, filename.as_ref()); + #[cfg(not(target_family = "windows"))] + let path_str = format!("{}/{}", directory, filename.as_ref()); + + let path = path::Path::new(&path_str); + let mut f = fs::File::create(path)?; + + for l in self.lines.iter().map(|l| l.as_bytes()) { + f.write_all(l)?; + } + + Ok(()) + } + + /// Add one or more lines after stripping common indentation. + pub fn multi_line(&mut self, s: &str) { + parse_multiline(s).into_iter().for_each(|l| self.line(&l)); + } + + /// Add a comment line. + pub fn comment(&mut self, s: impl AsRef<str>) { + fmtln!(self, "// {}", s.as_ref()); + } + + /// Add a (multi-line) documentation comment. + pub fn doc_comment(&mut self, contents: impl AsRef<str>) { + parse_multiline(contents.as_ref()) + .iter() + .map(|l| { + if l.is_empty() { + "///".into() + } else { + format!("/// {}", l) + } + }) + .for_each(|s| self.line(s.as_str())); + } + + /// Add a match expression. + pub fn add_match(&mut self, m: Match) { + fmtln!(self, "match {} {{", m.expr); + self.indent(|fmt| { + for (&(ref fields, ref body), ref names) in m.arms.iter() { + // name { fields } | name { fields } => { body } + let conditions = names + .iter() + .map(|name| { + if !fields.is_empty() { + format!("{} {{ {} }}", name, fields.join(", ")) + } else { + name.clone() + } + }) + .collect::<Vec<_>>() + .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); + }); + fmt.line("}"); + } + }); + self.line("}"); + } +} + +/// Compute the indentation of s, or None of an empty line. +fn _indent(s: &str) -> Option<usize> { + if s.is_empty() { + None + } else { + let t = s.trim_start(); + Some(s.len() - t.len()) + } +} + +/// Given a multi-line string, split it into a sequence of lines after +/// stripping a common indentation. This is useful for strings defined with +/// doc strings. +fn parse_multiline(s: &str) -> Vec<String> { + // Convert tabs into spaces. + let expanded_tab = format!("{:-1$}", " ", SHIFTWIDTH); + let lines: Vec<String> = s.lines().map(|l| l.replace("\t", &expanded_tab)).collect(); + + // Determine minimum indentation, ignoring the first line and empty lines. + let indent = lines + .iter() + .skip(1) + .filter(|l| !l.trim().is_empty()) + .map(|l| l.len() - l.trim_start().len()) + .min(); + + // Strip off leading blank lines. + let mut lines_iter = lines.iter().skip_while(|l| l.is_empty()); + let mut trimmed = Vec::with_capacity(lines.len()); + + // Remove indentation (first line is special) + if let Some(s) = lines_iter.next().map(|l| l.trim()).map(|l| l.to_string()) { + trimmed.push(s); + } + + // Remove trailing whitespace from other lines. + let mut other_lines = if let Some(indent) = indent { + // Note that empty lines may have fewer than `indent` chars. + lines_iter + .map(|l| &l[cmp::min(indent, l.len())..]) + .map(|l| l.trim_end()) + .map(|l| l.to_string()) + .collect::<Vec<_>>() + } else { + lines_iter + .map(|l| l.trim_end()) + .map(|l| l.to_string()) + .collect::<Vec<_>>() + }; + + trimmed.append(&mut other_lines); + + // Strip off trailing blank lines. + while let Some(s) = trimmed.pop() { + if s.is_empty() { + continue; + } else { + trimmed.push(s); + break; + } + } + + trimmed +} + +/// Match formatting class. +/// +/// Match objects collect all the information needed to emit a Rust `match` +/// expression, automatically deduplicating overlapping identical arms. +/// +/// Note that this class is ignorant of Rust types, and considers two fields +/// with the same name to be equivalent. BTreeMap/BTreeSet are used to +/// represent the arms in order to make the order deterministic. +pub(crate) struct Match { + expr: String, + arms: BTreeMap<(Vec<String>, String), BTreeSet<String>>, + /// The clause for the placeholder pattern _. + catch_all: Option<String>, +} + +impl Match { + /// Create a new match statement on `expr`. + pub fn new(expr: impl Into<String>) -> Self { + Self { + expr: expr.into(), + arms: BTreeMap::new(), + catch_all: None, + } + } + + 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<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 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); + } + + /// 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); + } +} + +#[cfg(test)] +mod srcgen_tests { + use super::parse_multiline; + use super::Formatter; + use super::Match; + + fn from_raw_string<S: Into<String>>(s: S) -> Vec<String> { + s.into() + .trim() + .split("\n") + .into_iter() + .map(|x| format!("{}\n", x)) + .collect() + } + + #[test] + fn adding_arms_works() { + let mut m = Match::new("x"); + m.arm("Orange", vec!["a", "b"], "some body"); + m.arm("Yellow", vec!["a", "b"], "some body"); + m.arm("Green", vec!["a", "b"], "different body"); + m.arm("Blue", vec!["x", "y"], "some body"); + assert_eq!(m.arms.len(), 3); + + 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 } | + Yellow { a, b } => { + some body + } + Blue { x, y } => { + some body + } +} + "#, + ); + 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"; + let expected = vec!["hello", "world"]; + let output = parse_multiline(input); + assert_eq!(output, expected); + } + + #[test] + fn formatter_basic_example_works() { + let mut fmt = Formatter::new(); + fmt.line("Hello line 1"); + fmt.indent_push(); + fmt.comment("Nested comment"); + fmt.indent_pop(); + fmt.line("Back home again"); + let expected_lines = vec![ + "Hello line 1\n", + " // Nested comment\n", + "Back home again\n", + ]; + assert_eq!(fmt.lines, expected_lines); + } + + #[test] + fn get_indent_works() { + let mut fmt = Formatter::new(); + let expected_results = vec!["", " ", " ", ""]; + + let actual_results = Vec::with_capacity(4); + (0..3).for_each(|_| { + fmt.get_indent(); + fmt.indent_push(); + }); + (0..3).for_each(|_| fmt.indent_pop()); + fmt.get_indent(); + + actual_results + .into_iter() + .zip(expected_results.into_iter()) + .for_each(|(actual, expected): (String, &str)| assert_eq!(&actual, expected)); + } + + #[test] + fn fmt_can_add_type_to_lines() { + let mut fmt = Formatter::new(); + fmt.line(format!("pub const {}: Type = Type({:#x});", "example", 0,)); + let expected_lines = vec!["pub const example: Type = Type(0x0);\n"]; + assert_eq!(fmt.lines, expected_lines); + } + + #[test] + fn fmt_can_add_indented_line() { + let mut fmt = Formatter::new(); + fmt.line("hello"); + fmt.indent_push(); + fmt.line("world"); + let expected_lines = vec!["hello\n", " world\n"]; + assert_eq!(fmt.lines, expected_lines); + } + + #[test] + fn fmt_can_add_doc_comments() { + let mut fmt = Formatter::new(); + fmt.doc_comment("documentation\nis\ngood"); + let expected_lines = vec!["/// documentation\n", "/// is\n", "/// good\n"]; + assert_eq!(fmt.lines, expected_lines); + } + + #[test] + fn fmt_can_add_doc_comments_with_empty_lines() { + let mut fmt = Formatter::new(); + fmt.doc_comment( + r#"documentation + can be really good. + + If you stick to writing it. +"#, + ); + let expected_lines = from_raw_string( + r#" +/// documentation +/// can be really good. +/// +/// If you stick to writing it."#, + ); + assert_eq!(fmt.lines, expected_lines); + } +} |