Skip to main content

polydat_core/library/
polyfill.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Polyfill edge adapters covering every cell of the type
5//! matrix from `polydat/docs/design/type_system.md` §3 that
6//! has a sensible conversion.
7//!
8//! Existing widening + obvious coercions live in
9//! `crate::library::convert`. This module fills in the rest:
10//!
11//! - Numeric narrowings (`U64→U32`, `I64→I32`, `F64→F32`, …)
12//!   with range-check panic on out-of-range.
13//! - Bool↔every numeric beyond `Bool↔U64`.
14//! - String parsers (`Str→{U32, I32, I64, F32, Bytes, Json,
15//!   VecF32, VecI32}`) — boundary-only because they can panic
16//!   on unparseable input.
17//! - Bytes serdes with **little-endian** byte order for
18//!   numeric and vector targets, **lowercase hex** for
19//!   `Bytes↔Str`.
20//! - Json serdes with **try-parse-or-error-wrap** for
21//!   `Str→Json` and shape-validating parses elsewhere.
22//! - Vec↔{Str, Bytes, Json} serdes; `VecF32↔VecI32` casts.
23//!
24//! See `polydat/src/compile/assembly.rs::auto_adapter` and
25//! `::boundary_adapter` for catalog registration. The split is
26//! by failure mode: anything that can panic on input the
27//! assembler couldn't verify (range, parse, shape) lives in
28//! `boundary_adapter` only; lossless widenings live in both
29//! via the `boundary_adapter` superset relation.
30//!
31//! Every cell is authored as an individual `#[polydat_node]`
32//! free function per SRD-80b §S14 — the macro emits the
33//! matching PascalCase struct (e.g. `__u64_to_i32` → `U64ToI32`),
34//! the `impl PolydatNode`, and the inventory registration. The
35//! `__` prefix on the function identifier carries through to
36//! the DSL/NodeMeta name, which is the convention assembly's
37//! adapter-detection passes use to identify auto-inserted
38//! type-coercion bridges (`name.starts_with("__")`).
39
40use std::sync::Arc;
41
42// =================================================================
43// 1. Numeric → numeric (narrowings + non-widening casts)
44// =================================================================
45//
46// Bit-stuffing convention (`type_system.md` §1):
47//   - U64/U32/I64/I32 share `Value::U64` storage. Narrow ints
48//     occupy the low bits with sign extension for signed types.
49//   - F64/F32 share `Value::U64` storage via `f32::to_bits()`;
50//     the macro-generated `IntoValue for f32` performs the
51//     bit-stuffing.
52//
53// Range-check panics keep silent truncation/saturation out of
54// the substrate. Authors who want saturating or wrapping
55// narrowing use explicit `*_to_*_wrapping` / `_saturating`
56// nodes (not provided by polyfill).
57
58#[crate::polydat_node(category = Conversions)]
59fn __u64_to_u32(n: u64) -> u32 {
60    if n > u32::MAX as u64 {
61        panic!("__u64_to_u32: value {n} exceeds u32::MAX ({})", u32::MAX);
62    }
63    n as u32
64}
65
66#[crate::polydat_node(category = Conversions)]
67fn __u64_to_i64(n: u64) -> i64 {
68    if n > i64::MAX as u64 {
69        panic!("__u64_to_i64: value {n} exceeds i64::MAX ({})", i64::MAX);
70    }
71    n as i64
72}
73
74#[crate::polydat_node(category = Conversions)]
75fn __u64_to_i32(n: u64) -> i32 {
76    if n > i32::MAX as u64 {
77        panic!("__u64_to_i32: value {n} exceeds i32::MAX ({})", i32::MAX);
78    }
79    n as i32
80}
81
82#[crate::polydat_node(category = Conversions)]
83fn __u64_to_f32(n: u64) -> f32 {
84    // u64 → f32 always succeeds (saturates to f32::INFINITY for
85    // very large values) but loses precision; that's the
86    // expected lossy-narrowing semantic.
87    n as f32
88}
89
90#[crate::polydat_node(category = Conversions)]
91fn __u32_to_i32(n: u32) -> i32 {
92    if n > i32::MAX as u32 {
93        panic!("__u32_to_i32: value {n} exceeds i32::MAX ({})", i32::MAX);
94    }
95    n as i32
96}
97
98#[crate::polydat_node(category = Conversions)]
99fn __u32_to_f32(n: u32) -> f32 {
100    n as f32
101}
102
103#[crate::polydat_node(category = Conversions)]
104fn __i64_to_u64(n: i64) -> u64 {
105    if n < 0 {
106        panic!("__i64_to_u64: negative value {n} cannot be represented as u64");
107    }
108    n as u64
109}
110
111#[crate::polydat_node(category = Conversions)]
112fn __i64_to_u32(n: i64) -> u32 {
113    if n < 0 || n > u32::MAX as i64 {
114        panic!("__i64_to_u32: value {n} out of u32 range [0, {}]", u32::MAX);
115    }
116    n as u32
117}
118
119#[crate::polydat_node(category = Conversions)]
120fn __i64_to_i32(n: i64) -> i32 {
121    if n < i32::MIN as i64 || n > i32::MAX as i64 {
122        panic!(
123            "__i64_to_i32: value {n} out of i32 range [{}, {}]",
124            i32::MIN,
125            i32::MAX
126        );
127    }
128    n as i32
129}
130
131#[crate::polydat_node(category = Conversions)]
132fn __i64_to_f32(n: i64) -> f32 {
133    n as f32
134}
135
136#[crate::polydat_node(category = Conversions)]
137fn __i32_to_u64(n: i32) -> u64 {
138    if n < 0 {
139        panic!("__i32_to_u64: negative value {n} cannot be represented as u64");
140    }
141    n as u64
142}
143
144#[crate::polydat_node(category = Conversions)]
145fn __i32_to_u32(n: i32) -> u32 {
146    if n < 0 {
147        panic!("__i32_to_u32: negative value {n} cannot be represented as u32");
148    }
149    n as u32
150}
151
152#[crate::polydat_node(category = Conversions)]
153fn __i32_to_f32(n: i32) -> f32 {
154    n as f32
155}
156
157#[crate::polydat_node(category = Conversions)]
158fn __f64_to_u64_checked(f: f64) -> u64 {
159    if !f.is_finite() {
160        panic!("__f64_to_u64_checked: non-finite value {f} cannot be represented as u64");
161    }
162    let n = f.trunc();
163    if n < 0.0 || n > u64::MAX as f64 {
164        panic!(
165            "__f64_to_u64_checked: value {f} out of u64 range [0, {}]",
166            u64::MAX
167        );
168    }
169    n as u64
170}
171
172#[crate::polydat_node(category = Conversions)]
173fn __f64_to_u32(f: f64) -> u32 {
174    if !f.is_finite() {
175        panic!("__f64_to_u32: non-finite value {f} cannot be represented as u32");
176    }
177    let n = f.trunc();
178    if n < 0.0 || n > u32::MAX as f64 {
179        panic!("__f64_to_u32: value {f} out of u32 range [0, {}]", u32::MAX);
180    }
181    n as u32
182}
183
184#[crate::polydat_node(category = Conversions)]
185fn __f64_to_i64(f: f64) -> i64 {
186    if !f.is_finite() {
187        panic!("__f64_to_i64: non-finite value {f} cannot be represented as i64");
188    }
189    let n = f.trunc();
190    if n < i64::MIN as f64 || n > i64::MAX as f64 {
191        panic!(
192            "__f64_to_i64: value {f} out of i64 range [{}, {}]",
193            i64::MIN,
194            i64::MAX
195        );
196    }
197    n as i64
198}
199
200#[crate::polydat_node(category = Conversions)]
201fn __f64_to_i32(f: f64) -> i32 {
202    if !f.is_finite() {
203        panic!("__f64_to_i32: non-finite value {f} cannot be represented as i32");
204    }
205    let n = f.trunc();
206    if n < i32::MIN as f64 || n > i32::MAX as f64 {
207        panic!(
208            "__f64_to_i32: value {f} out of i32 range [{}, {}]",
209            i32::MIN,
210            i32::MAX
211        );
212    }
213    n as i32
214}
215
216#[crate::polydat_node(category = Conversions)]
217fn __f64_to_f32(f: f64) -> f32 {
218    // f64 → f32 always succeeds (saturates to ±INFINITY for
219    // very large magnitudes) but loses precision.
220    f as f32
221}
222
223#[crate::polydat_node(category = Conversions)]
224fn __f32_to_u64(f: f32) -> u64 {
225    if !f.is_finite() {
226        panic!("__f32_to_u64: non-finite value {f} cannot be represented as u64");
227    }
228    let n = f.trunc();
229    if n < 0.0 || n > u64::MAX as f32 {
230        panic!("__f32_to_u64: value {f} out of u64 range [0, {}]", u64::MAX);
231    }
232    n as u64
233}
234
235#[crate::polydat_node(category = Conversions)]
236fn __f32_to_u32(f: f32) -> u32 {
237    if !f.is_finite() {
238        panic!("__f32_to_u32: non-finite value {f} cannot be represented as u32");
239    }
240    let n = f.trunc();
241    if n < 0.0 || n > u32::MAX as f32 {
242        panic!("__f32_to_u32: value {f} out of u32 range [0, {}]", u32::MAX);
243    }
244    n as u32
245}
246
247#[crate::polydat_node(category = Conversions)]
248fn __f32_to_i64(f: f32) -> i64 {
249    if !f.is_finite() {
250        panic!("__f32_to_i64: non-finite value {f} cannot be represented as i64");
251    }
252    let n = f.trunc();
253    if n < i64::MIN as f32 || n > i64::MAX as f32 {
254        panic!(
255            "__f32_to_i64: value {f} out of i64 range [{}, {}]",
256            i64::MIN,
257            i64::MAX
258        );
259    }
260    n as i64
261}
262
263#[crate::polydat_node(category = Conversions)]
264fn __f32_to_i32(f: f32) -> i32 {
265    if !f.is_finite() {
266        panic!("__f32_to_i32: non-finite value {f} cannot be represented as i32");
267    }
268    let n = f.trunc();
269    if n < i32::MIN as f32 || n > i32::MAX as f32 {
270        panic!(
271            "__f32_to_i32: value {f} out of i32 range [{}, {}]",
272            i32::MIN,
273            i32::MAX
274        );
275    }
276    n as i32
277}
278
279// =================================================================
280// 2. Bool ↔ numeric (beyond Bool↔U64 in convert.rs)
281// =================================================================
282
283#[crate::polydat_node(category = Conversions)]
284fn __bool_to_u32(b: bool) -> u32 {
285    if b { 1 } else { 0 }
286}
287
288#[crate::polydat_node(category = Conversions)]
289fn __bool_to_i64(b: bool) -> i64 {
290    if b { 1 } else { 0 }
291}
292
293#[crate::polydat_node(category = Conversions)]
294fn __bool_to_i32(b: bool) -> i32 {
295    if b { 1 } else { 0 }
296}
297
298#[crate::polydat_node(category = Conversions)]
299fn __bool_to_f64(b: bool) -> f64 {
300    if b { 1.0 } else { 0.0 }
301}
302
303#[crate::polydat_node(category = Conversions)]
304fn __bool_to_f32(b: bool) -> f32 {
305    if b { 1.0 } else { 0.0 }
306}
307
308#[crate::polydat_node(category = Conversions)]
309fn __u32_to_bool(n: u32) -> bool {
310    n != 0
311}
312
313#[crate::polydat_node(category = Conversions)]
314fn __i64_to_bool(n: i64) -> bool {
315    n != 0
316}
317
318#[crate::polydat_node(category = Conversions)]
319fn __i32_to_bool(n: i32) -> bool {
320    n != 0
321}
322
323#[crate::polydat_node(category = Conversions)]
324fn __f64_to_bool(f: f64) -> bool {
325    f != 0.0 && !f.is_nan()
326}
327
328#[crate::polydat_node(category = Conversions)]
329fn __f32_to_bool(f: f32) -> bool {
330    f != 0.0 && !f.is_nan()
331}
332
333// =================================================================
334// 3. Str → narrow numerics + collections (parsers)
335// =================================================================
336//
337// Boundary-only — every parser can panic on unparseable
338// input. Trims whitespace before parsing. Out-of-range
339// inputs panic with the diagnostic name + offending value.
340
341#[crate::polydat_node(category = Conversions)]
342fn __str_to_u32(input: &str) -> u32 {
343    let raw = input.trim();
344    raw.parse::<u32>()
345        .unwrap_or_else(|e| panic!("__str_to_u32: cannot parse {raw:?} as u32: {e}"))
346}
347
348/// The adapter's parse, shared with the native helper so a failure is
349/// the same diagnostic on every engine.
350pub(crate) fn parse_i64(input: &str) -> i64 {
351    let raw = input.trim();
352    raw.parse::<i64>()
353        .unwrap_or_else(|e| panic!("__str_to_i64: cannot parse {raw:?} as i64: {e}"))
354}
355
356#[crate::polydat_node(category = Conversions)]
357fn __str_to_i64(input: &str) -> i64 {
358    parse_i64(input)
359}
360
361#[crate::polydat_node(category = Conversions)]
362fn __str_to_i32(input: &str) -> i32 {
363    let raw = input.trim();
364    raw.parse::<i32>()
365        .unwrap_or_else(|e| panic!("__str_to_i32: cannot parse {raw:?} as i32: {e}"))
366}
367
368#[crate::polydat_node(category = Conversions)]
369fn __str_to_f32(input: &str) -> f32 {
370    let raw = input.trim();
371    raw.parse::<f32>()
372        .unwrap_or_else(|e| panic!("__str_to_f32: cannot parse {raw:?} as f32: {e}"))
373}
374
375#[crate::polydat_node(category = Conversions)]
376fn __str_to_bytes(input: &str) -> Vec<u8> {
377    let raw = input.trim();
378    data_encoding::HEXLOWER_PERMISSIVE
379        .decode(raw.as_bytes())
380        .unwrap_or_else(|e| panic!("__str_to_bytes: cannot hex-decode {raw:?}: {e}"))
381}
382
383#[crate::polydat_node(category = Conversions)]
384fn __str_to_json(input: &str) -> Arc<serde_json::Value> {
385    // Try-parse-or-error-wrap: well-formed JSON parses through;
386    // malformed JSON wraps as a structured error so the substrate
387    // never silently loses the original content. Workload authors
388    // see the wrapped error when they pull the resulting Json
389    // value downstream.
390    match serde_json::from_str::<serde_json::Value>(input) {
391        Ok(parsed) => Arc::new(parsed),
392        Err(e) => {
393            let wrapped = serde_json::json!({
394                "error": "invalid JSON",
395                "message": e.to_string(),
396                "raw": input,
397            });
398            Arc::new(wrapped)
399        }
400    }
401}
402
403#[crate::polydat_node(category = Conversions)]
404fn __str_to_vec_f32(input: &str) -> Vec<f32> {
405    let raw = input.trim();
406    let parsed: serde_json::Value = serde_json::from_str(raw)
407        .unwrap_or_else(|e| panic!("__str_to_vec_f32: cannot parse {raw:?} as JSON array: {e}"));
408    let arr = parsed
409        .as_array()
410        .unwrap_or_else(|| panic!("__str_to_vec_f32: parsed JSON is not an array: {raw:?}"));
411    arr.iter()
412        .map(|j| {
413            j.as_f64().unwrap_or_else(|| {
414                panic!("__str_to_vec_f32: element {j:?} is not a number in {raw:?}")
415            }) as f32
416        })
417        .collect()
418}
419
420#[crate::polydat_node(category = Conversions)]
421fn __str_to_vec_i32(input: &str) -> Vec<i32> {
422    let raw = input.trim();
423    let parsed: serde_json::Value = serde_json::from_str(raw)
424        .unwrap_or_else(|e| panic!("__str_to_vec_i32: cannot parse {raw:?} as JSON array: {e}"));
425    let arr = parsed
426        .as_array()
427        .unwrap_or_else(|| panic!("__str_to_vec_i32: parsed JSON is not an array: {raw:?}"));
428    arr.iter()
429        .map(|j| {
430            let n = j.as_i64().unwrap_or_else(|| {
431                panic!("__str_to_vec_i32: element {j:?} is not an integer in {raw:?}")
432            });
433            if !(i32::MIN as i64..=i32::MAX as i64).contains(&n) {
434                panic!(
435                    "__str_to_vec_i32: element {n} out of i32 range [{}, {}]",
436                    i32::MIN,
437                    i32::MAX
438                );
439            }
440            n as i32
441        })
442        .collect()
443}
444
445// =================================================================
446// 4. Bytes ↔ {numerics, Bool, Str, Json, Vec}
447// =================================================================
448//
449// Conventions:
450//   - Numeric ↔ Bytes: **little-endian**, exactly sizeof(N)
451//     bytes. Wrong length panics. Matches native CPU layout
452//     and binary protocols (CQL, Postgres) that this substrate
453//     adapts to.
454//   - Bool ↔ Bytes: 1 byte (0x00 / 0x01).
455//   - Vec ↔ Bytes: little-endian element bytes. Bytes length
456//     must be a multiple of sizeof(element).
457//   - Bytes ↔ Str: **lowercase hex** (`data_encoding::HEXLOWER`).
458//     Roundtrip-lossless, unambiguous, JSON-safe.
459
460#[crate::polydat_node(category = Conversions)]
461fn __u64_to_bytes(n: u64) -> Vec<u8> {
462    n.to_le_bytes().to_vec()
463}
464
465#[crate::polydat_node(category = Conversions)]
466fn __u32_to_bytes(n: u32) -> Vec<u8> {
467    n.to_le_bytes().to_vec()
468}
469
470#[crate::polydat_node(category = Conversions)]
471fn __i64_to_bytes(n: i64) -> Vec<u8> {
472    n.to_le_bytes().to_vec()
473}
474
475#[crate::polydat_node(category = Conversions)]
476fn __i32_to_bytes(n: i32) -> Vec<u8> {
477    n.to_le_bytes().to_vec()
478}
479
480#[crate::polydat_node(category = Conversions)]
481fn __f64_to_bytes(f: f64) -> Vec<u8> {
482    f.to_le_bytes().to_vec()
483}
484
485#[crate::polydat_node(category = Conversions)]
486fn __f32_to_bytes(f: f32) -> Vec<u8> {
487    f.to_le_bytes().to_vec()
488}
489
490#[crate::polydat_node(category = Conversions)]
491fn __bool_to_bytes(b: bool) -> Vec<u8> {
492    vec![if b { 1 } else { 0 }]
493}
494
495#[crate::polydat_node(category = Conversions)]
496fn __bytes_to_u64(b: &[u8]) -> u64 {
497    if b.len() != 8 {
498        panic!(
499            "__bytes_to_u64: expected exactly 8 bytes for u64, got {}",
500            b.len()
501        );
502    }
503    u64::from_le_bytes(b.try_into().unwrap())
504}
505
506#[crate::polydat_node(category = Conversions)]
507fn __bytes_to_u32(b: &[u8]) -> u32 {
508    if b.len() != 4 {
509        panic!(
510            "__bytes_to_u32: expected exactly 4 bytes for u32, got {}",
511            b.len()
512        );
513    }
514    u32::from_le_bytes(b.try_into().unwrap())
515}
516
517#[crate::polydat_node(category = Conversions)]
518fn __bytes_to_i64(b: &[u8]) -> i64 {
519    if b.len() != 8 {
520        panic!(
521            "__bytes_to_i64: expected exactly 8 bytes for i64, got {}",
522            b.len()
523        );
524    }
525    i64::from_le_bytes(b.try_into().unwrap())
526}
527
528#[crate::polydat_node(category = Conversions)]
529fn __bytes_to_i32(b: &[u8]) -> i32 {
530    if b.len() != 4 {
531        panic!(
532            "__bytes_to_i32: expected exactly 4 bytes for i32, got {}",
533            b.len()
534        );
535    }
536    i32::from_le_bytes(b.try_into().unwrap())
537}
538
539#[crate::polydat_node(category = Conversions)]
540fn __bytes_to_f64(b: &[u8]) -> f64 {
541    if b.len() != 8 {
542        panic!(
543            "__bytes_to_f64: expected exactly 8 bytes for f64, got {}",
544            b.len()
545        );
546    }
547    f64::from_le_bytes(b.try_into().unwrap())
548}
549
550#[crate::polydat_node(category = Conversions)]
551fn __bytes_to_f32(b: &[u8]) -> f32 {
552    if b.len() != 4 {
553        panic!(
554            "__bytes_to_f32: expected exactly 4 bytes for f32, got {}",
555            b.len()
556        );
557    }
558    f32::from_le_bytes(b.try_into().unwrap())
559}
560
561#[crate::polydat_node(category = Conversions)]
562fn __bytes_to_bool(b: &[u8]) -> bool {
563    if b.len() != 1 {
564        panic!(
565            "__bytes_to_bool: expected exactly 1 byte for bool, got {}",
566            b.len()
567        );
568    }
569    b[0] != 0
570}
571
572#[crate::polydat_node(category = Conversions)]
573fn __bytes_to_str(b: &[u8]) -> String {
574    data_encoding::HEXLOWER.encode(b)
575}
576
577#[crate::polydat_node(category = Conversions)]
578fn __bytes_to_json(b: &[u8]) -> Arc<serde_json::Value> {
579    let hex = data_encoding::HEXLOWER.encode(b);
580    Arc::new(serde_json::Value::String(hex))
581}
582
583#[crate::polydat_node(category = Conversions)]
584fn __bytes_to_vec_f32(b: &[u8]) -> Vec<f32> {
585    if !b.len().is_multiple_of(4) {
586        panic!(
587            "__bytes_to_vec_f32: byte length {} is not a multiple of 4 (f32 size)",
588            b.len()
589        );
590    }
591    b.as_chunks::<4>()
592        .0
593        .iter()
594        .map(|c| f32::from_le_bytes(*c))
595        .collect()
596}
597
598#[crate::polydat_node(category = Conversions)]
599fn __bytes_to_vec_i32(b: &[u8]) -> Vec<i32> {
600    if !b.len().is_multiple_of(4) {
601        panic!(
602            "__bytes_to_vec_i32: byte length {} is not a multiple of 4 (i32 size)",
603            b.len()
604        );
605    }
606    b.as_chunks::<4>()
607        .0
608        .iter()
609        .map(|c| i32::from_le_bytes(*c))
610        .collect()
611}
612
613// =================================================================
614// 5. Json ↔ {numerics, Bool, Bytes, Vec}
615// =================================================================
616//
617// Json→X extracts from the matching Json variant and panics
618// otherwise. X→Json wraps in the corresponding Json variant.
619// Bytes round-trips through Json::String of hex per §4.
620
621#[crate::polydat_node(category = Conversions)]
622fn __u64_to_json(n: u64) -> Arc<serde_json::Value> {
623    Arc::new(serde_json::Value::from(n))
624}
625
626#[crate::polydat_node(category = Conversions)]
627fn __u32_to_json(n: u32) -> Arc<serde_json::Value> {
628    Arc::new(serde_json::Value::from(n))
629}
630
631#[crate::polydat_node(category = Conversions)]
632fn __i64_to_json(n: i64) -> Arc<serde_json::Value> {
633    Arc::new(serde_json::Value::from(n))
634}
635
636#[crate::polydat_node(category = Conversions)]
637fn __i32_to_json(n: i32) -> Arc<serde_json::Value> {
638    Arc::new(serde_json::Value::from(n))
639}
640
641#[crate::polydat_node(category = Conversions)]
642fn __f64_to_json(f: f64) -> Arc<serde_json::Value> {
643    let n = serde_json::Number::from_f64(f).unwrap_or_else(|| {
644        panic!("__f64_to_json: non-finite f64 {f} not representable as JSON number")
645    });
646    Arc::new(serde_json::Value::Number(n))
647}
648
649#[crate::polydat_node(category = Conversions)]
650fn __f32_to_json(f: f32) -> Arc<serde_json::Value> {
651    let n = serde_json::Number::from_f64(f as f64).unwrap_or_else(|| {
652        panic!("__f32_to_json: non-finite f32 {f} not representable as JSON number")
653    });
654    Arc::new(serde_json::Value::Number(n))
655}
656
657#[crate::polydat_node(category = Conversions)]
658fn __bool_to_json(b: bool) -> Arc<serde_json::Value> {
659    Arc::new(serde_json::Value::Bool(b))
660}
661
662#[crate::polydat_node(category = Conversions)]
663fn __json_to_u64(j: &serde_json::Value) -> u64 {
664    j.as_u64()
665        .unwrap_or_else(|| panic!("__json_to_u64: JSON value {j} is not a u64"))
666}
667
668#[crate::polydat_node(category = Conversions)]
669fn __json_to_u32(j: &serde_json::Value) -> u32 {
670    let n = j
671        .as_u64()
672        .unwrap_or_else(|| panic!("__json_to_u32: JSON value {j} is not a u64"));
673    if n > u32::MAX as u64 {
674        panic!("__json_to_u32: value {n} exceeds u32::MAX ({})", u32::MAX);
675    }
676    n as u32
677}
678
679#[crate::polydat_node(category = Conversions)]
680fn __json_to_i64(j: &serde_json::Value) -> i64 {
681    j.as_i64()
682        .unwrap_or_else(|| panic!("__json_to_i64: JSON value {j} is not an i64"))
683}
684
685#[crate::polydat_node(category = Conversions)]
686fn __json_to_i32(j: &serde_json::Value) -> i32 {
687    let n = j
688        .as_i64()
689        .unwrap_or_else(|| panic!("__json_to_i32: JSON value {j} is not an i64"));
690    if !(i32::MIN as i64..=i32::MAX as i64).contains(&n) {
691        panic!(
692            "__json_to_i32: value {n} out of i32 range [{}, {}]",
693            i32::MIN,
694            i32::MAX
695        );
696    }
697    n as i32
698}
699
700#[crate::polydat_node(category = Conversions)]
701fn __json_to_f64(j: &serde_json::Value) -> f64 {
702    j.as_f64()
703        .unwrap_or_else(|| panic!("__json_to_f64: JSON value {j} is not an f64"))
704}
705
706#[crate::polydat_node(category = Conversions)]
707fn __json_to_f32(j: &serde_json::Value) -> f32 {
708    let f = j
709        .as_f64()
710        .unwrap_or_else(|| panic!("__json_to_f32: JSON value {j} is not an f64"));
711    f as f32
712}
713
714#[crate::polydat_node(category = Conversions)]
715fn __json_to_bool(j: &serde_json::Value) -> bool {
716    j.as_bool()
717        .unwrap_or_else(|| panic!("__json_to_bool: JSON value {j} is not a bool"))
718}
719
720#[crate::polydat_node(category = Conversions)]
721fn __json_to_bytes(j: &serde_json::Value) -> Vec<u8> {
722    let s = j.as_str().unwrap_or_else(|| {
723        panic!("__json_to_bytes: JSON value {j} is not a string (expected hex)")
724    });
725    data_encoding::HEXLOWER_PERMISSIVE
726        .decode(s.as_bytes())
727        .unwrap_or_else(|e| panic!("__json_to_bytes: cannot hex-decode {s:?}: {e}"))
728}
729
730#[crate::polydat_node(category = Conversions)]
731fn __json_to_vec_f32(j: &serde_json::Value) -> Vec<f32> {
732    let arr = j
733        .as_array()
734        .unwrap_or_else(|| panic!("__json_to_vec_f32: JSON value {j} is not an array"));
735    arr.iter()
736        .map(|x| {
737            x.as_f64()
738                .unwrap_or_else(|| panic!("__json_to_vec_f32: element {x} is not a number"))
739                as f32
740        })
741        .collect()
742}
743
744#[crate::polydat_node(category = Conversions)]
745fn __json_to_vec_i32(j: &serde_json::Value) -> Vec<i32> {
746    let arr = j
747        .as_array()
748        .unwrap_or_else(|| panic!("__json_to_vec_i32: JSON value {j} is not an array"));
749    arr.iter()
750        .map(|x| {
751            let n = x
752                .as_i64()
753                .unwrap_or_else(|| panic!("__json_to_vec_i32: element {x} is not an integer"));
754            if !(i32::MIN as i64..=i32::MAX as i64).contains(&n) {
755                panic!(
756                    "__json_to_vec_i32: element {n} out of i32 range [{}, {}]",
757                    i32::MIN,
758                    i32::MAX
759                );
760            }
761            n as i32
762        })
763        .collect()
764}
765
766// =================================================================
767// 6. Vec → {Str, Bytes, Json}, Vec ↔ Vec
768// =================================================================
769//
770// Vec → scalar (U64/F64/Bool/...) is intentionally NOT in the
771// catalog — there's no single natural convention for
772// 'collection to scalar' (first? last? length? sum? mean?).
773// Authors who need a length use the explicit `vec_len(v)`
774// stdlib node; first element uses `vec_first(v)`, etc.
775// (See type_system.md §4 for the exclusion rationale.)
776
777#[crate::polydat_node(category = Conversions)]
778fn __vec_f32_to_str(elems: &[f32]) -> String {
779    let arr: Vec<serde_json::Value> = elems
780        .iter()
781        .map(|&f| {
782            serde_json::Value::Number(
783                serde_json::Number::from_f64(f as f64)
784                    .unwrap_or_else(|| panic!("__vec_f32_to_str: non-finite element {f}")),
785            )
786        })
787        .collect();
788    serde_json::Value::Array(arr).to_string()
789}
790
791#[crate::polydat_node(category = Conversions)]
792fn __vec_i32_to_str(elems: &[i32]) -> String {
793    let arr: Vec<serde_json::Value> = elems
794        .iter()
795        .map(|&n| serde_json::Value::Number(serde_json::Number::from(n)))
796        .collect();
797    serde_json::Value::Array(arr).to_string()
798}
799
800#[crate::polydat_node(category = Conversions)]
801fn __vec_f32_to_bytes(elems: &[f32]) -> Vec<u8> {
802    let mut buf = Vec::with_capacity(elems.len() * 4);
803    for &f in elems {
804        buf.extend_from_slice(&f.to_le_bytes());
805    }
806    buf
807}
808
809#[crate::polydat_node(category = Conversions)]
810fn __vec_i32_to_bytes(elems: &[i32]) -> Vec<u8> {
811    let mut buf = Vec::with_capacity(elems.len() * 4);
812    for &n in elems {
813        buf.extend_from_slice(&n.to_le_bytes());
814    }
815    buf
816}
817
818#[crate::polydat_node(category = Conversions)]
819fn __vec_f32_to_json(elems: &[f32]) -> Arc<serde_json::Value> {
820    let arr: Vec<serde_json::Value> = elems
821        .iter()
822        .map(|&f| {
823            serde_json::Value::Number(
824                serde_json::Number::from_f64(f as f64)
825                    .unwrap_or_else(|| panic!("__vec_f32_to_json: non-finite element {f}")),
826            )
827        })
828        .collect();
829    Arc::new(serde_json::Value::Array(arr))
830}
831
832#[crate::polydat_node(category = Conversions)]
833fn __vec_i32_to_json(elems: &[i32]) -> Arc<serde_json::Value> {
834    let arr: Vec<serde_json::Value> = elems
835        .iter()
836        .map(|&n| serde_json::Value::Number(serde_json::Number::from(n)))
837        .collect();
838    Arc::new(serde_json::Value::Array(arr))
839}
840
841#[crate::polydat_node(category = Conversions)]
842fn __vec_i32_to_vec_f32(elems: &[i32]) -> Vec<f32> {
843    elems.iter().map(|&n| n as f32).collect()
844}
845
846#[crate::polydat_node(category = Conversions)]
847fn __vec_f32_to_vec_i32(elems: &[f32]) -> Vec<i32> {
848    elems
849        .iter()
850        .map(|&f| {
851            if !f.is_finite() {
852                panic!("__vec_f32_to_vec_i32: non-finite element {f} cannot be represented as i32");
853            }
854            let rounded = f.round();
855            if rounded < i32::MIN as f32 || rounded > i32::MAX as f32 {
856                panic!(
857                    "__vec_f32_to_vec_i32: element {f} out of i32 range [{}, {}]",
858                    i32::MIN,
859                    i32::MAX
860                );
861            }
862            rounded as i32
863        })
864        .collect()
865}
866
867// =================================================================
868// Tests
869// =================================================================
870
871#[cfg(test)]
872mod tests {
873    use super::*;
874    use crate::ast::{PolydatNode, SliceArc, Value};
875
876    fn check<N: PolydatNode>(node: &N, input: Value, expected: Value) {
877        let mut out = [Value::None];
878        node.eval(&[input], &mut out);
879        assert_eq!(
880            out[0],
881            expected,
882            "{} produced wrong output",
883            node.meta().name
884        );
885    }
886
887    fn check_panics<N: PolydatNode>(node: &N, input: Value, msg_substring: &str) {
888        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
889            let mut out = [Value::None];
890            node.eval(&[input], &mut out);
891        }));
892        match result {
893            Ok(_) => panic!("{} did not panic as expected", node.meta().name),
894            Err(payload) => {
895                let s = payload
896                    .downcast_ref::<String>()
897                    .cloned()
898                    .or_else(|| payload.downcast_ref::<&'static str>().map(|s| (*s).into()))
899                    .unwrap_or_default();
900                assert!(
901                    s.contains(msg_substring),
902                    "{} panicked but message didn't contain {msg_substring:?}: {s}",
903                    node.meta().name
904                );
905            }
906        }
907    }
908
909    // Bit-stuffed-f32 helper: macro `IntoValue for f32` produces
910    // `Value::U64(self.to_bits() as u64)`. The polyfill family's
911    // F32-targeting nodes all return through this convention, so
912    // the test expectations construct the same shape.
913    fn f32_value(f: f32) -> Value {
914        Value::U64(f.to_bits() as u64)
915    }
916
917    // -----------------------------------------------------------
918    // Numeric → numeric (happy path)
919    // -----------------------------------------------------------
920
921    #[test]
922    fn numeric_narrowings_happy_path() {
923        check(&U64ToU32::new(), Value::U64(42), Value::U64(42));
924        check(&U64ToI64::new(), Value::U64(100), Value::I64(100));
925        check(&U64ToI32::new(), Value::U64(100), Value::I64(100));
926        check(&U64ToF32::new(), Value::U64(7), f32_value(7.0));
927        check(&U32ToI32::new(), Value::U64(50), Value::I64(50));
928        check(&U32ToF32::new(), Value::U64(3), f32_value(3.0));
929        check(&I64ToU64::new(), Value::I64(42), Value::U64(42));
930        check(&I64ToU32::new(), Value::I64(42), Value::U64(42));
931        check(&I64ToI32::new(), Value::I64(-100), Value::I64(-100));
932        check(&I32ToU64::new(), Value::I64(7), Value::U64(7));
933        check(&I32ToU32::new(), Value::I64(7), Value::U64(7));
934        check(&F64ToF32::new(), Value::F64(1.5), f32_value(1.5));
935        check(&F64ToU32::new(), Value::F64(42.7), Value::U64(42));
936        check(&F64ToI64::new(), Value::F64(-5.9), Value::I64(-5));
937        check(&F64ToI32::new(), Value::F64(-5.9), Value::I64(-5));
938    }
939
940    #[test]
941    fn numeric_narrowing_range_panics() {
942        check_panics(
943            &U64ToU32::new(),
944            Value::U64(u32::MAX as u64 + 1),
945            "exceeds u32::MAX",
946        );
947        check_panics(&U64ToI64::new(), Value::U64(u64::MAX), "exceeds i64::MAX");
948        check_panics(&I64ToU64::new(), Value::I64(-1), "negative");
949        check_panics(&I64ToI32::new(), Value::I64(i64::MAX), "out of i32 range");
950        check_panics(&F64ToU64Checked::new(), Value::F64(f64::NAN), "non-finite");
951        check_panics(
952            &F64ToU64Checked::new(),
953            Value::F64(-1.0),
954            "out of u64 range",
955        );
956        check_panics(&F64ToI32::new(), Value::F64(f64::INFINITY), "non-finite");
957    }
958
959    // -----------------------------------------------------------
960    // Bool ↔ numeric
961    // -----------------------------------------------------------
962
963    #[test]
964    fn bool_to_numerics_round_trip() {
965        check(&BoolToU32::new(), Value::Bool(true), Value::U64(1));
966        check(&BoolToI64::new(), Value::Bool(false), Value::I64(0));
967        check(&BoolToI32::new(), Value::Bool(true), Value::I64(1));
968        check(&BoolToF64::new(), Value::Bool(true), Value::F64(1.0));
969        check(&BoolToF32::new(), Value::Bool(false), f32_value(0.0));
970        check(&U32ToBool::new(), Value::U64(7), Value::Bool(true));
971        check(&U32ToBool::new(), Value::U64(0), Value::Bool(false));
972        check(&I64ToBool::new(), Value::I64(-1), Value::Bool(true));
973        check(&I32ToBool::new(), Value::I64(0), Value::Bool(false));
974        check(&F64ToBool::new(), Value::F64(0.1), Value::Bool(true));
975        check(&F64ToBool::new(), Value::F64(0.0), Value::Bool(false));
976        check(&F64ToBool::new(), Value::F64(f64::NAN), Value::Bool(false));
977        check(&F32ToBool::new(), f32_value(2.5), Value::Bool(true));
978    }
979
980    // -----------------------------------------------------------
981    // Str → narrow numerics + collections
982    // -----------------------------------------------------------
983
984    #[test]
985    fn str_to_narrow_numerics() {
986        check(&StrToU32::new(), Value::Str("42".into()), Value::U64(42));
987        check(
988            &StrToI64::new(),
989            Value::Str("-100".into()),
990            Value::I64(-100),
991        );
992        check(&StrToI32::new(), Value::Str("-7".into()), Value::I64(-7));
993        check(&StrToF32::new(), Value::Str("1.5".into()), f32_value(1.5));
994        check_panics(
995            &StrToU32::new(),
996            Value::Str("4294967296".into()),
997            "cannot parse",
998        );
999        check_panics(&StrToI32::new(), Value::Str("abc".into()), "cannot parse");
1000    }
1001
1002    #[test]
1003    fn str_to_bytes_hex_roundtrip() {
1004        check(
1005            &StrToBytes::new(),
1006            Value::Str("0a0b0c".into()),
1007            Value::Bytes(Arc::from(&[10u8, 11, 12][..])),
1008        );
1009        check_panics(
1010            &StrToBytes::new(),
1011            Value::Str("not hex!".into()),
1012            "hex-decode",
1013        );
1014    }
1015
1016    #[test]
1017    fn str_to_json_parses_or_wraps_error() {
1018        // Well-formed JSON parses through.
1019        let node = StrToJson::new();
1020        let mut out = [Value::None];
1021        node.eval(&[Value::Str("[1, 2, 3]".into())], &mut out);
1022        match &out[0] {
1023            Value::Json(j) => {
1024                assert!(j.is_array());
1025                assert_eq!(j.as_array().unwrap().len(), 3);
1026            }
1027            other => panic!("expected Json::Array, got {other:?}"),
1028        }
1029        // Malformed JSON wraps as error structure (does NOT panic).
1030        let mut out = [Value::None];
1031        node.eval(&[Value::Str("{bad json".into())], &mut out);
1032        match &out[0] {
1033            Value::Json(j) => {
1034                let obj = j.as_object().expect("error wrap is object");
1035                assert_eq!(
1036                    obj.get("error").and_then(|v| v.as_str()),
1037                    Some("invalid JSON")
1038                );
1039                assert_eq!(obj.get("raw").and_then(|v| v.as_str()), Some("{bad json"));
1040                assert!(obj.get("message").and_then(|v| v.as_str()).is_some());
1041            }
1042            other => panic!("expected Json error wrap, got {other:?}"),
1043        }
1044    }
1045
1046    #[test]
1047    fn str_to_vec_parses_arrays() {
1048        let mut out = [Value::None];
1049        StrToVecF32::new().eval(&[Value::Str("[1.0, 2.5, -3.0]".into())], &mut out);
1050        match &out[0] {
1051            Value::VecF32(arr) => assert_eq!(arr.as_ref(), &[1.0_f32, 2.5, -3.0]),
1052            other => panic!("expected VecF32, got {other:?}"),
1053        }
1054        let mut out = [Value::None];
1055        StrToVecI32::new().eval(&[Value::Str("[1, 2, -3]".into())], &mut out);
1056        match &out[0] {
1057            Value::VecI32(arr) => assert_eq!(arr.as_ref(), &[1_i32, 2, -3]),
1058            other => panic!("expected VecI32, got {other:?}"),
1059        }
1060    }
1061
1062    // -----------------------------------------------------------
1063    // Bytes ↔ X (little-endian, lowercase hex)
1064    // -----------------------------------------------------------
1065
1066    #[test]
1067    fn numeric_bytes_le_roundtrip() {
1068        // u64 round trip
1069        let mut out = [Value::None];
1070        U64ToBytes::new().eval(&[Value::U64(0x0102030405060708)], &mut out);
1071        match &out[0] {
1072            Value::Bytes(b) => assert_eq!(b.as_ref(), &[8u8, 7, 6, 5, 4, 3, 2, 1]),
1073            other => panic!("expected Bytes, got {other:?}"),
1074        }
1075        let mut out = [Value::None];
1076        BytesToU64::new().eval(
1077            &[Value::Bytes(Arc::from(&[8u8, 7, 6, 5, 4, 3, 2, 1][..]))],
1078            &mut out,
1079        );
1080        assert_eq!(out[0], Value::U64(0x0102030405060708));
1081
1082        // u32 round trip
1083        let mut out = [Value::None];
1084        U32ToBytes::new().eval(&[Value::U64(0x01020304)], &mut out);
1085        match &out[0] {
1086            Value::Bytes(b) => assert_eq!(b.as_ref(), &[4u8, 3, 2, 1]),
1087            other => panic!("expected Bytes, got {other:?}"),
1088        }
1089
1090        // f64 round trip
1091        let mut out_b = [Value::None];
1092        F64ToBytes::new().eval(&[Value::F64(3.14)], &mut out_b);
1093        let bytes = match &out_b[0] {
1094            Value::Bytes(b) => b.clone(),
1095            _ => panic!(),
1096        };
1097        let mut out_f = [Value::None];
1098        BytesToF64::new().eval(&[Value::Bytes(bytes)], &mut out_f);
1099        assert_eq!(out_f[0], Value::F64(3.14));
1100    }
1101
1102    #[test]
1103    fn bytes_length_panics() {
1104        check_panics(
1105            &BytesToU64::new(),
1106            Value::Bytes(Arc::from(&[1u8, 2, 3][..])),
1107            "expected exactly 8 bytes",
1108        );
1109        check_panics(
1110            &BytesToU32::new(),
1111            Value::Bytes(Arc::from(&[1u8, 2, 3, 4, 5][..])),
1112            "expected exactly 4 bytes",
1113        );
1114        check_panics(
1115            &BytesToBool::new(),
1116            Value::Bytes(Arc::from(&[][..])),
1117            "expected exactly 1 byte",
1118        );
1119    }
1120
1121    #[test]
1122    fn bytes_str_lowercase_hex() {
1123        let mut out = [Value::None];
1124        BytesToStr::new().eval(
1125            &[Value::Bytes(Arc::from(&[0xDE_u8, 0xAD, 0xBE, 0xEF][..]))],
1126            &mut out,
1127        );
1128        match &out[0] {
1129            Value::Str(s) => assert_eq!(&**s, "deadbeef"),
1130            other => panic!("expected Str, got {other:?}"),
1131        }
1132    }
1133
1134    #[test]
1135    fn bytes_vec_roundtrip() {
1136        // VecF32 round trip
1137        let mut out_b = [Value::None];
1138        VecF32ToBytes::new().eval(
1139            &[Value::VecF32(SliceArc::from_vec(vec![1.0_f32, 2.0, 3.0]))],
1140            &mut out_b,
1141        );
1142        let bytes = match &out_b[0] {
1143            Value::Bytes(b) => b.clone(),
1144            _ => panic!(),
1145        };
1146        let mut out_v = [Value::None];
1147        BytesToVecF32::new().eval(&[Value::Bytes(bytes)], &mut out_v);
1148        match &out_v[0] {
1149            Value::VecF32(arr) => assert_eq!(arr.as_ref(), &[1.0_f32, 2.0, 3.0]),
1150            other => panic!("expected VecF32, got {other:?}"),
1151        }
1152        // Bad length panics
1153        check_panics(
1154            &BytesToVecF32::new(),
1155            Value::Bytes(Arc::from(&[1u8, 2, 3][..])),
1156            "not a multiple of 4",
1157        );
1158    }
1159
1160    // -----------------------------------------------------------
1161    // Json ↔ X
1162    // -----------------------------------------------------------
1163
1164    #[test]
1165    fn json_scalar_roundtrip() {
1166        let mut out = [Value::None];
1167        U64ToJson::new().eval(&[Value::U64(42)], &mut out);
1168        let j = match &out[0] {
1169            Value::Json(j) => j.clone(),
1170            _ => panic!(),
1171        };
1172        let mut out_back = [Value::None];
1173        JsonToU64::new().eval(&[Value::Json(j)], &mut out_back);
1174        assert_eq!(out_back[0], Value::U64(42));
1175
1176        let mut out = [Value::None];
1177        BoolToJson::new().eval(&[Value::Bool(true)], &mut out);
1178        assert_eq!(out[0], Value::Json(Arc::new(serde_json::Value::Bool(true))));
1179    }
1180
1181    #[test]
1182    fn json_shape_panics() {
1183        check_panics(
1184            &JsonToU64::new(),
1185            Value::Json(Arc::new(serde_json::Value::String("abc".into()))),
1186            "is not a u64",
1187        );
1188        check_panics(
1189            &JsonToBool::new(),
1190            Value::Json(Arc::new(serde_json::Value::from(0))),
1191            "is not a bool",
1192        );
1193        check_panics(
1194            &JsonToVecF32::new(),
1195            Value::Json(Arc::new(serde_json::Value::Bool(false))),
1196            "is not an array",
1197        );
1198    }
1199
1200    #[test]
1201    fn json_bytes_via_hex() {
1202        let mut out_j = [Value::None];
1203        BytesToJson::new().eval(&[Value::Bytes(Arc::from(&[0xDE_u8, 0xAD][..]))], &mut out_j);
1204        let j = match &out_j[0] {
1205            Value::Json(j) => j.clone(),
1206            _ => panic!(),
1207        };
1208        assert_eq!(j.as_str(), Some("dead"));
1209        let mut out_b = [Value::None];
1210        JsonToBytes::new().eval(&[Value::Json(j)], &mut out_b);
1211        match &out_b[0] {
1212            Value::Bytes(b) => assert_eq!(b.as_ref(), &[0xDE_u8, 0xAD]),
1213            other => panic!("expected Bytes, got {other:?}"),
1214        }
1215    }
1216
1217    #[test]
1218    fn float_to_json_non_finite_panics() {
1219        check_panics(&F64ToJson::new(), Value::F64(f64::NAN), "non-finite");
1220    }
1221
1222    // -----------------------------------------------------------
1223    // Vec ↔ Vec / Vec → {Str, Json, Bytes}
1224    // -----------------------------------------------------------
1225
1226    #[test]
1227    fn vec_cast_lossless_and_lossy() {
1228        // i32 → f32 (lossless)
1229        let mut out = [Value::None];
1230        VecI32ToVecF32::new().eval(
1231            &[Value::VecI32(SliceArc::from_vec(vec![1_i32, -2, 3]))],
1232            &mut out,
1233        );
1234        match &out[0] {
1235            Value::VecF32(arr) => assert_eq!(arr.as_ref(), &[1.0_f32, -2.0, 3.0]),
1236            other => panic!("expected VecF32, got {other:?}"),
1237        }
1238
1239        // f32 → i32 (round, panic on out of range)
1240        let mut out = [Value::None];
1241        VecF32ToVecI32::new().eval(
1242            &[Value::VecF32(SliceArc::from_vec(vec![1.4_f32, 2.7, -3.5]))],
1243            &mut out,
1244        );
1245        match &out[0] {
1246            Value::VecI32(arr) => assert_eq!(arr.as_ref(), &[1_i32, 3, -4]),
1247            other => panic!("expected VecI32, got {other:?}"),
1248        }
1249        check_panics(
1250            &VecF32ToVecI32::new(),
1251            Value::VecF32(SliceArc::from_vec(vec![f32::INFINITY])),
1252            "non-finite",
1253        );
1254    }
1255
1256    #[test]
1257    fn vec_str_json_serialization() {
1258        let mut out = [Value::None];
1259        VecI32ToStr::new().eval(
1260            &[Value::VecI32(SliceArc::from_vec(vec![1_i32, 2, 3]))],
1261            &mut out,
1262        );
1263        match &out[0] {
1264            Value::Str(s) => assert_eq!(&**s, "[1,2,3]"),
1265            other => panic!("expected Str, got {other:?}"),
1266        }
1267
1268        let mut out = [Value::None];
1269        VecF32ToJson::new().eval(
1270            &[Value::VecF32(SliceArc::from_vec(vec![1.5_f32, 2.0]))],
1271            &mut out,
1272        );
1273        match &out[0] {
1274            Value::Json(j) => {
1275                assert!(j.is_array());
1276                let arr = j.as_array().unwrap();
1277                assert_eq!(arr[0].as_f64().unwrap() as f32, 1.5_f32);
1278                assert_eq!(arr[1].as_f64().unwrap() as f32, 2.0_f32);
1279            }
1280            other => panic!("expected Json, got {other:?}"),
1281        }
1282    }
1283}