From 3ac682b2de45e7267a6f0efb47d45a785bd90b23 Mon Sep 17 00:00:00 2001 From: dearblue Date: Sat, 24 Jan 2026 11:31:32 +0900 Subject: [PATCH] Add the `MRB_ENSURE()` macro --- include/mruby.h | 4 ++++ include/mruby/error.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/include/mruby.h b/include/mruby.h index 542215541..b5a66dc18 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -1615,6 +1615,10 @@ mrbmemset(void *s, int c, size_t n) #define mrb_int_hash_func(mrb,key) (uint32_t)((key)^((key)<<2)^((key)>>2)) +#define MRB_UNIQNAME(name) MRB_UNIQNAME_1(name, __LINE__) +#define MRB_UNIQNAME_1(name, line) MRB_UNIQNAME_2(name, line) +#define MRB_UNIQNAME_2(name, line) name##line + MRB_END_DECL #endif /* MRUBY_H */ diff --git a/include/mruby/error.h b/include/mruby/error.h index e8c63dc3e..ba091fcfe 100644 --- a/include/mruby/error.h +++ b/include/mruby/error.h @@ -127,6 +127,42 @@ MRB_API mrb_value mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_val mrb_func_t rescue, mrb_value r_data, mrb_int len, struct RClass **classes); +/** + * Calls `func` via `mrb_protect_error()` and then always executes the user block exactly once. + * Even if a global jump (similar to a Ruby exception) occurs within `func`, the block will be executed, + * and after the block's completion, the global jump will be re-thrown. + * + * By checking `mrb->exc != NULL` within the block, you can determine if a global jump occurred in `func`. + * + * If you want to suppress the global jump and continue processing, use `mrb_clear_error(mrb); break;`. + * + * - `mrb`: The mruby state reference + * - `result_var`: Pre-defined mrb_value type variable (to receive `func`'s return value) + * - `func`: Function to call (compatible with `mrb_protect_error_func`) + * - `data`: User data to pass to `func` + * + * Example: + * + * mrb_value result; + * MRB_ENSURE(mrb, result, body_func, userdata) { + * // This block is always executed (equivalent to Ruby's ensure) + * + * if (mrb->exc) { + * // Post-processing when an exception occurs + * } + * + * // To ignore the global jump, use `mrb_clear_error(mrb); break;` here + * } + */ +#define MRB_ENSURE(mrb, result_var, func, data) \ + for (mrb_bool MRB_UNIQNAME(_break_) = FALSE; \ + !MRB_UNIQNAME(_break_) && \ + (((result_var) = mrb_protect_error(mrb, func, data, &MRB_UNIQNAME(_break_))), \ + ((mrb)->exc = (MRB_UNIQNAME(_break_) ? mrb_obj_ptr((result_var)) : NULL)), \ + TRUE); \ + (void)(MRB_UNIQNAME(_break_) && (mrb)->jmp && (mrb_exc_raise(mrb, result_var), TRUE)), \ + MRB_UNIQNAME(_break_) = TRUE) + MRB_END_DECL #endif /* MRUBY_ERROR_H */