Files
mruby-mruby/mrbgems/mruby-regexp
Yukihiro "Matz" Matsumoto 9d8d41006b mruby-regexp: alias original String#split before overriding it
`class String; def split` in mruby-regexp/mrblib/string_regexp.rb
replaced the C-defined String#split rather than overriding it, so
the in-Ruby `return super if pattern.nil?` paths raised
NoMethodError for any `"x".split(...)` call once mruby-regexp was
loaded (the default full-core production binary).

The regression wasn't caught by the test suite because per-gem
tests run under mrb_open_core() with only the gem's dep_list, so
the broken override is never visible from test/t/string.rb
(mruby-test, not mruby-regexp).

Add `alias __split split` at the top of the override class body,
which captures the C-defined method, and change the Ruby override
to delegate via `__split(pattern, limit)` for non-regexp fallback
paths. Add a bintest under mruby-bin-mruby that runs through
bin/mruby (full gem load) to catch this regression class.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-11 12:45:43 +09:00
..

mruby-regexp

Built-in regular expression engine for mruby using a Pike VM (NFA simulation) with backtracking fallback.

Features

Pattern Syntax

  • . any character (except newline by default)
  • *, +, ? greedy quantifiers
  • *?, +?, ?? non-greedy quantifiers
  • {n}, {n,}, {n,m} repetition counts
  • [abc], [a-z], [^abc] character classes
  • \d, \w, \s digit, word, whitespace shortcuts
  • \D, \W, \S negated shortcuts
  • (...) capture group
  • (?:...) non-capturing group
  • (?<name>...) named capture group
  • | alternation
  • \1-\9 backreferences
  • (?=...) positive lookahead
  • (?!...) negative lookahead
  • (?<=...) positive lookbehind (fixed-length only)
  • (?<!...) negative lookbehind (fixed-length only)

Anchors

  • ^ beginning of line
  • $ end of line
  • \A beginning of string
  • \z end of string
  • \Z end of string (or before final newline)
  • \b word boundary
  • \B non-word boundary

Flags

  • i (Regexp::IGNORECASE) case-insensitive matching (ASCII)
  • m (Regexp::MULTILINE) . matches newline; ^/$ match at line boundaries
  • x (Regexp::EXTENDED) free-spacing mode; unescaped whitespace ignored, # starts comments

Ruby API

# Regexp
re = Regexp.new("pattern", Regexp::IGNORECASE)
re = /pattern/i                   # literal syntax
re.match("string")                # => MatchData or nil
re.match?("string")               # => true/false
re =~ "string"                    # => index or nil
re === "string"                   # => true/false (for case/when)
re.source                         # => "pattern"
re.options                        # => flags integer
Regexp.escape("a.b")              # => "a\\.b"
Regexp.last_match(n)              # => nth capture from last match

# MatchData
md = /(\w+)@(\w+)/.match("user@host")
md[0]                             # => "user@host" (full match)
md[1]                             # => "user"
md[2]                             # => "host"
md[:name]                         # named capture access
md.captures                       # => ["user", "host"]
md.to_a                           # => ["user@host", "user", "host"]
md.begin(0)                       # => match start position
md.end(0)                         # => match end position
md.pre_match                      # => string before match
md.post_match                     # => string after match
md.named_captures                 # => {"name" => "value", ...}

# String methods
str.match(re)                     # => MatchData or nil
str.match?(re)                    # => true/false
str =~ re                         # => index or nil
str.sub(re, replacement)          # replace first occurrence
str.sub(re) { |m| ... }           # replace with block
str.gsub(re, replacement)         # replace all occurrences
str.gsub(re) { |m| ... }          # replace all with block
str.scan(re)                      # => array of matches
str.split(re)                     # => array of parts

# Global variables
$~                                # last MatchData

Engine Architecture

The gem uses two execution engines:

Pike VM (NFA simulation): Used for patterns without backreferences, non-greedy quantifiers, or lookahead. Guarantees O(pattern x text) time complexity, making it immune to ReDoS attacks.

Backtracking engine: Used when patterns contain \1-\9 backreferences, non-greedy quantifiers (*?, +?, ??), or lookahead assertions ((?=...), (?!...)). Protected by a configurable step limit (MRB_REGEXP_STEP_LIMIT, default 1M) to prevent excessive backtracking.

The engine is selected automatically at compile time based on pattern analysis.

Limitations

  • Fixed-length lookbehind only: (?<=...) and (?<!...) require a fixed-length pattern (no *, +, ?, or alternation). Maximum 255 bytes.
  • No Unicode properties: \p{Alpha}, \p{L}, etc. are not supported.
  • ASCII case folding only: The i flag handles ASCII letters only.
  • Step limit on backtracking: Patterns that require the backtracking engine are subject to a step limit.

Configuration

/* Maximum step count for backtracking engine (ReDoS protection) */
#ifndef MRB_REGEXP_STEP_LIMIT
#define MRB_REGEXP_STEP_LIMIT 1000000
#endif

License

MIT License. See the mruby license file for details.

This gem is an original implementation. No external regexp library code is used.