Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ keywords = ["cruby", "mri", "ruby", "ruru"]
license = "MIT"

[dependencies]
ruby-sys = "0.3.0"
ruby-sys = { git = "https://github.com/danielpclark/ruby-sys", branch = "playground" }
lazy_static = "0.2.1"
8 changes: 8 additions & 0 deletions src/binding/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ pub fn call_method(receiver: Value, method: &str, arguments: Option<Vec<Value>>)
// TODO: Update the signature of `rb_funcallv` in ruby-sys to receive an `Option`
unsafe { ruby_sys_util::rb_funcallv(receiver, method_id, argc, argv) }
}

pub fn call_public_method(receiver: Value, method: &str, arguments: Option<Vec<Value>>) -> Value {
let (argc, argv) = util::process_arguments(&arguments);
let method_id = internal_id(method);

// TODO: Update the signature of `rb_funcallv_public` in ruby-sys to receive an `Option`
unsafe { ruby_sys_util::rb_funcallv_public(receiver, method_id, argc, argv) }
}
37 changes: 35 additions & 2 deletions src/binding/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ pub fn require(name: &str) {
}
}

// "evaluation can raise an exception."
pub fn eval_string(string: &str) -> Value {
let s = util::str_to_cstring(string);

unsafe {
vm::rb_eval_string(s.as_ptr())
}
}

pub fn eval_string_protect(string: &str) -> Result<Value, c_int> {
let s = util::str_to_cstring(string);
let mut state = 0;
let value = unsafe {
vm::rb_eval_string_protect(
s.as_ptr(),
&mut state as *mut c_int
)
};
if state == 0 {
Ok(value)
} else {
Err(state)
}
}

pub fn raise(exception: Value, message: &str) {
let message = util::str_to_cstring(message);

Expand All @@ -37,6 +62,14 @@ pub fn raise(exception: Value, message: &str) {
}
}

pub fn errinfo() -> Value {
unsafe { vm::rb_errinfo() }
}

pub fn set_errinfo(err: Value) {
unsafe { vm::rb_set_errinfo(err) }
}

pub fn thread_call_without_gvl<F, R, G>(func: F, unblock_func: Option<G>) -> R
where
F: FnOnce() -> R,
Expand Down Expand Up @@ -108,9 +141,9 @@ extern "C" fn callbox(boxptr: *mut c_void) -> *const c_void {
fnbox()
}

