Skip to main content

polydat_core/library/
polyfill_complete.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Matrix-completion adapters — the boundary (class-B) conversions
5//! that fill every *meaningful* `(from, to)` cell the §3 catalog
6//! had left as `·`. Generated by `paste!`-driven macros so the
7//! ~130 trivial transforms aren't hand-written; each is still a
8//! real `#[polydat_node]` (the SRD-80b authoring path), so they
9//! JIT and register exactly like the hand-written adapters.
10//!
11//! Semantics follow the existing scalar/vector adapters:
12//!
13//! - **integer narrowing / cross-sign** uses `TryFrom` and panics
14//!   with a range diagnostic — same contract as `polyfill.rs`.
15//! - **float → integer** range-checks against the target's
16//!   `[MIN, MAX]` and rejects non-finite inputs.
17//! - **integer → narrow float** (`f16`/`f32`) saturates per IEEE
18//!   (lossy, hence class B, but never panics).
19//!
20//! See `type_system.md` §3 and `tests/adapter_catalog_invariants.rs`.
21
22// ── Scalar narrowing / cross-sign (class B) ───────────────────────
23
24/// Integer → integer narrowing / sign-change via `TryFrom`, which
25/// range-checks (and rejects negatives into unsigned). Panics with
26/// the offending value on overflow.
27macro_rules! int_narrow {
28    ( $( $from:ident $fty:ty => $( $to:ident $tty:ty ),+ );+ $(;)? ) => {
29        paste::paste! { $( $(
30            #[crate::polydat_node(category = Conversions)]
31            fn [<__ $from _to_ $to>](n: $fty) -> $tty {
32                <$tty>::try_from(n).unwrap_or_else(|_| panic!(
33                    concat!(stringify!([<__ $from _to_ $to>]),
34                            ": value {} out of ", stringify!($tty), " range"), n))
35            }
36        )+ )+ }
37    };
38}
39
40int_narrow! {
41    u8   u8   => i8 i8;
42    i8   i8   => u8 u8, u16 u16, u32 u32, u64 u64, u128 u128;
43    u16  u16  => i8 i8, i16 i16;
44    i16  i16  => u8 u8, i8 i8, u16 u16, u32 u32, u64 u64, u128 u128;
45    u32  u32  => i8 i8, i16 i16;
46    i32  i32  => u8 u8, u16 u16, u128 u128;
47    u128 u128 => u8 u8, i8 i8, u16 u16, i16 i16, u32 u32, i32 i32, i64 i64;
48    i128 i128 => u8 u8, i8 i8, u16 u16, i16 i16, u32 u32, i32 i32, u64 u64;
49}
50
51/// Integer → narrow float (`f16`/`f32`). Lossy (saturates per
52/// IEEE) but total — class B because precision is not preserved.
53macro_rules! int_to_narrow_float {
54    ( f16 : $( $from:ident $fty:ty ),+ ) => {
55        paste::paste! { $(
56            #[crate::polydat_node(category = Conversions)]
57            fn [<__ $from _to_f16>](n: $fty) -> half::f16 { half::f16::from_f64(n as f64) }
58        )+ }
59    };
60    ( f32 : $( $from:ident $fty:ty ),+ ) => {
61        paste::paste! { $(
62            #[crate::polydat_node(category = Conversions)]
63            fn [<__ $from _to_f32>](n: $fty) -> f32 { n as f32 }
64        )+ }
65    };
66}
67
68int_to_narrow_float!(f16: u16 u16, i16 i16, u32 u32, i32 i32, i64 i64, u128 u128, i128 i128);
69int_to_narrow_float!(f32: u128 u128, i128 i128);
70
71/// Float → integer: reject non-finite, range-check against the
72/// target, then cast. Mirrors `polyfill::F64ToU64Checked`.
73macro_rules! float_to_int {
74    ( $from:ident $fty:ty | $to_f64:expr ; $( $to:ident $tty:ty ),+ ) => {
75        paste::paste! { $(
76            #[crate::polydat_node(category = Conversions)]
77            fn [<__ $from _to_ $to>](n: $fty) -> $tty {
78                let v: f64 = $to_f64(n);
79                if !v.is_finite() || v < <$tty>::MIN as f64 || v > <$tty>::MAX as f64 {
80                    panic!(concat!(stringify!([<__ $from _to_ $to>]),
81                        ": value {} out of ", stringify!($tty), " range or non-finite"), v);
82                }
83                v as $tty
84            }
85        )+ }
86    };
87}
88
89float_to_int!(f16 half::f16 | (|n: half::f16| n.to_f64()) ;
90    u8 u8, i8 i8, u16 u16, i16 i16, u32 u32, i32 i32, u64 u64, i64 i64, u128 u128, i128 i128);
91float_to_int!(f32 f32 | (|n: f32| n as f64) ;
92    u8 u8, i8 i8, u16 u16, i16 i16, u128 u128, i128 i128);
93
94// ── Vector lane casts (element-wise; complete the 7×7 lane block) ──
95//
96// Class follows the element conversion (the catalog arm picks
97// auto vs boundary): widening / `int → f64` is A, everything lossy
98// or range-checked is B. Narrowing panics per element on overflow,
99// matching the scalar contract and the existing `VecF32 → VecI32`.
100
101/// Plain `as` element cast — int widening, `int → float`,
102/// `float → float`. (f16 endpoints use the to/from_f16 forms.)
103macro_rules! vec_as {
104    ( $( $from:ident $fe:ty => $( $to:ident $te:ty ),+ );+ $(;)? ) => {
105        paste::paste! { $( $(
106            #[crate::polydat_node(category = Conversions)]
107            fn [<__ vec_ $from _to_vec_ $to>](elems: &[$fe]) -> Vec<$te> {
108                elems.iter().map(|&x| x as $te).collect()
109            }
110        )+ )+ }
111    };
112}
113
114/// Checked integer narrowing per element (`TryFrom`).
115macro_rules! vec_narrow_int {
116    ( $( $from:ident $fe:ty => $( $to:ident $te:ty ),+ );+ $(;)? ) => {
117        paste::paste! { $( $(
118            #[crate::polydat_node(category = Conversions)]
119            fn [<__ vec_ $from _to_vec_ $to>](elems: &[$fe]) -> Vec<$te> {
120                elems.iter().map(|&x| <$te>::try_from(x).unwrap_or_else(|_| panic!(
121                    concat!("__vec_", stringify!($from), "_to_vec_", stringify!($to),
122                            ": element {} out of ", stringify!($te), " range"), x))).collect()
123            }
124        )+ )+ }
125    };
126}
127
128/// `f32`/`f64` → int per element: reject non-finite, range-check.
129macro_rules! vec_float_to_int {
130    ( $from:ident $fe:ty => $( $to:ident $te:ty ),+ ) => {
131        paste::paste! { $(
132            #[crate::polydat_node(category = Conversions)]
133            fn [<__ vec_ $from _to_vec_ $to>](elems: &[$fe]) -> Vec<$te> {
134                elems.iter().map(|&x| {
135                    let v = x as f64;
136                    if !v.is_finite() || v < <$te>::MIN as f64 || v > <$te>::MAX as f64 {
137                        panic!(concat!("__vec_", stringify!($from), "_to_vec_", stringify!($to),
138                            ": element {} out of ", stringify!($te), " range or non-finite"), v);
139                    }
140                    v as $te
141                }).collect()
142            }
143        )+ }
144    };
145}
146
147/// `f16` → int per element.
148macro_rules! vec_f16_to_int {
149    ( $( $to:ident $te:ty ),+ ) => {
150        paste::paste! { $(
151            #[crate::polydat_node(category = Conversions)]
152            fn [<__ vec_f16_to_vec_ $to>](elems: &[half::f16]) -> Vec<$te> {
153                elems.iter().map(|&x| {
154                    let v = x.to_f64();
155                    if !v.is_finite() || v < <$te>::MIN as f64 || v > <$te>::MAX as f64 {
156                        panic!(concat!("__vec_f16_to_vec_", stringify!($to),
157                            ": element {} out of ", stringify!($te), " range or non-finite"), v);
158                    }
159                    v as $te
160                }).collect()
161            }
162        )+ }
163    };
164}
165
166/// `int`/`float` → `f16` per element (saturating, lossy).
167macro_rules! vec_to_f16 {
168    ( $( $from:ident $fe:ty ),+ ) => {
169        paste::paste! { $(
170            #[crate::polydat_node(category = Conversions)]
171            fn [<__ vec_ $from _to_vec_f16>](elems: &[$fe]) -> Vec<half::f16> {
172                elems.iter().map(|&x| half::f16::from_f64(x as f64)).collect()
173            }
174        )+ }
175    };
176}
177
178vec_as! {
179    f32 f32 => f64 f64;
180    i32 i32 => f64 f64, i64 i64;
181    f64 f64 => f32 f32;
182    i64 i64 => f32 f32, f64 f64;
183    i16 i16 => f32 f32, i32 i32, f64 f64, i64 i64;
184    i8  i8  => f32 f32, i32 i32, f64 f64, i64 i64, i16 i16;
185}
186vec_narrow_int! {
187    i32 i32 => i16 i16, i8 i8;
188    i64 i64 => i32 i32, i16 i16, i8 i8;
189    i16 i16 => i8 i8;
190}
191vec_float_to_int!(f32 f32 => i64 i64, i16 i16, i8 i8);
192vec_float_to_int!(f64 f64 => i32 i32, i64 i64, i16 i16, i8 i8);
193vec_f16_to_int!(i32 i32, i64 i64, i16 i16, i8 i8);
194vec_to_f16!(f32 f32, i32 i32, f64 f64, i64 i64, i16 i16, i8 i8);
195
196#[crate::polydat_node(category = Conversions)]
197fn __vec_f16_to_vec_f32(elems: &[half::f16]) -> Vec<f32> {
198    elems.iter().map(|&x| x.to_f32()).collect()
199}
200#[crate::polydat_node(category = Conversions)]
201fn __vec_f16_to_vec_f64(elems: &[half::f16]) -> Vec<f64> {
202    elems.iter().map(|&x| x.to_f64()).collect()
203}
204
205// ── Vector lane ↔ container (Bytes / Json / Str) ──────────────────
206//
207// Mirrors the existing `VecF32`/`VecI32` container adapters for the
208// five newer lanes (plus the lone `VecI32 → Str`). Bytes are
209// little-endian, exactly `sizeof(elem)` per lane; Json/Str are JSON
210// arrays. Float lanes reject non-finite on the `→ Json`/`→ Str`
211// path (JSON has no Inf/NaN), matching `VecF32 → Json`.
212
213use std::sync::Arc;
214
215/// `VecX → Bytes` (little-endian element serialise).
216macro_rules! vec_to_bytes {
217    ( $( $from:ident $fe:ty ),+ ) => {
218        paste::paste! { $(
219            #[crate::polydat_node(category = Conversions)]
220            fn [<__ vec_ $from _to_bytes>](elems: &[$fe]) -> Vec<u8> {
221                let mut buf = Vec::with_capacity(elems.len() * std::mem::size_of::<$fe>());
222                for &v in elems { buf.extend_from_slice(&v.to_le_bytes()); }
223                buf
224            }
225        )+ }
226    };
227}
228vec_to_bytes!(f64 f64, i64 i64, i16 i16, i8 i8);
229
230#[crate::polydat_node(category = Conversions)]
231fn __vec_f16_to_bytes(elems: &[half::f16]) -> Vec<u8> {
232    let mut buf = Vec::with_capacity(elems.len() * 2);
233    for &v in elems {
234        buf.extend_from_slice(&v.to_bits().to_le_bytes());
235    }
236    buf
237}
238
239/// `Bytes → VecX` (length-checked little-endian decode).
240macro_rules! bytes_to_vec {
241    ( $( $to:ident $te:ty ),+ ) => {
242        paste::paste! { $(
243            #[crate::polydat_node(category = Conversions)]
244            fn [<__ bytes_to_vec_ $to>](b: &[u8]) -> Vec<$te> {
245                let n = std::mem::size_of::<$te>();
246                if b.len() % n != 0 {
247                    panic!(concat!("__bytes_to_vec_", stringify!($to),
248                        ": byte length {} is not a multiple of the element size"), b.len());
249                }
250                b.chunks_exact(n).map(|c| <$te>::from_le_bytes(c.try_into().unwrap())).collect()
251            }
252        )+ }
253    };
254}
255bytes_to_vec!(f64 f64, i64 i64, i16 i16, i8 i8);
256
257#[crate::polydat_node(category = Conversions)]
258fn __bytes_to_vec_f16(b: &[u8]) -> Vec<half::f16> {
259    if !b.len().is_multiple_of(2) {
260        panic!(
261            "__bytes_to_vec_f16: byte length {} is not a multiple of 2",
262            b.len()
263        );
264    }
265    b.as_chunks::<2>()
266        .0
267        .iter()
268        .map(|c| half::f16::from_bits(u16::from_le_bytes(*c)))
269        .collect()
270}
271
272/// Integer `VecX → Json` (array of JSON numbers).
273macro_rules! vec_int_to_json {
274    ( $( $from:ident $fe:ty ),+ ) => {
275        paste::paste! { $(
276            #[crate::polydat_node(category = Conversions)]
277            fn [<__ vec_ $from _to_json>](elems: &[$fe]) -> Arc<serde_json::Value> {
278                let arr: Vec<serde_json::Value> = elems.iter()
279                    .map(|&v| serde_json::Value::Number(serde_json::Number::from(v)))
280                    .collect();
281                Arc::new(serde_json::Value::Array(arr))
282            }
283        )+ }
284    };
285}
286vec_int_to_json!(i64 i64, i16 i16, i8 i8);
287
288#[crate::polydat_node(category = Conversions)]
289fn __vec_f64_to_json(elems: &[f64]) -> Arc<serde_json::Value> {
290    let arr: Vec<serde_json::Value> = elems
291        .iter()
292        .map(|&v| {
293            serde_json::Value::Number(
294                serde_json::Number::from_f64(v)
295                    .unwrap_or_else(|| panic!("__vec_f64_to_json: non-finite element {v}")),
296            )
297        })
298        .collect();
299    Arc::new(serde_json::Value::Array(arr))
300}
301#[crate::polydat_node(category = Conversions)]
302fn __vec_f16_to_json(elems: &[half::f16]) -> Arc<serde_json::Value> {
303    let arr: Vec<serde_json::Value> = elems
304        .iter()
305        .map(|&v| {
306            let f = v.to_f64();
307            serde_json::Value::Number(
308                serde_json::Number::from_f64(f)
309                    .unwrap_or_else(|| panic!("__vec_f16_to_json: non-finite element {f}")),
310            )
311        })
312        .collect();
313    Arc::new(serde_json::Value::Array(arr))
314}
315
316/// Integer `VecX → Str` (JSON-array string).
317macro_rules! vec_int_to_str {
318    ( $( $from:ident $fe:ty ),+ ) => {
319        paste::paste! { $(
320            #[crate::polydat_node(category = Conversions)]
321            fn [<__ vec_ $from _to_str>](elems: &[$fe]) -> String {
322                let arr: Vec<serde_json::Value> = elems.iter()
323                    .map(|&v| serde_json::Value::Number(serde_json::Number::from(v)))
324                    .collect();
325                serde_json::Value::Array(arr).to_string()
326            }
327        )+ }
328    };
329}
330// `VecI32 → Str` is polyfill.rs's, with its tests.
331vec_int_to_str!(i64 i64, i16 i16, i8 i8);
332
333#[crate::polydat_node(category = Conversions)]
334fn __vec_f64_to_str(elems: &[f64]) -> String {
335    let arr: Vec<serde_json::Value> = elems
336        .iter()
337        .map(|&v| {
338            serde_json::Value::Number(
339                serde_json::Number::from_f64(v)
340                    .unwrap_or_else(|| panic!("__vec_f64_to_str: non-finite element {v}")),
341            )
342        })
343        .collect();
344    serde_json::Value::Array(arr).to_string()
345}
346#[crate::polydat_node(category = Conversions)]
347fn __vec_f16_to_str(elems: &[half::f16]) -> String {
348    let arr: Vec<serde_json::Value> = elems
349        .iter()
350        .map(|&v| {
351            let f = v.to_f64();
352            serde_json::Value::Number(
353                serde_json::Number::from_f64(f)
354                    .unwrap_or_else(|| panic!("__vec_f16_to_str: non-finite element {f}")),
355            )
356        })
357        .collect();
358    serde_json::Value::Array(arr).to_string()
359}
360
361/// `Json → VecX` for integer lanes (range-checked per element).
362macro_rules! json_to_vec_int {
363    ( $( $to:ident $te:ty ),+ ) => {
364        paste::paste! { $(
365            #[crate::polydat_node(category = Conversions)]
366            fn [<__ json_to_vec_ $to>](j: &serde_json::Value) -> Vec<$te> {
367                let arr = j.as_array().unwrap_or_else(||
368                    panic!(concat!("__json_to_vec_", stringify!($to), ": JSON value is not an array")));
369                arr.iter().map(|e| {
370                    let n = e.as_i64().unwrap_or_else(||
371                        panic!(concat!("__json_to_vec_", stringify!($to), ": element {:?} is not an integer"), e));
372                    <$te>::try_from(n).unwrap_or_else(|_|
373                        panic!(concat!("__json_to_vec_", stringify!($to), ": element {} out of range"), n))
374                }).collect()
375            }
376        )+ }
377    };
378}
379json_to_vec_int!(i64 i64, i16 i16, i8 i8);
380
381#[crate::polydat_node(category = Conversions)]
382fn __json_to_vec_f64(j: &serde_json::Value) -> Vec<f64> {
383    let arr = j
384        .as_array()
385        .unwrap_or_else(|| panic!("__json_to_vec_f64: JSON value is not an array"));
386    arr.iter()
387        .map(|e| {
388            e.as_f64()
389                .unwrap_or_else(|| panic!("__json_to_vec_f64: element {e:?} is not a number"))
390        })
391        .collect()
392}
393#[crate::polydat_node(category = Conversions)]
394fn __json_to_vec_f16(j: &serde_json::Value) -> Vec<half::f16> {
395    let arr = j
396        .as_array()
397        .unwrap_or_else(|| panic!("__json_to_vec_f16: JSON value is not an array"));
398    arr.iter()
399        .map(|e| {
400            half::f16::from_f64(
401                e.as_f64()
402                    .unwrap_or_else(|| panic!("__json_to_vec_f16: element {e:?} is not a number")),
403            )
404        })
405        .collect()
406}
407
408/// `Str → VecX` for integer lanes (parse JSON array, range-check).
409macro_rules! str_to_vec_int {
410    ( $( $to:ident $te:ty ),+ ) => {
411        paste::paste! { $(
412            #[crate::polydat_node(category = Conversions)]
413            fn [<__ str_to_vec_ $to>](input: &str) -> Vec<$te> {
414                let raw = input.trim();
415                let parsed: serde_json::Value = serde_json::from_str(raw).unwrap_or_else(|e|
416                    panic!(concat!("__str_to_vec_", stringify!($to), ": cannot parse {:?} as JSON array: {}"), raw, e));
417                let arr = parsed.as_array().unwrap_or_else(||
418                    panic!(concat!("__str_to_vec_", stringify!($to), ": parsed JSON is not an array: {:?}"), raw));
419                arr.iter().map(|e| {
420                    let n = e.as_i64().unwrap_or_else(||
421                        panic!(concat!("__str_to_vec_", stringify!($to), ": element {:?} is not an integer"), e));
422                    <$te>::try_from(n).unwrap_or_else(|_|
423                        panic!(concat!("__str_to_vec_", stringify!($to), ": element {} out of range"), n))
424                }).collect()
425            }
426        )+ }
427    };
428}
429str_to_vec_int!(i64 i64, i16 i16, i8 i8);
430
431#[crate::polydat_node(category = Conversions)]
432fn __str_to_vec_f64(input: &str) -> Vec<f64> {
433    let raw = input.trim();
434    let parsed: serde_json::Value = serde_json::from_str(raw)
435        .unwrap_or_else(|e| panic!("__str_to_vec_f64: cannot parse {raw:?} as JSON array: {e}"));
436    let arr = parsed
437        .as_array()
438        .unwrap_or_else(|| panic!("__str_to_vec_f64: parsed JSON is not an array: {raw:?}"));
439    arr.iter()
440        .map(|e| {
441            e.as_f64().unwrap_or_else(|| {
442                panic!("__str_to_vec_f64: element {e:?} is not a number in {raw:?}")
443            })
444        })
445        .collect()
446}
447#[crate::polydat_node(category = Conversions)]
448fn __str_to_vec_f16(input: &str) -> Vec<half::f16> {
449    let raw = input.trim();
450    let parsed: serde_json::Value = serde_json::from_str(raw)
451        .unwrap_or_else(|e| panic!("__str_to_vec_f16: cannot parse {raw:?} as JSON array: {e}"));
452    let arr = parsed
453        .as_array()
454        .unwrap_or_else(|| panic!("__str_to_vec_f16: parsed JSON is not an array: {raw:?}"));
455    arr.iter()
456        .map(|e| {
457            half::f16::from_f64(e.as_f64().unwrap_or_else(|| {
458                panic!("__str_to_vec_f16: element {e:?} is not a number in {raw:?}")
459            }))
460        })
461        .collect()
462}