Commit Graph

5470 Commits

Author SHA1 Message Date
HASUMI Hitoshi 63dd1832bc Improve memory management of mrb_execute_proc_synchronously
Wrap sync task by mrb_gc_arena_save/restore to release objects from arena
that a sync task allocated so that they can be freed in GC cycle
2026-01-14 14:12:51 +09:00
HASUMI Hitoshi 8a0263026e Add scheduler_lock check
And refactoring to consolidate duplicate code

ref PR #6699
2026-01-12 13:44:54 +09:00
HASUMI Hitoshi 0a21eef938 Fix mruby-task for PicoRuby Integration
With this PR, I can remove the original task.c in picoruby/picoruby and future development will be much easier.

## Add

### General

- C API functions exported with MRB_API for external integration:
  - mrb_execute_proc_synchronously() for synchronous proc execution
  - Task control APIs (mrb_create_task, mrb_suspend_task, mrb_resume_task, mrb_terminate_task, mrb_stop_task, mrb_task_value, mrb_task_status)
  - Task context management APIs for picoruby-sandbox (mrb_task_init_context, mrb_task_reset_context, mrb_task_proc_set)
  - Task.tick class method to get current tick count
- Comprehensive C API documentation with WASM integration examples

### For PicoRuby.wasm

- WASM/Emscripten support: Disable SIGALRM timer when __EMSCRIPTEN__ is defined, as JavaScript handles tick calls via setInterval
- Scheduler lock mechanism to prevent asynchronous task operations during synchronous execution (scheduler_lock counter in mrb_task_state)
- mrb_task_run_once() for single-step execution (event loop integration)

## Fix

### task.c
- Replace MRB_FIBER_TERMINATED with MRB_TASK_STOPPED just for clarity
- Allow suspending DORMANT and WAITING tasks in mrb_task_suspend (See comment in the source)
- Task context initialization by removing dummy callinfo push/pop

*NOTE*

With the dummy callinfo code that I deleted, IRB in PicoRuby ended SEGV.
If that code is mandatory, we need to discuss how to solve my problem.

### vm.c
- Handle MRB_TASK_CREATED status in VM's NORMAL_RETURN phase to properly stop tasks

----

These changes are necessary to make PicoRuby work.
Nevertheless, even with this patch, MicroRuby for Raspberry Pi Pico 2 is still unstable.
I would like to merge this PR anyway to make development easier by involving the PicoRuby community.
2026-01-11 17:07:04 +09:00
Yukihiro "Matz" Matsumoto ef64ca32a1 mruby-bigint: optimize multiplication for power-of-2 numbers
Add fast path for multiplying by powers of 2 (2^n). Uses left shift
instead of Karatsuba multiplication: x * 2^n = x << n.

This optimizes "mostly-zero" patterns common in fuzzing tests, where
numbers like 2^2097150 (single bit set) would otherwise trigger slow
Karatsuba multiplication.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 12:29:50 +09:00
Yukihiro "Matz" Matsumoto b7593cde15 mruby-bigint: optimize multiplication for all-ones numbers
Add fast path for multiplying numbers of form 2^n - 1 (all bits set).

Uses algebraic identities:
- (2^n - 1) * (2^m - 1) = 2^(n+m) - 2^n - 2^m + 1
- (2^n - 1) * y = (y << n) - y

These are O(n) operations instead of O(n^1.585) for Karatsuba.
Fuzzing test cases using all-ones patterns now complete in 0.01s
instead of 13+ seconds.

Also raises KARATSUBA_THRESHOLD from 8 to 32 for ~32% speedup
on general large number multiplication.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 12:25:22 +09:00
Yukihiro "Matz" Matsumoto df778e09d2 mruby-bigint: raise Karatsuba threshold from 8 to 32
Reduces recursion overhead for large number multiplication.
Benchmarks show ~32% speedup for million-bit operands.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 11:36:38 +09:00
Yukihiro "Matz" Matsumoto 0a5ec60e5c mruby-bigint: fix OOM by limiting left shift size in mpz_mul_2exp
Add MRB_BIGINT_BIT_LIMIT (1 billion bits / 128MB) to prevent
unreasonably large allocations when left-shifting by huge amounts.
Raises RangeError instead of attempting multi-GB allocations.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 08:33:58 +09:00
Yukihiro "Matz" Matsumoto a6b7f3b018 mruby-bigint: fix memory leak by trimming bitwise operation results
mpz_and, mpz_or, and mpz_xor were not calling trim() on their results,
causing inflated size values with trailing zero limbs. This led to
incorrect comparisons in ucmp() and caused udiv() to take wrong code
paths, resulting in memory leaks when exceptions occurred.

