[build] Implement registers code generation in the Rust meta crate;

This commit is contained in:
Benjamin Bouvier
2018-10-16 16:42:40 +02:00
committed by Dan Gohman
parent 4f2d7dd54f
commit b7f2acf0ea
15 changed files with 714 additions and 51 deletions

View File

@@ -24,7 +24,11 @@ use meta::isa::Isa;
use std::env; use std::env;
use std::process; use std::process;
use std::time::Instant;
fn main() { fn main() {
let start_time = Instant::now();
let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set");
let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set"); let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set");
let cranelift_targets = env::var("CRANELIFT_TARGETS").ok(); let cranelift_targets = env::var("CRANELIFT_TARGETS").ok();
@@ -35,7 +39,7 @@ fn main() {
match isa_targets(cranelift_targets, &target_triple) { match isa_targets(cranelift_targets, &target_triple) {
Ok(isa_targets) => { Ok(isa_targets) => {
for isa in &isa_targets { for isa in &isa_targets {
println!("cargo:rustc-cfg=build_{}", isa.name()); println!("cargo:rustc-cfg=build_{}", isa.to_string());
} }
} }
Err(err) => { Err(err) => {
@@ -44,8 +48,6 @@ fn main() {
} }
} }
println!("Build script generating files in {}", out_dir);
let cur_dir = env::current_dir().expect("Can't access current working directory"); let cur_dir = env::current_dir().expect("Can't access current working directory");
let crate_dir = cur_dir.as_path(); let crate_dir = cur_dir.as_path();
@@ -81,10 +83,28 @@ fn main() {
// Now that the Python build process is complete, generate files that are // Now that the Python build process is complete, generate files that are
// emitted by the `meta` crate. // emitted by the `meta` crate.
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
let isas = meta::isa::define_all();
if let Err(err) = meta::gen_types::generate("types.rs", &out_dir) { if let Err(err) = meta::gen_types::generate("types.rs", &out_dir) {
eprintln!("Error: {}", err); eprintln!("Error: {}", err);
process::exit(1); process::exit(1);
} }
for isa in isas {
if let Err(err) = meta::gen_registers::generate(isa, "new_registers", &out_dir) {
eprintln!("Error: {}", err);
process::exit(1);
}
}
println!(
"cargo:warning=Cranelift meta-build step took {:?}",
Instant::now() - start_time
);
println!(
"cargo:warning=Meta-build script generated files in {}",
out_dir
);
} }
fn identify_python() -> &'static str { fn identify_python() -> &'static str {

View File

@@ -87,7 +87,6 @@ class RegBank(object):
self.names = names self.names = names
self.classes = list() # type: List[RegClass] self.classes = list() # type: List[RegClass]
self.toprcs = list() # type: List[RegClass] self.toprcs = list() # type: List[RegClass]
self.first_toprc_index = None # type: int
assert len(names) <= units assert len(names) <= units
@@ -248,7 +247,7 @@ class RegClass(object):
def intersect(self, other): def intersect(self, other):
# type: (RegClass) -> RCTup # type: (RegClass) -> RCTup
""" """
Get a tuple representing the intersction of two register classes. Get a tuple representing the intersection of two register classes.
Returns `None` if the two classes are disjoint. Returns `None` if the two classes are disjoint.
""" """

View File

@@ -7,6 +7,9 @@ license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/CraneStation/cranelift" repository = "https://github.com/CraneStation/cranelift"
readme = "README.md" readme = "README.md"
[dependencies]
cranelift-entity = { path = "../../entity", version = "0.22.0", default-features = false }
[badges] [badges]
maintenance = { status = "experimental" } maintenance = { status = "experimental" }
travis-ci = { repository = "CraneStation/cranelift" } travis-ci = { repository = "CraneStation/cranelift" }

View File

@@ -0,0 +1,142 @@
use cranelift_entity::PrimaryMap;
use super::regs::{
RegBank, RegBankBuilder, RegBankIndex, RegClass, RegClassBuilder, RegClassIndex,
};
pub struct TargetIsa {
pub name: &'static str,
pub reg_banks: PrimaryMap<RegBankIndex, RegBank>,
pub reg_classes: PrimaryMap<RegClassIndex, RegClass>,
}
impl TargetIsa {
pub fn new(name: &'static str) -> Self {
Self {
name,
reg_banks: PrimaryMap::new(),
reg_classes: PrimaryMap::new(),
}
}
pub fn add_reg_bank(&mut self, builder: RegBankBuilder) -> RegBankIndex {
let first_unit = if self.reg_banks.len() == 0 {
0
} else {
let last = &self.reg_banks.last().unwrap();
let first_available_unit = (last.first_unit + last.units) as i8;
let units = builder.units;
let align = if units.is_power_of_two() {
units
} else {
units.next_power_of_two()
} as i8;
(first_available_unit + align - 1) & -align
} as u8;
self.reg_banks.push(RegBank::new(
builder.name,
first_unit,
builder.units,
builder.names,
builder.prefix,
builder
.pressure_tracking
.expect("Pressure tracking must be explicitly set"),
))
}
pub fn add_reg_class(&mut self, builder: RegClassBuilder) -> RegClassIndex {
let reg_bank_units = self.reg_banks.get(builder.bank).unwrap().units;
let start = builder.start;
assert!(start < reg_bank_units);
let count = if builder.count != 0 {
builder.count
} else {
reg_bank_units / builder.width
};
let reg_class_index = builder.index;
assert!(
self.reg_classes.next_key() == reg_class_index,
"should have inserted RegClass where expected"
);
let reg_class = RegClass::new(
builder.name,
reg_class_index,
builder.width,
builder.bank,
builder.toprc,
count,
start,
);
self.reg_classes.push(reg_class);
let reg_bank = self.reg_banks.get_mut(builder.bank).unwrap();
reg_bank.classes.push(reg_class_index);
reg_class_index
}
/// Checks that the set of register classes satisfies:
///
/// 1. Closed under intersection: The intersection of any two register
/// classes in the set is either empty or identical to a member of the
/// set.
/// 2. There are no identical classes under different names.
/// 3. Classes are sorted topologically such that all subclasses have a
/// higher index that the superclass.
pub fn check(&self) {
for reg_bank in self.reg_banks.values() {
for i1 in reg_bank.classes.iter() {
for i2 in reg_bank.classes.iter() {
if i1 >= i2 {
continue;
}
let rc1 = self.reg_classes.get(*i1).unwrap();
let rc2 = self.reg_classes.get(*i2).unwrap();
let rc1_mask = rc1.mask(0);
let rc2_mask = rc2.mask(0);
assert!(
rc1.width != rc2.width || rc1_mask != rc2_mask,
"no duplicates"
);
if rc1.width != rc2.width {
continue;
}
let mut intersect = Vec::new();
for (a, b) in rc1_mask.iter().zip(rc2_mask.iter()) {
intersect.push(a & b);
}
if intersect == vec![0; intersect.len()] {
continue;
}
// Classes must be topologically ordered, so the intersection can't be the
// superclass.
assert!(intersect != rc1_mask);
// If the intersection is the second one, then it must be a subclass.
if intersect == rc2_mask {
assert!(
self.reg_classes
.get(*i1)
.unwrap()
.subclasses
.iter()
.find(|x| **x == *i2)
.is_some()
);
}
}
}
}
}
}

View File

@@ -3,6 +3,8 @@
//! This module defines the classes that are used to define Cranelift //! This module defines the classes that are used to define Cranelift
//! instructions and other entities. //! instructions and other entities.
pub mod isa;
pub mod regs;
pub mod types; pub mod types;
/// Convert the string `s` to CamelCase. /// Convert the string `s` to CamelCase.

View File

@@ -0,0 +1,199 @@
use cranelift_entity::EntityRef;
use super::isa::TargetIsa;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RegBankIndex(u32);
entity_impl!(RegBankIndex);
pub struct RegBank {
pub name: &'static str,
pub first_unit: u8,
pub units: u8,
pub names: Vec<&'static str>,
pub prefix: &'static str,
pub pressure_tracking: bool,
pub toprcs: Vec<RegClassIndex>,
pub classes: Vec<RegClassIndex>,
}
impl RegBank {
pub fn new(
name: &'static str,
first_unit: u8,
units: u8,
names: Vec<&'static str>,
prefix: &'static str,
pressure_tracking: bool,
) -> Self {
RegBank {
name,
first_unit,
units,
names,
prefix,
pressure_tracking,
toprcs: Vec::new(),
classes: Vec::new(),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RegClassIndex(u32);
entity_impl!(RegClassIndex);
pub struct RegClass {
pub name: &'static str,
pub index: RegClassIndex,
pub width: u8,
pub bank: RegBankIndex,
pub toprc: RegClassIndex,
pub count: u8,
pub start: u8,
pub subclasses: Vec<RegClassIndex>,
}
impl RegClass {
pub fn new(
name: &'static str,
index: RegClassIndex,
width: u8,
bank: RegBankIndex,
toprc: RegClassIndex,
count: u8,
start: u8,
) -> Self {
Self {
name,
index,
width,
bank,
toprc,
count,
start,
subclasses: Vec::new(),
}
}
/// Compute a bit-mask of subclasses, including self.
pub fn subclass_mask(&self) -> u64 {
let mut m = 1 << self.index.index();
for rc in self.subclasses.iter() {
m |= 1 << rc.index();
}
m
}
/// Compute a bit-mask of the register units allocated by this register class.
pub fn mask(&self, bank_first_unit: u8) -> Vec<u32> {
let mut u = (self.start + bank_first_unit) as usize;
let mut out_mask = vec![0, 0, 0];
for _ in 0..self.count {
out_mask[u / 32] |= 1 << (u % 32);
u += self.width as usize;
}
out_mask
}
}
pub struct RegClassBuilder {
pub name: &'static str,
pub index: RegClassIndex,
pub width: u8,
pub bank: RegBankIndex,
pub toprc: RegClassIndex,
pub count: u8,
pub start: u8,
}
impl RegClassBuilder {
pub fn new_toplevel(isa: &mut TargetIsa, name: &'static str, bank: RegBankIndex) -> Self {
let index = isa.reg_classes.next_key();
// Add it to the top-level register classes of the register bank.
isa.reg_banks.get_mut(bank).unwrap().toprcs.push(index);
Self {
name,
index,
width: 1,
bank,
toprc: index,
count: 0,
start: 0,
}
}
pub fn subclass_of(
isa: &mut TargetIsa,
name: &'static str,
parent_index: RegClassIndex,
start: u8,
stop: u8,
) -> Self {
assert!(stop >= start);
let index = isa.reg_classes.next_key();
let toprc = isa.reg_classes.get(parent_index).unwrap().toprc;
for reg_class in isa.reg_classes.values_mut() {
if reg_class.toprc == toprc {
reg_class.subclasses.push(index);
}
}
let parent = &isa.reg_classes.get(parent_index).unwrap();
Self {
name,
count: stop - start,
width: parent.width,
start: parent.start + start * parent.width,
bank: parent.bank,
toprc: parent.toprc,
index,
}
}
pub fn count(mut self, count: u8) -> Self {
self.count = count;
self
}
pub fn width(mut self, width: u8) -> Self {
self.width = width;
self
}
}
pub struct RegBankBuilder {
pub name: &'static str,
pub units: u8,
pub names: Vec<&'static str>,
pub prefix: &'static str,
pub pressure_tracking: Option<bool>,
}
impl RegBankBuilder {
pub fn new(name: &'static str, prefix: &'static str) -> Self {
Self {
name,
units: 0,
names: vec![],
prefix,
pressure_tracking: None,
}
}
pub fn units(mut self, units: u8) -> Self {
self.units = units;
self
}
pub fn names(mut self, names: Vec<&'static str>) -> Self {
self.names = names;
self
}
pub fn track_pressure(mut self, track: bool) -> Self {
self.pressure_tracking = Some(track);
self
}
}

View File

@@ -0,0 +1,140 @@
use cdsl::isa::TargetIsa;
use cdsl::regs::{RegBank, RegClass};
use cranelift_entity::EntityRef;
use error;
use srcgen::Formatter;
fn gen_regbank(fmt: &mut Formatter, reg_bank: &RegBank) {
let names = if reg_bank.names.len() > 0 {
format!(r#""{}""#, reg_bank.names.join(r#"", ""#))
} else {
"".to_string()
};
fmt.line("RegBank {");
fmt.indent(|fmt| {
fmt.line(&format!(r#"name: "{}","#, reg_bank.name));
fmt.line(&format!("first_unit: {},", reg_bank.first_unit));
fmt.line(&format!("units: {},", reg_bank.units));
fmt.line(&format!("names: &[{}],", names));
fmt.line(&format!(r#"prefix: "{}","#, reg_bank.prefix));
fmt.line(&format!("first_toprc: {},", reg_bank.toprcs[0].index()));
fmt.line(&format!("num_toprcs: {},", reg_bank.toprcs.len()));
fmt.line(&format!(
"pressure_tracking: {},",
if reg_bank.pressure_tracking {
"true"
} else {
"false"
}
));
});
fmt.line("},");
}
fn gen_regclass(isa: &TargetIsa, reg_class: &RegClass, fmt: &mut Formatter) {
let reg_bank = isa.reg_banks.get(reg_class.bank).unwrap();
let mask: Vec<String> = reg_class
.mask(reg_bank.first_unit)
.iter()
.map(|x| format!("0x{:08x}", x))
.collect();
let mask = mask.join(", ");
fmt.line(&format!(
"pub static {}_DATA: RegClassData = RegClassData {{",
reg_class.name
));
fmt.indent(|fmt| {
fmt.line(&format!(r#"name: "{}","#, reg_class.name));
fmt.line(&format!("index: {},", reg_class.index.index()));
fmt.line(&format!("width: {},", reg_class.width));
fmt.line(&format!("bank: {},", reg_class.bank.index()));
fmt.line(&format!("toprc: {},", reg_class.toprc.index()));
fmt.line(&format!(
"first: {},",
reg_bank.first_unit + reg_class.start
));
fmt.line(&format!("subclasses: {:#x},", reg_class.subclass_mask()));
fmt.line(&format!("mask: [{}],", mask));
fmt.line("info: &INFO,");
});
fmt.line("};");
fmt.line("#[allow(dead_code)]");
fmt.line(&format!(
"pub static {}: RegClass = &{}_DATA;",
reg_class.name, reg_class.name
));
}
fn gen_regbank_units(reg_bank: &RegBank, fmt: &mut Formatter) {
for unit in 0..reg_bank.units {
let v = unit + reg_bank.first_unit;
if (unit as usize) < reg_bank.names.len() {
fmt.line(&format!("{} = {},", reg_bank.names[unit as usize], v));
continue;
}
fmt.line(&format!("{}{} = {},", reg_bank.prefix, unit, v));
}
}
fn gen_isa(isa: &TargetIsa, fmt: &mut Formatter) -> Result<(), error::Error> {
// Emit RegInfo.
fmt.line("pub static INFO: RegInfo = RegInfo {");
fmt.indent(|fmt| {
fmt.line("banks: &[");
// Bank descriptors.
fmt.indent(|fmt| {
for reg_bank in isa.reg_banks.values() {
gen_regbank(fmt, &reg_bank);
}
});
fmt.line("],");
// References to register classes.
fmt.line("classes: &[");
fmt.indent(|fmt| {
for reg_class in isa.reg_classes.values() {
fmt.line(&format!("&{}_DATA,", reg_class.name));
}
});
fmt.line("],");
});
fmt.line("};");
// Register class descriptors.
for rc in isa.reg_classes.values() {
gen_regclass(&isa, rc, fmt);
}
// Emit constants for all the register units.
fmt.line("#[allow(dead_code, non_camel_case_types)]");
fmt.line("#[derive(Clone, Copy)]");
fmt.line("pub enum RU {");
fmt.indent(|fmt| {
for reg_bank in isa.reg_banks.values() {
gen_regbank_units(reg_bank, fmt);
}
});
fmt.line("}");
// Emit Into conversion for the RU class.
fmt.line("impl Into<RegUnit> for RU {");
fmt.indent(|fmt| {
fmt.line("fn into(self) -> RegUnit {");
fmt.indent(|fmt| {
fmt.line("self as RegUnit");
});
fmt.line("}")
});
fmt.line("}");
Ok(())
}
pub fn generate(isa: TargetIsa, base_filename: &str, out_dir: &str) -> Result<(), error::Error> {
let mut fmt = Formatter::new();
gen_isa(&isa, &mut fmt)?;
fmt.update_file(&format!("{}-{}.rs", base_filename, isa.name), out_dir)?;
Ok(())
}

View File

@@ -0,0 +1,39 @@
use cdsl::regs::{RegBankBuilder, RegClassBuilder};
use isa;
pub fn define() -> isa::TargetIsa {
let mut isa = isa::TargetIsa::new("arm32");
let builder = RegBankBuilder::new("FloatRegs", "s")
.units(64)
.track_pressure(true);
let float_regs = isa.add_reg_bank(builder);
let builder = RegBankBuilder::new("IntRegs", "r")
.units(16)
.track_pressure(true);
let int_regs = isa.add_reg_bank(builder);
let builder = RegBankBuilder::new("FlagRegs", "")
.units(1)
.names(vec!["nzcv"])
.track_pressure(false);
let flag_reg = isa.add_reg_bank(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "S", float_regs).count(32);
isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "D", float_regs).width(2);
isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "Q", float_regs).width(4);
isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "GPR", int_regs);
isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "FLAG", flag_reg);
isa.add_reg_class(builder);
isa
}

View File

@@ -0,0 +1,35 @@
use cdsl::regs::{RegBankBuilder, RegClassBuilder};
use isa;
pub fn define() -> isa::TargetIsa {
let mut isa = isa::TargetIsa::new("arm64");
// The `x31` regunit serves as the stack pointer / zero register depending on context. We
// reserve it and don't model the difference.
let builder = RegBankBuilder::new("IntRegs", "x")
.units(32)
.track_pressure(true);
let int_regs = isa.add_reg_bank(builder);
let builder = RegBankBuilder::new("FloatRegs", "v")
.units(32)
.track_pressure(true);
let float_regs = isa.add_reg_bank(builder);
let builder = RegBankBuilder::new("FlagRegs", "")
.units(1)
.names(vec!["nzcv"])
.track_pressure(false);
let flag_reg = isa.add_reg_bank(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "GPR", int_regs);
isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "FPR", float_regs);
isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "FLAG", flag_reg);
isa.add_reg_class(builder);
isa
}

View File

@@ -1,3 +1,11 @@
use cdsl::isa::TargetIsa;
use std::fmt;
mod arm32;
mod arm64;
mod riscv;
mod x86;
/// Represents known ISA target. /// Represents known ISA target.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum Isa { pub enum Isa {
@@ -13,7 +21,7 @@ impl Isa {
Isa::all() Isa::all()
.iter() .iter()
.cloned() .cloned()
.filter(|isa| isa.name() == name) .filter(|isa| isa.to_string() == name)
.next() .next()
} }
@@ -31,16 +39,6 @@ impl Isa {
[Isa::Riscv, Isa::X86, Isa::Arm32, Isa::Arm64] [Isa::Riscv, Isa::X86, Isa::Arm32, Isa::Arm64]
} }
/// Returns name of the isa target.
pub fn name(&self) -> &'static str {
match *self {
Isa::Riscv => "riscv",
Isa::X86 => "x86",
Isa::Arm32 => "arm32",
Isa::Arm64 => "arm64",
}
}
/// Checks if arch is applicable for the isa target. /// Checks if arch is applicable for the isa target.
fn is_arch_applicable(&self, arch: &str) -> bool { fn is_arch_applicable(&self, arch: &str) -> bool {
match *self { match *self {
@@ -51,3 +49,27 @@ impl Isa {
} }
} }
} }
impl fmt::Display for Isa {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Isa::Riscv => write!(f, "riscv"),
Isa::X86 => write!(f, "x86"),
Isa::Arm32 => write!(f, "arm32"),
Isa::Arm64 => write!(f, "arm64"),
}
}
}
pub fn define_all() -> Vec<TargetIsa> {
let isas = vec![
riscv::define(),
arm32::define(),
arm64::define(),
x86::define(),
];
for isa in isas.iter() {
isa.check();
}
isas
}

View File

@@ -0,0 +1,24 @@
use cdsl::regs::{RegBankBuilder, RegClassBuilder};
use isa;
pub fn define() -> isa::TargetIsa {
let mut isa = isa::TargetIsa::new("riscv");
let builder = RegBankBuilder::new("IntRegs", "x")
.units(32)
.track_pressure(true);
let int_regs = isa.add_reg_bank(builder);
let builder = RegBankBuilder::new("FloatRegs", "f")
.units(32)
.track_pressure(true);
let float_regs = isa.add_reg_bank(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "GPR", int_regs);
isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "FPR", float_regs);
isa.add_reg_class(builder);
isa
}

