use std::fmt::{self, Display};
pub struct PtxWriter {
buf: String,
indent_level: usize,
label_counter: u32,
}
const INDENT: &str = " ";
impl Default for PtxWriter {
fn default() -> Self {
Self::new()
}
}
impl PtxWriter {
pub fn new() -> Self {
Self {
buf: String::new(),
indent_level: 0,
label_counter: 0,
}
}
pub fn line(&mut self, s: &str) -> fmt::Result {
for _ in 0..self.indent_level {
self.buf.push_str(INDENT);
}
self.buf.push_str(s);
self.buf.push('\n');
Ok(())
}
pub fn raw_line(&mut self, s: &str) -> fmt::Result {
self.buf.push_str(s);
self.buf.push('\n');
Ok(())
}
pub fn blank(&mut self) -> fmt::Result {
self.buf.push('\n');
Ok(())
}
pub fn indent(&mut self) {
self.indent_level += 1;
}
pub fn dedent(&mut self) {
self.indent_level = self.indent_level.saturating_sub(1);
}
pub fn instruction(&mut self, mnemonic: &str, operands: &[&dyn Display]) -> fmt::Result {
for _ in 0..self.indent_level {
self.buf.push_str(INDENT);
}
self.buf.push_str(mnemonic);
if !operands.is_empty() {
self.buf.push(' ');
for (i, op) in operands.iter().enumerate() {
if i > 0 {
self.buf.push_str(", ");
}
self.buf.push_str(&op.to_string());
}
}
self.buf.push_str(";\n");
Ok(())
}
pub fn fresh_label(&mut self, prefix: &str) -> String {
let label = format!("{prefix}_{}", self.label_counter);
self.label_counter += 1;
label
}
pub fn finish(self) -> String {
self.buf
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn line_with_no_indent() {
let mut w = PtxWriter::new();
w.line(".version 7.8").unwrap();
assert_eq!(w.finish(), ".version 7.8\n");
}
#[test]
fn line_with_indent() {
let mut w = PtxWriter::new();
w.indent();
w.line("ret;").unwrap();
assert_eq!(w.finish(), " ret;\n");
}
#[test]
fn indent_dedent() {
let mut w = PtxWriter::new();
w.indent();
w.indent();
w.line("deep").unwrap();
w.dedent();
w.line("shallow").unwrap();
w.dedent();
w.line("top").unwrap();
assert_eq!(w.finish(), " deep\n shallow\ntop\n");
}
#[test]
fn dedent_saturates_at_zero() {
let mut w = PtxWriter::new();
w.dedent(); w.line("ok").unwrap();
assert_eq!(w.finish(), "ok\n");
}
#[test]
fn blank_line() {
let mut w = PtxWriter::new();
w.line("a").unwrap();
w.blank().unwrap();
w.line("b").unwrap();
assert_eq!(w.finish(), "a\n\nb\n");
}
#[test]
fn instruction_formatting() {
let mut w = PtxWriter::new();
w.indent();
let a = "%f0";
let b = "%f1";
let c = "%f2";
w.instruction(".add.f32", &[&a, &b, &c]).unwrap();
assert_eq!(w.finish(), " .add.f32 %f0, %f1, %f2;\n");
}
#[test]
fn instruction_no_operands() {
let mut w = PtxWriter::new();
w.indent();
w.instruction("ret", &[]).unwrap();
assert_eq!(w.finish(), " ret;\n");
}
#[test]
fn fresh_label_uniqueness() {
let mut w = PtxWriter::new();
let l0 = w.fresh_label("EXIT");
let l1 = w.fresh_label("LOOP");
let l2 = w.fresh_label("EXIT");
assert_eq!(l0, "EXIT_0");
assert_eq!(l1, "LOOP_1");
assert_eq!(l2, "EXIT_2");
}
}