rustre_core/
value.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
//! [`Value`] type, (mostly?) used for const-evaluation

use crate::types::Type;
use ecow::EcoVec;
use num_traits::ToPrimitive;
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};

/// Match statement with a single pattern that we are certain will match
macro_rules! unsafe_irrefutable_let {
    ($pattern:pat = $value:expr) => {
        let $pattern = $value else {
            #[cfg(debug_assertions)]
            unreachable!("this pattern shouldn't be reachable");
            #[cfg(not(debug_assertions))]
            unsafe {
                std::hint::unreachable_unchecked()
            };
        };
    };
}

/// A Lustre value with its type, stored as a compact opaque value
///
/// ### Usage
///
/// - Use [`Value::type_of()`] to get the type of the value
/// - Use [`Value::unpack()`] to inspect the contents of the value
///
/// ### Internal representation
///
/// [`Value`]s are usually stored as a tuple of [`Type`] × [`EcoVec<u8>`][EcoVec]\*. The
/// interpretation of the byte array is directly dependent on the type of the value. The
/// implementation may use unsafe code that assumes that the byte array has the correct length for a
/// given type.
///
/// <sup>\*The type is generic over both of these, so you can have a borrowed version of a
/// value</sup>
///
/// A given type always has a constant size that may be 0. The types respect the following encoding:
/// - `bool`s are encoded as 0 or 1, as a single byte
/// - `int`s are encoded as [i32] in native endianness
/// - `real`s are encoded as [f32] in native endianness
/// - Arrays are encoded as a continuous concatenation of its element's encodings
/// - Tuples are encoded the same way, but may obviously be heterogeneous
#[derive(Clone)]
pub struct Value<T = Type, B = EcoVec<u8>> {
    r#type: T,
    bytes: B,
}

/// Returns the size of a Lustre type for storage in the [`Value`]'s raw bytes
///
/// **This function may return 0**
fn size_of_type(typ: &Type) -> usize {
    match typ {
        Type::Unknown => unimplemented!(),
        Type::Boolean => 1,
        Type::Integer => 4,
        Type::Real => 4,
        Type::Array { elem, size } => size * size_of_type(elem),
        Type::Tuple(inner) => inner.iter().map(size_of_type).sum(),
    }
}

impl<T: Borrow<Type>, B> Value<T, B> {
    pub fn type_of(&self) -> &Type {
        self.r#type.borrow()
    }
}

impl<T: Borrow<Type>, B: AsRef<[u8]>> Value<T, B> {
    /// Construct a new [`Value`]
    ///
    /// ### Safety
    ///
    /// The bytes must match the exact encoding expected by the associated type.
    unsafe fn new_raw(r#type: T, bytes: B) -> Self {
        debug_assert_eq!(size_of_type(r#type.borrow()), bytes.as_ref().len());
        Self { r#type, bytes }
    }

    pub fn unpack(&self) -> UValue {
        let r#type = self.r#type.borrow();
        let bytes = self.bytes.as_ref();
        match r#type {
            Type::Unknown => unreachable!(),
            Type::Boolean => {
                unsafe_irrefutable_let!(Some([b @ 0..=1]) = bytes.get(..));
                if *b == 0 {
                    UValue::Bool(false)
                } else {
                    UValue::Bool(true)
                }
            }
            Type::Integer => {
                unsafe_irrefutable_let!(Some(&[a, b, c, d]) = bytes.get(..));
                UValue::Int(i32::from_ne_bytes([a, b, c, d]))
            }
            Type::Real => {
                unsafe_irrefutable_let!(Some(&[a, b, c, d]) = bytes.get(..));
                UValue::Real(decorum::N32::from_inner(f32::from_ne_bytes([a, b, c, d])))
            }
            array_type @ Type::Array { elem, size } => UValue::Array(UArray {
                element_type: array_type,
                element_size: size_of_type(elem),
                remaining_len: *size,
                total_len: *size,
                bytes,
            }),
            Type::Tuple(elements) => UValue::Tuple(UTuple {
                elements: elements.iter(),
                remaining_len: elements.len(),
                bytes,
            }),
        }
    }

    pub fn as_ref(&self) -> Value<&Type, &[u8]> {
        unsafe { Value::new_raw(self.r#type.borrow(), self.bytes.as_ref()) }
    }

    pub fn to_owned(&self) -> Value {
        Value {
            r#type: self.r#type.borrow().clone(),
            bytes: self.bytes.as_ref().into(),
        }
    }
}

impl Value {
    pub fn from_bool(value: bool) -> Self {
        unsafe { Self::new_raw(Type::Boolean, [value as u8].into()) }
    }

    pub fn from_int(value: i32) -> Self {
        unsafe { Self::new_raw(Type::Integer, value.to_ne_bytes().into()) }
    }

    pub fn from_real(value: decorum::N32) -> Self {
        unsafe { Self::new_raw(Type::Real, value.to_f32().unwrap().to_ne_bytes().into()) }
    }

    /// Create an array of `n` repeating elements of value `self`
    pub fn repeat(self, n: usize) -> Self {
        Self {
            r#type: Type::Array {
                elem: Box::new(self.r#type),
                size: n,
            },
            bytes: self.bytes.repeat(n).into(),
        }
    }
}

impl<T: Borrow<Type>, B: AsRef<[u8]>> Debug for Value<T, B> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(&self.unpack(), f)
    }
}

impl<T: Borrow<Type>, B: AsRef<[u8]>> Eq for Value<T, B> {}
impl<T: Borrow<Type>, B: AsRef<[u8]>> PartialEq for Value<T, B> {
    fn eq(&self, other: &Self) -> bool {
        self.r#type.borrow() == other.r#type.borrow() && self.bytes.as_ref() == other.bytes.as_ref()
    }
}

impl<T: Borrow<Type>, B: AsRef<[u8]>> Ord for Value<T, B> {
    fn cmp(&self, other: &Self) -> Ordering {
        Ord::cmp(self.r#type.borrow(), other.r#type.borrow())
            .then_with(|| Ord::cmp(&self.unpack(), &other.unpack()))
    }
}

impl<T: Borrow<Type>, B: AsRef<[u8]>> PartialOrd for Value<T, B> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(Ord::cmp(self, other))
    }
}

impl<T: Borrow<Type>, B: AsRef<[u8]>> Hash for Value<T, B> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        Hash::hash(self.r#type.borrow(), state);
        Hash::hash(self.bytes.as_ref(), state);
    }
}

