use std::cell::RefCell;
use std::ffi::{c_void, CString};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use crate::class;
use crate::convert::RustBackedValue;
use crate::module;
use crate::sys;
use crate::{Mrb, MrbError};
pub type Free = unsafe extern "C" fn(mrb: *mut sys::mrb_state, data: *mut c_void);
pub unsafe extern "C" fn rust_data_free<T: RustBackedValue>(
_mrb: *mut sys::mrb_state,
data: *mut c_void,
) {
let data = Rc::from_raw(data as *const RefCell<T>);
drop(data);
}
pub type Method =
unsafe extern "C" fn(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value;
#[derive(Clone, Debug)]
pub enum EnclosingRubyScope {
Class {
spec: Rc<RefCell<class::Spec>>,
},
Module {
spec: Rc<RefCell<module::Spec>>,
},
}
impl EnclosingRubyScope {
#[allow(clippy::needless_pass_by_value)]
pub fn class(spec: Rc<RefCell<class::Spec>>) -> Self {
EnclosingRubyScope::Class {
spec: Rc::clone(&spec),
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn module(spec: Rc<RefCell<module::Spec>>) -> Self {
EnclosingRubyScope::Module {
spec: Rc::clone(&spec),
}
}
pub fn rclass(&self, interp: &Mrb) -> Option<*mut sys::RClass> {
match self {
EnclosingRubyScope::Class { spec } => spec.borrow().rclass(interp),
EnclosingRubyScope::Module { spec } => spec.borrow().rclass(interp),
}
}
pub fn fqname(&self) -> String {
match self {
EnclosingRubyScope::Class { spec } => spec.borrow().fqname(),
EnclosingRubyScope::Module { spec } => spec.borrow().fqname(),
}
}
}
impl Eq for EnclosingRubyScope {}
impl PartialEq for EnclosingRubyScope {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
EnclosingRubyScope::Class { spec: this },
EnclosingRubyScope::Class { spec: other },
) => this == other,
(
EnclosingRubyScope::Module { spec: this },
EnclosingRubyScope::Module { spec: other },
) => this == other,
_ => false,
}
}
}
impl Hash for EnclosingRubyScope {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
EnclosingRubyScope::Class { spec } => spec.borrow().hash(state),
EnclosingRubyScope::Module { spec } => spec.borrow().hash(state),
};
}
}
pub trait Define
where
Self: ClassLike,
{
fn define(&self, interp: &Mrb) -> Result<*mut sys::RClass, MrbError>;
}
pub trait ClassLike
where
Self: fmt::Debug + fmt::Display,
{
fn add_method(&mut self, name: &str, method: Method, args: sys::mrb_aspec);
fn add_self_method(&mut self, name: &str, method: Method, args: sys::mrb_aspec);
fn cstring(&self) -> &CString;
fn name(&self) -> &str;
fn enclosing_scope(&self) -> Option<EnclosingRubyScope>;
fn rclass(&self, interp: &Mrb) -> Option<*mut sys::RClass>;
fn fqname(&self) -> String {
if let Some(scope) = self.enclosing_scope() {
format!("{}::{}", scope.fqname(), self.name())
} else {
self.name().to_owned()
}
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use crate::def::{ClassLike, Define, EnclosingRubyScope};
#[test]
fn fqname() {
struct Root;
struct ModuleUnderRoot;
struct ClassUnderRoot;
struct ClassUnderModule;
struct ModuleUnderClass;
struct ClassUnderClass;
let interp = crate::interpreter().expect("mrb init");
{
let mut api = interp.borrow_mut();
let root = api.def_module::<Root>("A", None);
let mod_under_root = api.def_module::<ModuleUnderRoot>(
"B",
Some(EnclosingRubyScope::module(Rc::clone(&root))),
);
let cls_under_root =
api.def_class::<ClassUnderRoot>("C", Some(EnclosingRubyScope::module(root)), None);
let _cls_under_mod = api.def_class::<ClassUnderModule>(
"D",
Some(EnclosingRubyScope::module(mod_under_root)),
None,
);
let _mod_under_cls = api.def_module::<ModuleUnderClass>(
"E",
Some(EnclosingRubyScope::class(Rc::clone(&cls_under_root))),
);
let _cls_under_cls = api.def_class::<ClassUnderClass>(
"F",
Some(EnclosingRubyScope::class(cls_under_root)),
None,
);
}
let spec = interp.borrow().module_spec::<Root>().unwrap();
spec.borrow().define(&interp).expect("def module");
let spec = interp.borrow().module_spec::<ModuleUnderRoot>().unwrap();
spec.borrow().define(&interp).expect("def module");
let spec = interp.borrow().class_spec::<ClassUnderRoot>().unwrap();
spec.borrow().define(&interp).expect("def class");
let spec = interp.borrow().class_spec::<ClassUnderModule>().unwrap();
spec.borrow().define(&interp).expect("def class");
let spec = interp.borrow().module_spec::<ModuleUnderClass>().unwrap();
spec.borrow().define(&interp).expect("def module");
let spec = interp.borrow().class_spec::<ClassUnderClass>().unwrap();
spec.borrow().define(&interp).expect("def class");
let spec = interp
.borrow()
.module_spec::<Root>()
.expect("Root not defined");
assert_eq!(&spec.borrow().fqname(), "A");
assert_eq!(&format!("{}", spec.borrow()), "mruby module spec -- A");
let spec = interp
.borrow()
.module_spec::<ModuleUnderRoot>()
.expect("ModuleUnderRoot not defined");
assert_eq!(&spec.borrow().fqname(), "A::B");
assert_eq!(&format!("{}", spec.borrow()), "mruby module spec -- A::B");
let spec = interp
.borrow()
.class_spec::<ClassUnderRoot>()
.expect("ClassUnderRoot not defined");
assert_eq!(&spec.borrow().fqname(), "A::C");
assert_eq!(&format!("{}", spec.borrow()), "mruby class spec -- A::C");
let spec = interp
.borrow()
.class_spec::<ClassUnderModule>()
.expect("ClassUnderModule not defined");
assert_eq!(&spec.borrow().fqname(), "A::B::D");
assert_eq!(&format!("{}", spec.borrow()), "mruby class spec -- A::B::D");
let spec = interp
.borrow()
.module_spec::<ModuleUnderClass>()
.expect("ModuleUnderClass not defined");
assert_eq!(&spec.borrow().fqname(), "A::C::E");
assert_eq!(
&format!("{}", spec.borrow()),
"mruby module spec -- A::C::E"
);
let spec = interp
.borrow()
.class_spec::<ClassUnderClass>()
.expect("ClassUnderClass not defined");
assert_eq!(&spec.borrow().fqname(), "A::C::F");
assert_eq!(&format!("{}", spec.borrow()), "mruby class spec -- A::C::F");
}
mod functional {
use crate::convert::TryFromMrb;
use crate::def::{ClassLike, Define};
use crate::eval::MrbEval;
use crate::sys;
#[test]
fn define_method() {
struct Class;
struct Module;
extern "C" fn value(_mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value {
unsafe {
match slf.tt {
sys::mrb_vtype::MRB_TT_CLASS => sys::mrb_sys_fixnum_value(8),
sys::mrb_vtype::MRB_TT_MODULE => sys::mrb_sys_fixnum_value(27),
sys::mrb_vtype::MRB_TT_OBJECT => sys::mrb_sys_fixnum_value(64),
_ => sys::mrb_sys_fixnum_value(125),
}
}
}
let interp = crate::interpreter().expect("mrb init");
let (cls, module) = {
let mut api = interp.borrow_mut();
let cls = api.def_class::<Class>("DefineMethodTestClass", None, None);
let module = api.def_module::<Module>("DefineMethodTestModule", None);
cls.borrow_mut()
.add_method("value", value, sys::mrb_args_none());
cls.borrow_mut()
.add_self_method("value", value, sys::mrb_args_none());
module
.borrow_mut()
.add_method("value", value, sys::mrb_args_none());
module
.borrow_mut()
.add_self_method("value", value, sys::mrb_args_none());
(cls, module)
};
cls.borrow().define(&interp).expect("class install");
module.borrow().define(&interp).expect("module install");
interp
.eval(
r#"
class DynamicTestClass
include DefineMethodTestModule
extend DefineMethodTestModule
end
module DynamicTestModule
extend DefineMethodTestModule
end
"#,
)
.expect("eval");
let result = interp
.eval("DefineMethodTestClass.new.value")
.expect("eval");
let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
assert_eq!(result, 64);
let result = interp.eval("DefineMethodTestClass.value").expect("eval");
let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
assert_eq!(result, 8);
let result = interp.eval("DefineMethodTestModule.value").expect("eval");
let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
assert_eq!(result, 27);
let result = interp.eval("DynamicTestClass.new.value").expect("eval");
let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
assert_eq!(result, 64);
let result = interp.eval("DynamicTestClass.value").expect("eval");
let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
assert_eq!(result, 8);
let result = interp.eval("DynamicTestModule.value").expect("eval");
let result = unsafe { i64::try_from_mrb(&interp, result).expect("convert") };
assert_eq!(result, 27);
}
}
}