pub fn protect<F>(func: F) -> Result<Value, c_int>
pub fn protect<F, R>(func: F) -> Result<Value, c_int>
where
F: FnOnce(),
F: FnOnce() -> R,
{
let mut state = 0;
let value = unsafe {
Expand Down
135 changes: 132 additions & 3 deletions src/class/traits/object.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use std::convert::From;

use binding::class;
use binding::vm;
use binding::global::ValueType;
use binding::util as binding_util;
use result::{Error, Result};
use result::{Error, Result as RuruResult};
use typed_data::DataTypeWrapper;
use types::{Callback, Value};
use util;

use {AnyObject, Boolean, Class, VerifiedObject};
use {AnyObject, Boolean, Class, NilClass, VerifiedObject};

/// `Object`
///
Expand Down Expand Up @@ -613,6 +614,134 @@ pub trait Object: From<Value> {
class::respond_to(self.value(), method)
}

/// `protect_send` returns Result<AnyObject, AnyObject>
///
/// Protects against crash with `send` when exception object raised which will
/// be returned in the `Err` result.
///
/// # Examples
///
/// ```
/// use ruru::{RString, Fixnum, Object, Class, VM};
/// # VM::init();
///
/// let kernel = Class::from_existing("Kernel");
///
/// let result = kernel.protect_send("nil?".to_string(), None);
///
/// if let Ok(r) = result {
/// assert!(!r.value().is_true());
/// } else {
/// unreachable!()
/// }
///
/// let kernel = Class::from_existing("Kernel");
///
/// let result = kernel.protect_send(
/// "raise".to_string(),
/// Some(&vec![RString::new("flowers").to_any_object()])
/// );
///
/// if let Err(error) = result {
/// assert_eq!(
/// Class::from(error.value()).
/// send("message", None).
/// try_convert_to::<RString>().
/// ok().
/// unwrap().
/// to_str(),
/// "flowers"
/// );
/// } else {
/// unreachable!()
/// }
/// ```
fn protect_send(&self, method: String, arguments: Option<&[AnyObject]>) -> Result<AnyObject, AnyObject> {
let v = self.value();
let arguments = util::arguments_to_values(arguments);

let closure = move || {
binding_util::call_method(v, &method, arguments)
};

let result = vm::protect(closure);

if let Ok(value) = result {
Ok(AnyObject::from(value))
} else {
let output = Err(AnyObject::from(vm::errinfo()));

// error cleanup
vm::set_errinfo(NilClass::new().value());

output
}
}

/// `protect_public_send` returns Result<AnyObject, AnyObject>
///
/// Protects against crash with `public_send` when exception object raised which will
/// be returned in the `Err` result.
///
/// # Examples
///
/// ```
/// use ruru::{RString, Fixnum, Object, Class, VM};
/// # VM::init();
///
/// let kernel = Class::from_existing("Kernel");
///
/// let result = kernel.protect_public_send("nil?".to_string(), None);
///
/// if let Ok(r) = result {
/// assert!(!r.value().is_true());
/// } else {
/// unreachable!()
/// }
///
/// let kernel = Class::from_existing("Kernel");
///
/// let result = kernel.protect_public_send(
/// "raise".to_string(),
/// Some(&vec![RString::new("flowers").to_any_object()])
/// );
///
/// if let Err(error) = result {
/// assert_eq!(
/// Class::from(error.value()).
/// send("message", None).
/// try_convert_to::<RString>().
/// ok().
/// unwrap().
/// to_str(),
/// "flowers"
/// );
/// } else {
/// unreachable!()
/// }
/// ```
fn protect_public_send(&self, method: String, arguments: Option<&[AnyObject]>) -> Result<AnyObject, AnyObject> {
let v = self.value();
let arguments = util::arguments_to_values(arguments);

let closure = move || {
binding_util::call_public_method(v, &method, arguments)
};

let result = vm::protect(closure);

if let Ok(value) = result {
Ok(AnyObject::from(value))
} else {
let output = Err(AnyObject::from(vm::errinfo()));

// error cleanup
vm::set_errinfo(NilClass::new().value());

output
}
}

/// Checks whether the object is `nil`
///
/// # Examples
Expand Down Expand Up @@ -1009,7 +1138,7 @@ pub trait Object: From<Value> {
/// end
/// end
/// ```
fn try_convert_to<T: VerifiedObject>(&self) -> Result<T> {
fn try_convert_to<T: VerifiedObject>(&self) -> RuruResult<T> {
if T::is_correct_type(self) {
let converted_object = unsafe { self.to::<T>() };

Expand Down
101 changes: 100 additions & 1 deletion src/class/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::slice;
use binding::vm;
use types::{Argc, Value};

use {AnyObject, Class, Object, Proc};
use {AnyObject, Class, Object, Proc, NilClass};

/// Virtual Machine and helpers
pub struct VM;
Expand Down Expand Up @@ -102,6 +102,105 @@ impl VM {
vm::raise(exception.value(), message);
}

/// Evals string and returns an Result<AnyObject, c_int>
///
/// # Examples
///
/// ```
/// #[macro_use]
/// extern crate ruru;
///
/// use ruru::{Class, Fixnum, Object, VM};
///
/// fn main() {
/// # VM::init();
///
/// // Successful example
///
/// let result = VM::eval("2+2").ok().unwrap().try_convert_to::<Fixnum>();
///
/// assert_eq!(result, Ok(Fixnum::new(4)));
///
/// // Error example
///
/// let result = VM::eval("raise 'flowers'");
///
/// assert!(result.is_err());
/// }
/// ```
///
/// `Err` will return an `AnyObject` of the exception class raised.
///
///
/// ```
/// #[macro_use]
/// extern crate ruru;
///
/// use ruru::{Class, Fixnum, Object, RString, VM};
///
/// fn main() {
/// # VM::init();
///
/// let result = VM::eval("raise IndexError, 'flowers'");
///
/// match result {
/// Err(ao) => {
/// let err = Class::from(ao.value());
/// let message = err.send("message", None);
/// let s = message.try_convert_to::<RString>();
/// assert_eq!(s.ok().unwrap().to_string(), "flowers");
/// },
/// _ => { unreachable!() }
/// }
/// }
/// ```
///
/// Be aware when checking for equality amongst types like strings, that even
/// with the same content in Ruby, they will evaluate to different values in
/// C/Rust.
pub fn eval(string: &str) -> Result<AnyObject, AnyObject> {
vm::eval_string_protect(string).map(|v|
AnyObject::from(v)
).map_err(|_| {
let output = AnyObject::from(vm::errinfo());

// error cleanup
vm::set_errinfo(NilClass::new().value());

output
})
}

/// Evals string and returns an AnyObject
///
/// # Examples
///
/// ```
/// #[macro_use]
/// extern crate ruru;
///
/// use ruru::{Class, Fixnum, Object, VM};
///
/// fn main() {
/// # VM::init();
///
/// let result = unsafe { VM::eval_str("2+2").try_convert_to::<Fixnum>() };
///
/// assert_eq!(result, Ok(Fixnum::new(4)));
/// }
/// ```
///
/// Be aware when checking for equality amongst types like strings, that even
/// with the same content in Ruby, they will evaluate to different values in
/// C/Rust.
///
/// Marked unsafe because "evaluation can raise an exception."
pub unsafe fn eval_str(string: &str) -> AnyObject {
AnyObject::from(
vm::eval_string(string)
)
}

/// Converts a block given to current method to a `Proc`
///
/// It works similarly to `def method(&block)` which converts block to `Proc`
Expand Down