Skip to main content

proof_engine/scripting/
vm.rs

1//! Stack-based bytecode virtual machine.
2//!
3//! Executes `Chunk` bytecode produced by `Compiler::compile_script`.
4
5use std::collections::HashMap;
6use std::fmt;
7use std::sync::Arc;
8
9use super::compiler::{Chunk, Constant, Instruction};
10
11// ── Value ────────────────────────────────────────────────────────────────────
12
13/// A runtime scripting value.
14#[derive(Clone, Debug)]
15pub enum Value {
16    Nil,
17    Bool(bool),
18    Int(i64),
19    Float(f64),
20    Str(Arc<String>),
21    Table(Table),
22    Function(Arc<Closure>),
23    NativeFunction(Arc<NativeFunc>),
24}
25
26impl PartialEq for Value {
27    fn eq(&self, other: &Self) -> bool {
28        match (self, other) {
29            (Value::Nil, Value::Nil)                 => true,
30            (Value::Bool(a), Value::Bool(b))         => a == b,
31            (Value::Int(a), Value::Int(b))           => a == b,
32            (Value::Float(a), Value::Float(b))       => a == b,
33            (Value::Str(a), Value::Str(b))           => a == b,
34            (Value::Int(a), Value::Float(b))         => (*a as f64) == *b,
35            (Value::Float(a), Value::Int(b))         => *a == (*b as f64),
36            (Value::Table(a), Value::Table(b))       => Arc::ptr_eq(&a.inner, &b.inner),
37            (Value::Function(a), Value::Function(b)) => Arc::ptr_eq(a, b),
38            _ => false,
39        }
40    }
41}
42
43impl fmt::Display for Value {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Value::Nil              => write!(f, "nil"),
47            Value::Bool(b)         => write!(f, "{}", b),
48            Value::Int(n)          => write!(f, "{}", n),
49            Value::Float(n)        => write!(f, "{}", n),
50            Value::Str(s)          => write!(f, "{}", s),
51            Value::Table(_)        => write!(f, "table"),
52            Value::Function(_)     => write!(f, "function"),
53            Value::NativeFunction(n) => write!(f, "function: {}", n.name),
54        }
55    }
56}
57
58impl Value {
59    pub fn is_truthy(&self) -> bool {
60        !matches!(self, Value::Nil | Value::Bool(false))
61    }
62
63    pub fn type_name(&self) -> &'static str {
64        match self {
65            Value::Nil              => "nil",
66            Value::Bool(_)         => "boolean",
67            Value::Int(_)          => "integer",
68            Value::Float(_)        => "float",
69            Value::Str(_)          => "string",
70            Value::Table(_)        => "table",
71            Value::Function(_)     => "function",
72            Value::NativeFunction(_) => "function",
73        }
74    }
75
76    pub fn to_float(&self) -> Option<f64> {
77        match self {
78            Value::Float(f) => Some(*f),
79            Value::Int(i)   => Some(*i as f64),
80            Value::Str(s)   => s.parse::<f64>().ok(),
81            _ => None,
82        }
83    }
84
85    pub fn to_int(&self) -> Option<i64> {
86        match self {
87            Value::Int(i)   => Some(*i),
88            Value::Float(f) => if f.fract() == 0.0 { Some(*f as i64) } else { None },
89            Value::Str(s)   => s.parse::<i64>().ok(),
90            _ => None,
91        }
92    }
93
94    pub fn to_str_repr(&self) -> Option<String> {
95        match self {
96            Value::Str(s)   => Some(s.as_ref().clone()),
97            Value::Int(i)   => Some(i.to_string()),
98            Value::Float(f) => Some(f.to_string()),
99            _ => None,
100        }
101    }
102}
103
104impl From<&Constant> for Value {
105    fn from(c: &Constant) -> Self {
106        match c {
107            Constant::Nil      => Value::Nil,
108            Constant::Bool(b)  => Value::Bool(*b),
109            Constant::Int(i)   => Value::Int(*i),
110            Constant::Float(f) => Value::Float(*f),
111            Constant::Str(s)   => Value::Str(Arc::new(s.clone())),
112        }
113    }
114}
115
116// ── Table ─────────────────────────────────────────────────────────────────────
117
118#[derive(Clone, Debug)]
119pub struct Table {
120    pub(crate) inner: Arc<std::cell::RefCell<TableData>>,
121}
122
123#[derive(Debug)]
124struct TableData {
125    hash:      HashMap<TableKey, Value>,
126    array:     Vec<Value>,
127    metatable: Option<Table>,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131enum TableKey {
132    Str(String),
133    Int(i64),
134    Bool(bool),
135}
136
137impl TableKey {
138    fn from_value(v: &Value) -> Option<Self> {
139        match v {
140            Value::Str(s)  => Some(TableKey::Str(s.as_ref().clone())),
141            Value::Int(i)  => Some(TableKey::Int(*i)),
142            Value::Bool(b) => Some(TableKey::Bool(*b)),
143            Value::Float(f) => {
144                let i = *f as i64;
145                if i as f64 == *f { Some(TableKey::Int(i)) } else { None }
146            }
147            _ => None,
148        }
149    }
150}
151
152impl Table {
153    pub fn new() -> Self {
154        Table { inner: Arc::new(std::cell::RefCell::new(TableData {
155            hash: HashMap::new(), array: Vec::new(), metatable: None,
156        }))}
157    }
158
159    pub fn get(&self, key: &Value) -> Value {
160        let d = self.inner.borrow();
161        if let Some(i) = int_key(key) {
162            if i >= 1 {
163                if let Some(v) = d.array.get((i - 1) as usize) {
164                    return v.clone();
165                }
166                // Integer keys beyond the array part (t[5] = x on a shorter
167                // table) are stored in the hash; look there too instead of
168                // returning nil.
169            }
170        }
171        TableKey::from_value(key)
172            .and_then(|k| d.hash.get(&k).cloned())
173            .unwrap_or(Value::Nil)
174    }
175
176    pub fn rawget_str(&self, key: &str) -> Value {
177        self.inner.borrow().hash
178            .get(&TableKey::Str(key.to_string()))
179            .cloned()
180            .unwrap_or(Value::Nil)
181    }
182
183    pub fn set(&self, key: Value, val: Value) {
184        let mut d = self.inner.borrow_mut();
185        if let Some(i) = int_key(&key) {
186            if i >= 1 {
187                let idx = (i - 1) as usize;
188                if idx <= d.array.len() {
189                    if matches!(val, Value::Nil) {
190                        if idx < d.array.len() { d.array[idx] = Value::Nil; }
191                    } else if idx == d.array.len() {
192                        d.array.push(val);
193                    } else {
194                        d.array[idx] = val;
195                    }
196                    return;
197                }
198            }
199        }
200        if let Some(k) = TableKey::from_value(&key) {
201            match val {
202                Value::Nil => { d.hash.remove(&k); }
203                v          => { d.hash.insert(k, v); }
204            }
205        }
206    }
207
208    pub fn rawset_str(&self, key: &str, val: Value) {
209        let mut d = self.inner.borrow_mut();
210        let k = TableKey::Str(key.to_string());
211        match val {
212            Value::Nil => { d.hash.remove(&k); }
213            v          => { d.hash.insert(k, v); }
214        }
215    }
216
217    pub fn length(&self) -> i64 {
218        self.inner.borrow().array.len() as i64
219    }
220
221    pub fn push(&self, v: Value) {
222        self.inner.borrow_mut().array.push(v);
223    }
224
225    pub fn array_values(&self) -> Vec<Value> {
226        self.inner.borrow().array.clone()
227    }
228
229    pub fn set_metatable(&self, mt: Option<Table>) {
230        self.inner.borrow_mut().metatable = mt;
231    }
232
233    pub fn get_metatable(&self) -> Option<Table> {
234        self.inner.borrow().metatable.clone()
235    }
236
237    pub fn next(&self, after: &Value) -> Option<(Value, Value)> {
238        let d = self.inner.borrow();
239        match after {
240            Value::Nil => {
241                if let Some(v) = d.array.first() {
242                    return Some((Value::Int(1), v.clone()));
243                }
244                return d.hash.iter().next().map(|(k, v)| (tk_to_val(k), v.clone()));
245            }
246            _ => {
247                if let Some(i) = int_key(after) {
248                    if i >= 1 {
249                        let next_i = i as usize;
250                        if next_i < d.array.len() {
251                            return Some((Value::Int(i + 1), d.array[next_i].clone()));
252                        }
253                        return d.hash.iter().next().map(|(k, v)| (tk_to_val(k), v.clone()));
254                    }
255                }
256                if let Some(k) = TableKey::from_value(after) {
257                    let mut found = false;
258                    for (hk, hv) in &d.hash {
259                        if found { return Some((tk_to_val(hk), hv.clone())); }
260                        if *hk == k { found = true; }
261                    }
262                }
263                None
264            }
265        }
266    }
267}
268
269fn int_key(v: &Value) -> Option<i64> {
270    match v {
271        Value::Int(i) => Some(*i),
272        Value::Float(f) if f.fract() == 0.0 => Some(*f as i64),
273        _ => None,
274    }
275}
276
277fn tk_to_val(k: &TableKey) -> Value {
278    match k {
279        TableKey::Str(s)  => Value::Str(Arc::new(s.clone())),
280        TableKey::Int(i)  => Value::Int(*i),
281        TableKey::Bool(b) => Value::Bool(*b),
282    }
283}
284
285// ── Closure / NativeFunc ──────────────────────────────────────────────────────
286
287#[derive(Debug)]
288pub struct Closure {
289    pub chunk:    Arc<Chunk>,
290    pub upvalues: Vec<UpvalueCell>,
291}
292
293#[derive(Debug, Clone)]
294pub struct UpvalueCell(Arc<std::cell::RefCell<Value>>);
295
296impl UpvalueCell {
297    pub fn new(v: Value) -> Self { UpvalueCell(Arc::new(std::cell::RefCell::new(v))) }
298    pub fn get(&self) -> Value { self.0.borrow().clone() }
299    pub fn set(&self, v: Value) { *self.0.borrow_mut() = v; }
300}
301
302pub struct NativeFunc {
303    pub name: String,
304    pub func: Box<dyn Fn(&mut Vm, Vec<Value>) -> Result<Vec<Value>, ScriptError> + Send + Sync>,
305}
306
307impl fmt::Debug for NativeFunc {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        write!(f, "NativeFunc({})", self.name)
310    }
311}
312
313// ── ScriptError ───────────────────────────────────────────────────────────────
314
315#[derive(Debug, Clone)]
316pub struct ScriptError {
317    pub message: String,
318    pub line:    Option<u32>,
319}
320
321impl ScriptError {
322    pub fn new(msg: impl Into<String>) -> Self {
323        ScriptError { message: msg.into(), line: None }
324    }
325
326    pub fn at(msg: impl Into<String>, line: u32) -> Self {
327        ScriptError { message: msg.into(), line: Some(line) }
328    }
329
330    fn runtime(msg: impl Into<String>) -> Self { ScriptError::new(msg) }
331}
332
333impl fmt::Display for ScriptError {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        if let Some(l) = self.line { write!(f, "[line {}] {}", l, self.message) }
336        else { write!(f, "{}", self.message) }
337    }
338}
339
340impl std::error::Error for ScriptError {}
341
342// ── CallFrame ─────────────────────────────────────────────────────────────────
343
344struct CallFrame {
345    chunk:    Arc<Chunk>,
346    upvalues: Vec<UpvalueCell>,
347    ip:       usize,
348    base:     usize,
349    /// Stack height recorded by `MarkReturn`.
350    ret_mark: usize,
351}
352
353// ── Vm ────────────────────────────────────────────────────────────────────────
354
355const MAX_DEPTH: usize = 200;
356
357pub struct Vm {
358    stack:   Vec<Value>,
359    frames:  Vec<CallFrame>,
360    globals: HashMap<String, Value>,
361    depth:   usize,
362    pub output: Vec<String>,
363}
364
365impl Vm {
366    /// Push call results, padded with nil or truncated to `nret` (0 = all).
367    fn push_results(stack: &mut Vec<Value>, results: Vec<Value>, nret: usize) {
368        if nret == 0 {
369            stack.extend(results);
370            return;
371        }
372        let mut it = results.into_iter();
373        for _ in 0..nret {
374            stack.push(it.next().unwrap_or(Value::Nil));
375        }
376    }
377
378    pub fn new() -> Self {
379        Vm {
380            stack:   Vec::with_capacity(64),
381            frames:  Vec::with_capacity(32),
382            globals: HashMap::new(),
383            depth:   0,
384            output:  Vec::new(),
385        }
386    }
387
388    pub fn set_global(&mut self, name: impl Into<String>, v: Value) {
389        self.globals.insert(name.into(), v);
390    }
391
392    pub fn get_global(&self, name: &str) -> Value {
393        self.globals.get(name).cloned().unwrap_or(Value::Nil)
394    }
395
396    pub fn register_native(
397        &mut self,
398        name: impl Into<String>,
399        f: impl Fn(&mut Vm, Vec<Value>) -> Result<Vec<Value>, ScriptError> + Send + Sync + 'static,
400    ) {
401        let name = name.into();
402        let nf = Arc::new(NativeFunc { name: name.clone(), func: Box::new(f) });
403        self.globals.insert(name, Value::NativeFunction(nf));
404    }
405
406    /// Execute a compiled `Chunk` as the top-level script.
407    pub fn execute(&mut self, chunk: Arc<Chunk>) -> Result<Vec<Value>, ScriptError> {
408        self.run_chunk(chunk, vec![], vec![])
409    }
410
411    /// Call any callable Value.
412    pub fn call(&mut self, callee: Value, args: Vec<Value>) -> Result<Vec<Value>, ScriptError> {
413        match callee {
414            Value::Function(c) => {
415                let chunk    = Arc::clone(&c.chunk);
416                let upvalues = c.upvalues.clone();
417                self.run_chunk(chunk, upvalues, args)
418            }
419            Value::NativeFunction(nf) => {
420                let func = Arc::clone(&nf);
421                (func.func)(self, args)
422            }
423            other => Err(ScriptError::runtime(format!(
424                "attempt to call a {} value", other.type_name()
425            ))),
426        }
427    }
428
429    fn run_chunk(
430        &mut self,
431        chunk:    Arc<Chunk>,
432        upvalues: Vec<UpvalueCell>,
433        args:     Vec<Value>,
434    ) -> Result<Vec<Value>, ScriptError> {
435        self.depth += 1;
436        if self.depth > MAX_DEPTH {
437            self.depth -= 1;
438            return Err(ScriptError::runtime("stack overflow"));
439        }
440        let base = self.stack.len();
441        for a in args { self.stack.push(a); }
442        let param_count = chunk.param_count as usize;
443        while self.stack.len() < base + param_count {
444            self.stack.push(Value::Nil);
445        }
446        let frame_count = self.frames.len();
447        self.frames.push(CallFrame { chunk, upvalues, ip: 0, base, ret_mark: base });
448        let result = self.run();
449        self.depth -= 1;
450        if result.is_err() {
451            // Unwind what the failed call left behind. Without this, a
452            // runtime error caught by pcall left the dead frame on the frame
453            // stack, so the caller carried on in the wrong frame (pcall's
454            // error message was lost).
455            self.frames.truncate(frame_count);
456            self.stack.truncate(base);
457        }
458        result
459    }
460
461    fn run(&mut self) -> Result<Vec<Value>, ScriptError> {
462        loop {
463            let fi = self.frames.len() - 1;
464            let ip = self.frames[fi].ip;
465            let code = self.frames[fi].chunk.instructions.clone();
466
467            if ip >= code.len() {
468                let base = self.frames[fi].base;
469                self.stack.truncate(base);
470                self.frames.pop();
471                return Ok(vec![]);
472            }
473
474            let instr = code[ip].clone();
475            self.frames[fi].ip += 1;
476
477            match instr {
478                Instruction::LoadNil => self.push(Value::Nil),
479
480                Instruction::LoadBool(b) => self.push(Value::Bool(b)),
481
482                Instruction::LoadInt(n) => self.push(Value::Int(n)),
483
484                Instruction::LoadFloat(n) => self.push(Value::Float(n)),
485
486                Instruction::LoadStr(s) => self.push(Value::Str(Arc::new(s))),
487
488                Instruction::LoadConst(idx) => {
489                    let fi = self.frames.len() - 1;
490                    let v = self.frames[fi].chunk.constants[idx].clone();
491                    self.push(v);
492                }
493
494                Instruction::Pop  => { self.stack.pop(); }
495                Instruction::Dup  => {
496                    let v = self.stack.last().cloned().unwrap_or(Value::Nil);
497                    self.push(v);
498                }
499                Instruction::Swap => {
500                    let len = self.stack.len();
501                    if len >= 2 { self.stack.swap(len - 1, len - 2); }
502                }
503
504                Instruction::GetLocal(slot) => {
505                    let fi   = self.frames.len() - 1;
506                    let base = self.frames[fi].base;
507                    let v = self.stack.get(base + slot).cloned().unwrap_or(Value::Nil);
508                    self.push(v);
509                }
510                Instruction::SetLocal(slot) => {
511                    let fi   = self.frames.len() - 1;
512                    let base = self.frames[fi].base;
513                    let v = self.pop();
514                    let idx = base + slot;
515                    while self.stack.len() <= idx { self.stack.push(Value::Nil); }
516                    self.stack[idx] = v;
517                }
518
519                Instruction::GetUpvalue(idx) => {
520                    let fi = self.frames.len() - 1;
521                    let v = self.frames[fi].upvalues.get(idx)
522                        .map(|uv| uv.get())
523                        .unwrap_or(Value::Nil);
524                    self.push(v);
525                }
526                Instruction::SetUpvalue(idx) => {
527                    let v = self.pop();
528                    let fi = self.frames.len() - 1;
529                    if let Some(uv) = self.frames[fi].upvalues.get(idx) {
530                        uv.set(v);
531                    }
532                }
533
534                Instruction::GetGlobal(name) => {
535                    let v = self.globals.get(&name).cloned().unwrap_or(Value::Nil);
536                    self.push(v);
537                }
538                Instruction::SetGlobal(name) => {
539                    let v = self.pop();
540                    self.globals.insert(name, v);
541                }
542
543                Instruction::NewTable => self.push(Value::Table(Table::new())),
544
545                Instruction::SetField(name) => {
546                    let val   = self.pop();
547                    let table = self.peek();
548                    match table {
549                        Value::Table(t) => t.rawset_str(&name, val),
550                        _ => return Err(ScriptError::runtime("SetField on non-table")),
551                    }
552                }
553                Instruction::GetField(name) => {
554                    let table = self.pop();
555                    let v = match &table {
556                        Value::Table(t) => t.rawget_str(&name),
557                        Value::Str(_) => {
558                            self.globals.get("string")
559                                .and_then(|s| if let Value::Table(t) = s {
560                                    Some(t.rawget_str(&name))
561                                } else { None })
562                                .unwrap_or(Value::Nil)
563                        }
564                        other => return Err(ScriptError::runtime(format!(
565                            "attempt to index a {} value", other.type_name()
566                        ))),
567                    };
568                    self.push(v);
569                }
570                Instruction::SetIndex => {
571                    let val   = self.pop();
572                    let key   = self.pop();
573                    let table = self.pop();
574                    match table {
575                        Value::Table(t) => t.set(key, val),
576                        other => return Err(ScriptError::runtime(format!(
577                            "attempt to index a {} value", other.type_name()
578                        ))),
579                    }
580                }
581                Instruction::GetIndex => {
582                    let key   = self.pop();
583                    let table = self.pop();
584                    let v = match &table {
585                        Value::Table(t) => t.get(&key),
586                        other => return Err(ScriptError::runtime(format!(
587                            "attempt to index a {} value", other.type_name()
588                        ))),
589                    };
590                    self.push(v);
591                }
592                Instruction::TableAppend => {
593                    let val   = self.pop();
594                    let table = self.peek();
595                    if let Value::Table(t) = table { t.push(val); }
596                }
597
598                Instruction::Len => {
599                    let a = self.pop();
600                    let n = match a {
601                        Value::Table(t) => t.length(),
602                        Value::Str(s)   => s.len() as i64,
603                        other => return Err(ScriptError::runtime(format!(
604                            "attempt to get length of {} value", other.type_name()
605                        ))),
606                    };
607                    self.push(Value::Int(n));
608                }
609                Instruction::Neg => {
610                    let a = self.pop();
611                    let r = match a {
612                        Value::Int(i)   => Value::Int(-i),
613                        Value::Float(f) => Value::Float(-f),
614                        other => return Err(ScriptError::runtime(format!(
615                            "unary - on {}", other.type_name()
616                        ))),
617                    };
618                    self.push(r);
619                }
620                Instruction::Not    => { let a = self.pop(); self.push(Value::Bool(!a.is_truthy())); }
621                Instruction::BitNot => {
622                    let a = self.pop();
623                    let i = a.to_int().ok_or_else(|| ScriptError::runtime(
624                        format!("bitwise not on {}", a.type_name())))?;
625                    self.push(Value::Int(!i));
626                }
627
628                Instruction::Add => self.arith2(|a, b| num_arith(a, b, i64::wrapping_add, |x, y| x + y))?,
629                Instruction::Sub => self.arith2(|a, b| num_arith(a, b, i64::wrapping_sub, |x, y| x - y))?,
630                Instruction::Mul => self.arith2(|a, b| num_arith(a, b, i64::wrapping_mul, |x, y| x * y))?,
631                Instruction::Div => {
632                    let b = self.pop(); let a = self.pop();
633                    let af = a.to_float().ok_or_else(|| ScriptError::runtime(format!("arith on {}", a.type_name())))?;
634                    let bf = b.to_float().ok_or_else(|| ScriptError::runtime(format!("arith on {}", b.type_name())))?;
635                    self.push(Value::Float(af / bf));
636                }
637                Instruction::IDiv => {
638                    let b = self.pop(); let a = self.pop();
639                    let ai = a.to_int().ok_or_else(|| ScriptError::runtime("floor div requires integers"))?;
640                    let bi = b.to_int().ok_or_else(|| ScriptError::runtime("floor div requires integers"))?;
641                    if bi == 0 { return Err(ScriptError::runtime("integer divide by zero")); }
642                    self.push(Value::Int(ai.div_euclid(bi)));
643                }
644                Instruction::Mod => {
645                    let b = self.pop(); let a = self.pop();
646                    let r = match (&a, &b) {
647                        (Value::Int(ai), Value::Int(bi)) => {
648                            if *bi == 0 { return Err(ScriptError::runtime("modulo by zero")); }
649                            Value::Int(ai.rem_euclid(*bi))
650                        }
651                        _ => {
652                            let af = a.to_float().ok_or_else(|| ScriptError::runtime(format!("arith on {}", a.type_name())))?;
653                            let bf = b.to_float().ok_or_else(|| ScriptError::runtime(format!("arith on {}", b.type_name())))?;
654                            Value::Float(af % bf)
655                        }
656                    };
657                    self.push(r);
658                }
659                Instruction::Pow => {
660                    let b = self.pop(); let a = self.pop();
661                    let af = a.to_float().ok_or_else(|| ScriptError::runtime(format!("arith on {}", a.type_name())))?;
662                    let bf = b.to_float().ok_or_else(|| ScriptError::runtime(format!("arith on {}", b.type_name())))?;
663                    self.push(Value::Float(af.powf(bf)));
664                }
665                Instruction::Concat => {
666                    let b = self.pop(); let a = self.pop();
667                    let sa = a.to_str_repr().ok_or_else(|| ScriptError::runtime(format!("concat on {}", a.type_name())))?;
668                    let sb = b.to_str_repr().ok_or_else(|| ScriptError::runtime(format!("concat on {}", b.type_name())))?;
669                    self.push(Value::Str(Arc::new(sa + sb.as_str())));
670                }
671
672                Instruction::BitAnd => self.bitwise(|a, b| a & b)?,
673                Instruction::BitOr  => self.bitwise(|a, b| a | b)?,
674                Instruction::BitXor => self.bitwise(|a, b| a ^ b)?,
675                Instruction::Shl    => self.bitwise(|a, b| a.wrapping_shl(b as u32))?,
676                Instruction::Shr    => self.bitwise(|a, b| a.wrapping_shr(b as u32))?,
677
678                Instruction::Eq    => { let b = self.pop(); let a = self.pop(); self.push(Value::Bool(a == b)); }
679                Instruction::NotEq => { let b = self.pop(); let a = self.pop(); self.push(Value::Bool(a != b)); }
680                Instruction::Lt    => self.cmp(|a, b| a < b)?,
681                Instruction::LtEq  => self.cmp(|a, b| a <= b)?,
682                Instruction::Gt    => self.cmp(|a, b| a > b)?,
683                Instruction::GtEq  => self.cmp(|a, b| a >= b)?,
684
685                Instruction::Jump(off) => {
686                    let fi = self.frames.len() - 1;
687                    self.frames[fi].ip = (self.frames[fi].ip as isize + off) as usize;
688                }
689                Instruction::JumpIf(off) => {
690                    if self.stack.last().map(|v| v.is_truthy()).unwrap_or(false) {
691                        let fi = self.frames.len() - 1;
692                        self.frames[fi].ip = (self.frames[fi].ip as isize + off) as usize;
693                    }
694                }
695                Instruction::JumpIfNot(off) => {
696                    if !self.stack.last().map(|v| v.is_truthy()).unwrap_or(false) {
697                        let fi = self.frames.len() - 1;
698                        self.frames[fi].ip = (self.frames[fi].ip as isize + off) as usize;
699                    }
700                }
701                Instruction::JumpIfNotPop(off) => {
702                    let top = self.pop();
703                    if !top.is_truthy() {
704                        let fi = self.frames.len() - 1;
705                        self.frames[fi].ip = (self.frames[fi].ip as isize + off) as usize;
706                    } else {
707                        self.push(top);
708                    }
709                }
710                Instruction::JumpIfPop(off) => {
711                    let top = self.pop();
712                    if top.is_truthy() {
713                        let fi = self.frames.len() - 1;
714                        self.frames[fi].ip = (self.frames[fi].ip as isize + off) as usize;
715                    } else {
716                        self.push(top);
717                    }
718                }
719                Instruction::JumpAbs(abs_ip) => {
720                    let fi = self.frames.len() - 1;
721                    self.frames[fi].ip = abs_ip;
722                }
723
724                Instruction::Call(nargs, nret) => {
725                    let top  = self.stack.len();
726                    let base = top.saturating_sub(nargs + 1);
727                    let args: Vec<Value> = self.stack.drain(base + 1..).collect();
728                    let callee = self.stack.pop().unwrap_or(Value::Nil);
729                    let results = self.call(callee, args)?;
730                    // Push exactly `nret` values (nret 0: all of them). The
731                    // compiler counts on one value per call expression; a
732                    // function returning none (table.sort) used to push
733                    // nothing, so the following Pop removed a local.
734                    Self::push_results(&mut self.stack, results, nret);
735                }
736
737                Instruction::CallMethod(method_name, nargs, nret) => {
738                    let top  = self.stack.len();
739                    let base = top.saturating_sub(nargs + 1);
740                    let extra: Vec<Value> = self.stack.drain(base + 1..).collect();
741                    let obj = self.stack.pop().unwrap_or(Value::Nil);
742                    let method = match &obj {
743                        Value::Table(t) => t.rawget_str(&method_name),
744                        other => return Err(ScriptError::runtime(format!(
745                            "method call on {} value", other.type_name()
746                        ))),
747                    };
748                    let mut args = vec![obj];
749                    args.extend(extra);
750                    let results = self.call(method, args)?;
751                    Self::push_results(&mut self.stack, results, nret);
752                }
753
754                Instruction::MarkReturn => {
755                    let fi = self.frames.len() - 1;
756                    self.frames[fi].ret_mark = self.stack.len();
757                }
758
759                Instruction::ReturnFromMark => {
760                    let fi   = self.frames.len() - 1;
761                    let mark = self.frames[fi].ret_mark.min(self.stack.len());
762                    let returns: Vec<Value> = self.stack.drain(mark..).collect();
763                    let base = self.frames[fi].base;
764                    self.stack.truncate(base);
765                    self.frames.pop();
766                    return Ok(returns);
767                }
768
769                Instruction::Return(nret) => {
770                    let top = self.stack.len();
771                    let ret_start = if nret == 0 {
772                        self.frames[self.frames.len() - 1].base
773                    } else {
774                        top.saturating_sub(nret)
775                    };
776                    let returns: Vec<Value> = self.stack.drain(ret_start..).collect();
777                    let fi   = self.frames.len() - 1;
778                    let base = self.frames[fi].base;
779                    self.stack.truncate(base);
780                    self.frames.pop();
781                    return Ok(returns);
782                }
783
784                Instruction::MakeFunction(idx) => {
785                    let fi  = self.frames.len() - 1;
786                    let sub = Arc::clone(&self.frames[fi].chunk.sub_chunks[idx]);
787                    let closure = Arc::new(Closure { chunk: sub, upvalues: Vec::new() });
788                    self.push(Value::Function(closure));
789                }
790
791                Instruction::MakeClosure(idx, captures) => {
792                    let fi  = self.frames.len() - 1;
793                    let sub = Arc::clone(&self.frames[fi].chunk.sub_chunks[idx]);
794                    let mut upvalues = Vec::new();
795                    for (is_local, slot) in captures {
796                        if is_local {
797                            let base = self.frames[fi].base;
798                            let v = self.stack.get(base + slot).cloned().unwrap_or(Value::Nil);
799                            upvalues.push(UpvalueCell::new(v));
800                        } else {
801                            let v = self.frames[fi].upvalues.get(slot)
802                                .map(|uv| uv.clone())
803                                .unwrap_or_else(|| UpvalueCell::new(Value::Nil));
804                            upvalues.push(v);
805                        }
806                    }
807                    let closure = Arc::new(Closure { chunk: sub, upvalues });
808                    self.push(Value::Function(closure));
809                }
810
811                Instruction::CloseUpvalue(_slot) => {
812                    // Upvalues are captured by value on closure creation; nothing to do here.
813                }
814
815                Instruction::ForPrep(_nvars) => {
816                    // Generic for: iterator function, state, control are on stack.
817                    // Body sets locals from results; handled by subsequent ForStep.
818                }
819
820                Instruction::ForStep(local_idx, jump_off) => {
821                    // Numeric for: locals at [local_idx]=current, [local_idx+1]=limit, [local_idx+2]=step
822                    let fi   = self.frames.len() - 1;
823                    let base = self.frames[fi].base;
824                    let cur   = self.stack.get(base + local_idx).cloned().unwrap_or(Value::Nil);
825                    let limit = self.stack.get(base + local_idx + 1).cloned().unwrap_or(Value::Nil);
826                    let step  = self.stack.get(base + local_idx + 2).cloned().unwrap_or(Value::Nil);
827                    let cv = cur.to_float().unwrap_or(0.0);
828                    let lv = limit.to_float().unwrap_or(0.0);
829                    let sv = step.to_float().unwrap_or(1.0);
830                    let should_continue = if sv > 0.0 { cv <= lv } else { cv >= lv };
831                    if !should_continue {
832                        let fi = self.frames.len() - 1;
833                        self.frames[fi].ip = (self.frames[fi].ip as isize + jump_off) as usize;
834                    } else {
835                        let next = match (&cur, &step) {
836                            (Value::Int(c), Value::Int(s)) => Value::Int(c.wrapping_add(*s)),
837                            _ => Value::Float(cv + sv),
838                        };
839                        let fi   = self.frames.len() - 1;
840                        let base = self.frames[fi].base;
841                        let idx  = base + local_idx;
842                        while self.stack.len() <= idx { self.stack.push(Value::Nil); }
843                        self.stack[idx] = next;
844                    }
845                }
846
847                Instruction::Nop => {}
848            }
849        }
850    }
851
852    // ── Stack helpers ─────────────────────────────────────────────────────
853
854    #[inline] fn push(&mut self, v: Value) { self.stack.push(v); }
855    #[inline] fn pop(&mut self) -> Value { self.stack.pop().unwrap_or(Value::Nil) }
856    #[inline] fn peek(&self) -> Value { self.stack.last().cloned().unwrap_or(Value::Nil) }
857
858    fn arith2<F>(&mut self, f: F) -> Result<(), ScriptError>
859    where F: Fn(Value, Value) -> Result<Value, ScriptError>
860    {
861        let b = self.pop(); let a = self.pop();
862        self.push(f(a, b)?);
863        Ok(())
864    }
865
866    fn cmp<F>(&mut self, op: F) -> Result<(), ScriptError>
867    where F: Fn(f64, f64) -> bool
868    {
869        let b = self.pop(); let a = self.pop();
870        let av = a.to_float().ok_or_else(|| ScriptError::runtime(format!("compare on {}", a.type_name())))?;
871        let bv = b.to_float().ok_or_else(|| ScriptError::runtime(format!("compare on {}", b.type_name())))?;
872        self.push(Value::Bool(op(av, bv)));
873        Ok(())
874    }
875
876    fn bitwise<F>(&mut self, op: F) -> Result<(), ScriptError>
877    where F: Fn(i64, i64) -> i64
878    {
879        let b = self.pop(); let a = self.pop();
880        let ai = a.to_int().ok_or_else(|| ScriptError::runtime(format!("bitwise on {}", a.type_name())))?;
881        let bi = b.to_int().ok_or_else(|| ScriptError::runtime(format!("bitwise on {}", b.type_name())))?;
882        self.push(Value::Int(op(ai, bi)));
883        Ok(())
884    }
885}
886
887fn num_arith<FI, FF>(a: Value, b: Value, fi: FI, ff: FF) -> Result<Value, ScriptError>
888where
889    FI: Fn(i64, i64) -> i64,
890    FF: Fn(f64, f64) -> f64,
891{
892    match (&a, &b) {
893        (Value::Int(ai), Value::Int(bi))     => Ok(Value::Int(fi(*ai, *bi))),
894        (Value::Float(af), Value::Float(bf)) => Ok(Value::Float(ff(*af, *bf))),
895        (Value::Int(ai), Value::Float(bf))   => Ok(Value::Float(ff(*ai as f64, *bf))),
896        (Value::Float(af), Value::Int(bi))   => Ok(Value::Float(ff(*af, *bi as f64))),
897        _ => Err(ScriptError::runtime(format!(
898            "attempt to perform arithmetic on {} and {} values",
899            a.type_name(), b.type_name()
900        ))),
901    }
902}
903
904// ── Tests ─────────────────────────────────────────────────────────────────────
905
906#[cfg(test)]
907mod tests {
908    use super::*;
909    use crate::scripting::compiler::Compiler;
910    use crate::scripting::parser::Parser;
911
912    fn run(src: &str) -> Result<Vec<Value>, ScriptError> {
913        let script = Parser::from_source("test", src)
914            .map_err(|e| ScriptError::new(e.to_string()))?;
915        let chunk = Compiler::compile_script(&script);
916        let mut vm = Vm::new();
917        vm.execute(chunk)
918    }
919
920    #[test]
921    fn test_return_int() {
922        let r = run("return 42").unwrap();
923        assert_eq!(r[0], Value::Int(42));
924    }
925
926    #[test]
927    fn test_arithmetic() {
928        let r = run("return 2 + 3 * 4").unwrap();
929        assert_eq!(r[0], Value::Int(14));
930    }
931
932    #[test]
933    fn test_string_concat() {
934        let r = run("return \"hello\" .. \" world\"").unwrap();
935        assert!(matches!(&r[0], Value::Str(s) if s.as_ref() == "hello world"));
936    }
937
938    #[test]
939    fn test_local_variable() {
940        let r = run("local x = 10 return x").unwrap();
941        assert_eq!(r[0], Value::Int(10));
942    }
943
944    #[test]
945    fn test_if_else() {
946        let r = run("if true then return 1 else return 2 end").unwrap();
947        assert_eq!(r[0], Value::Int(1));
948    }
949
950    #[test]
951    fn test_function_call() {
952        let r = run("local function add(a, b) return a + b end return add(3, 4)").unwrap();
953        assert_eq!(r[0], Value::Int(7));
954    }
955}