mrubyedge 1.0.7

mruby/edge is yet another mruby that is specialized for running on WASM
Documentation
use std::rc::Rc;

use crate::Error;

use super::{optable::push_callinfo, value::{resolve_method, RClass, RFn, RModule, RObject, RProc, RSym, RValue}, vm::VM};

fn call_block(
    vm: &mut VM,
    block: RProc,
    recv: Rc<RObject>,
    args: &[Rc<RObject>],
    method_info: Option<(RSym, Rc<RModule>)>,
) -> Result<Rc<RObject>, Error> {
    let (method_id, method_owner) = match method_info {
        Some((id, owner)) => (id, Some(owner)),
        None => (RSym::new("<block>".to_string()), None),
    };
    push_callinfo(vm, method_id, args.len(), method_owner);

    let old_callinfo = vm.current_callinfo.take();

    // Since call_block does not move the registers offset,
    // keep the state before the call.
    let prev_self = vm.current_regs()[0].replace(recv);

    let mut prev_args = vec![];
    for (i, arg) in args.iter().enumerate() {
        let old = vm.current_regs()[i + 1].replace(arg.clone());
        prev_args.push(old);
    }

    vm.pc.set(0);
    vm.current_irep = block.irep.as_ref().ok_or_else(|| Error::RuntimeError("No IREP".to_string()))?.clone();
    vm.upper = block.environ;

    let res = vm.run();

    if let Some(prev) = prev_self {
        vm.current_regs()[0].replace(prev);
    } else {
        vm.current_regs()[0].take();
    }
    for (i, prev_arg) in prev_args.into_iter().enumerate() {
        if let Some(prev) = prev_arg {
            vm.current_regs()[i + 1].replace(prev);
        } else {
            vm.current_regs()[i + 1].take();
        }
    }

    if let Some(ci) = old_callinfo {
        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 let Some(upper) = vm.upper.take() {
        if let Some(upper) = &upper.as_ref().upper {
            vm.upper.replace(upper.clone());
        }
    }

    match &res {
        Ok(res) => {
            Ok(res.clone())
        },
        Err(e) => {
            let err = if let Some(e) = e.downcast_ref::<Error>() {
                e.clone()
            } else {
                // TODO: Rust level error
                Error::RuntimeError(format!("{:?}", e.as_ref()))
            };
            Err(err)
        }
    }   
}

pub fn mrb_call_block(vm: &mut VM, block: Rc<RObject>, recv: Option<Rc<RObject>>, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let block = match &block.value {
        RValue::Proc(p) => p.clone(),
        _ => panic!("Not a block"),
        
    };
    let recv = match recv {
        Some(r) => r,
        None => block.block_self.clone().ok_or_else(|| Error::RuntimeError("No block self assigned".to_string()))?,
    };
    call_block(vm, block, recv, args, None)
}

pub fn mrb_funcall(vm: &mut VM, top_self: Option<Rc<RObject>>, name: &str, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let recv: Rc<RObject> = match top_self {
        Some(obj) => obj,
        None => vm.getself()?,
    };
    let binding = recv.as_ref().get_class(vm);
    let (owner_module, method) = resolve_method(&binding, name).ok_or_else(|| Error::NoMethodError(name.to_string()))?;
    
    if method.is_rb_func {
        let method_id = method.sym_id.clone().unwrap_or_else(|| RSym::new(name.to_string()));
        call_block(vm, method, recv.clone(), args, Some((method_id, owner_module)))
    } else {
        vm.current_regs_offset += 2; // FIXME: magick number?
        vm.current_regs()[0].replace(recv.clone());

        let func = vm.fn_table[method.func.unwrap()].clone();
        let res = func(vm, &args);
        vm.current_regs_offset -= 2;

        res
    }
}

pub fn mrb_define_cmethod(vm: &mut VM, klass: Rc<RClass>, name: &str, cmethod: RFn) {
    let index = vm.register_fn(cmethod);
    let method = RProc {
        is_rb_func: false,
        sym_id: Some(RSym::new(name.to_string())),
        next: None,
        irep: None,
        func: Some(index),
        environ: None,
        block_self: None,
    };
    let mut procs = klass.procs.borrow_mut();
    procs.insert(name.to_string(), method);
}

pub fn mrb_define_method(_vm: &mut VM, klass: Rc<RClass>, name: &str, method: RProc) {
    let mut procs = klass.procs.borrow_mut();
    procs.insert(name.to_string(), method);
}

pub fn mrb_define_module_cmethod(vm: &mut VM, module: Rc<RModule>, name: &str, cmethod: RFn) {
    let index = vm.register_fn(cmethod);
    let method = RProc {
        is_rb_func: false,
        sym_id: Some(RSym::new(name.to_string())),
        next: None,
        irep: None,
        func: Some(index),
        environ: None,
        block_self: None,
    };
    let mut procs = module.procs.borrow_mut();
    procs.insert(name.to_string(), method);
}

pub fn mrb_define_module_method(_vm: &mut VM, module: Rc<RModule>, name: &str, method: RProc) {
    let mut procs = module.procs.borrow_mut();
    procs.insert(name.to_string(), method);
}