///|
pub fn VM::new(
  inst? : Inst = Return,
  env? : Environment = Environment::base(),
) -> VM {
  { //
    acc: Value::default(),
    next: inst,
    env,
    rib: [],
    stack: None,
  }
}

///|
fn VM::save_frame(vm : VM, ret : Inst) -> Frame {
  { ret, env: vm.env, rib: vm.rib, stack: vm.stack }
}

///|
fn VM::load_frame(vm : VM, frame : Frame) -> Unit {
  vm.next = frame.ret
  vm.env = frame.env
  vm.rib = frame.rib
  vm.stack = frame.stack
}

///|
pub fn VM::run_one_step(vm : VM) -> Unit raise SchemeException {
  match vm.next {
    // Const
    Const(v, next) => {
      vm.acc = v
      vm.next = next
    }
    // Env
    Refer(name, next) => {
      vm.acc = vm.env.lookup(name)
      vm.next = next
    }
    Set(name, next) => {
      vm.env.set_var(name, vm.acc)
      vm.acc = Value::default()
      vm.next = next
    }
    Define(name, next) => {
      vm.env.define_var(name, vm.acc)
      vm.acc = Value::default()
      vm.next = next
    }
    // Control
    Branch(nt, nf) =>
      vm.next = match vm.acc {
        False => nf
        _ => nt
      }
    Return =>
      match vm.stack {
        None => () // Halt
        Some(saved_frame) => vm.load_frame(saved_frame)
      }
    Close(parm_names, body, name, next) => {
      vm.acc = Closure({ env: vm.env, parm_names, body, name })
      vm.next = next
    }
    // Apply
    Save(next, ret) => {
      vm.stack = Some(vm.save_frame(ret))
      vm.next = next
    }
    Args(args_length, next) => {
      vm.rib = FixedArray::make(args_length, Value::default())
      vm.next = next
    }
    Push(i, next) => {
      vm.rib[i] = vm.acc
      vm.next = next
    }
    Apply => vm.apply()
  }
}

///|
fn VM::apply(vm : VM) -> Unit raise SchemeException {
  match vm.acc {
    Closure(clo) => {
      guard vm.rib.length() == clo.parm_names.length() else {
        raise ArgumentCount
      }
      vm.env = clo.env.extend_(clo.parm_names, vm.rib, clo)
      vm.next = clo.body
    }
    Primitive(p) =>
      match p {
        Normal(body, ..) => {
          vm.acc = body(vm.rib)
          vm.next = Return
        }
        CallCC => {
          guard vm.rib is [f] else { raise ArgumentCount }
          vm.acc = f
          vm.rib[0] = Continuation(vm.stack)
          vm.apply()
        }
        Apply => {
          guard vm.rib is [f, v] else { raise ArgumentCount }
          guard v.list_tail_and_length() is (Nil, length) else {
            raise TypeError(1)
          }
          vm.acc = f
          vm.rib = v.unsafe_to_fixedarray(length)
          vm.apply()
        }
      }
    Continuation(stack) => {
      guard vm.rib is [v] else { raise ArgumentCount }
      vm.acc = v
      vm.stack = stack
      vm.next = Return
    }
    _ => raise CallNonProcedure
  }
}

///|
pub fn VM::run_to_halt(vm : VM) -> Unit raise SchemeException {
  while !((vm.next, vm.stack) is (Return, None)) {
    vm.run_one_step()
  }
}