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
91fn 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
100fn uchar(byte: u8) -> u8 {
102 byte
103}
104
105fn digit(byte: u8) -> bool {
107 byte.is_ascii_digit()
108}
109
110fn string_len(ctx: NativeCallContext) -> NativeCallResult {
112 ctx.push_integer(unsafe { ctx.arg(1).string()? }.len() as i32)?;
113 Ok(1)
114}
115
116fn 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
141fn 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
161fn 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
181fn 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
201fn 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
264fn 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
295fn 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
320fn 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
363unsafe 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 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}