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