lust/vm/
stdlib.rs

1use super::corelib::{string_key, unwrap_lua_value};
2use super::VM;
3use crate::bytecode::value::ValueKey;
4use crate::bytecode::{NativeCallResult, Value};
5use crate::config::LustConfig;
6use crate::lua_compat::register_lust_function;
7use crate::LustInt;
8use rand::rngs::StdRng;
9use rand::{Rng, SeedableRng};
10use regex::Regex;
11use std::fs;
12use std::io::{self, Read, Write};
13use std::rc::Rc;
14use std::sync::{Mutex, OnceLock};
15use std::thread;
16use std::time::{Duration, SystemTime, UNIX_EPOCH};
17
18static RNG: OnceLock<Mutex<StdRng>> = OnceLock::new();
19pub fn create_stdlib(config: &LustConfig, vm: &VM) -> Vec<(&'static str, Value)> {
20    let mut stdlib = vec![
21        ("print", create_print_fn()),
22        ("println", create_println_fn()),
23        ("type", create_type_fn()),
24        ("select", create_select_fn()),
25        ("random", create_math_random_fn()),
26        ("randomseed", create_math_randomseed_fn()),
27    ];
28    if config.is_module_enabled("io") {
29        stdlib.push(("io", create_io_module(vm)));
30    }
31
32    if config.is_module_enabled("string") {
33        stdlib.push(("string", create_string_module(vm)));
34    }
35
36    if config.is_module_enabled("os") {
37        stdlib.push(("os", create_os_module(vm)));
38    }
39
40    stdlib
41}
42
43fn create_print_fn() -> Value {
44    Value::NativeFunction(Rc::new(|args: &[Value]| {
45        for (i, arg) in args.iter().enumerate() {
46            if i > 0 {
47                print!("\t");
48            }
49
50            print!("{}", arg);
51        }
52
53        Ok(NativeCallResult::Return(Value::Nil))
54    }))
55}
56
57fn create_println_fn() -> Value {
58    Value::NativeFunction(Rc::new(|args: &[Value]| {
59        for (i, arg) in args.iter().enumerate() {
60            if i > 0 {
61                print!("\t");
62            }
63
64            print!("{}", arg);
65        }
66
67        println!();
68        Ok(NativeCallResult::Return(Value::Nil))
69    }))
70}
71
72fn create_type_fn() -> Value {
73    Value::NativeFunction(Rc::new(|args: &[Value]| {
74        if args.is_empty() {
75            return Err("type() requires at least one argument".to_string());
76        }
77
78        let value = &args[0];
79
80        // Special handling for LuaValue enum - return Lua type names
81        if let Value::Enum { enum_name, variant, .. } = value {
82            if enum_name == "LuaValue" {
83                let lua_type = match variant.as_str() {
84                    "Nil" => "nil",
85                    "Bool" => "boolean",
86                    "Int" | "Float" => "number",
87                    "String" => "string",
88                    "Table" => "table",
89                    "Function" => "function",
90                    "Userdata" | "LightUserdata" => "userdata",
91                    "Thread" => "thread",
92                    _ => "unknown",
93                };
94                return Ok(NativeCallResult::Return(Value::enum_variant(
95                    "LuaValue",
96                    "String",
97                    vec![Value::string(lua_type)],
98                )));
99            }
100        }
101
102        // Regular Lust types - also wrap in LuaValue for Lua compat
103        let type_name = match value {
104            Value::Nil => "nil",
105            Value::Bool(_) => "bool",
106            Value::Int(_) => "int",
107            Value::Float(_) => "float",
108            Value::String(_) => "string",
109            Value::Array(_) => "array",
110            Value::Tuple(_) => "tuple",
111            Value::Map(_) => "map",
112            Value::Struct { .. } | Value::WeakStruct(_) => "struct",
113            Value::Enum { .. } => "enum",
114            Value::Function(_) => "function",
115            Value::NativeFunction(_) => "function",
116            Value::Closure { .. } => "function",
117            Value::Iterator(_) => "iterator",
118            Value::Task(_) => "task",
119        };
120        Ok(NativeCallResult::Return(Value::enum_variant(
121            "LuaValue",
122            "String",
123            vec![Value::string(type_name)],
124        )))
125    }))
126}
127
128pub(crate) fn create_select_fn() -> Value {
129    Value::NativeFunction(Rc::new(|args: &[Value]| {
130        if args.is_empty() {
131            return Err("select expects at least one argument".to_string());
132        }
133        let selector = unwrap_lua_value(args[0].clone());
134        let mut values: Vec<Value> = Vec::new();
135        for arg in args.iter().skip(1) {
136            let val = unwrap_lua_value(arg.clone());
137            if let Some(arr) = val.as_array() {
138                values.extend(arr.into_iter());
139            } else {
140                values.push(val);
141            }
142        }
143        if let Some(s) = selector.as_string() {
144            if s == "#" {
145                return Ok(NativeCallResult::Return(Value::Int(values.len() as LustInt)));
146            } else {
147                return Err("select expects '#' or an index as the first argument".to_string());
148            }
149        }
150        let raw_idx = if let Some(i) = selector.as_int() {
151            i
152        } else if let Some(f) = selector.as_float() {
153            f as LustInt
154        } else {
155            return Err("select expects '#' or an integer as the first argument".to_string());
156        };
157        let len = values.len() as isize;
158        let mut start = if raw_idx < 0 {
159            len + raw_idx as isize + 1
160        } else {
161            raw_idx as isize
162        };
163        if start < 1 {
164            start = 1;
165        }
166        let start_idx = (start - 1) as usize;
167        if start_idx >= values.len() {
168            return return_lua_values(Vec::new());
169        }
170        return_lua_values(values[start_idx..].to_vec())
171    }))
172}
173
174fn create_io_module(vm: &VM) -> Value {
175    let entries = [
176        (string_key("read_file"), create_io_read_file_fn()),
177        (
178            string_key("read_file_bytes"),
179            create_io_read_file_bytes_fn(),
180        ),
181        (string_key("write_file"), create_io_write_file_fn()),
182        (string_key("read_stdin"), create_io_read_stdin_fn()),
183        (string_key("read_line"), create_io_read_line_fn()),
184        (string_key("write_stdout"), create_io_write_stdout_fn()),
185    ];
186    vm.map_with_entries(entries)
187}
188
189fn create_io_read_file_fn() -> Value {
190    Value::NativeFunction(Rc::new(|args: &[Value]| {
191        if args.len() != 1 {
192            return Ok(NativeCallResult::Return(Value::err(Value::string(
193                "io.read_file(path) requires a single string path",
194            ))));
195        }
196
197        let path = match args[0].as_string() {
198            Some(p) => p,
199            None => {
200                return Ok(NativeCallResult::Return(Value::err(Value::string(
201                    "io.read_file(path) requires a string path",
202                ))))
203            }
204        };
205        match fs::read_to_string(path) {
206            Ok(contents) => Ok(NativeCallResult::Return(Value::ok(Value::string(contents)))),
207            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
208                err.to_string(),
209            )))),
210        }
211    }))
212}
213
214fn create_io_read_file_bytes_fn() -> Value {
215    Value::NativeFunction(Rc::new(|args: &[Value]| {
216        if args.len() != 1 {
217            return Ok(NativeCallResult::Return(Value::err(Value::string(
218                "io.read_file_bytes(path) requires a single string path",
219            ))));
220        }
221
222        let path = match args[0].as_string() {
223            Some(p) => p,
224            None => {
225                return Ok(NativeCallResult::Return(Value::err(Value::string(
226                    "io.read_file_bytes(path) requires a string path",
227                ))))
228            }
229        };
230
231        match fs::read(path) {
232            Ok(bytes) => {
233                let values: Vec<Value> = bytes
234                    .into_iter()
235                    .map(|b| Value::Int(b as LustInt))
236                    .collect();
237                Ok(NativeCallResult::Return(Value::ok(Value::array(values))))
238            }
239
240            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
241                err.to_string(),
242            )))),
243        }
244    }))
245}
246
247fn create_io_write_file_fn() -> Value {
248    Value::NativeFunction(Rc::new(|args: &[Value]| {
249        if args.len() < 2 {
250            return Ok(NativeCallResult::Return(Value::err(Value::string(
251                "io.write_file(path, contents) requires a path and value",
252            ))));
253        }
254
255        let path = match args[0].as_string() {
256            Some(p) => p,
257            None => {
258                return Ok(NativeCallResult::Return(Value::err(Value::string(
259                    "io.write_file(path, contents) requires a string path",
260                ))))
261            }
262        };
263        let contents = if let Some(s) = args[1].as_string() {
264            s.to_string()
265        } else {
266            format!("{}", args[1])
267        };
268        match fs::write(path, contents) {
269            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::Nil))),
270            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
271                err.to_string(),
272            )))),
273        }
274    }))
275}
276
277fn create_io_read_stdin_fn() -> Value {
278    Value::NativeFunction(Rc::new(|args: &[Value]| {
279        if !args.is_empty() {
280            return Ok(NativeCallResult::Return(Value::err(Value::string(
281                "io.read_stdin() takes no arguments",
282            ))));
283        }
284
285        let mut buffer = String::new();
286        match io::stdin().read_to_string(&mut buffer) {
287            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::string(buffer)))),
288            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
289                err.to_string(),
290            )))),
291        }
292    }))
293}
294
295fn create_io_read_line_fn() -> Value {
296    Value::NativeFunction(Rc::new(|args: &[Value]| {
297        if !args.is_empty() {
298            return Ok(NativeCallResult::Return(Value::err(Value::string(
299                "io.read_line() takes no arguments",
300            ))));
301        }
302
303        let mut line = String::new();
304        match io::stdin().read_line(&mut line) {
305            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::string(line)))),
306            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
307                err.to_string(),
308            )))),
309        }
310    }))
311}
312
313fn create_io_write_stdout_fn() -> Value {
314    Value::NativeFunction(Rc::new(|args: &[Value]| {
315        let mut stdout = io::stdout();
316        for arg in args {
317            if let Err(err) = write!(stdout, "{}", arg) {
318                return Ok(NativeCallResult::Return(Value::err(Value::string(
319                    err.to_string(),
320                ))));
321            }
322        }
323
324        if let Err(err) = stdout.flush() {
325            return Ok(NativeCallResult::Return(Value::err(Value::string(
326                err.to_string(),
327            ))));
328        }
329
330        Ok(NativeCallResult::Return(Value::ok(Value::Nil)))
331    }))
332}
333
334fn create_os_module(vm: &VM) -> Value {
335    let entries = [
336        (string_key("time"), create_os_time_fn()),
337        (string_key("sleep"), create_os_sleep_fn()),
338        (string_key("create_file"), create_os_create_file_fn()),
339        (string_key("create_dir"), create_os_create_dir_fn()),
340        (string_key("remove_file"), create_os_remove_file_fn()),
341        (string_key("remove_dir"), create_os_remove_dir_fn()),
342        (string_key("rename"), create_os_rename_fn()),
343    ];
344    vm.map_with_entries(entries)
345}
346
347fn create_os_time_fn() -> Value {
348    Value::NativeFunction(Rc::new(|args: &[Value]| {
349        if !args.is_empty() {
350            return Ok(NativeCallResult::Return(Value::err(Value::string(
351                "os.time() takes no arguments",
352            ))));
353        }
354
355        let now = SystemTime::now();
356        let seconds = match now.duration_since(UNIX_EPOCH) {
357            Ok(duration) => duration.as_secs_f64(),
358            Err(err) => -(err.duration().as_secs_f64()),
359        };
360
361        Ok(NativeCallResult::Return(Value::Float(seconds)))
362    }))
363}
364
365fn create_os_sleep_fn() -> Value {
366    Value::NativeFunction(Rc::new(|args: &[Value]| {
367        if args.len() != 1 {
368            return Ok(NativeCallResult::Return(Value::err(Value::string(
369                "os.sleep(seconds) requires a single float duration",
370            ))));
371        }
372
373        let seconds = match args[0].as_float() {
374            Some(value) => value,
375            None => {
376                return Ok(NativeCallResult::Return(Value::err(Value::string(
377                    "os.sleep(seconds) requires a float duration",
378                ))))
379            }
380        };
381
382        if !seconds.is_finite() || seconds < 0.0 {
383            return Ok(NativeCallResult::Return(Value::err(Value::string(
384                "os.sleep(seconds) requires a finite, non-negative duration",
385            ))));
386        }
387
388        if seconds > (u64::MAX as f64) {
389            return Ok(NativeCallResult::Return(Value::err(Value::string(
390                "os.sleep(seconds) duration is too large",
391            ))));
392        }
393
394        thread::sleep(Duration::from_secs_f64(seconds));
395
396        Ok(NativeCallResult::Return(Value::ok(Value::Nil)))
397    }))
398}
399
400fn create_os_create_file_fn() -> Value {
401    Value::NativeFunction(Rc::new(|args: &[Value]| {
402        if args.len() != 1 {
403            return Ok(NativeCallResult::Return(Value::err(Value::string(
404                "os.create_file(path) requires a single string path",
405            ))));
406        }
407
408        let path = match args[0].as_string() {
409            Some(p) => p,
410            None => {
411                return Ok(NativeCallResult::Return(Value::err(Value::string(
412                    "os.create_file(path) requires a string path",
413                ))))
414            }
415        };
416        match fs::OpenOptions::new().write(true).create(true).open(path) {
417            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::Nil))),
418            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
419                err.to_string(),
420            )))),
421        }
422    }))
423}
424
425fn create_os_create_dir_fn() -> Value {
426    Value::NativeFunction(Rc::new(|args: &[Value]| {
427        if args.len() != 1 {
428            return Ok(NativeCallResult::Return(Value::err(Value::string(
429                "os.create_dir(path) requires a single string path",
430            ))));
431        }
432
433        let path = match args[0].as_string() {
434            Some(p) => p,
435            None => {
436                return Ok(NativeCallResult::Return(Value::err(Value::string(
437                    "os.create_dir(path) requires a string path",
438                ))))
439            }
440        };
441        match fs::create_dir_all(path) {
442            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::Nil))),
443            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
444                err.to_string(),
445            )))),
446        }
447    }))
448}
449
450fn create_os_remove_file_fn() -> Value {
451    Value::NativeFunction(Rc::new(|args: &[Value]| {
452        if args.len() != 1 {
453            return Ok(NativeCallResult::Return(Value::err(Value::string(
454                "os.remove_file(path) requires a single string path",
455            ))));
456        }
457
458        let path = match args[0].as_string() {
459            Some(p) => p,
460            None => {
461                return Ok(NativeCallResult::Return(Value::err(Value::string(
462                    "os.remove_file(path) requires a string path",
463                ))))
464            }
465        };
466        match fs::remove_file(path) {
467            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::Nil))),
468            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
469                err.to_string(),
470            )))),
471        }
472    }))
473}
474
475fn create_os_remove_dir_fn() -> Value {
476    Value::NativeFunction(Rc::new(|args: &[Value]| {
477        if args.len() != 1 {
478            return Ok(NativeCallResult::Return(Value::err(Value::string(
479                "os.remove_dir(path) requires a single string path",
480            ))));
481        }
482
483        let path = match args[0].as_string() {
484            Some(p) => p,
485            None => {
486                return Ok(NativeCallResult::Return(Value::err(Value::string(
487                    "os.remove_dir(path) requires a string path",
488                ))))
489            }
490        };
491        match fs::remove_dir_all(path) {
492            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::Nil))),
493            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
494                err.to_string(),
495            )))),
496        }
497    }))
498}
499
500fn create_os_rename_fn() -> Value {
501    Value::NativeFunction(Rc::new(|args: &[Value]| {
502        if args.len() != 2 {
503            return Ok(NativeCallResult::Return(Value::err(Value::string(
504                "os.rename(from, to) requires two string paths",
505            ))));
506        }
507
508        let from = match args[0].as_string() {
509            Some(f) => f,
510            None => {
511                return Ok(NativeCallResult::Return(Value::err(Value::string(
512                    "os.rename(from, to) requires string paths",
513                ))))
514            }
515        };
516        let to = match args[1].as_string() {
517            Some(t) => t,
518            None => {
519                return Ok(NativeCallResult::Return(Value::err(Value::string(
520                    "os.rename(from, to) requires string paths",
521                ))))
522            }
523        };
524        match fs::rename(from, to) {
525            Ok(_) => Ok(NativeCallResult::Return(Value::ok(Value::Nil))),
526            Err(err) => Ok(NativeCallResult::Return(Value::err(Value::string(
527                err.to_string(),
528            )))),
529        }
530    }))
531}
532
533fn create_string_module(vm: &VM) -> Value {
534    let entries = [
535        (string_key("len"), create_string_len_fn()),
536        (string_key("lower"), create_string_lower_fn()),
537        (string_key("upper"), create_string_upper_fn()),
538        (string_key("sub"), create_string_sub_fn()),
539        (string_key("byte"), create_string_byte_fn()),
540        (string_key("char"), create_string_char_fn()),
541        (string_key("find"), create_string_find_fn()),
542        (string_key("match"), create_string_match_fn()),
543        (string_key("gsub"), create_string_gsub_fn()),
544        (string_key("format"), create_string_format_fn()),
545    ];
546    vm.map_with_entries(entries)
547}
548
549fn create_string_len_fn() -> Value {
550    Value::NativeFunction(Rc::new(|args: &[Value]| {
551        let input = args.get(0).cloned().unwrap_or(Value::Nil);
552        let value = unwrap_lua_value(input);
553        match value {
554            Value::Nil => Ok(NativeCallResult::Return(Value::Int(0))), // Nil has length 0
555            Value::String(s) => Ok(NativeCallResult::Return(Value::Int(s.len() as LustInt))),
556            other => Err(format!("string.len expects a string, got {:?}", other)),
557        }
558    }))
559}
560
561fn create_string_lower_fn() -> Value {
562    Value::NativeFunction(Rc::new(|args: &[Value]| {
563        let value = unwrap_lua_value(args.get(0).cloned().unwrap_or(Value::Nil));
564        let s = value
565            .as_string()
566            .ok_or_else(|| "string.lower expects a string".to_string())?;
567        Ok(NativeCallResult::Return(Value::string(&s.to_lowercase())))
568    }))
569}
570
571fn create_string_upper_fn() -> Value {
572    Value::NativeFunction(Rc::new(|args: &[Value]| {
573        let value = unwrap_lua_value(args.get(0).cloned().unwrap_or(Value::Nil));
574        let s = value
575            .as_string()
576            .ok_or_else(|| "string.upper expects a string".to_string())?;
577        Ok(NativeCallResult::Return(Value::string(&s.to_uppercase())))
578    }))
579}
580
581fn create_string_sub_fn() -> Value {
582    Value::NativeFunction(Rc::new(|args: &[Value]| {
583        let value = unwrap_lua_value(args.get(0).cloned().unwrap_or(Value::Nil));
584        let source = value
585            .as_string()
586            .ok_or_else(|| "string.sub expects a string".to_string())?;
587        let start = args
588            .get(1)
589            .map(|v| unwrap_lua_value(v.clone()).as_int().unwrap_or(1))
590            .unwrap_or(1);
591        let end = args
592            .get(2)
593            .map(|v| unwrap_lua_value(v.clone()).as_int().unwrap_or(source.len() as LustInt));
594        let (start_idx, end_idx) = normalize_range(start, end, source.len());
595        if start_idx >= source.len() || start_idx >= end_idx {
596            return Ok(NativeCallResult::Return(Value::string("")));
597        }
598        let slice = &source.as_bytes()[start_idx..end_idx.min(source.len())];
599        Ok(NativeCallResult::Return(Value::string(
600            String::from_utf8_lossy(slice),
601        )))
602    }))
603}
604
605fn create_string_byte_fn() -> Value {
606    Value::NativeFunction(Rc::new(|args: &[Value]| {
607        let value = unwrap_lua_value(args.get(0).cloned().unwrap_or(Value::Nil));
608        let source = value
609            .as_string()
610            .ok_or_else(|| "string.byte expects a string".to_string())?;
611        let start = args
612            .get(1)
613            .map(|v| unwrap_lua_value(v.clone()).as_int().unwrap_or(1))
614            .unwrap_or(1);
615        let end = args
616            .get(2)
617            .map(|v| unwrap_lua_value(v.clone()).as_int().unwrap_or(start));
618        let (start_idx, end_idx) = normalize_range(start, end, source.len());
619        let bytes = source.as_bytes();
620        if start_idx >= bytes.len() || start_idx >= end_idx {
621            return return_lua_values(vec![lua_nil()]);
622        }
623        let mut values = Vec::new();
624        for b in &bytes[start_idx..end_idx.min(bytes.len())] {
625            values.push(Value::Int(*b as LustInt));
626        }
627        return_lua_values(values)
628    }))
629}
630
631fn create_string_char_fn() -> Value {
632    Value::NativeFunction(Rc::new(|args: &[Value]| {
633        let mut output = String::new();
634        for arg in args {
635            let raw = unwrap_lua_value(arg.clone());
636            let code = raw
637                .as_int()
638                .or_else(|| raw.as_float().map(|f| f as LustInt))
639                .ok_or_else(|| "string.char expects numeric arguments".to_string())?;
640            if code < 0 || code > 255 {
641                return Err("string.char codepoints must be in [0,255]".to_string());
642            }
643            if let Some(ch) = char::from_u32(code as u32) {
644                output.push(ch);
645            }
646        }
647        Ok(NativeCallResult::Return(Value::string(output)))
648    }))
649}
650
651fn create_string_find_fn() -> Value {
652    Value::NativeFunction(Rc::new(|args: &[Value]| {
653        let subject = unwrap_lua_value(args.get(0).cloned().unwrap_or(Value::Nil));
654        let pattern_val = unwrap_lua_value(args.get(1).cloned().unwrap_or(Value::Nil));
655        let haystack = subject
656            .as_string()
657            .ok_or_else(|| "string.find expects a string subject".to_string())?;
658        let pattern = pattern_val
659            .as_string()
660            .ok_or_else(|| "string.find expects a pattern string".to_string())?;
661        let start = args
662            .get(2)
663            .map(|v| unwrap_lua_value(v.clone()).as_int().unwrap_or(1))
664            .unwrap_or(1);
665        let plain = args
666            .get(3)
667            .map(|v| matches!(unwrap_lua_value(v.clone()), Value::Bool(true)))
668            .unwrap_or(false);
669        let (offset, _) = normalize_range(start, None, haystack.len());
670        if offset > haystack.len() {
671            return return_lua_values(vec![lua_nil()]);
672        }
673        let slice = haystack.get(offset..).unwrap_or("");
674        if plain {
675            if let Some(pos) = slice.find(pattern) {
676                let begin = offset + pos;
677                let end = begin + pattern.len().saturating_sub(1);
678                return return_lua_values(vec![
679                    Value::Int((begin as LustInt) + 1),
680                    Value::Int((end as LustInt) + 1),
681                ]);
682            }
683            return return_lua_values(vec![lua_nil()]);
684        }
685        let regex = lua_pattern_to_regex(pattern)?;
686        if let Some(caps) = regex.captures(slice) {
687            if let Some(mat) = caps.get(0) {
688                let begin = offset + mat.start();
689                let end = offset + mat.end().saturating_sub(1);
690                let mut results: Vec<Value> = vec![
691                    Value::Int((begin as LustInt) + 1),
692                    Value::Int((end as LustInt) + 1),
693                ];
694                for idx in 1..caps.len() {
695                    if let Some(c) = caps.get(idx) {
696                        results.push(Value::string(c.as_str()));
697                    } else {
698                        results.push(lua_nil());
699                    }
700                }
701                return return_lua_values(results);
702            }
703        }
704        return_lua_values(vec![lua_nil()])
705    }))
706}
707
708fn create_string_match_fn() -> Value {
709    Value::NativeFunction(Rc::new(|args: &[Value]| {
710        let subject = unwrap_lua_value(args.get(0).cloned().unwrap_or(Value::Nil));
711        let pattern_val = unwrap_lua_value(args.get(1).cloned().unwrap_or(Value::Nil));
712        let haystack = subject
713            .as_string()
714            .ok_or_else(|| "string.match expects a string subject".to_string())?;
715        let pattern = pattern_val
716            .as_string()
717            .ok_or_else(|| "string.match expects a pattern string".to_string())?;
718        let start = args
719            .get(2)
720            .map(|v| unwrap_lua_value(v.clone()).as_int().unwrap_or(1))
721            .unwrap_or(1);
722        let (offset, _) = normalize_range(start, None, haystack.len());
723        if offset > haystack.len() {
724            return Ok(NativeCallResult::Return(Value::Nil));
725        }
726        let slice = haystack.get(offset..).unwrap_or("");
727        let regex = lua_pattern_to_regex(pattern)?;
728        let Some(caps) = regex.captures(slice) else {
729            return Ok(NativeCallResult::Return(Value::Nil));
730        };
731        let Some(mat) = caps.get(0) else {
732            return Ok(NativeCallResult::Return(Value::Nil));
733        };
734
735        let capture_count = caps.len().saturating_sub(1);
736        if capture_count == 0 {
737            return Ok(NativeCallResult::Return(Value::string(mat.as_str())));
738        }
739        if capture_count == 1 {
740            if let Some(c) = caps.get(1) {
741                return Ok(NativeCallResult::Return(Value::string(c.as_str())));
742            }
743            return Ok(NativeCallResult::Return(Value::Nil));
744        }
745
746        let mut results = Vec::with_capacity(capture_count);
747        for idx in 1..caps.len() {
748            if let Some(c) = caps.get(idx) {
749                results.push(Value::string(c.as_str()));
750            } else {
751                results.push(Value::Nil);
752            }
753        }
754        return_lua_values(results)
755    }))
756}
757
758fn create_string_gsub_fn() -> Value {
759    Value::NativeFunction(Rc::new(|args: &[Value]| {
760        let subject = unwrap_lua_value(args.get(0).cloned().unwrap_or(Value::Nil));
761        let pattern_val = unwrap_lua_value(args.get(1).cloned().unwrap_or(Value::Nil));
762        let repl = args.get(2).cloned().unwrap_or(Value::Nil);
763        let limit = args
764            .get(3)
765            .map(|v| unwrap_lua_value(v.clone()).as_int().unwrap_or(-1))
766            .unwrap_or(-1);
767        let text = subject
768            .as_string()
769            .ok_or_else(|| "string.gsub expects a string subject".to_string())?;
770        let pattern = pattern_val
771            .as_string()
772            .ok_or_else(|| "string.gsub expects a pattern string".to_string())?;
773        let regex = lua_pattern_to_regex(pattern)?;
774        let max_repls = if limit < 0 { i64::MAX } else { limit };
775        enum Replacer {
776            String(String),
777            Func(Value),
778            Other(Value),
779        }
780        let replacer = match &repl {
781            Value::Enum { enum_name, variant, .. }
782                if enum_name == "LuaValue" && variant == "Function" =>
783            {
784                Replacer::Func(repl.clone())
785            }
786            Value::Function(_) | Value::NativeFunction(_) | Value::Closure { .. } => {
787                Replacer::Func(repl.clone())
788            }
789            _ => {
790                let unwrapped = unwrap_lua_value(repl.clone());
791                match unwrapped {
792                    Value::String(s) => Replacer::String(s.to_string()),
793                    other => Replacer::Other(other),
794                }
795            }
796        };
797        let mut last_end = 0;
798        let mut count: i64 = 0;
799        let mut output = String::new();
800        for caps in regex.captures_iter(text) {
801            if count >= max_repls {
802                break;
803            }
804            let mat = caps.get(0).unwrap();
805            output.push_str(&text[last_end..mat.start()]);
806            let replacement = match &replacer {
807                Replacer::String(template) => build_template_replacement(template.as_str(), &caps),
808                Replacer::Func(func_val) => {
809                    VM::with_current(|vm| {
810                        let mut call_args = Vec::new();
811                        if caps.len() > 1 {
812                            for idx in 1..caps.len() {
813                                if let Some(c) = caps.get(idx) {
814                                    call_args.push(to_lua_value(vm, Value::string(c.as_str()))?);
815                                } else {
816                                    call_args.push(lua_nil());
817                                }
818                            }
819                        } else if let Some(m) = caps.get(0) {
820                            call_args.push(to_lua_value(vm, Value::string(m.as_str()))?);
821                        }
822                        let result = vm
823                            .call_value(func_val, call_args)
824                            .map_err(|e| e.to_string())?;
825                        let first = unwrap_first_return(result);
826                        Ok(first.to_string())
827                    })?
828                }
829                Replacer::Other(other) => other.to_string(),
830            };
831            output.push_str(&replacement);
832            last_end = mat.end();
833            count += 1;
834        }
835        output.push_str(&text[last_end..]);
836        return_lua_values(vec![Value::string(output), Value::Int(count)])
837    }))
838}
839
840fn create_string_format_fn() -> Value {
841    Value::NativeFunction(Rc::new(|args: &[Value]| {
842        if args.is_empty() {
843            return Err("string.format requires a format string".to_string());
844        }
845        let fmt_val = unwrap_lua_value(args[0].clone());
846        let fmt = fmt_val
847            .as_string()
848            .ok_or_else(|| "string.format expects a string format".to_string())?;
849        let rendered = render_format(fmt, &args[1..])?;
850        Ok(NativeCallResult::Return(Value::string(rendered)))
851    }))
852}
853
854#[derive(Clone)]
855enum TableData {
856    Array(Value),
857    Map(Value),
858}
859
860fn table_data(value: &Value) -> Option<TableData> {
861    if value.as_array().is_some() {
862        return Some(TableData::Array(value.clone()));
863    }
864
865    if value.as_map().is_some() {
866        return Some(TableData::Map(value.clone()));
867    }
868
869    if let Some(map) = value.struct_get_field("table") {
870        if map.as_map().is_some() {
871            return Some(TableData::Map(map));
872        }
873    }
874
875    None
876}
877
878fn read_sequence(data: &TableData) -> Vec<Value> {
879    match data {
880        TableData::Array(val) => val.as_array().unwrap_or_default(),
881        TableData::Map(val) => {
882            let map = val.as_map().unwrap_or_default();
883            let mut seq: Vec<Value> = Vec::new();
884            let mut idx: LustInt = 1;
885            loop {
886                let key = ValueKey::from_value(&Value::Int(idx));
887                if let Some(val) = map.get(&key) {
888                    seq.push(val.clone());
889                    idx += 1;
890                } else {
891                    break;
892                }
893            }
894            seq
895        }
896    }
897}
898
899pub(crate) fn create_table_unpack_fn() -> Value {
900    Value::NativeFunction(Rc::new(|args: &[Value]| {
901        if args.is_empty() {
902            return Err("table.unpack expects a table/array".to_string());
903        }
904        let table_val = unwrap_lua_value(args[0].clone());
905        let Some(data) = table_data(&table_val) else {
906            return Err("table.unpack expects a table/array".to_string());
907        };
908        let seq = read_sequence(&data);
909        let start = args
910            .get(1)
911            .and_then(|v| unwrap_lua_value(v.clone()).as_int())
912            .unwrap_or(1);
913        let end = args
914            .get(2)
915            .and_then(|v| unwrap_lua_value(v.clone()).as_int())
916            .unwrap_or(seq.len() as LustInt);
917        let start_idx = (start - 1).max(0) as usize;
918        let end_idx = end.max(0) as usize;
919        let mut values: Vec<Value> = Vec::new();
920        for (i, val) in seq.iter().enumerate() {
921            if i < start_idx || i >= end_idx {
922                continue;
923            }
924            values.push(val.clone());
925        }
926        return_lua_values(values)
927    }))
928}
929
930fn create_math_random_fn() -> Value {
931    Value::NativeFunction(Rc::new(|args: &[Value]| {
932        let lower = args
933            .get(0)
934            .map(|v| unwrap_lua_value(v.clone()))
935            .and_then(|v| if matches!(v, Value::Nil) { None } else { Some(v) });
936        let upper = args
937            .get(1)
938            .map(|v| unwrap_lua_value(v.clone()))
939            .and_then(|v| if matches!(v, Value::Nil) { None } else { Some(v) });
940        let value = with_rng_mut(|rng| match (lower.as_ref(), upper.as_ref()) {
941            (None, _) => Value::Float(rng.gen::<f64>()),
942            (Some(max), None) => {
943                let hi = coerce_int(max).unwrap_or(1);
944                let upper_bound = if hi < 1 { 1 } else { hi };
945                Value::Int(rng.gen_range(1..=upper_bound))
946            }
947            (Some(min), Some(max)) => {
948                let lo = coerce_int(min).unwrap_or(1);
949                let hi = coerce_int(max).unwrap_or(lo);
950                let (start, end) = if lo <= hi { (lo, hi) } else { (hi, lo) };
951                Value::Int(rng.gen_range(start..=end))
952            }
953        })?;
954        Ok(NativeCallResult::Return(value))
955    }))
956}
957
958fn create_math_randomseed_fn() -> Value {
959    Value::NativeFunction(Rc::new(|args: &[Value]| {
960        let seed_val = args
961            .get(0)
962            .map(|v| unwrap_lua_value(v.clone()))
963            .unwrap_or(Value::Int(0));
964        let seed = coerce_int(&seed_val).unwrap_or(0) as u64;
965        let mutex = RNG.get_or_init(|| Mutex::new(StdRng::from_entropy()));
966        *mutex.lock().map_err(|e| e.to_string())? = StdRng::seed_from_u64(seed);
967        Ok(NativeCallResult::Return(Value::Nil))
968    }))
969}
970
971fn coerce_int(value: &Value) -> Option<LustInt> {
972    match value {
973        Value::Int(i) => Some(*i),
974        Value::Float(f) => Some(*f as LustInt),
975        Value::Bool(b) => Some(if *b { 1 } else { 0 }),
976        _ => None,
977    }
978}
979
980fn with_rng_mut<F, R>(f: F) -> Result<R, String>
981where
982    F: FnOnce(&mut StdRng) -> R,
983{
984    let mutex = RNG.get_or_init(|| Mutex::new(StdRng::from_entropy()));
985    mutex
986        .lock()
987        .map_err(|e| e.to_string())
988        .map(|mut guard| f(&mut *guard))
989}
990
991fn render_format(fmt: &str, args: &[Value]) -> Result<String, String> {
992    let mut out = String::new();
993    let mut arg_idx = 0;
994    let mut chars = fmt.chars().peekable();
995    while let Some(ch) = chars.next() {
996        if ch != '%' {
997            out.push(ch);
998            continue;
999        }
1000        if let Some('%') = chars.peek() {
1001            chars.next();
1002            out.push('%');
1003            continue;
1004        }
1005        let mut zero_pad = false;
1006        if let Some('0') = chars.peek() {
1007            zero_pad = true;
1008            chars.next();
1009        }
1010        let mut width_str = String::new();
1011        while let Some(next) = chars.peek() {
1012            if next.is_ascii_digit() {
1013                width_str.push(*next);
1014                chars.next();
1015            } else {
1016                break;
1017            }
1018        }
1019        let mut precision: Option<usize> = None;
1020        if let Some('.') = chars.peek() {
1021            chars.next();
1022            let mut prec = String::new();
1023            while let Some(next) = chars.peek() {
1024                if next.is_ascii_digit() {
1025                    prec.push(*next);
1026                    chars.next();
1027                } else {
1028                    break;
1029                }
1030            }
1031            if !prec.is_empty() {
1032                precision = prec.parse().ok();
1033            }
1034        }
1035        let spec = chars
1036            .next()
1037            .ok_or_else(|| "incomplete format specifier".to_string())?;
1038        let width = if width_str.is_empty() {
1039            None
1040        } else {
1041            width_str.parse().ok()
1042        };
1043        let arg = args
1044            .get(arg_idx)
1045            .cloned()
1046            .unwrap_or(Value::Nil);
1047        arg_idx += 1;
1048        let raw = unwrap_lua_value(arg);
1049        let formatted = match spec {
1050            's' => raw.to_string(),
1051            'd' | 'i' | 'u' => {
1052                let num = raw
1053                    .as_int()
1054                    .or_else(|| raw.as_float().map(|f| f as LustInt))
1055                    .unwrap_or(0);
1056                pad_value(format!("{}", num), width, zero_pad)
1057            }
1058            'x' => {
1059                let num = raw
1060                    .as_int()
1061                    .or_else(|| raw.as_float().map(|f| f as LustInt))
1062                    .unwrap_or(0);
1063                pad_value(format!("{:x}", num), width, zero_pad)
1064            }
1065            'X' => {
1066                let num = raw
1067                    .as_int()
1068                    .or_else(|| raw.as_float().map(|f| f as LustInt))
1069                    .unwrap_or(0);
1070                pad_value(format!("{:X}", num), width, zero_pad)
1071            }
1072            'c' => {
1073                let num = raw
1074                    .as_int()
1075                    .or_else(|| raw.as_float().map(|f| f as LustInt))
1076                    .unwrap_or(0);
1077                if let Some(ch) = char::from_u32(num as u32) {
1078                    ch.to_string()
1079                } else {
1080                    "".to_string()
1081                }
1082            }
1083            'f' | 'g' => {
1084                let num = raw
1085                    .as_float()
1086                    .or_else(|| raw.as_int().map(|i| i as f64))
1087                    .unwrap_or(0.0);
1088                if let Some(p) = precision {
1089                    pad_value(format!("{:.*}", p, num), width, zero_pad)
1090                } else {
1091                    pad_value(format!("{}", num), width, zero_pad)
1092                }
1093            }
1094            other => {
1095                pad_value(format!("{}", other), width, zero_pad)
1096            }
1097        };
1098        out.push_str(&formatted);
1099    }
1100    Ok(out)
1101}
1102
1103fn pad_value(value: String, width: Option<usize>, zero_pad: bool) -> String {
1104    if let Some(w) = width {
1105        if value.len() < w {
1106            let mut padded = String::new();
1107            let pad_char = if zero_pad { '0' } else { ' ' };
1108            for _ in 0..(w - value.len()) {
1109                padded.push(pad_char);
1110            }
1111            padded.push_str(&value);
1112            return padded;
1113        }
1114    }
1115    value
1116}
1117
1118fn normalize_range(start: LustInt, end: Option<LustInt>, len: usize) -> (usize, usize) {
1119    let len_i = len as LustInt;
1120    let mut s = if start < 0 { len_i + start + 1 } else { start };
1121    let mut e = end.unwrap_or(len_i);
1122    if e < 0 {
1123        e = len_i + e + 1;
1124    }
1125    if s < 1 {
1126        s = 1;
1127    }
1128    if e < 0 {
1129        e = 0;
1130    }
1131    if e > len_i {
1132        e = len_i;
1133    }
1134    if s > e {
1135        return (len, len);
1136    }
1137    (
1138        s.saturating_sub(1) as usize,
1139        e.max(0) as usize,
1140    )
1141}
1142
1143fn lua_pattern_to_regex(pattern: &str) -> Result<Regex, String> {
1144    fn is_regex_meta(ch: char) -> bool {
1145        matches!(ch, '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\')
1146    }
1147    let mut out = String::new();
1148    let mut chars = pattern.chars().peekable();
1149    let mut in_class = false;
1150    while let Some(ch) = chars.next() {
1151        match ch {
1152            '%' => {
1153                if let Some(next) = chars.next() {
1154                    let translated = match next {
1155                        'a' => Some(if in_class { "A-Za-z" } else { "[A-Za-z]" }),
1156                        'c' => Some(if in_class { "\\p{Cc}" } else { "[\\p{Cc}]" }),
1157                        'd' => Some(if in_class { "0-9" } else { "[0-9]" }),
1158                        'l' => Some(if in_class { "a-z" } else { "[a-z]" }),
1159                        'u' => Some(if in_class { "A-Z" } else { "[A-Z]" }),
1160                        'w' => Some(if in_class { "A-Za-z0-9_" } else { "[A-Za-z0-9_]" }),
1161                        'x' => Some(if in_class { "A-Fa-f0-9" } else { "[A-Fa-f0-9]" }),
1162                        's' => Some(if in_class { "\\s" } else { "[\\s]" }),
1163                        'p' => Some(if in_class { "\\p{P}" } else { "[\\p{P}]" }),
1164                        'z' => Some(if in_class { "\\x00" } else { "[\\x00]" }),
1165                        '%' => Some("%"),
1166                        _ => None,
1167                    };
1168                    if let Some(rep) = translated {
1169                        out.push_str(rep);
1170                    } else {
1171                        if is_regex_meta(next) {
1172                            out.push('\\');
1173                        }
1174                        out.push(next);
1175                    }
1176                } else {
1177                    out.push('%');
1178                }
1179            }
1180            '[' => {
1181                in_class = true;
1182                out.push('[');
1183            }
1184            ']' => {
1185                in_class = false;
1186                out.push(']');
1187            }
1188            '.' | '+' | '*' | '?' => out.push(ch),
1189            '^' | '$' | '(' | ')' => out.push(ch),
1190            '{' | '}' | '|' | '\\' => {
1191                out.push('\\');
1192                out.push(ch);
1193            }
1194            '-' => {
1195                if in_class {
1196                    out.push('-');
1197                } else {
1198                    out.push_str("*?");
1199                }
1200            }
1201            other => {
1202                if !in_class && is_regex_meta(other) {
1203                    out.push('\\');
1204                }
1205                out.push(other);
1206            }
1207        }
1208    }
1209    Regex::new(&out).map_err(|e| e.to_string())
1210}
1211
1212fn build_template_replacement(template: &str, caps: &regex::Captures) -> String {
1213    let mut out = String::new();
1214    let mut chars = template.chars().peekable();
1215    while let Some(ch) = chars.next() {
1216        if ch == '%' {
1217            if let Some(next) = chars.next() {
1218                if next == '%' {
1219                    out.push('%');
1220                    continue;
1221                }
1222                if let Some(d) = next.to_digit(10) {
1223                    let idx = d as usize;
1224                    if idx == 0 {
1225                        if let Some(m) = caps.get(0) {
1226                            out.push_str(m.as_str());
1227                        }
1228                    } else if let Some(m) = caps.get(idx) {
1229                        out.push_str(m.as_str());
1230                    }
1231                    continue;
1232                }
1233                out.push(next);
1234            } else {
1235                out.push('%');
1236            }
1237        } else {
1238            out.push(ch);
1239        }
1240    }
1241    out
1242}
1243
1244fn to_lua_value(vm: &VM, value: Value) -> Result<Value, String> {
1245    if let Value::Enum { enum_name, .. } = &value {
1246        if enum_name == "LuaValue" {
1247            return Ok(value);
1248        }
1249    }
1250    Ok(match value.clone() {
1251        Value::Nil => Value::enum_unit("LuaValue", "Nil"),
1252        Value::Bool(b) => Value::enum_variant("LuaValue", "Bool", vec![Value::Bool(b)]),
1253        Value::Int(i) => Value::enum_variant("LuaValue", "Int", vec![Value::Int(i)]),
1254        Value::Float(f) => Value::enum_variant("LuaValue", "Float", vec![Value::Float(f)]),
1255        Value::String(s) => Value::enum_variant("LuaValue", "String", vec![Value::String(s)]),
1256        Value::Map(map) => {
1257            let table = Value::Map(map.clone());
1258            let metamethods = vm.new_map_value();
1259            let lua_table = vm
1260                .instantiate_struct(
1261                    "LuaTable",
1262                    vec![
1263                        (Rc::new("table".to_string()), table),
1264                        (Rc::new("metamethods".to_string()), metamethods),
1265                    ],
1266                )
1267                .map_err(|e| e.to_string())?;
1268            Value::enum_variant("LuaValue", "Table", vec![lua_table])
1269        }
1270        Value::Function(_) | Value::Closure { .. } | Value::NativeFunction(_) => {
1271            let handle = register_lust_function(value.clone());
1272            let lua_fn = vm
1273                .instantiate_struct(
1274                    "LuaFunction",
1275                    vec![(Rc::new("handle".to_string()), Value::Int(handle as LustInt))],
1276                )
1277                .map_err(|e| e.to_string())?;
1278            Value::enum_variant("LuaValue", "Function", vec![lua_fn])
1279        }
1280        other => Value::enum_variant(
1281            "LuaValue",
1282            "LightUserdata",
1283            vec![Value::Int(other.type_of() as LustInt)],
1284        ),
1285    })
1286}
1287
1288fn return_lua_values(values: Vec<Value>) -> Result<NativeCallResult, String> {
1289    VM::with_current(|vm| pack_lua_values(vm, values).map(NativeCallResult::Return))
1290}
1291
1292fn pack_lua_values(vm: &VM, values: Vec<Value>) -> Result<Value, String> {
1293    let mut packed = Vec::with_capacity(values.len());
1294    for value in values {
1295        packed.push(to_lua_value(vm, value)?);
1296    }
1297    Ok(Value::array(packed))
1298}
1299
1300fn unwrap_first_return(value: Value) -> Value {
1301    if let Value::Array(arr) = value {
1302        if let Some(first) = arr.borrow().get(0) {
1303            return unwrap_lua_value(first.clone());
1304        }
1305        return Value::Nil;
1306    }
1307    unwrap_lua_value(value)
1308}
1309
1310fn lua_nil() -> Value {
1311    Value::enum_unit("LuaValue", "Nil")
1312}
1313
1314#[cfg(test)]
1315mod tests {
1316    use super::*;
1317    #[test]
1318    fn stdlib_defaults_without_optional_modules() {
1319        let vm = VM::with_config(&LustConfig::default());
1320        let stdlib = create_stdlib(&LustConfig::default(), &vm);
1321        assert!(!stdlib.iter().any(|(name, _)| *name == "io"));
1322        assert!(!stdlib.iter().any(|(name, _)| *name == "os"));
1323        assert!(!stdlib.iter().any(|(name, _)| *name == "string"));
1324    }
1325
1326    #[test]
1327    fn stdlib_includes_optional_modules_when_configured() {
1328        let cfg = LustConfig::from_toml_str(
1329            r#"
1330                [settings]
1331                stdlib_modules = ["io", "os", "string"]
1332            "#,
1333        )
1334        .expect("parse");
1335        let vm = VM::with_config(&cfg);
1336        let stdlib = create_stdlib(&cfg, &vm);
1337        assert!(stdlib.iter().any(|(name, _)| *name == "io"));
1338        assert!(stdlib.iter().any(|(name, _)| *name == "os"));
1339        assert!(stdlib.iter().any(|(name, _)| *name == "string"));
1340    }
1341}