#[crate::polydat_node(category = Conversions)]
fn __u64_to_string(input: u64) -> String { input.to_string() }
#[crate::polydat_node(category = Conversions)]
fn __f64_to_string(input: f64) -> String { input.to_string() }
#[crate::polydat_node(category = Conversions)]
fn __u64_to_f64(input: u64) -> f64 { input as f64 }
#[crate::polydat_node(category = Conversions)]
fn __bool_to_str(input: bool) -> String {
if input { "true".into() } else { "false".into() }
}
#[crate::polydat_node(category = Conversions)]
fn __bool_to_u64(input: bool) -> u64 {
if input { 1 } else { 0 }
}
#[crate::polydat_node(category = Conversions)]
fn __u64_to_bool(input: u64) -> bool { input != 0 }
#[crate::polydat_node(category = Conversions)]
fn __u32_to_u64(input: u32) -> u64 { input as u64 }
#[crate::polydat_node(category = Conversions)]
fn __u32_to_i64(input: u32) -> i64 { input as i64 }
#[crate::polydat_node(category = Conversions)]
fn __i32_to_i64(input: i32) -> i64 { input as i64 }
#[crate::polydat_node(category = Conversions)]
fn __f32_to_f64(input: f32) -> f64 { input as f64 }
#[crate::polydat_node(category = Conversions)]
fn __i32_to_f64(input: i32) -> f64 { input as f64 }
#[crate::polydat_node(category = Conversions)]
fn __u32_to_f64(input: u32) -> f64 { input as f64 }
#[crate::polydat_node(category = Conversions)]
fn __i64_to_f64(input: i64) -> f64 { input as f64 }
#[crate::polydat_node(category = Conversions)]
fn __i32_to_string(input: i32) -> String { input.to_string() }
#[crate::polydat_node(category = Conversions)]
fn __i64_to_string(input: i64) -> String { input.to_string() }
#[crate::polydat_node(category = Conversions)]
fn __f32_to_string(input: f32) -> String { input.to_string() }
#[crate::polydat_node(category = Conversions)]
fn __u32_to_string(input: u32) -> String { input.to_string() }
#[crate::polydat_node(category = Conversions)]
fn trunc_u64(input: f64) -> u64 {
if input.is_nan() { 0 } else { input.trunc().max(0.0).min(u64::MAX as f64) as u64 }
}
#[crate::polydat_node(category = Conversions)]
fn round_u64(input: f64) -> u64 {
if input.is_nan() { 0 } else { input.round().max(0.0).min(u64::MAX as f64) as u64 }
}
fn coercion_diagnostic(raw: &str, target: &str, detail: &str) -> String {
let braced = format!("{{{raw}}}"); format!(
"value {raw:?} is not {target}.\n\n\
This usually means a name was written where its VALUE was intended. In a \
scenario `set:` block, values are text-templates, so an iteration variable \
must be BRACED to substitute its value:\n \
set: {{ field: \"{braced}\" }} # the value of `{raw}`\n \
set: {{ field: {raw} }} # the literal text \"{raw}\" <-- likely the bug\n\
In a `bindings:` block, reference names unquoted instead: `const field := {raw}`.\n\n\
(coercion detail: {detail})"
)
}
#[crate::polydat_node(category = Conversions)]
fn __str_to_bool(input: &str) -> bool {
let raw = input.trim();
match raw.to_ascii_lowercase().as_str() {
"true" | "1" => true,
"false" | "0" => false,
_ => panic!("{}", coercion_diagnostic(
raw, "a boolean",
"__str_to_bool expected case-insensitive true/false or 1/0",
)),
}
}
#[crate::polydat_node(category = Conversions)]
fn __str_to_u64(input: &str) -> u64 {
let raw = input.trim();
raw.parse::<u64>().unwrap_or_else(|e| {
panic!("{}", coercion_diagnostic(raw, "a whole number", &format!("__str_to_u64: {e}")))
})
}
#[crate::polydat_node(category = Conversions)]
fn __str_to_f64(input: &str) -> f64 {
let raw = input.trim();
raw.parse::<f64>().unwrap_or_else(|e| {
panic!("{}", coercion_diagnostic(raw, "a number", &format!("__str_to_f64: {e}")))
})
}
#[crate::polydat_node(category = Conversions)]
fn f64_to_u64(input: f64) -> u64 { input as u64 }
#[crate::polydat_node(category = Conversions)]
fn round_to_u64(input: f64) -> u64 { input.round() as u64 }
#[crate::polydat_node(category = Conversions)]
fn floor_to_u64(input: f64) -> u64 { input.floor() as u64 }
#[crate::polydat_node(category = Conversions)]
fn ceil_to_u64(input: f64) -> u64 { input.ceil() as u64 }
#[crate::polydat_node(category = Conversions)]
fn discretize(
input: f64,
#[poly_default(100.0f64)] range: crate::derive_support::Const<f64>,
#[poly_default(10u64)] buckets: crate::derive_support::Const<u64>,
) -> u64 {
let r = *range;
let b = *buckets;
let v = input.clamp(0.0, r - f64::EPSILON);
let bucket = (v / r * b as f64) as u64;
bucket.min(b.saturating_sub(1))
}
#[crate::polydat_node(category = Conversions)]
fn format_u64(
input: u64,
#[poly_default(10u64)] radix: crate::derive_support::Const<u64>,
) -> String {
match *radix {
2 => format!("0b{input:b}"),
8 => format!("0o{input:o}"),
16 => format!("0x{input:x}"),
_ => input.to_string(),
}
}
impl FormatU64 {
pub fn decimal() -> Self { Self::new(10) }
pub fn hex() -> Self { Self::new(16) }
pub fn octal() -> Self { Self::new(8) }
pub fn binary() -> Self { Self::new(2) }
pub fn with_radix(radix: u32) -> Self { Self::new(radix as u64) }
}
#[crate::polydat_node(category = Conversions)]
fn format_f64(input: f64, #[poly_default(2)] precision: crate::derive_support::Const<u64>) -> String {
format!("{:.prec$}", input, prec = *precision as usize)
}
#[crate::polydat_node(category = Conversions)]
fn zero_pad_u64(input: u64, #[poly_default(10)] width: crate::derive_support::Const<u64>) -> String {
format!("{:0>width$}", input, width = *width as usize)
}
use crate::dsl::registry::{FuncCategory, FuncSig};
pub fn signatures() -> &'static [FuncSig] {
#[allow(unused_imports)]
use FuncCategory as C;
&[
]
}
#[crate::polydat_node(category = Conversions)]
fn to_f64(input: u64) -> f64 { input as f64 }
pub(crate) fn build_node(_name: &str, _wires: &[crate::compile::assembly::WireRef], _wire_types: &[crate::ast::PortType], _consts: &[crate::dsl::factory::ConstArg]) -> Option<Result<Box<dyn crate::ast::PolydatNode>, String>> {
None
}
pub(crate) fn validate_node(
_name: &str,
_consts: &[crate::dsl::factory::ConstArg],
) -> Result<(), String> {
Ok(())
}
crate::register_nodes!(signatures, build_node, validate_node);
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{PolydatNode, Value};
#[test]
fn f64_to_u64_truncates() {
let node = F64ToU64::new();
let mut out = [Value::None];
node.eval(&[Value::F64(3.7)], &mut out);
assert_eq!(out[0].as_u64(), 3);
node.eval(&[Value::F64(3.2)], &mut out);
assert_eq!(out[0].as_u64(), 3);
}
#[test]
fn round_to_u64_rounds() {
let node = RoundToU64::new();
let mut out = [Value::None];
node.eval(&[Value::F64(3.7)], &mut out);
assert_eq!(out[0].as_u64(), 4);
node.eval(&[Value::F64(3.2)], &mut out);
assert_eq!(out[0].as_u64(), 3);
}
#[test]
fn floor_to_u64_floors() {
let node = FloorToU64::new();
let mut out = [Value::None];
node.eval(&[Value::F64(3.9)], &mut out);
assert_eq!(out[0].as_u64(), 3);
}
#[test]
fn ceil_to_u64_ceils() {
let node = CeilToU64::new();
let mut out = [Value::None];
node.eval(&[Value::F64(3.1)], &mut out);
assert_eq!(out[0].as_u64(), 4);
}
#[test]
fn discretize_basic() {
let node = Discretize::new(100.0, 10);
let mut out = [Value::None];
node.eval(&[Value::F64(0.0)], &mut out);
assert_eq!(out[0].as_u64(), 0);
node.eval(&[Value::F64(55.0)], &mut out);
assert_eq!(out[0].as_u64(), 5);
node.eval(&[Value::F64(99.0)], &mut out);
assert_eq!(out[0].as_u64(), 9);
}
#[test]
fn discretize_clamps() {
let node = Discretize::new(100.0, 10);
let mut out = [Value::None];
node.eval(&[Value::F64(-5.0)], &mut out);
assert_eq!(out[0].as_u64(), 0);
node.eval(&[Value::F64(200.0)], &mut out);
assert_eq!(out[0].as_u64(), 9);
}
#[test]
fn format_u64_hex() {
let node = FormatU64::hex();
let mut out = [Value::None];
node.eval(&[Value::U64(255)], &mut out);
assert_eq!(out[0].as_str(), "0xff");
}
#[test]
fn format_u64_binary() {
let node = FormatU64::binary();
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_str(), "0b101010");
}
#[test]
fn format_u64_decimal() {
let node = FormatU64::decimal();
let mut out = [Value::None];
node.eval(&[Value::U64(12345)], &mut out);
assert_eq!(out[0].as_str(), "12345");
}
#[test]
fn format_f64_precision() {
let node = FormatF64::new(2);
let mut out = [Value::None];
node.eval(&[Value::F64(3.14159)], &mut out);
assert_eq!(out[0].as_str(), "3.14");
}
#[test]
fn format_f64_zero_precision() {
let node = FormatF64::new(0);
let mut out = [Value::None];
node.eval(&[Value::F64(3.7)], &mut out);
assert_eq!(out[0].as_str(), "4");
}
#[test]
fn zero_pad() {
let node = ZeroPadU64::new(8);
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_str(), "00000042");
}
#[test]
fn zero_pad_no_truncation() {
let node = ZeroPadU64::new(3);
let mut out = [Value::None];
node.eval(&[Value::U64(12345)], &mut out);
assert_eq!(out[0].as_str(), "12345");
}
#[test]
fn u32_to_u64_zero_extends() {
let node = U32ToU64::new();
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_u64(), 42);
node.eval(&[Value::U64(0xFFFF_FFFF_0000_0001)], &mut out);
assert_eq!(out[0].as_u64(), 1);
}
#[test]
fn i32_to_i64_sign_extends() {
let node = I32ToI64::new();
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0], Value::I64(42));
node.eval(&[Value::U64(0xFFFF_FFFF)], &mut out);
assert_eq!(out[0], Value::I64(-1));
node.eval(&[Value::I64(-1)], &mut out);
assert_eq!(out[0], Value::I64(-1));
}
#[test]
fn f32_to_f64_widens() {
let node = F32ToF64::new();
let mut out = [Value::None];
let f32_bits = 3.14f32.to_bits() as u64;
node.eval(&[Value::U64(f32_bits)], &mut out);
let result = out[0].as_f64();
assert!((result - 3.14).abs() < 0.001, "got {result}");
}
#[test]
fn i32_to_f64_converts() {
let node = I32ToF64::new();
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_f64(), 42.0);
node.eval(&[Value::U64((-10i32) as u32 as u64)], &mut out);
assert_eq!(out[0].as_f64(), -10.0);
}
#[test]
fn u32_to_f64_converts() {
let node = U32ToF64::new();
let mut out = [Value::None];
node.eval(&[Value::U64(1000)], &mut out);
assert_eq!(out[0].as_f64(), 1000.0);
}
#[test]
fn i64_to_f64_converts() {
let node = I64ToF64::new();
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_f64(), 42.0);
node.eval(&[Value::U64((-1i64) as u64)], &mut out);
assert_eq!(out[0].as_f64(), -1.0);
}
#[test]
fn i32_to_string_formats_signed() {
let node = I32ToString::new();
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_str(), "42");
node.eval(&[Value::U64((-7i32) as u32 as u64)], &mut out);
assert_eq!(out[0].as_str(), "-7");
}
#[test]
fn i64_to_string_formats_signed() {
let node = I64ToString::new();
let mut out = [Value::None];
node.eval(&[Value::U64(100)], &mut out);
assert_eq!(out[0].as_str(), "100");
node.eval(&[Value::U64((-42i64) as u64)], &mut out);
assert_eq!(out[0].as_str(), "-42");
}
#[test]
fn f32_to_string_formats() {
let node = F32ToString::new();
let mut out = [Value::None];
let bits = 2.5f32.to_bits() as u64;
node.eval(&[Value::U64(bits)], &mut out);
assert_eq!(out[0].as_str(), "2.5");
}
#[test]
fn u32_to_string_formats() {
let node = U32ToString::new();
let mut out = [Value::None];
node.eval(&[Value::U64(12345)], &mut out);
assert_eq!(out[0].as_str(), "12345");
}
#[test]
fn str_to_bool_canonical_forms() {
let node = StrToBool::new();
let mut out = [Value::None];
for (input, expected) in [
("true", true), ("false", false),
("True", true), ("False", false),
("TRUE", true), ("FALSE", false),
("1", true), ("0", false),
] {
node.eval(&[Value::Str(input.into())], &mut out);
assert_eq!(out[0].as_bool(), expected, "input={input:?}");
}
}
#[test]
fn str_to_bool_trims_whitespace() {
let node = StrToBool::new();
let mut out = [Value::None];
node.eval(&[Value::Str(" true ".into())], &mut out);
assert!(out[0].as_bool());
node.eval(&[Value::Str("\tfalse\n".into())], &mut out);
assert!(!out[0].as_bool());
}
#[test]
#[should_panic(expected = "__str_to_bool")]
fn str_to_bool_panics_on_unparseable() {
let node = StrToBool::new();
let mut out = [Value::None];
node.eval(&[Value::Str("yes".into())], &mut out);
}
#[test]
fn str_to_u64_basic() {
let node = StrToU64::new();
let mut out = [Value::None];
node.eval(&[Value::Str("0".into())], &mut out);
assert_eq!(out[0].as_u64(), 0);
node.eval(&[Value::Str("42".into())], &mut out);
assert_eq!(out[0].as_u64(), 42);
node.eval(&[Value::Str("18446744073709551615".into())], &mut out);
assert_eq!(out[0].as_u64(), u64::MAX);
}
#[test]
fn str_to_u64_trims_whitespace() {
let node = StrToU64::new();
let mut out = [Value::None];
node.eval(&[Value::Str(" 42 ".into())], &mut out);
assert_eq!(out[0].as_u64(), 42);
}
#[test]
#[should_panic(expected = "__str_to_u64")]
fn str_to_u64_panics_on_negative() {
let node = StrToU64::new();
let mut out = [Value::None];
node.eval(&[Value::Str("-1".into())], &mut out);
}
#[test]
#[should_panic(expected = "__str_to_u64")]
fn str_to_u64_panics_on_garbage() {
let node = StrToU64::new();
let mut out = [Value::None];
node.eval(&[Value::Str("abc".into())], &mut out);
}
#[test]
fn str_to_f64_basic() {
let node = StrToF64::new();
let mut out = [Value::None];
node.eval(&[Value::Str("0.0".into())], &mut out);
assert_eq!(out[0].as_f64(), 0.0);
node.eval(&[Value::Str("3.14".into())], &mut out);
assert!((out[0].as_f64() - 3.14).abs() < 1e-12);
node.eval(&[Value::Str("-2.5e3".into())], &mut out);
assert_eq!(out[0].as_f64(), -2500.0);
node.eval(&[Value::Str("inf".into())], &mut out);
assert!(out[0].as_f64().is_infinite());
}
#[test]
fn str_to_f64_trims_whitespace() {
let node = StrToF64::new();
let mut out = [Value::None];
node.eval(&[Value::Str(" 1.5 ".into())], &mut out);
assert_eq!(out[0].as_f64(), 1.5);
}
#[test]
#[should_panic(expected = "__str_to_f64")]
fn str_to_f64_panics_on_garbage() {
let node = StrToF64::new();
let mut out = [Value::None];
node.eval(&[Value::Str("not-a-number".into())], &mut out);
}
#[test]
fn narrowing_casts_saturate() {
let t = TruncU64::new();
let r = RoundU64::new();
let mut out = [Value::None];
t.eval(&[Value::F64(900.9)], &mut out);
assert_eq!(out[0].as_u64(), 900, "trunc drops the fraction");
r.eval(&[Value::F64(900.9)], &mut out);
assert_eq!(out[0].as_u64(), 901, "round goes to nearest");
t.eval(&[Value::F64(-5.0)], &mut out);
assert_eq!(out[0].as_u64(), 0, "negative saturates to 0");
r.eval(&[Value::F64(f64::NAN)], &mut out);
assert_eq!(out[0].as_u64(), 0, "NaN saturates to 0");
t.eval(&[Value::F64(f64::INFINITY)], &mut out);
assert_eq!(out[0].as_u64(), u64::MAX, "overflow saturates to MAX");
}
}