Commit 232e07ad authored by dearblue's avatar dearblue

Reimplement mruby-catch; ref #5321

When there is a corresponding tag, the `RBreak` object is used to make a global jump.

Like CRuby, it can't be caught by `rescue`.
It is also the same as CRuby that it can be canceled in the middle by `ensure`.

 ### How to find the corresponding tag with `throw`

The called `catch` method remains in the call stack, and the tag also remains in the stack at that time.
So it is possible to find the called location by searching the two.

Note that no method can be given to the `proc` object specified in `RBreak`.
Therefore, inside the `catch` method, the argument block is called in a seemingly meaningless closure.

Also, as a countermeasure against `alias` etc., the `proc` object, which is the body of the `catch` method, is saved when mrbgem is initialized.
parent c6c632cf
class ThrowCatchJump < Exception
class UncaughtThrowError < ArgumentError
attr_reader :_tag, :_val
def initialize(tag, val)
@_tag = tag
......@@ -9,14 +9,14 @@ end
module Kernel
def catch(tag=Object.new, &block)
block.call(tag)
rescue ThrowCatchJump => e
unless e._tag.equal?(tag)
raise e
end
return e._val
# A double closure is required to make the nested `catch` distinguishable
# and because `break` goes back to `proc->upper`.
-> { -> { block.call(tag) }.call }.call
end
def throw(tag, val=nil)
raise ThrowCatchJump.new(tag, val)
__throw(tag, val)
raise UncaughtThrowError.new(tag, val)
end
__preserve_catch_method
end
#include <mruby.h>
#include <mruby/class.h>
#include <mruby/variable.h>
#include <mruby/error.h>
#include <mruby/proc.h>
#include <mruby/presym.h>
#define ID_PRESERVED_CATCH MRB_SYM(__preserved_catch_proc)
static const mrb_callinfo *
find_catcher(mrb_state *mrb, mrb_value tag)
{
mrb_value pval = mrb_obj_iv_get(mrb, (struct RObject *)mrb->kernel_module, ID_PRESERVED_CATCH);
mrb_assert(mrb_proc_p(pval));
const struct RProc *proc = mrb_proc_ptr(pval);
const mrb_callinfo *ci = mrb->c->ci;
size_t n = ci - mrb->c->cibase;
ci--;
for (; n > 0; n--, ci--) {
const mrb_value *arg1 = ci->stack + 1;
if (ci->proc == proc && mrb_obj_eq(mrb, *arg1, tag)) {
return ci;
}
}
return NULL;
}
static mrb_value
mrb_f_throw(mrb_state *mrb, mrb_value self)
{
mrb_value tag, obj;
mrb_get_args(mrb, "oo", &tag, &obj);
const mrb_callinfo *ci = find_catcher(mrb, tag);
if (ci) {
struct RBreak *b = (struct RBreak *)mrb_obj_alloc(mrb, MRB_TT_BREAK, NULL);
mrb_break_value_set(b, obj);
mrb_break_proc_set(b, ci[2].proc); /* Back to the closure in `catch` method */
mrb_exc_raise(mrb, mrb_obj_value(b));
}
return mrb_nil_value();
}
static mrb_value
mrb_s_preserve_catch(mrb_state *mrb, mrb_value self)
{
mrb_method_t m = mrb_method_search(mrb, mrb->kernel_module, MRB_SYM(catch));
mrb_assert(!MRB_METHOD_UNDEF_P(m));
mrb_assert(!MRB_METHOD_CFUNC_P(m));
mrb_obj_iv_set(mrb, (struct RObject *)mrb->kernel_module, ID_PRESERVED_CATCH, mrb_obj_value(MRB_METHOD_PROC(m)));
mrb_remove_method(mrb, mrb_class(mrb, mrb_obj_value(mrb->kernel_module)), MRB_SYM(__preserve_catch_method));
return mrb_nil_value();
}
void
mrb_mruby_catch_gem_init(mrb_state *mrb)
{
mrb_define_method(mrb, mrb->kernel_module, "__throw", mrb_f_throw, MRB_ARGS_REQ(2));
mrb_define_class_method(mrb, mrb->kernel_module, "__preserve_catch_method", mrb_s_preserve_catch, MRB_ARGS_NONE());
}
void
mrb_mruby_catch_gem_final(mrb_state *mrb)
{
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment