Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 34 additions & 61 deletions cranelift/srcgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ use std::io::Write;

pub mod error;

static SHIFTWIDTH: usize = 4;

/// A macro for constructing a [`FileLocation`] at the current location.
#[macro_export]
macro_rules! loc {
Expand Down Expand Up @@ -85,10 +83,19 @@ impl Language {
}
}

static SHIFTWIDTH: usize = 4;

/// Returns 4 times as many spaces. Max indent is 64, which consists of 256 spaces
fn spaces_for_indent(indent: usize) -> &'static str {
// static str of 256 spaces
static INDENT_STR: &'static str = " ";
&INDENT_STR[..INDENT_STR.len().min(indent * SHIFTWIDTH)]
}

/// Collect source code to be written to a file and keep track of indentation.
pub struct Formatter {
indent: usize,
lines: Vec<String>,
lines: String,
lang: Language,
}

Expand All @@ -98,7 +105,7 @@ impl Formatter {
pub fn new(lang: Language) -> Self {
Self {
indent: 0,
lines: Vec::new(),
lines: String::new(),
lang,
}
}
Expand All @@ -123,19 +130,16 @@ impl Formatter {
}

/// 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)
}
fn get_indent(&self) -> &'static str {
spaces_for_indent(self.indent)
}

/// Add an indented line.
pub fn line(&mut self, contents: impl Display) {
use std::fmt::Write;

let indent = self.get_indent();
let indented_line = format!("{indent}{contents}\n");
self.lines.push(indented_line);
write!(&mut self.lines, "{indent}{contents}\n").unwrap();
}

/// Add an indented line with a given a `location` appended as a comment to
Expand All @@ -144,21 +148,19 @@ impl Formatter {
use std::fmt::Write;

let indent = self.get_indent();
let mut buf = String::new();

write!(&mut buf, "{indent}{contents}").unwrap();
write!(&mut self.lines, "{indent}{contents}").unwrap();
// just after writing contents, check ends_with using should_append_location
if self.lang.should_append_location(&buf) {
if self.lang.should_append_location(&self.lines) {
let comment_token = self.lang.comment_token();
write!(&mut buf, " {comment_token} {location}").unwrap();
write!(&mut self.lines, " {comment_token} {location}").unwrap();
}
buf.push('\n');
self.lines.push(buf);
self.lines.push('\n');
}

/// Pushes an empty line.
pub fn empty_line(&mut self) {
self.lines.push("\n".to_string());
self.lines.push('\n');
}

/// Add one or more lines after stripping common indentation.
Expand Down Expand Up @@ -248,30 +250,17 @@ impl Formatter {
eprintln!("Writing generated file: {}", path.display());
let mut f = fs::File::create(path)?;

for l in self.lines.iter().map(|l| l.as_bytes()) {
f.write_all(l)?;
}

f.write_all(self.lines.as_bytes())?;
Ok(())
}
}

/// 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 expanded_tab = spaces_for_indent(1);
let lines: Vec<String> = s.lines().map(|l| l.replace('\t', &expanded_tab)).collect();

// Determine minimum indentation, ignoring the first line and empty lines.
Expand Down Expand Up @@ -393,14 +382,6 @@ mod srcgen_tests {
use super::Match;
use super::parse_multiline;

fn from_raw_string<S: Into<String>>(s: S) -> Vec<String> {
s.into()
.trim()
.split("\n")
.map(|x| format!("{x}\n"))
.collect()
}

#[test]
fn adding_arms_works() {
let mut m = Match::new("x");
Expand All @@ -413,9 +394,7 @@ mod srcgen_tests {
let mut fmt = Formatter::new(Language::Rust);
fmt.add_match(m);

let expected_lines = from_raw_string(
r#"
match x {
let expected_lines = r#"match x {
Green { a, b } => {
different body
}
Expand All @@ -427,8 +406,7 @@ match x {
some body
}
}
"#,
);
"#;
assert_eq!(fmt.lines, expected_lines);
}

Expand All @@ -444,9 +422,7 @@ match x {
let mut fmt = Formatter::new(Language::Rust);
fmt.add_match(m);

let expected_lines = from_raw_string(
r#"
match x {
let expected_lines = r#"match x {
Green { a, b } => {
different body
}
Expand All @@ -457,8 +433,7 @@ match x {
unreachable!()
}
}
"#,
);
"#;
assert_eq!(fmt.lines, expected_lines);
}

Expand All @@ -483,7 +458,7 @@ match x {
" // Nested comment\n",
"Back home again\n",
];
assert_eq!(fmt.lines, expected_lines);
assert_eq!(fmt.lines, expected_lines.join(""));
}

#[test]
Expand All @@ -509,7 +484,7 @@ match x {
fn fmt_can_add_type_to_lines() {
let mut fmt = Formatter::new(Language::Rust);
fmt.line(format!("pub const {}: Type = Type({:#x});", "example", 0));
let expected_lines = vec!["pub const example: Type = Type(0x0);\n"];
let expected_lines = "pub const example: Type = Type(0x0);\n";
assert_eq!(fmt.lines, expected_lines);
}

Expand All @@ -520,15 +495,15 @@ match x {
fmt.indent_push();
fmt.line("world");
let expected_lines = vec!["hello\n", " world\n"];
assert_eq!(fmt.lines, expected_lines);
assert_eq!(fmt.lines, expected_lines.join(""));
}

#[test]
fn fmt_can_add_doc_comments() {
let mut fmt = Formatter::new(Language::Rust);
fmt.doc_comment("documentation\nis\ngood");
let expected_lines = vec!["/// documentation\n", "/// is\n", "/// good\n"];
assert_eq!(fmt.lines, expected_lines);
assert_eq!(fmt.lines, expected_lines.join(""));
}

#[test]
Expand All @@ -541,13 +516,11 @@ match x {
If you stick to writing it.
"#,
);
let expected_lines = from_raw_string(
r#"
/// documentation
let expected_lines = r#"/// documentation
/// can be really good.
///
/// If you stick to writing it."#,
);
/// If you stick to writing it.
"#;
assert_eq!(fmt.lines, expected_lines);
}
}
Loading