use crate::ast::Value;
use crate::derive_support::PolydatSetup;
#[derive(Debug, Clone)]
pub enum Segment {
Literal(String),
Placeholder(FormatSpec),
}
#[derive(Debug, Clone)]
pub struct FormatSpec {
index: usize,
width: Option<usize>,
precision: Option<usize>,
fill: char,
conversion: char,
}
#[derive(Debug, Clone)]
pub struct ParsedFormat {
segments: Vec<Segment>,
}
impl PolydatSetup for ParsedFormat {}
#[crate::polydat_node(category = Formatting)]
fn printf(
format: Const<&str>,
#[poly_const(ParsedFormat::from_format_str, from = format)]
parsed: &ParsedFormat,
parts: &[polydat::ast::Value],
) -> String {
let mut result = String::new();
for seg in &parsed.segments {
match seg {
Segment::Literal(s) => result.push_str(s),
Segment::Placeholder(spec) => {
let val = match parts.get(spec.index) {
Some(v) => v,
None => panic!(
"printf: format references input #{} but only {} wire input(s) supplied",
spec.index,
parts.len(),
),
};
let formatted = format_value(val, spec);
result.push_str(&formatted);
}
}
}
result
}
impl ParsedFormat {
pub fn from_format_str(fmt: &str) -> Self {
Self { segments: parse_format(fmt) }
}
}
fn format_value(val: &Value, spec: &FormatSpec) -> String {
match val {
Value::U64(v) => format_u64(*v, spec),
Value::F64(v) => format_f64(*v, spec),
Value::Bool(v) => v.to_string(),
Value::Str(v) => {
if let Some(w) = spec.width {
format!("{:>width$}", v, width = w)
} else {
v.to_string()
}
}
_ => format!("{val:?}"),
}
}
fn format_u64(v: u64, spec: &FormatSpec) -> String {
let raw = match spec.conversion {
'x' => format!("{v:x}"),
'X' => format!("{v:X}"),
'b' => format!("{v:b}"),
'o' => format!("{v:o}"),
_ => v.to_string(),
};
apply_width(&raw, spec)
}
fn format_f64(v: f64, spec: &FormatSpec) -> String {
let raw = if let Some(prec) = spec.precision {
format!("{v:.prec$}")
} else {
format!("{v:?}")
};
apply_width(&raw, spec)
}
fn apply_width(s: &str, spec: &FormatSpec) -> String {
if let Some(w) = spec.width {
if s.len() < w {
let pad = w - s.len();
let fill = spec.fill;
format!("{}{s}", std::iter::repeat_n(fill, pad).collect::<String>())
} else {
s.to_string()
}
} else {
s.to_string()
}
}
fn parse_format(fmt: &str) -> Vec<Segment> {
let mut segments = Vec::new();
let mut literal = String::new();
let chars: Vec<char> = fmt.chars().collect();
let mut i = 0;
let mut placeholder_idx = 0;
while i < chars.len() {
if chars[i] == '{' && i + 1 < chars.len() && chars[i + 1] == '{' {
literal.push('{');
i += 2;
} else if chars[i] == '{' {
if !literal.is_empty() {
segments.push(Segment::Literal(std::mem::take(&mut literal)));
}
let start = i + 1;
while i < chars.len() && chars[i] != '}' {
i += 1;
}
let spec_str: String = chars[start..i].iter().collect();
let spec = parse_spec(&spec_str, placeholder_idx);
segments.push(Segment::Placeholder(spec));
placeholder_idx += 1;
i += 1; } else if chars[i] == '}' && i + 1 < chars.len() && chars[i + 1] == '}' {
literal.push('}');
i += 2;
} else {
literal.push(chars[i]);
i += 1;
}
}
if !literal.is_empty() {
segments.push(Segment::Literal(literal));
}
segments
}
fn parse_spec(spec: &str, index: usize) -> FormatSpec {
let mut result = FormatSpec {
index,
width: None,
precision: None,
fill: ' ',
conversion: 'd',
};
if spec.is_empty() {
return result;
}
let spec = spec.strip_prefix(':').unwrap_or(spec);
if spec.is_empty() {
return result;
}
let chars: Vec<char> = spec.chars().collect();
let mut pos = 0;
if pos < chars.len() && chars[pos] == '0' && pos + 1 < chars.len() && chars[pos + 1].is_ascii_digit() {
result.fill = '0';
pos += 1;
}
let width_start = pos;
while pos < chars.len() && chars[pos].is_ascii_digit() {
pos += 1;
}
if pos > width_start {
let w: String = chars[width_start..pos].iter().collect();
result.width = Some(w.parse().unwrap());
}
if pos < chars.len() && chars[pos] == '.' {
pos += 1;
let prec_start = pos;
while pos < chars.len() && chars[pos].is_ascii_digit() {
pos += 1;
}
if pos > prec_start {
let p: String = chars[prec_start..pos].iter().collect();
result.precision = Some(p.parse().unwrap());
}
}
if pos < chars.len() {
result.conversion = chars[pos];
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PolydatNode;
#[test]
fn printf_simple() {
let node = Printf::new("hello {}".to_string(), 1);
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_str(), "hello 42");
}
#[test]
fn printf_multiple() {
let node = Printf::new("{} + {} = {}".to_string(), 3);
let mut out = [Value::None];
node.eval(&[Value::U64(1), Value::U64(2), Value::U64(3)], &mut out);
assert_eq!(out[0].as_str(), "1 + 2 = 3");
}
#[test]
fn printf_zero_pad() {
let node = Printf::new("{:05}".to_string(), 1);
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_str(), "00042");
}
#[test]
fn printf_hex() {
let node = Printf::new("{:x}".to_string(), 1);
let mut out = [Value::None];
node.eval(&[Value::U64(255)], &mut out);
assert_eq!(out[0].as_str(), "ff");
}
#[test]
fn printf_hex_upper() {
let node = Printf::new("{:X}".to_string(), 1);
let mut out = [Value::None];
node.eval(&[Value::U64(255)], &mut out);
assert_eq!(out[0].as_str(), "FF");
}
#[test]
fn printf_precision() {
let node = Printf::new("{:.2}".to_string(), 1);
let mut out = [Value::None];
node.eval(&[Value::F64(3.14159)], &mut out);
assert_eq!(out[0].as_str(), "3.14");
}
#[test]
fn printf_mixed() {
let node = Printf::new("id={:05} val={:.1}".to_string(), 2);
let mut out = [Value::None];
node.eval(&[Value::U64(7), Value::F64(98.6)], &mut out);
assert_eq!(out[0].as_str(), "id=00007 val=98.6");
}
#[test]
fn printf_literal_braces() {
let node = Printf::new("{{escaped}} {}".to_string(), 1);
let mut out = [Value::None];
node.eval(&[Value::U64(1)], &mut out);
assert_eq!(out[0].as_str(), "{escaped} 1");
}
#[test]
fn printf_no_placeholders() {
let node = Printf::new("just text".to_string(), 0);
let mut out = [Value::None];
node.eval(&[], &mut out);
assert_eq!(out[0].as_str(), "just text");
}
#[test]
fn printf_string_input() {
let node = Printf::new("hello {}".to_string(), 1);
let mut out = [Value::None];
node.eval(&[Value::Str("world".into())], &mut out);
assert_eq!(out[0].as_str(), "hello world");
}
#[test]
fn printf_all_present_unchanged() {
let node = Printf::new("a={} b={}".to_string(), 2);
let mut out = [Value::None];
node.eval(&[Value::U64(1), Value::U64(2)], &mut out);
assert_eq!(out[0].as_str(), "a=1 b=2");
}
#[test]
fn printf_no_placeholders_still_renders() {
let node = Printf::new("static text".to_string(), 0);
let mut out = [Value::None];
node.eval(&[], &mut out);
assert_eq!(out[0].as_str(), "static text");
}
}