Skip to main content

toml_spanner/
table.rs

1#[cfg(test)]
2#[path = "./table_tests.rs"]
3mod tests;
4
5use crate::Span;
6use crate::value::{
7    FLAG_HEADER, FLAG_MASK, FLAG_SHIFT, FLAG_TABLE, Item, Key, MaybeItem, NONE, TAG_MASK,
8    TAG_SHIFT, TAG_TABLE,
9};
10use std::mem::size_of;
11use std::ptr::NonNull;
12
13use crate::arena::Arena;
14
15type TableEntry<'de> = (Key<'de>, Item<'de>);
16
17const MIN_CAP: u32 = 2;
18
19/// A TOML table: a flat list of key-value pairs with linear lookup.
20#[repr(C, align(8))]
21pub(crate) struct InnerTable<'de> {
22    len: u32,
23    cap: u32,
24    ptr: NonNull<TableEntry<'de>>,
25}
26
27impl<'de> InnerTable<'de> {
28    /// Creates an empty table.
29    #[inline]
30    pub fn new() -> Self {
31        Self {
32            len: 0,
33            cap: 0,
34            ptr: NonNull::dangling(),
35        }
36    }
37
38    /// Inserts a key-value pair. Does **not** check for duplicates.
39    pub fn insert(
40        &mut self,
41        key: Key<'de>,
42        item: Item<'de>,
43        arena: &'de Arena,
44    ) -> &mut TableEntry<'de> {
45        let len = self.len;
46        if self.len == self.cap {
47            self.grow(arena);
48        }
49        // SAFETY: grow() ensures len < cap, so ptr.add(len) is within the
50        // allocation. The write targets uninitialized memory past the current length.
51        unsafe {
52            let ptr = self.ptr.as_ptr().add(len as usize);
53            ptr.write((key, item));
54            self.len = len + 1;
55            &mut (*ptr)
56        }
57    }
58
59    /// Returns the number of entries.
60    #[inline]
61    pub fn len(&self) -> usize {
62        self.len as usize
63    }
64
65    /// Returns `true` if the table has no entries.
66    #[inline]
67    pub fn is_empty(&self) -> bool {
68        self.len == 0
69    }
70
71    /// Linear scan for a key, returning both key and value references.
72    pub fn get_entry(&self, name: &str) -> Option<(&Key<'de>, &Item<'de>)> {
73        for entry in self.entries() {
74            if entry.0.name == name {
75                return Some((&entry.0, &entry.1));
76            }
77        }
78        None
79    }
80
81    /// Returns a reference to the value for `name`.
82    pub fn get(&self, name: &str) -> Option<&Item<'de>> {
83        for entry in self.entries() {
84            if entry.0.name == name {
85                return Some(&entry.1);
86            }
87        }
88        None
89    }
90
91    /// Returns a mutable reference to the value for `name`.
92    pub fn get_mut(&mut self, name: &str) -> Option<&mut Item<'de>> {
93        for entry in self.entries_mut() {
94            if entry.0.name == name {
95                return Some(&mut entry.1);
96            }
97        }
98        None
99    }
100
101    /// Returns `true` if the table contains the key.
102    #[inline]
103    pub fn contains_key(&self, name: &str) -> bool {
104        self.get(name).is_some()
105    }
106
107    /// Removes the first entry matching `name`, returning the key-value pair.
108    /// Uses swap-remove, so the ordering of remaining entries may change.
109    pub fn remove_entry(&mut self, name: &str) -> Option<(Key<'de>, Item<'de>)> {
110        let idx = self.find_index(name)?;
111        Some(self.remove_at(idx))
112    }
113
114    /// Returns the span start of the first key. Used as a table discriminator
115    /// in the parser's hash index.
116    ///
117    /// # Safety
118    ///
119    /// The table must be non-empty (`self.len > 0`).
120    #[inline]
121    pub(crate) unsafe fn first_key_span_start_unchecked(&self) -> u32 {
122        debug_assert!(self.len > 0);
123        // SAFETY: caller guarantees len > 0, so the first entry is initialized.
124        unsafe { (*self.ptr.as_ptr()).0.span.start }
125    }
126
127    /// Returns a slice of all entries.
128    #[inline]
129    pub fn entries(&self) -> &[TableEntry<'de>] {
130        // SAFETY: ptr points to arena-allocated memory with at least len
131        // initialized entries. When len == 0, ptr is NonNull::dangling() which
132        // satisfies from_raw_parts' alignment requirement for zero-length slices.
133        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len as usize) }
134    }
135
136    #[inline]
137    pub(crate) fn entries_mut(&mut self) -> &mut [TableEntry<'de>] {
138        // SAFETY: same as entries() — ptr is valid for len initialized entries.
139        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len as usize) }
140    }
141
142    pub(crate) fn find_index(&self, name: &str) -> Option<usize> {
143        for (i, entry) in self.entries().iter().enumerate() {
144            if entry.0.name == name {
145                return Some(i);
146            }
147        }
148        None
149    }
150
151    /// Remove entry at `idx` by swapping it with the last entry.
152    fn remove_at(&mut self, idx: usize) -> (Key<'de>, Item<'de>) {
153        let last = self.len as usize - 1;
154        // SAFETY: idx was returned by find_index, so idx < len and the
155        // pointer is within initialized entries. read() moves the value out.
156        let ptr = unsafe { self.ptr.as_ptr().add(idx) };
157        let entry = unsafe { ptr.read() };
158        if idx != last {
159            // Safety: `last` is a valid, initialized index distinct from `idx`.
160            unsafe {
161                ptr.write(self.ptr.as_ptr().add(last).read());
162            }
163        }
164        self.len -= 1;
165        entry
166    }
167
168    #[cold]
169    fn grow(&mut self, arena: &'de Arena) {
170        let new_cap = if self.cap == 0 {
171            MIN_CAP
172        } else {
173            self.cap.checked_mul(2).expect("capacity overflow")
174        };
175        self.grow_to(new_cap, arena);
176    }
177
178    fn grow_to(&mut self, new_cap: u32, arena: &'de Arena) {
179        // On 64-bit, u32 * size_of::<TableEntry>() cannot overflow usize.
180        #[cfg(target_pointer_width = "32")]
181        let new_size = (new_cap as usize)
182            .checked_mul(size_of::<TableEntry<'_>>())
183            .expect("capacity overflow");
184        #[cfg(not(target_pointer_width = "32"))]
185        let new_size = new_cap as usize * size_of::<TableEntry<'_>>();
186        if self.cap > 0 {
187            let old_size = self.cap as usize * size_of::<TableEntry<'_>>();
188            // Safety: ptr was returned by a prior arena alloc of old_size bytes.
189            self.ptr = unsafe { arena.realloc(self.ptr.cast(), old_size, new_size).cast() };
190        } else {
191            self.ptr = arena.alloc(new_size).cast();
192        }
193        self.cap = new_cap;
194    }
195}
196
197impl std::fmt::Debug for InnerTable<'_> {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        let mut map = f.debug_map();
200        for (k, v) in self.entries() {
201            map.entry(k, v);
202        }
203        map.finish()
204    }
205}
206
207/// Consuming iterator over a [`Table`], yielding `(`[`Key`]`, `[`Item`]`)` pairs.
208pub struct IntoIter<'de> {
209    table: InnerTable<'de>,
210    index: u32,
211}
212
213impl<'de> Iterator for IntoIter<'de> {
214    type Item = (Key<'de>, Item<'de>);
215
216    fn next(&mut self) -> Option<Self::Item> {
217        if self.index < self.table.len {
218            // SAFETY: index < len is checked above, so the read is within
219            // bounds of initialized entries.
220            let entry = unsafe { self.table.ptr.as_ptr().add(self.index as usize).read() };
221            self.index += 1;
222            Some(entry)
223        } else {
224            None
225        }
226    }
227
228    fn size_hint(&self) -> (usize, Option<usize>) {
229        let remaining = (self.table.len - self.index) as usize;
230        (remaining, Some(remaining))
231    }
232}
233
234impl ExactSizeIterator for IntoIter<'_> {}
235
236/// A TOML table with span information.
237///
238/// A `Table` is the top-level value returned by [`parse`](crate::parse) and is
239/// also the value inside any `[section]` or inline `{ ... }` table in TOML.
240/// It stores `(`[`Key`]`, `[`Item`]`)` pairs in insertion order.
241///
242/// # Accessing values
243///
244/// - **Index operators** — `table["key"]` returns a [`MaybeItem`] that never
245///   panics on missing keys, and supports chained indexing.
246/// - **`get` / `get_mut`** — return `Option<&Item>` / `Option<&mut Item>`.
247///
248/// For type-safe deserialization, use [`Item::table_helper`](crate::value::Item::table_helper)
249/// to create a [`TableHelper`](crate::de::TableHelper).
250///
251/// # Lookup performance
252///
253/// Direct lookups ([`get`](Self::get), `table["key"]`) perform a linear scan
254/// over entries — O(n) in the number of keys. For small tables or a handful
255/// of lookups, as is typical in TOML, this is well fast enough.
256///
257/// For structured deserialization of larger tables, use
258/// [`TableHelper`](crate::de::TableHelper) via
259/// [`Root::helper`](crate::Root::helper) or
260/// [`Item::table_helper`](crate::value::Item::table_helper). The
261/// [`Context`](crate::de::Context) returned by [`parse`](crate::parse)
262/// carries the parser's hash index.
263///
264/// # Constructing tables
265///
266/// Tables are normally obtained from [`parse`](crate::parse). To build one
267/// programmatically, create a table with [`Table::new`] and insert entries
268/// with [`Table::insert`]. An [`Arena`](crate::Arena) is required for
269/// insertion because entries are arena-allocated.
270///
271/// # Iteration
272///
273/// `Table` implements [`IntoIterator`] (both by reference and by value),
274/// yielding `(`[`Key`]`, `[`Item`]`)` pairs.
275///
276/// Removal via [`remove_entry`](Self::remove_entry) uses swap-remove and may reorder
277/// remaining entries.
278#[repr(C)]
279pub struct Table<'de> {
280    pub(crate) value: InnerTable<'de>,
281    /// Bits 2-0: tag, bits 31-3: span.start
282    start_and_tag: u32,
283    /// Bit 0: flag bit (parser-internal), bits 31-1: span.end
284    end_and_flag: u32,
285}
286
287impl<'de> Table<'de> {
288    /// Creates an empty table with the given span.
289    pub fn new(span: Span) -> Table<'de> {
290        Table {
291            start_and_tag: span.start << TAG_SHIFT | TAG_TABLE,
292            end_and_flag: (span.end << FLAG_SHIFT) | FLAG_TABLE,
293            value: InnerTable::new(),
294        }
295    }
296    /// Returns the byte-offset span of this table in the source document.
297    pub fn span(&self) -> Span {
298        Span {
299            start: self.start_and_tag >> TAG_SHIFT,
300            end: self.end_and_flag >> FLAG_SHIFT,
301        }
302    }
303}
304
305impl<'de> Default for Table<'de> {
306    fn default() -> Self {
307        Self::new(Span::default())
308    }
309}
310
311impl std::fmt::Debug for Table<'_> {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        self.value.fmt(f)
314    }
315}
316
317impl<'de> Table<'de> {
318    /// Inserts a key-value pair. Does **not** check for duplicates.
319    pub fn insert(&mut self, key: Key<'de>, value: Item<'de>, arena: &'de Arena) {
320        self.value.insert(key, value, arena);
321    }
322
323    /// Returns the number of entries.
324    #[inline]
325    pub fn len(&self) -> usize {
326        self.value.len()
327    }
328
329    /// Returns `true` if the table has no entries.
330    #[inline]
331    pub fn is_empty(&self) -> bool {
332        self.value.is_empty()
333    }
334
335    /// Linear scan for a key, returning both key and value references.
336    pub fn get_key_value(&self, name: &str) -> Option<(&Key<'de>, &Item<'de>)> {
337        self.value.get_entry(name)
338    }
339
340    /// Returns a reference to the value for `name`.
341    pub fn get(&self, name: &str) -> Option<&Item<'de>> {
342        self.value.get(name)
343    }
344
345    /// Returns a mutable reference to the value for `name`.
346    pub fn get_mut(&mut self, name: &str) -> Option<&mut Item<'de>> {
347        self.value.get_mut(name)
348    }
349
350    /// Returns `true` if the table contains the key.
351    #[inline]
352    pub fn contains_key(&self, name: &str) -> bool {
353        self.value.contains_key(name)
354    }
355
356    /// Removes the first entry matching `name`, returning the key-value pair.
357    /// Uses swap-remove, so the ordering of remaining entries may change.
358    pub fn remove_entry(&mut self, name: &str) -> Option<(Key<'de>, Item<'de>)> {
359        self.value.remove_entry(name)
360    }
361
362    /// Returns a slice of all entries.
363    #[inline]
364    pub fn entries(&self) -> &[TableEntry<'de>] {
365        self.value.entries()
366    }
367
368    /// Converts this `Table` into an [`Item`] with the same span and payload.
369    pub fn as_item(&self) -> &Item<'de> {
370        unsafe {
371            // SAFETY: Table and Item have the same layout and alignment, so this
372            // is safe as long as we don't mutate through the Item reference.
373            &*(self as *const Table<'de>).cast::<Item<'de>>()
374        }
375    }
376
377    /// Converts this `Table` into an [`Item`] with the same span and payload.
378    pub fn into_item(self) -> Item<'de> {
379        let span = self.span();
380        Item::table(self.value, span)
381    }
382}
383
384impl<'a, 'de> IntoIterator for &'a mut Table<'de> {
385    type Item = &'a mut (Key<'de>, Item<'de>);
386    type IntoIter = std::slice::IterMut<'a, TableEntry<'de>>;
387
388    fn into_iter(self) -> Self::IntoIter {
389        self.value.entries_mut().iter_mut()
390    }
391}
392impl<'a, 'de> IntoIterator for &'a Table<'de> {
393    type Item = &'a (Key<'de>, Item<'de>);
394    type IntoIter = std::slice::Iter<'a, TableEntry<'de>>;
395
396    fn into_iter(self) -> Self::IntoIter {
397        self.value.entries().iter()
398    }
399}
400
401impl<'de> IntoIterator for Table<'de> {
402    type Item = (Key<'de>, Item<'de>);
403    type IntoIter = IntoIter<'de>;
404
405    fn into_iter(self) -> Self::IntoIter {
406        IntoIter {
407            table: self.value,
408            index: 0,
409        }
410    }
411}
412
413const _: () = assert!(std::mem::size_of::<Table<'_>>() == std::mem::size_of::<Item<'_>>());
414const _: () = assert!(std::mem::align_of::<Table<'_>>() == std::mem::align_of::<Item<'_>>());
415
416impl<'de> Table<'de> {
417    #[inline]
418    pub(crate) fn span_start(&self) -> u32 {
419        self.start_and_tag >> TAG_SHIFT
420    }
421
422    #[inline]
423    pub(crate) fn set_span_start(&mut self, v: u32) {
424        self.start_and_tag = (v << TAG_SHIFT) | (self.start_and_tag & TAG_MASK);
425    }
426
427    #[inline]
428    pub(crate) fn set_span_end(&mut self, v: u32) {
429        self.end_and_flag = (v << FLAG_SHIFT) | (self.end_and_flag & FLAG_MASK);
430    }
431
432    #[inline]
433    pub(crate) fn extend_span_end(&mut self, new_end: u32) {
434        let old = self.end_and_flag;
435        let current = old >> FLAG_SHIFT;
436        self.end_and_flag = (current.max(new_end) << FLAG_SHIFT) | (old & FLAG_MASK);
437    }
438
439    #[inline]
440    pub(crate) fn set_header_flag(&mut self) {
441        self.end_and_flag = (self.end_and_flag & !FLAG_MASK) | FLAG_HEADER;
442    }
443}
444
445impl<'de> std::ops::Index<&str> for Table<'de> {
446    type Output = MaybeItem<'de>;
447
448    #[inline]
449    fn index(&self, index: &str) -> &Self::Output {
450        if let Some(item) = self.get(index) {
451            return MaybeItem::from_ref(item);
452        }
453        &NONE
454    }
455}