Commit Graph

18123 Commits

Author SHA1 Message Date
John Bampton 95b74eefdf pre-commit: add hook to ensure Makefiles are indented with tabs
Manual hook added which runs a shell script.

Can be run locally manually by Linux or Mac users and we have this also running on the GitHub Actions CI.

Will fail if Makefiles uses spaces for indentation.

Can run the manual hooks with:

`pre-commit run check-makefile-indentation --all-files --hook-stage manual`
2025-12-30 03:30:04 +10: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 b8f05f0752 benchmark: optimize bm_so_mandelbrot.rb
Replace for..in loops with while loops to avoid closure overhead.
Cache constants in local variables to avoid repeated lookups.
59% faster (7.4s -> 3.0s).

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 12:28:02 +09:00
Yukihiro "Matz" Matsumoto 8a5283cf4e benchmark: terminal version of mandelbrot
Optimized to avoid Math.sqrt by squaring the threshold
(sqrt(x) < 1000 => x < 1000000) and caching zr*zr/zi*zi
to avoid redundant computation. 29% faster than naive version.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-29 11:56:58 +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
Yukihiro "Matz" Matsumoto a16dd941c7 Merge pull request #6689 from jbampton/run-manual-hooks 2025-12-29 11:56:46 +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 e4496f9bbd Merge pull request #6688 from mruby/dependabot/github_actions/super-linter/super-linter-8.3.2 2025-12-27 09:22:42 +09: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
dependabot[bot] b55169fbce build(deps): bump super-linter/super-linter from 8.3.1 to 8.3.2
Bumps [super-linter/super-linter](https://github.com/super-linter/super-linter) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/super-linter/super-linter/releases)
- [Changelog](https://github.com/super-linter/super-linter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/super-linter/super-linter/compare/v8.3.1...v8.3.2)

---
updated-dependencies:
- dependency-name: super-linter/super-linter
  dependency-version: 8.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 14:01:13 +00:00
Yukihiro "Matz" Matsumoto 6f32125178 NEWS.md: add release notes for mruby 3.5
- rename NEWS to NEWS.md with markdown format
- document pattern matching (case/in) feature
- document new gems (mruby-task, mruby-benchmark, mruby-strftime)
- document mirb improvements
- document HAL platform abstraction
- document C API changes
- list fixed GitHub issues
- list 101 merged pull requests from community contributors
- list security fixes

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:44 +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 b841bd7439 doc/internal/opcode.md: document OP_GETIDX/OP_SETIDX optimization
Add notes section explaining the optimization behavior:
- Which functions are used for direct access
- When fallback to method dispatch occurs
- Why subclasses can override []/[]=

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:44 +09:00
Yukihiro "Matz" Matsumoto 8a29ab591e vm.c: use mrb_obj_ptr()->c for faster class check in GETIDX/SETIDX
Replace mrb_obj_class() with direct mrb_obj_ptr(va)->c access:

- Skips unnecessary mrb_immediate_p() check (these types are never immediate)
- Skips mrb_class_real() traversal for singleton classes
- Objects with singleton methods now fall back to method dispatch
  (correct behavior since they might have overridden []/[]=)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:44 +09:00
Yukihiro "Matz" Matsumoto 115438aa1e vm.c: optimize OP_SETIDX for Array and Hash; ref #6675
Add inline optimizations for Array#[]= and Hash#[]= in OP_SETIDX,
matching the pattern established for OP_GETIDX:

- Array class: use mrb_ary_set() directly (integer index only)
- Hash class: use mrb_hash_set() directly
- Subclasses: fall back to method dispatch (can override []=)
- String: unchanged (complex 2-3 argument signature)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:44 +09:00
Yukihiro "Matz" Matsumoto 5102ef8022 vm.c: allow String subclasses to override []; ref #6675
Apply the same pattern as the Array/Hash fix: the OP_GETIDX optimization
now only applies to instances of the String class itself. Subclasses
fall back to method dispatch, allowing them to override the [] method.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:44 +09:00
Yukihiro "Matz" Matsumoto d65fb765ef vm.c: allow Array subclasses to override []; ref #6675
Apply the same pattern as the Hash fix: the OP_GETIDX optimization
now only applies to instances of the Array class itself. Subclasses
fall back to method dispatch, allowing them to override the [] method.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:43 +09:00
Yukihiro "Matz" Matsumoto 35af869d7d vm.c: allow Hash subclasses to override []; close #6675
The OP_GETIDX optimization now only applies to instances of the Hash
class itself. Subclasses fall back to method dispatch, allowing them
to override the [] method. This fixes compatibility with libraries
like mruby-hashie that rely on aliasing/overriding [] in subclasses.

Trade-off: Hash#[] cannot be overridden on the Hash class itself
(only on subclasses). This is a reasonable semantic for mruby since
subclassing is the proper pattern for customization.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-25 18:01:43 +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
Yukihiro "Matz" Matsumoto 42cea9d3ee mruby-compiler: optimize JMPNOT+JMP to JMPIF in alternative patterns
In alternative patterns (e.g., `Integer|String`), when the left pattern
has a single JMPNOT immediately before the JMP to success, convert the
JMPNOT to JMPIF and eliminate the JMP instruction.

This saves 3 bytes per optimized alternative pattern.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-24 07:45:40 +09:00
Yukihiro "Matz" Matsumoto fd01a0af73 mruby-compiler: optimize array literal pattern matching
When pattern matching an array literal against an array pattern with
matching sizes (e.g., `[1,2] => a,b`), skip the runtime calls to
#deconstruct and #size. Instead, directly extract elements using the
VM's GETIDX opcode.

This reduces bytecode from 72 to 28 bytes (61% reduction) and
eliminates 4 method calls per pattern match.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-24 07:35:55 +09:00
Yukihiro "Matz" Matsumoto 2b72d8a7c1 mruby-compiler: optimize JMPNOT+JMP to JMPIF in pattern matching
For patterns with a single failure check (like `1 => String`), invert
JMPNOT to JMPIF and eliminate the following JMP instruction.

Before: JMPNOT fail; JMP end; fail: error; end: (8 bytes for jumps)
After:  JMPIF end; error; end: (4 bytes for jump)

The optimization only applies when:
1. There's exactly one JMPNOT in the failure chain
2. The JMPNOT is immediately before the JMP (no code between)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 20:12:28 +09:00
Yukihiro "Matz" Matsumoto 3af15e0012 mruby-compiler: optimize simple variable pattern to match assignment
For `1 => a`, generate the same bytecode as `a = 1` by leveraging
gen_move's peephole optimization. The peephole optimizer rewrites
LOADI+MOVE into a single LOADI to the target register.

Before: LOADI_1 R2; MOVE R1 R2 (6 bytes)
After:  LOADI_1 R1 (3 bytes)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 15:28:15 +09:00
Yukihiro "Matz" Matsumoto 9d4ad58adc mruby-compiler: optimize pattern matching for always-matching patterns
Skip generating failure handling code (JMP, GETCONST, STRING, SEND,
RAISEIF) when the pattern always succeeds (e.g., simple variable
binding like `1 => a`).

Before: 27 bytes for `1 => a; p a`
After: 13 bytes (52% reduction)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 15:19:21 +09:00
Yukihiro "Matz" Matsumoto 3a012a7b45 TODO.md: add amalgamation
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 13:57:34 +09:00
Yukihiro "Matz" Matsumoto e06ec699a4 mruby-compiler: rename get_node_type to node_type
Resolve static function name collision between parse.y and codegen.c
for amalgamation support.

- parse.y: rename get_node_type() to node_type() (keeps validation)
- codegen.c: replace with node_type() macro (NULL-safe via NODE_TYPE)
- node.h: rename VAR_NODE_TYPE() to NODE_TYPE()

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 12:27:12 +09:00
Yukihiro "Matz" Matsumoto acc82da637 codegen.c: rename new_sym to sym_idx
The function registers a symbol in the IREP symbol table and returns
its index. The new name better reflects this behavior and avoids
collision with parse.y's new_sym (which creates AST nodes).

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 11:44:22 +09:00
Yukihiro "Matz" Matsumoto e369bb3475 mruby-sprintf: rename get_format_info to get_fmt_spec
Avoid static function name collision with mruby-pack for future
amalgamation support.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 11:27:47 +09:00
Yukihiro "Matz" Matsumoto 587561100e mruby-pack: rename get_format_info to pack_format_info
Avoid static function name collision with mruby-sprintf for future
amalgamation support.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 11:02:34 +09:00
Yukihiro "Matz" Matsumoto 768a1f7752 string.c: add mrb_strcasecmp_p for case-insensitive comparison
Move casecmp_p from mruby-string-ext and mruby-encoding to core as
mrb_strcasecmp_p (predicate function returning mrb_bool). Add
MRB_STR_CASECMP_P macro to internal.h for comparing mrb_value strings
with literal strings.

This eliminates code duplication and avoids static function name
collision for future amalgamation support.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 10:41:21 +09:00
Yukihiro "Matz" Matsumoto ce570c28bb mruby-rational: inline int_lshift into rational_new_f
Remove the static int_lshift function and directly call mrb_bint_lshift
at the only call site. This simplifies the code and avoids static
function name collision with src/numeric.c for future amalgamation
support.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 08:50:30 +09:00
Yukihiro "Matz" Matsumoto 3d0b5c848a mruby-eval: rename exec_irep to eval_irep
Avoid static function name collision with src/vm.c for future
amalgamation support.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 08:34:52 +09:00
Yukihiro "Matz" Matsumoto d5c93fc724 class.c, variable.c: rename bsearch_idx to avoid name collision
Rename static bsearch_idx functions to disambiguate:
- class.c: mt_bsearch_idx (method table)
- variable.c: iv_bsearch_idx (instance variable table)

This prepares for future amalgamation support.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-23 08:32:23 +09:00
Yukihiro "Matz" Matsumoto ca364e3f3e emscripten.rake: use native WASM exception handling
Enable -fwasm-exceptions and -sSUPPORT_LONGJMP=wasm for the Emscripten
toolchain. This implements setjmp/longjmp using native WebAssembly
exception handling instructions instead of Asyncify-based emulation.

Benefits:
- Minimal memory overhead (no shadow stack buffer needed)
- No code size penalty
- Works with both C and C++ code

WASM exception handling is supported by all major browsers since 2021-2022
(Chrome 95+, Firefox 100+, Safari 15.2+) and standalone runtimes
(Node.js 17+, Wasmtime, Wasmer).

For older runtimes, users can override with CFLAGS/LDFLAGS environment
variables.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 18:02:33 +09:00
Yukihiro "Matz" Matsumoto 98c33acc75 mruby.h: fix build with MRB_NO_METHOD_CACHE
mrb_method_cache_clear() was called unconditionally from class.c and
state.c, but the function definition was guarded by MRB_NO_METHOD_CACHE.
This caused linker errors when building with MRB_NO_METHOD_CACHE defined.

Add empty macro definition when MRB_NO_METHOD_CACHE is defined, matching
the existing pattern used for mrb_mc_clear_by_class().

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 13:55:42 +09:00
Yukihiro "Matz" Matsumoto 58604ba1d5 mruby-bin-mirb: extract is_line_blank() helper function
Consolidate duplicated blank line check logic from two places in the
ENTER key handler into a single helper function.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 11:24:12 +09:00
Yukihiro "Matz" Matsumoto c777762c9a mruby-bin-mirb: remove unused cursor save/restore functions
mirb_term_save_cursor() and mirb_term_restore_cursor() were never
called anywhere in the codebase.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 11:22:22 +09:00
Yukihiro "Matz" Matsumoto c418fc90a8 mruby-bin-mirb: add mirb_buffer_to_string_upto_line()
move buffer_to_string_upto_line() from mirb_editor.c to mirb_buffer.c
as a public API. mirb_buffer_to_string() now delegates to this function.
this eliminates code duplication and provides proper module encapsulation.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 10:28:03 +09:00
Yukihiro "Matz" Matsumoto c09fbbd262 mirb_editor.c: extract is_dedent_keyword() helper
consolidate duplicated dedenting keyword detection logic that was
repeated in reindent_line(), handle_tab_indent(), and handle_key().
the helper checks for end, else, elsif, when, in, rescue, ensure, and }.

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-22 09:13:51 +09:00