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 render_with<'a>(&self, argc: usize, arg: impl Fn(usize) -> FmtArg<'a>) -> String {
153 let mut result = String::new();
154 self.render_into(argc, arg, &mut result);
155 result
156 }
157
158 pub fn render_into<'a, W: std::fmt::Write>(
160 &self,
161 argc: usize,
162 arg: impl Fn(usize) -> FmtArg<'a>,
163 out: &mut W,
164 ) {
165 for seg in &self.segments {
166 match seg {
167 Segment::Literal(s) => {
168 let _ = out.write_str(s);
169 }
170 Segment::Placeholder(spec) => {
171 if spec.index >= argc {
172 panic!(
173 "printf: format references input #{} but only {argc} wire input(s) supplied",
174 spec.index,
175 );
176 }
177 let _ = out.write_str(&format_arg(&arg(spec.index), spec));
178 }
179 }
180 }
181 }
182}
183
184fn format_arg(arg: &FmtArg<'_>, spec: &FormatSpec) -> String {
185 match arg {
186 FmtArg::U64(v) => format_u64(*v, spec),
187 FmtArg::F64(v) => format_f64(*v, spec),
188 FmtArg::Bool(v) => v.to_string(),
189 FmtArg::Str(v) => {
190 if let Some(w) = spec.width {
191 format!("{:>width$}", v, width = w)
192 } else {
193 v.to_string()
194 }
195 }
196 FmtArg::Value(val @ Value::Ext(_)) => val.to_display_string(),
200 FmtArg::Value(val) => format!("{val:?}"),
201 }
202}
203
204fn format_u64(v: u64, spec: &FormatSpec) -> String {
205 let raw = match spec.conversion {
206 'x' => format!("{v:x}"),
207 'X' => format!("{v:X}"),
208 'b' => format!("{v:b}"),
209 'o' => format!("{v:o}"),
210 _ => v.to_string(),
211 };
212 apply_width(&raw, spec)
213}
214
215fn format_f64(v: f64, spec: &FormatSpec) -> String {
216 let raw = if let Some(prec) = spec.precision {
217 format!("{v:.prec$}")
218 } else {
219 format!("{v:?}")
225 };
226 apply_width(&raw, spec)
227}
228
229fn apply_width(s: &str, spec: &FormatSpec) -> String {
230 if let Some(w) = spec.width {
231 if s.len() < w {
232 let pad = w - s.len();
233 let fill = spec.fill;
234 format!("{}{s}", std::iter::repeat_n(fill, pad).collect::<String>())
235 } else {
236 s.to_string()
237 }
238 } else {
239 s.to_string()
240 }
241}
242
243fn parse_format(fmt: &str) -> Vec<Segment> {
244 let mut segments = Vec::new();
245 let mut literal = String::new();
246 let chars: Vec<char> = fmt.chars().collect();
247 let mut i = 0;
248 let mut placeholder_idx = 0;
249
250 while i < chars.len() {
251 if chars[i] == '{' && i + 1 < chars.len() && chars[i + 1] == '{' {
252 literal.push('{');
253 i += 2;
254 } else if chars[i] == '{' {
255 if !literal.is_empty() {
256 segments.push(Segment::Literal(std::mem::take(&mut literal)));
257 }
258 let start = i + 1;
260 while i < chars.len() && chars[i] != '}' {
261 i += 1;
262 }
263 let spec_str: String = chars[start..i].iter().collect();
264 let spec = parse_spec(&spec_str, placeholder_idx);
265 segments.push(Segment::Placeholder(spec));
266 placeholder_idx += 1;
267 i += 1; } else if chars[i] == '}' && i + 1 < chars.len() && chars[i + 1] == '}' {
269 literal.push('}');
270 i += 2;
271 } else {
272 literal.push(chars[i]);
273 i += 1;
274 }
275 }
276
277 if !literal.is_empty() {
278 segments.push(Segment::Literal(literal));
279 }
280
281 segments
282}
283
284fn parse_spec(spec: &str, index: usize) -> FormatSpec {
285 let mut result = FormatSpec {
286 index,
287 width: None,
288 precision: None,
289 fill: ' ',
290 conversion: 'd',
291 };
292
293 if spec.is_empty() {
294 return result;
295 }
296
297 let spec = spec.strip_prefix(':').unwrap_or(spec);
299 if spec.is_empty() {
300 return result;
301 }
302
303 let chars: Vec<char> = spec.chars().collect();
304 let mut pos = 0;
305
306 if pos < chars.len()
308 && chars[pos] == '0'
309 && pos + 1 < chars.len()
310 && chars[pos + 1].is_ascii_digit()
311 {
312 result.fill = '0';
313 pos += 1;
314 }
315
316 let width_start = pos;
318 while pos < chars.len() && chars[pos].is_ascii_digit() {
319 pos += 1;
320 }
321 if pos > width_start {
322 let w: String = chars[width_start..pos].iter().collect();
323 result.width = Some(w.parse().unwrap());
324 }
325
326 if pos < chars.len() && chars[pos] == '.' {
328 pos += 1;
329 let prec_start = pos;
330 while pos < chars.len() && chars[pos].is_ascii_digit() {
331 pos += 1;
332 }
333 if pos > prec_start {
334 let p: String = chars[prec_start..pos].iter().collect();
335 result.precision = Some(p.parse().unwrap());
336 }
337 }
338
339 if pos < chars.len() {
341 result.conversion = chars[pos];
342 }
343
344 result
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use crate::ast::PolydatNode;
351
352 #[test]
353 fn printf_simple() {
354 let node = Printf::new("hello {}".to_string(), 1);
355 let mut out = [Value::None];
356 node.eval(&[Value::U64(42)], &mut out);
357 assert_eq!(out[0].as_str(), "hello 42");
358 }
359
360 #[test]
361 fn printf_multiple() {
362 let node = Printf::new("{} + {} = {}".to_string(), 3);
363 let mut out = [Value::None];
364 node.eval(&[Value::U64(1), Value::U64(2), Value::U64(3)], &mut out);
365 assert_eq!(out[0].as_str(), "1 + 2 = 3");
366 }
367
368 #[test]
369 fn printf_zero_pad() {
370 let node = Printf::new("{:05}".to_string(), 1);
371 let mut out = [Value::None];
372 node.eval(&[Value::U64(42)], &mut out);
373 assert_eq!(out[0].as_str(), "00042");
374 }
375
376 #[test]
377 fn printf_hex() {
378 let node = Printf::new("{:x}".to_string(), 1);
379 let mut out = [Value::None];
380 node.eval(&[Value::U64(255)], &mut out);
381 assert_eq!(out[0].as_str(), "ff");
382 }
383
384 #[test]
385 fn printf_hex_upper() {
386 let node = Printf::new("{:X}".to_string(), 1);
387 let mut out = [Value::None];
388 node.eval(&[Value::U64(255)], &mut out);
389 assert_eq!(out[0].as_str(), "FF");
390 }
391
392 #[test]
393 fn printf_precision() {
394 let node = Printf::new("{:.2}".to_string(), 1);
395 let mut out = [Value::None];
396 node.eval(&[Value::F64(3.14159)], &mut out);
397 assert_eq!(out[0].as_str(), "3.14");
398 }
399
400 #[test]
401 fn printf_mixed() {
402 let node = Printf::new("id={:05} val={:.1}".to_string(), 2);
403 let mut out = [Value::None];
404 node.eval(&[Value::U64(7), Value::F64(98.6)], &mut out);
405 assert_eq!(out[0].as_str(), "id=00007 val=98.6");
406 }
407
408 #[test]
409 fn printf_literal_braces() {
410 let node = Printf::new("{{escaped}} {}".to_string(), 1);
411 let mut out = [Value::None];
412 node.eval(&[Value::U64(1)], &mut out);
413 assert_eq!(out[0].as_str(), "{escaped} 1");
414 }
415
416 #[test]
417 fn printf_no_placeholders() {
418 let node = Printf::new("just text".to_string(), 0);
419 let mut out = [Value::None];
420 node.eval(&[], &mut out);
421 assert_eq!(out[0].as_str(), "just text");
422 }
423
424 #[test]
425 fn printf_string_input() {
426 let node = Printf::new("hello {}".to_string(), 1);
427 let mut out = [Value::None];
428 node.eval(&[Value::Str("world".into())], &mut out);
429 assert_eq!(out[0].as_str(), "hello world");
430 }
431
432 #[test]
452 fn printf_all_present_unchanged() {
453 let node = Printf::new("a={} b={}".to_string(), 2);
457 let mut out = [Value::None];
458 node.eval(&[Value::U64(1), Value::U64(2)], &mut out);
459 assert_eq!(out[0].as_str(), "a=1 b=2");
460 }
461
462 #[test]
463 fn printf_no_placeholders_still_renders() {
464 let node = Printf::new("static text".to_string(), 0);
467 let mut out = [Value::None];
468 node.eval(&[], &mut out);
469 assert_eq!(out[0].as_str(), "static text");
470 }
471}