Skip to main content

stet_core/
string_store.rs

1// stet - A PostScript Interpreter
2// Copyright (c) 2026 Scott Bowman
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Contiguous byte buffer for PostScript string storage.
6//!
7//! Strings are identified by `EntityId` indices into the entity table,
8//! which provides indirection for save/restore COW semantics.
9
10use crate::entity_table::EntityTable;
11use crate::object::EntityId;
12
13/// Storage for PostScript string byte data.
14pub struct StringStore {
15    data: Vec<u8>,
16    pub entities: EntityTable,
17}
18
19impl StringStore {
20    pub fn new() -> Self {
21        Self {
22            data: Vec::new(),
23            entities: EntityTable::new(),
24        }
25    }
26
27    /// Allocate `len` zero-filled bytes, returning an `EntityId`.
28    pub fn allocate(&mut self, len: usize) -> EntityId {
29        let offset = self.data.len() as u32;
30        self.data.resize(self.data.len() + len, 0);
31        self.entities.allocate(offset, len as u32, 0, false, 0)
32    }
33
34    /// Allocate and copy `bytes` into the store.
35    pub fn allocate_from(&mut self, bytes: &[u8]) -> EntityId {
36        let offset = self.data.len() as u32;
37        self.data.extend_from_slice(bytes);
38        self.entities
39            .allocate(offset, bytes.len() as u32, 0, false, 0)
40    }
41
42    /// Allocate and copy `bytes` with a specific save level and global flag.
43    pub fn allocate_from_with(
44        &mut self,
45        bytes: &[u8],
46        save_level: u16,
47        global: bool,
48        created_after_save: u32,
49    ) -> EntityId {
50        let offset = self.data.len() as u32;
51        self.data.extend_from_slice(bytes);
52        self.entities.allocate(
53            offset,
54            bytes.len() as u32,
55            save_level,
56            global,
57            created_after_save,
58        )
59    }
60
61    /// Allocate with a specific save level and global flag.
62    pub fn allocate_with(
63        &mut self,
64        len: usize,
65        save_level: u16,
66        global: bool,
67        created_after_save: u32,
68    ) -> EntityId {
69        let offset = self.data.len() as u32;
70        self.data.resize(self.data.len() + len, 0);
71        self.entities
72            .allocate(offset, len as u32, save_level, global, created_after_save)
73    }
74
75    /// Get a slice of the string data via entity table indirection.
76    /// `start` is the byte offset from the entity's base; `len` is the number of bytes.
77    pub fn get(&self, entity: EntityId, start: u32, len: u32) -> &[u8] {
78        let base = self.entities.get(entity).offset as usize + start as usize;
79        &self.data[base..base + len as usize]
80    }
81
82    /// Get a mutable slice of the string data via entity table indirection.
83    /// `start` is the byte offset from the entity's base; `len` is the number of bytes.
84    pub fn get_mut(&mut self, entity: EntityId, start: u32, len: u32) -> &mut [u8] {
85        let base = self.entities.get(entity).offset as usize + start as usize;
86        &mut self.data[base..base + len as usize]
87    }
88
89    /// Set a single byte.
90    pub fn put_byte(&mut self, entity: EntityId, offset: u32, byte: u8) {
91        let base = self.entities.get(entity).offset as usize;
92        self.data[base + offset as usize] = byte;
93    }
94
95    /// Get a single byte.
96    pub fn get_byte(&self, entity: EntityId, offset: u32) -> u8 {
97        let base = self.entities.get(entity).offset as usize;
98        self.data[base + offset as usize]
99    }
100
101    /// Copy entity data to a new region (for COW). Returns the new EntityId
102    /// pointing to the copy. The original entity's offset is updated to
103    /// point at the copy, so the original EntityId now sees the new data.
104    pub fn cow_copy(&mut self, entity: EntityId) -> EntityId {
105        let meta = self.entities.get(entity);
106        let old_offset = meta.offset as usize;
107        let len = meta.len;
108        let save_level = meta.save_level;
109        let is_global = meta.is_global();
110        let created_after_save = meta.created_after_save;
111
112        // Copy data to a new region
113        let temp: Vec<u8> = self.data[old_offset..old_offset + len as usize].to_vec();
114        let new_offset = self.data.len() as u32;
115        self.data.extend_from_slice(&temp);
116
117        // Create a new entity pointing at the OLD data (this is the backup)
118        let copy_id = self.entities.allocate(
119            meta.offset, // points to original data
120            len,
121            save_level,
122            is_global,
123            created_after_save,
124        );
125
126        // Update the original entity to point at the NEW copy
127        self.entities.get_mut(entity).offset = new_offset;
128
129        copy_id
130    }
131
132    /// Swap offsets between two entities (used by restore).
133    pub fn swap_offsets(&mut self, a: EntityId, b: EntityId) {
134        let off_a = self.entities.get(a).offset;
135        let off_b = self.entities.get(b).offset;
136        self.entities.get_mut(a).offset = off_b;
137        self.entities.get_mut(b).offset = off_a;
138    }
139
140    /// Access to the backing data (for advanced operations).
141    pub fn data(&self) -> &[u8] {
142        &self.data
143    }
144}
145
146impl Default for StringStore {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_allocate_from() {
158        let mut store = StringStore::new();
159        let id = store.allocate_from(b"hello");
160        assert_eq!(store.get(id, 0, 5), b"hello");
161    }
162
163    #[test]
164    fn test_allocate_zeroed() {
165        let mut store = StringStore::new();
166        let id = store.allocate(3);
167        assert_eq!(store.get(id, 0, 3), &[0, 0, 0]);
168    }
169
170    #[test]
171    fn test_put_get_byte() {
172        let mut store = StringStore::new();
173        let id = store.allocate(3);
174        store.put_byte(id, 1, 42);
175        assert_eq!(store.get_byte(id, 0), 0);
176        assert_eq!(store.get_byte(id, 1), 42);
177    }
178
179    #[test]
180    fn test_multiple_strings() {
181        let mut store = StringStore::new();
182        let id1 = store.allocate_from(b"abc");
183        let id2 = store.allocate_from(b"xyz");
184        assert_eq!(store.get(id1, 0, 3), b"abc");
185        assert_eq!(store.get(id2, 0, 3), b"xyz");
186    }
187
188    #[test]
189    fn test_entity_indirection() {
190        let mut store = StringStore::new();
191        let id = store.allocate_from(b"test");
192        let meta = store.entities.get(id);
193        assert_eq!(meta.len, 4);
194        assert_eq!(meta.save_level, 0);
195        assert!(!meta.is_global());
196    }
197
198    #[test]
199    fn test_cow_copy() {
200        let mut store = StringStore::new();
201        let id = store.allocate_from(b"hello");
202
203        // Mutate via the original entity
204        store.put_byte(id, 0, b'H');
205        assert_eq!(store.get(id, 0, 5), b"Hello");
206
207        // COW copy: backup the original, original now points to copy
208        let backup = store.cow_copy(id);
209
210        // Modify the original — should not affect the backup
211        store.put_byte(id, 1, b'a');
212        assert_eq!(store.get(id, 0, 5), b"Hallo");
213        assert_eq!(store.get(backup, 0, 5), b"Hello");
214    }
215
216    #[test]
217    fn test_swap_offsets() {
218        let mut store = StringStore::new();
219        let id1 = store.allocate_from(b"aaa");
220        let id2 = store.allocate_from(b"bbb");
221
222        store.swap_offsets(id1, id2);
223        assert_eq!(store.get(id1, 0, 3), b"bbb");
224        assert_eq!(store.get(id2, 0, 3), b"aaa");
225    }
226
227    #[test]
228    fn test_allocate_with_save_level() {
229        let mut store = StringStore::new();
230        let id = store.allocate_with(5, 2, true, 0);
231        let meta = store.entities.get(id);
232        assert_eq!(meta.save_level, 2);
233        assert!(meta.is_global());
234    }
235}