use std::collections::BTreeMap;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Ref(pub u64);
impl std::fmt::Display for Ref {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "#{}", self.0)
}
}
pub struct Emitter {
next: u64,
lines: Vec<String>,
counts: BTreeMap<String, usize>,
interned: HashMap<String, Ref>,
}
impl Emitter {
pub fn new() -> Self {
Emitter {
next: 1,
lines: Vec::new(),
counts: BTreeMap::new(),
interned: HashMap::new(),
}
}
pub fn emit(&mut self, type_: &str, params: &str) -> Ref {
let id = self.next;
self.next += 1;
self.lines.push(format!("#{id} = {type_}({params});"));
*self.counts.entry(type_.to_string()).or_insert(0) += 1;
Ref(id)
}
pub fn emit_raw(&mut self, tally: &str, body: &str) -> Ref {
let id = self.next;
self.next += 1;
self.lines.push(format!("#{id} = {body};"));
*self.counts.entry(tally.to_string()).or_insert(0) += 1;
Ref(id)
}
pub fn emit_interned(&mut self, type_: &str, params: &str) -> Ref {
let key = format!("{type_}|{params}");
if let Some(r) = self.interned.get(&key) {
return *r;
}
let r = self.emit(type_, params);
self.interned.insert(key, r);
r
}
pub fn counts(&self) -> &BTreeMap<String, usize> {
&self.counts
}
pub fn total(&self) -> usize {
self.lines.len()
}
pub fn into_lines(self) -> Vec<String> {
self.lines
}
}
pub fn real(v: f64) -> String {
if !v.is_finite() {
return "0.".to_string();
}
let mut s = format!("{v}");
if let Some(e_pos) = s.find(['e', 'E']) {
let (mantissa, exp) = s.split_at(e_pos);
let exp = &exp[1..];
let mantissa = if mantissa.contains('.') {
mantissa.to_string()
} else {
format!("{mantissa}.")
};
let exp = if let Some(rest) = exp.strip_prefix('-') {
format!("-{rest}")
} else {
exp.strip_prefix('+').unwrap_or(exp).to_string()
};
return format!("{mantissa}E{exp}");
}
if !s.contains('.') {
s.push('.');
}
s
}
pub fn string(s: &str) -> String {
use std::fmt::Write as _;
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for ch in s.chars() {
if ch == '\'' {
out.push_str("''");
} else if ch.is_ascii() && !ch.is_ascii_control() {
out.push(ch);
} else {
out.push_str("\\X2\\");
let mut buf = [0u16; 2];
for cu in ch.encode_utf16(&mut buf) {
let _ = write!(out, "{cu:04X}");
}
out.push_str("\\X0\\");
}
}
out.push('\'');
out
}
pub fn refs(items: &[Ref]) -> String {
let mut out = String::from("(");
for (i, r) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push_str(&r.to_string());
}
out.push(')');
out
}