Skip to main content

wolfram_serialize/
constants.rs

1//! WXF wire-format constants and enums.
2//!
3//! Mirrors `wolframclient/serializers/wxfencoder/constants.py`. Each public enum
4//! is `#[repr(u8)]` with discriminants equal to the byte that tags the value on
5//! the wire, so `value as u8` is the wire byte and `TryFrom<u8>` decodes one.
6
7use std::convert::TryFrom;
8
9//---- Internal byte values (private — callers use the enums below) ----
10
11const WXF_VERSION: u8 = b'8';
12const WXF_HEADER_SEPARATOR: u8 = b':';
13const WXF_HEADER_COMPRESS: u8 = b'C';
14
15const WXF_FUNCTION: u8 = b'f';
16const WXF_SYMBOL: u8 = b's';
17const WXF_STRING: u8 = b'S';
18const WXF_BYTE_ARRAY: u8 = b'B';
19const WXF_INTEGER8: u8 = b'C';
20const WXF_INTEGER16: u8 = b'j';
21const WXF_INTEGER32: u8 = b'i';
22const WXF_INTEGER64: u8 = b'L';
23const WXF_REAL64: u8 = b'r';
24const WXF_BIG_INTEGER: u8 = b'I';
25const WXF_BIG_REAL: u8 = b'R';
26const WXF_PACKED_ARRAY: u8 = 0xC1;
27const WXF_NUMERIC_ARRAY: u8 = 0xC2;
28const WXF_ASSOCIATION: u8 = b'A';
29const WXF_RULE: u8 = b'-';
30const WXF_RULE_DELAYED: u8 = b':';
31
32const WXF_ARRAY_INTEGER8: u8 = 0x00;
33const WXF_ARRAY_INTEGER16: u8 = 0x01;
34const WXF_ARRAY_INTEGER32: u8 = 0x02;
35const WXF_ARRAY_INTEGER64: u8 = 0x03;
36const WXF_ARRAY_UNSIGNED_INTEGER8: u8 = 0x10;
37const WXF_ARRAY_UNSIGNED_INTEGER16: u8 = 0x11;
38const WXF_ARRAY_UNSIGNED_INTEGER32: u8 = 0x12;
39const WXF_ARRAY_UNSIGNED_INTEGER64: u8 = 0x13;
40const WXF_ARRAY_REAL32: u8 = 0x22;
41const WXF_ARRAY_REAL64: u8 = 0x23;
42const WXF_ARRAY_COMPLEX_REAL32: u8 = 0x33;
43const WXF_ARRAY_COMPLEX_REAL64: u8 = 0x34;
44
45//---- Shared lookup tables (ExpressionEnum + NumericArrayEnum/PackedArrayEnum) ----
46//
47// Expression token bytes (0x2D–0xC2) and array element-type bytes (0x00–0x34)
48// do not overlap, so a single function covers both without ambiguity.
49// HeaderEnum bytes overlap with some expression bytes and are excluded.
50
51fn token_to_size_in_bytes(byte: u8) -> usize {
52    match byte {
53        WXF_ARRAY_INTEGER8 | WXF_ARRAY_UNSIGNED_INTEGER8 => 1,
54        WXF_ARRAY_INTEGER16 | WXF_ARRAY_UNSIGNED_INTEGER16 => 2,
55        WXF_ARRAY_INTEGER32 | WXF_ARRAY_UNSIGNED_INTEGER32 | WXF_ARRAY_REAL32 => 4,
56        WXF_ARRAY_INTEGER64
57        | WXF_ARRAY_UNSIGNED_INTEGER64
58        | WXF_ARRAY_REAL64
59        | WXF_ARRAY_COMPLEX_REAL32 => 8,
60        WXF_ARRAY_COMPLEX_REAL64 => 16,
61        _ => panic!("token_to_size_in_bytes: unknown byte 0x{:02X}", byte),
62    }
63}
64
65fn token_to_name(byte: u8) -> &'static str {
66    match byte {
67        WXF_FUNCTION => "Function",
68        WXF_SYMBOL => "Symbol",
69        WXF_STRING => "String",
70        WXF_BYTE_ARRAY => "ByteArray",
71        WXF_INTEGER8 => "Integer8",
72        WXF_INTEGER16 => "Integer16",
73        WXF_INTEGER32 => "Integer32",
74        WXF_INTEGER64 => "Integer64",
75        WXF_REAL64 => "Real64",
76        WXF_BIG_INTEGER => "BigInteger",
77        WXF_BIG_REAL => "BigReal",
78        WXF_PACKED_ARRAY => "PackedArray",
79        WXF_NUMERIC_ARRAY => "NumericArray",
80        WXF_ASSOCIATION => "Association",
81        WXF_RULE => "Rule",
82        WXF_RULE_DELAYED => "RuleDelayed",
83        WXF_ARRAY_INTEGER8 => "Integer8",
84        WXF_ARRAY_INTEGER16 => "Integer16",
85        WXF_ARRAY_INTEGER32 => "Integer32",
86        WXF_ARRAY_INTEGER64 => "Integer64",
87        WXF_ARRAY_UNSIGNED_INTEGER8 => "UnsignedInteger8",
88        WXF_ARRAY_UNSIGNED_INTEGER16 => "UnsignedInteger16",
89        WXF_ARRAY_UNSIGNED_INTEGER32 => "UnsignedInteger32",
90        WXF_ARRAY_UNSIGNED_INTEGER64 => "UnsignedInteger64",
91        WXF_ARRAY_REAL32 => "Real32",
92        WXF_ARRAY_REAL64 => "Real64",
93        WXF_ARRAY_COMPLEX_REAL32 => "ComplexReal32",
94        WXF_ARRAY_COMPLEX_REAL64 => "ComplexReal64",
95        _ => "<unknown>",
96    }
97}
98
99//======================================
100// HeaderEnum
101//======================================
102
103/// WXF framing header bytes. No Display — header bytes overlap with some
104/// expression token bytes and are not used in error messages.
105#[derive(Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive)]
106#[repr(u8)]
107pub enum HeaderEnum {
108    /// Version marker (`8`) — the first byte of every WXF stream.
109    Version = WXF_VERSION,
110    /// Header/body separator (`:`).
111    Separator = WXF_HEADER_SEPARATOR,
112    /// Compression flag (`C`) — present between version and separator when the
113    /// body is zlib-compressed.
114    Compress = WXF_HEADER_COMPRESS,
115}
116
117//======================================
118// ExpressionEnum — top-level WXF token
119//======================================
120
121/// Top-level WXF expression token. `#[repr(u8)]` discriminants are the wire bytes.
122#[derive(Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive)]
123#[repr(u8)]
124pub enum ExpressionEnum {
125    /// General expression `head[args…]`, written as a length-prefixed head plus
126    /// elements (`f`).
127    Function = WXF_FUNCTION,
128    /// A symbol such as `` System`Plus `` (`s`).
129    Symbol = WXF_SYMBOL,
130    /// A UTF-8 string (`S`).
131    String = WXF_STRING,
132    /// A raw byte buffer / `ByteArray` (`B`).
133    ByteArray = WXF_BYTE_ARRAY,
134    /// Machine integer that fits in 8 bits (`C`).
135    Integer8 = WXF_INTEGER8,
136    /// Machine integer that fits in 16 bits (`j`).
137    Integer16 = WXF_INTEGER16,
138    /// Machine integer that fits in 32 bits (`i`).
139    Integer32 = WXF_INTEGER32,
140    /// Machine integer that fits in 64 bits (`L`).
141    Integer64 = WXF_INTEGER64,
142    /// IEEE 754 double-precision real (`r`).
143    Real64 = WXF_REAL64,
144    /// Arbitrary-precision integer, encoded as its decimal digit string (`I`).
145    BigInteger = WXF_BIG_INTEGER,
146    /// Arbitrary-precision real, encoded as its textual representation (`R`).
147    BigReal = WXF_BIG_REAL,
148    /// A `PackedArray` of machine numbers (`0xC1`).
149    PackedArray = WXF_PACKED_ARRAY,
150    /// A `NumericArray` of fixed-width numbers (`0xC2`).
151    NumericArray = WXF_NUMERIC_ARRAY,
152    /// An `Association` of rules (`A`).
153    Association = WXF_ASSOCIATION,
154    /// A `Rule` (`->`) entry inside an association (`-`).
155    Rule = WXF_RULE,
156    /// A `RuleDelayed` (`:>`) entry inside an association (`:`).
157    RuleDelayed = WXF_RULE_DELAYED,
158}
159
160impl ExpressionEnum {
161    /// The Wolfram Language head this token decodes to (e.g. `"Integer"`,
162    /// `"Real"`, `"List"`), as used in error and diagnostic messages.
163    pub fn name(self) -> &'static str {
164        token_to_name(self as u8)
165    }
166}
167//======================================
168// NumericArrayEnum — element type
169//======================================
170
171/// WXF element-type tag for NumericArray. Discriminants are the WXF wire bytes.
172#[derive(
173    Debug,
174    Copy,
175    Clone,
176    PartialEq,
177    Eq,
178    PartialOrd,
179    Ord,
180    Hash,
181    num_enum::TryFromPrimitive
182)]
183#[repr(u8)]
184pub enum NumericArrayEnum {
185    /// Signed 8-bit integer elements (`i8`).
186    Integer8 = WXF_ARRAY_INTEGER8,
187    /// Signed 16-bit integer elements (`i16`).
188    Integer16 = WXF_ARRAY_INTEGER16,
189    /// Signed 32-bit integer elements (`i32`).
190    Integer32 = WXF_ARRAY_INTEGER32,
191    /// Signed 64-bit integer elements (`i64`).
192    Integer64 = WXF_ARRAY_INTEGER64,
193    /// Unsigned 8-bit integer elements (`u8`).
194    UnsignedInteger8 = WXF_ARRAY_UNSIGNED_INTEGER8,
195    /// Unsigned 16-bit integer elements (`u16`).
196    UnsignedInteger16 = WXF_ARRAY_UNSIGNED_INTEGER16,
197    /// Unsigned 32-bit integer elements (`u32`).
198    UnsignedInteger32 = WXF_ARRAY_UNSIGNED_INTEGER32,
199    /// Unsigned 64-bit integer elements (`u64`).
200    UnsignedInteger64 = WXF_ARRAY_UNSIGNED_INTEGER64,
201    /// 32-bit IEEE 754 float elements (`f32`).
202    Real32 = WXF_ARRAY_REAL32,
203    /// 64-bit IEEE 754 float elements (`f64`).
204    Real64 = WXF_ARRAY_REAL64,
205    /// Complex elements with 32-bit float real/imaginary parts.
206    ComplexReal32 = WXF_ARRAY_COMPLEX_REAL32,
207    /// Complex elements with 64-bit float real/imaginary parts.
208    ComplexReal64 = WXF_ARRAY_COMPLEX_REAL64,
209}
210
211impl NumericArrayEnum {
212    /// Size in bytes of a single element of this type (e.g. 1 for `Integer8`,
213    /// 16 for `ComplexReal64`).
214    pub fn size_in_bytes(self) -> usize {
215        token_to_size_in_bytes(self as u8)
216    }
217
218    /// The element type's Wolfram Language name (e.g. `"Integer8"`, `"Real64"`).
219    pub fn name(self) -> &'static str {
220        token_to_name(self as u8)
221    }
222}
223
224//======================================
225// PackedArrayEnum — element type (packed-compatible subset)
226//======================================
227
228/// WXF element-type tag for PackedArray. Same wire bytes as [`NumericArrayEnum`]
229/// but restricted to the packed-compatible variants (no unsigned integers).
230#[derive(
231    Debug,
232    Copy,
233    Clone,
234    PartialEq,
235    Eq,
236    PartialOrd,
237    Ord,
238    Hash,
239    num_enum::TryFromPrimitive
240)]
241#[repr(u8)]
242pub enum PackedArrayEnum {
243    /// Signed 8-bit integer elements (`i8`).
244    Integer8 = WXF_ARRAY_INTEGER8,
245    /// Signed 16-bit integer elements (`i16`).
246    Integer16 = WXF_ARRAY_INTEGER16,
247    /// Signed 32-bit integer elements (`i32`).
248    Integer32 = WXF_ARRAY_INTEGER32,
249    /// Signed 64-bit integer elements (`i64`).
250    Integer64 = WXF_ARRAY_INTEGER64,
251    /// 32-bit IEEE 754 float elements (`f32`).
252    Real32 = WXF_ARRAY_REAL32,
253    /// 64-bit IEEE 754 float elements (`f64`).
254    Real64 = WXF_ARRAY_REAL64,
255    /// Complex elements with 32-bit float real/imaginary parts.
256    ComplexReal32 = WXF_ARRAY_COMPLEX_REAL32,
257    /// Complex elements with 64-bit float real/imaginary parts.
258    ComplexReal64 = WXF_ARRAY_COMPLEX_REAL64,
259}
260
261impl PackedArrayEnum {
262    /// Size in bytes of a single element of this type (e.g. 8 for `Integer64`,
263    /// 8 for `Real64`).
264    pub fn size_in_bytes(self) -> usize {
265        token_to_size_in_bytes(self as u8)
266    }
267
268    /// The element type's Wolfram Language name (e.g. `"Integer64"`, `"Real64"`).
269    pub fn name(self) -> &'static str {
270        token_to_name(self as u8)
271    }
272}
273
274impl From<PackedArrayEnum> for NumericArrayEnum {
275    fn from(p: PackedArrayEnum) -> Self {
276        NumericArrayEnum::try_from(p as u8)
277            .expect("PackedArrayEnum byte is always valid NumericArrayEnum")
278    }
279}
280
281impl TryFrom<NumericArrayEnum> for PackedArrayEnum {
282    type Error = ();
283    fn try_from(n: NumericArrayEnum) -> Result<Self, ()> {
284        PackedArrayEnum::try_from(n as u8).map_err(|_| ())
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use std::convert::TryFrom;
292
293    #[test]
294    fn expression_enum_try_from_known() {
295        assert_eq!(
296            ExpressionEnum::try_from(WXF_ASSOCIATION),
297            Ok(ExpressionEnum::Association)
298        );
299    }
300
301    #[test]
302    fn expression_enum_try_from_invalid() {
303        assert!(ExpressionEnum::try_from(0xFF_u8).is_err());
304    }
305
306    #[test]
307    fn numeric_array_enum_try_from_known() {
308        assert_eq!(
309            NumericArrayEnum::try_from(WXF_ARRAY_INTEGER32),
310            Ok(NumericArrayEnum::Integer32)
311        );
312    }
313
314    #[test]
315    fn numeric_array_enum_try_from_invalid() {
316        assert!(NumericArrayEnum::try_from(0xFF_u8).is_err());
317    }
318
319    #[test]
320    fn packed_array_enum_rejects_unsigned() {
321        assert!(PackedArrayEnum::try_from(WXF_ARRAY_UNSIGNED_INTEGER8).is_err());
322    }
323
324    #[test]
325    fn packed_to_numeric_roundtrip() {
326        let p = PackedArrayEnum::Integer32;
327        let n = NumericArrayEnum::from(p);
328        assert_eq!(n, NumericArrayEnum::Integer32);
329    }
330}