View File

@@ -0,0 +1,43 @@
use cdsl::regs::{RegBankBuilder, RegClassBuilder};
use isa;
pub fn define() -> isa::TargetIsa {
let mut isa = isa::TargetIsa::new("x86");
let builder = RegBankBuilder::new("IntRegs", "r")
.units(16)
.names(vec!["rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi"])
.track_pressure(true);
let int_regs = isa.add_reg_bank(builder);
let builder = RegBankBuilder::new("FloatRegs", "xmm")
.units(16)
.track_pressure(true);
let float_regs = isa.add_reg_bank(builder);
let builder = RegBankBuilder::new("FlagRegs", "")
.units(1)
.names(vec!["rflags"])
.track_pressure(false);
let flag_reg = isa.add_reg_bank(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "GPR", int_regs);
let gpr = isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "FPR", float_regs);
let fpr = isa.add_reg_class(builder);
let builder = RegClassBuilder::new_toplevel(&mut isa, "FLAG", flag_reg);
isa.add_reg_class(builder);
let builder = RegClassBuilder::subclass_of(&mut isa, "GPR8", gpr, 0, 8);
let gpr8 = isa.add_reg_class(builder);
let builder = RegClassBuilder::subclass_of(&mut isa, "ABCD", gpr8, 0, 4);
isa.add_reg_class(builder);
let builder = RegClassBuilder::subclass_of(&mut isa, "FPR8", fpr, 0, 8);
isa.add_reg_class(builder);
isa
}

