driven/binary/
string_table.rs1use crate::{DrivenError, Result};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
10pub struct StringId(pub u32);
11
12impl StringId {
13 pub const NULL: Self = Self(0);
15
16 pub fn new(index: u32) -> Self {
18 Self(index)
19 }
20
21 pub fn index(self) -> u32 {
23 self.0
24 }
25
26 pub fn is_null(self) -> bool {
28 self.0 == 0
29 }
30}
31
32#[derive(Debug)]
34pub struct StringTable<'a> {
35 count: u32,
37 offsets: &'a [u32],
39 data: &'a [u8],
41}
42
43impl<'a> StringTable<'a> {
44 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self> {
46 if bytes.len() < 8 {
47 return Err(DrivenError::InvalidBinary("String table too small".into()));
48 }
49
50 let count = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
51 let total_size = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
52
53 let offset_table_size = (count as usize) * 4;
54 if bytes.len() < 8 + offset_table_size {
55 return Err(DrivenError::InvalidBinary(
56 "String table offset table truncated".into(),
57 ));
58 }
59
60 let offsets_bytes = &bytes[8..8 + offset_table_size];
62 let offsets: &[u32] = bytemuck::cast_slice(offsets_bytes);
63
64 let data_start = 8 + offset_table_size;
65 let data_end = data_start + total_size as usize;
66 if bytes.len() < data_end {
67 return Err(DrivenError::InvalidBinary(
68 "String table data truncated".into(),
69 ));
70 }
71
72 let data = &bytes[data_start..data_end];
73
74 Ok(Self {
75 count,
76 offsets,
77 data,
78 })
79 }
80
81 pub fn get(&self, id: StringId) -> Option<&'a str> {
83 if id.is_null() {
84 return Some("");
85 }
86
87 let idx = id.0 as usize;
88 if idx == 0 || idx > self.count as usize {
89 return None;
90 }
91
92 let idx = idx - 1;
94
95 let start = self.offsets[idx] as usize;
96 let end = if idx + 1 < self.count as usize {
97 self.offsets[idx + 1] as usize
98 } else {
99 self.data.len()
100 };
101
102 if end > self.data.len() || start > end {
103 return None;
104 }
105
106 std::str::from_utf8(&self.data[start..end]).ok()
107 }
108
109 pub fn len(&self) -> usize {
111 self.count as usize
112 }
113
114 pub fn is_empty(&self) -> bool {
116 self.count == 0
117 }
118
119 pub fn iter(&self) -> impl Iterator<Item = (StringId, &'a str)> + '_ {
121 (1..=self.count).filter_map(move |i| {
122 let id = StringId(i);
123 self.get(id).map(|s| (id, s))
124 })
125 }
126}
127
128#[derive(Debug, Default)]
130pub struct StringTableBuilder {
131 strings: Vec<String>,
133 index: HashMap<String, StringId>,
135}
136
137impl StringTableBuilder {
138 pub fn new() -> Self {
140 Self::default()
141 }
142
143 pub fn intern(&mut self, s: &str) -> StringId {
145 if s.is_empty() {
146 return StringId::NULL;
147 }
148
149 if let Some(&id) = self.index.get(s) {
150 return id;
151 }
152
153 let id = StringId((self.strings.len() + 1) as u32);
154 self.strings.push(s.to_string());
155 self.index.insert(s.to_string(), id);
156 id
157 }
158
159 pub fn get(&self, s: &str) -> Option<StringId> {
161 if s.is_empty() {
162 return Some(StringId::NULL);
163 }
164 self.index.get(s).copied()
165 }
166
167 pub fn len(&self) -> usize {
169 self.strings.len()
170 }
171
172 pub fn is_empty(&self) -> bool {
174 self.strings.is_empty()
175 }
176
177 pub fn build(&self) -> Vec<u8> {
179 let count = self.strings.len() as u32;
180
181 let total_size: usize = self.strings.iter().map(|s| s.len()).sum();
183
184 let mut offsets = Vec::with_capacity(self.strings.len());
186 let mut offset = 0u32;
187 for s in &self.strings {
188 offsets.push(offset);
189 offset += s.len() as u32;
190 }
191
192 let mut output = Vec::with_capacity(8 + offsets.len() * 4 + total_size);
194
195 output.extend_from_slice(&count.to_le_bytes());
197 output.extend_from_slice(&(total_size as u32).to_le_bytes());
198
199 for off in &offsets {
201 output.extend_from_slice(&off.to_le_bytes());
202 }
203
204 for s in &self.strings {
206 output.extend_from_slice(s.as_bytes());
207 }
208
209 output
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_string_id_null() {
219 assert!(StringId::NULL.is_null());
220 assert!(!StringId(1).is_null());
221 }
222
223 #[test]
224 fn test_builder_intern() {
225 let mut builder = StringTableBuilder::new();
226
227 let id1 = builder.intern("hello");
228 let id2 = builder.intern("world");
229 let id3 = builder.intern("hello"); assert_eq!(id1, id3); assert_ne!(id1, id2);
233 assert_eq!(builder.len(), 2);
234 }
235
236 #[test]
237 fn test_roundtrip() {
238 let mut builder = StringTableBuilder::new();
239 let id1 = builder.intern("hello");
240 let id2 = builder.intern("world");
241 let id3 = builder.intern("test");
242
243 let bytes = builder.build();
244 let table = StringTable::from_bytes(&bytes).unwrap();
245
246 assert_eq!(table.len(), 3);
247 assert_eq!(table.get(id1), Some("hello"));
248 assert_eq!(table.get(id2), Some("world"));
249 assert_eq!(table.get(id3), Some("test"));
250 assert_eq!(table.get(StringId::NULL), Some(""));
251 }
252
253 #[test]
254 fn test_empty_string() {
255 let builder = StringTableBuilder::new();
256 let id = builder.get("").unwrap();
257 assert!(id.is_null());
258 }
259}