Also added defensive overflow checks in mpz_init_heap and udiv.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 08:18:15 +09:00
Yukihiro "Matz" Matsumoto c46f9e0793 mruby-compiler: fix uninitialized memory in realloc_pool_str()
when converting a shared/static string (IREP_TT_SSTR) to heap-allocated
(IREP_TT_STR), copy the original content to the new buffer.

previously, the original content was lost when allocating new memory,
leaving the first bytes uninitialized. this caused find_pool_str() to
read uninitialized memory via memcmp() when searching for duplicate
strings.

reported by OSS-Fuzz.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-08 12:58:22 +09:00
Yukihiro "Matz" Matsumoto ad5301970f Merge pull request #6694 from katafrakt/mirb-cosmopolitan 2026-01-08 12:58:09 +09:00
Yukihiro "Matz" Matsumoto 9a48049109 Merge pull request #6696 from khasinski/fix-required-kwarg-parsing 2026-01-08 11:02:18 +09:00
Chris Hasiński 1e932dd161 Fix parse error with required kwargs and omitted parens
When defining a method with a required keyword argument without
parentheses, mruby incorrectly parsed the next line as the default
value:

    def foo arg:
      123
    end

Was parsed as: def foo(arg: 123); end  (optional kwarg, empty body)
Should be:     def foo(arg:); 123; end (required kwarg, body returns 123)

The fix sets EXPR_ARG lexer state after parsing f_label, making
newlines significant. This prevents the parser from consuming
expressions across line boundaries as default values for keyword
arguments.

Also fixes a pre-existing bug in f_label where tNUMPARAM (type <num>)
was implicitly assigned to $$ (type <id>) without conversion. Now
explicitly uses intern_numparam() to convert numbered parameters to
symbols.

Fixes https://github.com/mruby/mruby/issues/6268
2026-01-08 00:50:55 +01:00
Yukihiro "Matz" Matsumoto 7fe5c2e260 gc.c: rename mrb_alloca() to mrb_temp_alloc() and fix memory leaks
rename mrb_alloca() to mrb_temp_alloc() for clearer naming - the new name
better describes its purpose as GC-managed temporary allocation. keep
mrb_alloca() as a macro alias for backward compatibility.

apply mrb_temp_alloc() to fix potential memory leaks in:
- mruby-strftime: if mrb_str_cat() raises, allocated buffers now cleaned by GC
- mruby-io File.readlink: if mrb_str_new() raises, buffer now cleaned by GC

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-08 08:23:51 +09:00
Yukihiro "Matz" Matsumoto c9e3af60e1 mruby-set: fix memory leak caused by recursive hash computation
when a Set contains itself (directly or indirectly), computing its hash
would cause infinite recursion leading to SystemStackError. the exception
during khash rebuild leaked memory.

add recursion detection flag to Set#hash that returns 0 for recursive
references, similar to Ruby's behavior.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-08 08:23:43 +09:00
Yukihiro "Matz" Matsumoto 0e42c95df2 mruby-bigint: add exponent size check in mrb_bint_pow
prevent resource exhaustion when computing power with extremely large
exponents (e.g., 81.pow(51742871469327219)). the check estimates the
result size and raises RangeError if it would exceed 1 million bits.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-06 07:51:07 +09:00
Yukihiro "Matz" Matsumoto bbcadd6bf9 mruby-rational: fix left shift overflow in rational_new_f
Shifting 1 left by MRB_INT_BIT-1 (e.g., 63 on 64-bit) bits into the sign
bit is undefined behavior. Change the overflow check from >= MRB_INT_BIT
to >= MRB_INT_BIT-1 to prevent this.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-05 17:47:57 +09:00
Yukihiro "Matz" Matsumoto d5c7a906f9 mruby-time: fix integer overflow in time_mktime
When year value is close to MRB_INT_MIN, subtracting TM_YEAR_BASE (1900)
causes signed integer overflow. Add underflow check before the subtraction.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-05 12:35:09 +09:00
Yukihiro "Matz" Matsumoto 5a1123ed22 mruby-io: remove unused flock function
The local flock() function for Windows is now dead code since the
HAL refactoring. The Windows implementation is in hal-win-io which
provides mrb_hal_io_flock().

