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