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