Fixes warning: 'flock' defined but not used [-Wunused-function]

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-05 08:55:53 +09:00
Paweł Świątkowski f856cf811f Require sys/socket.h (for Cosmopolitan)
Compilation of mirb with Cosmopolitan fails because of missing include
(Cosmopolitan seems to be more strict than "traditional" compilers.
2026-01-04 19:52:11 +01:00
Yukihiro "Matz" Matsumoto 2e4a8e8edd mruby-compiler: fix sp tracking in pattern match failure path
After pattern matching code generation, the sp (stack pointer) must
be restored to match the success path value. The failure path (after
RAISEIF) left sp in a different state, causing incorrect register
allocation in subsequent code like string interpolation.

This caused OP_STRCAT to use the wrong register, leading to
null-dereference when trying to modify a non-string value as a string.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-04 09:12:35 +09:00
Yukihiro "Matz" Matsumoto af3f9b65f1 mruby-compiler: fix sp imbalance in pattern matching with rescue
The => pattern matching codegen was doing push() after RAISEIF, even though
RAISEIF never returns. This caused sp to be off by 1 when success and failure
paths joined, resulting in wrong register allocation for subsequent operations.

For string interpolation like "#{ expr => pattern rescue body }", the base
string would be at R2 but STRCAT would incorrectly use R3, causing memory
corruption and crashes.

Test case: %{#{.=>.,. rescue def .()end}} (from oss-fuzz)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-03 20:21:44 +09:00
Yukihiro "Matz" Matsumoto a9825e92df mruby-rational: fix crash in rational_new_f with negative exponent
rational_new_b() expects both arguments to be bigints, but rational_new_f()
was passing an integer value for the numerator when the exponent was negative.
This caused a segfault in mrb_bint_reduce() which called RBIGINT() on the
integer value.

Test case: 5r**-92 (from oss-fuzz)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-03 13:13:02 +09:00
Yukihiro "Matz" Matsumoto 53a25bab14 mruby-sleep, hal-posix-socket: fix amalgamation compatibility
mruby-sleep: declare slp_tm before #ifdef _WIN32 block to fix
undeclared variable error in non-Windows branch.

hal-posix-socket: use #if defined(HAVE_SA_LEN) && HAVE_SA_LEN instead
of #ifdef HAVE_SA_LEN, since mruby-socket defines HAVE_SA_LEN to 0 on
non-BSD platforms.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-02 18:28:31 +09:00
Yukihiro "Matz" Matsumoto 94e831cc70 init.c, mruby-io: undef DONE macro
Add #undef DONE after last usage to prevent macro redefinition
warnings in amalgamation builds.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-01 13:18:33 +09:00
Yukihiro "Matz" Matsumoto d1178ec8eb hash.c, symbol.c, string.c, mruby-string-ext: undef lesser macro
Add #undef lesser after last usage to prevent macro redefinition
warnings when files are amalgamated into a single translation unit.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-01 10:23:21 +09:00
Yukihiro "Matz" Matsumoto c7463af767 mruby-io, hal-posix-io: fix amalgamation compatibility
Remove unused mrb_stat typedef from file.c that conflicted with the
mrb_stat() function in file_test.c when compiled as a single
translation unit.

Fix convert_stat() in hal-posix-io to handle st_atime macro correctly
in both normal and amalgamated builds by extracting time values before
undefining the macros.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-01 09:01:15 +09:00
Yukihiro "Matz" Matsumoto 510ebd738d mruby-compiler: terminate parsing early after too many errors
When parsing malformed input with many syntax errors (e.g., via eval
with a long garbage string), the parser would continue until the end
of input, causing long execution times.

Add an early termination check in the lexer that returns EOF once
the error count exceeds 10 (same as error_buffer size). This prevents
DoS from inputs like eval("garbage" * 1000).

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-31 09:11:44 +09:00
Yukihiro "Matz" Matsumoto 53ee1a7826 bigint.c: fix memory leak in mpz_div_2exp
mpz_div_2exp() was calling mpz_init_heap() on output parameter z
without first freeing z's existing memory. When called from
mpz_barrett_reduce() with pre-allocated temporaries, this caused
memory leaks.

Add mpz_clear(ctx, z) before mpz_init_heap() in both affected code
paths, matching the pattern already used in mpz_mod_2exp().

Fixes ClusterFuzz issue detected with input "8.pow 7*2515881+186,8 ^4>>-509".

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-30 18:20:41 +09:00
Yukihiro "Matz" Matsumoto 8f259fb560 mruby-bigint, mruby-numeric-ext: fix Integer#pow with negative modulus
Support negative modulus in Integer#pow(exp, mod) with proper Ruby
semantics. Previously, negative modulus caused an infinite loop in
Barrett reduction. Now:

- Use absolute value of modulus for computation
- Apply signed modulo adjustment (result + m for non-zero result
  when m is negative)
- Add early return for zero base with positive exponent (0^n = 0)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-30 16:25:49 +09:00
Yukihiro "Matz" Matsumoto 54fbf6c3ec bigint.c: fix buffer overflow in mpz_div_2exp with large shift
When right-shifting by more bits than the number contains, the loop
condition `i < x->sz - digs` would underflow (since size_t is unsigned),
causing out-of-bounds memory access.

Fixed by checking if digs >= x->sz upfront and returning zero in that
case, since shifting right by more bits than the number has always
yields zero.

Discovered via ClusterFuzz with input "7<<78<<-772".

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-30 14:46:26 +09:00
Yukihiro "Matz" Matsumoto 5eca2fae1e rational.c: fix undefined behavior from large shift exponents
In rational_new_f(), the code performed ((mrb_int)1)<<exp without
checking if exp >= MRB_INT_BIT. Shifting by a value >= bit width
is undefined behavior in C.

Also fixed the negative exponent case which incorrectly used
deno >>= exp (right-shift by negative is UB). The correct logic
is deno <<= -exp to multiply denominator by 2^(-exp).

Both cases now check for overflow before shifting and fall back
to bigint operations when necessary.

Discovered via ClusterFuzz with input "92r**11".

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-30 14:28:44 +09:00
Yukihiro "Matz" Matsumoto ff0e20453f bigint.c: fix GCD infinite loop and size handling bugs
Fixed three bugs that caused infinite loops in GCD calculations:

1. mpz_set_int() didn't shrink sz when setting a smaller value.
   mpz_realloc() only grows allocations, so setting a 1-limb value
   to an mpz_t with sz=3 would leave sz=3, breaking algorithms
   that depend on correct sz values.

2. mpz_set_uint64() had the same issue.

3. mpz_gcd() used mpz_init_set() which preserves the sign.
   GCD should work with absolute values since gcd(a,b) = gcd(|a|,|b|).
   With negative inputs, the sign would oscillate during mod operations,
   preventing the Euclidean algorithm from converging.

4. mpz_div_2exp() when e==0 and z==x would corrupt data by calling
   mpz_init_heap() which overwrites z->p before copying from x.

These bugs were discovered via ClusterFuzz with complex rational
number calculations.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-30 12:04:56 +09:00
Yukihiro "Matz" Matsumoto 27c9037d48 bigint.c: fix FPE caused by inconsistent zero sign state
Fix two functions that could create bigints with sn != 0 but value of 0:

- mpz_mod_limb: single-limb case set r->sn = x->sn even when result was 0
- mpz_mul_2exp: set z->sn = sn unconditionally after zero-producing ops

This inconsistent state caused GCD loop (!zero_p(&b)) to continue with
a zero divisor, eventually causing FPE in mpz_mod_limb with m = 0.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 23:39:30 +09:00
Yukihiro "Matz" Matsumoto 9f0950da13 codegen.c: fix stack tracking in pattern match branching code
When generating code for pattern matching with potential failures, the
success and failure paths both need to pop the matched value. At
runtime, only one path executes. But during codegen, both pop() calls
affected the compile-time stack pointer (cursp), corrupting register
allocation and causing heap-buffer-overflow when accessing symbol
tables with wrong indices.

Fix by saving/restoring the stack pointer around the branch point, so
each path correctly tracks the stack state independently.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 22:28:45 +09:00
Yukihiro "Matz" Matsumoto da2d652ec9 codegen.c: fix NODE_MATCH_PAT to push result when val is true
Pattern matching expressions were not pushing a result value in several
code paths when used in value context (e.g., string interpolation).
This caused crashes when the result was expected on the stack.

Fix all code paths in NODE_MATCH_PAT to push the appropriate value:
- 'in' pattern returns true/false
- '=>' pattern returns nil (matches CRuby behavior)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 19:09:06 +09:00
Yukihiro "Matz" Matsumoto f6f8124406 codegen.c: fix crash in parallel assignment optimization
The direct literal generation optimization for parallel assignment was
using the RHS count as the loop bound but only filling registers for
LHS variables. When RHS has more elements than LHS (e.g., `a,=1,2`),
this caused uninitialized register indices to be used, generating
garbage opcodes that crashed the VM.

Fix by counting LHS variables and only applying the optimization when
LHS and RHS counts match exactly.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 18:19:58 +09:00
Yukihiro "Matz" Matsumoto 225cdaa16a mruby-compiler: eliminate bison shift-reduce conflict for 'in'
Add precedence declarations to resolve the ambiguity between:
- One-line pattern match: `arg in pattern`
- Case/in clause: `case expr; in pattern; end`

When seeing `arg in`, the parser should shift to parse `arg in pattern`
as a complete expression (matching CRuby behavior), not reduce `arg`
to start a case clause.

Changes:
- Add keyword_in to %nonassoc precedence declarations
- Add %prec tLOWEST to the plain `arg` reduction rule

This eliminates all bison shift-reduce conflicts (was 2, now 0).

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 17:56:54 +09:00
Yukihiro "Matz" Matsumoto a91ffcfe0d mruby-compiler: fix as-pattern parsing with symbol values
Remove non-standard `symbol tASSOC p_as` rule from hash pattern
elements. This rule conflicted with the as-pattern rule and caused
`:foo => x` to be incorrectly parsed as a hash pattern instead of
an as-pattern.

CRuby only supports label syntax (foo:) for hash pattern keys,
not hashrocket syntax (:foo =>). This change aligns mruby with
CRuby behavior and reduces bison shift-reduce conflicts from 2 to 1.

Before: `case :foo; in :foo => x; end` raised NoMethodError
After:  `case :foo; in :foo => x; end` binds x to :foo

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 13:42:19 +09:00
Yukihiro "Matz" Matsumoto 4fc81e8ea0 mruby-compiler: fix crash in pattern matching with string literal
The p_value grammar rule passed raw tSTRING token (a (len . str) cons
cell) directly to new_pat_value() without wrapping it as a proper AST
node. When codegen processed this malformed node, it read the length
field as the node type, causing misinterpretation and crash.

Wrap tSTRING with new_str(p, list1($1)) to create a proper NODE_STR,
consistent with how the primary:string rule handles strings.

Found by ClusterFuzz (oss-fuzz/mruby_fuzzer).

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 13:34:13 +09:00
Yukihiro "Matz" Matsumoto e19b107642 mruby-random: fix FPE in rand_i() for large ranges
When rand is called with a range exceeding UINT32_MAX (e.g.,
rand(2..4294967297)), the span value could overflow when cast
to uint32_t, causing division by zero in the modulo operation.

Add 64-bit path for MRB_INT64 builds that combines two 32-bit
randoms when the range exceeds 32 bits.

Found by ClusterFuzz (oss-fuzz/mruby_fuzzer).

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 12:54:32 +09:00
Yukihiro "Matz" Matsumoto 624272b15d mruby-bin-mirb: add syntax highlighting for keywords and strings
Add syntax highlighting to mirb's multi-line editor with support for:
- keywords (def, if, class, end, etc.) in magenta
- strings ("...", '...', %q{...}) in green
- comments (#...) in gray
- numbers (42, 3.14, 0xff) in cyan
- symbols (:foo) in yellow
- constants (Array, Foo) in bold yellow
- instance variables (@var) in blue
- global variables ($var) in bold blue

Features:
- auto-detects light/dark theme via COLORFGBG env var
- MIRB_THEME=light/dark for explicit override
- method calls like obj.class correctly not highlighted as keywords
- enabled automatically when terminal supports color

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 11:56:58 +09:00
Yukihiro "Matz" Matsumoto a866a5b0e6 mruby-sprintf: improve initial buffer size estimation
Estimate initial buffer size based on format string to reduce
reallocations. The new formula uses format string length plus
120 bytes base, plus 24 bytes per format specifier, capped at 4096.

This reduces reallocations by ~60% in typical use cases and
improves performance by 2-21% depending on output size.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 11:56:58 +09:00
Yukihiro "Matz" Matsumoto 4a97da33c3 mruby-bin-mirb: add UTF-8 multibyte character support
When MRB_UTF8_STRING is defined, mirb editor now properly handles
UTF-8 multibyte characters:
- cursor movement skips entire UTF-8 characters
- backspace/delete removes entire UTF-8 characters
- display cursor positioning accounts for wide characters (CJK)
- accepts UTF-8 byte sequences from terminal input

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 11:56:57 +09:00
John Bampton 12b128bf43 docs: fix pre-commit manual hooks; fix link
Ran `pre-commit run --all-files --hook-stage manual` and this ran prettier.

We had a Markdown table reformated and an backslash escape added.

A link was also fixed.

Tested both the standard and manual hooks pass
2025-12-28 20:28:00 +10:00
Yukihiro "Matz" Matsumoto 95ae434710 mirb_completion.c: fix strndup for MinGW
Use _WIN32 instead of _MSC_VER to provide strndup implementation
for all Windows compilers including MinGW/MSYS.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-26 21:21:20 +09:00
Yukihiro "Matz" Matsumoto d51a3504e8 mruby-bin-mirb: fix Windows/MSVC compilation warnings
- Define strdup as _strdup on MSVC to avoid deprecation warning
- Add strndup implementation for Windows (not available in MSVC)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-26 17:21:33 +09:00
Yukihiro "Matz" Matsumoto 2ae1160b39 mruby-array-ext: add Array#find and Array#rfind
Array#find is an optimized version of Enumerable#find for arrays,
using direct index access instead of each iterator.

Array#rfind finds from the end of the array, returning the first
match when scanning backwards.

Both methods support the ifnone parameter for default values.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:44 +09:00
Yukihiro "Matz" Matsumoto d1aa722a98 mruby-compiler: fix JMPNOT+JMP optimization for nested alternatives
The previous optimization for converting JMPNOT+JMP to JMPIF in
alternative patterns had two bugs:

1. It triggered incorrectly for nested alternatives like `1 | 2 | 3`
   (parsed as `((1|2)|3)`), causing memory corruption.

2. The chain end detection was wrong - it checked `prev_offset == 0`
   but the chain actually ends when `(pos+2) + offset == 0`.

Fix by:
- Only applying optimization when left pattern is not NODE_PAT_ALT
- Correctly detecting chain end by checking if next_addr == 0
- Properly unlinking the last JMPNOT from the fail chain

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:43 +09:00
Yukihiro "Matz" Matsumoto bbd5dd690b mruby-compiler: optimize pattern matching for array literals
When the match target is a known array literal, apply these optimizations:
1. Skip #deconstruct call - array literals are already arrays
2. Skip runtime #size check - verify size at compile time
3. Use GETIDX opcode instead of SEND :[] for element access

For the general (non-array-literal) case, improve efficiency by:
- Using EQ opcode instead of SEND :== for size comparison
- Using GE opcode instead of SEND :>= for minimum length check

This reduces bytecode size by ~27% for patterns like:
  [1,2] in Array|[Integer,Integer]

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:43 +09:00
Yukihiro "Matz" Matsumoto e042557d53 mruby-compiler: fix pattern matching === operand order bug
When matching array/hash element patterns like `[Integer]` against
values, the element register was being overwritten by codegen before
the comparison. This caused `[1] in [Integer]|[String]` to incorrectly
return false because the bytecode was effectively doing `1.===(Integer)`
instead of `Integer.===(1)`.

Fix by preserving the element value with push() before calling
codegen_pattern, so the element stays at cursp()-1 while the pattern
value is generated at cursp().

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-24 08:20:31 +09:00