Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working Exception and AnyException #93

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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"
4 changes: 4 additions & 0 deletions src/binding/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub fn raise(exception: Value, message: &str) {
}
}

pub fn raise_ex(exception: Value) {
unsafe { vm::rb_exc_raise(exception); }
}

pub fn thread_call_without_gvl<F, R, G>(func: F, unblock_func: Option<G>) -> R
where
F: FnOnce() -> R,
Expand Down
32 changes: 32 additions & 0 deletions src/class/any_exception.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use ::{Object, VerifiedObject, Exception};
use ::types::{Value, ValueType};

pub struct AnyException {
value: Value
}

impl From<Value> for AnyException {
fn from(value: Value) -> Self {
AnyException { value: value }
}
}

impl Object for AnyException {
#[inline]
fn value(&self) -> Value {
self.value
}
}

impl Exception for AnyException {}

impl VerifiedObject for AnyException {
fn is_correct_type<T: Object>(object: &T) -> bool {
object.value().ty() == ValueType::Class &&
object.respond_to("set_backtrace")
}

fn error_message() -> &'static str {
"Error converting to AnyException"
}
}
1 change: 1 addition & 0 deletions src/class/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod any_object;
pub mod any_exception;
pub mod array;
pub mod boolean;
pub mod class;
Expand Down
226 changes: 226 additions & 0 deletions src/class/traits/exception.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
use ::{AnyObject, Object, RString, Array, Class};
use binding::util as binding_util;

/// Descendants of class Exception are used to communicate between Kernel#raise
/// and rescue statements in `begin ... end` blocks. Exception objects carry
/// information about the exception – its type (the exception's class name), an
/// optional descriptive string, and optional traceback information. Exception
/// subclasses may add additional information like NameError#name.
///
/// Programs may make subclasses of Exception, typically of StandardError or
/// RuntimeError, to provide custom classes and add additional information.
/// See the subclass list below for defaults for `raise` and `rescue`.
pub trait Exception: Object {
/// Construct a new Exception object, optionally passing in a message.
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM};
/// # VM::init();
///
/// assert_eq!(
/// AnyException::new("StandardError", None).to_s(),
/// "StandardError"
/// );
/// ```
fn new(class: &str, msg: Option<&str>) -> Self {
let class = Class::from_existing(class);
let arguments = msg.map(|s| vec![RString::new(s).value()]);

Self::from(binding_util::call_method(class.value(), "new", arguments))
}

/// With no argument, or if the argument is the same as the receiver,
/// return the receiver. Otherwise, create a new exception object of
/// the same class as the receiver, but with a message equal
/// to `string.to_str`.
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM};
/// # VM::init();
///
/// assert_eq!(
/// AnyException::new("StandardError", Some("something went wrong")).to_s(),
/// "something went wrong"
/// );
/// ```
fn exception(&self, string: Option<&str>) -> Self {
let arguments = string.map(|s| vec![RString::new(s).value()]);

Self::from(binding_util::call_method(self.value(), "exception", arguments))
}

/// Returns any backtrace associated with the exception. The
/// backtrace is an array of strings, each containing either
/// “filename:lineNo: in `method''' or “filename:lineNo.''
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM, RString};
/// # VM::init();
///
/// let x = AnyException::new("StandardError", Some("something went wrong"));
///
/// assert!(x.backtrace().is_none());
/// ```
fn backtrace(&self) -> Option<Array> {
let result = binding_util::call_method(self.value(), "backtrace", None);

if result.is_nil() {
return None;
}

Some(Array::from(result))
}

/// Returns any backtrace associated with the exception. This
/// method is similar to #backtrace, but the backtrace is an
/// array of Thread::Backtrace::Location.
///
/// Now, this method is not affected by #set_backtrace.
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM, RString};
/// # VM::init();
///
/// let x = AnyException::new("StandardError", Some("something went wrong"));
///
/// assert!(x.backtrace_locations().is_none());
/// ```
fn backtrace_locations(&self) -> Option<Array> {
let result = binding_util::call_method(self.value(), "backtrace_locations", None);

if result.is_nil() {
return None;
}

Some(Array::from(result))
}

/// Returns the previous exception at the time this
/// exception was raised. This is useful for wrapping exceptions
/// and retaining the original exception information.
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM, RString};
/// # VM::init();
///
/// let x = AnyException::new("StandardError", Some("something went wrong"));
///
/// assert!(x.cause().is_none());
/// ```
fn cause(&self) -> Option<Self> {
let result = binding_util::call_method(self.value(), "cause", None);

if result.is_nil() {
return None;
}

Some(Self::from(result))
}

