use std::cell::Cell;
use std::cell::RefCell;
#[cfg(feature = "mrubyedge-debug")]
use std::env;
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_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;
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)?;
}
GETCONST => {
op_getconst(vm, operand)?;
}
SETCONST => {
op_setconst(vm, operand)?;
}
GETMCNST => {
op_getmcnst(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)?;
}
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)?;
}
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)?;
}
_ => {
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,
) {
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();
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 = vm
.globals
.get(&val.name)
.ok_or_else(|| Error::internal(format!("global variable not found {}", val.name)))?
.clone();
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_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);
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)?;
vm.consts.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_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_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> {
if vm.current_irep.catch_target_pos.is_empty() {
op_jmp(vm, operand, end_pos)
} else {
let target_pos = vm.current_irep.catch_target_pos[0];
vm.pc.set(target_pos);
consume_ensure_block(vm)?;
op_jmp(vm, operand, end_pos)
}
}
fn consume_ensure_block(vm: &mut VM) -> Result<(), Error> {
loop {
let pc = vm.pc.get();
if vm.current_irep.code.len() <= pc {
return Err(Error::internal(
"end of opcode reached while consuming ensure block",
));
}
let op = *vm
.current_irep
.code
.get(pc)
.ok_or_else(|| Error::internal("end of opcode reached"))?;
let operand = op.operand;
vm.pc.set(pc + 1);
if matches!(op.code, OpCode::RAISEIF) {
return Ok(());
}
#[cfg(feature = "mrubyedge-debug")]
if let Ok(v) = env::var("MRUBYEDGE_DEBUG") {
let level: i32 = v.parse().unwrap_or(1);
if level >= 2 {
vm.debug_dump_to_stdout(32);
}
eprintln!(
"{:?}: {:?} (pos={} len={})",
op.code, &operand, op.pos, op.len
);
}
match consume_expr(vm, op.code, &operand, op.pos, op.len) {
Ok(_) => {}
Err(e) => {
let exception = RException::from_error(vm, &e);
vm.exception = Some(Rc::new(exception));
continue;
}
}
}
}
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)?;
match (&val.value, exc_klass.value.clone()) {
(RValue::Exception(exc), RValue::Class(klass)) => {
let etype = exc.error_type.borrow();
let is_rescued = etype.is_a(vm, klass);
let val = RObject::boolean(is_rescued);
vm.current_regs()[b as usize].replace(val.to_refcount_assigned());
}
_ => unreachable!("rescue must be called on exception"),
};
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
{
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 do_op_send(
vm: &mut VM,
recv_index: usize,
blk_index: Option<usize>,
a: u8,
b: u8,
c: u8,
) -> Result<(), Error> {
let mut n: usize = (c & 0x0f) as usize;
let k: usize = (c >> 4) as usize;
let method_id = vm.current_irep.syms[b as usize].clone();
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 args = (0..n)
.map(|i| {
vm.get_current_regs_cloned(a as usize + i + 1)
.expect("args too short for required")
})
.collect::<Vec<_>>();
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);
}
vm.kargs.borrow_mut().replace(map);
if let Some(blk_index) = blk_index {
let blk_val = vm.get_current_regs_cloned(blk_index)?;
if matches!(blk_val.tt, RType::Symbol) {
let proc_val = mrb_funcall(vm, Some(blk_val), "to_proc", &[])?;
args.push(proc_val);
} else {
args.push(blk_val);
}
} else {
vm.current_regs()[block_index].replace(Rc::new(RObject::nil()));
}
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, &mut args, &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()
))
})?;
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 {
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);
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) => {
vm.current_regs()[a as usize].replace(Rc::new(RObject::nil()));
return Err(e);
}
}
return Ok(());
}
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(blk_index.is_some());
}
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,
args: &mut Vec<Rc<RObject>>,
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())));
}
args.insert(0, method_name.clone());
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 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 {
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_callinfo.as_ref().map_or(0, |ci| ci.n_args);
let arg_info = EnterArgInfo::from(a);
let m1_argc = arg_info.m1 as usize;
for i in 0..m1_argc {
match vm.current_regs()[i + 1].as_ref() {
Some(_) => {}
None => {
return Err(Error::ArgumentError(format!(
"argument {} not passed",
i + 1
)));
}
}
}
let optional_arg = arg_info.o as usize;
if optional_arg > 0 {
let m2_argc = arg_info.m2 as usize;
let total_preset_args = argc.saturating_sub(m1_argc + m2_argc);
for peek_pc in 0..total_preset_args {
match vm.current_irep.code[vm.pc.get() + peek_pc].code {
OpCode::JMP => {}
_ => {
unreachable!("unexpected opcode while processing optional args")
}
}
}
vm.pc.set(vm.pc.get() + total_preset_args);
}
let splat_arg = arg_info.r as usize;
if splat_arg == 1 {
let total_args = argc;
let passed_args = total_args.saturating_sub(m1_argc);
let mut array = Vec::new();
for i in 0..passed_args {
if let Some(val) = vm.current_regs()[m1_argc + i + 1].take() {
array.push(val);
}
}
let splat = RObject::array(array);
vm.current_regs()[m1_argc + splat_arg].replace(splat.to_refcount_assigned());
}
let kwrest_arg = arg_info.d as usize;
let kwrest_pos = if kwrest_arg == 1 {
m1_argc + splat_arg + kwrest_arg
} else {
0
};
kwarg_op_enter(vm, kwrest_pos);
if kwrest_arg == 1 {
let mut map = RHashMap::default();
for (k, v) in vm
.get_kwargs()
.ok_or_else(|| Error::RuntimeError("kwargs not defined".to_string()))?
.iter()
{
let k = RObject::symbol(RSym::new(k.clone())).to_refcount_assigned();
map.insert(k.as_hash_key()?, (k, v.clone()));
}
let kwrest = RObject::hash(map);
vm.current_regs()[kwrest_pos].replace(kwrest.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 old_irep = vm.current_irep.clone();
let nregs = old_irep.nregs;
let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].to_vec();
if let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) {
environ.capture_no_clone(regs0_cloned);
environ.as_ref().expire();
vm.has_env_ref.remove(&vm.current_irep.__id);
}
let regs0 = vm.current_regs();
if let Some(regs_a) = regs0[a].clone() {
regs0[0].replace(regs_a);
}
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_irep_id = vm
.get_outermost_env()
.expect("not found outermost env")
.__irep_id;
Err(Error::BlockReturn(target_irep_id, 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)?;
Err(Error::Break(val))
}
pub(crate) fn op_blkpush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
let (a, _s) = operand.as_bs()?;
let n = vm.current_callinfo.as_ref().unwrap().n_args;
let block = vm.get_current_regs_cloned(n + 1)?;
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 n1 = n1.borrow_mut();
let n2 = n2.borrow();
for c in n2.iter() {
n1.push(*c);
}
val1.clone()
}
_ => {
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(())
}
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(())
}
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.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::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)),
_ => {
unreachable!("lt supports only numeric")
}
};
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.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::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)),
_ => {
unreachable!("le supports only numeric")
}
};
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.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::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)),
_ => {
unreachable!("gt supports only numeric")
}
};
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.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::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)),
_ => {
unreachable!("ge supports only numeric")
}
};
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_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)?;
match (&val1.value, &val2.value) {
(RValue::String(s1, _), RValue::String(s2, _)) => {
let mut s1 = s1.borrow_mut();
let s2 = s2.borrow();
for c in s2.iter() {
s1.push(*c);
}
}
(RValue::String(s1, _), RValue::Integer(s2)) => {
let mut s1 = s1.borrow_mut();
let s2 = s2.to_string();
for c in s2.as_bytes() {
s1.push(*c);
}
}
(RValue::String(s1, _), _) => {
let mut s1 = s1.borrow_mut();
let s2 = mrb_funcall(vm, Some(val2.clone()), "to_s", &[])?;
let s2 = match &s2.value {
RValue::String(s, _) => s.borrow(),
_ => unreachable!("to_s must return string"),
};
for c in s2.to_vec().iter() {
s1.push(*c);
}
}
_ => {
unreachable!("strcat supports only string")
}
};
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_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 = ENV {
__irep_id: vm.current_irep.__id,
upper: vm.upper.clone(),
current_regs_offset: vm.current_regs_offset,
is_expired: Cell::new(false),
captured: RefCell::new(None),
};
let environ = Rc::new(environ);
vm.cur_env.insert(vm.current_irep.__id, environ.clone());
vm.has_env_ref.insert(vm.current_irep.__id, true);
let val = RObject {
tt: RType::Proc,
value: RValue::Proc(RProc {
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 = ENV {
__irep_id: vm.current_irep.__id,
upper: vm.upper.clone(),
current_regs_offset: vm.current_regs_offset,
is_expired: Cell::new(false),
captured: RefCell::new(None),
};
let environ = Rc::new(environ);
vm.cur_env.insert(vm.current_irep.__id, environ.clone());
vm.has_env_ref.insert(vm.current_irep.__id, true);
let val = RObject {
tt: RType::Proc,
value: RValue::Proc(RProc {
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 {
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 = RObject::module(module.clone()).to_refcount_assigned();
if let Some(parent) = parent_module {
parent
.consts
.borrow_mut()
.insert(name.clone(), module_value);
} else {
vm.consts.insert(name.clone(), module_value);
}
vm.current_regs()[a as usize].replace(Rc::new(module.into()));
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(())
}
pub(crate) fn op_stop(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
vm.flag_preemption.set(true);
Ok(())
}