Skip to main content

wolfram_serialize/
from_wxf.rs

1//! [`FromWXF`] — pull-based typed deserialization from a [`WxfReader`].
2//!
3//! Lifetime-parameterized like serde's `Deserialize<'de>`: `'de` is the input
4//! buffer's lifetime. **Owned** types implement `FromWXF<'de>` for *every* `'de`
5//! (they borrow nothing); **borrowed** types (`&'de str`, `&'de [u8]`, and
6//! derived structs with reference fields) tie to a specific `'de` and read
7//! zero-copy straight out of the buffer.
8//!
9//! Peek-free by construction: every value begins with one expression token, read
10//! once via [`WxfReader::read_expr_token`] and threaded into
11//! [`FromWXF::from_wxf_with_tag`].
12
13use std::collections::{BTreeMap, HashMap};
14
15use crate::constants::ExpressionEnum;
16use crate::reader::Reader;
17use crate::wxf::reader::WxfReader;
18use crate::Error;
19
20/// Deserialize a typed value by pulling tokens from a [`WxfReader`].
21///
22/// Implement this trait manually for fine-grained control, or derive it with
23/// `#[derive(FromWXF)]` for structs and enums:
24///
25/// ```
26/// use wolfram_serialize::{FromWXF, ToWXF, to_wxf, from_wxf};
27///
28/// #[derive(ToWXF, FromWXF, PartialEq, Debug)]
29/// struct Point {
30///     x: f64,
31///     y: f64,
32/// }
33///
34/// let original = Point { x: 1.0, y: 2.0 };
35/// let bytes = to_wxf(&original, None).unwrap();
36/// let roundtrip: Point = from_wxf(&bytes).unwrap();
37/// assert_eq!(original, roundtrip);
38/// ```
39///
40/// Structs with `&'de str` or `&'de [u8]` fields borrow directly from the
41/// input buffer (zero-copy). Because the borrow is tied to the input, read
42/// them inside a [`read_wxf`][crate::read_wxf] closure rather than returning them:
43///
44/// ```
45/// use wolfram_serialize::{FromWXF, ToWXF, to_wxf, read_wxf};
46///
47/// // Owned counterpart used for encoding
48/// #[derive(ToWXF)]
49/// struct Dataset {
50///     name: String,
51///     values: Vec<f64>,
52/// }
53///
54/// // Borrowed counterpart — borrows `name` from the input buffer
55/// #[derive(FromWXF)]
56/// struct DatasetRef<'a> {
57///     name: &'a str,
58///     values: Vec<f64>,
59/// }
60///
61/// let ds = Dataset { name: "test".into(), values: vec![1.0, 2.0] };
62/// let bytes = to_wxf(&ds, None).unwrap();
63/// read_wxf(&bytes, |r| {
64///     let ds_ref = DatasetRef::from_wxf(r)?;
65///     assert_eq!(ds_ref.name, "test");  // borrowed, no alloc
66///     Ok(())
67/// }).unwrap();
68/// ```
69///
70/// `'de` is the lifetime of the input buffer. Owned types implement
71/// `FromWXF<'de>` for every `'de`; borrowed types name it in `Self`.
72pub trait FromWXF<'de>: Sized {
73    /// Read a complete value: its expression token, then its body.
74    fn from_wxf<R: Reader<'de>>(r: &mut WxfReader<R>) -> Result<Self, Error> {
75        let tok = r.read_expr_token()?;
76        Self::from_wxf_with_tag(r, tok)
77    }
78
79    #[doc(hidden)]
80    fn from_wxf_with_tag<R: Reader<'de>>(
81        r: &mut WxfReader<R>,
82        tok: ExpressionEnum,
83    ) -> Result<Self, Error>;
84}
85
86/// Build a `Deserialize` error tagged with a path. Used by the derive.
87pub fn err_at(path: impl Into<String>, expected: &'static str, got: String) -> Error {
88    Error::Deserialize {
89        path: path.into(),
90        expected: expected,
91        got: got,
92    }
93}
94
95// FromWXF impls for the `wolfram-expr` value types (Expr, Symbol, Association,
96// NumericArray, PackedArray, BigInteger, BigReal) live in `wolfram-expr`, which
97// depends on this crate.
98
99//==============================================================================
100// Borrowed (zero-copy) primitives
101//==============================================================================
102
103impl<'de> FromWXF<'de> for &'de str {
104    fn from_wxf_with_tag<R: Reader<'de>>(
105        r: &mut WxfReader<R>,
106        tok: ExpressionEnum,
107    ) -> Result<Self, Error> {
108        if tok != ExpressionEnum::String {
109            return Err(Error::unexpected_token(&["String"], tok));
110        }
111        r.read_str()
112    }
113}
114
115impl<'de> FromWXF<'de> for &'de [u8] {
116    fn from_wxf_with_tag<R: Reader<'de>>(
117        r: &mut WxfReader<R>,
118        tok: ExpressionEnum,
119    ) -> Result<Self, Error> {
120        if tok != ExpressionEnum::ByteArray {
121            return Err(Error::unexpected_token(&["ByteArray"], tok));
122        }
123        r.read_byte_array()
124    }
125}
126
127//==============================================================================
128// Primitive scalars (owned — generic over 'de)
129//==============================================================================
130
131// One macro for all numeric scalars. Each call site lists *exactly* the wire
132// tokens that fit in the target type without any runtime range check or silent
133// truncation. Tokens are matched directly to their read method via a helper.
134//
135// Helper: map a wire-token identifier to its WxfReader read call.
136macro_rules! read_wire {
137    (Integer8,  $r:expr) => {
138        $r.read_i8()? as _
139    };
140    (Integer16, $r:expr) => {
141        $r.read_i16()? as _
142    };
143    (Integer32, $r:expr) => {
144        $r.read_i32()? as _
145    };
146    (Integer64, $r:expr) => {
147        $r.read_i64()? as _
148    };
149    (Real64,    $r:expr) => {
150        $r.read_f64()? as _
151    };
152}
153
154macro_rules! impl_numeric_from_wxf {
155    ($t:ty, [$($tok:ident),+]) => {
156        impl<'de> FromWXF<'de> for $t {
157            fn from_wxf_with_tag<R: Reader<'de>>(
158                r: &mut WxfReader<R>,
159                tok: ExpressionEnum,
160            ) -> Result<Self, Error> {
161                match tok {
162                    $(ExpressionEnum::$tok => Ok(read_wire!($tok, r)),)+
163                    other => Err(Error::unexpected_token(
164                        &[$(stringify!($tok)),+],
165                        other,
166                    )),
167                }
168            }
169        }
170    };
171}
172
173// Signed integers: accept only wire tokens whose values always fit (same or
174// smaller width). No runtime range check — the type of the wire read guarantees it.
175impl_numeric_from_wxf!(i8, [Integer8]);
176impl_numeric_from_wxf!(i16, [Integer8, Integer16]);
177impl_numeric_from_wxf!(i32, [Integer8, Integer16, Integer32]);
178impl_numeric_from_wxf!(i64, [Integer8, Integer16, Integer32, Integer64]);
179
180// Floats: accept integer wire tokens whose bit width fits in the mantissa, plus
181// Real64 (the only real wire type — f32 narrows it, unavoidably).
182// f32 mantissa = 23 bits: i8 (7-bit) and i16 (15-bit) fit; i32 (31-bit) does not.
183// f64 mantissa = 52 bits: i8, i16, i32 (31-bit) fit; i64 (63-bit) does not.
184impl_numeric_from_wxf!(f32, [Integer8, Integer16]);
185impl_numeric_from_wxf!(f64, [Integer8, Integer16, Integer32, Real64]);
186
187impl<'de> FromWXF<'de> for bool {
188    fn from_wxf_with_tag<R: Reader<'de>>(
189        r: &mut WxfReader<R>,
190        tok: ExpressionEnum,
191    ) -> Result<Self, Error> {
192        if tok != ExpressionEnum::Symbol {
193            return Err(Error::unexpected_token(&["Symbol"], tok));
194        }
195        match r.read_str()? {
196            "System`True" => Ok(true),
197            "System`False" => Ok(false),
198            other => Err(Error::UnexpectedSymbol {
199                expected: vec!["System`True", "System`False"],
200                got: other.to_owned(),
201            }),
202        }
203    }
204}
205
206// Owned `String` is the borrowed `&str` read + a copy.
207impl<'de> FromWXF<'de> for String {
208    fn from_wxf_with_tag<R: Reader<'de>>(
209        r: &mut WxfReader<R>,
210        tok: ExpressionEnum,
211    ) -> Result<Self, Error> {
212        <&str as FromWXF<'de>>::from_wxf_with_tag(r, tok).map(str::to_owned)
213    }
214}
215
216//==============================================================================
217// Containers
218//==============================================================================
219
220impl<'de> FromWXF<'de> for () {
221    fn from_wxf_with_tag<R: Reader<'de>>(
222        r: &mut WxfReader<R>,
223        tok: ExpressionEnum,
224    ) -> Result<Self, Error> {
225        if tok != ExpressionEnum::Symbol {
226            return Err(Error::unexpected_token(&["Symbol"], tok));
227        }
228        match r.read_str()? {
229            "Null" | "System`Null" => Ok(()),
230            other => Err(Error::UnexpectedSymbol {
231                expected: vec!["Null", "System`Null"],
232                got: other.to_owned(),
233            }),
234        }
235    }
236}
237
238// Option<T> / Result<T, E> read the same enum-association format the derive
239// emits (and that `Option`/`Result` ToWXF writes) — via the shared
240// `read_enum_header` helper.
241impl<'de, T: FromWXF<'de>> FromWXF<'de> for Option<T> {
242    fn from_wxf_with_tag<R: Reader<'de>>(
243        r: &mut WxfReader<R>,
244        tok: ExpressionEnum,
245    ) -> Result<Self, Error> {
246        let (_n, variant) = crate::strategy::read_enum_header(r, tok)?;
247        match variant.as_str() {
248            "None" => Ok(None),
249            "Some" => Ok(Some(T::from_wxf(r)?)),
250            other => Err(Error::UnexpectedSymbol {
251                expected: vec!["None", "Some"],
252                got: other.to_owned(),
253            }),
254        }
255    }
256}
257
258impl<'de, T: FromWXF<'de>, E: FromWXF<'de>> FromWXF<'de> for Result<T, E> {
259    fn from_wxf_with_tag<R: Reader<'de>>(
260        r: &mut WxfReader<R>,
261        tok: ExpressionEnum,
262    ) -> Result<Self, Error> {
263        let (_n, variant) = crate::strategy::read_enum_header(r, tok)?;
264        match variant.as_str() {
265            "Ok" => Ok(Ok(T::from_wxf(r)?)),
266            "Err" => Ok(Err(E::from_wxf(r)?)),
267            other => Err(Error::UnexpectedSymbol {
268                expected: vec!["Ok", "Err"],
269                got: other.to_owned(),
270            }),
271        }
272    }
273}
274
275impl<'de, K, V> FromWXF<'de> for HashMap<K, V>
276where
277    K: FromWXF<'de> + Eq + std::hash::Hash,
278    V: FromWXF<'de>,
279{
280    fn from_wxf_with_tag<R: Reader<'de>>(
281        r: &mut WxfReader<R>,
282        tok: ExpressionEnum,
283    ) -> Result<Self, Error> {
284        if tok != ExpressionEnum::Association {
285            return Err(Error::unexpected_token(&["Association"], tok));
286        }
287        let n = r.read_varint()?;
288        let mut out = HashMap::with_capacity(crate::capped_capacity(n as usize));
289        for _ in 0..n {
290            let _delayed = r.read_rule()?;
291            let k = K::from_wxf(r)?;
292            let v = V::from_wxf(r)?;
293            out.insert(k, v);
294        }
295        Ok(out)
296    }
297}
298
299impl<'de, K, V> FromWXF<'de> for BTreeMap<K, V>
300where
301    K: FromWXF<'de> + Ord,
302    V: FromWXF<'de>,
303{
304    fn from_wxf_with_tag<R: Reader<'de>>(
305        r: &mut WxfReader<R>,
306        tok: ExpressionEnum,
307    ) -> Result<Self, Error> {
308        if tok != ExpressionEnum::Association {
309            return Err(Error::unexpected_token(&["Association"], tok));
310        }
311        let n = r.read_varint()?;
312        let mut out = BTreeMap::new();
313        for _ in 0..n {
314            let _delayed = r.read_rule()?;
315            let k = K::from_wxf(r)?;
316            let v = V::from_wxf(r)?;
317            out.insert(k, v);
318        }
319        Ok(out)
320    }
321}
322
323//==============================================================================
324// Vec<T>
325//==============================================================================
326
327// Owned `Vec<u8>` (= `wolfram_expr::ByteArray`) is the borrowed `&[u8]` read + a copy.
328impl<'de> FromWXF<'de> for Vec<u8> {
329    fn from_wxf_with_tag<R: Reader<'de>>(
330        r: &mut WxfReader<R>,
331        tok: ExpressionEnum,
332    ) -> Result<Self, Error> {
333        <&[u8] as FromWXF<'de>>::from_wxf_with_tag(r, tok).map(<[u8]>::to_vec)
334    }
335}
336
337// `Vec<numeric>` for the 9 non-u8 numeric primitives, via the widening helpers.
338macro_rules! impl_vec_numeric_from_wxf {
339    ($($t:ty),+ $(,)?) => {
340        $(
341            impl<'de> FromWXF<'de> for Vec<$t> {
342                fn from_wxf_with_tag<R: Reader<'de>>(r: &mut WxfReader<R>, tok: ExpressionEnum) -> Result<Self, Error> {
343                    crate::numeric_in::read_vec_with_tag::<$t, R>(r, tok, "")
344                }
345            }
346        )+
347    };
348}
349impl_vec_numeric_from_wxf!(i8, i16, i32, i64, u16, u32, u64, f32, f64);
350
351// `Vec<T>` for derived structs/enums → `Function[List, …]`.
352impl<'de, T: FromWXF<'de> + crate::to_wxf::WxfStruct> FromWXF<'de> for Vec<T> {
353    fn from_wxf_with_tag<R: Reader<'de>>(
354        r: &mut WxfReader<R>,
355        tok: ExpressionEnum,
356    ) -> Result<Self, Error> {
357        if tok != ExpressionEnum::Function {
358            return Err(Error::unexpected_token(&["Function"], tok));
359        }
360        let n = r.read_varint()?;
361        r.skip()?; // discard head (any head accepted)
362        let mut items = Vec::with_capacity(crate::capped_capacity(n as usize));
363        for _ in 0..n {
364            items.push(T::from_wxf(r)?);
365        }
366        Ok(items)
367    }
368}