Skip to main content

proof_engine/scripting/
stdlib.rs

1//! Standard library for the scripting engine.
2//!
3//! Registers built-in functions: print, tostring, tonumber, type,
4//! math.*, string.*, table.*, io.*, os.*, bit.*, pairs, ipairs, pcall, etc.
5//!
6//! Every function has a real implementation — no stubs.
7
8use std::cell::RefCell;
9use std::sync::Arc;
10
11use super::vm::{NativeFunc, ScriptError, Table, Value, Vm};
12
13// ── Public Struct ─────────────────────────────────────────────────────────────
14
15/// Standard library registrar.
16pub struct Stdlib;
17
18impl Stdlib {
19    /// Register all standard library functions into the VM.
20    pub fn register_all(vm: &mut Vm) {
21        register_all(vm);
22    }
23}
24
25// ── Registration entry point ─────────────────────────────────────────────────
26
27/// Register all standard library functions into the VM.
28pub fn register_all(vm: &mut Vm) {
29    register_globals(vm);
30    register_math(vm);
31    register_string(vm);
32    register_table(vm);
33    register_io(vm);
34    register_os(vm);
35    register_bit(vm);
36}
37
38// ── Helper: make a NativeFunction value ──────────────────────────────────────
39
40fn native(name: &str, f: impl Fn(&mut Vm, Vec<Value>) -> Result<Vec<Value>, ScriptError> + Send + Sync + 'static) -> Value {
41    Value::NativeFunction(Arc::new(NativeFunc {
42        name: name.to_string(),
43        func: Box::new(f),
44    }))
45}
46
47// ── Global functions ──────────────────────────────────────────────────────────
48
49fn register_globals(vm: &mut Vm) {
50    // print
51    vm.register_native("print", |vm, args| {
52        let out: Vec<String> = args.iter().map(|v| v.to_string()).collect();
53        let line = out.join("\t");
54        vm.output.push(line.clone());
55        Ok(vec![])
56    });
57
58    // tostring
59    vm.register_native("tostring", |_vm, args| {
60        let s = args.into_iter().next().unwrap_or(Value::Nil).to_string();
61        Ok(vec![Value::Str(Arc::new(s))])
62    });
63
64    // tonumber with optional base
65    vm.register_native("tonumber", |_vm, args| {
66        let v    = args.first().cloned().unwrap_or(Value::Nil);
67        let base = args.get(1).and_then(|x| x.to_int()).unwrap_or(10);
68        let result = match &v {
69            Value::Int(i)   => Value::Int(*i),
70            Value::Float(f) => Value::Float(*f),
71            Value::Str(s)   => {
72                let s = s.trim();
73                if base == 10 {
74                    if let Ok(i) = s.parse::<i64>() {
75                        Value::Int(i)
76                    } else if let Ok(f) = s.parse::<f64>() {
77                        Value::Float(f)
78                    } else {
79                        Value::Nil
80                    }
81                } else {
82                    match i64::from_str_radix(s.trim_start_matches("0x").trim_start_matches("0X"), base as u32) {
83                        Ok(i) => Value::Int(i),
84                        Err(_) => Value::Nil,
85                    }
86                }
87            }
88            _ => Value::Nil,
89        };
90        Ok(vec![result])
91    });
92
93    // type
94    vm.register_native("type", |_vm, args| {
95        let t = args.into_iter().next().unwrap_or(Value::Nil).type_name();
96        Ok(vec![Value::Str(Arc::new(t.to_string()))])
97    });
98
99    // assert
100    vm.register_native("assert", |_vm, args| {
101        let cond = args.first().cloned().unwrap_or(Value::Nil);
102        if !cond.is_truthy() {
103            let msg = args.get(1)
104                .map(|v| v.to_string())
105                .unwrap_or_else(|| "assertion failed!".to_string());
106            return Err(ScriptError::new(msg));
107        }
108        Ok(args)
109    });
110
111    // error
112    vm.register_native("error", |_vm, args| {
113        let msg = args.into_iter().next().unwrap_or(Value::Nil).to_string();
114        Err(ScriptError::new(msg))
115    });
116
117    // pcall
118    vm.register_native("pcall", |vm, mut args| {
119        if args.is_empty() {
120            return Ok(vec![Value::Bool(false), Value::Str(Arc::new("no function".to_string()))]);
121        }
122        let func = args.remove(0);
123        match vm.call(func, args) {
124            Ok(mut results) => {
125                results.insert(0, Value::Bool(true));
126                Ok(results)
127            }
128            Err(e) => Ok(vec![Value::Bool(false), Value::Str(Arc::new(e.message))]),
129        }
130    });
131
132    // xpcall
133    vm.register_native("xpcall", |vm, mut args| {
134        if args.len() < 2 {
135            return Ok(vec![Value::Bool(false)]);
136        }
137        let func    = args.remove(0);
138        let handler = args.remove(0);
139        match vm.call(func, args) {
140            Ok(mut results) => {
141                results.insert(0, Value::Bool(true));
142                Ok(results)
143            }
144            Err(e) => {
145                let err_val = Value::Str(Arc::new(e.message.clone()));
146                let handler_result = vm.call(handler, vec![err_val.clone()]);
147                let msg = match handler_result {
148                    Ok(r) => r.into_iter().next().unwrap_or(err_val),
149                    Err(_) => err_val,
150                };
151                Ok(vec![Value::Bool(false), msg])
152            }
153        }
154    });
155
156    // ipairs
157    vm.register_native("ipairs", |_vm, args| {
158        let table = args.into_iter().next().unwrap_or(Value::Nil);
159        let iter_fn = Arc::new(NativeFunc {
160            name: "ipairs_iter".to_string(),
161            func: Box::new(|_vm, args| {
162                let table    = args.first().cloned().unwrap_or(Value::Nil);
163                let idx      = args.get(1).and_then(|v| v.to_int()).unwrap_or(0);
164                let next_idx = idx + 1;
165                match &table {
166                    Value::Table(t) => {
167                        let v = t.get(&Value::Int(next_idx));
168                        if matches!(v, Value::Nil) {
169                            Ok(vec![Value::Nil])
170                        } else {
171                            Ok(vec![Value::Int(next_idx), v])
172                        }
173                    }
174                    _ => Ok(vec![Value::Nil]),
175                }
176            }),
177        });
178        Ok(vec![Value::NativeFunction(iter_fn), table, Value::Int(0)])
179    });
180
181    // pairs
182    vm.register_native("pairs", |_vm, args| {
183        let table = args.into_iter().next().unwrap_or(Value::Nil);
184        let iter_fn = Arc::new(NativeFunc {
185            name: "pairs_iter".to_string(),
186            func: Box::new(|_vm, args| {
187                let table = args.first().cloned().unwrap_or(Value::Nil);
188                let key   = args.get(1).cloned().unwrap_or(Value::Nil);
189                match &table {
190                    Value::Table(t) => match t.next(&key) {
191                        Some((k, v)) => Ok(vec![k, v]),
192                        None         => Ok(vec![Value::Nil]),
193                    },
194                    _ => Ok(vec![Value::Nil]),
195                }
196            }),
197        });
198        Ok(vec![Value::NativeFunction(iter_fn), table, Value::Nil])
199    });
200
201    // next
202    vm.register_native("next", |_vm, args| {
203        let table = args.first().cloned().unwrap_or(Value::Nil);
204        let key   = args.get(1).cloned().unwrap_or(Value::Nil);
205        match &table {
206            Value::Table(t) => match t.next(&key) {
207                Some((k, v)) => Ok(vec![k, v]),
208                None         => Ok(vec![Value::Nil]),
209            },
210            _ => Err(ScriptError::new("next: not a table")),
211        }
212    });
213
214    // select
215    vm.register_native("select", |_vm, args| {
216        let selector = args.first().cloned().unwrap_or(Value::Nil);
217        match selector {
218            Value::Str(ref s) if s.as_ref() == "#" => {
219                Ok(vec![Value::Int((args.len() as i64) - 1)])
220            }
221            Value::Int(i) if i > 0 => {
222                let rest: Vec<Value> = args.into_iter().skip(i as usize).collect();
223                Ok(rest)
224            }
225            Value::Int(i) if i < 0 => {
226                let total = args.len() as i64 - 1;
227                let start = (total + i).max(0) as usize + 1;
228                let rest: Vec<Value> = args.into_iter().skip(start).collect();
229                Ok(rest)
230            }
231            _ => Err(ScriptError::new("select: invalid index")),
232        }
233    });
234
235    // unpack (global alias)
236    vm.register_native("unpack", |_vm, args| {
237        let table = args.first().cloned().unwrap_or(Value::Nil);
238        let i     = args.get(1).and_then(|v| v.to_int()).unwrap_or(1);
239        let j_opt = args.get(2).and_then(|v| v.to_int());
240        match table {
241            Value::Table(t) => {
242                let j = j_opt.unwrap_or_else(|| t.length());
243                let mut result = Vec::new();
244                for idx in i..=j {
245                    result.push(t.get(&Value::Int(idx)));
246                }
247                Ok(result)
248            }
249            _ => Err(ScriptError::new("unpack: not a table")),
250        }
251    });
252
253    // rawget
254    vm.register_native("rawget", |_vm, args| {
255        let table = args.first().cloned().unwrap_or(Value::Nil);
256        let key   = args.get(1).cloned().unwrap_or(Value::Nil);
257        match table {
258            Value::Table(t) => Ok(vec![t.get(&key)]),
259            _ => Err(ScriptError::new("rawget: not a table")),
260        }
261    });
262
263    // rawset
264    vm.register_native("rawset", |_vm, args| {
265        let table = args.first().cloned().unwrap_or(Value::Nil);
266        let key   = args.get(1).cloned().unwrap_or(Value::Nil);
267        let val   = args.get(2).cloned().unwrap_or(Value::Nil);
268        match &table {
269            Value::Table(t) => { t.set(key, val); Ok(vec![table]) }
270            _ => Err(ScriptError::new("rawset: not a table")),
271        }
272    });
273
274    // rawequal
275    vm.register_native("rawequal", |_vm, args| {
276        let a = args.first().cloned().unwrap_or(Value::Nil);
277        let b = args.get(1).cloned().unwrap_or(Value::Nil);
278        Ok(vec![Value::Bool(a == b)])
279    });
280
281    // rawlen
282    vm.register_native("rawlen", |_vm, args| {
283        let v = args.into_iter().next().unwrap_or(Value::Nil);
284        let n = match v {
285            Value::Table(t) => t.length(),
286            Value::Str(s)   => s.len() as i64,
287            _ => return Err(ScriptError::new("rawlen: not a table or string")),
288        };
289        Ok(vec![Value::Int(n)])
290    });
291
292    // setmetatable
293    vm.register_native("setmetatable", |_vm, args| {
294        let table = args.first().cloned().unwrap_or(Value::Nil);
295        let mt    = args.get(1).cloned().unwrap_or(Value::Nil);
296        if let Value::Table(t) = &table {
297            match mt {
298                Value::Table(mt_table) => t.set_metatable(Some(mt_table)),
299                Value::Nil             => t.set_metatable(None),
300                _ => return Err(ScriptError::new("setmetatable: metatable must be a table or nil")),
301            }
302            Ok(vec![table])
303        } else {
304            Err(ScriptError::new("setmetatable: not a table"))
305        }
306    });
307
308    // getmetatable
309    vm.register_native("getmetatable", |_vm, args| {
310        let v = args.into_iter().next().unwrap_or(Value::Nil);
311        if let Value::Table(t) = &v {
312            match t.get_metatable() {
313                Some(mt) => Ok(vec![Value::Table(mt)]),
314                None     => Ok(vec![Value::Nil]),
315            }
316        } else {
317            Ok(vec![Value::Nil])
318        }
319    });
320
321    // require — minimal stub; host can override
322    vm.register_native("require", |_vm, args| {
323        let _path = args.into_iter().next();
324        Ok(vec![Value::Nil])
325    });
326
327    // collectgarbage
328    vm.register_native("collectgarbage", |_vm, _args| {
329        Ok(vec![Value::Int(0)])
330    });
331}
332
333// ── math.* ────────────────────────────────────────────────────────────────────
334
335/// Thread-local xorshift64 PRNG state.
336thread_local! {
337    static RAND_STATE: RefCell<u64> = RefCell::new(0x853c49e6748fea9b);
338}
339
340fn xorshift64() -> u64 {
341    RAND_STATE.with(|s| {
342        let mut x = s.borrow().wrapping_add(1);
343        if x == 0 { x = 0x853c49e6748fea9b; }
344        x ^= x << 13;
345        x ^= x >> 7;
346        x ^= x << 17;
347        *s.borrow_mut() = x;
348        x
349    })
350}
351
352fn register_math(vm: &mut Vm) {
353    let math = Table::new();
354
355    // Constants
356    math.rawset_str("pi",         Value::Float(std::f64::consts::PI));
357    math.rawset_str("tau",        Value::Float(std::f64::consts::TAU));
358    math.rawset_str("huge",       Value::Float(f64::INFINITY));
359    math.rawset_str("nan",        Value::Float(f64::NAN));
360    math.rawset_str("maxinteger", Value::Int(i64::MAX));
361    math.rawset_str("mininteger", Value::Int(i64::MIN));
362
363    // Single-arg float functions
364    math_fn1(&math, "abs",   |a| a.abs());
365    math_fn1(&math, "ceil",  |a| a.ceil());
366    math_fn1(&math, "floor", |a| a.floor());
367    math_fn1(&math, "sqrt",  |a| a.sqrt());
368    math_fn1(&math, "cbrt",  |a| a.cbrt());
369    math_fn1(&math, "exp",   |a| a.exp());
370    math_fn1(&math, "ln",    |a| a.ln());
371    math_fn1(&math, "log2",  |a| a.log2());
372    math_fn1(&math, "log10", |a| a.log10());
373    math_fn1(&math, "sin",   |a| a.sin());
374    math_fn1(&math, "cos",   |a| a.cos());
375    math_fn1(&math, "tan",   |a| a.tan());
376    math_fn1(&math, "asin",  |a| a.asin());
377    math_fn1(&math, "acos",  |a| a.acos());
378    math_fn1(&math, "atan",  |a| a.atan());
379
380    // round: returns integer when input is integer, else float
381    math.rawset_str("round", native("math.round", |_vm, args| {
382        match args.first().cloned().unwrap_or(Value::Nil) {
383            Value::Int(i)   => Ok(vec![Value::Int(i)]),
384            Value::Float(f) => Ok(vec![Value::Float(f.round())]),
385            v => Err(ScriptError::new(format!("math.round: not a number (got {})", v.type_name()))),
386        }
387    }));
388
389    // atan2(y, x)
390    math.rawset_str("atan2", native("math.atan2", |_vm, args| {
391        let y = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
392        let x = args.get(1).and_then(|v| v.to_float()).unwrap_or(1.0);
393        Ok(vec![Value::Float(y.atan2(x))])
394    }));
395
396    // hypot(x, y)
397    math.rawset_str("hypot", native("math.hypot", |_vm, args| {
398        let x = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
399        let y = args.get(1).and_then(|v| v.to_float()).unwrap_or(0.0);
400        Ok(vec![Value::Float(x.hypot(y))])
401    }));
402
403    // pow(x, y)
404    math.rawset_str("pow", native("math.pow", |_vm, args| {
405        let x = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
406        let y = args.get(1).and_then(|v| v.to_float()).unwrap_or(1.0);
407        Ok(vec![Value::Float(x.powf(y))])
408    }));
409
410    // log(x [, base])
411    math.rawset_str("log", native("math.log", |_vm, args| {
412        let x    = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
413        let base = args.get(1).and_then(|v| v.to_float());
414        let r    = match base {
415            Some(b) => x.log(b),
416            None    => x.ln(),
417        };
418        Ok(vec![Value::Float(r)])
419    }));
420
421    // max(...)
422    math.rawset_str("max", native("math.max", |_vm, args| {
423        if args.is_empty() { return Err(ScriptError::new("math.max: no arguments")); }
424        let mut best = args[0].clone();
425        for a in &args[1..] {
426            let bv = best.to_float().unwrap_or(f64::NEG_INFINITY);
427            let av = a.to_float().unwrap_or(f64::NEG_INFINITY);
428            if av > bv { best = a.clone(); }
429        }
430        Ok(vec![best])
431    }));
432
433    // min(...)
434    math.rawset_str("min", native("math.min", |_vm, args| {
435        if args.is_empty() { return Err(ScriptError::new("math.min: no arguments")); }
436        let mut best = args[0].clone();
437        for a in &args[1..] {
438            let bv = best.to_float().unwrap_or(f64::INFINITY);
439            let av = a.to_float().unwrap_or(f64::INFINITY);
440            if av < bv { best = a.clone(); }
441        }
442        Ok(vec![best])
443    }));
444
445    // clamp(x, min, max)
446    math.rawset_str("clamp", native("math.clamp", |_vm, args| {
447        let x   = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
448        let lo  = args.get(1).and_then(|v| v.to_float()).unwrap_or(f64::NEG_INFINITY);
449        let hi  = args.get(2).and_then(|v| v.to_float()).unwrap_or(f64::INFINITY);
450        Ok(vec![Value::Float(x.clamp(lo, hi))])
451    }));
452
453    // fmod(x, y)
454    math.rawset_str("fmod", native("math.fmod", |_vm, args| {
455        let a = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
456        let b = args.get(1).and_then(|v| v.to_float()).unwrap_or(1.0);
457        Ok(vec![Value::Float(a % b)])
458    }));
459
460    // modf(x) -> int_part, frac_part
461    math.rawset_str("modf", native("math.modf", |_vm, args| {
462        let a = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
463        Ok(vec![Value::Float(a.trunc()), Value::Float(a.fract())])
464    }));
465
466    // type(x) -> "integer" | "float" | "other"
467    math.rawset_str("type", native("math.type", |_vm, args| {
468        let t = match args.into_iter().next().unwrap_or(Value::Nil) {
469            Value::Int(_)   => "integer",
470            Value::Float(_) => "float",
471            _               => "other",
472        };
473        Ok(vec![Value::Str(Arc::new(t.to_string()))])
474    }));
475
476    // tointeger(x)
477    math.rawset_str("tointeger", native("math.tointeger", |_vm, args| {
478        let v = args.into_iter().next().unwrap_or(Value::Nil);
479        Ok(vec![v.to_int().map(Value::Int).unwrap_or(Value::Nil)])
480    }));
481
482    // isnan(x)
483    math.rawset_str("isnan", native("math.isnan", |_vm, args| {
484        let f = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
485        Ok(vec![Value::Bool(f.is_nan())])
486    }));
487
488    // isinf(x)
489    math.rawset_str("isinf", native("math.isinf", |_vm, args| {
490        let f = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
491        Ok(vec![Value::Bool(f.is_infinite())])
492    }));
493
494    // random([m [, n]])
495    math.rawset_str("random", native("math.random", |_vm, args| {
496        let r = (xorshift64() as f64) / (u64::MAX as f64);
497        let result = match args.len() {
498            0 => Value::Float(r),
499            1 => {
500                let m = args[0].to_int().unwrap_or(1).max(1);
501                Value::Int(1 + (r * m as f64) as i64 % m)
502            }
503            _ => {
504                let lo = args[0].to_int().unwrap_or(1);
505                let hi = args[1].to_int().unwrap_or(1);
506                if hi < lo { return Err(ScriptError::new("math.random: bad argument")); }
507                let range = (hi - lo + 1).max(1);
508                Value::Int(lo + (r * range as f64) as i64 % range)
509            }
510        };
511        Ok(vec![result])
512    }));
513
514    // randomseed(x)
515    math.rawset_str("randomseed", native("math.randomseed", |_vm, args| {
516        let seed = args.first().and_then(|v| v.to_int()).unwrap_or(0) as u64;
517        RAND_STATE.with(|s| {
518            *s.borrow_mut() = if seed == 0 { 0x853c49e6748fea9b } else { seed };
519        });
520        Ok(vec![])
521    }));
522
523    vm.set_global("math", Value::Table(math));
524}
525
526fn math_fn1(table: &Table, name: &'static str, f: fn(f64) -> f64) {
527    table.rawset_str(name, Value::NativeFunction(Arc::new(NativeFunc {
528        name: format!("math.{}", name),
529        func: Box::new(move |_vm, args| {
530            let a = args.first().and_then(|v| v.to_float()).unwrap_or(0.0);
531            Ok(vec![Value::Float(f(a))])
532        }),
533    })));
534}
535
536// ── string.* ─────────────────────────────────────────────────────────────────
537
538fn register_string(vm: &mut Vm) {
539    let string = Table::new();
540
541    // len(s)
542    string.rawset_str("len", native("string.len", |_vm, args| {
543        let s = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
544        Ok(vec![Value::Int(s.len() as i64)])
545    }));
546
547    // sub(s, i [, j])
548    string.rawset_str("sub", native("string.sub", |_vm, args| {
549        let s   = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
550        let len = s.len() as i64;
551        let i   = args.get(1).and_then(|v| v.to_int()).unwrap_or(1);
552        let j   = args.get(2).and_then(|v| v.to_int()).unwrap_or(-1);
553        let from = if i < 0 { (len + i).max(0) } else { (i - 1).max(0) } as usize;
554        let to   = if j < 0 { (len + j + 1).max(0) } else { j.min(len) } as usize;
555        let result = if from <= to && from < s.len() {
556            s.get(from..to.min(s.len())).unwrap_or("").to_string()
557        } else {
558            String::new()
559        };
560        Ok(vec![Value::Str(Arc::new(result))])
561    }));
562
563    // rep(s, n [, sep])
564    string.rawset_str("rep", native("string.rep", |_vm, args| {
565        let s   = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
566        let n   = args.get(1).and_then(|v| v.to_int()).unwrap_or(0).max(0) as usize;
567        let sep = args.get(2).and_then(|v| v.to_str_repr()).unwrap_or_default();
568        let result = if n == 0 {
569            String::new()
570        } else {
571            let parts: Vec<&str> = std::iter::repeat(s.as_str()).take(n).collect();
572            parts.join(&sep)
573        };
574        Ok(vec![Value::Str(Arc::new(result))])
575    }));
576
577    // rev(s)
578    string.rawset_str("rev", native("string.rev", |_vm, args| {
579        let s = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
580        Ok(vec![Value::Str(Arc::new(s.chars().rev().collect()))])
581    }));
582
583    // upper(s)
584    string.rawset_str("upper", native("string.upper", |_vm, args| {
585        let s = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
586        Ok(vec![Value::Str(Arc::new(s.to_uppercase()))])
587    }));
588
589    // lower(s)
590    string.rawset_str("lower", native("string.lower", |_vm, args| {
591        let s = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
592        Ok(vec![Value::Str(Arc::new(s.to_lowercase()))])
593    }));
594
595    // byte(s [, i [, j]])
596    string.rawset_str("byte", native("string.byte", |_vm, args| {
597        let s = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
598        let i = args.get(1).and_then(|v| v.to_int()).unwrap_or(1);
599        let j = args.get(2).and_then(|v| v.to_int()).unwrap_or(i);
600        let bytes = s.as_bytes();
601        let mut result = Vec::new();
602        for idx in i..=j {
603            if idx >= 1 && (idx as usize) <= bytes.len() {
604                result.push(Value::Int(bytes[idx as usize - 1] as i64));
605            }
606        }
607        Ok(result)
608    }));
609
610    // char(...)
611    string.rawset_str("char", native("string.char", |_vm, args| {
612        let mut s = String::new();
613        for a in &args {
614            if let Some(i) = a.to_int() {
615                if let Some(c) = char::from_u32(i as u32) {
616                    s.push(c);
617                }
618            }
619        }
620        Ok(vec![Value::Str(Arc::new(s))])
621    }));
622
623    // dump(fn) -> hex string of chunk pointer (sandbox-safe fake)
624    string.rawset_str("dump", native("string.dump", |_vm, args| {
625        let v = args.into_iter().next().unwrap_or(Value::Nil);
626        let hex = match &v {
627            Value::Function(f)       => format!("{:016x}", Arc::as_ptr(f) as usize),
628            Value::NativeFunction(f) => format!("{:016x}", Arc::as_ptr(f) as usize),
629            _ => return Err(ScriptError::new("string.dump: function expected")),
630        };
631        Ok(vec![Value::Str(Arc::new(hex))])
632    }));
633
634    // split(s, sep)
635    string.rawset_str("split", native("string.split", |_vm, args| {
636        let s   = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
637        let sep = args.get(1).and_then(|v| v.to_str_repr()).unwrap_or_else(|| "\t".to_string());
638        let tbl = Table::new();
639        let parts: Vec<&str> = if sep.is_empty() {
640            s.chars().map(|_| "").collect() // degenerate — split by char below
641        } else {
642            s.split(sep.as_str()).collect()
643        };
644        if sep.is_empty() {
645            for (i, ch) in s.chars().enumerate() {
646                tbl.set(Value::Int(i as i64 + 1), Value::Str(Arc::new(ch.to_string())));
647            }
648        } else {
649            for (i, p) in parts.into_iter().enumerate() {
650                tbl.set(Value::Int(i as i64 + 1), Value::Str(Arc::new(p.to_string())));
651            }
652        }
653        Ok(vec![Value::Table(tbl)])
654    }));
655
656    // format(fmt, ...)
657    string.rawset_str("format", native("string.format", |_vm, args| {
658        let fmt_str = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
659        let result  = string_format(&fmt_str, &args[1.min(args.len())..]);
660        Ok(vec![Value::Str(Arc::new(result))])
661    }));
662
663    // find(s, pattern [, init [, plain]])
664    string.rawset_str("find", native("string.find", |_vm, args| {
665        let s       = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
666        let pattern = args.get(1).and_then(|v| v.to_str_repr()).unwrap_or_default();
667        let init    = args.get(2).and_then(|v| v.to_int()).unwrap_or(1);
668        let plain   = args.get(3).map(|v| v.is_truthy()).unwrap_or(false);
669        let start   = lua_idx_to_usize(init, s.len());
670        if plain {
671            if let Some(pos) = s[start..].find(pattern.as_str()) {
672                let abs_start = start + pos + 1;
673                let abs_end   = abs_start + pattern.len() - 1;
674                return Ok(vec![Value::Int(abs_start as i64), Value::Int(abs_end as i64)]);
675            }
676            return Ok(vec![Value::Nil]);
677        }
678        match lua_pattern_find(&s, &pattern, start) {
679            Some((ms, me, caps)) => {
680                let mut result = vec![Value::Int(ms as i64 + 1), Value::Int(me as i64)];
681                for cap in caps {
682                    result.push(Value::Str(Arc::new(cap)));
683                }
684                Ok(result)
685            }
686            None => Ok(vec![Value::Nil]),
687        }
688    }));
689
690    // match(s, pattern [, init])
691    string.rawset_str("match", native("string.match", |_vm, args| {
692        let s       = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
693        let pattern = args.get(1).and_then(|v| v.to_str_repr()).unwrap_or_default();
694        let init    = args.get(2).and_then(|v| v.to_int()).unwrap_or(1);
695        let start   = lua_idx_to_usize(init, s.len());
696        match lua_pattern_find(&s, &pattern, start) {
697            Some((ms, me, caps)) => {
698                if caps.is_empty() {
699                    Ok(vec![Value::Str(Arc::new(s[ms..me].to_string()))])
700                } else {
701                    Ok(caps.into_iter().map(|c| Value::Str(Arc::new(c))).collect())
702                }
703            }
704            None => Ok(vec![Value::Nil]),
705        }
706    }));
707
708    // gmatch(s, pattern) -> iterator
709    string.rawset_str("gmatch", native("string.gmatch", |_vm, args| {
710        let s       = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
711        let pattern = args.get(1).and_then(|v| v.to_str_repr()).unwrap_or_default();
712        // Pre-collect all matches
713        let matches: Vec<Vec<String>> = gmatch_collect(&s, &pattern);
714        let matches_arc = Arc::new(std::sync::Mutex::new((matches, 0usize)));
715        let iter_fn = Arc::new(NativeFunc {
716            name: "gmatch_iter".to_string(),
717            func: Box::new(move |_vm, _args| {
718                let mut guard = matches_arc.lock().unwrap();
719                let (ref matches, ref mut idx) = *guard;
720                if *idx >= matches.len() {
721                    return Ok(vec![Value::Nil]);
722                }
723                let m = &matches[*idx];
724                *idx += 1;
725                Ok(m.iter().map(|s| Value::Str(Arc::new(s.clone()))).collect())
726            }),
727        });
728        Ok(vec![Value::NativeFunction(iter_fn)])
729    }));
730
731    // gsub(s, pattern, repl [, n])
732    string.rawset_str("gsub", native("string.gsub", |vm, args| {
733        let s       = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
734        let pattern = args.get(1).and_then(|v| v.to_str_repr()).unwrap_or_default();
735        let repl    = args.get(2).cloned().unwrap_or(Value::Nil);
736        let max_n   = args.get(3).and_then(|v| v.to_int());
737
738        let (result, count) = gsub_impl(vm, &s, &pattern, &repl, max_n)?;
739        Ok(vec![Value::Str(Arc::new(result)), Value::Int(count as i64)])
740    }));
741
742    vm.set_global("string", Value::Table(string));
743}
744
745// ── Pattern matching helpers ──────────────────────────────────────────────────
746
747fn lua_idx_to_usize(i: i64, len: usize) -> usize {
748    if i >= 1 { (i as usize - 1).min(len) }
749    else if i < 0 { (len as i64 + i).max(0) as usize }
750    else { 0 }
751}
752
753/// Very small Lua-pattern engine.  Supports: `.` `*` `+` `?` `^` `$`
754/// and char classes `%d %a %l %u %s %w %p %c %x` (and their upper-case inverses).
755fn lua_pattern_find(s: &str, pattern: &str, start: usize) -> Option<(usize, usize, Vec<String>)> {
756    let sb = s.as_bytes();
757    let pb = pattern.as_bytes();
758
759    let anchored = pb.first() == Some(&b'^');
760    let pb = if anchored { &pb[1..] } else { pb };
761
762    let search_start = start;
763    let search_end   = if anchored { search_start + 1 } else { sb.len() + 1 };
764
765    for si in search_start..search_end {
766        if si > sb.len() { break; }
767        let mut caps: Vec<(usize, usize)> = Vec::new();
768        if let Some(end) = pat_match(sb, si, pb, 0, &mut caps) {
769            let captures: Vec<String> = caps.iter()
770                .map(|(cs, ce)| String::from_utf8_lossy(&sb[*cs..*ce]).to_string())
771                .collect();
772            return Some((si, end, captures));
773        }
774    }
775    None
776}
777
778fn pat_match(s: &[u8], mut si: usize, p: &[u8], mut pi: usize, caps: &mut Vec<(usize, usize)>) -> Option<usize> {
779    loop {
780        if pi >= p.len() {
781            // Check for trailing $
782            return Some(si);
783        }
784        if p[pi] == b'$' && pi + 1 == p.len() {
785            return if si == s.len() { Some(si) } else { None };
786        }
787        // Capture group open
788        if p[pi] == b'(' {
789            let cap_idx = caps.len();
790            caps.push((si, si));
791            let result = pat_match(s, si, p, pi + 1, caps);
792            if result.is_none() { caps.pop(); }
793            return result;
794        }
795        if p[pi] == b')' {
796            let cap_idx = caps.len() - 1;
797            let old = caps[cap_idx];
798            caps[cap_idx] = (old.0, si);
799            let result = pat_match(s, si, p, pi + 1, caps);
800            if result.is_none() { caps[cap_idx] = old; }
801            return result;
802        }
803        // Read single pattern element + optional quantifier
804        let (cls_len, cls_end) = pat_class_len(p, pi);
805        let quantifier = p.get(pi + cls_len).copied();
806        match quantifier {
807            Some(b'*') => {
808                // greedy match 0+
809                let mut count = 0;
810                while si + count < s.len() && pat_class_match(s[si + count], p, pi) {
811                    count += 1;
812                }
813                for c in (0..=count).rev() {
814                    if let Some(end) = pat_match(s, si + c, p, pi + cls_len + 1, caps) {
815                        return Some(end);
816                    }
817                }
818                return None;
819            }
820            Some(b'+') => {
821                let mut count = 0;
822                while si + count < s.len() && pat_class_match(s[si + count], p, pi) {
823                    count += 1;
824                }
825                if count == 0 { return None; }
826                for c in (1..=count).rev() {
827                    if let Some(end) = pat_match(s, si + c, p, pi + cls_len + 1, caps) {
828                        return Some(end);
829                    }
830                }
831                return None;
832            }
833            Some(b'?') => {
834                if si < s.len() && pat_class_match(s[si], p, pi) {
835                    if let Some(end) = pat_match(s, si + 1, p, pi + cls_len + 1, caps) {
836                        return Some(end);
837                    }
838                }
839                pi += cls_len + 1;
840                // fall through to try 0-match
841                continue;
842            }
843            _ => {
844                // Exactly one match
845                if si >= s.len() { return None; }
846                if !pat_class_match(s[si], p, pi) { return None; }
847                si += 1;
848                pi += cls_len;
849            }
850        }
851    }
852}
853
854fn pat_class_len(p: &[u8], pi: usize) -> (usize, usize) {
855    if p[pi] == b'%' && pi + 1 < p.len() { (2, pi + 2) }
856    else { (1, pi + 1) }
857}
858
859fn pat_class_match(c: u8, p: &[u8], pi: usize) -> bool {
860    if p[pi] == b'%' && pi + 1 < p.len() {
861        match_percent_class(c, p[pi + 1])
862    } else if p[pi] == b'.' {
863        true
864    } else {
865        p[pi] == c
866    }
867}
868
869fn match_percent_class(c: u8, cls: u8) -> bool {
870    let ch = c as char;
871    match cls {
872        b'd' => ch.is_ascii_digit(),
873        b'D' => !ch.is_ascii_digit(),
874        b'a' => ch.is_ascii_alphabetic(),
875        b'A' => !ch.is_ascii_alphabetic(),
876        b'l' => ch.is_ascii_lowercase(),
877        b'L' => !ch.is_ascii_lowercase(),
878        b'u' => ch.is_ascii_uppercase(),
879        b'U' => !ch.is_ascii_uppercase(),
880        b's' => ch.is_ascii_whitespace(),
881        b'S' => !ch.is_ascii_whitespace(),
882        b'w' => ch.is_ascii_alphanumeric(),
883        b'W' => !ch.is_ascii_alphanumeric(),
884        b'p' => ch.is_ascii_punctuation(),
885        b'P' => !ch.is_ascii_punctuation(),
886        b'c' => ch.is_ascii_control(),
887        b'C' => !ch.is_ascii_control(),
888        b'x' => ch.is_ascii_hexdigit(),
889        b'X' => !ch.is_ascii_hexdigit(),
890        _    => c == cls, // literal %X -> X
891    }
892}
893
894fn gmatch_collect(s: &str, pattern: &str) -> Vec<Vec<String>> {
895    let sb = s.as_bytes();
896    let pb = pattern.as_bytes();
897    let anchored = pb.first() == Some(&b'^');
898    let pb2 = if anchored { &pb[1..] } else { pb };
899    let mut results = Vec::new();
900    let mut si = 0;
901    while si <= sb.len() {
902        let mut caps: Vec<(usize, usize)> = Vec::new();
903        if let Some(end) = pat_match(sb, si, pb2, 0, &mut caps) {
904            if caps.is_empty() {
905                results.push(vec![String::from_utf8_lossy(&sb[si..end]).to_string()]);
906            } else {
907                results.push(caps.iter().map(|(cs, ce)| {
908                    String::from_utf8_lossy(&sb[*cs..*ce]).to_string()
909                }).collect());
910            }
911            if end == si { si += 1; } else { si = end; }
912            if anchored { break; }
913        } else {
914            si += 1;
915        }
916    }
917    results
918}
919
920fn gsub_impl(vm: &mut Vm, s: &str, pattern: &str, repl: &Value, max_n: Option<i64>) -> Result<(String, usize), ScriptError> {
921    let sb  = s.as_bytes();
922    let pb  = pattern.as_bytes();
923    let anchored = pb.first() == Some(&b'^');
924    let pb2 = if anchored { &pb[1..] } else { pb };
925    let mut result = String::new();
926    let mut si     = 0usize;
927    let mut count  = 0usize;
928    let limit      = max_n.unwrap_or(i64::MAX) as usize;
929
930    while si <= sb.len() && count < limit {
931        let mut caps: Vec<(usize, usize)> = Vec::new();
932        if let Some(end) = pat_match(sb, si, pb2, 0, &mut caps) {
933            let whole = &s[si..end];
934            let replacement = match repl {
935                Value::Str(r) => {
936                    // Handle %0, %1, etc.
937                    let mut rep = String::new();
938                    let rb = r.as_bytes();
939                    let mut ri = 0;
940                    while ri < rb.len() {
941                        if rb[ri] == b'%' && ri + 1 < rb.len() {
942                            let next = rb[ri + 1];
943                            if next == b'%' { rep.push('%'); ri += 2; }
944                            else if next >= b'0' && next <= b'9' {
945                                let ci = (next - b'0') as usize;
946                                let cap_str = if ci == 0 { whole.to_string() }
947                                else { caps.get(ci - 1).map(|(cs, ce)| s[*cs..*ce].to_string()).unwrap_or_default() };
948                                rep.push_str(&cap_str);
949                                ri += 2;
950                            } else { rep.push('%'); ri += 1; }
951                        } else { rep.push(rb[ri] as char); ri += 1; }
952                    }
953                    rep
954                }
955                Value::Table(t) => {
956                    let key = if caps.is_empty() {
957                        Value::Str(Arc::new(whole.to_string()))
958                    } else {
959                        let (cs, ce) = caps[0];
960                        Value::Str(Arc::new(s[cs..ce].to_string()))
961                    };
962                    let v = t.get(&key);
963                    if v.is_truthy() { v.to_string() } else { whole.to_string() }
964                }
965                Value::Function(_) | Value::NativeFunction(_) => {
966                    let call_args: Vec<Value> = if caps.is_empty() {
967                        vec![Value::Str(Arc::new(whole.to_string()))]
968                    } else {
969                        caps.iter().map(|(cs, ce)| Value::Str(Arc::new(s[*cs..*ce].to_string()))).collect()
970                    };
971                    let res = vm.call(repl.clone(), call_args)?;
972                    let v   = res.into_iter().next().unwrap_or(Value::Nil);
973                    if v.is_truthy() { v.to_string() } else { whole.to_string() }
974                }
975                _ => whole.to_string(),
976            };
977            result.push_str(&s[si..si]); // nothing before match in current window
978            result.push_str(&replacement);
979            count += 1;
980            if end == si {
981                if si < sb.len() { result.push(sb[si] as char); }
982                si += 1;
983            } else {
984                si = end;
985            }
986            if anchored { break; }
987        } else {
988            if si < sb.len() { result.push(sb[si] as char); }
989            si += 1;
990        }
991    }
992    // Append remainder
993    if si <= s.len() { result.push_str(&s[si..]); }
994    Ok((result, count))
995}
996
997/// Printf-style formatter supporting %d %i %u %o %x %X %f %e %g %s %q %%
998/// with optional width/precision flags.
999fn string_format(fmt: &str, args: &[Value]) -> String {
1000    let mut result = String::new();
1001    let mut chars  = fmt.chars().peekable();
1002    let mut arg_i  = 0usize;
1003
1004    while let Some(c) = chars.next() {
1005        if c != '%' { result.push(c); continue; }
1006        // Parse flags
1007        let mut flags_str = String::new();
1008        loop {
1009            match chars.peek() {
1010                Some(&'-') | Some(&'+') | Some(&' ') | Some(&'0') | Some(&'#') => {
1011                    flags_str.push(chars.next().unwrap());
1012                }
1013                _ => break,
1014            }
1015        }
1016        // Width
1017        let mut width_str = String::new();
1018        while chars.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
1019            width_str.push(chars.next().unwrap());
1020        }
1021        // Precision
1022        let mut prec_str = String::new();
1023        if chars.peek() == Some(&'.') {
1024            chars.next();
1025            while chars.peek().map(|c| c.is_ascii_digit()).unwrap_or(false) {
1026                prec_str.push(chars.next().unwrap());
1027            }
1028        }
1029        let width: usize = width_str.parse().unwrap_or(0);
1030        let prec:  Option<usize> = if prec_str.is_empty() { None } else { prec_str.parse().ok() };
1031        let left_align = flags_str.contains('-');
1032        let zero_pad   = flags_str.contains('0') && !left_align;
1033        let plus_sign  = flags_str.contains('+');
1034
1035        match chars.next() {
1036            Some('%') => result.push('%'),
1037            Some(spec @ ('d' | 'i')) => {
1038                let v = args.get(arg_i).and_then(|v| v.to_int()).unwrap_or(0);
1039                arg_i += 1;
1040                let s = if plus_sign && v >= 0 { format!("+{}", v) } else { v.to_string() };
1041                result.push_str(&pad_str(&s, width, left_align, if zero_pad { '0' } else { ' ' }));
1042                let _ = spec;
1043            }
1044            Some('u') => {
1045                let v = args.get(arg_i).and_then(|v| v.to_int()).unwrap_or(0) as u64;
1046                arg_i += 1;
1047                let s = v.to_string();
1048                result.push_str(&pad_str(&s, width, left_align, if zero_pad { '0' } else { ' ' }));
1049            }
1050            Some('o') => {
1051                let v = args.get(arg_i).and_then(|v| v.to_int()).unwrap_or(0) as u64;
1052                arg_i += 1;
1053                let s = format!("{:o}", v);
1054                result.push_str(&pad_str(&s, width, left_align, if zero_pad { '0' } else { ' ' }));
1055            }
1056            Some('x') => {
1057                let v = args.get(arg_i).and_then(|v| v.to_int()).unwrap_or(0) as u64;
1058                arg_i += 1;
1059                let s = format!("{:x}", v);
1060                result.push_str(&pad_str(&s, width, left_align, if zero_pad { '0' } else { ' ' }));
1061            }
1062            Some('X') => {
1063                let v = args.get(arg_i).and_then(|v| v.to_int()).unwrap_or(0) as u64;
1064                arg_i += 1;
1065                let s = format!("{:X}", v);
1066                result.push_str(&pad_str(&s, width, left_align, if zero_pad { '0' } else { ' ' }));
1067            }
1068            Some('f') => {
1069                let v = args.get(arg_i).and_then(|v| v.to_float()).unwrap_or(0.0);
1070                arg_i += 1;
1071                let p = prec.unwrap_or(6);
1072                let s = if plus_sign && v >= 0.0 { format!("+{:.prec$}", v, prec = p) } else { format!("{:.prec$}", v, prec = p) };
1073                result.push_str(&pad_str(&s, width, left_align, if zero_pad { '0' } else { ' ' }));
1074            }
1075            Some('e') => {
1076                let v = args.get(arg_i).and_then(|v| v.to_float()).unwrap_or(0.0);
1077                arg_i += 1;
1078                let p = prec.unwrap_or(6);
1079                let s = format!("{:.prec$e}", v, prec = p);
1080                result.push_str(&pad_str(&s, width, left_align, ' '));
1081            }
1082            Some('g') => {
1083                let v = args.get(arg_i).and_then(|v| v.to_float()).unwrap_or(0.0);
1084                arg_i += 1;
1085                let s = format!("{}", v);
1086                result.push_str(&pad_str(&s, width, left_align, ' '));
1087            }
1088            Some('s') => {
1089                let v = args.get(arg_i).map(|v| v.to_string()).unwrap_or_default();
1090                arg_i += 1;
1091                let s = if let Some(p) = prec { v.chars().take(p).collect() } else { v };
1092                result.push_str(&pad_str(&s, width, left_align, ' '));
1093            }
1094            Some('q') => {
1095                let v = args.get(arg_i).and_then(|v| v.to_str_repr()).unwrap_or_default();
1096                arg_i += 1;
1097                result.push('"');
1098                for ch in v.chars() {
1099                    match ch {
1100                        '"'  => result.push_str("\\\""),
1101                        '\\' => result.push_str("\\\\"),
1102                        '\n' => result.push_str("\\n"),
1103                        '\r' => result.push_str("\\r"),
1104                        '\0' => result.push_str("\\0"),
1105                        c    => result.push(c),
1106                    }
1107                }
1108                result.push('"');
1109            }
1110            Some(x) => { result.push('%'); result.push(x); }
1111            None    => result.push('%'),
1112        }
1113    }
1114    result
1115}
1116
1117fn pad_str(s: &str, width: usize, left: bool, pad: char) -> String {
1118    if s.len() >= width { return s.to_string(); }
1119    let padding: String = std::iter::repeat(pad).take(width - s.len()).collect();
1120    if left { format!("{}{}", s, padding) } else { format!("{}{}", padding, s) }
1121}
1122
1123// ── table.* ───────────────────────────────────────────────────────────────────
1124
1125fn register_table(vm: &mut Vm) {
1126    let tbl = Table::new();
1127
1128    // insert(t [, pos], v)
1129    tbl.rawset_str("insert", native("table.insert", |_vm, args| {
1130        let table = args.first().cloned().unwrap_or(Value::Nil);
1131        match table {
1132            Value::Table(t) => {
1133                if args.len() == 2 {
1134                    t.push(args[1].clone());
1135                } else if args.len() >= 3 {
1136                    let pos = args[1].to_int().unwrap_or(t.length() + 1);
1137                    let val = args[2].clone();
1138                    let len = t.length();
1139                    // Shift right
1140                    for i in (pos..=len).rev() {
1141                        let v = t.get(&Value::Int(i));
1142                        t.set(Value::Int(i + 1), v);
1143                    }
1144                    t.set(Value::Int(pos), val);
1145                }
1146                Ok(vec![])
1147            }
1148            _ => Err(ScriptError::new("table.insert: not a table")),
1149        }
1150    }));
1151
1152    // remove(t [, pos])
1153    tbl.rawset_str("remove", native("table.remove", |_vm, args| {
1154        let table = args.first().cloned().unwrap_or(Value::Nil);
1155        match &table {
1156            Value::Table(t) => {
1157                let len = t.length();
1158                let pos = args.get(1).and_then(|v| v.to_int()).unwrap_or(len);
1159                if len == 0 { return Ok(vec![Value::Nil]); }
1160                let removed = t.get(&Value::Int(pos));
1161                for i in pos..len {
1162                    let next = t.get(&Value::Int(i + 1));
1163                    t.set(Value::Int(i), next);
1164                }
1165                t.set(Value::Int(len), Value::Nil);
1166                Ok(vec![removed])
1167            }
1168            _ => Err(ScriptError::new("table.remove: not a table")),
1169        }
1170    }));
1171
1172    // concat(t [, sep [, i [, j]]])
1173    tbl.rawset_str("concat", native("table.concat", |_vm, args| {
1174        let table = args.first().cloned().unwrap_or(Value::Nil);
1175        let sep   = args.get(1).and_then(|v| v.to_str_repr()).unwrap_or_default();
1176        let i     = args.get(2).and_then(|v| v.to_int()).unwrap_or(1);
1177        let j_opt = args.get(3).and_then(|v| v.to_int());
1178        match &table {
1179            Value::Table(t) => {
1180                let j = j_opt.unwrap_or_else(|| t.length());
1181                let mut parts = Vec::new();
1182                for idx in i..=j {
1183                    let v = t.get(&Value::Int(idx));
1184                    parts.push(v.to_str_repr().unwrap_or_else(|| v.to_string()));
1185                }
1186                Ok(vec![Value::Str(Arc::new(parts.join(&sep)))])
1187            }
1188            _ => Err(ScriptError::new("table.concat: not a table")),
1189        }
1190    }));
1191
1192    // sort(t [, comp])
1193    tbl.rawset_str("sort", native("table.sort", |vm, args| {
1194        let table = args.first().cloned().unwrap_or(Value::Nil);
1195        let comp  = args.get(1).cloned();
1196        match &table {
1197            Value::Table(t) => {
1198                let mut v = t.array_values();
1199                // Sort with optional comparator
1200                let mut err: Option<ScriptError> = None;
1201                match comp {
1202                    Some(Value::Function(_)) | Some(Value::NativeFunction(_)) => {
1203                        let comp_val = comp.unwrap();
1204                        // Insertion sort to allow vm.call (can't use unstable sort with closure returning Result)
1205                        let n = v.len();
1206                        for i in 1..n {
1207                            let mut j = i;
1208                            while j > 0 {
1209                                let res = vm.call(comp_val.clone(), vec![v[j].clone(), v[j-1].clone()]);
1210                                match res {
1211                                    Ok(r) => {
1212                                        if r.first().map(|x| x.is_truthy()).unwrap_or(false) {
1213                                            v.swap(j, j - 1);
1214                                            j -= 1;
1215                                        } else {
1216                                            break;
1217                                        }
1218                                    }
1219                                    Err(e) => { err = Some(e); break; }
1220                                }
1221                            }
1222                            if err.is_some() { break; }
1223                        }
1224                    }
1225                    _ => {
1226                        v.sort_by(|a, b| {
1227                            match (a, b) {
1228                                (Value::Int(ai), Value::Int(bi))     => ai.cmp(bi),
1229                                (Value::Float(af), Value::Float(bf)) => af.partial_cmp(bf).unwrap_or(std::cmp::Ordering::Equal),
1230                                (Value::Str(sa), Value::Str(sb))     => sa.as_ref().cmp(sb.as_ref()),
1231                                _ => {
1232                                    let af = a.to_float().unwrap_or(0.0);
1233                                    let bf = b.to_float().unwrap_or(0.0);
1234                                    af.partial_cmp(&bf).unwrap_or(std::cmp::Ordering::Equal)
1235                                }
1236                            }
1237                        });
1238                    }
1239                }
1240                if let Some(e) = err { return Err(e); }
1241                for (i, val) in v.into_iter().enumerate() {
1242                    t.set(Value::Int(i as i64 + 1), val);
1243                }
1244                Ok(vec![])
1245            }
1246            _ => Err(ScriptError::new("table.sort: not a table")),
1247        }
1248    }));
1249
1250    // unpack(t [, i [, j]])
1251    tbl.rawset_str("unpack", native("table.unpack", |_vm, args| {
1252        let table = args.first().cloned().unwrap_or(Value::Nil);
1253        let i     = args.get(1).and_then(|v| v.to_int()).unwrap_or(1);
1254        let j_opt = args.get(2).and_then(|v| v.to_int());
1255        match table {
1256            Value::Table(t) => {
1257                let j = j_opt.unwrap_or_else(|| t.length());
1258                let mut result = Vec::new();
1259                for idx in i..=j { result.push(t.get(&Value::Int(idx))); }
1260                Ok(result)
1261            }
1262            _ => Err(ScriptError::new("table.unpack: not a table")),
1263        }
1264    }));
1265
1266    // pack(...)
1267    tbl.rawset_str("pack", native("table.pack", |_vm, args| {
1268        let t = Table::new();
1269        let n = args.len() as i64;
1270        for (i, v) in args.into_iter().enumerate() {
1271            t.set(Value::Int(i as i64 + 1), v);
1272        }
1273        t.rawset_str("n", Value::Int(n));
1274        Ok(vec![Value::Table(t)])
1275    }));
1276
1277    // move(a1, f, e, t [, a2])
1278    tbl.rawset_str("move", native("table.move", |_vm, args| {
1279        let a1  = args.first().cloned().unwrap_or(Value::Nil);
1280        let f   = args.get(1).and_then(|v| v.to_int()).unwrap_or(1);
1281        let e   = args.get(2).and_then(|v| v.to_int()).unwrap_or(0);
1282        let t_p = args.get(3).and_then(|v| v.to_int()).unwrap_or(1);
1283        let a2  = args.get(4).cloned().unwrap_or_else(|| a1.clone());
1284        if let (Value::Table(src), Value::Table(dst)) = (&a1, &a2) {
1285            let mut vals = Vec::new();
1286            for i in f..=e { vals.push(src.get(&Value::Int(i))); }
1287            for (offset, val) in vals.into_iter().enumerate() {
1288                dst.set(Value::Int(t_p + offset as i64), val);
1289            }
1290        }
1291        Ok(vec![a2])
1292    }));
1293
1294    // clone(t) — shallow
1295    tbl.rawset_str("clone", native("table.clone", |_vm, args| {
1296        let v = args.into_iter().next().unwrap_or(Value::Nil);
1297        match &v {
1298            Value::Table(t) => {
1299                let new_t = Table::new();
1300                let arr = t.array_values();
1301                for (i, val) in arr.into_iter().enumerate() {
1302                    new_t.set(Value::Int(i as i64 + 1), val);
1303                }
1304                let mut key = Value::Nil;
1305                loop {
1306                    match t.next(&key) {
1307                        Some((k, val)) => {
1308                            // Skip integer keys already handled
1309                            if !matches!(&k, Value::Int(_)) {
1310                                new_t.set(k.clone(), val);
1311                            }
1312                            key = k;
1313                        }
1314                        None => break,
1315                    }
1316                }
1317                Ok(vec![Value::Table(new_t)])
1318            }
1319            _ => Err(ScriptError::new("table.clone: not a table")),
1320        }
1321    }));
1322
1323    // deep_clone(t)
1324    tbl.rawset_str("deep_clone", native("table.deep_clone", |_vm, args| {
1325        let v = args.into_iter().next().unwrap_or(Value::Nil);
1326        Ok(vec![deep_clone_value(v, 0)])
1327    }));
1328
1329    // keys(t)
1330    tbl.rawset_str("keys", native("table.keys", |_vm, args| {
1331        let v = args.into_iter().next().unwrap_or(Value::Nil);
1332        match &v {
1333            Value::Table(t) => {
1334                let kt = Table::new();
1335                let mut key = Value::Nil;
1336                let mut idx = 1i64;
1337                loop {
1338                    match t.next(&key) {
1339                        Some((k, _)) => { kt.set(Value::Int(idx), k.clone()); idx += 1; key = k; }
1340                        None => break,
1341                    }
1342                }
1343                Ok(vec![Value::Table(kt)])
1344            }
1345            _ => Err(ScriptError::new("table.keys: not a table")),
1346        }
1347    }));
1348
1349    // values(t)
1350    tbl.rawset_str("values", native("table.values", |_vm, args| {
1351        let v = args.into_iter().next().unwrap_or(Value::Nil);
1352        match &v {
1353            Value::Table(t) => {
1354                let vt = Table::new();
1355                let mut key = Value::Nil;
1356                let mut idx = 1i64;
1357                loop {
1358                    match t.next(&key) {
1359                        Some((k, val)) => { vt.set(Value::Int(idx), val); idx += 1; key = k; }
1360                        None => break,
1361                    }
1362                }
1363                Ok(vec![Value::Table(vt)])
1364            }
1365            _ => Err(ScriptError::new("table.values: not a table")),
1366        }
1367    }));
1368
1369    // pairs(t) — same as global pairs but in table namespace
1370    tbl.rawset_str("pairs", native("table.pairs", |_vm, args| {
1371        let table = args.into_iter().next().unwrap_or(Value::Nil);
1372        let iter_fn = Arc::new(NativeFunc {
1373            name: "table.pairs_iter".to_string(),
1374            func: Box::new(|_vm, args| {
1375                let table = args.first().cloned().unwrap_or(Value::Nil);
1376                let key   = args.get(1).cloned().unwrap_or(Value::Nil);
1377                match &table {
1378                    Value::Table(t) => match t.next(&key) {
1379                        Some((k, v)) => Ok(vec![k, v]),
1380                        None         => Ok(vec![Value::Nil]),
1381                    },
1382                    _ => Ok(vec![Value::Nil]),
1383                }
1384            }),
1385        });
1386        Ok(vec![Value::NativeFunction(iter_fn), table, Value::Nil])
1387    }));
1388
1389    // ipairs(t) — integer iterator
1390    tbl.rawset_str("ipairs", native("table.ipairs", |_vm, args| {
1391        let table = args.into_iter().next().unwrap_or(Value::Nil);
1392        let iter_fn = Arc::new(NativeFunc {
1393            name: "table.ipairs_iter".to_string(),
1394            func: Box::new(|_vm, args| {
1395                let table = args.first().cloned().unwrap_or(Value::Nil);
1396                let idx   = args.get(1).and_then(|v| v.to_int()).unwrap_or(0) + 1;
1397                match &table {
1398                    Value::Table(t) => {
1399                        let v = t.get(&Value::Int(idx));
1400                        if matches!(v, Value::Nil) {
1401                            Ok(vec![Value::Nil])
1402                        } else {
1403                            Ok(vec![Value::Int(idx), v])
1404                        }
1405                    }
1406                    _ => Ok(vec![Value::Nil]),
1407                }
1408            }),
1409        });
1410        Ok(vec![Value::NativeFunction(iter_fn), table, Value::Int(0)])
1411    }));
1412
1413    // len(t)
1414    tbl.rawset_str("len", native("table.len", |_vm, args| {
1415        match args.into_iter().next().unwrap_or(Value::Nil) {
1416            Value::Table(t) => Ok(vec![Value::Int(t.length())]),
1417            _ => Err(ScriptError::new("table.len: not a table")),
1418        }
1419    }));
1420
1421    // contains(t, v)
1422    tbl.rawset_str("contains", native("table.contains", |_vm, args| {
1423        let table = args.first().cloned().unwrap_or(Value::Nil);
1424        let needle = args.get(1).cloned().unwrap_or(Value::Nil);
1425        match &table {
1426            Value::Table(t) => {
1427                let mut key = Value::Nil;
1428                loop {
1429                    match t.next(&key) {
1430                        Some((k, v)) => {
1431                            if v == needle { return Ok(vec![Value::Bool(true)]); }
1432                            key = k;
1433                        }
1434                        None => break,
1435                    }
1436                }
1437                Ok(vec![Value::Bool(false)])
1438            }
1439            _ => Err(ScriptError::new("table.contains: not a table")),
1440        }
1441    }));
1442
1443    // merge(t1, t2) — returns new table with t2 overriding t1
1444    tbl.rawset_str("merge", native("table.merge", |_vm, args| {
1445        let t1 = args.first().cloned().unwrap_or(Value::Nil);
1446        let t2 = args.get(1).cloned().unwrap_or(Value::Nil);
1447        let result = Table::new();
1448        for src in &[&t1, &t2] {
1449            if let Value::Table(t) = src {
1450                let mut key = Value::Nil;
1451                loop {
1452                    match t.next(&key) {
1453                        Some((k, v)) => { result.set(k.clone(), v); key = k; }
1454                        None => break,
1455                    }
1456                }
1457            }
1458        }
1459        Ok(vec![Value::Table(result)])
1460    }));
1461
1462    vm.set_global("table", Value::Table(tbl));
1463}
1464
1465fn deep_clone_value(v: Value, depth: usize) -> Value {
1466    if depth > 32 { return v; }
1467    match v {
1468        Value::Table(t) => {
1469            let new_t = Table::new();
1470            let mut key = Value::Nil;
1471            loop {
1472                match t.next(&key) {
1473                    Some((k, val)) => {
1474                        let new_k   = deep_clone_value(k.clone(), depth + 1);
1475                        let new_val = deep_clone_value(val, depth + 1);
1476                        new_t.set(new_k, new_val);
1477                        key = k;
1478                    }
1479                    None => break,
1480                }
1481            }
1482            Value::Table(new_t)
1483        }
1484        other => other,
1485    }
1486}
1487
1488// ── io.* ─────────────────────────────────────────────────────────────────────
1489
1490/// Sandboxed IO state shared through the VM output buffer.
1491fn register_io(vm: &mut Vm) {
1492    let io = Table::new();
1493
1494    // write(...) — appends to output buffer
1495    io.rawset_str("write", native("io.write", |vm, args| {
1496        let s: String = args.iter().map(|v| v.to_string()).collect::<Vec<_>>().join("");
1497        vm.output.push(s);
1498        Ok(vec![])
1499    }));
1500
1501    // read([fmt]) — reads from pre-loaded input (sandboxed: returns nil)
1502    io.rawset_str("read", native("io.read", |_vm, _args| {
1503        Ok(vec![Value::Nil])
1504    }));
1505
1506    // lines() — iterator over input lines (sandboxed: empty iterator)
1507    io.rawset_str("lines", native("io.lines", |_vm, _args| {
1508        let done = Arc::new(std::sync::atomic::AtomicBool::new(true));
1509        let iter_fn = Arc::new(NativeFunc {
1510            name: "io.lines_iter".to_string(),
1511            func: Box::new(move |_vm, _args| {
1512                Ok(vec![Value::Nil])
1513            }),
1514        });
1515        Ok(vec![Value::NativeFunction(iter_fn)])
1516    }));
1517
1518    // flush() — no-op
1519    io.rawset_str("flush", native("io.flush", |_vm, _args| {
1520        Ok(vec![Value::Bool(true)])
1521    }));
1522
1523    // open(path [, mode]) — returns fake file handle table
1524    io.rawset_str("open", native("io.open", |_vm, args| {
1525        let path = args.first().and_then(|v| v.to_str_repr()).unwrap_or_default();
1526        let mode = args.get(1).and_then(|v| v.to_str_repr()).unwrap_or_else(|| "r".to_string());
1527        let fh   = Table::new();
1528        fh.rawset_str("__path", Value::Str(Arc::new(path)));
1529        fh.rawset_str("__mode", Value::Str(Arc::new(mode)));
1530        fh.rawset_str("__buf",  Value::Str(Arc::new(String::new())));
1531        fh.rawset_str("read",   native("io.handle.read",  |_vm, _args| Ok(vec![Value::Nil])));
1532        fh.rawset_str("write",  native("io.handle.write", |_vm, _args| Ok(vec![Value::Nil])));
1533        fh.rawset_str("close",  native("io.handle.close", |_vm, _args| Ok(vec![Value::Bool(true)])));
1534        fh.rawset_str("lines",  native("io.handle.lines", |_vm, _args| {
1535            let iter_fn = Arc::new(NativeFunc {
1536                name: "io.handle.lines_iter".to_string(),
1537                func: Box::new(|_vm, _args| Ok(vec![Value::Nil])),
1538            });
1539            Ok(vec![Value::NativeFunction(iter_fn)])
1540        }));
1541        Ok(vec![Value::Table(fh)])
1542    }));
1543
1544    vm.set_global("io", Value::Table(io));
1545}
1546
1547// ── os.* ─────────────────────────────────────────────────────────────────────
1548
1549thread_local! {
1550    static START_INSTANT: std::time::Instant = std::time::Instant::now();
1551    static EXIT_REQUESTED: RefCell<bool> = RefCell::new(false);
1552}
1553
1554fn register_os(vm: &mut Vm) {
1555    let os = Table::new();
1556
1557    // time() — returns 0 (sandboxed)
1558    os.rawset_str("time", native("os.time", |_vm, _args| {
1559        Ok(vec![Value::Int(0)])
1560    }));
1561
1562    // clock() — monotonic seconds since first call
1563    os.rawset_str("clock", native("os.clock", |_vm, _args| {
1564        let elapsed = START_INSTANT.with(|s| s.elapsed().as_secs_f64());
1565        Ok(vec![Value::Float(elapsed)])
1566    }));
1567
1568    // date([fmt [, t]]) — returns table with fake fields
1569    os.rawset_str("date", native("os.date", |_vm, args| {
1570        let fmt = args.first().and_then(|v| v.to_str_repr()).unwrap_or_else(|| "%c".to_string());
1571        if fmt.starts_with('*') && fmt.contains('t') {
1572            // Return table
1573            let t = Table::new();
1574            t.rawset_str("year",  Value::Int(1970));
1575            t.rawset_str("month", Value::Int(1));
1576            t.rawset_str("day",   Value::Int(1));
1577            t.rawset_str("hour",  Value::Int(0));
1578            t.rawset_str("min",   Value::Int(0));
1579            t.rawset_str("sec",   Value::Int(0));
1580            t.rawset_str("wday",  Value::Int(5)); // Thursday
1581            t.rawset_str("yday",  Value::Int(1));
1582            t.rawset_str("isdst", Value::Bool(false));
1583            Ok(vec![Value::Table(t)])
1584        } else {
1585            Ok(vec![Value::Str(Arc::new("Thu Jan  1 00:00:00 1970".to_string()))])
1586        }
1587    }));
1588
1589    // exit([code]) — sets exit flag
1590    os.rawset_str("exit", native("os.exit", |_vm, args| {
1591        let _code = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1592        EXIT_REQUESTED.with(|e| *e.borrow_mut() = true);
1593        Err(ScriptError::new("os.exit called"))
1594    }));
1595
1596    // getenv(name) — returns nil (sandboxed)
1597    os.rawset_str("getenv", native("os.getenv", |_vm, _args| {
1598        Ok(vec![Value::Nil])
1599    }));
1600
1601    // difftime(t2, t1) — always 0 in sandbox
1602    os.rawset_str("difftime", native("os.difftime", |_vm, _args| {
1603        Ok(vec![Value::Int(0)])
1604    }));
1605
1606    vm.set_global("os", Value::Table(os));
1607}
1608
1609// ── bit.* ─────────────────────────────────────────────────────────────────────
1610
1611fn register_bit(vm: &mut Vm) {
1612    let bit = Table::new();
1613
1614    // band(a, b, ...)
1615    bit.rawset_str("band", native("bit.band", |_vm, args| {
1616        let mut result = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1617        for a in args.iter().skip(1) {
1618            result &= a.to_int().unwrap_or(0);
1619        }
1620        Ok(vec![Value::Int(result)])
1621    }));
1622
1623    // bor(a, b, ...)
1624    bit.rawset_str("bor", native("bit.bor", |_vm, args| {
1625        let mut result = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1626        for a in args.iter().skip(1) {
1627            result |= a.to_int().unwrap_or(0);
1628        }
1629        Ok(vec![Value::Int(result)])
1630    }));
1631
1632    // bxor(a, b, ...)
1633    bit.rawset_str("bxor", native("bit.bxor", |_vm, args| {
1634        let mut result = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1635        for a in args.iter().skip(1) {
1636            result ^= a.to_int().unwrap_or(0);
1637        }
1638        Ok(vec![Value::Int(result)])
1639    }));
1640
1641    // bnot(a)
1642    bit.rawset_str("bnot", native("bit.bnot", |_vm, args| {
1643        let a = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1644        Ok(vec![Value::Int(!a)])
1645    }));
1646
1647    // lshift(a, n)
1648    bit.rawset_str("lshift", native("bit.lshift", |_vm, args| {
1649        let a = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1650        let n = args.get(1).and_then(|v| v.to_int()).unwrap_or(0);
1651        let result = if n >= 64 || n <= -64 { 0 } else if n >= 0 {
1652            a.wrapping_shl(n as u32)
1653        } else {
1654            ((a as u64).wrapping_shr((-n) as u32)) as i64
1655        };
1656        Ok(vec![Value::Int(result)])
1657    }));
1658
1659    // rshift(a, n) — logical right shift
1660    bit.rawset_str("rshift", native("bit.rshift", |_vm, args| {
1661        let a = args.first().and_then(|v| v.to_int()).unwrap_or(0) as u64;
1662        let n = args.get(1).and_then(|v| v.to_int()).unwrap_or(0);
1663        let result = if n >= 64 || n <= -64 { 0u64 } else if n >= 0 {
1664            a.wrapping_shr(n as u32)
1665        } else {
1666            a.wrapping_shl((-n) as u32)
1667        };
1668        Ok(vec![Value::Int(result as i64)])
1669    }));
1670
1671    // arshift(a, n) — arithmetic right shift
1672    bit.rawset_str("arshift", native("bit.arshift", |_vm, args| {
1673        let a = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1674        let n = args.get(1).and_then(|v| v.to_int()).unwrap_or(0);
1675        let result = if n >= 64 { if a < 0 { -1 } else { 0 } }
1676        else if n <= 0 { a.wrapping_shl((-n) as u32) }
1677        else { a.wrapping_shr(n as u32) };
1678        Ok(vec![Value::Int(result)])
1679    }));
1680
1681    // tobit(a) — normalize to 32-bit signed
1682    bit.rawset_str("tobit", native("bit.tobit", |_vm, args| {
1683        let a = args.first().and_then(|v| v.to_int()).unwrap_or(0);
1684        Ok(vec![Value::Int((a as i32) as i64)])
1685    }));
1686
1687    // tohex(a [, n]) — hex string of 32-bit value
1688    bit.rawset_str("tohex", native("bit.tohex", |_vm, args| {
1689        let a = args.first().and_then(|v| v.to_int()).unwrap_or(0) as u32;
1690        let n = args.get(1).and_then(|v| v.to_int()).unwrap_or(8).abs() as usize;
1691        let s = format!("{:0>width$x}", a, width = n);
1692        let trimmed: String = s.chars().rev().take(n).collect::<String>().chars().rev().collect();
1693        Ok(vec![Value::Str(Arc::new(trimmed))])
1694    }));
1695
1696    // bswap(a) — byte-swap 32-bit
1697    bit.rawset_str("bswap", native("bit.bswap", |_vm, args| {
1698        let a = args.first().and_then(|v| v.to_int()).unwrap_or(0) as u32;
1699        Ok(vec![Value::Int(a.swap_bytes() as i64)])
1700    }));
1701
1702    vm.set_global("bit", Value::Table(bit));
1703}
1704
1705// ── Tests ─────────────────────────────────────────────────────────────────────
1706
1707#[cfg(test)]
1708mod tests {
1709    use super::*;
1710    use crate::scripting::{compiler::Compiler, parser::Parser};
1711
1712    fn run(src: &str) -> Vec<Value> {
1713        let script = Parser::from_source("test", src).expect("parse error");
1714        let chunk  = Compiler::compile_script(&script);
1715        let mut vm = Vm::new();
1716        register_all(&mut vm);
1717        let result = vm.execute(chunk).expect("runtime error");
1718        eprintln!("[DEBUG run] src={:?} => {:?}", src, result);
1719        result
1720    }
1721
1722    fn run_err(src: &str) -> String {
1723        let script = Parser::from_source("test", src).expect("parse error");
1724        let chunk  = Compiler::compile_script(&script);
1725        let mut vm = Vm::new();
1726        register_all(&mut vm);
1727        vm.execute(chunk).unwrap_err().message
1728    }
1729
1730    #[test]
1731    fn test_math_abs() {
1732        let r = run("return math.abs(-5)");
1733        assert!(matches!(&r[0], Value::Float(f) if (*f - 5.0).abs() < 1e-9));
1734    }
1735
1736    #[test]
1737    fn test_math_sqrt() {
1738        let r = run("return math.sqrt(9)");
1739        assert!(matches!(&r[0], Value::Float(f) if (*f - 3.0).abs() < 1e-9));
1740    }
1741
1742    #[test]
1743    fn test_math_clamp() {
1744        let r = run("return math.clamp(10, 0, 5)");
1745        assert!(matches!(&r[0], Value::Float(f) if (*f - 5.0).abs() < 1e-9));
1746    }
1747
1748    #[test]
1749    fn test_math_random_range() {
1750        let r = run("return math.random(1, 10)");
1751        if let Value::Int(n) = r[0] { assert!(n >= 1 && n <= 10); } else { panic!("expected int"); }
1752    }
1753
1754    #[test]
1755    fn test_string_format_d() {
1756        let r = run("return string.format(\"%05d\", 42)");
1757        assert!(matches!(&r[0], Value::Str(s) if s.as_ref() == "00042"));
1758    }
1759
1760    #[test]
1761    fn test_string_format_s() {
1762        let r = run("return string.format(\"%-10s|\", \"hi\")");
1763        assert!(matches!(&r[0], Value::Str(s) if s.as_ref() == "hi        |"));
1764    }
1765
1766    #[test]
1767    fn test_string_rep() {
1768        let r = run("return string.rep(\"ab\", 3, \"-\")");
1769        assert!(matches!(&r[0], Value::Str(s) if s.as_ref() == "ab-ab-ab"));
1770    }
1771
1772    #[test]
1773    fn test_string_split() {
1774        let r = run("local t = string.split(\"a,b,c\", \",\") return t[1], t[2], t[3]");
1775        assert!(matches!(&r[0], Value::Str(s) if s.as_ref() == "a"));
1776        assert!(matches!(&r[1], Value::Str(s) if s.as_ref() == "b"));
1777        assert!(matches!(&r[2], Value::Str(s) if s.as_ref() == "c"));
1778    }
1779
1780    #[test]
1781    fn test_table_sort_default() {
1782        let r = run("local t = {3,1,2} table.sort(t) return t[1], t[2], t[3]");
1783        assert_eq!(r[0], Value::Int(1));
1784        assert_eq!(r[1], Value::Int(2));
1785        assert_eq!(r[2], Value::Int(3));
1786    }
1787
1788    #[test]
1789    fn test_table_pack_unpack() {
1790        let r = run("local t = table.pack(10, 20, 30) return table.unpack(t, 1, t.n)");
1791        assert_eq!(r[0], Value::Int(10));
1792        assert_eq!(r[2], Value::Int(30));
1793    }
1794
1795    #[test]
1796    fn test_table_merge() {
1797        let r = run("local a = {x=1} local b = {y=2} local c = table.merge(a, b) return c.x, c.y");
1798        assert_eq!(r[0], Value::Int(1));
1799        assert_eq!(r[1], Value::Int(2));
1800    }
1801
1802    #[test]
1803    fn test_bit_band() {
1804        let r = run("return bit.band(0xFF, 0x0F)");
1805        assert_eq!(r[0], Value::Int(0x0F));
1806    }
1807
1808    #[test]
1809    fn test_bit_bxor() {
1810        let r = run("return bit.bxor(0xFF, 0x0F)");
1811        assert_eq!(r[0], Value::Int(0xF0));
1812    }
1813
1814    #[test]
1815    fn test_bit_lshift() {
1816        let r = run("return bit.lshift(1, 4)");
1817        assert_eq!(r[0], Value::Int(16));
1818    }
1819
1820    #[test]
1821    fn test_pcall_success() {
1822        let r = run("return pcall(function() return 42 end)");
1823        assert_eq!(r[0], Value::Bool(true));
1824        assert_eq!(r[1], Value::Int(42));
1825    }
1826
1827    #[test]
1828    fn test_pcall_error() {
1829        let r = run("return pcall(function() error(\"oops\") end)");
1830        assert_eq!(r[0], Value::Bool(false));
1831        assert!(matches!(&r[1], Value::Str(s) if s.as_ref() == "oops"));
1832    }
1833
1834    #[test]
1835    fn test_select_hash() {
1836        let r = run("return select(\"#\", 1, 2, 3)");
1837        assert_eq!(r[0], Value::Int(3));
1838    }
1839
1840    #[test]
1841    fn test_tonumber_base16() {
1842        let r = run("return tonumber(\"ff\", 16)");
1843        assert_eq!(r[0], Value::Int(255));
1844    }
1845
1846    #[test]
1847    fn test_string_byte_char() {
1848        let r = run("return string.char(65, 66, 67)");
1849        assert!(matches!(&r[0], Value::Str(s) if s.as_ref() == "ABC"));
1850    }
1851
1852    #[test]
1853    fn test_type_function() {
1854        let r = run("return type(42), type(3.14), type(\"hi\"), type(nil), type(true)");
1855        assert!(matches!(&r[0], Value::Str(s) if s.as_ref() == "integer"));
1856        assert!(matches!(&r[1], Value::Str(s) if s.as_ref() == "float"));
1857        assert!(matches!(&r[2], Value::Str(s) if s.as_ref() == "string"));
1858        assert!(matches!(&r[3], Value::Str(s) if s.as_ref() == "nil"));
1859        assert!(matches!(&r[4], Value::Str(s) if s.as_ref() == "boolean"));
1860    }
1861}