/// Value type that has been "unpacked" on its first level
///
/// [`Value`] is quite opaque in itself due to how its memory representation was designed. To read
/// its contents, call [`Value::unpack`] and pattern-match on the returned [`UValue`].
///
/// Unpacked values are only unpacked on the "first level". For instance, an unpacked tuple will
/// only reveal that it is a tuple and what its size is, but its field will be packed [`Value`]s
/// that will need to be unpacked after (if needed). This makes the entire abstraction
/// allocation-free.
///
/// ### Note
///
/// _UValue is supposed to mean "unpacked value" but since we're goin to pattern match a lot with
/// it, I'd rather make it short to type._
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum UValue<'value> {
    Bool(bool),
    Int(i32),
    Real(decorum::N32),
    Array(UArray<'value>),
    Tuple(UTuple<'value>),
}

impl Debug for UValue<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            UValue::Bool(value) => Debug::fmt(value, f),
            UValue::Int(value) => Debug::fmt(value, f),
            UValue::Real(value) => Debug::fmt(value, f),
            UValue::Array(value) => {
                let mut f = f.debug_list();
                for element in value.clone() {
                    f.entry(&element);
                }
                f.finish()
            }
            UValue::Tuple(value) => {
                let mut f = f.debug_tuple("");
                for element in value.clone() {
                    f.field(&element);
                }
                f.finish()
            }
        }
    }
}

/// Iterator over the elements of an unpacked array, found within [`UValue::Array`]
#[derive(Clone, Eq)]
pub struct UArray<'value> {
    element_type: &'value Type,
    element_size: usize,
    remaining_len: usize,
    total_len: usize,
    bytes: &'value [u8],
}

impl<'value> UArray<'value> {
    pub fn element_type(&self) -> &Type {
        self.element_type
    }

    pub fn total_len(&self) -> usize {
        self.total_len
    }

    pub fn is_empty(&self) -> bool {
        self.remaining_len == 0
    }

    unsafe fn unsafe_advance(&mut self) -> Value<&'value Type, &'value [u8]> {
        let (element, rest) = self.bytes.split_at_unchecked(self.element_size);
        self.bytes = rest;
        self.remaining_len -= 1;
        Value::new_raw(self.element_type, element)
    }
}

impl<'value> Iterator for UArray<'value> {
    type Item = Value<&'value Type, &'value [u8]>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.is_empty() {
            None
        } else {
            Some(unsafe { self.unsafe_advance() })
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.total_len - self.bytes.len() / self.element_size;
        (remaining, Some(remaining))
    }
}

impl PartialEq for UArray<'_> {
    fn eq(&self, other: &Self) -> bool {
        Iterator::eq(self.clone(), other.clone())
    }
}

impl Ord for UArray<'_> {
    fn cmp(&self, other: &Self) -> Ordering {
        Iterator::cmp(self.clone(), other.clone())
    }
}

impl PartialOrd for UArray<'_> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(Ord::cmp(self, other))
    }
}

impl Hash for UArray<'_> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        Hash::hash(&self.element_type, state);
        Hash::hash(&self.bytes, state);
    }
}

/// Iterator over the elements of an unpacked tuple, found within [`UValue::Tuple`]
#[derive(Clone)]
pub struct UTuple<'value> {
    elements: std::slice::Iter<'value, Type>,
    remaining_len: usize,
    bytes: &'value [u8],
}

impl<'value> UTuple<'value> {
    pub fn is_empty(&self) -> bool {
        self.remaining_len == 0
    }

    unsafe fn unsafe_advance(&mut self) -> Value<&'value Type, &'value [u8]> {
        unsafe_irrefutable_let!(Some(next_element) = self.elements.next());
        let (element, rest) = self.bytes.split_at_unchecked(size_of_type(next_element));
        self.bytes = rest;
        self.remaining_len -= 1;
        Value::new_raw(next_element, element)
    }
}

impl<'value> Iterator for UTuple<'value> {
    type Item = Value<&'value Type, &'value [u8]>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.is_empty() {
            None
        } else {
            Some(unsafe { self.unsafe_advance() })
        }
    }
}

impl Eq for UTuple<'_> {}
impl PartialEq for UTuple<'_> {
    fn eq(&self, other: &Self) -> bool {
        Iterator::eq(self.clone(), other.clone())
    }
}

impl Ord for UTuple<'_> {
    fn cmp(&self, other: &Self) -> Ordering {
        Iterator::cmp(self.clone(), other.clone())
    }
}

impl PartialOrd for UTuple<'_> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(Ord::cmp(self, other))
    }
}

impl Hash for UTuple<'_> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        for element in self.clone() {
            Hash::hash(&element, state);
        }
    }
}