1#[crate::polydat_node(category = Conversions)]
28fn __u64_to_string(input: u64) -> String {
29 input.to_string()
30}
31
32#[crate::polydat_node(category = Conversions)]
33fn __f64_to_string(input: f64) -> String {
34 input.to_string()
35}
36
37#[crate::polydat_node(category = Conversions)]
38fn __u64_to_f64(input: u64) -> f64 {
39 input as f64
40}
41
42#[crate::polydat_node(category = Conversions)]
43fn __bool_to_str(input: bool) -> String {
44 if input { "true".into() } else { "false".into() }
45}
46
47#[crate::polydat_node(category = Conversions)]
48fn __bool_to_u64(input: bool) -> u64 {
49 if input { 1 } else { 0 }
50}
51
52#[crate::polydat_node(category = Conversions)]
53fn __u64_to_bool(input: u64) -> bool {
54 input != 0
55}
56
57#[crate::polydat_node(category = Conversions)]
58fn __u32_to_u64(input: u32) -> u64 {
59 input as u64
60}
61
62#[crate::polydat_node(category = Conversions)]
65fn __u32_to_i64(input: u32) -> i64 {
66 input as i64
67}
68
69#[crate::polydat_node(category = Conversions)]
70fn __i32_to_i64(input: i32) -> i64 {
71 input as i64
72}
73
74#[crate::polydat_node(category = Conversions)]
75fn __f32_to_f64(input: f32) -> f64 {
76 input as f64
77}
78
79#[crate::polydat_node(category = Conversions)]
80fn __i32_to_f64(input: i32) -> f64 {
81 input as f64
82}
83
84#[crate::polydat_node(category = Conversions)]
85fn __u32_to_f64(input: u32) -> f64 {
86 input as f64
87}
88
89#[crate::polydat_node(category = Conversions)]
90fn __i64_to_f64(input: i64) -> f64 {
91 input as f64
92}
93
94#[crate::polydat_node(category = Conversions)]
95fn __i32_to_string(input: i32) -> String {
96 input.to_string()
97}
98
99#[crate::polydat_node(category = Conversions)]
100fn __i64_to_string(input: i64) -> String {
101 input.to_string()
102}
103
104#[crate::polydat_node(category = Conversions)]
105fn __f32_to_string(input: f32) -> String {
106 input.to_string()
107}
108
109#[crate::polydat_node(category = Conversions)]
110fn __u32_to_string(input: u32) -> String {
111 input.to_string()
112}
113
114#[crate::polydat_node(category = Conversions)]
129fn trunc_u64(input: f64) -> u64 {
130 if input.is_nan() {
131 0
132 } else {
133 input.trunc().max(0.0).min(u64::MAX as f64) as u64
134 }
135}
136
137#[crate::polydat_node(category = Conversions)]
140fn round_u64(input: f64) -> u64 {
141 if input.is_nan() {
142 0
143 } else {
144 input.round().max(0.0).min(u64::MAX as f64) as u64
145 }
146}
147
148fn coercion_diagnostic(raw: &str, target: &str, detail: &str) -> String {
179 let braced = format!("{{{raw}}}"); format!(
181 "value {raw:?} is not {target}.\n\n\
182 This usually means a name was written where its VALUE was intended. In a \
183 scenario `set:` block, values are text-templates, so an iteration variable \
184 must be BRACED to substitute its value:\n \
185 set: {{ field: \"{braced}\" }} # the value of `{raw}`\n \
186 set: {{ field: {raw} }} # the literal text \"{raw}\" <-- likely the bug\n\
187 In a `bindings:` block, reference names unquoted instead: `const field := {raw}`.\n\n\
188 (coercion detail: {detail})"
189 )
190}
191
192pub(crate) fn parse_bool(input: &str) -> bool {
205 let raw = input.trim();
206 match raw.to_ascii_lowercase().as_str() {
207 "true" | "1" => true,
208 "false" | "0" => false,
209 _ => panic!(
210 "{}",
211 coercion_diagnostic(
212 raw,
213 "a boolean",
214 "__str_to_bool expected case-insensitive true/false or 1/0",
215 )
216 ),
217 }
218}
219
220#[crate::polydat_node(category = Conversions)]
221fn __str_to_bool(input: &str) -> bool {
222 parse_bool(input)
223}
224
225pub(crate) fn parse_u64(input: &str) -> u64 {
228 let raw = input.trim();
229 raw.parse::<u64>().unwrap_or_else(|e| {
230 panic!(
231 "{}",
232 coercion_diagnostic(raw, "a whole number", &format!("__str_to_u64: {e}"))
233 )
234 })
235}
236
237#[crate::polydat_node(category = Conversions)]
238fn __str_to_u64(input: &str) -> u64 {
239 parse_u64(input)
240}
241
242pub(crate) fn parse_f64(input: &str) -> f64 {
245 let raw = input.trim();
246 raw.parse::<f64>().unwrap_or_else(|e| {
247 panic!(
248 "{}",
249 coercion_diagnostic(raw, "a number", &format!("__str_to_f64: {e}"))
250 )
251 })
252}
253
254#[crate::polydat_node(category = Conversions)]
255fn __str_to_f64(input: &str) -> f64 {
256 parse_f64(input)
257}
258
259#[crate::polydat_node(category = Conversions)]
275fn f64_to_u64(input: f64) -> u64 {
276 input as u64
277}
278
279#[crate::polydat_node(category = Conversions)]
280fn round_to_u64(input: f64) -> u64 {
281 input.round() as u64
282}
283
284#[crate::polydat_node(category = Conversions)]
285fn floor_to_u64(input: f64) -> u64 {
286 input.floor() as u64
287}
288
289#[crate::polydat_node(category = Conversions)]
299fn ceil_to_u64(input: f64) -> u64 {
300 input.ceil() as u64
301}
302
303#[crate::polydat_node(category = Conversions)]
318fn discretize(
319 input: f64,
320 #[poly_default(100.0f64)] range: crate::derive_support::Const<f64>,
321 #[poly_default(10u64)] buckets: crate::derive_support::Const<u64>,
322) -> u64 {
323 let r = *range;
324 let b = *buckets;
325 let v = input.clamp(0.0, r - f64::EPSILON);
326 let bucket = (v / r * b as f64) as u64;
327 bucket.min(b.saturating_sub(1))
328}
329
330#[crate::polydat_node(category = Conversions)]
342fn format_u64(
343 input: u64,
344 #[poly_default(10u64)] radix: crate::derive_support::Const<u64>,
345) -> String {
346 match *radix {
347 2 => format!("0b{input:b}"),
348 8 => format!("0o{input:o}"),
349 16 => format!("0x{input:x}"),
350 _ => input.to_string(),
351 }
352}
353
354impl FormatU64 {
355 pub fn decimal() -> Self {
357 Self::new(10)
358 }
359 pub fn hex() -> Self {
361 Self::new(16)
362 }
363 pub fn octal() -> Self {
365 Self::new(8)
366 }
367 pub fn binary() -> Self {
369 Self::new(2)
370 }
371 pub fn with_radix(radix: u32) -> Self {
373 Self::new(radix as u64)
374 }
375}
376
377#[crate::polydat_node(category = Conversions)]
391fn format_f64(
392 input: f64,
393 #[poly_default(2)] precision: crate::derive_support::Const<u64>,
394) -> String {
395 format!("{:.prec$}", input, prec = *precision as usize)
396}
397
398#[crate::polydat_node(category = Conversions)]
402fn zero_pad_u64(
403 input: u64,
404 #[poly_default(10)] width: crate::derive_support::Const<u64>,
405) -> String {
406 format!("{:0>width$}", input, width = *width as usize)
407}
408
409use crate::dsl::registry::{FuncCategory, FuncSig};
414
415pub fn signatures() -> &'static [FuncSig] {
417 #[allow(unused_imports)]
418 use FuncCategory as C;
419 &[
420 ]
432}
433
434#[crate::polydat_node(category = Conversions)]
436fn to_f64(input: u64) -> f64 {
437 input as f64
438}
439
440pub(crate) fn build_node(
444 _name: &str,
445 _wires: &[crate::compile::assembly::WireRef],
446 _wire_types: &[crate::ast::PortType],
447 _consts: &[crate::dsl::factory::ConstArg],
448) -> Option<Result<Box<dyn crate::ast::PolydatNode>, String>> {
449 None
457}
458
459pub(crate) fn validate_node(
466 _name: &str,
467 _consts: &[crate::dsl::factory::ConstArg],
468) -> Result<(), String> {
469 Ok(())
470}
471
472crate::register_nodes!(signatures, build_node, validate_node);
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use crate::ast::{PolydatNode, Value};
477
478 #[test]
479 fn f64_to_u64_truncates() {
480 let node = F64ToU64::new();
481 let mut out = [Value::None];
482 node.eval(&[Value::F64(3.7)], &mut out);
483 assert_eq!(out[0].as_u64(), 3);
484 node.eval(&[Value::F64(3.2)], &mut out);
485 assert_eq!(out[0].as_u64(), 3);
486 }
487
488 #[test]
489 fn round_to_u64_rounds() {
490 let node = RoundToU64::new();
491 let mut out = [Value::None];
492 node.eval(&[Value::F64(3.7)], &mut out);
493 assert_eq!(out[0].as_u64(), 4);
494 node.eval(&[Value::F64(3.2)], &mut out);
495 assert_eq!(out[0].as_u64(), 3);
496 }
497
498 #[test]
499 fn floor_to_u64_floors() {
500 let node = FloorToU64::new();
501 let mut out = [Value::None];
502 node.eval(&[Value::F64(3.9)], &mut out);
503 assert_eq!(out[0].as_u64(), 3);
504 }
505
506 #[test]
507 fn ceil_to_u64_ceils() {
508 let node = CeilToU64::new();
509 let mut out = [Value::None];
510 node.eval(&[Value::F64(3.1)], &mut out);
511 assert_eq!(out[0].as_u64(), 4);
512 }
513
514 #[test]
515 fn discretize_basic() {
516 let node = Discretize::new(100.0, 10);
517 let mut out = [Value::None];
518 node.eval(&[Value::F64(0.0)], &mut out);
519 assert_eq!(out[0].as_u64(), 0);
520 node.eval(&[Value::F64(55.0)], &mut out);
521 assert_eq!(out[0].as_u64(), 5);
522 node.eval(&[Value::F64(99.0)], &mut out);
523 assert_eq!(out[0].as_u64(), 9);
524 }
525
526 #[test]
527 fn discretize_clamps() {
528 let node = Discretize::new(100.0, 10);
529 let mut out = [Value::None];
530 node.eval(&[Value::F64(-5.0)], &mut out);
531 assert_eq!(out[0].as_u64(), 0);
532 node.eval(&[Value::F64(200.0)], &mut out);
533 assert_eq!(out[0].as_u64(), 9);
534 }
535
536 #[test]
537 fn format_u64_hex() {
538 let node = FormatU64::hex();
539 let mut out = [Value::None];
540 node.eval(&[Value::U64(255)], &mut out);
541 assert_eq!(out[0].as_str(), "0xff");
542 }
543
544 #[test]
545 fn format_u64_binary() {
546 let node = FormatU64::binary();
547 let mut out = [Value::None];
548 node.eval(&[Value::U64(42)], &mut out);
549 assert_eq!(out[0].as_str(), "0b101010");
550 }
551
552 #[test]
553 fn format_u64_decimal() {
554 let node = FormatU64::decimal();
555 let mut out = [Value::None];
556 node.eval(&[Value::U64(12345)], &mut out);
557 assert_eq!(out[0].as_str(), "12345");
558 }
559
560 #[test]
561 fn format_f64_precision() {
562 let node = FormatF64::new(2);
563 let mut out = [Value::None];
564 node.eval(&[Value::F64(3.14159)], &mut out);
565 assert_eq!(out[0].as_str(), "3.14");
566 }
567
568 #[test]
569 fn format_f64_zero_precision() {
570 let node = FormatF64::new(0);
571 let mut out = [Value::None];
572 node.eval(&[Value::F64(3.7)], &mut out);
573 assert_eq!(out[0].as_str(), "4");
574 }
575
576 #[test]
577 fn zero_pad() {
578 let node = ZeroPadU64::new(8);
579 let mut out = [Value::None];
580 node.eval(&[Value::U64(42)], &mut out);
581 assert_eq!(out[0].as_str(), "00000042");
582 }
583
584 #[test]
585 fn zero_pad_no_truncation() {
586 let node = ZeroPadU64::new(3);
587 let mut out = [Value::None];
588 node.eval(&[Value::U64(12345)], &mut out);
589 assert_eq!(out[0].as_str(), "12345");
590 }
591
592 #[test]
595 fn u32_to_u64_zero_extends() {
596 let node = U32ToU64::new();
597 let mut out = [Value::None];
598 node.eval(&[Value::U64(42)], &mut out);
599 assert_eq!(out[0].as_u64(), 42);
600 node.eval(&[Value::U64(0xFFFF_FFFF_0000_0001)], &mut out);
602 assert_eq!(out[0].as_u64(), 1);
603 }
604
605 #[test]
606 fn i32_to_i64_sign_extends() {
607 let node = I32ToI64::new();
608 let mut out = [Value::None];
609 node.eval(&[Value::U64(42)], &mut out);
613 assert_eq!(out[0], Value::I64(42));
614 node.eval(&[Value::U64(0xFFFF_FFFF)], &mut out);
617 assert_eq!(out[0], Value::I64(-1));
618 node.eval(&[Value::I64(-1)], &mut out);
620 assert_eq!(out[0], Value::I64(-1));
621 }
622
623 #[test]
624 fn f32_to_f64_widens() {
625 let node = F32ToF64::new();
626 let mut out = [Value::None];
627 let f32_bits = 3.14f32.to_bits() as u64;
628 node.eval(&[Value::U64(f32_bits)], &mut out);
629 let result = out[0].as_f64();
631 assert!((result - 3.14).abs() < 0.001, "got {result}");
632 }
633
634 #[test]
635 fn i32_to_f64_converts() {
636 let node = I32ToF64::new();
637 let mut out = [Value::None];
638 node.eval(&[Value::U64(42)], &mut out);
639 assert_eq!(out[0].as_f64(), 42.0);
640 node.eval(&[Value::U64((-10i32) as u32 as u64)], &mut out);
642 assert_eq!(out[0].as_f64(), -10.0);
643 }
644
645 #[test]
646 fn u32_to_f64_converts() {
647 let node = U32ToF64::new();
648 let mut out = [Value::None];
649 node.eval(&[Value::U64(1000)], &mut out);
650 assert_eq!(out[0].as_f64(), 1000.0);
651 }
652
653 #[test]
654 fn i64_to_f64_converts() {
655 let node = I64ToF64::new();
656 let mut out = [Value::None];
657 node.eval(&[Value::U64(42)], &mut out);
658 assert_eq!(out[0].as_f64(), 42.0);
659 node.eval(&[Value::U64((-1i64) as u64)], &mut out);
661 assert_eq!(out[0].as_f64(), -1.0);
662 }
663
664 #[test]
667 fn i32_to_string_formats_signed() {
668 let node = I32ToString::new();
669 let mut out = [Value::None];
670 node.eval(&[Value::U64(42)], &mut out);
671 assert_eq!(out[0].as_str(), "42");
672 node.eval(&[Value::U64((-7i32) as u32 as u64)], &mut out);
673 assert_eq!(out[0].as_str(), "-7");
674 }
675
676 #[test]
677 fn i64_to_string_formats_signed() {
678 let node = I64ToString::new();
679 let mut out = [Value::None];
680 node.eval(&[Value::U64(100)], &mut out);
681 assert_eq!(out[0].as_str(), "100");
682 node.eval(&[Value::U64((-42i64) as u64)], &mut out);
683 assert_eq!(out[0].as_str(), "-42");
684 }
685
686 #[test]
687 fn f32_to_string_formats() {
688 let node = F32ToString::new();
689 let mut out = [Value::None];
690 let bits = 2.5f32.to_bits() as u64;
691 node.eval(&[Value::U64(bits)], &mut out);
692 assert_eq!(out[0].as_str(), "2.5");
693 }
694
695 #[test]
696 fn u32_to_string_formats() {
697 let node = U32ToString::new();
698 let mut out = [Value::None];
699 node.eval(&[Value::U64(12345)], &mut out);
700 assert_eq!(out[0].as_str(), "12345");
701 }
702
703 #[test]
708 fn str_to_bool_canonical_forms() {
709 let node = StrToBool::new();
710 let mut out = [Value::None];
711 for (input, expected) in [
712 ("true", true),
713 ("false", false),
714 ("True", true),
715 ("False", false),
716 ("TRUE", true),
717 ("FALSE", false),
718 ("1", true),
719 ("0", false),
720 ] {
721 node.eval(&[Value::Str(input.into())], &mut out);
722 assert_eq!(out[0].as_bool(), expected, "input={input:?}");
723 }
724 }
725
726 #[test]
727 fn str_to_bool_trims_whitespace() {
728 let node = StrToBool::new();
729 let mut out = [Value::None];
730 node.eval(&[Value::Str(" true ".into())], &mut out);
731 assert!(out[0].as_bool());
732 node.eval(&[Value::Str("\tfalse\n".into())], &mut out);
733 assert!(!out[0].as_bool());
734 }
735
736 #[test]
737 #[should_panic(expected = "__str_to_bool")]
738 fn str_to_bool_panics_on_unparseable() {
739 let node = StrToBool::new();
740 let mut out = [Value::None];
741 node.eval(&[Value::Str("yes".into())], &mut out);
742 }
743
744 #[test]
745 fn str_to_u64_basic() {
746 let node = StrToU64::new();
747 let mut out = [Value::None];
748 node.eval(&[Value::Str("0".into())], &mut out);
749 assert_eq!(out[0].as_u64(), 0);
750 node.eval(&[Value::Str("42".into())], &mut out);
751 assert_eq!(out[0].as_u64(), 42);
752 node.eval(&[Value::Str("18446744073709551615".into())], &mut out);
753 assert_eq!(out[0].as_u64(), u64::MAX);
754 }
755
756 #[test]
757 fn str_to_u64_trims_whitespace() {
758 let node = StrToU64::new();
759 let mut out = [Value::None];
760 node.eval(&[Value::Str(" 42 ".into())], &mut out);
761 assert_eq!(out[0].as_u64(), 42);
762 }
763
764 #[test]
765 #[should_panic(expected = "__str_to_u64")]
766 fn str_to_u64_panics_on_negative() {
767 let node = StrToU64::new();
768 let mut out = [Value::None];
769 node.eval(&[Value::Str("-1".into())], &mut out);
770 }
771
772 #[test]
773 #[should_panic(expected = "__str_to_u64")]
774 fn str_to_u64_panics_on_garbage() {
775 let node = StrToU64::new();
776 let mut out = [Value::None];
777 node.eval(&[Value::Str("abc".into())], &mut out);
778 }
779
780 #[test]
781 fn str_to_f64_basic() {
782 let node = StrToF64::new();
783 let mut out = [Value::None];
784 node.eval(&[Value::Str("0.0".into())], &mut out);
785 assert_eq!(out[0].as_f64(), 0.0);
786 node.eval(&[Value::Str("3.14".into())], &mut out);
787 assert!((out[0].as_f64() - 3.14).abs() < 1e-12);
788 node.eval(&[Value::Str("-2.5e3".into())], &mut out);
789 assert_eq!(out[0].as_f64(), -2500.0);
790 node.eval(&[Value::Str("inf".into())], &mut out);
791 assert!(out[0].as_f64().is_infinite());
792 }
793
794 #[test]
795 fn str_to_f64_trims_whitespace() {
796 let node = StrToF64::new();
797 let mut out = [Value::None];
798 node.eval(&[Value::Str(" 1.5 ".into())], &mut out);
799 assert_eq!(out[0].as_f64(), 1.5);
800 }
801
802 #[test]
803 #[should_panic(expected = "__str_to_f64")]
804 fn str_to_f64_panics_on_garbage() {
805 let node = StrToF64::new();
806 let mut out = [Value::None];
807 node.eval(&[Value::Str("not-a-number".into())], &mut out);
808 }
809
810 #[test]
814 fn narrowing_casts_saturate() {
815 let t = TruncU64::new();
816 let r = RoundU64::new();
817 let mut out = [Value::None];
818 t.eval(&[Value::F64(900.9)], &mut out);
819 assert_eq!(out[0].as_u64(), 900, "trunc drops the fraction");
820 r.eval(&[Value::F64(900.9)], &mut out);
821 assert_eq!(out[0].as_u64(), 901, "round goes to nearest");
822 t.eval(&[Value::F64(-5.0)], &mut out);
823 assert_eq!(out[0].as_u64(), 0, "negative saturates to 0");
824 r.eval(&[Value::F64(f64::NAN)], &mut out);
825 assert_eq!(out[0].as_u64(), 0, "NaN saturates to 0");
826 t.eval(&[Value::F64(f64::INFINITY)], &mut out);
827 assert_eq!(out[0].as_u64(), u64::MAX, "overflow saturates to MAX");
828 }
829}