use crate::{
binding::{global::RubySpecialConsts, symbol, vm},
rubysys::{exception::rb_eNameError, variable},
types::{Id, InternalValue, Value},
util,
};
pub fn global_get(name: &str) -> Value {
let name = util::str_to_cstring(name);
unsafe { variable::rb_gv_get(name.as_ptr()) }
}
pub fn global_set(name: &str, value: Value) -> Value {
let name = util::str_to_cstring(name);
unsafe { variable::rb_gv_set(name.as_ptr(), value) }
}
pub fn define_variable(name: &str, initial: Value, readonly: bool) -> *mut Value {
let name = util::str_to_cstring(name);
let address = Box::into_raw(Box::new(initial));
unsafe {
if readonly {
variable::rb_define_readonly_variable(name.as_ptr(), address);
} else {
variable::rb_define_variable(name.as_ptr(), address);
}
}
address
}
#[repr(C)]
struct VirtualVariable<G, S> {
marked: Value,
getter: G,
setter: Option<S>,
}
rutie_callback! {
fn virtual_getter<G, S>(_id: Id, data: *mut Value) -> Value
where
G: FnMut() -> Value,
{
let variable = unsafe { &mut *(data as *mut VirtualVariable<G, S>) };
vm::call_catching_panic(|| (variable.getter)())
}
}
rutie_callback! {
fn virtual_setter<G, S>(value: Value, id: Id, data: *mut Value)
where
S: FnMut(Value),
{
let variable = unsafe { &mut *(data as *mut VirtualVariable<G, S>) };
match variable.setter {
Some(ref mut setter) => vm::call_catching_panic(|| setter(value)),
None => {
let message = format!("{} is a read-only variable", symbol::id_to_string(id));
vm::raise_message(unsafe { rb_eNameError }, &message)
}
}
}
}
pub fn define_virtual_variable<G, S>(name: &str, getter: G, setter: Option<S>)
where
G: FnMut() -> Value + 'static,
S: FnMut(Value) + 'static,
{
let name = util::str_to_cstring(name);
let variable = Box::new(VirtualVariable {
marked: Value::from(RubySpecialConsts::Nil as InternalValue),
getter,
setter,
});
let data = Box::into_raw(variable) as *mut Value;
unsafe {
variable::rb_define_hooked_variable(
name.as_ptr(),
data,
Some(virtual_getter::<G, S>),
Some(virtual_setter::<G, S>),
)
}
}