summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authortake_cheeze <[email protected]>2015-06-22 00:05:45 +0900
committerYukihiro "Matz" Matsumoto <[email protected]>2015-07-15 14:46:56 +0900
commitcdd72d9c3783749c979edf62522cf9636681538d (patch)
treefbedcf59fc7176d7e6b82adf20170e44b3787a98
parent2e4bc2de8889a321fbde9898bc9afc9daefd8f76 (diff)
downloadmruby-cdd72d9c3783749c979edf62522cf9636681538d.tar.gz
mruby-cdd72d9c3783749c979edf62522cf9636681538d.zip
Implement `mrb_protect`, `mrb_ensure`, `mrb_rescue`, `mrb_rescue_exceptions`.
(`mrb_rescue_exceptions` is mruby implementation of `rb_rescue2`.) Closes #2844, closes #2837.
-rw-r--r--include/mruby/error.h8
-rw-r--r--mrbgems/mruby-error/mrbgem.rake10
-rw-r--r--mrbgems/mruby-error/src/exception.c102
-rw-r--r--mrbgems/mruby-error/test/exception.c57
-rw-r--r--mrbgems/mruby-error/test/exception.rb53
5 files changed, 230 insertions, 0 deletions
diff --git a/include/mruby/error.h b/include/mruby/error.h
index 282be0a24..3a985236a 100644
--- a/include/mruby/error.h
+++ b/include/mruby/error.h
@@ -29,6 +29,14 @@ MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_va
/* declaration for fail method */
MRB_API mrb_value mrb_f_raise(mrb_state*, mrb_value);
+MRB_API mrb_value mrb_protect(mrb_state *mrb, mrb_func_t body, mrb_value data, mrb_bool *state);
+MRB_API mrb_value mrb_ensure(mrb_state *mrb, mrb_func_t body, mrb_value b_data,
+ mrb_func_t ensure, mrb_value e_data);
+MRB_API mrb_value mrb_rescue(mrb_state *mrb, mrb_func_t body, mrb_value b_data,
+ mrb_func_t rescue, mrb_value r_data);
+MRB_API mrb_value mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_value b_data,
+ mrb_func_t rescue, mrb_value r_data, ...);
+
#if defined(__cplusplus)
} /* extern "C" { */
#endif
diff --git a/mrbgems/mruby-error/mrbgem.rake b/mrbgems/mruby-error/mrbgem.rake
new file mode 100644
index 000000000..b8281b17e
--- /dev/null
+++ b/mrbgems/mruby-error/mrbgem.rake
@@ -0,0 +1,10 @@
+MRuby::Gem::Specification.new('mruby-error') do |spec|
+ spec.license = 'MIT'
+ spec.author = 'mruby developers'
+ spec.summary = 'extensional error handling'
+
+ if build.cxx_abi_enabled?
+ @objs << build.compile_as_cxx("#{spec.dir}/src/exception.c", "#{spec.build_dir}/src/exception.cxx")
+ @objs.delete_if { |v| v == objfile("#{spec.build_dir}/src/exception") }
+ end
+end
diff --git a/mrbgems/mruby-error/src/exception.c b/mrbgems/mruby-error/src/exception.c
new file mode 100644
index 000000000..abd03bc29
--- /dev/null
+++ b/mrbgems/mruby-error/src/exception.c
@@ -0,0 +1,102 @@
+#include <stdarg.h>
+#include "mruby.h"
+#include "mruby/throw.h"
+#include "mruby/error.h"
+
+MRB_API mrb_value
+mrb_protect(mrb_state *mrb, mrb_func_t body, mrb_value data, mrb_bool *state)
+{
+ struct mrb_jmpbuf *prev_jmp = mrb->jmp;
+ struct mrb_jmpbuf c_jmp;
+ mrb_value result = mrb_nil_value();
+
+ if (state) { *state = FALSE; }
+
+ MRB_TRY(&c_jmp) {
+ mrb->jmp = &c_jmp;
+ result = body(mrb, data);
+ mrb->jmp = prev_jmp;
+ } MRB_CATCH(&c_jmp) {
+ mrb->jmp = prev_jmp;
+ mrb->exc = NULL;
+ if (state) { *state = TRUE; }
+ } MRB_END_EXC(&c_jmp);
+
+ mrb_gc_protect(mrb, result);
+ return result;
+}
+
+MRB_API mrb_value
+mrb_ensure(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t ensure, mrb_value e_data)
+{
+ struct mrb_jmpbuf *prev_jmp = mrb->jmp;
+ struct mrb_jmpbuf c_jmp;
+ mrb_value result;
+
+ MRB_TRY(&c_jmp) {
+ mrb->jmp = &c_jmp;
+ result = body(mrb, b_data);
+ mrb->jmp = prev_jmp;
+ } MRB_CATCH(&c_jmp) {
+ mrb->jmp = prev_jmp;
+ ensure(mrb, e_data);
+ MRB_THROW(mrb->jmp); /* rethrow catched exceptions */
+ } MRB_END_EXC(&c_jmp);
+
+ ensure(mrb, e_data);
+ mrb_gc_protect(mrb, result);
+ return result;
+}
+
+MRB_API mrb_value
+mrb_rescue(mrb_state *mrb, mrb_func_t body, mrb_value b_data,
+ mrb_func_t rescue, mrb_value r_data)
+{
+ return mrb_rescue_exceptions(mrb, body, b_data, rescue, r_data, mrb->eStandardError_class, NULL);
+}
+
+MRB_API mrb_value
+mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t rescue, mrb_value r_data, ...)
+{
+ struct mrb_jmpbuf *prev_jmp = mrb->jmp;
+ struct mrb_jmpbuf c_jmp;
+ mrb_value result;
+ va_list excs;
+ struct RClass *cls;
+ mrb_bool error_matched = FALSE;
+
+ MRB_TRY(&c_jmp) {
+ mrb->jmp = &c_jmp;
+ result = body(mrb, b_data);
+ mrb->jmp = prev_jmp;
+ } MRB_CATCH(&c_jmp) {
+ mrb->jmp = prev_jmp;
+
+ va_start(excs, r_data);
+ while((cls = va_arg(excs, struct RClass*))) {
+ if (mrb_obj_is_kind_of(mrb, mrb_obj_value(mrb->exc), cls)) {
+ error_matched = TRUE;
+ break;
+ }
+ }
+ va_end(excs);
+
+ if (!error_matched) { MRB_THROW(mrb->jmp); }
+
+ mrb->exc = NULL;
+ result = rescue(mrb, r_data);
+ } MRB_END_EXC(&c_jmp);
+
+ mrb_gc_protect(mrb, result);
+ return result;
+}
+
+void
+mrb_mruby_error_gem_init(mrb_state *mrb)
+{
+}
+
+void
+mrb_mruby_error_gem_final(mrb_state *mrb)
+{
+}
diff --git a/mrbgems/mruby-error/test/exception.c b/mrbgems/mruby-error/test/exception.c
new file mode 100644
index 000000000..7c15fb457
--- /dev/null
+++ b/mrbgems/mruby-error/test/exception.c
@@ -0,0 +1,57 @@
+#include "mruby.h"
+#include "mruby/error.h"
+#include "mruby/array.h"
+
+static mrb_value
+protect_cb(mrb_state *mrb, mrb_value b)
+{
+ return mrb_yield_argv(mrb, b, 0, NULL);
+}
+
+static mrb_value
+run_protect(mrb_state *mrb, mrb_value self)
+{
+ mrb_value b;
+ mrb_value ret[2];
+ mrb_bool state;
+ mrb_get_args(mrb, "&", &b);
+ ret[0] = mrb_protect(mrb, protect_cb, b, &state);
+ ret[1] = mrb_bool_value(state);
+ return mrb_ary_new_from_values(mrb, 2, ret);
+}
+
+static mrb_value
+run_ensure(mrb_state *mrb, mrb_value self)
+{
+ mrb_value b, e;
+ mrb_get_args(mrb, "oo", &b, &e);
+ return mrb_ensure(mrb, protect_cb, b, protect_cb, e);
+}
+
+static mrb_value
+run_rescue(mrb_state *mrb, mrb_value self)
+{
+ mrb_value b, r;
+ mrb_get_args(mrb, "oo", &b, &r);
+ return mrb_rescue(mrb, protect_cb, b, protect_cb, r);
+}
+
+static mrb_value
+run_rescue_exceptions(mrb_state *mrb, mrb_value self)
+{
+ mrb_value b, r;
+ mrb_get_args(mrb, "oo", &b, &r);
+ return mrb_rescue_exceptions(mrb, protect_cb, b, protect_cb, r, E_TYPE_ERROR, NULL);
+}
+
+void
+mrb_mruby_error_gem_test(mrb_state *mrb)
+{
+ struct RClass *cls;
+
+ cls = mrb_define_class(mrb, "ExceptionTest", mrb->object_class);
+ mrb_define_module_function(mrb, cls, "mrb_protect", run_protect, MRB_ARGS_NONE() | MRB_ARGS_BLOCK());
+ mrb_define_module_function(mrb, cls, "mrb_ensure", run_ensure, MRB_ARGS_REQ(2));
+ mrb_define_module_function(mrb, cls, "mrb_rescue", run_rescue, MRB_ARGS_REQ(2));
+ mrb_define_module_function(mrb, cls, "mrb_rescue_exceptions", run_rescue_exceptions, MRB_ARGS_REQ(2));
+}
diff --git a/mrbgems/mruby-error/test/exception.rb b/mrbgems/mruby-error/test/exception.rb
new file mode 100644
index 000000000..b5ea719a2
--- /dev/null
+++ b/mrbgems/mruby-error/test/exception.rb
@@ -0,0 +1,53 @@
+assert 'mrb_protect' do
+ assert_equal ['test', false] do
+ ExceptionTest.mrb_protect { 'test' }
+ end
+ assert_equal [nil, true] do
+ ExceptionTest.mrb_protect { raise 'test' }
+ end
+end
+
+assert 'mrb_ensure' do
+ a = false
+ assert_equal 'test' do
+ ExceptionTest.mrb_ensure Proc.new { 'test' }, Proc.new { a = true }
+ end
+ assert_true a
+
+ a = false
+ assert_raise RuntimeError do
+ ExceptionTest.mrb_ensure Proc.new { raise 'test' }, Proc.new { a = true }
+ end
+ assert_true a
+end
+
+assert 'mrb_rescue' do
+ assert_equal 'test' do
+ ExceptionTest.mrb_rescue Proc.new { 'test' }, Proc.new {}
+ end
+
+ class CustomExp < Exception
+ end
+
+ assert_raise CustomExp do
+ ExceptionTest.mrb_rescue Proc.new { raise CustomExp.new 'test' }, Proc.new { 'rescue' }
+ end
+
+ assert_equal 'rescue' do
+ ExceptionTest.mrb_rescue Proc.new { raise 'test' }, Proc.new { 'rescue' }
+ end
+end
+
+assert 'mrb_rescue_exceptions' do
+ assert_equal 'test' do
+ ExceptionTest.mrb_rescue_exceptions Proc.new { 'test' }, Proc.new {}
+ end
+
+ assert_raise RangeError do
+ ExceptionTest.mrb_rescue_exceptions Proc.new { raise RangeError.new 'test' }, Proc.new { 'rescue' }
+ end
+
+ assert_equal 'rescue' do
+ ExceptionTest.mrb_rescue_exceptions Proc.new { raise TypeError.new 'test' }, Proc.new { 'rescue' }
+ end
+end