use std::cell::Cell;
use std::cell::RefCell;
use std::rc::Rc;
use crate::Error;
use crate::rite::insn::{Fetched, OpCode};
use crate::yamrb::helpers::mrb_call_inspect;
use super::prelude::hash::mrb_hash_delete;
use super::prelude::object::mrb_object_is_equal;
use super::value::RHashMap;
use super::{helpers::mrb_funcall, value::*, vm::*};
const ENTER_N1_MASK: u32 = 0b1 << 23;
const ENTER_M1_MASK: u32 = 0b11111 << 18;
const ENTER_O_MASK: u32 = 0b11111 << 13;
const ENTER_R_MASK: u32 = 0b1 << 12;
const ENTER_M2_MASK: u32 = 0b11111 << 7;
const ENTER_K_MASK: u32 = 0b11111 << 2;
const ENTER_D_MASK: u32 = 0b1 << 1;
const ENTER_B_MASK: u32 = 0b1 << 0;
#[inline(always)]
pub(crate) fn consume_expr(
vm: &mut VM,
code: OpCode,
operand: &Fetched,
pos: usize,
len: usize,
) -> Result<(), Error> {
use crate::rite::insn::OpCode::*;
match code {
NOP => {
op_nop(vm, operand)?;
}
MOVE => {
op_move(vm, operand)?;
}
LOADL => {
op_loadl(vm, operand)?;
}
LOADI => {
op_loadi(vm, operand)?;
}
LOADINEG => {
op_loadineg(vm, operand)?;
}
LOADI__1 => {
op_loadi_n(vm, -1, operand)?;
}
LOADI_0 => {
op_loadi_n(vm, 0, operand)?;
}
LOADI_1 => {
op_loadi_n(vm, 1, operand)?;
}
LOADI_2 => {
op_loadi_n(vm, 2, operand)?;
}
LOADI_3 => {
op_loadi_n(vm, 3, operand)?;
}
LOADI_4 => {
op_loadi_n(vm, 4, operand)?;
}
LOADI_5 => {
op_loadi_n(vm, 5, operand)?;
}
LOADI_6 => {
op_loadi_n(vm, 6, operand)?;
}
LOADI_7 => {
op_loadi_n(vm, 7, operand)?;
}
LOADI16 => {
op_loadi16(vm, operand)?;
}
LOADI32 => {
op_loadi32(vm, operand)?;
}
LOADSYM => {
op_loadsym(vm, operand)?;
}
LOADNIL => {
op_loadnil(vm, operand)?;
}
LOADSELF => {
op_loadself(vm, operand)?;
}
LOADT => {
op_loadt(vm, operand)?;
}
LOADF => {
op_loadf(vm, operand)?;
}
GETGV => {
op_getgv(vm, operand)?;
}
SETGV => {
op_setgv(vm, operand)?;
}
GETIV => {
op_getiv(vm, operand)?;
}
SETIV => {
op_setiv(vm, operand)?;
}
GETCV => {
op_getcv(vm, operand)?;
}
SETCV => {
op_setcv(vm, operand)?;
}
GETCONST => {
op_getconst(vm, operand)?;
}
SETCONST => {
op_setconst(vm, operand)?;
}
GETMCNST => {
op_getmcnst(vm, operand)?;
}
SETMCNST => {
op_setmcnst(vm, operand)?;
}
GETUPVAR => {
op_getupvar(vm, operand)?;
}
SETUPVAR => {
op_setupvar(vm, operand)?;
}
GETIDX => {
op_getidx(vm, operand)?;
}
SETIDX => {
op_setidx(vm, operand)?;
}
JMP => {
op_jmp(vm, operand, pos + len)?;
}
JMPIF => {
op_jmpif(vm, operand, pos + len)?;
}
JMPNOT => {
op_jmpnot(vm, operand, pos + len)?;
}
JMPNIL => {
op_jmpnil(vm, operand, pos + len)?;
}
JMPUW => {
op_jmpuw(vm, operand, pos + len)?;
}
EXCEPT => {
op_except(vm, operand)?;
}
RESCUE => {
op_rescue(vm, operand)?;
}
RAISEIF => {
op_raiseif(vm, operand)?;
}
SSEND => {
op_ssend(vm, operand)?;
}
SSENDB => {
op_ssendb(vm, operand)?;
}
SEND => {
op_send(vm, operand)?;
}
SENDB => {
op_sendb(vm, operand)?;
}
CALL => {
op_call(vm, operand)?;
}
SUPER => {
op_super(vm, operand)?;
}
ENTER => {
op_enter(vm, operand)?;
}
KEY_P => {
op_key_p(vm, operand)?;
}
KEYEND => {
op_keyend(vm, operand)?;
}
KARG => {
op_karg(vm, operand)?;
}
RETURN => {
op_return(vm, operand)?;
}
RETURN_BLK => {
op_return_blk(vm, operand)?;
}
BREAK => {
op_break(vm, operand)?;
}
BLKPUSH => {
op_blkpush(vm, operand)?;
}
ADD => {
op_add(vm, operand)?;
}
ADDI => {
op_addi(vm, operand)?;
}
SUB => {
op_sub(vm, operand)?;
}
SUBI => {
op_subi(vm, operand)?;
}
MUL => {
op_mul(vm, operand)?;
}
DIV => {
op_div(vm, operand)?;
}
EQ => {
op_eq(vm, operand)?;
}
LT => {
op_lt(vm, operand)?;
}
LE => {
op_le(vm, operand)?;
}
GT => {
op_gt(vm, operand)?;
}
GE => {
op_ge(vm, operand)?;
}
ARRAY => {
op_array(vm, operand)?;
}
ARRAY2 => {
op_array2(vm, operand)?;
}
ARYCAT => {
op_arycat(vm, operand)?;
}
ARYPUSH => {
op_arypush(vm, operand)?;
}
ARYSPLAT => {
op_arysplat(vm, operand)?;
}
AREF => {
op_aref(vm, operand)?;
}
APOST => {
op_apost(vm, operand)?;
}
SYMBOL => {
op_symbol(vm, operand)?;
}
STRING => {
op_string(vm, operand)?;
}
STRCAT => {
op_strcat(vm, operand)?;
}
HASH => {
op_hash(vm, operand)?;
}
HASHADD => {
op_hashadd(vm, operand)?;
}
HASHCAT => {
op_hashcat(vm, operand)?;
}
LAMBDA => {
op_lambda(vm, operand)?;
}
BLOCK => {
op_block(vm, operand)?;
}
METHOD => {
op_method(vm, operand)?;
}
RANGE_INC => {
op_range_inc(vm, operand)?;
}
RANGE_EXC => {
op_range_exc(vm, operand)?;
}
OCLASS => {
op_oclass(vm, operand)?;
}
CLASS => {
op_class(vm, operand)?;
}
MODULE => {
op_module(vm, operand)?;
}
EXEC => {
op_exec(vm, operand)?;
}
DEF => {
op_def(vm, operand)?;
}
ALIAS => {
op_alias(vm, operand)?;
}
UNDEF => {
op_undef(vm, operand)?;
}
SCLASS => {
op_sclass(vm, operand)?;
}
TCLASS => {
op_tclass(vm, operand)?;
}
STOP => {
op_stop(vm, operand)?;
}
GETIDX0 => {
op_getidx0(vm, operand)?;
}
MATCHERR => {
op_matcherr(vm, operand)?;
}
SSEND0 => {
op_ssend0(vm, operand)?;
}
SEND0 => {
op_send0(vm, operand)?;
}
TDEF => {
op_tdef(vm, operand)?;
}
SDEF => {
op_sdef(vm, operand)?;
}
BLKCALL => {
op_blkcall(vm, operand)?;
}
RETSELF => {
op_retself(vm, operand)?;
}
RETNIL => {
op_retnil(vm, operand)?;
}
RETTRUE => {
op_rettrue(vm, operand)?;
}
RETFALSE => {
op_retfalse(vm, operand)?;
}
ADDILV => {
op_addilv(vm, operand)?;
}
SUBILV => {
op_subilv(vm, operand)?;
}
_ => {
unimplemented!("{:?}: Not supported yet", code)
}
}
Ok(())
}
pub(crate) fn push_callinfo(
vm: &mut VM,
method_id: RSym,
n_args: usize,
method_owner: Option<Rc<RModule>>,
return_reg: usize,
) {
vm.enter_frame();
vm.current_n_args.set(n_args);
let callinfo = CALLINFO {
prev: vm.current_callinfo.clone(),
method_id,
pc_irep: vm.current_irep.clone(),
pc: vm.pc.get(),
current_regs_offset: vm.current_regs_offset,
n_args,
return_reg,
target_class: vm.target_class.clone(),
method_owner,
has_block: Cell::new(false),
};
vm.current_callinfo = Some(Rc::new(callinfo));
}
#[allow(dead_code)]
pub(crate) fn pop_callinfo(vm: &mut VM) {
let ci = vm.current_callinfo.take();
if ci.is_none() {
unreachable!("callinfo underflow");
}
let ci = ci.unwrap();
vm.leave_frame();
if let Some(prev) = &ci.prev {
vm.current_callinfo.replace(prev.clone());
}
vm.current_irep = ci.pc_irep.clone();
vm.pc.set(ci.pc);
vm.current_regs_offset = ci.current_regs_offset;
vm.target_class = ci.target_class.clone();
}
fn calcurate_pc(irep: &IREP, pc: usize, original_pc: usize) -> usize {
let mut next_pc = pc;
loop {
let op = irep.code.get(next_pc).expect("cannot fetch op anymore");
if op.pos == original_pc {
break;
}
next_pc += 1;
}
next_pc
}
pub(crate) fn op_nop(_vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
Ok(())
}
pub(crate) fn op_loadi_n(vm: &mut VM, n: i32, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = RObject::integer(n as i64);
vm.current_regs()[a].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_loadl(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let pool_val = vm.current_irep.pool[b as usize].clone();
let val = match pool_val {
RPool::Str(s) => Rc::new(RObject::string(s)),
RPool::Int(i) => Rc::new(RObject::integer(i)),
RPool::Float(f) => Rc::new(RObject::float(f)),
RPool::Data(_) => {
return Err(Error::Internal(
"Binary data in pool not supported yet".to_string(),
));
}
};
vm.current_regs()[a as usize].replace(val);
Ok(())
}
pub(crate) fn op_loadi16(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bs()?;
let val = RObject::integer(b as i64);
vm.current_regs()[a as usize].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_loadi32(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bss()?;
let val = RObject::integer((b as i64) << 16 | c as i64);
vm.current_regs()[a as usize].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_loadi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = RObject::integer(b as i64);
vm.current_regs()[a as usize].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_loadineg(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = RObject::integer(-(b as i64));
vm.current_regs()[a as usize].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_loadsym(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = vm.current_irep.syms[b as usize].clone();
vm.current_regs()[a as usize].replace(Rc::new(RObject::symbol(val)));
Ok(())
}
pub(crate) fn op_loadnil(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = RObject::nil();
vm.current_regs()[a].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_loadself(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val: Rc<RObject> = vm.getself()?;
vm.current_regs()[a].replace(val);
Ok(())
}
pub(crate) fn op_loadt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = RObject::boolean(true);
vm.current_regs()[a].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_loadf(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = RObject::boolean(false);
vm.current_regs()[a].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_getgv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = vm.current_irep.syms[b as usize].clone();
let val = match vm.globals.get(&val.name) {
Some(val) => val.clone(),
None => RObject::nil().to_refcount_assigned(),
};
vm.current_regs()[a as usize].replace(val);
Ok(())
}
pub(crate) fn op_setgv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = vm.get_current_regs_cloned(a as usize)?;
let sym = vm.current_irep.syms[b as usize].clone();
vm.globals.insert(sym.name.clone(), val);
Ok(())
}
pub(crate) fn op_getiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let this = vm.getself()?;
let key = vm.current_irep.syms[b as usize].name.clone();
vm.current_regs()[a as usize].replace(this.get_ivar(&key));
Ok(())
}
pub(crate) fn op_setiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let this = vm.getself()?;
let val = vm.get_current_regs_cloned(a as usize)?;
let key = vm.current_irep.syms[b as usize].name.clone();
this.set_ivar(&key, val.clone());
Ok(())
}
pub(crate) fn op_getcv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let name = vm.current_irep.syms[b as usize].name.clone();
let value = cvar_lookup(vm, &name)?;
vm.current_regs()[a as usize].replace(value);
Ok(())
}
pub(crate) fn op_setcv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let name = vm.current_irep.syms[b as usize].name.clone();
let value = vm.get_current_regs_cloned(a as usize)?;
cvar_set(vm, &name, value);
Ok(())
}
fn class_context(vm: &mut VM) -> Result<Rc<RClass>, Error> {
let obj = vm.current_regs()[0]
.clone()
.ok_or_else(|| Error::internal("self is not assigned"))?;
match &obj.value {
RValue::Class(klass) => Ok(klass.clone()),
RValue::Module(_) => Err(Error::TypeMismatch),
_ => Ok(obj.get_class(vm)),
}
}
fn cvar_lookup(vm: &mut VM, name: &str) -> Result<Rc<RObject>, Error> {
let mut current: Option<Rc<RClass>> = Some(class_context(vm)?);
while let Some(klass) = current.clone() {
let wrapper = RObject::class(klass.clone(), vm);
if let Some(val) = wrapper.ivar.borrow().get(name).cloned() {
return Ok(val);
}
current = klass.super_class.clone();
}
Err(Error::NameError(format!(
"uninitialized class variable {name}"
)))
}
fn cvar_set(vm: &mut VM, name: &str, value: Rc<RObject>) {
let cls = match class_context(vm) {
Ok(c) => c,
Err(_) => return,
};
let mut current = Some(cls.clone());
while let Some(klass) = current.clone() {
let wrapper = RObject::class(klass.clone(), vm);
if wrapper.ivar.borrow().contains_key(name) {
wrapper.ivar.borrow_mut().insert(name.to_string(), value);
return;
}
current = klass.super_class.clone();
}
let wrapper = RObject::class(cls, vm);
wrapper.ivar.borrow_mut().insert(name.to_string(), value);
}
pub(crate) fn op_getconst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let name = vm.current_irep.syms[b as usize].name.clone();
let mut current = current_namespace(vm).or_else(|| {
vm.current_regs()[0]
.clone()
.map(|o| o.get_class(vm).module.clone())
});
while let Some(ns) = current.clone() {
if let Some(val) = ns.consts.borrow().get(&name).cloned() {
vm.current_regs()[a as usize].replace(val);
return Ok(());
}
current = ns.parent.borrow().clone();
}
if let Some(val) = vm.consts.get(&name).cloned() {
vm.current_regs()[a as usize].replace(val);
return Ok(());
}
Err(Error::NameError(name))
}
pub(crate) fn op_setconst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let name = vm.current_irep.syms[b as usize].name.clone();
let val = vm.get_current_regs_cloned(a as usize)?;
match current_namespace(vm) {
Some(ns) => {
ns.consts.borrow_mut().insert(name, val);
}
None => {
vm.consts.insert(name.clone(), val.clone());
vm.object_class.module.consts.borrow_mut().insert(name, val);
}
}
Ok(())
}
pub(crate) fn op_getmcnst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let recv = vm.get_current_regs_cloned(a as usize)?;
let name = vm.current_irep.syms[b as usize].name.clone();
let mut module = match &recv.value {
RValue::Class(klass) => Some(klass.module.clone()),
RValue::Module(module) => Some(module.clone()),
_ => None,
};
while let Some(current) = module.clone() {
if let Some(val) = current.consts.borrow().get(&name).cloned() {
vm.current_regs()[a as usize].replace(val);
return Ok(());
}
module = current.parent.borrow().clone();
}
Err(Error::NameError(name.clone()))
}
pub(crate) fn op_setmcnst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let name = vm.current_irep.syms[b as usize].name.clone();
let module = vm.get_current_regs_cloned(a as usize + 1)?;
let value = vm.get_current_regs_cloned(a as usize)?;
match &module.value {
RValue::Class(klass) => {
klass.module.consts.borrow_mut().insert(name, value);
}
RValue::Module(module) => {
module.consts.borrow_mut().insert(name, value);
}
_ => {
return Err(Error::TypeMismatch);
}
}
Ok(())
}
pub(crate) fn op_getupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let n = c as usize;
let mut environ = vm
.upper
.as_ref()
.ok_or_else(|| Error::internal("op_getupvar expects upper env"))?;
for _ in 0..n {
environ = environ
.upper
.as_ref()
.ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?;
}
let environ = environ.clone();
let up_regs = &vm.regs[environ.current_regs_offset..];
if !environ.expired() {
if let Some(val) = up_regs[b as usize].as_ref().cloned() {
vm.current_regs()[a as usize].replace(val);
} else {
return Err(Error::internal(format!("register {} is empty", b)));
}
} else {
let captured = environ.captured.borrow();
let val = &captured
.as_ref()
.ok_or_else(|| Error::internal("captured environment not found"))?[b as usize];
let val = val.clone();
vm.current_regs()[a as usize]
.replace(val.ok_or_else(|| Error::internal("captured value not found"))?);
}
Ok(())
}
pub(crate) fn op_setupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let n = c as usize;
let mut environ = vm
.upper
.as_ref()
.ok_or_else(|| Error::internal("op_getupvar expects upper env"))?;
for _ in 0..n {
environ = environ
.upper
.as_ref()
.ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?;
}
let environ = environ.clone();
let current_regs_offset = environ.current_regs_offset;
let val = vm.get_current_regs_cloned(a as usize)?;
if !environ.expired() {
let up_regs = &mut vm.regs[current_regs_offset..];
let target = &mut up_regs[b as usize];
target.replace(val);
} else {
let mut captured = environ.captured.borrow_mut();
let captured = captured
.as_mut()
.ok_or_else(|| Error::internal("captured environment not found"))?;
let target = &mut captured[b as usize];
target.replace(val);
}
Ok(())
}
pub(crate) fn op_getidx(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let recv = vm.get_current_regs_cloned(a)?;
let idx = vm.get_current_regs_cloned(a + 1)?;
let args = vec![idx];
let val = mrb_funcall(vm, Some(recv), "[]", &args)?;
vm.current_regs()[a].replace(val);
Ok(())
}
pub(crate) fn op_getidx0(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let recv = vm.get_current_regs_cloned(b as usize)?;
let zero = RObject::integer(0).to_refcount_assigned();
vm.current_regs()[a as usize].replace(recv);
vm.current_regs()[a as usize + 1].replace(zero);
do_op_send_with_id(vm, a as usize, None, a, RSym::new("[]".to_string()), 1)
}
pub(crate) fn op_matcherr(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let value = vm.get_current_regs_cloned(a)?;
if value.is_truthy() {
return Ok(());
}
Err(Error::TaggedError(
"NoMatchingPatternError".to_string(),
"pattern not matched".to_string(),
))
}
pub(crate) fn op_setidx(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let recv = vm.get_current_regs_cloned(a)?;
let idx = vm.get_current_regs_cloned(a + 1)?;
let val = vm.get_current_regs_cloned(a + 2)?;
let args = vec![idx, val];
mrb_funcall(vm, Some(recv), "[]=", &args)?;
Ok(())
}
pub(crate) fn op_jmp(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
let a = operand.as_s()?;
let offset = a as i16;
let next_pc = calcurate_pc(
&vm.current_irep,
0,
(end_pos as isize + offset as isize) as usize,
);
vm.pc.set(next_pc);
Ok(())
}
pub(crate) fn op_jmpif(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
let (a, b) = operand.as_bs()?;
let val = vm.get_current_regs_cloned(a as usize)?;
if val.is_truthy() {
let offset = b as i16;
let next_pc = calcurate_pc(
&vm.current_irep,
0,
(end_pos as isize + offset as isize) as usize,
);
vm.pc.set(next_pc);
}
Ok(())
}
pub(crate) fn op_jmpnot(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
let (a, b) = operand.as_bs()?;
let val = vm.get_current_regs_cloned(a as usize)?;
if val.is_falsy() {
let offset = b as i16;
let next_pc = calcurate_pc(
&vm.current_irep,
0,
(end_pos as isize + offset as isize) as usize,
);
vm.pc.set(next_pc);
}
Ok(())
}
pub(crate) fn op_jmpnil(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
let (a, b) = operand.as_bs()?;
let val = vm.get_current_regs_cloned(a as usize)?;
if val.is_nil() {
let offset = b as i16;
let next_pc = calcurate_pc(
&vm.current_irep,
0,
(end_pos as isize + offset as isize) as usize,
);
vm.pc.set(next_pc);
}
Ok(())
}
pub(crate) fn op_jmpuw(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
let offset = operand.as_s()? as i16;
let target = calcurate_pc(
&vm.current_irep,
0,
(end_pos as isize + offset as isize) as usize,
);
Err(Error::UnwindJump(target))
}
pub(crate) fn op_except(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()?;
let val = vm
.exception
.take()
.map(|e| RObject::exception(e).to_refcount_assigned())
.unwrap_or_else(|| RObject::nil().to_refcount_assigned());
vm.current_regs()[a as usize].replace(val);
Ok(())
}
pub(crate) fn op_rescue(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = vm.get_current_regs_cloned(a as usize)?;
let exc_klass = vm.take_current_regs(b as usize)?;
let RValue::Class(klass) = exc_klass.value.clone() else {
return Err(Error::TaggedError(
"TypeError".to_string(),
"class or module required for rescue clause".to_string(),
));
};
let is_rescued = match &val.value {
RValue::Exception(exc) => {
let etype = exc.error_type.borrow();
etype.is_a(vm, klass)
}
_ => false,
};
let val = RObject::boolean(is_rescued);
vm.current_regs()[b as usize].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_raiseif(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()?;
let val = vm.current_regs()[a as usize].as_ref().cloned();
if let Some(val) = val
&& let RValue::Exception(e) = &val.value
{
vm.pending_exception = Some(e.clone());
return Err(e.as_ref().error_type.borrow().clone());
}
Ok(())
}
pub(crate) fn op_move(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = vm.get_current_regs_cloned(b as usize)?;
let _old = vm.current_regs()[a as usize].replace(val);
Ok(())
}
pub(crate) fn op_ssend(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
do_op_send(vm, 0, None, a, b, c)
}
pub(crate) fn op_ssendb(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let n: usize = (c & 0x0f) as usize;
let k: usize = (c >> 4) as usize;
do_op_send(vm, 0, Some(a as usize + n + k * 2 + 1), a, b, c)
}
pub(crate) fn op_send(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
do_op_send(vm, a as usize, None, a, b, c)
}
pub(crate) fn op_sendb(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let n: usize = (c & 0x0f) as usize;
let k: usize = (c >> 4) as usize;
do_op_send(vm, a as usize, Some(a as usize + n + k * 2 + 1), a, b, c)
}
pub(crate) fn op_ssend0(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
do_op_send(vm, 0, None, a, b, 0)
}
pub(crate) fn op_send0(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
do_op_send(vm, a as usize, None, a, b, 0)
}
pub(crate) fn op_blkcall(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let block = vm.get_current_regs_cloned(a as usize)?;
if !matches!(block.value, RValue::Proc(_)) {
return Err(Error::TaggedError(
"TypeError".to_string(),
"wrong type (expected Proc)".to_string(),
));
}
do_op_send_with_id(vm, a as usize, None, a, RSym::new("call".to_string()), b)
}
pub(crate) fn do_op_send(
vm: &mut VM,
recv_index: usize,
blk_index: Option<usize>,
a: u8,
b: u8,
c: u8,
) -> Result<(), Error> {
let method_id = vm.current_irep.syms[b as usize].clone();
do_op_send_with_id(vm, recv_index, blk_index, a, method_id, c)
}
pub(crate) fn do_op_send_with_id(
vm: &mut VM,
recv_index: usize,
blk_index: Option<usize>,
a: u8,
method_id: RSym,
c: u8,
) -> Result<(), Error> {
let mut n: usize = (c & 0x0f) as usize;
let k: usize = (c >> 4) as usize;
if &method_id.name == "__debug__vm_info" {
vm.debug_dump_to_stdout(32);
vm.current_regs()[a as usize].replace(Rc::new(RObject::nil()));
return Ok(());
}
let block_index = a as usize + n + k * 2 + 1;
let recv = if recv_index == 0 {
vm.getself()?
} else {
vm.get_current_regs_cloned(recv_index)?
};
let mut map = RHashMap::default();
for i in 0..k {
let key = vm
.get_current_regs_cloned(a as usize + n + i * 2 + 1)?
.intern()?;
let val = vm
.get_current_regs_cloned(a as usize + n + i * 2 + 2)?
.clone();
map.insert(key, val);
}
let block = if let Some(blk_index) = blk_index {
let value = vm.get_current_regs_cloned(blk_index)?;
let block = match &value.value {
RValue::Nil | RValue::Proc(_) => value,
_ => {
let start = a as usize + 1;
let saved = vm.current_regs()[start..start + n].to_vec();
let converted = mrb_funcall(vm, Some(value), "to_proc", &[]);
vm.current_regs()[start..start + n].clone_from_slice(&saved);
let converted = converted?;
if !matches!(converted.value, RValue::Proc(_)) {
return Err(Error::TaggedError(
"TypeError".into(),
"to_proc must return a Proc".into(),
));
}
converted
}
};
block
} else {
Rc::new(RObject::nil())
};
vm.current_regs()[block_index].replace(block.clone());
let klass = recv.get_class(vm);
let klass = if klass.is_singleton {
klass
} else {
recv.singleton_or_this_class(vm)
};
let (owner_module, method) = resolve_method(&klass, &method_id.name)
.or_else(|| {
unshift_method_name(vm, &method_id, a as usize, n + k * 2 + 1);
n += 1;
resolve_method(&klass, "method_missing")
})
.ok_or_else(|| {
Error::Internal(format!(
"[BUG] method_missing not defined. {} for {}",
method_id.name,
klass.full_name()
))
})?;
vm.kargs.borrow_mut().replace(map);
let upper = vm.current_breadcrumb.take();
let new_breadcrumb = Rc::new(Breadcrumb {
upper,
event: "do_op_send",
caller: Some(method_id.name.clone()),
return_reg: Some(a as usize),
});
vm.current_breadcrumb.replace(new_breadcrumb);
vm.current_regs()[a as usize].replace(recv.clone());
if !method.is_rb_func {
let mut args = Vec::with_capacity(n + usize::from(blk_index.is_some()));
for i in 0..n {
args.push(vm.get_current_regs_cloned(a as usize + i + 1)?);
}
if blk_index.is_some() {
args.push(block.clone());
}
kwarg_op_enter(vm, 0);
let func = vm
.get_fn(method.func.unwrap())
.ok_or_else(|| Error::internal("function not found"))?;
vm.current_regs_offset += a as usize;
let res = func(vm, &args);
if let Err(error) = &res {
vm.prepare_exception(error, Some(&method_id.name));
}
kwarg_op_return(vm);
vm.current_regs_offset -= a as usize;
for i in (a as usize + 1)..block_index {
vm.current_regs()[i].take();
}
match res {
Ok(val) => {
vm.current_regs()[a as usize].replace(val);
let cur = vm
.current_breadcrumb
.take()
.expect("send should push breadcrumb");
let upper = cur.upper.clone();
vm.current_breadcrumb
.replace(upper.expect("should have upper breadcrumb"));
}
Err(e) => {
let cur = vm.current_breadcrumb.take().expect("send breadcrumb");
vm.current_breadcrumb = cur.upper.clone();
let anchored = vm
.break_landing
.borrow()
.as_ref()
.is_some_and(|(target, _)| Rc::ptr_eq(target, &cur));
if anchored {
let mut cursor = vm.current_breadcrumb.clone();
let mut landing = None;
while let Some(bc) = cursor {
if let Some(reg) = bc.return_reg {
landing = Some((bc.clone(), reg));
break;
}
cursor = bc.upper.clone();
}
vm.break_landing.replace(landing);
}
vm.current_regs()[a as usize].replace(Rc::new(RObject::nil()));
return Err(e);
}
}
return Ok(());
}
vm.current_regs()[a as usize + n + 1].replace(block.clone());
push_callinfo(vm, method_id, n, Some(owner_module), a as usize);
if let Some(ci) = vm.current_callinfo.as_ref() {
ci.has_block.set(!matches!(block.value, RValue::Nil));
}
vm.pc.set(0);
vm.current_irep = method.irep.ok_or_else(|| Error::internal("empry irep"))?;
vm.current_regs_offset += a as usize;
Ok(())
}
fn unshift_method_name(vm: &mut VM, method_id: &RSym, a: usize, total_args: usize) {
let method_name = RObject::symbol(method_id.clone()).to_refcount_assigned();
for i in (a + 1..=a + total_args).rev() {
let val = vm.current_regs().get(i).and_then(|r| r.as_ref().cloned());
val.as_ref().cloned().map(|v| mrb_call_inspect(vm, v));
vm.current_regs()[i + 1].replace(val.unwrap_or_else(|| Rc::new(RObject::nil())));
}
vm.current_regs()[a + 1].replace(method_name);
}
fn kwarg_op_enter(vm: &mut VM, rest_pos: usize) {
let kwrest_reg = Cell::new(rest_pos);
let current_arg = if let Some(args) = vm.kargs.borrow_mut().take() {
let upper = vm.current_kargs.borrow_mut().take();
KArgs {
args: RefCell::new(args),
kwrest_reg,
upper,
}
} else {
KArgs {
args: RefCell::new(RHashMap::default()),
kwrest_reg,
upper: None,
}
};
vm.current_kargs.borrow_mut().replace(Rc::new(current_arg));
}
fn kwarg_op_return(vm: &mut VM) {
let old_kargs = vm.current_kargs.borrow_mut().take();
if let Some(upper) = old_kargs.as_ref().and_then(|kargs| kargs.upper.clone()) {
vm.current_kargs.borrow_mut().replace(upper);
}
}
pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
let upper = vm.current_breadcrumb.take();
let new_breadcrumb = Rc::new(Breadcrumb {
upper,
event: "op_call",
caller: Some("<tailcall>".into()),
return_reg: None,
});
vm.current_breadcrumb.replace(new_breadcrumb);
push_callinfo(vm, "<tailcall>".into(), 0, None, 0);
vm.pc.set(0);
let proc = vm.current_regs()[0]
.as_ref()
.cloned()
.ok_or_else(|| Error::internal("proc not found"))?;
match &proc.value {
RValue::Proc(proc) => {
vm.current_irep = proc
.irep
.as_ref()
.ok_or_else(|| Error::internal("empry irep"))?
.clone();
}
_ => unreachable!("call must be called on proc"),
}
Ok(())
}
pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let callinfo = vm
.current_callinfo
.as_ref()
.ok_or_else(|| Error::internal("no current callinfo"))?;
let sym_id = callinfo.method_id.name.clone();
let owner_module = callinfo
.method_owner
.clone()
.ok_or_else(|| Error::RuntimeError("super called outside of method".to_string()))?;
let recv = vm.getself()?;
let args = (0..b)
.map(|i| {
vm.get_current_regs_cloned((a + i + 1) as usize)
.expect("args too short for super")
})
.collect::<Vec<_>>();
let klass = match &recv.value {
RValue::Instance(ins) => ins.class.clone(),
_ => recv.initialize_or_get_singleton_class(vm),
};
let (next_owner, method) =
resolve_next_method(&klass, &sym_id, &owner_module).ok_or_else(|| {
Error::NoMethodError(format!("{} for {}", sym_id.clone(), klass.full_name()))
})?;
if !method.is_rb_func {
let func = vm.get_fn(method.func.unwrap()).ok_or_else(|| {
Error::internal(format!("functon registerd but no entry found: {}", &sym_id))
})?;
let res = func(vm, &args);
for i in (a as usize + 1)..(a as usize + b as usize + 1) {
vm.current_regs()[i].take();
}
match res {
Ok(val) => {
vm.current_regs()[a as usize].replace(val);
}
Err(e) => {
vm.current_regs()[a as usize].replace(Rc::new(RObject::nil()));
return Err(e);
}
}
return Ok(());
}
let upper = vm.current_breadcrumb.take();
let new_breadcrumb = Rc::new(Breadcrumb {
upper,
event: "super",
caller: Some(format!("super({})", sym_id)),
return_reg: None,
});
vm.current_breadcrumb.replace(new_breadcrumb);
vm.current_regs()[a as usize].replace(recv.clone());
push_callinfo(
vm,
method.sym_id.clone().unwrap(),
b as usize,
Some(next_owner),
a as usize,
);
vm.pc.set(0);
vm.current_irep = method
.irep
.as_ref()
.ok_or_else(|| Error::internal("empty irep"))?
.clone();
vm.current_regs_offset += a as usize;
Ok(())
}
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
pub(crate) struct EnterArgInfo {
pub n1: u32,
pub m1: u32,
pub o: u32,
pub r: u32,
pub m2: u32,
pub k: u32,
pub d: u32,
pub b: u32,
}
impl From<u32> for EnterArgInfo {
fn from(val: u32) -> Self {
EnterArgInfo {
n1: (val & ENTER_N1_MASK) >> 23,
m1: (val & ENTER_M1_MASK) >> 18,
o: (val & ENTER_O_MASK) >> 13,
r: (val & ENTER_R_MASK) >> 12,
m2: (val & ENTER_M2_MASK) >> 7,
k: (val & ENTER_K_MASK) >> 2,
d: (val & ENTER_D_MASK) >> 1,
b: (val & ENTER_B_MASK),
}
}
}
pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_w()?;
let argc = vm.current_n_args.get();
let arg_info = EnterArgInfo::from(a);
let block = vm.get_current_regs_cloned(argc + 1)?;
if arg_info.n1 == 1 && !matches!(block.value, RValue::Nil) {
return Err(Error::ArgumentError("no block accepted".to_string()));
}
let args = (1..=argc)
.map(|i| vm.get_current_regs_cloned(i))
.collect::<Result<Vec<_>, _>>()?;
let required = arg_info.m1 as usize;
let optional = arg_info.o as usize;
let rest = arg_info.r as usize;
let post = arg_info.m2 as usize;
let keyword_slot = usize::from(arg_info.k > 0 || arg_info.d > 0);
let positional_slots = required + optional + rest + post;
let nil = Rc::new(RObject::nil());
let method_frame = vm
.current_callinfo
.as_ref()
.or_else(|| vm.suspended_callinfos.last());
if method_frame.is_some_and(|ci| ci.method_owner.is_some())
&& (argc < required + post || (rest == 0 && argc > required + optional + post))
{
return Err(Error::ArgumentError("wrong number of arguments".into()));
}
for i in 0..required {
vm.current_regs()[i + 1].replace(args.get(i).cloned().unwrap_or_else(|| nil.clone()));
}
let post_count = post.min(argc.saturating_sub(required));
let optional_count = optional.min(argc.saturating_sub(required + post_count));
for i in 0..optional {
let value = if i < optional_count {
args[required + i].clone()
} else {
nil.clone()
};
vm.current_regs()[required + i + 1].replace(value);
}
if rest == 1 {
let start = (required + optional_count).min(argc);
let end = argc - post_count;
vm.current_regs()[required + optional + 1]
.replace(RObject::array(args[start..end].to_vec()).to_refcount_assigned());
}
for i in 0..post {
let value = if i < post_count {
args[argc - post_count + i].clone()
} else {
nil.clone()
};
vm.current_regs()[required + optional + rest + i + 1].replace(value);
}
let kwrest_pos = positional_slots + keyword_slot;
let block_pos = kwrest_pos + 1;
vm.current_regs()[block_pos].replace(block);
vm.pc.set(vm.pc.get() + optional_count);
kwarg_op_enter(vm, if arg_info.d > 0 { kwrest_pos } else { 0 });
if keyword_slot > 0 {
let mut map = RHashMap::default();
for (k, v) in vm.get_kwargs().unwrap_or_default().iter() {
let k = RObject::symbol(RSym::new(k.clone())).to_refcount_assigned();
map.insert(k.as_hash_key()?, (k, v.clone()));
}
vm.current_regs()[kwrest_pos].replace(RObject::hash(map).to_refcount_assigned());
}
Ok(())
}
pub(crate) fn op_key_p(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let key = vm.current_irep.syms[b as usize].clone();
let key_robj = RObject::symbol(key.clone()).to_refcount_assigned();
let (val, kwrest_pos) = {
let kargs = vm.current_kargs.borrow();
let kargs = kargs
.as_ref()
.ok_or_else(|| Error::internal("no kargs found"))?;
let kwrest_pos = kargs.kwrest_reg.get();
(
RObject::boolean(kargs.args.borrow().contains_key(&key)),
kwrest_pos,
)
};
if kwrest_pos != 0 {
let kwrest = vm.get_current_regs_cloned(kwrest_pos)?;
mrb_hash_delete(kwrest, key_robj)?;
}
vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_keyend(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
match vm.current_kargs.borrow().as_deref() {
Some(kargs) => {
let is_empty = kargs.args.borrow().is_empty();
if is_empty {
Ok(())
} else {
Err(Error::ArgumentError(
"unexpected keyword arguments".to_string(),
))
}
}
None => Err(Error::internal("no kargs found")),
}
}
pub(crate) fn op_karg(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val = {
let key = vm.current_irep.syms[b as usize].clone();
let kargs = vm.current_kargs.borrow();
let kargs = kargs
.as_ref()
.ok_or_else(|| Error::internal("no kargs found"))?;
let mut args = kargs.args.borrow_mut();
args.remove(&key).ok_or_else(|| {
Error::ArgumentError(format!("keyword argument '{}' not found", key.name))
})?
};
vm.current_regs()[a as usize].replace(val);
Ok(())
}
pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let value = vm.current_regs()[a].clone();
do_return(vm, value)
}
pub(crate) fn op_retself(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
let value = vm.current_regs()[0].clone();
do_return(vm, value)
}
pub(crate) fn op_retnil(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
do_return(vm, Some(RObject::nil().to_refcount_assigned()))
}
pub(crate) fn op_rettrue(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
do_return(vm, Some(RObject::boolean(true).to_refcount_assigned()))
}
pub(crate) fn op_retfalse(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
do_return(vm, Some(RObject::boolean(false).to_refcount_assigned()))
}
pub(crate) fn do_return(vm: &mut VM, value: Option<Rc<RObject>>) -> Result<(), Error> {
vm.leave_frame();
let regs0 = vm.current_regs();
if let Some(value) = value {
regs0[0].replace(value);
}
let ci = vm.current_callinfo.take();
if ci.is_none() {
let cur = vm.current_breadcrumb.take().expect("not found breadcrumb");
if let Some(upper) = &cur.as_ref().upper {
vm.current_breadcrumb.replace(upper.clone());
}
if let Some(e) = &vm.exception {
return Err(e.error_type.borrow().clone());
}
vm.flag_preemption.set(true);
return Ok(());
}
let ci = ci.unwrap();
if let Some(prev) = &ci.prev {
vm.current_callinfo.replace(prev.clone());
}
vm.current_irep = ci.pc_irep.clone();
vm.pc.set(ci.pc);
vm.current_regs_offset = ci.current_regs_offset;
vm.target_class = ci.target_class.clone();
if vm.current_regs()[0].is_none() {
unreachable!("debug");
}
kwarg_op_return(vm);
let cur = vm.current_breadcrumb.take().expect("not found breadcrumb");
if let Some(upper) = &cur.as_ref().upper {
vm.current_breadcrumb.replace(upper.clone());
}
Ok(())
}
pub(crate) fn op_return_blk(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = vm.get_current_regs_cloned(a)?;
let target = vm.return_target();
if !vm.frames.iter().any(|f| f.id == target) {
return Err(Error::TaggedError(
"LocalJumpError".into(),
"unexpected return".into(),
));
}
Err(Error::BlockReturn(target, val))
}
pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = vm.get_current_regs_cloned(a)?;
if vm.frames.last().is_some_and(|f| f.is_lambda) {
return Err(Error::BlockReturn(vm.frame_id(), val));
}
let mut cursor = vm.current_breadcrumb.clone();
let mut landing = None;
while let Some(bc) = cursor {
if bc.event == "do_op_send" && bc.return_reg.is_some() {
landing = Some((bc.clone(), bc.return_reg.unwrap_or(0)));
break;
}
cursor = bc.upper.clone();
}
vm.break_landing.replace(landing);
Err(Error::Break(val))
}
pub(crate) fn op_blkpush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, layout) = operand.as_bs()?;
let pre = (layout >> 11) & 0x3f;
let rest = (layout >> 10) & 1;
let post = (layout >> 5) & 0x1f;
let keywords = (layout >> 4) & 1;
let level = layout & 0xf;
let index = (pre + rest + post + keywords + 1) as usize;
let missing = || Error::TaggedError("LocalJumpError".into(), "no block given".into());
let block = if level == 0 {
vm.current_regs().get(index).and_then(Clone::clone)
} else {
let mut env = vm.upper.clone().ok_or_else(missing)?;
for _ in 1..level {
env = env.upper.clone().ok_or_else(missing)?;
}
if env.expired() {
env.captured
.borrow()
.as_ref()
.and_then(|regs| regs.get(index))
.and_then(Clone::clone)
} else {
vm.regs
.get(env.current_regs_offset + index)
.and_then(Clone::clone)
}
}
.ok_or_else(missing)?;
if matches!(block.value, RValue::Nil) {
return Err(missing());
}
vm.current_regs()[a as usize].replace(block);
Ok(())
}
pub(crate) fn op_add(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.take_current_regs(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => Rc::new(RObject::integer(n1 + n2)),
(RValue::Float(n1), RValue::Float(n2)) => Rc::new(RObject::float(n1 + n2)),
(RValue::Integer(n1), RValue::Float(n2)) => Rc::new(RObject::float(*n1 as f64 + n2)),
(RValue::Float(n1), RValue::Integer(n2)) => Rc::new(RObject::float(n1 + *n2 as f64)),
(RValue::String(n1, _), RValue::String(n2, _)) => {
let mut bytes = n1.borrow().to_vec();
bytes.extend_from_slice(&n2.borrow());
Rc::new(RObject::string_from_vec(bytes))
}
_ => {
let args = vec![val2.clone()];
mrb_funcall(vm, Some(val1.clone()), "+", &args)?
}
};
vm.current_regs()[a].replace(result);
Ok(())
}
pub(crate) fn op_addi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val1 = vm.take_current_regs(a as usize)?;
let val2 = b as i64;
let result = match &val1.value {
RValue::Integer(n1) => RObject::integer(*n1 + val2),
RValue::Float(n1) => RObject::float(n1 + val2 as f64),
_ => {
unreachable!("addi supports only integer and float")
}
};
vm.current_regs()[a as usize].replace(result.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_sub(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.take_current_regs(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => {
RObject::integer(n1 - n2).to_refcount_assigned()
}
(RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 - n2).to_refcount_assigned(),
(RValue::Integer(n1), RValue::Float(n2)) => {
RObject::float(*n1 as f64 - n2).to_refcount_assigned()
}
(RValue::Float(n1), RValue::Integer(n2)) => {
RObject::float(n1 - *n2 as f64).to_refcount_assigned()
}
_ => {
let args = vec![val2.clone()];
mrb_funcall(vm, Some(val1.clone()), "-", &args)?
}
};
vm.current_regs()[a].replace(result);
Ok(())
}
pub(crate) fn op_subi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let val1 = vm.take_current_regs(a as usize)?;
let val2 = b as i64;
let result = match &val1.value {
RValue::Integer(n1) => RObject::integer(*n1 - val2),
_ => {
unreachable!("subi supports only integer")
}
};
vm.current_regs()[a as usize].replace(result.to_refcount_assigned());
Ok(())
}
fn math_immediate_to_local(vm: &mut VM, operand: &Fetched, add: bool) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let a = a as usize;
let amount = if add { c as i64 } else { -(c as i64) };
let value = vm.get_current_regs_cloned(a)?;
let result = match &value.value {
RValue::Integer(n) => RObject::integer(n + amount).to_refcount_assigned(),
RValue::Float(n) => RObject::float(n + amount as f64).to_refcount_assigned(),
_ => {
let arg = RObject::integer(c as i64).to_refcount_assigned();
vm.current_regs_offset += b as usize;
let res = mrb_funcall(vm, Some(value), if add { "+" } else { "-" }, &[arg]);
vm.current_regs_offset -= b as usize;
res?
}
};
vm.current_regs()[a].replace(result);
Ok(())
}
pub(crate) fn op_addilv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
math_immediate_to_local(vm, operand, true)
}
pub(crate) fn op_subilv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
math_immediate_to_local(vm, operand, false)
}
pub(crate) fn op_mul(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.take_current_regs(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => {
RObject::integer(n1 * n2).to_refcount_assigned()
}
(RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 * n2).to_refcount_assigned(),
(RValue::Integer(n1), RValue::Float(n2)) => {
RObject::float(*n1 as f64 * n2).to_refcount_assigned()
}
(RValue::Float(n1), RValue::Integer(n2)) => {
RObject::float(n1 * *n2 as f64).to_refcount_assigned()
}
_ => mrb_funcall(vm, Some(val1), "*", &[val2])?,
};
vm.current_regs()[a].replace(result);
Ok(())
}
pub(crate) fn op_div(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.take_current_regs(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => {
RObject::integer(n1 / n2).to_refcount_assigned()
}
(RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 / n2).to_refcount_assigned(),
(RValue::Integer(n1), RValue::Float(n2)) => {
RObject::float(*n1 as f64 / n2).to_refcount_assigned()
}
(RValue::Float(n1), RValue::Integer(n2)) => {
RObject::float(n1 / *n2 as f64).to_refcount_assigned()
}
_ => mrb_funcall(vm, Some(val1), "/", &[val2])?,
};
vm.current_regs()[a].replace(result);
Ok(())
}
fn compare_via_spaceship(
vm: &mut VM,
val1: Rc<RObject>,
val2: Rc<RObject>,
op: &str,
) -> Result<RObject, Error> {
let r = mrb_funcall(vm, Some(val1), "<=>", std::slice::from_ref(&val2))?;
match r.value {
RValue::Nil => Err(Error::ArgumentError("comparison failed".into())),
_ => {
let ord = i64::try_from(r.as_ref())
.map_err(|_| Error::ArgumentError("bad <=> result".into()))?;
let hit = match op {
"<" => ord < 0,
"<=" => ord <= 0,
">" => ord > 0,
_ => ord >= 0,
};
Ok(RObject::boolean(hit))
}
}
}
pub(crate) fn op_lt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.get_current_regs_cloned(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 < n2),
(RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 < n2),
(RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) < *n2),
(RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 < (*n2 as f64)),
_ => compare_via_spaceship(vm, val1.clone(), val2.clone(), "<")?,
};
vm.current_regs()[a].replace(Rc::new(result));
Ok(())
}
pub(crate) fn op_le(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.get_current_regs_cloned(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 <= n2),
(RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 <= n2),
(RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) <= *n2),
(RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 <= (*n2 as f64)),
_ => compare_via_spaceship(vm, val1.clone(), val2.clone(), "<=")?,
};
vm.current_regs()[a].replace(Rc::new(result));
Ok(())
}
pub(crate) fn op_eq(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let lhs = vm.take_current_regs(a)?;
let rhs = vm.get_current_regs_cloned(b)?;
let result = mrb_object_is_equal(vm, lhs, rhs);
vm.current_regs()[a].replace(result);
Ok(())
}
pub(crate) fn op_gt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.get_current_regs_cloned(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 > n2),
(RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 > n2),
(RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) > *n2),
(RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 > (*n2 as f64)),
_ => compare_via_spaceship(vm, val1.clone(), val2.clone(), ">")?,
};
vm.current_regs()[a].replace(Rc::new(result));
Ok(())
}
pub(crate) fn op_ge(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.get_current_regs_cloned(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let result = match (&val1.value, &val2.value) {
(RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 >= n2),
(RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 >= n2),
(RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) >= *n2),
(RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 >= (*n2 as f64)),
_ => compare_via_spaceship(vm, val1.clone(), val2.clone(), ">=")?,
};
vm.current_regs()[a].replace(Rc::new(result));
Ok(())
}
pub(crate) fn op_array(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
do_op_array(vm, a as usize, a as usize, b as usize)
}
pub(crate) fn op_array2(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
do_op_array(vm, a as usize, b as usize, c as usize)
}
fn do_op_array(vm: &mut VM, this: usize, start: usize, n: usize) -> Result<(), Error> {
let mut ary = Vec::with_capacity(n);
for i in 0..n {
if this == start && i == 0 {
ary.push(vm.take_current_regs(start)?);
} else {
ary.push(vm.get_current_regs_cloned(start + i)?);
}
}
let val = RObject::array(ary);
vm.current_regs()[this].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_arycat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.get_current_regs_cloned(a)?;
let val2 = vm.take_current_regs(b)?;
match (&val1.value, &val2.value) {
(RValue::Array(ary1), RValue::Array(ary2)) => {
let mut ary1 = ary1.borrow_mut();
let ary2 = ary2.borrow();
for item in ary2.iter() {
ary1.push(item.clone());
}
}
(RValue::Nil, RValue::Array(ary2)) => {
let mut ary1 = Vec::new();
let ary2 = ary2.borrow();
for item in ary2.iter() {
ary1.push(item.clone());
}
let val = RObject::array(ary1);
vm.current_regs()[a].replace(val.to_refcount_assigned());
}
_ => {
unreachable!("arycat supports only array")
}
};
Ok(())
}
pub(crate) fn op_arypush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let a = a as usize;
let b = b as usize;
let items: Vec<Rc<RObject>> = (0..b)
.map(|i| vm.take_current_regs(a + 1 + i))
.collect::<Result<_, _>>()?;
let ary = vm.get_current_regs_cloned(a)?;
match &ary.value {
RValue::Array(_) => {
let mut inner = ary.array_borrow_mut()?;
inner.extend(items);
}
RValue::Nil => {
let val = RObject::array(items);
vm.current_regs()[a].replace(val.to_refcount_assigned());
}
_ => {
unreachable!("arypush supports only array")
}
}
Ok(())
}
pub(crate) fn op_arysplat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = vm.get_current_regs_cloned(a)?;
match &val.value {
RValue::Array(_) => {}
RValue::Nil => {
let ary = RObject::array(Vec::new());
vm.current_regs()[a].replace(ary.to_refcount_assigned());
}
_ => {
let ary = RObject::array(vec![val]);
vm.current_regs()[a].replace(ary.to_refcount_assigned());
}
}
Ok(())
}
pub(crate) fn op_aref(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let array = vm.get_current_regs_cloned(b as usize)?;
let index = c as usize;
match &array.value {
RValue::Array(ary) => {
let ary = ary.borrow();
let val = ary
.get(index)
.cloned()
.unwrap_or_else(|| Rc::new(RObject::nil()));
vm.current_regs()[a as usize].replace(val);
}
_ => {
unreachable!("aref supports only array")
}
};
Ok(())
}
pub(crate) fn op_apost(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
if c != 0 {
return Err(Error::internal(
"apost with 3 operands is not supported yet",
));
}
let array = vm.get_current_regs_cloned(a as usize)?;
let n = b as usize;
match &array.value {
RValue::Array(ary) => {
let mut dest = Vec::new();
let ary = ary.borrow();
for i in n..ary.len() {
dest.push(ary[i].clone());
}
let newval = RObject::array(dest).to_refcount_assigned();
vm.current_regs()[a as usize].replace(newval);
}
_ => {
unreachable!("apost supports only array")
}
};
Ok(())
}
pub(crate) fn op_symbol(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let symstr = vm.current_irep.pool[b as usize].as_str().to_string();
let sym = RSym::new(symstr);
let val = RObject::symbol(sym);
vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_string(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let str = vm.current_irep.pool[b as usize].as_str().to_string();
let val = RObject::string(str);
vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_strcat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let b = a + 1;
let val1 = vm.get_current_regs_cloned(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let s1 = match &val1.value {
RValue::String(s, _) => s,
_ => unreachable!("strcat supports only string"),
};
match &val2.value {
RValue::String(s2, _) => {
let bytes = s2.borrow().to_vec();
s1.borrow_mut().extend_from_slice(&bytes);
}
RValue::Integer(i) => {
let bytes = i.to_string().into_bytes();
s1.borrow_mut().extend_from_slice(&bytes);
}
_ => append_to_string(vm, s1, &val2)?,
}
Ok(())
}
fn append_to_string(vm: &mut VM, s1: &RefCell<Vec<u8>>, val2: &Rc<RObject>) -> Result<(), Error> {
let s2 = mrb_funcall(vm, Some(val2.clone()), "to_s", &[])?;
let bytes = match &s2.value {
RValue::String(s, _) => s.borrow().to_vec(),
_ => unreachable!("to_s must return string"),
};
s1.borrow_mut().extend_from_slice(&bytes);
Ok(())
}
pub(crate) fn op_hash(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let a = a as usize;
let b = b as usize;
let mut hash = RHashMap::default();
for i in 0..b {
let key = vm.get_current_regs_cloned(a + i * 2)?;
let val = vm.get_current_regs_cloned(a + i * 2 + 1)?;
hash.insert(key.as_hash_key()?, (key, val));
}
let val = RObject::hash(hash);
vm.current_regs()[a].replace(Rc::new(val));
Ok(())
}
pub(crate) fn op_hashadd(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let a = a as usize;
let b = b as usize;
let pairs: Vec<(Rc<RObject>, Rc<RObject>)> = (0..b)
.map(|i| {
let key = vm.take_current_regs(a + i * 2 + 1)?;
let val = vm.take_current_regs(a + i * 2 + 2)?;
Ok((key, val))
})
.collect::<Result<_, Error>>()?;
let hash = vm.get_current_regs_cloned(a)?;
let mut inner = hash.hash_borrow_mut()?;
for (key, val) in pairs {
let hashed = key.as_hash_key()?;
inner.insert(hashed, (key, val));
}
Ok(())
}
pub(crate) fn op_hashcat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let other = vm.take_current_regs(a + 1)?;
let hash = vm.get_current_regs_cloned(a)?;
match (&hash.value, &other.value) {
(RValue::Hash(_), RValue::Hash(other_hash)) => {
let mut inner = hash.hash_borrow_mut()?;
for (hashed, (key, val)) in other_hash.borrow().iter() {
inner.insert(hashed.clone(), (key.clone(), val.clone()));
}
}
(RValue::Nil, RValue::Hash(other_hash)) => {
let mut fresh = RHashMap::default();
for (hashed, (key, val)) in other_hash.borrow().iter() {
fresh.insert(hashed.clone(), (key.clone(), val.clone()));
}
let val = RObject::hash(fresh);
vm.current_regs()[a].replace(val.to_refcount_assigned());
}
_ => {
return Err(Error::TypeMismatch);
}
}
Ok(())
}
pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let irep = Some(vm.current_irep.reps[b as usize].clone());
let environ = vm.capture_env();
let val = RObject {
tt: RType::Proc,
value: RValue::Proc(RProc {
is_lambda: true,
return_target: None,
irep,
is_rb_func: true,
is_fnblock: false,
sym_id: Some("<lambda>".into()),
next: None,
func: None,
environ: Some(environ),
block_self: Some(vm.getself()?),
}),
object_id: u64::MAX.into(),
singleton_class: RefCell::new(None),
ivar: RefCell::new(RHashMap::default()),
};
vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let irep = Some(vm.current_irep.reps[b as usize].clone());
let environ = vm.capture_env();
let val = RObject {
tt: RType::Proc,
value: RValue::Proc(RProc {
is_lambda: false,
return_target: Some(vm.return_target()),
irep,
is_rb_func: true,
is_fnblock: false,
sym_id: Some("<block>".into()),
next: None,
func: None,
environ: Some(environ),
block_self: Some(vm.getself()?),
}),
object_id: u64::MAX.into(),
singleton_class: RefCell::new(None),
ivar: RefCell::new(RHashMap::default()),
};
vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_method(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let irep = Some(vm.current_irep.reps[b as usize].clone());
let val = RObject {
tt: super::value::RType::Proc,
value: super::value::RValue::Proc(super::value::RProc {
is_lambda: false,
return_target: None,
irep,
is_rb_func: true,
is_fnblock: false,
sym_id: None,
next: None,
func: None,
environ: None,
block_self: None,
}),
object_id: u64::MAX.into(),
singleton_class: RefCell::new(None),
ivar: RefCell::new(RHashMap::default()),
};
vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_range_inc(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()?;
do_op_range(vm, a as usize, a as usize + 1, false)
}
pub(crate) fn op_range_exc(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()?;
do_op_range(vm, a as usize, a as usize + 1, true)
}
fn do_op_range(vm: &mut VM, a: usize, b: usize, exclusive: bool) -> Result<(), Error> {
let val1 = vm.get_current_regs_cloned(a)?;
let val2 = vm.get_current_regs_cloned(b)?;
let val = RObject::range(val1, val2, exclusive);
vm.current_regs()[a].replace(val.to_refcount_assigned());
Ok(())
}
pub(crate) fn op_oclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = RObject::class(vm.object_class.clone(), vm);
vm.current_regs()[a].replace(val);
Ok(())
}
fn current_namespace(vm: &mut VM) -> Option<Rc<RModule>> {
match vm.current_regs()[0].as_ref() {
Some(obj) => match &obj.value {
RValue::Class(klass) => Some(klass.module.clone()),
RValue::Module(module) => Some(module.clone()),
_ => None,
},
None => None,
}
}
pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let superclass = vm.current_regs()[a as usize + 1].as_ref().cloned();
let name = vm.current_irep.syms[b as usize].clone();
let superclass = match superclass {
Some(superclass) => {
if let RValue::Class(klass) = &superclass.value {
klass.clone()
} else {
vm.object_class.clone()
}
}
None => vm.object_class.clone(),
};
let parent_module = current_namespace(vm);
let name = name.name;
let klass = vm.define_class(&name, Some(superclass), parent_module.clone());
let class_value = RObject::class(klass.clone(), vm);
class_value.initialize_or_get_singleton_class_for_class(vm);
if let Some(parent) = parent_module {
parent
.consts
.borrow_mut()
.insert(name.clone(), class_value.clone());
} else {
vm.consts.insert(name.clone(), class_value.clone());
}
vm.current_regs()[a as usize].replace(class_value);
Ok(())
}
pub(crate) fn op_module(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let name = vm.current_irep.syms[b as usize].clone();
let name = name.name;
let parent_module = current_namespace(vm);
let module = vm.define_module(&name, parent_module.clone());
let module_value = Rc::new(RObject::module(module.clone()));
if let Some(parent) = parent_module {
parent
.consts
.borrow_mut()
.insert(name.clone(), module_value.clone());
} else {
vm.consts.insert(name.clone(), module_value.clone());
}
vm.current_regs()[a as usize].replace(module_value);
Ok(())
}
pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let recv = vm.get_current_regs_cloned(a as usize)?;
let upper = vm.current_breadcrumb.take();
let new_breadcrumb = Rc::new(Breadcrumb {
upper,
event: "exec",
caller: Some("<exec>".into()),
return_reg: None,
});
vm.current_breadcrumb.replace(new_breadcrumb);
push_callinfo(vm, "<exec>".into(), 0, None, a as usize);
vm.pc.set(0);
let irep = vm.current_irep.reps[b as usize].clone();
vm.current_irep = irep;
vm.current_regs_offset += a as usize;
vm.target_class = match &recv.value {
RValue::Class(klass) => TargetContext::Class(klass.clone()),
RValue::Module(module) => TargetContext::Module(module.clone()),
_ => TargetContext::Class(recv.get_class(vm)),
};
Ok(())
}
pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let target = vm.get_current_regs_cloned(a as usize)?;
let method = vm.get_current_regs_cloned(a as usize + 1)?;
let sym = vm.current_irep.syms[b as usize].clone();
let method_ref = method.as_ref();
let method = match &method_ref.value {
RValue::Proc(proc) => {
let mut method = proc.clone();
method.environ = None; method.sym_id = Some(sym.clone());
Ok(method)
}
_ => Err(Error::ArgumentError(
"def operand 2 must be Proc (method)".to_string(),
)),
}?;
let target_ref = target.as_ref();
match &target_ref.value {
RValue::Class(klass) => {
let mut procs = klass.procs.borrow_mut();
procs.insert(sym.name.clone(), method);
}
RValue::Module(module) => {
let mut procs = module.procs.borrow_mut();
procs.insert(sym.name.clone(), method);
}
_ => {
let robject = target.clone();
let current_class = robject.get_class(vm);
let sclass = if current_class.is_singleton {
current_class
} else {
robject.initialize_or_get_singleton_class(vm)
};
let mut procs = sclass.procs.borrow_mut();
procs.insert(sym.name.clone(), method);
}
}
vm.current_regs()[a as usize].replace(RObject::symbol(sym).to_refcount_assigned());
Ok(())
}
pub(crate) fn op_alias(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b) = operand.as_bb()?;
let new_name = vm.current_irep.syms[a as usize].clone();
let old_name = vm.current_irep.syms[b as usize].clone();
let owner = vm.target_class.clone();
let (owner_module, method) = match &owner {
TargetContext::Class(klass) => {
let (owner_module, method) = resolve_method(klass, &old_name.name)
.ok_or_else(|| Error::NoMethodError(old_name.name.clone()))?;
(owner_module, method)
}
TargetContext::Module(module) => {
let method = module
.procs
.borrow()
.get(&old_name.name)
.cloned()
.ok_or_else(|| Error::NoMethodError(old_name.name.clone()))?;
(module.clone(), method)
}
};
let mut new_method = method.clone();
new_method.sym_id = Some(new_name.clone());
let mut procs = owner_module.procs.borrow_mut();
procs.insert(new_name.name.clone(), new_method);
Ok(())
}
pub(crate) fn op_undef(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()?;
let sym = vm.current_irep.syms[a as usize].clone();
let owner = vm.target_class.clone();
match &owner {
TargetContext::Class(klass) => {
let mut procs = klass.procs.borrow_mut();
procs.remove(&sym.name);
}
TargetContext::Module(module) => {
let mut procs = module.procs.borrow_mut();
procs.remove(&sym.name);
}
};
Ok(())
}
pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val = vm.current_regs()[a]
.take()
.expect("SCLASS: operand too short");
let singleton_class = match val.tt {
RType::Class | RType::Module => val.initialize_or_get_singleton_class_for_class(vm),
_ => val.initialize_or_get_singleton_class(vm),
};
let robj = RObject::class(singleton_class.clone(), vm);
vm.current_regs()[a].replace(robj);
Ok(())
}
pub(crate) fn op_tclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let a = operand.as_b()? as usize;
let val: Rc<RObject> = match &vm.target_class {
TargetContext::Class(klass) => RObject::class(klass.clone(), vm),
TargetContext::Module(module) => Rc::new(module.clone().into()),
};
vm.current_regs()[a].replace(val);
Ok(())
}
fn method_from_irep(vm: &VM, index: usize, sym: RSym) -> RProc {
RProc {
is_lambda: false,
return_target: None,
irep: Some(vm.current_irep.reps[index].clone()),
is_rb_func: true,
is_fnblock: false,
sym_id: Some(sym),
next: None,
func: None,
environ: None,
block_self: None,
}
}
pub(crate) fn op_tdef(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let sym = vm.current_irep.syms[b as usize].clone();
let method = method_from_irep(vm, c as usize, sym.clone());
match vm.target_class.clone() {
TargetContext::Class(klass) => {
klass.procs.borrow_mut().insert(sym.name.clone(), method);
}
TargetContext::Module(module) => {
module.procs.borrow_mut().insert(sym.name.clone(), method);
}
}
vm.current_regs()[a as usize].replace(RObject::symbol(sym).to_refcount_assigned());
Ok(())
}
pub(crate) fn op_sdef(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, b, c) = operand.as_bbb()?;
let sym = vm.current_irep.syms[b as usize].clone();
let method = method_from_irep(vm, c as usize, sym.clone());
let target = vm.get_current_regs_cloned(a as usize)?;
let singleton = match target.tt {
RType::Class | RType::Module => target.initialize_or_get_singleton_class_for_class(vm),
_ => target.initialize_or_get_singleton_class(vm),
};
singleton
.procs
.borrow_mut()
.insert(sym.name.clone(), method);
vm.current_regs()[a as usize].replace(RObject::symbol(sym).to_refcount_assigned());
Ok(())
}
pub(crate) fn op_stop(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
vm.flag_preemption.set(true);
Ok(())
}