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}