oxide_core/heap/
string.rs1use std::cell::{Cell, OnceCell};
10
11use super::arena::Record;
12use super::object::NONE;
13
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub enum StringRepr {
16 OneByte(Box<[u8]>),
17 TwoByte(Box<[u16]>),
18}
19
20impl StringRepr {
21 pub fn from_text(text: &str) -> StringRepr {
22 if text.is_ascii() {
23 return StringRepr::OneByte(text.as_bytes().into());
24 }
25 let units: Vec<u16> = text.encode_utf16().collect();
26 StringRepr::from_units(&units)
27 }
28
29 pub fn from_units(units: &[u16]) -> StringRepr {
30 if units.iter().all(|&unit| unit <= 0xFF) {
31 StringRepr::OneByte(units.iter().map(|&unit| unit as u8).collect())
32 } else {
33 StringRepr::TwoByte(units.into())
34 }
35 }
36
37 pub fn len(&self) -> usize {
38 match self {
39 StringRepr::OneByte(bytes) => bytes.len(),
40 StringRepr::TwoByte(units) => units.len(),
41 }
42 }
43
44 pub fn is_empty(&self) -> bool {
45 self.len() == 0
46 }
47
48 pub fn unit(&self, index: usize) -> u16 {
49 match self {
50 StringRepr::OneByte(bytes) => u16::from(bytes[index]),
51 StringRepr::TwoByte(units) => units[index],
52 }
53 }
54
55 pub fn units(&self) -> impl Iterator<Item = u16> + '_ {
56 (0..self.len()).map(|index| self.unit(index))
57 }
58
59 pub fn content_bytes(&self) -> usize {
60 match self {
61 StringRepr::OneByte(bytes) => bytes.len(),
62 StringRepr::TwoByte(units) => units.len() * 2,
63 }
64 }
65
66 pub fn to_rust_string(&self) -> String {
68 match self {
69 StringRepr::OneByte(bytes) => bytes.iter().map(|&byte| char::from(byte)).collect(),
70 StringRepr::TwoByte(units) => String::from_utf16_lossy(units),
71 }
72 }
73
74 pub fn array_index(&self) -> Option<u32> {
76 array_index(self.len(), self.units())
77 }
78
79 pub fn content_hash(&self) -> u32 {
80 content_hash(self.units())
81 }
82}
83
84pub fn array_index(len: usize, mut units: impl Iterator<Item = u16>) -> Option<u32> {
86 if len == 0 || len > 10 {
87 return None;
88 }
89 let first = units.next()?;
90 if first == u16::from(b'0') {
91 return (len == 1).then_some(0);
92 }
93 let mut value: u64 = 0;
94 for unit in std::iter::once(first).chain(units) {
95 let digit = unit
96 .checked_sub(u16::from(b'0'))
97 .filter(|digit| *digit <= 9)?;
98 value = value * 10 + u64::from(digit);
99 }
100 (value < u64::from(u32::MAX)).then_some(value as u32)
101}
102
103pub fn content_hash(units: impl Iterator<Item = u16>) -> u32 {
104 let mut hash: u32 = 0x811C_9DC5;
105 for unit in units {
106 hash = (hash ^ u32::from(unit)).wrapping_mul(0x0100_0193);
107 }
108 hash
109}
110
111#[derive(Clone, Copy, PartialEq, Eq, Debug)]
113pub enum AtomKeep {
114 Weak,
116 Held,
119 Pinned,
121}
122
123pub struct StringRecord {
124 flat: OnceCell<StringRepr>,
127 len: u32,
128 one_byte: bool,
129 born_rope: bool,
130 hash: Cell<u32>,
132 pub is_atom: bool,
133 pub keep: AtomKeep,
135 pub array_index: u32,
138}
139
140const _: () = assert!(size_of::<StringRecord>() <= 40);
141
142impl StringRecord {
143 pub fn new(repr: StringRepr) -> Self {
144 let len = repr.len() as u32;
145 let one_byte = matches!(repr, StringRepr::OneByte(_));
146 StringRecord {
147 flat: OnceCell::from(repr),
148 len,
149 one_byte,
150 born_rope: false,
151 hash: Cell::new(0),
152 is_atom: false,
153 keep: AtomKeep::Weak,
154 array_index: NONE,
155 }
156 }
157
158 pub fn rope(len: u32, one_byte: bool) -> Self {
160 StringRecord {
161 flat: OnceCell::new(),
162 len,
163 one_byte,
164 born_rope: true,
165 hash: Cell::new(0),
166 is_atom: false,
167 keep: AtomKeep::Weak,
168 array_index: NONE,
169 }
170 }
171
172 #[inline]
174 pub fn repr(&self) -> &StringRepr {
175 self.flat.get().expect("a flattened string")
176 }
177
178 pub(crate) fn flat(&self) -> Option<&StringRepr> {
179 self.flat.get()
180 }
181
182 pub(crate) fn set_flat(&self, repr: StringRepr) {
183 let _ = self.flat.set(repr);
184 }
185
186 pub(crate) fn flattened_rope_bytes(&self) -> usize {
189 match self.flat.get() {
190 Some(flat) if self.born_rope => flat.content_bytes(),
191 _ => 0,
192 }
193 }
194
195 #[inline]
196 pub fn len(&self) -> usize {
197 self.len as usize
198 }
199
200 #[inline]
201 pub fn is_empty(&self) -> bool {
202 self.len == 0
203 }
204
205 #[inline]
206 pub fn is_one_byte(&self) -> bool {
207 self.one_byte
208 }
209
210 pub fn hash(&self) -> u32 {
211 let cached = self.hash.get();
212 if cached != 0 {
213 return cached;
214 }
215 let hash = self.repr().content_hash().max(1);
216 self.hash.set(hash);
217 hash
218 }
219}
220
221impl Record for StringRecord {
222 fn vacant() -> Self {
223 StringRecord::new(StringRepr::OneByte(Box::new([])))
224 }
225
226 fn extra_bytes(&self) -> usize {
227 match self.flat.get() {
228 Some(flat) if !self.born_rope => flat.content_bytes(),
229 _ => 0,
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn representation_is_canonical() {
240 assert_eq!(
241 StringRepr::from_text("abc"),
242 StringRepr::OneByte(b"abc".to_vec().into())
243 );
244 assert_eq!(
245 StringRepr::from_text("caf\u{e9}"),
246 StringRepr::from_units(&[99, 97, 102, 0xE9])
247 );
248 assert!(matches!(
249 StringRepr::from_text("\u{3b1}"),
250 StringRepr::TwoByte(_)
251 ));
252 assert_eq!(StringRepr::from_text("\u{1F600}").len(), 2);
253 assert_eq!(
254 StringRepr::from_text("\u{3b1}x").to_rust_string(),
255 "\u{3b1}x"
256 );
257 }
258
259 #[test]
260 fn array_index_is_canonical_u32_below_max() {
261 let index = |text: &str| StringRepr::from_text(text).array_index();
262 assert_eq!(index("0"), Some(0));
263 assert_eq!(index("10"), Some(10));
264 assert_eq!(index("4294967294"), Some(u32::MAX - 1));
265 assert_eq!(index("4294967295"), None);
266 assert_eq!(index("01"), None);
267 assert_eq!(index(""), None);
268 assert_eq!(index("-1"), None);
269 assert_eq!(index("99999999999"), None);
270 }
271}