///|
/// Call frame for function execution
struct Frame {
  func_idx : Int32
  locals : Array[@types.Value]
  ip : Int32 // instruction pointer
  return_arity : Int32 // number of return values
} derive(Debug)

///|
pub fn Frame::Frame(
  func_idx : Int32,
  locals : Array[@types.Value],
  return_arity : Int32,
) -> Frame {
  { func_idx, locals, ip: 0, return_arity, }
}

///|
pub fn Frame::get_func_idx(self : Frame) -> Int32 {
  self.func_idx
}

///|
pub fn Frame::get_local(
  self : Frame,
  idx : Int32,
) -> @types.Value raise RuntimeError {
  if idx < 0 || idx >= self.locals.length() {
    raise TypeMismatch
  }
  self.locals[idx]
}

///|
pub fn Frame::set_local(
  self : Frame,
  idx : Int32,
  value : @types.Value,
) -> Unit raise RuntimeError {
  if idx < 0 || idx >= self.locals.length() {
    raise TypeMismatch
  }
  self.locals[idx] = value
}

///|
/// Snapshot local values (for GC root collection/debugging)
pub fn Frame::locals_values(self : Frame) -> Array[@types.Value] {
  let locals : Array[@types.Value] = []
  for local_value in self.locals {
    locals.push(local_value)
  }
  locals
}