1use crate::ast::Value;
26use crate::derive_support::PolydatSetup;
27
28#[derive(Debug, Clone)]
30pub enum Segment {
31 Literal(String),
33 Placeholder(FormatSpec),
35}
36
37#[derive(Debug, Clone)]
38pub struct FormatSpec {
40 index: usize,
42 width: Option<usize>,
44 precision: Option<usize>,
46 fill: char,
48 conversion: char,
50}
51
52#[derive(Debug, Clone)]
57pub struct ParsedFormat {
58 segments: Vec<Segment>,
59}
60
61impl PolydatSetup for ParsedFormat {}
62
63#[crate::polydat_node(category = Formatting)]
99fn printf(
100 format: Const<&str>,
101 #[poly_const(ParsedFormat::from_format_str, from = format)] parsed: &ParsedFormat,
102 parts: &[polydat::ast::Value],
103) -> String {
104 parsed.render_with(parts.len(), |i| FmtArg::from(&parts[i]))
105}
106
107pub enum FmtArg<'a> {
113 U64(u64),
115 F64(f64),
117 Bool(bool),
119 Str(&'a str),
121 Value(Value),
123}
124
125impl<'a> From<&'a Value> for FmtArg<'a> {
126 fn from(v: &'a Value) -> Self {
127 match v {
128 Value::U64(x) => FmtArg::U64(*x),
129 Value::F64(x) => FmtArg::F64(*x),
130 Value::Bool(b) => FmtArg::Bool(*b),
131 Value::Str(s) => FmtArg::Str(s),
132 other => FmtArg::Value(other.clone()),
133 }
134 }
135}
136
137impl ParsedFormat {
138 pub fn from_format_str(fmt: &str) -> Self {
143 Self {
144 segments: parse_format(fmt),
145 }
146 }
147
148 pub fn render_with<'a>(&self, argc: usize, arg: impl Fn(usize) -> FmtArg<'a>) -> String {
152 let mut result = String::new();
153 self.render_into(argc, arg, &mut result);
154 result
155 }
156
157 pub fn render_into<'a, W: std::fmt::Write>(
159 &self,
160 argc: usize,
161 arg: impl Fn(usize) -> FmtArg<'a>,
162 out: &mut W,
163 ) {
164 for seg in &self.segments {
165 match seg {
166 Segment::Literal(s) => {
167 let _ = out.write_str(s);
168 }
169 Segment::Placeholder(spec) => {
170 if spec.index >= argc {
171 panic!(
172 "printf: format references input #{} but only {argc} wire input(s) supplied",
173 spec.index,
174 );
175 }
176 let _ = out.write_str(&format_arg(&arg(spec.index), spec));
177 }
178 }
179 }
180 }
181}
182
183fn format_arg(arg: &FmtArg<'_>, spec: &FormatSpec) -> String {
184 match arg {
185 FmtArg::U64(v) => format_u64(*v, spec),
186 FmtArg::F64(v) => format_f64(*v, spec),
187 FmtArg::Bool(v) => v.to_string(),
188 FmtArg::Str(v) => {
189 if let Some(w) = spec.width {
190 format!("{:>width$}", v, width = w)
191 } else {
192 v.to_string()
193 }
194 }
195 FmtArg::Value(val @ Value::Ext(_)) => val.to_display_string(),
199 FmtArg::Value(val) => format!("{val:?}"),
200 }
201}
202
203fn format_u64(v: u64, spec: &FormatSpec) -> String {
204 let raw = match spec.conversion {
205 'x' => format!("{v:x}"),
206 'X' => format!("{v:X}"),
207 'b' => format!("{v:b}"),
208 'o' => format!("{v:o}"),
209 _ => v.to_string(),
210 };
211 apply_width(&raw, spec)
212}
213
214fn format_f64(v: f64, spec: &FormatSpec) -> String {
215 let raw = if let Some(prec) = spec.precision {
216 format!("{v:.prec$}")
217 } else {
218 format!("{v:?}")
224 };
225 apply_width(&raw, spec)
226}
227
228fn apply_width(s: &str, spec: &FormatSpec) -> String {
229 if let Some(w) = spec.width {
230 if s.len() < w {
231 let pad = w - s.len();
232 let fill = spec.fill;
233 format!("{}{s}", std::iter::repeat_n(fill, pad).collect::<String>())
234 } else {
235 s.to_string()
236 }
237 } else {
238 s.to_string()
239 }
240}
241
242fn parse_format(fmt: &str) -> Vec<Segment> {
243 let mut segments = Vec::new();
244 let mut literal = String::new();
245 let chars: Vec<char> = fmt.chars().collect();
246 let mut i = 0;
247 let mut placeholder_idx = 0;
248
249 while i < chars.len() {
250 if chars[i] == '{' && i + 1 < chars.len() && chars[i + 1] == '{' {
251 literal.push('{');
252 i += 2;
253 } else if chars[i] == '{' {
254 if !literal.is_empty() {
255 segments.push(Segment::Literal(std::mem::take(&mut literal)));
256 }
257 let start = i + 1;
259 while i < chars.len() && chars[i] != '}' {
260 i += 1;
261 }
262 let spec_str: String = chars[start..i].iter().collect();
263 let spec = parse_spec(&spec_str, placeholder_idx);
264 segments.push(Segment::Placeholder(spec));
265 placeholder_idx += 1;
266 i += 1; } else if chars[i] == '}' && i + 1 < chars.len() && chars[i + 1] == '}' {
268 literal.push('}');
269 i += 2;
270 } else {
271 literal.push(chars[i]);
272 i += 1;
273 }
274 }
275
276 if !literal.is_empty() {
277 segments.push(Segment::Literal(literal));
278 }
279
280 segments
281}
282
283fn parse_spec(spec: &str, index: usize) -> FormatSpec {
284 let mut result = FormatSpec {
285 index,
286 width: None,
287 precision: None,
288 fill: ' ',
289 conversion: 'd',
290 };
291
292 if spec.is_empty() {
293 return result;
294 }
295
296 let spec = spec.strip_prefix(':').unwrap_or(spec);
298 if spec.is_empty() {
299 return result;
300 }
301
302 let chars: Vec<char> = spec.chars().collect();
303 let mut pos = 0;
304
305 if pos < chars.len()
307 && chars[pos] == '0'
308 && pos + 1 < chars.len()
309 && chars[pos + 1].is_ascii_digit()
310 {
311 result.fill = '0';
312 pos += 1;
313 }
314
315 let width_start = pos;
317 while pos < chars.len() && chars[pos].is_ascii_digit() {
318 pos += 1;
319 }
320 if pos > width_start {
321 let w: String = chars[width_start..pos].iter().collect();
322 result.width = Some(w.parse().unwrap());
323 }
324
325 if pos < chars.len() && chars[pos] == '.' {
327 pos += 1;
328 let prec_start = pos;
329 while pos < chars.len() && chars[pos].is_ascii_digit() {
330 pos += 1;
331 }
332 if pos > prec_start {
333 let p: String = chars[prec_start..pos].iter().collect();
334 result.precision = Some(p.parse().unwrap());
335 }
336 }
337
338 if pos < chars.len() {
340 result.conversion = chars[pos];
341 }
342
343 result
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::ast::PolydatNode;
350
351 #[test]
352 fn printf_simple() {
353 let node = Printf::new("hello {}".to_string(), 1);
354 let mut out = [Value::None];
355 node.eval(&[Value::U64(42)], &mut out);
356 assert_eq!(out[0].as_str(), "hello 42");
357 }
358
359 #[test]
360 fn printf_multiple() {
361 let node = Printf::new("{} + {} = {}".to_string(), 3);
362 let mut out = [Value::None];
363 node.eval(&[Value::U64(1), Value::U64(2), Value::U64(3)], &mut out);
364 assert_eq!(out[0].as_str(), "1 + 2 = 3");
365 }
366
367 #[test]
368 fn printf_zero_pad() {
369 let node = Printf::new("{:05}".to_string(), 1);
370 let mut out = [Value::None];
371 node.eval(&[Value::U64(42)], &mut out);
372 assert_eq!(out[0].as_str(), "00042");
373 }
374
375 #[test]
376 fn printf_hex() {
377 let node = Printf::new("{:x}".to_string(), 1);
378 let mut out = [Value::None];
379 node.eval(&[Value::U64(255)], &mut out);
380 assert_eq!(out[0].as_str(), "ff");
381 }
382
383 #[test]
384 fn printf_hex_upper() {
385 let node = Printf::new("{:X}".to_string(), 1);
386 let mut out = [Value::None];
387 node.eval(&[Value::U64(255)], &mut out);
388 assert_eq!(out[0].as_str(), "FF");
389 }
390
391 #[test]
392 fn printf_precision() {
393 let node = Printf::new("{:.2}".to_string(), 1);
394 let mut out = [Value::None];
395 node.eval(&[Value::F64(3.14159)], &mut out);
396 assert_eq!(out[0].as_str(), "3.14");
397 }
398
399 #[test]
400 fn printf_mixed() {
401 let node = Printf::new("id={:05} val={:.1}".to_string(), 2);
402 let mut out = [Value::None];
403 node.eval(&[Value::U64(7), Value::F64(98.6)], &mut out);
404 assert_eq!(out[0].as_str(), "id=00007 val=98.6");
405 }
406
407 #[test]
408 fn printf_literal_braces() {
409 let node = Printf::new("{{escaped}} {}".to_string(), 1);
410 let mut out = [Value::None];
411 node.eval(&[Value::U64(1)], &mut out);
412 assert_eq!(out[0].as_str(), "{escaped} 1");
413 }
414
415 #[test]
416 fn printf_no_placeholders() {
417 let node = Printf::new("just text".to_string(), 0);
418 let mut out = [Value::None];
419 node.eval(&[], &mut out);
420 assert_eq!(out[0].as_str(), "just text");
421 }
422
423 #[test]
424 fn printf_string_input() {
425 let node = Printf::new("hello {}".to_string(), 1);
426 let mut out = [Value::None];
427 node.eval(&[Value::Str("world".into())], &mut out);
428 assert_eq!(out[0].as_str(), "hello world");
429 }
430
431 #[test]
451 fn printf_all_present_unchanged() {
452 let node = Printf::new("a={} b={}".to_string(), 2);
456 let mut out = [Value::None];
457 node.eval(&[Value::U64(1), Value::U64(2)], &mut out);
458 assert_eq!(out[0].as_str(), "a=1 b=2");
459 }
460
461 #[test]
462 fn printf_no_placeholders_still_renders() {
463 let node = Printf::new("static text".to_string(), 0);
466 let mut out = [Value::None];
467 node.eval(&[], &mut out);
468 assert_eq!(out[0].as_str(), "static text");
469 }
470}