use std::borrow::Cow;
use std::error;
use std::ffi::{c_void, CStr};
use std::fmt;
use std::io::{self, Write as _};
use std::ptr::NonNull;
use spinoso_exception::{NameError, ScriptError};
use crate::class;
use crate::convert::BoxUnboxVmValue;
use crate::core::{ClassRegistry, TryConvertMut};
use crate::error::{Error, RubyException};
use crate::module;
use crate::sys;
use crate::Artichoke;
pub type Free = unsafe extern "C" fn(mrb: *mut sys::mrb_state, data: *mut c_void);
pub unsafe extern "C" fn box_unbox_free<T>(_mrb: *mut sys::mrb_state, data: *mut c_void)
where
T: 'static + BoxUnboxVmValue,
{
if let Some(data) = NonNull::new(data) {
T::free(data.as_ptr());
} else {
let _ignored = write!(
io::stderr(),
"Received null pointer in box_unbox_free::<{}>",
T::RUBY_TYPE,
);
}
}
#[cfg(test)]
mod free_test {
use crate::convert::HeapAllocatedData;
fn prototype(_func: super::Free) {}
#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct Data(String);
impl HeapAllocatedData for Data {
const RUBY_TYPE: &'static str = "Data";
}
#[test]
fn free_prototype() {
prototype(super::box_unbox_free::<Data>);
}
}
pub type Method = unsafe extern "C" fn(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ClassScope {
name: Box<str>,
name_cstr: &'static CStr,
enclosing_scope: Option<Box<EnclosingRubyScope>>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ModuleScope {
name: Box<str>,
name_cstr: &'static CStr,
name_symbol: u32,
enclosing_scope: Option<Box<EnclosingRubyScope>>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum EnclosingRubyScope {
Class(ClassScope),
Module(ModuleScope),
}
impl EnclosingRubyScope {
#[must_use]
pub fn class(spec: &class::Spec) -> Self {
let name_cstr = spec.name_c_str();
Self::Class(ClassScope {
name: String::from(spec.name()).into_boxed_str(),
name_cstr,
enclosing_scope: spec.enclosing_scope().map(Clone::clone).map(Box::new),
})
}
#[must_use]
pub fn module(spec: &module::Spec) -> Self {
let name_cstr = spec.name_c_str();
Self::Module(ModuleScope {
name: String::from(spec.name()).into_boxed_str(),
name_cstr,
name_symbol: spec.name_symbol(),
enclosing_scope: spec.enclosing_scope().map(Clone::clone).map(Box::new),
})
}
pub unsafe fn rclass(&self, mrb: *mut sys::mrb_state) -> Option<NonNull<sys::RClass>> {
match self {
Self::Class(scope) => {
let enclosing_scope = scope.enclosing_scope.clone().map(|scope| *scope);
class::Rclass::new(scope.name_cstr, enclosing_scope).resolve(mrb)
}
Self::Module(scope) => {
let enclosing_scope = scope.enclosing_scope.clone().map(|scope| *scope);
module::Rclass::new(scope.name_symbol, scope.name_cstr, enclosing_scope).resolve(mrb)
}
}
}
#[must_use]
pub fn fqname(&self) -> Cow<'_, str> {
let (name, enclosing_scope) = match self {
Self::Class(scope) => (&*scope.name, &scope.enclosing_scope),
Self::Module(scope) => (&*scope.name, &scope.enclosing_scope),
};
if let Some(scope) = enclosing_scope {
let mut fqname = String::from(scope.fqname());
fqname.push_str("::");
fqname.push_str(name);
fqname.into()
} else {
name.into()
}
}
}
#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstantNameError(Cow<'static, str>);
impl From<&'static str> for ConstantNameError {
fn from(name: &'static str) -> Self {
Self(name.into())
}
}
impl From<String> for ConstantNameError {
fn from(name: String) -> Self {
Self(name.into())
}
}
impl From<Cow<'static, str>> for ConstantNameError {
fn from(name: Cow<'static, str>) -> Self {
Self(name)
}
}
impl ConstantNameError {
#[must_use]
pub const fn new() -> Self {
Self(Cow::Borrowed(""))
}
}
impl fmt::Display for ConstantNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Invalid constant name contained a NUL byte")
}
}
impl error::Error for ConstantNameError {}
impl RubyException for ConstantNameError {
fn message(&self) -> Cow<'_, [u8]> {
Cow::Borrowed(b"Invalid constant name contained a NUL byte")
}
fn name(&self) -> Cow<'_, str> {
"NameError".into()
}
fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
let _ = interp;
None
}
fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
let message = interp.try_convert_mut(self.message()).ok()?;
let value = interp.new_instance::<NameError>(&[message]).ok().flatten()?;
Some(value.inner())
}
}
impl From<ConstantNameError> for Error {
fn from(exception: ConstantNameError) -> Self {
let err: Box<dyn RubyException> = Box::new(exception);
Self::from(err)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum NotDefinedError {
EnclosingScope(Cow<'static, str>),
Super(Cow<'static, str>),
Class(Cow<'static, str>),
Method(Cow<'static, str>),
Module(Cow<'static, str>),
GlobalConstant(Cow<'static, str>),
ClassConstant(Cow<'static, str>),
ModuleConstant(Cow<'static, str>),
}
impl NotDefinedError {
pub fn enclosing_scope<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::EnclosingScope(item.into())
}
pub fn super_class<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::Super(item.into())
}
pub fn class<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::Class(item.into())
}
pub fn method<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::Method(item.into())
}
pub fn module<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::Module(item.into())
}
pub fn global_constant<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::GlobalConstant(item.into())
}
pub fn class_constant<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::ClassConstant(item.into())
}
pub fn module_constant<T>(item: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::ModuleConstant(item.into())
}
#[must_use]
pub fn fqdn(&self) -> &str {
match self {
Self::EnclosingScope(ref fqdn)
| Self::Super(ref fqdn)
| Self::Class(ref fqdn)
| Self::Module(ref fqdn) => fqdn.as_ref(),
Self::GlobalConstant(ref name)
| Self::ClassConstant(ref name)
| Self::Method(ref name)
| Self::ModuleConstant(ref name) => name.as_ref(),
}
}
#[must_use]
pub const fn item_type(&self) -> &str {
match self {
Self::EnclosingScope(_) => "enclosing scope",
Self::Super(_) => "super class",
Self::Class(_) => "class",
Self::Method(_) => "method",
Self::Module(_) => "module",
Self::GlobalConstant(_) => "global constant",
Self::ClassConstant(_) => "class constant",
Self::ModuleConstant(_) => "module constant",
}
}
}
impl fmt::Display for NotDefinedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.item_type())?;
f.write_str(" '")?;
f.write_str(self.fqdn())?;
f.write_str("' not defined")?;
Ok(())
}
}
impl error::Error for NotDefinedError {}
impl RubyException for NotDefinedError {
fn message(&self) -> Cow<'_, [u8]> {
let mut message = String::from(self.item_type());
message.push(' ');
message.push_str(self.fqdn());
message.push_str(" not defined");
message.into_bytes().into()
}
fn name(&self) -> Cow<'_, str> {
"ScriptError".into()
}
fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
let _ = interp;
None
}
fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
let message = interp.try_convert_mut(self.message()).ok()?;
let value = interp.new_instance::<ScriptError>(&[message]).ok().flatten()?;
Some(value.inner())
}
}
impl From<NotDefinedError> for Error {
fn from(exception: NotDefinedError) -> Self {
let err: Box<dyn RubyException> = Box::new(exception);
Self::from(err)
}
}
#[cfg(test)]
mod tests {
mod fqname {
use crate::test::prelude::*;
#[derive(Debug)]
struct Root;
#[derive(Debug)]
struct ModuleUnderRoot;
#[derive(Debug)]
struct ClassUnderRoot;
#[derive(Debug)]
struct ClassUnderModule;
#[derive(Debug)]
struct ModuleUnderClass;
#[derive(Debug)]
struct ClassUnderClass;
#[test]
fn integration_test() {
let mut interp = interpreter();
let root = module::Spec::new(&mut interp, "A", qed::const_cstr_from_str!("A\0"), None).unwrap();
let mod_under_root = module::Spec::new(
&mut interp,
"B",
qed::const_cstr_from_str!("B\0"),
Some(EnclosingRubyScope::module(&root)),
)
.unwrap();
let cls_under_root = class::Spec::new(
"C",
qed::const_cstr_from_str!("C\0"),
Some(EnclosingRubyScope::module(&root)),
None,
)
.unwrap();
let cls_under_mod = class::Spec::new(
"D",
qed::const_cstr_from_str!("D\0"),
Some(EnclosingRubyScope::module(&mod_under_root)),
None,
)
.unwrap();
let mod_under_cls = module::Spec::new(
&mut interp,
"E",
qed::const_cstr_from_str!("E\0"),
Some(EnclosingRubyScope::class(&cls_under_root)),
)
.unwrap();
let cls_under_cls = class::Spec::new(
"F",
qed::const_cstr_from_str!("F\0"),
Some(EnclosingRubyScope::class(&cls_under_root)),
None,
)
.unwrap();
module::Builder::for_spec(&mut interp, &root).define().unwrap();
module::Builder::for_spec(&mut interp, &mod_under_root)
.define()
.unwrap();
class::Builder::for_spec(&mut interp, &cls_under_root).define().unwrap();
class::Builder::for_spec(&mut interp, &cls_under_mod).define().unwrap();
module::Builder::for_spec(&mut interp, &mod_under_cls).define().unwrap();
class::Builder::for_spec(&mut interp, &cls_under_cls).define().unwrap();
interp.def_module::<Root>(root).unwrap();
interp.def_module::<ModuleUnderRoot>(mod_under_root).unwrap();
interp.def_class::<ClassUnderRoot>(cls_under_root).unwrap();
interp.def_class::<ClassUnderModule>(cls_under_mod).unwrap();
interp.def_module::<ModuleUnderClass>(mod_under_cls).unwrap();
interp.def_class::<ClassUnderClass>(cls_under_cls).unwrap();
let root = interp.module_spec::<Root>().unwrap().unwrap();
assert_eq!(root.fqname().as_ref(), "A");
let mod_under_root = interp.module_spec::<ModuleUnderRoot>().unwrap().unwrap();
assert_eq!(mod_under_root.fqname().as_ref(), "A::B");
let cls_under_root = interp.class_spec::<ClassUnderRoot>().unwrap().unwrap();
assert_eq!(cls_under_root.fqname().as_ref(), "A::C");
let cls_under_mod = interp.class_spec::<ClassUnderModule>().unwrap().unwrap();
assert_eq!(cls_under_mod.fqname().as_ref(), "A::B::D");
let mod_under_cls = interp.module_spec::<ModuleUnderClass>().unwrap().unwrap();
assert_eq!(mod_under_cls.fqname().as_ref(), "A::C::E");
let cls_under_cls = interp.class_spec::<ClassUnderClass>().unwrap().unwrap();
assert_eq!(cls_under_cls.fqname().as_ref(), "A::C::F");
}
}
mod functional {
use crate::test::prelude::*;
#[derive(Debug)]
struct Class;
#[derive(Debug)]
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),
}
}
}
#[test]
fn define_method() {
let mut interp = interpreter();
let class = class::Spec::new(
"DefineMethodTestClass",
qed::const_cstr_from_str!("DefineMethodTestClass\0"),
None,
None,
)
.unwrap();
class::Builder::for_spec(&mut interp, &class)
.add_method("value", value, sys::mrb_args_none())
.unwrap()
.add_self_method("value", value, sys::mrb_args_none())
.unwrap()
.define()
.unwrap();
interp.def_class::<Class>(class).unwrap();
let module = module::Spec::new(
&mut interp,
"DefineMethodTestModule",
qed::const_cstr_from_str!("DefineMethodTestModule\0"),
None,
)
.unwrap();
module::Builder::for_spec(&mut interp, &module)
.add_method("value", value, sys::mrb_args_none())
.unwrap()
.add_self_method("value", value, sys::mrb_args_none())
.unwrap()
.define()
.unwrap();
interp.def_module::<Module>(module).unwrap();
interp
.eval(b"class DynamicTestClass; include DefineMethodTestModule; extend DefineMethodTestModule; end")
.unwrap();
interp
.eval(b"module DynamicTestModule; extend DefineMethodTestModule; end")
.unwrap();
let result = interp.eval(b"DefineMethodTestClass.new.value").unwrap();
let result = result.try_convert_into::<i64>(&interp).unwrap();
assert_eq!(result, 64);
let result = interp.eval(b"DefineMethodTestClass.value").unwrap();
let result = result.try_convert_into::<i64>(&interp).unwrap();
assert_eq!(result, 8);
let result = interp.eval(b"DefineMethodTestModule.value").unwrap();
let result = result.try_convert_into::<i64>(&interp).unwrap();
assert_eq!(result, 27);
let result = interp.eval(b"DynamicTestClass.new.value").unwrap();
let result = result.try_convert_into::<i64>(&interp).unwrap();
assert_eq!(result, 64);
let result = interp.eval(b"DynamicTestClass.value").unwrap();
let result = result.try_convert_into::<i64>(&interp).unwrap();
assert_eq!(result, 8);
let result = interp.eval(b"DynamicTestModule.value").unwrap();
let result = result.try_convert_into::<i64>(&interp).unwrap();
assert_eq!(result, 27);
}
}
}