rune/core/object/
string.rs

1use super::{CloneIn, IntoObject};
2use crate::core::gc::{AllocState, Block, GcHeap, GcMoveable, GcState, Trace};
3use std::cell::Cell;
4use std::fmt::{Debug, Display};
5use std::ops::Deref;
6use std::ptr::NonNull;
7
8pub(crate) type GcString<'a> = bumpalo::collections::String<'a>;
9pub(crate) struct LispString(GcHeap<LispStringInner>);
10
11// This type needs to be this complex due to to string mutation.
12//
13// Case 1: The new char is the same utf8 size as the old one:
14// We can update the string in place using the mutable pointer.
15//
16// Case 2: The new char is a different size:
17// Need to allocate a new string and update the cell to point to that.
18struct LispStringInner(Cell<*mut str>);
19
20impl GcMoveable for LispString {
21    type Value = std::ptr::NonNull<LispString>;
22
23    fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
24        match self.0.allocation_state() {
25            AllocState::Forwarded(f) => Some((f.cast::<Self>(), false)),
26            AllocState::Global => None,
27            AllocState::Unmoved => {
28                let ptr = {
29                    let mut new = GcString::from_str_in(self, to_space);
30                    let lisp_str = unsafe { LispString::new(new.as_mut_str(), false) };
31                    std::mem::forget(new);
32                    let alloc = to_space.alloc(lisp_str);
33                    NonNull::from(alloc)
34                };
35                self.0.forward(ptr.cast::<u8>());
36                Some((ptr, true))
37            }
38        }
39    }
40}
41
42impl Trace for LispString {
43    fn trace(&self, _state: &mut GcState) {}
44}
45
46impl Debug for LispString {
47    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
48        Debug::fmt(self.inner(), f)
49    }
50}
51
52impl Display for LispString {
53    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54        let mut output = String::new();
55        for c in self.inner().chars() {
56            match c {
57                '\\' => output.push_str("\\\\"),
58                '"' => output.push_str("\\\""),
59                c => output.push(c),
60            }
61        }
62        Display::fmt(&output, f)
63    }
64}
65
66impl PartialEq for LispString {
67    fn eq(&self, other: &Self) -> bool {
68        self.inner() == other.inner()
69    }
70}
71
72impl Eq for LispString {}
73
74impl PartialEq<str> for LispString {
75    fn eq(&self, other: &str) -> bool {
76        self.inner() == other
77    }
78}
79
80impl Deref for LispString {
81    type Target = str;
82
83    fn deref(&self) -> &Self::Target {
84        self.inner()
85    }
86}
87
88impl LispString {
89    pub(in crate::core) unsafe fn new(string: *mut str, constant: bool) -> Self {
90        Self(GcHeap::new(LispStringInner(Cell::new(string)), constant))
91    }
92
93    pub(crate) fn inner(&self) -> &str {
94        unsafe { &*self.0.0.get() }
95    }
96}
97
98impl LispString {
99    pub(crate) fn len(&self) -> usize {
100        self.chars().count()
101    }
102
103    pub(crate) fn clear(&self) {
104        let inner_mut_str = unsafe { &mut *self.0.0.get() };
105        for byte in unsafe { inner_mut_str.as_bytes_mut().iter_mut() } {
106            *byte = b'\0';
107        }
108    }
109}
110
111impl<'new> CloneIn<'new, &'new Self> for LispString {
112    fn clone_in<const C: bool>(&self, bk: &'new Block<C>) -> super::Gc<&'new Self> {
113        GcString::from_str_in(self.inner(), &bk.objects).into_obj(bk)
114    }
115}
116
117impl AsRef<str> for LispString {
118    fn as_ref(&self) -> &str {
119        self
120    }
121}
122
123impl<'a> From<&'a LispString> for &'a str {
124    fn from(value: &'a LispString) -> Self {
125        value
126    }
127}
128
129impl<'a> From<&'a LispString> for &'a [u8] {
130    fn from(value: &'a LispString) -> Self {
131        value.as_bytes()
132    }
133}
134
135pub(crate) struct ByteString(GcHeap<*mut [u8]>);
136type ByteVec<'a> = bumpalo::collections::Vec<'a, u8>;
137
138impl Deref for ByteString {
139    type Target = [u8];
140
141    fn deref(&self) -> &Self::Target {
142        self.inner()
143    }
144}
145
146impl GcMoveable for ByteString {
147    type Value = std::ptr::NonNull<ByteString>;
148
149    fn move_value(&self, to_space: &bumpalo::Bump) -> Option<(Self::Value, bool)> {
150        match self.0.allocation_state() {
151            AllocState::Forwarded(f) => Some((f.cast::<Self>(), false)),
152            AllocState::Global => None,
153            AllocState::Unmoved => {
154                let ptr = {
155                    let mut new = ByteVec::new_in(to_space);
156                    new.extend_from_slice(self.inner());
157                    let byte_string = ByteString::new(new.as_mut_slice(), false);
158                    std::mem::forget(new);
159                    let alloc = to_space.alloc(byte_string);
160                    NonNull::from(alloc)
161                };
162                self.0.forward(ptr.cast::<u8>());
163                Some((ptr, true))
164            }
165        }
166    }
167}
168
169impl PartialEq for ByteString {
170    fn eq(&self, other: &Self) -> bool {
171        self.inner() == other.inner()
172    }
173}
174
175impl Eq for ByteString {}
176
177impl Trace for ByteString {
178    fn trace(&self, _state: &mut GcState) {}
179}
180
181impl ByteString {
182    pub(in crate::core) fn new(string: *mut [u8], constant: bool) -> Self {
183        Self(GcHeap::new(string, constant))
184    }
185
186    pub(crate) fn inner(&self) -> &[u8] {
187        unsafe { &**self.0 }
188    }
189}
190
191impl<'new> CloneIn<'new, &'new Self> for ByteString {
192    fn clone_in<const C: bool>(&self, bk: &'new Block<C>) -> super::Gc<&'new Self> {
193        // TODO: Allocate in the non-gc heap here
194        (**self).to_vec().into_obj(bk)
195    }
196}
197
198impl Display for ByteString {
199    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
200        for byte in &**self {
201            if byte.is_ascii() {
202                write!(f, "{}", *byte as char)?;
203            } else {
204                write!(f, "\\{byte:03o}")?;
205            }
206        }
207        Ok(())
208    }
209}
210
211impl Debug for ByteString {
212    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
213        write!(f, "{self}")
214    }
215}
216
217#[cfg(test)]
218mod test {
219    use crate::core::gc::{Context, RootSet};
220    use rune_core::macros::root;
221
222    #[test]
223    fn test_string_aliasing() {
224        let roots = &RootSet::default();
225        let cx = &mut Context::new(roots);
226        let s1 = cx.add(String::from("hello"));
227        let mut s2 = cx.string_with_capacity(5);
228        s2.push_str("hello");
229        let s2 = cx.add(s2);
230        assert_eq!(s1, s2);
231        root!(s1, cx);
232        root!(s2, cx);
233        cx.garbage_collect(true);
234        assert_eq!(s1, s2);
235    }
236
237    #[test]
238    fn test_byte_string_aliasing() {
239        let roots = &RootSet::default();
240        let cx = &mut Context::new(roots);
241        let s1 = cx.add(vec![1u8, 2, 3]);
242        let s2 = cx.add(vec![1u8, 2, 3]);
243        let s2 = cx.add(s2);
244        assert_eq!(s1, s2);
245        root!(s1, cx);
246        root!(s2, cx);
247        cx.garbage_collect(true);
248        assert_eq!(s1, s2);
249    }
250}