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)]
100fn printf(
101 format: Const<&str>,
102 #[poly_const(ParsedFormat::from_format_str, from = format)] parsed: &ParsedFormat,
103 parts: &[polydat::ast::Value],
104) -> String {
105 parsed.render_with(parts.len(), |i| FmtArg::from(&parts[i]))
106}
107
108pub enum FmtArg<'a> {
115 U64(u64),
117 F64(f64),
119 Bool(bool),
121 Str(&'a str),
123 Value(Value),
125}
126
127impl<'a> From<&'a Value> for FmtArg<'a> {
128 fn from(v: &'a Value) -> Self {
129 match v {
130 Value::U64(x) => FmtArg::U64(*x),
131 Value::F64(x) => FmtArg::F64(*x),
132 Value::Bool(b) => FmtArg::Bool(*b),
133 Value::Str(s) => FmtArg::Str(s),
134 other => FmtArg::Value(other.clone()),
135 }
136 }
137}
138
139impl ParsedFormat {
140 pub fn from_format_str(fmt: &str) -> Self {
145 Self {
146 segments: parse_format(fmt),
147 }
148 }
149
150 pub fn interned(fmt: &str) -> &'static ParsedFormat {
156 use std::sync::RwLock;
157 static FORMATS: RwLock<Option<std::collections::HashMap<String, &'static ParsedFormat>>> =
158 RwLock::new(None);
159 if let Some(p) = FORMATS
160 .read()
161 .unwrap()
162 .as_ref()
163 .and_then(|m| m.get(fmt).copied())
164 {
165 return p;
166 }
167 let mut guard = FORMATS.write().unwrap();
168 let map = guard.get_or_insert_with(std::collections::HashMap::new);
169 if let Some(p) = map.get(fmt).copied() {
170 return p;
171 }
172 let leaked: &'static ParsedFormat = Box::leak(Box::new(Self::from_format_str(fmt)));
173 map.insert(fmt.to_string(), leaked);
174 leaked
175 }
176
177 pub fn render_with<'a>(&self, argc: usize, arg: impl Fn(usize) -> FmtArg<'a>) -> String {
181 let mut result = String::new();
182 self.render_into(argc, arg, &mut result);
183 result
184 }
185
186 pub fn render_into<'a, W: std::fmt::Write>(
189 &self,
190 argc: usize,
191 arg: impl Fn(usize) -> FmtArg<'a>,
192 out: &mut W,
193 ) {
194 for seg in &self.segments {
195 match seg {
196 Segment::Literal(s) => {
197 let _ = out.write_str(s);
198 }
199 Segment::Placeholder(spec) => {
200 if spec.index >= argc {
201 panic!(
202 "printf: format references input #{} but only {argc} wire input(s) supplied",
203 spec.index,
204 );
205 }
206 let _ = out.write_str(&format_arg(&arg(spec.index), spec));
207 }
208 }
209 }
210 }
211}
212
213fn format_arg(arg: &FmtArg<'_>, spec: &FormatSpec) -> String {
214 match arg {
215 FmtArg::U64(v) => format_u64(*v, spec),
216 FmtArg::F64(v) => format_f64(*v, spec),
217 FmtArg::Bool(v) => v.to_string(),
218 FmtArg::Str(v) => {
219 if let Some(w) = spec.width {
220 format!("{:>width$}", v, width = w)
221 } else {
222 v.to_string()
223 }
224 }
225 FmtArg::Value(val @ Value::Ext(_)) => val.to_display_string(),
229 FmtArg::Value(val) => format!("{val:?}"),
230 }
231}
232
233fn format_u64(v: u64, spec: &FormatSpec) -> String {
234 let raw = match spec.conversion {
235 'x' => format!("{v:x}"),
236 'X' => format!("{v:X}"),
237 'b' => format!("{v:b}"),
238 'o' => format!("{v:o}"),
239 _ => v.to_string(),
240 };
241 apply_width(&raw, spec)
242}
243
244fn format_f64(v: f64, spec: &FormatSpec) -> String {
245 let raw = if let Some(prec) = spec.precision {
246 format!("{v:.prec$}")
247 } else {
248 format!("{v:?}")
254 };
255 apply_width(&raw, spec)
256}
257
258fn apply_width(s: &str, spec: &FormatSpec) -> String {
259 if let Some(w) = spec.width {
260 if s.len() < w {
261 let pad = w - s.len();
262 let fill = spec.fill;
263 format!("{}{s}", std::iter::repeat_n(fill, pad).collect::<String>())
264 } else {
265 s.to_string()
266 }
267 } else {
268 s.to_string()
269 }
270}
271
272fn parse_format(fmt: &str) -> Vec<Segment> {
273 let mut segments = Vec::new();
274 let mut literal = String::new();
275 let chars: Vec<char> = fmt.chars().collect();
276 let mut i = 0;
277 let mut placeholder_idx = 0;
278
279 while i < chars.len() {
280 if chars[i] == '{' && i + 1 < chars.len() && chars[i + 1] == '{' {
281 literal.push('{');
282 i += 2;
283 } else if chars[i] == '{' {
284 if !literal.is_empty() {
285 segments.push(Segment::Literal(std::mem::take(&mut literal)));
286 }
287 let start = i + 1;
289 while i < chars.len() && chars[i] != '}' {
290 i += 1;
291 }
292 let spec_str: String = chars[start..i].iter().collect();
293 let spec = parse_spec(&spec_str, placeholder_idx);
294 segments.push(Segment::Placeholder(spec));
295 placeholder_idx += 1;
296 i += 1; } else if chars[i] == '}' && i + 1 < chars.len() && chars[i + 1] == '}' {
298 literal.push('}');
299 i += 2;
300 } else {
301 literal.push(chars[i]);
302 i += 1;
303 }
304 }
305
306 if !literal.is_empty() {
307 segments.push(Segment::Literal(literal));
308 }
309
310 segments
311}
312
313fn parse_spec(spec: &str, index: usize) -> FormatSpec {
314 let mut result = FormatSpec {
315 index,
316 width: None,
317 precision: None,
318 fill: ' ',
319 conversion: 'd',
320 };
321
322 if spec.is_empty() {
323 return result;
324 }
325
326 let spec = spec.strip_prefix(':').unwrap_or(spec);
328 if spec.is_empty() {
329 return result;
330 }
331
332 let chars: Vec<char> = spec.chars().collect();
333 let mut pos = 0;
334
335 if pos < chars.len()
337 && chars[pos] == '0'
338 && pos + 1 < chars.len()
339 && chars[pos + 1].is_ascii_digit()
340 {
341 result.fill = '0';
342 pos += 1;
343 }
344
345 let width_start = pos;
347 while pos < chars.len() && chars[pos].is_ascii_digit() {
348 pos += 1;
349 }
350 if pos > width_start {
351 let w: String = chars[width_start..pos].iter().collect();
352 result.width = Some(w.parse().unwrap());
353 }
354
355 if pos < chars.len() && chars[pos] == '.' {
357 pos += 1;
358 let prec_start = pos;
359 while pos < chars.len() && chars[pos].is_ascii_digit() {
360 pos += 1;
361 }
362 if pos > prec_start {
363 let p: String = chars[prec_start..pos].iter().collect();
364 result.precision = Some(p.parse().unwrap());
365 }
366 }
367
368 if pos < chars.len() {
370 result.conversion = chars[pos];
371 }
372
373 result
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use crate::ast::PolydatNode;
380
381 #[test]
382 fn printf_simple() {
383 let node = Printf::new("hello {}".to_string(), 1);
384 let mut out = [Value::None];
385 node.eval(&[Value::U64(42)], &mut out);
386 assert_eq!(out[0].as_str(), "hello 42");
387 }
388
389 #[test]
390 fn printf_multiple() {
391 let node = Printf::new("{} + {} = {}".to_string(), 3);
392 let mut out = [Value::None];
393 node.eval(&[Value::U64(1), Value::U64(2), Value::U64(3)], &mut out);
394 assert_eq!(out[0].as_str(), "1 + 2 = 3");
395 }
396
397 #[test]
398 fn printf_zero_pad() {
399 let node = Printf::new("{:05}".to_string(), 1);
400 let mut out = [Value::None];
401 node.eval(&[Value::U64(42)], &mut out);
402 assert_eq!(out[0].as_str(), "00042");
403 }
404
405 #[test]
406 fn printf_hex() {
407 let node = Printf::new("{:x}".to_string(), 1);
408 let mut out = [Value::None];
409 node.eval(&[Value::U64(255)], &mut out);
410 assert_eq!(out[0].as_str(), "ff");
411 }
412
413 #[test]
414 fn printf_hex_upper() {
415 let node = Printf::new("{:X}".to_string(), 1);
416 let mut out = [Value::None];
417 node.eval(&[Value::U64(255)], &mut out);
418 assert_eq!(out[0].as_str(), "FF");
419 }
420
421 #[test]
422 fn printf_precision() {
423 let node = Printf::new("{:.2}".to_string(), 1);
424 let mut out = [Value::None];
425 node.eval(&[Value::F64(3.14159)], &mut out);
426 assert_eq!(out[0].as_str(), "3.14");
427 }
428
429 #[test]
430 fn printf_mixed() {
431 let node = Printf::new("id={:05} val={:.1}".to_string(), 2);
432 let mut out = [Value::None];
433 node.eval(&[Value::U64(7), Value::F64(98.6)], &mut out);
434 assert_eq!(out[0].as_str(), "id=00007 val=98.6");
435 }
436
437 #[test]
438 fn printf_literal_braces() {
439 let node = Printf::new("{{escaped}} {}".to_string(), 1);
440 let mut out = [Value::None];
441 node.eval(&[Value::U64(1)], &mut out);
442 assert_eq!(out[0].as_str(), "{escaped} 1");
443 }
444
445 #[test]
446 fn printf_no_placeholders() {
447 let node = Printf::new("just text".to_string(), 0);
448 let mut out = [Value::None];
449 node.eval(&[], &mut out);
450 assert_eq!(out[0].as_str(), "just text");
451 }
452
453 #[test]
454 fn printf_string_input() {
455 let node = Printf::new("hello {}".to_string(), 1);
456 let mut out = [Value::None];
457 node.eval(&[Value::Str("world".into())], &mut out);
458 assert_eq!(out[0].as_str(), "hello world");
459 }
460
461 #[test]
481 fn printf_all_present_unchanged() {
482 let node = Printf::new("a={} b={}".to_string(), 2);
486 let mut out = [Value::None];
487 node.eval(&[Value::U64(1), Value::U64(2)], &mut out);
488 assert_eq!(out[0].as_str(), "a=1 b=2");
489 }
490
491 #[test]
492 fn printf_no_placeholders_still_renders() {
493 let node = Printf::new("static text".to_string(), 0);
496 let mut out = [Value::None];
497 node.eval(&[], &mut out);
498 assert_eq!(out[0].as_str(), "static text");
499 }
500}