Skip to main content

serpent_serializer/
value.rs

1//! The Lua value model.
2//!
3//! [`Value`] mirrors the Lua types serpent can serialize: nil, booleans,
4//! numbers, strings, tables, functions, and named globals. Tables use
5//! reference-counted interior mutability so shared and cyclic graphs keep their
6//! identity across a serialize and deserialize round trip.
7
8use std::cell::RefCell;
9use std::rc::Rc;
10
11/// A Lua value.
12///
13/// Numbers are `f64`, matching Lua's default number type. Strings hold raw
14/// bytes because Lua strings are byte sequences and may contain NUL or other
15/// control characters. Tables are shared handles so two references to the same
16/// table compare equal by identity.
17///
18/// Equality follows Lua. Scalars compare by value. `NaN` is not equal to itself.
19/// Tables, functions, and globals compare by identity, not by contents.
20#[derive(Clone, Debug, PartialEq)]
21pub enum Value {
22    /// Lua `nil`.
23    Nil,
24    /// Lua boolean.
25    Bool(bool),
26    /// Lua number. Serialized with `numformat`, or as `1/0`, `-1/0`, `0/0` for
27    /// the special values.
28    Number(f64),
29    /// Lua string, stored as raw bytes.
30    Str(Vec<u8>),
31    /// Lua table with an array part and a hash part.
32    Table(Table),
33    /// A Lua function. Held opaquely by an identity id. The serializer emits a
34    /// bytecode-reload stub or, under `nocode`, an empty body.
35    Function(Func),
36    /// A value known by a global name, such as `print` or `io.stdin`. The
37    /// serializer emits the name verbatim.
38    Global(Global),
39}
40
41/// A shared table handle.
42///
43/// Cloning a `Table` shares the same underlying storage, so identity is
44/// preserved. Use [`Table::new`] to build one and the insertion helpers to fill
45/// it.
46///
47/// Equality is by identity. Two `Table` handles are equal when they point at the
48/// same storage, matching Lua reference semantics. Contents are not compared.
49///
50/// The storage is private. Read contents through [`Table::get`],
51/// [`Table::entries`], or [`Table::with_entries`], and write through [`push`],
52/// [`set`], and the meta setters.
53///
54/// [`push`]: Table::push
55/// [`set`]: Table::set
56#[derive(Clone, Debug)]
57pub struct Table(Rc<RefCell<TableData>>);
58
59impl PartialEq for Table {
60    fn eq(&self, other: &Self) -> bool {
61        self.id() == other.id()
62    }
63}
64
65/// A table key. Only hashable Lua values can be keys.
66///
67/// Numbers, booleans, and strings hash by value. Tables and functions hash by
68/// identity, matching Lua reference semantics.
69///
70/// Equality follows the same split. Scalar keys compare by value, reference keys
71/// by identity. [`Key::same`] is the same relation expressed as a method.
72#[derive(Clone, Debug, PartialEq)]
73pub enum Key {
74    /// Boolean key.
75    Bool(bool),
76    /// Numeric key.
77    Number(f64),
78    /// String key.
79    Str(Vec<u8>),
80    /// Table key, tracked by identity.
81    Table(Table),
82    /// Function key, tracked by identity.
83    Function(Func),
84    /// Global-named key such as `io.stdin`.
85    Global(Global),
86}
87
88/// A metamethod. Returns the replacement value, or a [`MetaError`] to model a
89/// metamethod that raises. The serializer treats an error as "the metamethod did
90/// not apply" and serializes the table as itself.
91pub type MetaFn = Rc<dyn Fn() -> Result<Value, MetaError>>;
92
93/// An error raised by a `__serialize` or `__tostring` metamethod.
94///
95/// Carries the message the metamethod would raise with. The serializer ignores
96/// the error and falls back to serializing the table directly, but the message
97/// is available to callers that inspect it.
98#[derive(Clone, Debug, PartialEq, Eq)]
99pub struct MetaError {
100    /// The error message.
101    pub message: String,
102}
103
104impl MetaError {
105    /// Build a metamethod error with the given message.
106    #[must_use]
107    pub fn new(message: impl Into<String>) -> Self {
108        MetaError {
109            message: message.into(),
110        }
111    }
112}
113
114impl std::fmt::Display for MetaError {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "metamethod error: {}", self.message)
117    }
118}
119
120impl std::error::Error for MetaError {}
121
122/// The backing store for a table.
123///
124/// `entries` keeps insertion order for hash keys, which stands in for Lua's raw
125/// `pairs` order. The optional metamethods model `__serialize` and `__tostring`.
126#[derive(Default)]
127pub struct TableData {
128    /// Ordered key/value pairs, including integer keys.
129    pub entries: Vec<(Key, Value)>,
130    /// `__serialize` metamethod. When present and successful, its result
131    /// replaces the table during serialization.
132    pub serialize_meta: Option<MetaFn>,
133    /// `__tostring` metamethod. Used when `__serialize` is absent, gated by the
134    /// `metatostring` option.
135    pub tostring_meta: Option<MetaFn>,
136}
137
138impl std::fmt::Debug for TableData {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        f.debug_struct("TableData")
141            .field("entries", &self.entries)
142            .field("has_serialize_meta", &self.serialize_meta.is_some())
143            .field("has_tostring_meta", &self.tostring_meta.is_some())
144            .finish()
145    }
146}
147
148/// An opaque Lua function.
149///
150/// `id` gives identity for shared-reference tracking. `bytecode`, when present,
151/// is the `string.dump` output the serializer quotes into a reload stub.
152///
153/// Equality is by `id`. Two functions with the same identity are equal, matching
154/// Lua reference semantics. The bytecode is not compared.
155#[derive(Clone, Debug)]
156pub struct Func {
157    /// Identity of this function.
158    pub id: usize,
159    /// Optional dumped bytecode. `None` means the function cannot be dumped and
160    /// falls back to a global name or placeholder.
161    pub bytecode: Option<Vec<u8>>,
162}
163
164impl PartialEq for Func {
165    fn eq(&self, other: &Self) -> bool {
166        self.id == other.id
167    }
168}
169
170/// A value referenced by a global name.
171///
172/// `name` is the Lua expression that reaches it, such as `print` or `io.stdin`.
173/// `id` gives identity so two references to the same global stay shared.
174///
175/// Equality is by `id`. Two globals with the same identity are equal, matching
176/// Lua reference semantics. The name is not compared.
177#[derive(Clone, Debug)]
178pub struct Global {
179    /// The global name to emit.
180    pub name: String,
181    /// Identity of this global object.
182    pub id: usize,
183}
184
185impl PartialEq for Global {
186    fn eq(&self, other: &Self) -> bool {
187        self.id == other.id
188    }
189}
190
191impl Table {
192    /// Create an empty table.
193    #[must_use]
194    pub fn new() -> Self {
195        Table(Rc::new(RefCell::new(TableData::default())))
196    }
197
198    /// Append a value to the array part at the next integer index.
199    pub fn push(&self, v: Value) {
200        let n = self.array_len_raw() + 1;
201        self.set(Key::Number(n as f64), v);
202    }
203
204    /// Set `key` to `value`. Replaces an existing entry with an equal key.
205    pub fn set(&self, key: Key, value: Value) {
206        let mut d = self.0.borrow_mut();
207        if let Some(slot) = d.entries.iter_mut().find(|(k, _)| k.same(&key)) {
208            slot.1 = value;
209        } else {
210            d.entries.push((key, value));
211        }
212    }
213
214    /// Number of contiguous integer keys starting at 1.
215    fn array_len_raw(&self) -> usize {
216        let d = self.0.borrow();
217        let mut n = 0usize;
218        loop {
219            let want = (n + 1) as f64;
220            if d.entries.iter().any(|(k, _)| match k {
221                Key::Number(x) => *x == want,
222                _ => false,
223            }) {
224                n += 1;
225            } else {
226                return n;
227            }
228        }
229    }
230
231    /// The Lua `#` length: a border of the array part. This model uses the
232    /// contiguous integer prefix from 1.
233    #[must_use]
234    pub fn border(&self) -> usize {
235        self.array_len_raw()
236    }
237
238    /// True when the table has no entries.
239    #[must_use]
240    pub fn is_empty(&self) -> bool {
241        self.0.borrow().entries.is_empty()
242    }
243
244    /// Raw pointer used for identity comparison and hashing.
245    #[must_use]
246    pub fn id(&self) -> usize {
247        Rc::as_ptr(&self.0) as usize
248    }
249
250    /// Set the `__serialize` metamethod.
251    pub fn set_serialize_meta(&self, f: MetaFn) {
252        self.0.borrow_mut().serialize_meta = Some(f);
253    }
254
255    /// Set the `__tostring` metamethod.
256    pub fn set_tostring_meta(&self, f: MetaFn) {
257        self.0.borrow_mut().tostring_meta = Some(f);
258    }
259
260    /// The `__serialize` metamethod, if set.
261    #[must_use]
262    pub fn serialize_meta(&self) -> Option<MetaFn> {
263        self.0.borrow().serialize_meta.clone()
264    }
265
266    /// The `__tostring` metamethod, if set.
267    #[must_use]
268    pub fn tostring_meta(&self) -> Option<MetaFn> {
269        self.0.borrow().tostring_meta.clone()
270    }
271
272    /// The value for `key`, if present. Compares keys with [`Key::same`].
273    #[must_use]
274    pub fn get(&self, key: &Key) -> Option<Value> {
275        self.0
276            .borrow()
277            .entries
278            .iter()
279            .find(|(k, _)| k.same(key))
280            .map(|(_, v)| v.clone())
281    }
282
283    /// A snapshot of the key/value pairs in insertion order.
284    ///
285    /// This clones the entries. For a read-only pass without a clone, use
286    /// [`Table::with_entries`].
287    #[must_use]
288    pub fn entries(&self) -> Vec<(Key, Value)> {
289        self.0.borrow().entries.clone()
290    }
291
292    /// Run `f` over a borrow of the key/value pairs and return its result.
293    ///
294    /// This avoids cloning the entries when a read pass is enough. The borrow is
295    /// held for the duration of `f`, so `f` must not call back into methods that
296    /// borrow the same table mutably.
297    pub fn with_entries<R>(&self, f: impl FnOnce(&[(Key, Value)]) -> R) -> R {
298        f(&self.0.borrow().entries)
299    }
300}
301
302impl Default for Table {
303    fn default() -> Self {
304        Table::new()
305    }
306}
307
308impl Key {
309    /// Identity or value equality used to dedupe keys within one table.
310    ///
311    /// Equivalent to `self == other`. Kept as a named method for call sites that
312    /// read better with it.
313    #[must_use]
314    pub fn same(&self, other: &Key) -> bool {
315        self == other
316    }
317
318    /// Convert this key back to a value for serialization.
319    #[must_use]
320    pub fn to_value(&self) -> Value {
321        match self {
322            Key::Bool(b) => Value::Bool(*b),
323            Key::Number(n) => Value::Number(*n),
324            Key::Str(s) => Value::Str(s.clone()),
325            Key::Table(t) => Value::Table(t.clone()),
326            Key::Function(f) => Value::Function(f.clone()),
327            Key::Global(g) => Value::Global(g.clone()),
328        }
329    }
330}
331
332/// A hashable identity for any value, used to track shared references.
333///
334/// Scalars hash by value. Tables, functions, and globals hash by identity.
335#[derive(Clone, PartialEq, Eq, Hash, Debug)]
336pub enum Ident {
337    /// Table identity.
338    Table(usize),
339    /// Function identity.
340    Function(usize),
341    /// Global identity.
342    Global(usize),
343}
344
345impl Value {
346    /// The identity of a reference value, if it has one.
347    ///
348    /// Only tables, functions, and globals participate in shared-reference
349    /// tracking. Scalars return `None`.
350    #[must_use]
351    pub fn ident(&self) -> Option<Ident> {
352        match self {
353            Value::Table(t) => Some(Ident::Table(t.id())),
354            Value::Function(f) => Some(Ident::Function(f.id)),
355            Value::Global(g) => Some(Ident::Global(g.id)),
356            _ => None,
357        }
358    }
359}