mruby-catch: add comprehensive call-seq documentation and helper function comments

Added complete call-seq documentation for catch/throw functionality across
both Ruby and C implementations:

- Class documentation: explains exception raised for unmatched throws
- initialize: constructor with tag and value parameters, creates error
  message with proper tag inspection and stores thrown values for debugging

- throw: transfers control to matching catch block with optional return value,
  raises UncaughtThrowError if no matching catch found, supports both
  single tag and tag+value forms with comprehensive usage examples

- find_catcher: searches call stack for matching catch block by comparing
  tags using mrb_obj_eq, returns call stack index or 0 if not found

- catch_syms: pre-defined symbols (Object, new, call) used by catch bytecode
  implementation for efficient symbol lookup

- catch_iseq: bytecode instruction sequence implementing catch method logic,
  handles default tag creation (Object.new) and block parameter passing

- catch_irep: instruction representation containing bytecode metadata
  for catch method execution

- catch_proc: procedure object used to identify catch blocks in call stack
  during throw operations, marked with proper GC and scope flags

- mrb_mruby_catch_gem_init: defines catch and throw as private methods
  in Kernel module, initializes symbols and sets up bytecode procedure

- mrb_mruby_catch_gem_final: cleanup function (currently no-op as
  implementation uses static data structures)

Co-authored-by: Atlassian Rovo Dev
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-07-21 13:40:00 +09:00
parent d4f33feb23
commit d0892f1ba9
2 changed files with 72 additions and 1 deletions
+22 -1
View File
@@ -1,5 +1,26 @@
#
# Exception raised when a throw is executed without a corresponding catch.
# This error contains the tag and value that were thrown.
#
class UncaughtThrowError < ArgumentError
attr_reader :tag, :value
# The tag that was thrown
attr_reader :tag
# The value that was thrown with the tag
attr_reader :value
#
# call-seq:
# UncaughtThrowError.new(tag, value) -> exception
#
# Creates a new UncaughtThrowError with the given tag and value.
# The tag is the symbol or object that was thrown, and value is
# the associated value.
#
# error = UncaughtThrowError.new(:done, "finished")
# error.tag #=> :done
# error.value #=> "finished"
# error.message #=> "uncaught throw :done"
#
def initialize(tag, value)
@tag = tag
@value = value
+50
View File
@@ -6,11 +6,17 @@
#include <mruby/opcode.h>
#include <mruby/presym.h>
/* Pre-defined symbols used by catch implementation */
MRB_PRESYM_DEFINE_VAR_AND_INITER(catch_syms, 3, MRB_SYM(Object), MRB_SYM(new), MRB_SYM(call))
/*
* Bytecode implementation of catch method:
* def catch(r1 = Object.new, &r2)
* r2.call(r1)
* end
*
* This creates a default tag (Object.new) if none provided, then calls
* the block with the tag as argument.
*/
static const mrb_code catch_iseq[] = {
OP_ENTER, 0x00, 0x20, 0x01, // 000 ENTER 0:1:0:0:0:0:1 (0x2001)
@@ -29,6 +35,8 @@ static const mrb_code catch_iseq[] = {
OP_SEND, 0x02, 0x02, 0x01, // 023 SEND R2 :call n=1
OP_RETURN, 0x02, // 027 RETURN R2
};
/* Instruction representation for catch method bytecode */
static const mrb_irep catch_irep = {
3,5,0,
MRB_IREP_STATIC,catch_iseq,
@@ -37,12 +45,15 @@ static const mrb_irep catch_irep = {
NULL,
sizeof(catch_iseq),0,3,0,0
};
/* Procedure object for catch method - used to identify catch blocks in call stack */
mrb_alignas(8)
static const struct RProc catch_proc = {
NULL, NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
{ &catch_irep }, NULL, { NULL }
};
/* Helper function to find a matching catch block in the call stack */
static size_t
find_catcher(mrb_state *mrb, mrb_value tag)
{
@@ -59,6 +70,30 @@ find_catcher(mrb_state *mrb, mrb_value tag)
return 0;
}
/*
* call-seq:
* throw(tag) -> obj
* throw(tag, obj) -> obj
*
* Transfers control to the end of the active catch block waiting for tag.
* Raises UncaughtThrowError if there is no catch block for the tag. The
* optional second parameter supplies a return value for the catch block,
* which otherwise defaults to nil.
*
* def routine(n)
* puts n
* throw :done if n <= 0
* routine(n-1)
* end
*
* catch(:done) { routine(3) }
* 3
* 2
* 1
* 0
*
* catch(:done) { throw :done, "hello" } #=> "hello"
*/
static mrb_value
throw_m(mrb_state *mrb, mrb_value self)
{
@@ -80,6 +115,17 @@ throw_m(mrb_state *mrb, mrb_value self)
return mrb_nil_value();
}
/*
* Initializes the mruby-catch gem by defining catch and throw methods.
*
* - catch: defined using the pre-compiled bytecode procedure for efficiency,
* marked as private method in Kernel module
* - throw: defined as a regular C method that searches for matching catch blocks,
* also marked as private method in Kernel module
*
* Both methods are added to the Kernel module, making them available globally
* as private methods that can be called without a receiver.
*/
void
mrb_mruby_catch_gem_init(mrb_state *mrb)
{
@@ -93,6 +139,10 @@ mrb_mruby_catch_gem_init(mrb_state *mrb)
mrb_define_private_method_id(mrb, mrb->kernel_module, MRB_SYM(throw), throw_m, MRB_ARGS_ARG(1,1));
}
/*
* Finalizes the mruby-catch gem. Currently no cleanup is required
* as the catch/throw implementation uses static data structures.
*/
void
mrb_mruby_catch_gem_final(mrb_state *mrb)
{