mirror of https://github.com/tildeio/helix
Introduce safe function calls into Ruby
This commit is contained in:
parent
e8c6581203
commit
2cb2a8c6f9
|
@ -6,6 +6,9 @@ extern crate libc;
|
|||
use std::ffi::CStr;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub const PKG_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub fn check_version() {
|
||||
|
@ -36,6 +39,17 @@ unsafe impl Sync for ID {}
|
|||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct VALUE(*mut void);
|
||||
|
||||
impl VALUE {
|
||||
pub fn wrap(ptr: *mut void) -> VALUE {
|
||||
VALUE(ptr)
|
||||
}
|
||||
|
||||
// Is this correct?
|
||||
pub fn as_ptr(&self) -> *mut void {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct RubyException(isize);
|
||||
|
@ -52,6 +66,14 @@ impl RubyException {
|
|||
pub fn for_tag(tag: isize) -> RubyException {
|
||||
RubyException(tag)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
pub fn state(&self) -> isize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub const EMPTY_EXCEPTION: RubyException = RubyException(0);
|
||||
|
@ -203,31 +225,25 @@ extern "C" {
|
|||
#[link_name = "HELIX_T_DATA"]
|
||||
pub static T_DATA: isize;
|
||||
|
||||
// unknown if working?
|
||||
// fn rb_define_variable(name: c_string, value: *const VALUE);
|
||||
// It doesn't appear that these functions will rb_raise. If it turns out they can, we
|
||||
// should make sure to safe wrap them.
|
||||
pub fn rb_obj_class(obj: VALUE) -> VALUE;
|
||||
pub fn rb_obj_classname(obj: VALUE) -> c_string;
|
||||
pub fn rb_const_get(class: VALUE, name: ID) -> VALUE;
|
||||
pub fn rb_define_global_const(name: c_string, value: VALUE);
|
||||
pub fn rb_define_module(name: c_string) -> VALUE;
|
||||
pub fn rb_define_module_under(namespace: VALUE, name: c_string) -> VALUE;
|
||||
pub fn rb_define_class(name: c_string, superclass: VALUE) -> VALUE;
|
||||
pub fn rb_define_class_under(namespace: VALUE, name: c_string, superclass: VALUE) -> VALUE;
|
||||
pub fn rb_define_alloc_func(class: VALUE, func: extern "C" fn(class: VALUE) -> VALUE);
|
||||
pub fn rb_define_method(class: VALUE, name: c_string, func: c_func, arity: isize);
|
||||
pub fn rb_define_singleton_method(class: VALUE, name: c_string, func: c_func, arity: isize);
|
||||
pub fn rb_intern(string: c_string) -> ID;
|
||||
pub fn rb_intern_str(string: VALUE) -> ID;
|
||||
|
||||
// FIXME: BEGIN: Review these for safe calls
|
||||
pub fn rb_undef_method(class: VALUE, name: c_string);
|
||||
pub fn rb_enc_get_index(obj: VALUE) -> isize;
|
||||
pub fn rb_utf8_encindex() -> isize;
|
||||
pub fn rb_sprintf(specifier: c_string, ...) -> VALUE;
|
||||
pub fn rb_inspect(value: VALUE) -> VALUE;
|
||||
pub fn rb_intern(string: c_string) -> ID;
|
||||
pub fn rb_intern_str(string: VALUE) -> ID;
|
||||
|
||||
pub fn rb_sym2id(symbol: VALUE) -> ID;
|
||||
pub fn rb_id2sym(id: ID) -> VALUE;
|
||||
pub fn rb_id2str(id: ID) -> VALUE;
|
||||
pub fn rb_ary_new() -> VALUE;
|
||||
pub fn rb_ary_new_capa(capa: isize) -> VALUE;
|
||||
pub fn rb_ary_new_from_values(n: isize, elts: *const VALUE) -> VALUE;
|
||||
pub fn rb_ary_entry(ary: VALUE, offset: isize) -> VALUE;
|
||||
pub fn rb_ary_push(ary: VALUE, item: VALUE) -> VALUE;
|
||||
pub fn rb_hash_new() -> VALUE;
|
||||
|
@ -235,35 +251,58 @@ extern "C" {
|
|||
pub fn rb_hash_aset(hash: VALUE, key: VALUE, value: VALUE) -> VALUE;
|
||||
pub fn rb_hash_foreach(hash: VALUE, f: extern "C" fn(key: VALUE, value: VALUE, farg: *mut void) -> st_retval, farg: *mut void);
|
||||
pub fn rb_gc_mark(value: VALUE);
|
||||
pub fn rb_funcall(value: VALUE, mid: ID, argc: libc::c_int, ...) -> VALUE;
|
||||
pub fn rb_funcallv(value: VALUE, mid: ID, argc: libc::c_int, argv: *const VALUE) -> VALUE;
|
||||
pub fn rb_scan_args(argc: libc::c_int, argv: *const VALUE, fmt: c_string, ...);
|
||||
pub fn rb_funcall(value: VALUE, mid: ID, argc: isize, ...) -> VALUE;
|
||||
pub fn rb_funcallv(value: VALUE, mid: ID, argc: isize, argv: *const VALUE) -> VALUE;
|
||||
pub fn rb_scan_args(argc: isize, argv: *const VALUE, fmt: c_string, ...);
|
||||
pub fn rb_block_given_p() -> bool;
|
||||
pub fn rb_yield(value: VALUE) -> VALUE;
|
||||
pub fn rb_obj_dup(value: VALUE) -> VALUE;
|
||||
pub fn rb_obj_init_copy(value: VALUE, orig: VALUE) -> VALUE;
|
||||
|
||||
pub fn rb_raise(exc: VALUE, string: c_string, ...) -> !;
|
||||
pub fn rb_jump_tag(state: RubyException) -> !;
|
||||
pub fn rb_protect(try: extern "C" fn(v: *mut void) -> VALUE,
|
||||
arg: *mut void,
|
||||
state: *mut RubyException)
|
||||
-> VALUE;
|
||||
|
||||
#[link_name = "HELIX_rb_str_valid_encoding_p"]
|
||||
pub fn rb_str_valid_encoding_p(string: VALUE) -> bool;
|
||||
|
||||
#[link_name = "HELIX_rb_str_ascii_only_p"]
|
||||
pub fn rb_str_ascii_only_p(string: VALUE) -> bool;
|
||||
// FIXME: END: Review these for safe calls
|
||||
|
||||
pub fn rb_raise(exc: VALUE, string: c_string, ...) -> !;
|
||||
pub fn rb_jump_tag(state: RubyException) -> !;
|
||||
|
||||
// In official Ruby docs, all of these *mut voids are actually VALUEs.
|
||||
// However, they are interchangeable in practice and using a *mut void allows us to pass
|
||||
// other things that aren't VALUEs
|
||||
pub fn rb_protect(try: extern "C" fn(v: *mut void) -> *mut void,
|
||||
arg: *mut void,
|
||||
state: *mut RubyException)
|
||||
-> *mut void;
|
||||
}
|
||||
|
||||
// These may not all be strictly necessary. If we're concerned about performance we can
|
||||
// audit and if we're sure that `rb_raise` won't be called we can avoid the safe wrapper
|
||||
ruby_safe_c! {
|
||||
rb_const_get(class: VALUE, name: ID) -> VALUE;
|
||||
rb_define_global_const(name: c_string, value: VALUE);
|
||||
rb_define_module(name: c_string) -> VALUE;
|
||||
rb_define_module_under(namespace: VALUE, name: c_string) -> VALUE;
|
||||
rb_define_class(name: c_string, superclass: VALUE) -> VALUE;
|
||||
rb_define_class_under(namespace: VALUE, name: c_string, superclass: VALUE) -> VALUE;
|
||||
rb_define_alloc_func(klass: VALUE, func: extern "C" fn(klass: VALUE) -> VALUE);
|
||||
rb_define_method(class: VALUE, name: c_string, func: c_func, arity: isize);
|
||||
rb_define_singleton_method(class: VALUE, name: c_string, func: c_func, arity: isize);
|
||||
rb_inspect(value: VALUE) -> VALUE;
|
||||
|
||||
#[link_name = "HELIX_Data_Wrap_Struct"]
|
||||
pub fn Data_Wrap_Struct(klass: VALUE, mark: extern "C" fn(*mut void), free: extern "C" fn(*mut void), data: *mut void) -> VALUE;
|
||||
Data_Wrap_Struct(klass: VALUE, mark: extern "C" fn(*mut void), free: extern "C" fn(*mut void), data: *mut void) -> VALUE;
|
||||
|
||||
#[link_name = "HELIX_Data_Get_Struct_Value"]
|
||||
pub fn Data_Get_Struct_Value(obj: VALUE) -> *mut void;
|
||||
Data_Get_Struct_Value(obj: VALUE) -> *mut void {
|
||||
fn ret_to_ptr(ret: *mut void) -> *mut void { ret }
|
||||
fn ptr_to_ret(ptr: *mut void) -> *mut void { ptr }
|
||||
}
|
||||
|
||||
#[link_name = "HELIX_Data_Set_Struct_Value"]
|
||||
pub fn Data_Set_Struct_Value(obj: VALUE, data: *mut void);
|
||||
Data_Set_Struct_Value(obj: VALUE, data: *mut void);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
// TODO: See if we can simplify
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! ruby_extern_fns {
|
||||
// We don't need the body here
|
||||
{ #[$attr:meta] $name:ident($( $argn:ident: $argt:ty ),*) -> $ret:ty { $($_body:tt)* } $($rest:tt)* } => {
|
||||
ruby_extern_fns! { #[$attr] $name($( $argn: $argt ),*) -> $ret; $($rest)* } };
|
||||
|
||||
{ #[$attr:meta] $name:ident($( $argn:ident: $argt:ty ),*) -> $ret:ty; $($rest:tt)* } => {
|
||||
#[cfg_attr(windows, link(name="helix-runtime"))]
|
||||
extern "C" {
|
||||
#[$attr]
|
||||
pub fn $name($($argn: $argt),*) -> $ret;
|
||||
}
|
||||
ruby_extern_fns! { $($rest)* }
|
||||
};
|
||||
{ #[$attr:meta] $name:ident($( $argn:ident: $argt:ty ),*); $($rest:tt)* } => {
|
||||
#[cfg_attr(windows, link(name="helix-runtime"))]
|
||||
extern "C" {
|
||||
#[$attr]
|
||||
pub fn $name($($argn: $argt),*);
|
||||
}
|
||||
ruby_extern_fns! { $($rest)* }
|
||||
};
|
||||
|
||||
// We don't need the body here
|
||||
{ $name:ident($( $argn:ident: $argt:ty ),*) -> $ret:ty { $($_body:tt)* } $($rest:tt)* } => {
|
||||
ruby_extern_fns! { $name($( $argn: $argt ),*) -> $ret; $($rest)* } };
|
||||
|
||||
{ $name:ident($( $argn:ident: $argt:ty ),*) -> $ret:ty; $($rest:tt)* } => {
|
||||
#[cfg_attr(windows, link(name="helix-runtime"))]
|
||||
extern "C" { pub fn $name($($argn: $argt),*) -> $ret; }
|
||||
ruby_extern_fns! { $($rest)* }
|
||||
};
|
||||
{ $name:ident($( $argn:ident: $argt:ty ),*); $($rest:tt)* } => {
|
||||
#[cfg_attr(windows, link(name="helix-runtime"))]
|
||||
extern "C" { pub fn $name($($argn: $argt),*); }
|
||||
ruby_extern_fns! { $($rest)* }
|
||||
};
|
||||
|
||||
{ } => ()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! ruby_safe_fn {
|
||||
{ $name:ident($( $argn:ident: $argt:ty ),*) -> $ret:ty { $($funcs:tt)+ } } => {
|
||||
pub fn $name($( $argn: $argt ),*) -> Result<$ret, $crate::RubyException> {
|
||||
// FIXME: Avoid creating args struct if there are no args
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Args {
|
||||
pub $($argn: $argt),*
|
||||
};
|
||||
|
||||
let args = Args { $($argn: $argn),* };
|
||||
|
||||
// Must include ret_to_ptr and ptr_to_ret
|
||||
$($funcs)+
|
||||
|
||||
extern "C" fn cb(args_ptr: *mut $crate::void) -> *mut $crate::void {
|
||||
let ret = unsafe {
|
||||
let args: &Args = &*(args_ptr as *const Args);
|
||||
$crate::$name($( args.$argn ),*)
|
||||
};
|
||||
ret_to_ptr(ret)
|
||||
}
|
||||
|
||||
let mut state = $crate::EMPTY_EXCEPTION;
|
||||
|
||||
let res = unsafe {
|
||||
let args_ptr: *mut $crate::void = &args as *const _ as *mut $crate::void;
|
||||
$crate::rb_protect(cb, args_ptr, &mut state)
|
||||
};
|
||||
|
||||
if !state.is_empty() {
|
||||
Err(state)
|
||||
} else {
|
||||
Ok(ptr_to_ret(res))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! ruby_safe_fns {
|
||||
// We don't need the meta here
|
||||
{ #[$attr:meta] $($rest:tt)* } => {
|
||||
ruby_safe_fns! { $($rest)* }
|
||||
};
|
||||
|
||||
// It's not quite ideal to have to define each type separately, but the coercions are different
|
||||
{ $name:ident($( $argn:ident: $argt:ty ),*) -> VALUE; $($rest:tt)* } => {
|
||||
ruby_safe_fn! {
|
||||
$name($( $argn: $argt ),*) -> $crate::VALUE {
|
||||
fn ret_to_ptr(ret: $crate::VALUE) -> *mut $crate::void { ret.as_ptr() }
|
||||
fn ptr_to_ret(ptr: *mut $crate::void) -> $crate::VALUE { $crate::VALUE::wrap(ptr) }
|
||||
}
|
||||
}
|
||||
|
||||
ruby_safe_fns! { $($rest)* }
|
||||
};
|
||||
|
||||
{ $name:ident($( $argn:ident: $argt:ty ),*) -> $ret:ty { $($conv:tt)* } $($rest:tt)* } => {
|
||||
ruby_safe_fn! {
|
||||
$name($( $argn: $argt ),*) -> $ret { $($conv)* }
|
||||
}
|
||||
|
||||
ruby_safe_fns! { $($rest)* }
|
||||
};
|
||||
|
||||
{ $name:ident($( $argn:ident: $argt:ty ),*); $($rest:tt)* } => {
|
||||
ruby_safe_fn! {
|
||||
$name($( $argn: $argt ),*) -> () {
|
||||
fn ret_to_ptr(_: ()) -> *mut $crate::void { unsafe { $crate::Qnil }.as_ptr() }
|
||||
fn ptr_to_ret(_: *mut $crate::void) { }
|
||||
}
|
||||
}
|
||||
|
||||
ruby_safe_fns! { $($rest)* }
|
||||
};
|
||||
|
||||
{ } => ()
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ruby_safe_c {
|
||||
{ $($parts:tt)+ } => {
|
||||
ruby_extern_fns! {
|
||||
$($parts)+
|
||||
}
|
||||
|
||||
pub mod safe {
|
||||
use $crate::*;
|
||||
|
||||
ruby_safe_fns! {
|
||||
$($parts)+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +1,8 @@
|
|||
require 'helix_runtime'
|
||||
require 'console/native'
|
||||
|
||||
class Console
|
||||
def ruby_hello
|
||||
"hello"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -65,4 +65,15 @@ describe "Console" do
|
|||
expect { console.log(str) }.to raise_error(TypeError, "Expected a valid UTF-8 String, got #{str.inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
it "can handle calls back to Ruby" do
|
||||
expect(console.call_ruby).to eq("\"Object\", true, true")
|
||||
end
|
||||
|
||||
it "can handle invalid calls back to Ruby" do
|
||||
# NOTE: This doesn't verify that Rust unwound correctly
|
||||
expect {
|
||||
console.behave_badly
|
||||
}.to raise_error(NameError, "undefined method `does_not_exist' for Object:Class");
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'console'
|
|||
|
||||
module PrintMatchers
|
||||
def print(expected = nil)
|
||||
output(expected = nil).to_stdout_from_any_process
|
||||
output(expected).to_stdout_from_any_process
|
||||
end
|
||||
|
||||
def println(expected)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate helix;
|
||||
use helix::{sys,FromRuby};
|
||||
|
||||
ruby! {
|
||||
#[derive(Debug)]
|
||||
|
@ -53,5 +54,16 @@ ruby! {
|
|||
def panic(&self) {
|
||||
panic!("raised from Rust with `panic`");
|
||||
}
|
||||
|
||||
def behave_badly(&self) {
|
||||
ruby_funcall!(sys::rb_cObject, "does_not_exist", String::from("one"));
|
||||
}
|
||||
|
||||
def call_ruby(&self) -> String {
|
||||
let a = ruby_funcall!(sys::rb_cObject, "name"); // No arg
|
||||
let b = ruby_funcall!(sys::rb_cObject, "is_a?", sys::rb_cObject); // One arg
|
||||
let c = ruby_funcall!(sys::rb_cObject, "respond_to?", String::from("inspect"), true); // Two args
|
||||
format!("{:?}, {:?}, {:?}", String::from_ruby_unwrap(a), bool::from_ruby_unwrap(b), bool::from_ruby_unwrap(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,45 +28,39 @@ pub struct ClassDefinition {
|
|||
|
||||
impl ClassDefinition {
|
||||
pub fn new(name: c_string) -> ClassDefinition {
|
||||
let raw_class = unsafe { sys::rb_define_class(name, sys::rb_cObject) };
|
||||
let raw_class = ruby_try!(sys::safe::rb_define_class(name, unsafe { sys::rb_cObject }));
|
||||
ClassDefinition { class: Class(raw_class) }
|
||||
}
|
||||
|
||||
pub fn wrapped(name: c_string, alloc_func: extern "C" fn(klass: sys::VALUE) -> sys::VALUE) -> ClassDefinition {
|
||||
let raw_class = unsafe { sys::rb_define_class(name, sys::rb_cObject) };
|
||||
unsafe { sys::rb_define_alloc_func(raw_class, alloc_func) };
|
||||
let raw_class = ruby_try!(sys::safe::rb_define_class(name, unsafe { sys::rb_cObject }));
|
||||
ruby_try!(sys::safe::rb_define_alloc_func(raw_class, alloc_func));
|
||||
ClassDefinition { class: Class(raw_class) }
|
||||
}
|
||||
|
||||
pub fn reopen(name: c_string) -> ClassDefinition {
|
||||
let raw_class = unsafe {
|
||||
let class_id = sys::rb_intern(name);
|
||||
sys::rb_const_get(sys::rb_cObject, class_id)
|
||||
};
|
||||
let class_id = unsafe { sys::rb_intern(name) };
|
||||
let raw_class = ruby_try!(sys::safe::rb_const_get(unsafe { sys::rb_cObject }, class_id));
|
||||
ClassDefinition { class: Class(raw_class) }
|
||||
}
|
||||
|
||||
pub fn define_method(&self, def: MethodDefinition) {
|
||||
match def {
|
||||
MethodDefinition::Instance(def) => {
|
||||
unsafe {
|
||||
sys::rb_define_method(
|
||||
self.class.0,
|
||||
def.name,
|
||||
def.function,
|
||||
def.arity
|
||||
);
|
||||
};
|
||||
ruby_try!(sys::safe::rb_define_method(
|
||||
self.class.0,
|
||||
def.name,
|
||||
def.function,
|
||||
def.arity
|
||||
));
|
||||
},
|
||||
MethodDefinition::Class(def) => {
|
||||
unsafe {
|
||||
sys::rb_define_singleton_method(
|
||||
self.class.0,
|
||||
def.name,
|
||||
def.function,
|
||||
def.arity
|
||||
);
|
||||
};
|
||||
ruby_try!(sys::safe::rb_define_singleton_method(
|
||||
self.class.0,
|
||||
def.name,
|
||||
def.function,
|
||||
def.arity
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ mod slice;
|
|||
mod vec;
|
||||
mod hash;
|
||||
|
||||
use sys::{VALUE};
|
||||
use sys::VALUE;
|
||||
use super::{Error, ToError};
|
||||
use std::marker::{PhantomData, Sized};
|
||||
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
use super::{Class, ToRuby};
|
||||
use std::{any, fmt};
|
||||
use sys::{VALUE, SPRINTF_TO_S, c_string, rb_eRuntimeError, rb_raise};
|
||||
use sys::{VALUE, RubyException, SPRINTF_TO_S, c_string, rb_eRuntimeError, rb_raise, rb_jump_tag};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Error {
|
||||
class: Class,
|
||||
message: ErrorMessage
|
||||
pub enum Error {
|
||||
Library { class: Class, message: ErrorMessage },
|
||||
Ruby(RubyException)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum ErrorMessage {
|
||||
pub enum ErrorMessage {
|
||||
Static(c_string),
|
||||
Dynamic(VALUE)
|
||||
}
|
||||
|
||||
impl Error {
|
||||
// Currently unused
|
||||
pub fn with_c_string(message: c_string) -> Error {
|
||||
Error { class: unsafe { Class(rb_eRuntimeError) }, message: ErrorMessage::Static(message) }
|
||||
Error::Library { class: unsafe { Class(rb_eRuntimeError) }, message: ErrorMessage::Static(message) }
|
||||
}
|
||||
|
||||
pub fn with_value(message: VALUE) -> Error {
|
||||
Error { class: unsafe { Class(rb_eRuntimeError) }, message: ErrorMessage::Dynamic(message) }
|
||||
Error::Library { class: unsafe { Class(rb_eRuntimeError) }, message: ErrorMessage::Dynamic(message) }
|
||||
}
|
||||
|
||||
// TODO: Can we use a trait for this?
|
||||
pub fn from_ruby(exception: RubyException) -> Error {
|
||||
Error::Ruby(exception)
|
||||
}
|
||||
|
||||
pub fn from_any(any: Box<any::Any>) -> Error {
|
||||
|
@ -32,27 +38,39 @@ impl Error {
|
|||
}
|
||||
|
||||
pub fn with_class(self, class: Class) -> Error {
|
||||
Error { class, message: self.message }
|
||||
match self {
|
||||
Error::Library { message, .. } => Error::Library { class, message },
|
||||
_ => panic!("Only supported for Error::Library")
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn raise(self) -> ! {
|
||||
match self.message {
|
||||
ErrorMessage::Static(c_string) => rb_raise(self.class.to_value(), c_string),
|
||||
ErrorMessage::Dynamic(value) => rb_raise(self.class.to_value(), SPRINTF_TO_S, value)
|
||||
match self {
|
||||
Error::Library { class, message } => match message {
|
||||
ErrorMessage::Static(c_string) => rb_raise(class.to_value(), c_string),
|
||||
ErrorMessage::Dynamic(value) => rb_raise(class.to_value(), SPRINTF_TO_S, value)
|
||||
},
|
||||
Error::Ruby(exception) => rb_jump_tag(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.message {
|
||||
ErrorMessage::Static(c_string) => {
|
||||
use ::std::ffi::CStr;
|
||||
write!(f, "{}", unsafe { CStr::from_ptr(c_string) }.to_str().unwrap())
|
||||
match *self {
|
||||
Error::Library { message, .. } => match message {
|
||||
ErrorMessage::Static(c_string) => {
|
||||
use ::std::ffi::CStr;
|
||||
write!(f, "{}", unsafe { CStr::from_ptr(c_string) }.to_str().unwrap())
|
||||
},
|
||||
ErrorMessage::Dynamic(value) => {
|
||||
use super::FromRuby;
|
||||
write!(f, "{}", String::from_ruby_unwrap(value))
|
||||
}
|
||||
},
|
||||
ErrorMessage::Dynamic(value) => {
|
||||
use super::FromRuby;
|
||||
write!(f, "{}", String::from_ruby_unwrap(value))
|
||||
Error::Ruby(_exception) => {
|
||||
// FIXME: Implement properly
|
||||
write!(f, "Ruby Exception")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,3 +100,9 @@ impl ToError for String {
|
|||
Error::with_value(self.to_ruby().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToError for RubyException {
|
||||
fn to_error(self) -> Error {
|
||||
Error::from_ruby(self)
|
||||
}
|
||||
}
|
||||
|
|
34
src/lib.rs
34
src/lib.rs
|
@ -46,10 +46,12 @@ macro_rules! type_error {
|
|||
};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod class_definition;
|
||||
mod coercions;
|
||||
mod errors;
|
||||
mod macros;
|
||||
|
||||
pub use coercions::*;
|
||||
pub use errors::*;
|
||||
|
@ -95,27 +97,23 @@ pub trait RubyMethod {
|
|||
|
||||
impl RubyMethod for extern "C" fn(VALUE) -> VALUE {
|
||||
fn install(self, class: VALUE, name: &CStr) {
|
||||
unsafe {
|
||||
sys::rb_define_method(
|
||||
class,
|
||||
name.as_ptr(),
|
||||
self as *const libc::c_void,
|
||||
0
|
||||
);
|
||||
}
|
||||
ruby_try!(sys::safe::rb_define_method(
|
||||
class,
|
||||
name.as_ptr(),
|
||||
self as *const libc::c_void,
|
||||
0
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl RubyMethod for extern "C" fn(VALUE, VALUE) -> VALUE {
|
||||
fn install(self, class: VALUE, name: &CStr) {
|
||||
unsafe {
|
||||
sys::rb_define_method(
|
||||
class,
|
||||
name.as_ptr(),
|
||||
self as *const libc::c_void,
|
||||
1
|
||||
);
|
||||
}
|
||||
ruby_try!(sys::safe::rb_define_method(
|
||||
class,
|
||||
name.as_ptr(),
|
||||
self as *const libc::c_void,
|
||||
1
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +148,7 @@ impl Class {
|
|||
}
|
||||
|
||||
pub fn inspect(val: VALUE) -> String {
|
||||
unsafe { String::from_ruby_unwrap(sys::rb_inspect(val)) }
|
||||
String::from_ruby_unwrap(ruby_try!(sys::safe::rb_inspect(val)))
|
||||
}
|
||||
|
||||
pub unsafe fn as_usize(value: ::VALUE) -> usize {
|
||||
|
|
|
@ -13,6 +13,9 @@ mod coercions;
|
|||
#[macro_use]
|
||||
mod alloc;
|
||||
|
||||
#[macro_use]
|
||||
mod safe;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ruby {
|
||||
{ $($rest:tt)* } => {
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// TODO: Can we change this to use the macro from libcruby?
|
||||
#[macro_export]
|
||||
macro_rules! ruby_try {
|
||||
{ $val:expr } => { $val.unwrap_or_else(|e| panic!($crate::Error::from_ruby(e)) ) }
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ruby_funcall {
|
||||
// NOTE: Class and method cannot be variables. If that becomes necessary, I think we'll have to pass them
|
||||
($rb_class:expr, $meth:expr, $( $arg:expr ),*) => {
|
||||
{
|
||||
use $crate::ToRuby;
|
||||
|
||||
// This method takes a Ruby Array of arguments
|
||||
// If there is a way to make this behave like a closure, we could further simplify things.
|
||||
#[allow(unused_variables)]
|
||||
extern "C" fn __ruby_funcall_cb(arg_ary: *mut $crate::sys::void) -> *mut $crate::sys::void {
|
||||
unsafe {
|
||||
// Is this safe here?
|
||||
let arg_ary = $crate::sys::VALUE::wrap(arg_ary);
|
||||
// NOTE: We're using rb_intern_str, not rb_intern in the hopes that this means
|
||||
// Ruby will clean up the string in the event that there is an exception
|
||||
$crate::sys::rb_funcallv($rb_class, sys::rb_intern_str(String::from($meth).to_ruby().expect("valid string")),
|
||||
$crate::sys::RARRAY_LEN(arg_ary), $crate::sys::RARRAY_PTR(arg_ary)).as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = $crate::sys::EMPTY_EXCEPTION;
|
||||
|
||||
let res = unsafe {
|
||||
let mut values_ary: Vec<$crate::sys::VALUE> = Vec::new();
|
||||
$(
|
||||
// We have to create this iteratively since we have to call to_ruby individually
|
||||
values_ary.push($arg.to_ruby().expect("could convert to Ruby"));
|
||||
)*
|
||||
let ruby_values_ary = $crate::sys::rb_ary_new_from_values(values_ary.len() as isize, values_ary.as_mut_ptr());
|
||||
$crate::sys::rb_protect(__ruby_funcall_cb, ruby_values_ary.as_ptr(), &mut state)
|
||||
};
|
||||
|
||||
if !state.is_empty() {
|
||||
panic!($crate::Error::from_ruby(state));
|
||||
}
|
||||
|
||||
$crate::sys::VALUE::wrap(res)
|
||||
}
|
||||
};
|
||||
|
||||
($rb_class:expr, $meth:expr) => {
|
||||
ruby_funcall!($rb_class, $meth, )
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue