Skip to main content

luau_vm/libs/string/
mod.rs

1use core::mem::MaybeUninit;
2use core::slice;
3
4use luau_common::ByteSlice;
5
6use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
7use crate::string::MAX_STRING_SIZE;
8use crate::thread::{LUA_BUFFER_SIZE, LuaStringBuilder, LuaStringBuilderStorage, Thread};
9
10mod format;
11mod pack;
12mod pattern;
13
14use format::string_format;
15use pack::{string_pack, string_pack_size, string_unpack};
16use pattern::{string_find, string_gmatch, string_gsub, string_match};
17
18const L_ESC: u8 = b'%';
19
20static STRING_LIB: [NativeFunction; 17] = [
21    NativeFunction {
22        name: "byte",
23        function: string_byte,
24    },
25    NativeFunction {
26        name: "char",
27        function: string_char,
28    },
29    NativeFunction {
30        name: "find",
31        function: string_find,
32    },
33    NativeFunction {
34        name: "format",
35        function: string_format,
36    },
37    NativeFunction {
38        name: "gmatch",
39        function: string_gmatch,
40    },
41    NativeFunction {
42        name: "gsub",
43        function: string_gsub,
44    },
45    NativeFunction {
46        name: "len",
47        function: string_len,
48    },
49    NativeFunction {
50        name: "lower",
51        function: string_lower,
52    },
53    NativeFunction {
54        name: "match",
55        function: string_match,
56    },
57    NativeFunction {
58        name: "rep",
59        function: string_rep,
60    },
61    NativeFunction {
62        name: "reverse",
63        function: string_reverse,
64    },
65    NativeFunction {
66        name: "sub",
67        function: string_sub,
68    },
69    NativeFunction {
70        name: "upper",
71        function: string_upper,
72    },
73    NativeFunction {
74        name: "split",
75        function: string_split,
76    },
77    NativeFunction {
78        name: "pack",
79        function: string_pack,
80    },
81    NativeFunction {
82        name: "packsize",
83        function: string_pack_size,
84    },
85    NativeFunction {
86        name: "unpack",
87        function: string_unpack,
88    },
89];
90
91/// `posrelat`
92fn pos_relat(pos: i32, len: usize) -> i32 {
93    if pos < 0 {
94        pos + len as i32 + 1
95    } else {
96        pos.max(0)
97    }
98}
99
100/// `uchar`
101fn uchar(byte: u8) -> u8 {
102    byte
103}
104
105/// `digit`
106fn digit(byte: u8) -> bool {
107    byte.is_ascii_digit()
108}
109
110/// `str_len`
111fn string_len(ctx: NativeCallContext) -> NativeCallResult {
112    ctx.push_integer(unsafe { ctx.arg(1).string()? }.len() as i32)?;
113    Ok(1)
114}
115
116/// `str_sub`
117fn string_sub(ctx: NativeCallContext) -> NativeCallResult {
118    let thread = ctx.raw_thread();
119    unsafe {
120        let bytes = thread.check_string(1)?;
121        let mut start = pos_relat(thread.check_integer(2)?, bytes.len());
122        let mut end = pos_relat(thread.opt_integer(3, -1)?, bytes.len());
123
124        if start < 1 {
125            start = 1;
126        }
127        if end > bytes.len() as i32 {
128            end = bytes.len() as i32;
129        }
130
131        if start <= end {
132            thread.push_string(&bytes[(start - 1) as usize..end as usize])?;
133        } else {
134            thread.push_string("")?;
135        }
136    }
137
138    Ok(1)
139}
140
141/// `str_reverse`
142fn string_reverse(ctx: NativeCallContext) -> NativeCallResult {
143    let thread = ctx.raw_thread();
144    unsafe {
145        let bytes = thread.check_string(1)?;
146        let len = bytes.len();
147        let mut buffer_storage = LuaStringBuilderStorage::uninit();
148        let mut buffer = LuaStringBuilder::new(thread, &mut buffer_storage);
149        let out = buffer.reserve(len)?;
150        let out = slice::from_raw_parts_mut(out.as_ptr(), len);
151
152        for (dst, src) in out.iter_mut().zip(bytes.iter().rev()) {
153            *dst = *src;
154        }
155
156        buffer.finish_with_reserved(len)?;
157    }
158    Ok(1)
159}
160
161/// `str_lower`
162fn string_lower(ctx: NativeCallContext) -> NativeCallResult {
163    let thread = ctx.raw_thread();
164    unsafe {
165        let bytes = thread.check_string(1)?;
166        let len = bytes.len();
167        let mut buffer_storage = LuaStringBuilderStorage::uninit();
168        let mut buffer = LuaStringBuilder::new(thread, &mut buffer_storage);
169        let out = buffer.reserve(len)?;
170        let out = slice::from_raw_parts_mut(out.as_ptr(), len);
171
172        for (dst, src) in out.iter_mut().zip(bytes.iter()) {
173            *dst = src.to_ascii_lowercase();
174        }
175
176        buffer.finish_with_reserved(len)?;
177    }
178    Ok(1)
179}
180
181/// `str_upper`
182fn string_upper(ctx: NativeCallContext) -> NativeCallResult {
183    let thread = ctx.raw_thread();
184    unsafe {
185        let bytes = thread.check_string(1)?;
186        let len = bytes.len();
187        let mut buffer_storage = LuaStringBuilderStorage::uninit();
188        let mut buffer = LuaStringBuilder::new(thread, &mut buffer_storage);
189        let out = buffer.reserve(len)?;
190        let out = slice::from_raw_parts_mut(out.as_ptr(), len);
191
192        for (dst, src) in out.iter_mut().zip(bytes.iter()) {
193            *dst = src.to_ascii_uppercase();
194        }
195
196        buffer.finish_with_reserved(len)?;
197    }
198    Ok(1)
199}
200
201/// `str_rep`
202fn string_rep(ctx: NativeCallContext) -> NativeCallResult {
203    let thread = ctx.raw_thread();
204    unsafe {
205        let bytes = thread.check_string(1)?;
206        let n = thread.check_integer(2)?;
207
208        if n <= 0 {
209            thread.push_string("")?;
210            return Ok(1);
211        }
212
213        if bytes.len() > MAX_STRING_SIZE / n as usize {
214            return crate::error!(thread, "resulting string too large").map_err(Into::into);
215        }
216
217        let total = bytes.len() * n as usize;
218        if total <= LUA_BUFFER_SIZE {
219            let mut buffer =
220                MaybeUninit::<[MaybeUninit<u8>; LUA_BUFFER_SIZE]>::uninit().assume_init();
221            let out = buffer.as_mut_ptr().cast::<u8>();
222            core::ptr::copy_nonoverlapping(bytes.as_ptr(), out, bytes.len());
223
224            let mut written = bytes.len();
225            let mut left = total - bytes.len();
226            let mut step = bytes.len();
227
228            while step < left {
229                core::ptr::copy_nonoverlapping(out, out.add(written), step);
230                written += step;
231                left -= step;
232                step <<= 1;
233            }
234
235            core::ptr::copy_nonoverlapping(out, out.add(written), left);
236            thread.push_string(slice::from_raw_parts(out, total))?;
237            return Ok(1);
238        }
239
240        let mut buffer_storage = LuaStringBuilderStorage::uninit();
241        let mut buffer = LuaStringBuilder::new(thread, &mut buffer_storage);
242        let out = buffer.reserve(total)?;
243        let start = out.as_ptr();
244
245        core::ptr::copy_nonoverlapping(bytes.as_ptr(), start, bytes.len());
246
247        let mut written = bytes.len();
248        let mut left = total - bytes.len();
249        let mut step = bytes.len();
250
251        while step < left {
252            core::ptr::copy_nonoverlapping(start, start.add(written), step);
253            written += step;
254            left -= step;
255            step <<= 1;
256        }
257
258        core::ptr::copy_nonoverlapping(start, start.add(written), left);
259        buffer.finish_with_reserved(total)?;
260        Ok(1)
261    }
262}
263
264/// `str_byte`
265fn string_byte(ctx: NativeCallContext) -> NativeCallResult {
266    let thread = ctx.raw_thread();
267    unsafe {
268        let bytes = thread.check_string(1)?;
269        let mut start = pos_relat(thread.opt_integer(2, 1)?, bytes.len());
270        let mut end = pos_relat(thread.opt_integer(3, start)?, bytes.len());
271
272        if start <= 0 {
273            start = 1;
274        }
275        if end as usize > bytes.len() {
276            end = bytes.len() as i32;
277        }
278        if start > end {
279            return Ok(0);
280        }
281
282        let count = end - start + 1;
283        if start + count <= end {
284            return crate::error!(thread, "string slice too long").map_err(Into::into);
285        }
286
287        thread.lua_check_stack(count, Some("string slice too long"))?;
288        for index in 0..count as usize {
289            thread.push_integer(uchar(bytes[start as usize + index - 1]) as i32)?;
290        }
291        Ok(count as usize)
292    }
293}
294
295/// `str_char`
296fn string_char(ctx: NativeCallContext) -> NativeCallResult {
297    let thread = ctx.raw_thread();
298    unsafe {
299        let count = thread.get_top();
300        let mut buffer_storage = LuaStringBuilderStorage::uninit();
301        let mut buffer = LuaStringBuilder::new(thread, &mut buffer_storage);
302        let out = buffer.reserve(count as usize)?;
303        let out = slice::from_raw_parts_mut(out.as_ptr(), count as usize);
304
305        for index in 1..=count {
306            let ch = thread.check_integer(index)?;
307            if uchar(ch as u8) as i32 != ch {
308                return thread
309                    .lua_arg_error(index, "invalid value")
310                    .map_err(Into::into);
311            }
312            out[index as usize - 1] = ch as u8;
313        }
314
315        buffer.finish_with_reserved(count as usize)?;
316        Ok(1)
317    }
318}
319
320/// `str_split`
321fn string_split(ctx: NativeCallContext) -> NativeCallResult {
322    let thread = ctx.raw_thread();
323    unsafe {
324        let haystack = thread.check_string(1)?;
325        let needle = thread.opt_string(2)?.unwrap_or(b",".as_bstr());
326
327        let mut begin = 0usize;
328        let end = haystack.len();
329        let mut span_start = begin;
330        let mut matches = 0i32;
331
332        thread.create_table(0, 0)?;
333
334        if needle.is_empty() {
335            begin += 1;
336        }
337
338        let mut iter = begin;
339        while iter <= end.saturating_sub(needle.len()) {
340            if &haystack[iter..iter + needle.len()] == needle {
341                matches += 1;
342                thread.push_string(&haystack[span_start..iter])?;
343                thread.raw_seti(-2, matches)?;
344
345                span_start = iter + needle.len();
346                if !needle.is_empty() {
347                    iter += needle.len() - 1;
348                }
349            }
350
351            iter += 1;
352        }
353
354        if !needle.is_empty() {
355            thread.push_string(&haystack[span_start..])?;
356            thread.raw_seti(-2, matches + 1)?;
357        }
358
359        Ok(1)
360    }
361}
362
363/// `createmetatable`
364unsafe fn create_metatable(thread: &Thread) -> NativeCallResult {
365    unsafe {
366        thread.create_table(0, 1)?;
367        thread.push_string("")?;
368        thread.push_value(-2)?;
369        thread.set_metatable(-2)?;
370        thread.pop(1);
371        thread.push_value(-2)?;
372        thread.set_field(-2, "__index")?;
373        thread.pop(1);
374    }
375    Ok(1)
376}
377
378impl Thread {
379    /// `luaopen_string`
380    pub unsafe fn open_string(&self) -> NativeCallResult {
381        unsafe { self.register(Some(super::LUA_STRLIB_NAME), &STRING_LIB[..])? };
382        unsafe { create_metatable(self)? };
383        Ok(1)
384    }
385}