macro_rules! int_narrow {
( $( $from:ident $fty:ty => $( $to:ident $tty:ty ),+ );+ $(;)? ) => {
paste::paste! { $( $(
#[crate::polydat_node(category = Conversions)]
fn [<__ $from _to_ $to>](n: $fty) -> $tty {
<$tty>::try_from(n).unwrap_or_else(|_| panic!(
concat!(stringify!([<__ $from _to_ $to>]),
": value {} out of ", stringify!($tty), " range"), n))
}
)+ )+ }
};
}
int_narrow! {
u8 u8 => i8 i8;
i8 i8 => u8 u8, u16 u16, u32 u32, u64 u64, u128 u128;
u16 u16 => i8 i8, i16 i16;
i16 i16 => u8 u8, i8 i8, u16 u16, u32 u32, u64 u64, u128 u128;
u32 u32 => i8 i8, i16 i16;
i32 i32 => u8 u8, u16 u16, u128 u128;
u128 u128 => u8 u8, i8 i8, u16 u16, i16 i16, u32 u32, i32 i32, i64 i64;
i128 i128 => u8 u8, i8 i8, u16 u16, i16 i16, u32 u32, i32 i32, u64 u64;
}
/// Integer → narrow float (`f16`/`f32`). Lossy (saturates per
/// IEEE) but total — class B because precision is not preserved.
macro_rules! int_to_narrow_float {
( f16 : $( $from:ident $fty:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ $from _to_f16>](n: $fty) -> half::f16 { half::f16::from_f64(n as f64) }
)+ }
};
( f32 : $( $from:ident $fty:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ $from _to_f32>](n: $fty) -> f32 { n as f32 }
)+ }
};
}
int_to_narrow_float!(f16: u16 u16, i16 i16, u32 u32, i32 i32, i64 i64, u128 u128, i128 i128);
int_to_narrow_float!(f32: u128 u128, i128 i128);
/// Float → integer: reject non-finite, range-check against the
/// target, then cast. Mirrors `polyfill::F64ToU64Checked`.
macro_rules! float_to_int {
( $from:ident $fty:ty | $to_f64:expr ; $( $to:ident $tty:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ $from _to_ $to>](n: $fty) -> $tty {
let v: f64 = $to_f64(n);
if !v.is_finite() || v < <$tty>::MIN as f64 || v > <$tty>::MAX as f64 {
panic!(concat!(stringify!([<__ $from _to_ $to>]),
": value {} out of ", stringify!($tty), " range or non-finite"), v);
}
v as $tty
}
)+ }
};
}
float_to_int!(f16 half::f16 | (|n: half::f16| n.to_f64()) ;
u8 u8, i8 i8, u16 u16, i16 i16, u32 u32, i32 i32, u64 u64, i64 i64, u128 u128, i128 i128);
float_to_int!(f32 f32 | (|n: f32| n as f64) ;
u8 u8, i8 i8, u16 u16, i16 i16, u128 u128, i128 i128);
// ── Vector lane casts (element-wise; complete the 7×7 lane block) ──
//
// Class follows the element conversion (the catalog arm picks
// auto vs boundary): widening / `int → f64` is A, everything lossy
// or range-checked is B. Narrowing panics per element on overflow,
// matching the scalar contract and the existing `VecF32 → VecI32`.
/// Plain `as` element cast — int widening, `int → float`,
/// `float → float`. (f16 endpoints use the to/from_f16 forms.)
macro_rules! vec_as {
( $( $from:ident $fe:ty => $( $to:ident $te:ty ),+ );+ $(;)? ) => {
paste::paste! { $( $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_ $from _to_vec_ $to>](elems: &[$fe]) -> Vec<$te> {
elems.iter().map(|&x| x as $te).collect()
}
)+ )+ }
};
}
/// Checked integer narrowing per element (`TryFrom`).
macro_rules! vec_narrow_int {
( $( $from:ident $fe:ty => $( $to:ident $te:ty ),+ );+ $(;)? ) => {
paste::paste! { $( $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_ $from _to_vec_ $to>](elems: &[$fe]) -> Vec<$te> {
elems.iter().map(|&x| <$te>::try_from(x).unwrap_or_else(|_| panic!(
concat!("__vec_", stringify!($from), "_to_vec_", stringify!($to),
": element {} out of ", stringify!($te), " range"), x))).collect()
}
)+ )+ }
};
}
/// `f32`/`f64` → int per element: reject non-finite, range-check.
macro_rules! vec_float_to_int {
( $from:ident $fe:ty => $( $to:ident $te:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_ $from _to_vec_ $to>](elems: &[$fe]) -> Vec<$te> {
elems.iter().map(|&x| {
let v = x as f64;
if !v.is_finite() || v < <$te>::MIN as f64 || v > <$te>::MAX as f64 {
panic!(concat!("__vec_", stringify!($from), "_to_vec_", stringify!($to),
": element {} out of ", stringify!($te), " range or non-finite"), v);
}
v as $te
}).collect()
}
)+ }
};
}
/// `f16` → int per element.
macro_rules! vec_f16_to_int {
( $( $to:ident $te:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_f16_to_vec_ $to>](elems: &[half::f16]) -> Vec<$te> {
elems.iter().map(|&x| {
let v = x.to_f64();
if !v.is_finite() || v < <$te>::MIN as f64 || v > <$te>::MAX as f64 {
panic!(concat!("__vec_f16_to_vec_", stringify!($to),
": element {} out of ", stringify!($te), " range or non-finite"), v);
}
v as $te
}).collect()
}
)+ }
};
}
/// `int`/`float` → `f16` per element (saturating, lossy).
macro_rules! vec_to_f16 {
( $( $from:ident $fe:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_ $from _to_vec_f16>](elems: &[$fe]) -> Vec<half::f16> {
elems.iter().map(|&x| half::f16::from_f64(x as f64)).collect()
}
)+ }
};
}
vec_as! {
f32 f32 => f64 f64;
i32 i32 => f64 f64, i64 i64;
f64 f64 => f32 f32;
i64 i64 => f32 f32, f64 f64;
i16 i16 => f32 f32, i32 i32, f64 f64, i64 i64;
i8 i8 => f32 f32, i32 i32, f64 f64, i64 i64, i16 i16;
}
vec_narrow_int! {
i32 i32 => i16 i16, i8 i8;
i64 i64 => i32 i32, i16 i16, i8 i8;
i16 i16 => i8 i8;
}
vec_float_to_int!(f32 f32 => i64 i64, i16 i16, i8 i8);
vec_float_to_int!(f64 f64 => i32 i32, i64 i64, i16 i16, i8 i8);
vec_f16_to_int!(i32 i32, i64 i64, i16 i16, i8 i8);
vec_to_f16!(f32 f32, i32 i32, f64 f64, i64 i64, i16 i16, i8 i8);
#[crate::polydat_node(category = Conversions)]
fn __vec_f16_to_vec_f32(elems: &[half::f16]) -> Vec<f32> {
elems.iter().map(|&x| x.to_f32()).collect()
}
#[crate::polydat_node(category = Conversions)]
fn __vec_f16_to_vec_f64(elems: &[half::f16]) -> Vec<f64> {
elems.iter().map(|&x| x.to_f64()).collect()
}
// ── Vector lane ↔ container (Bytes / Json / Str) ──────────────────
//
// Mirrors the existing `VecF32`/`VecI32` container adapters for the
// five newer lanes (plus the lone `VecI32 → Str`). Bytes are
// little-endian, exactly `sizeof(elem)` per lane; Json/Str are JSON
// arrays. Float lanes reject non-finite on the `→ Json`/`→ Str`
// path (JSON has no Inf/NaN), matching `VecF32 → Json`.
use std::sync::Arc;
/// `VecX → Bytes` (little-endian element serialise).
macro_rules! vec_to_bytes {
( $( $from:ident $fe:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_ $from _to_bytes>](elems: &[$fe]) -> Vec<u8> {
let mut buf = Vec::with_capacity(elems.len() * std::mem::size_of::<$fe>());
for &v in elems { buf.extend_from_slice(&v.to_le_bytes()); }
buf
}
)+ }
};
}
vec_to_bytes!(f64 f64, i64 i64, i16 i16, i8 i8);
#[crate::polydat_node(category = Conversions)]
fn __vec_f16_to_bytes(elems: &[half::f16]) -> Vec<u8> {
let mut buf = Vec::with_capacity(elems.len() * 2);
for &v in elems { buf.extend_from_slice(&v.to_bits().to_le_bytes()); }
buf
}
/// `Bytes → VecX` (length-checked little-endian decode).
macro_rules! bytes_to_vec {
( $( $to:ident $te:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ bytes_to_vec_ $to>](b: &[u8]) -> Vec<$te> {
let n = std::mem::size_of::<$te>();
if b.len() % n != 0 {
panic!(concat!("__bytes_to_vec_", stringify!($to),
": byte length {} is not a multiple of the element size"), b.len());
}
b.chunks_exact(n).map(|c| <$te>::from_le_bytes(c.try_into().unwrap())).collect()
}
)+ }
};
}
bytes_to_vec!(f64 f64, i64 i64, i16 i16, i8 i8);
#[crate::polydat_node(category = Conversions)]
fn __bytes_to_vec_f16(b: &[u8]) -> Vec<half::f16> {
if b.len() % 2 != 0 {
panic!("__bytes_to_vec_f16: byte length {} is not a multiple of 2", b.len());
}
b.chunks_exact(2)
.map(|c| half::f16::from_bits(u16::from_le_bytes(c.try_into().unwrap())))
.collect()
}
/// Integer `VecX → Json` (array of JSON numbers).
macro_rules! vec_int_to_json {
( $( $from:ident $fe:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_ $from _to_json>](elems: &[$fe]) -> Arc<serde_json::Value> {
let arr: Vec<serde_json::Value> = elems.iter()
.map(|&v| serde_json::Value::Number(serde_json::Number::from(v)))
.collect();
Arc::new(serde_json::Value::Array(arr))
}
)+ }
};
}
vec_int_to_json!(i64 i64, i16 i16, i8 i8);
#[crate::polydat_node(category = Conversions)]
fn __vec_f64_to_json(elems: &[f64]) -> Arc<serde_json::Value> {
let arr: Vec<serde_json::Value> = elems.iter().map(|&v| serde_json::Value::Number(
serde_json::Number::from_f64(v)
.unwrap_or_else(|| panic!("__vec_f64_to_json: non-finite element {v}")))).collect();
Arc::new(serde_json::Value::Array(arr))
}
#[crate::polydat_node(category = Conversions)]
fn __vec_f16_to_json(elems: &[half::f16]) -> Arc<serde_json::Value> {
let arr: Vec<serde_json::Value> = elems.iter().map(|&v| {
let f = v.to_f64();
serde_json::Value::Number(serde_json::Number::from_f64(f)
.unwrap_or_else(|| panic!("__vec_f16_to_json: non-finite element {f}")))
}).collect();
Arc::new(serde_json::Value::Array(arr))
}
/// Integer `VecX → Str` (JSON-array string).
macro_rules! vec_int_to_str {
( $( $from:ident $fe:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ vec_ $from _to_str>](elems: &[$fe]) -> String {
let arr: Vec<serde_json::Value> = elems.iter()
.map(|&v| serde_json::Value::Number(serde_json::Number::from(v)))
.collect();
serde_json::Value::Array(arr).to_string()
}
)+ }
};
}
vec_int_to_str!(i32 i32, i64 i64, i16 i16, i8 i8);
#[crate::polydat_node(category = Conversions)]
fn __vec_f64_to_str(elems: &[f64]) -> String {
let arr: Vec<serde_json::Value> = elems.iter().map(|&v| serde_json::Value::Number(
serde_json::Number::from_f64(v)
.unwrap_or_else(|| panic!("__vec_f64_to_str: non-finite element {v}")))).collect();
serde_json::Value::Array(arr).to_string()
}
#[crate::polydat_node(category = Conversions)]
fn __vec_f16_to_str(elems: &[half::f16]) -> String {
let arr: Vec<serde_json::Value> = elems.iter().map(|&v| {
let f = v.to_f64();
serde_json::Value::Number(serde_json::Number::from_f64(f)
.unwrap_or_else(|| panic!("__vec_f16_to_str: non-finite element {f}")))
}).collect();
serde_json::Value::Array(arr).to_string()
}
/// `Json → VecX` for integer lanes (range-checked per element).
macro_rules! json_to_vec_int {
( $( $to:ident $te:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ json_to_vec_ $to>](j: &serde_json::Value) -> Vec<$te> {
let arr = j.as_array().unwrap_or_else(||
panic!(concat!("__json_to_vec_", stringify!($to), ": JSON value is not an array")));
arr.iter().map(|e| {
let n = e.as_i64().unwrap_or_else(||
panic!(concat!("__json_to_vec_", stringify!($to), ": element {:?} is not an integer"), e));
<$te>::try_from(n).unwrap_or_else(|_|
panic!(concat!("__json_to_vec_", stringify!($to), ": element {} out of range"), n))
}).collect()
}
)+ }
};
}
json_to_vec_int!(i64 i64, i16 i16, i8 i8);
#[crate::polydat_node(category = Conversions)]
fn __json_to_vec_f64(j: &serde_json::Value) -> Vec<f64> {
let arr = j.as_array().unwrap_or_else(|| panic!("__json_to_vec_f64: JSON value is not an array"));
arr.iter().map(|e| e.as_f64()
.unwrap_or_else(|| panic!("__json_to_vec_f64: element {e:?} is not a number"))).collect()
}
#[crate::polydat_node(category = Conversions)]
fn __json_to_vec_f16(j: &serde_json::Value) -> Vec<half::f16> {
let arr = j.as_array().unwrap_or_else(|| panic!("__json_to_vec_f16: JSON value is not an array"));
arr.iter().map(|e| half::f16::from_f64(e.as_f64()
.unwrap_or_else(|| panic!("__json_to_vec_f16: element {e:?} is not a number")))).collect()
}
/// `Str → VecX` for integer lanes (parse JSON array, range-check).
macro_rules! str_to_vec_int {
( $( $to:ident $te:ty ),+ ) => {
paste::paste! { $(
#[crate::polydat_node(category = Conversions)]
fn [<__ str_to_vec_ $to>](input: &str) -> Vec<$te> {
let raw = input.trim();
let parsed: serde_json::Value = serde_json::from_str(raw).unwrap_or_else(|e|
panic!(concat!("__str_to_vec_", stringify!($to), ": cannot parse {:?} as JSON array: {}"), raw, e));
let arr = parsed.as_array().unwrap_or_else(||
panic!(concat!("__str_to_vec_", stringify!($to), ": parsed JSON is not an array: {:?}"), raw));
arr.iter().map(|e| {
let n = e.as_i64().unwrap_or_else(||
panic!(concat!("__str_to_vec_", stringify!($to), ": element {:?} is not an integer"), e));
<$te>::try_from(n).unwrap_or_else(|_|
panic!(concat!("__str_to_vec_", stringify!($to), ": element {} out of range"), n))
}).collect()
}
)+ }
};
}
str_to_vec_int!(i64 i64, i16 i16, i8 i8);
#[crate::polydat_node(category = Conversions)]
fn __str_to_vec_f64(input: &str) -> Vec<f64> {
let raw = input.trim();
let parsed: serde_json::Value = serde_json::from_str(raw)
.unwrap_or_else(|e| panic!("__str_to_vec_f64: cannot parse {raw:?} as JSON array: {e}"));
let arr = parsed.as_array()
.unwrap_or_else(|| panic!("__str_to_vec_f64: parsed JSON is not an array: {raw:?}"));
arr.iter().map(|e| e.as_f64()
.unwrap_or_else(|| panic!("__str_to_vec_f64: element {e:?} is not a number in {raw:?}"))).collect()
}
#[crate::polydat_node(category = Conversions)]
fn __str_to_vec_f16(input: &str) -> Vec<half::f16> {
let raw = input.trim();
let parsed: serde_json::Value = serde_json::from_str(raw)
.unwrap_or_else(|e| panic!("__str_to_vec_f16: cannot parse {raw:?} as JSON array: {e}"));
let arr = parsed.as_array()
.unwrap_or_else(|| panic!("__str_to_vec_f16: parsed JSON is not an array: {raw:?}"));
arr.iter().map(|e| half::f16::from_f64(e.as_f64()
.unwrap_or_else(|| panic!("__str_to_vec_f16: element {e:?} is not a number in {raw:?}")))).collect()
}