use std::rc::Rc;
use crate::{
Error,
yamrb::{helpers::mrb_define_cmethod, value::*, vm::VM},
};
pub(crate) fn initialize_module(vm: &mut VM) {
let module_class = vm.define_standard_class("Module");
mrb_define_cmethod(vm, module_class, "include", Box::new(mrb_module_include));
}
fn mrb_module_include(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
if args.is_empty() {
return Err(Error::RuntimeError(
"Module#include expects at least one module".to_string(),
));
}
let self_obj = vm.getself()?;
let target_module = match &self_obj.value {
RValue::Class(klass) => klass.module.clone(),
RValue::Module(module) => module.clone(),
_ => {
return Err(Error::RuntimeError(
"Module#include must be called on class or module".to_string(),
));
}
};
let arg0 = &args[0];
let mixin = match &arg0.value {
RValue::Module(module) => module.clone(),
_ => {
return Err(Error::RuntimeError(
"Module#include expects module arguments".to_string(),
));
}
};
include_module(&target_module, mixin)?;
Ok(self_obj)
}
fn include_module(target: &Rc<RModule>, mixin: Rc<RModule>) -> Result<(), Error> {
if Rc::ptr_eq(target, &mixin) {
return Err(Error::RuntimeError("cannot include itself".to_string()));
}
let already_present = {
let modules = target.mixed_in_modules.borrow();
modules.iter().any(|m| Rc::ptr_eq(m, &mixin))
};
if already_present {
return Err(Error::RuntimeError("module already included".to_string()));
}
target.mixed_in_modules.borrow_mut().insert(0, mixin);
Ok(())
}