summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cranelift-codegen-meta/src/srcgen.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/cranelift-codegen-meta/src/srcgen.rs
parentInitial commit. (diff)
downloadfirefox-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.rs484
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);
+ }
+}