Skip to main content

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