View File

@@ -1,4 +1,8 @@
#[macro_use]
extern crate cranelift_entity;
pub mod error; pub mod error;
pub mod gen_registers;
pub mod gen_types; pub mod gen_types;
pub mod isa; pub mod isa;

View File

@@ -12,24 +12,6 @@ use error;
static SHIFTWIDTH: usize = 4; static SHIFTWIDTH: usize = 4;
struct _IndentedScope {
fmt: Formatter,
after: Option<String>,
}
impl _IndentedScope {
fn _enter(&mut self) {
self.fmt._indent_push();
}
fn _exit(&mut self) {
self.fmt._indent_pop();
if let Some(ref s) = self.after {
self.fmt.line(&s);
}
}
}
pub struct Formatter { pub struct Formatter {
indent: usize, indent: usize,
lines: Vec<String>, lines: Vec<String>,
@@ -46,16 +28,23 @@ impl Formatter {
} }
/// Increase current indentation level by one. /// Increase current indentation level by one.
pub fn _indent_push(&mut self) { pub fn indent_push(&mut self) {
self.indent += 1; self.indent += 1;
} }
/// Decrease indentation by one level. /// Decrease indentation by one level.
pub fn _indent_pop(&mut self) { pub fn indent_pop(&mut self) {
assert!(self.indent > 0, "Already at top level indentation"); assert!(self.indent > 0, "Already at top level indentation");
self.indent -= 1; 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. /// Get the current whitespace indentation in the form of a String.
fn get_indent(&self) -> String { fn get_indent(&self) -> String {
if self.indent == 0 { if self.indent == 0 {
@@ -68,9 +57,9 @@ impl Formatter {
/// Get a string containing whitespace outdented one level. Used for /// Get a string containing whitespace outdented one level. Used for
/// lines of code that are inside a single indented block. /// lines of code that are inside a single indented block.
fn _get_outdent(&mut self) -> String { fn _get_outdent(&mut self) -> String {
self._indent_push(); self.indent_push();
let s = self.get_indent(); let s = self.get_indent();
self._indent_pop(); self.indent_pop();
s s
} }
@@ -103,13 +92,6 @@ impl Formatter {
Ok(()) Ok(())
} }
/// Return a scope object for use with a `with` statement.
/// The optional `before` and `after` parameters are surrounding lines
/// which are *not* indented.
fn _indented(&self, _before: Option<&str>, _after: Option<&str>) -> _IndentedScope {
unimplemented!();
}
/// 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));
@@ -158,7 +140,6 @@ fn parse_multiline(s: &str) -> Vec<String> {
.iter() .iter()
.skip(1) .skip(1)
.map(|l| l.len() - l.trim_left().len()) .map(|l| l.len() - l.trim_left().len())
.filter(|&i| i > 0)
.min(); .min();
// Strip off leading blank lines. // Strip off leading blank lines.
@@ -257,9 +238,9 @@ mod srcgen_tests {
fn formatter_basic_example_works() { fn formatter_basic_example_works() {
let mut fmt = Formatter::new(); let mut fmt = Formatter::new();
fmt.line("Hello line 1"); fmt.line("Hello line 1");
fmt._indent_push(); fmt.indent_push();
fmt._comment("Nested comment"); fmt._comment("Nested comment");
fmt._indent_pop(); fmt.indent_pop();
fmt.line("Back home again"); fmt.line("Back home again");
let expected_lines = vec![ let expected_lines = vec![
"Hello line 1\n", "Hello line 1\n",
@@ -277,9 +258,9 @@ mod srcgen_tests {
let actual_results = Vec::with_capacity(4); let actual_results = Vec::with_capacity(4);
(0..3).for_each(|_| { (0..3).for_each(|_| {
fmt.get_indent(); fmt.get_indent();
fmt._indent_push(); fmt.indent_push();
}); });
(0..3).for_each(|_| fmt._indent_pop()); (0..3).for_each(|_| fmt.indent_pop());
fmt.get_indent(); fmt.get_indent();
actual_results actual_results
@@ -300,7 +281,7 @@ mod srcgen_tests {
fn fmt_can_add_indented_line() { fn fmt_can_add_indented_line() {
let mut fmt = Formatter::new(); let mut fmt = Formatter::new();
fmt.line("hello"); fmt.line("hello");
fmt._indent_push(); fmt.indent_push();
fmt.line("world"); fmt.line("world");
let expected_lines = vec!["hello\n", " world\n"]; let expected_lines = vec!["hello\n", " world\n"];
assert_eq!(fmt.lines, expected_lines); assert_eq!(fmt.lines, expected_lines);

View File

@@ -50,6 +50,11 @@ where
self.elems.get(k.index()) self.elems.get(k.index())
} }
/// Get the element at `k` if it exists, mutable version.
pub fn get_mut(&mut self, k: K) -> Option<&mut V> {
self.elems.get_mut(k.index())
}
/// Is this map completely empty? /// Is this map completely empty?
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.elems.is_empty() self.elems.is_empty()
@@ -101,6 +106,11 @@ where
self.elems.push(v); self.elems.push(v);
k k
} }
/// Returns the last element that was inserted in the map.
pub fn last(&self) -> Option<&V> {
self.elems.last()
}
} }
/// Immutable indexing into an `PrimaryMap`. /// Immutable indexing into an `PrimaryMap`.