// TODO: calling `full_string` panics in Rust
// /// Returns formatted string of exception. The returned string is
// /// formatted using the same format that Ruby uses when printing an
// /// uncaught exceptions to stderr. So it may differ by `$stderr.tty?`
// /// at the timing of a call.
// ///
// /// # Examples
// /// ```
// /// use ruru::{AnyException, Exception, Object, VM};
// /// # VM::init();
// ///
// /// assert_eq!(
// /// AnyException::new("StandardError", Some("something went wrong")).full_string(),
// /// "StandardError: something went wrong"
// /// );
// /// ```
// fn full_string(&self) -> String {
// RString::from(binding_util::call_method(self.value(), "full_string", None)).to_string()
// }

/// Return this exception's class name and message
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM};
/// # VM::init();
///
/// assert_eq!(
/// AnyException::new("StandardError", Some("oops")).inspect(),
/// "#<StandardError: oops>"
/// );
/// ```
fn inspect(&self) -> String {
RString::from(binding_util::call_method(self.value(), "inspect", None)).to_string()
}

/// Returns the result of invoking `exception.to_s`. Normally this
/// returns the exception's message or name.
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM};
/// # VM::init();
///
/// assert_eq!(
/// AnyException::new("StandardError", Some("oops")).message(),
/// "oops"
/// );
/// ```
fn message(&self) -> String {
RString::from(binding_util::call_method(self.value(), "message", None)).to_string()
}

/// Sets the backtrace information associated with exc. The backtrace
/// must be an array of String objects or a single String in the format
/// described in #backtrace.
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM, RString, Array};
/// # VM::init();
///
/// let x = AnyException::new("StandardError", Some("something went wrong"));
///
/// x.set_backtrace(RString::new("prog.rb:10").to_any_object());
///
/// assert_eq!(
/// x.backtrace().
/// unwrap().
/// pop().
/// try_convert_to::<RString>().
/// unwrap().
/// to_string(),
/// "prog.rb:10"
/// );
/// ```
fn set_backtrace(&self, backtrace: AnyObject) -> Option<Array> {
let result = binding_util::call_method(self.value(), "set_backtrace", Some(vec![backtrace.value()]));

if result.is_nil() {
return None;
}

Some(Array::from(result))
}

/// Returns exception's message (or the name of the exception if no message is set).
///
/// # Examples
/// ```
/// use ruru::{AnyException, Exception, Object, VM};
/// # VM::init();
///
/// assert_eq!(
/// AnyException::new("StandardError", Some("oops")).to_s(),
/// "oops"
/// );
/// ```
fn to_s(&self) -> String {
RString::from(binding_util::call_method(self.value(), "to_s", None)).to_string()
}
}
1 change: 1 addition & 0 deletions src/class/traits/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod object;
pub mod exception;
pub mod verified_object;
47 changes: 46 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, AnyException, Class, Object, Proc};

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

/// Raises an exception from a native `AnyException` object.
///
/// # Examples
///
/// ### Built-in exceptions
///
/// ```no_run
/// use ruru::{Class, VM, Exception, AnyException};
/// # VM::init();
///
/// VM::raise_ex(AnyException::new("StandardError", Some("something went wrong")));
/// ```
///
/// Ruby:
///
/// ```ruby
/// raise StandardError, 'something went wrong'
/// ```
///
/// ### Custom exceptions
///
/// ```no_run
/// use ruru::{Class, VM, Exception, AnyException};
/// # VM::init();
///
/// let standard_error = Class::from_existing("StandardError");
/// Class::new("CustomException", Some(&standard_error));
///
/// let exception = AnyException::new("CustomException", Some("something went wrong"));
///
/// VM::raise_ex(exception);
/// ```
///
/// Ruby:
///
/// ```ruby
/// class CustomException < StandardError
/// end
///
/// raise CustomException, 'Something went wrong'
/// ```
pub fn raise_ex(exception: AnyException) {
vm::raise_ex(exception.value());
}

/// Converts a block given to current method to a `Proc`
///
/// It works similarly to `def method(&block)` which converts block to `Proc`
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod typed_data;
pub mod types;
pub mod util;

pub use class::any_exception::AnyException;
pub use class::any_object::AnyObject;
pub use class::array::Array;
pub use class::boolean::Boolean;
Expand All @@ -32,6 +33,7 @@ pub use class::thread::Thread;
pub use class::vm::VM;

pub use class::traits::object::Object;
pub use class::traits::exception::Exception;
pub use class::traits::verified_object::VerifiedObject;

#[test]
Expand Down