Commit Graph

11 Commits

Author SHA1 Message Date
Yukihiro "Matz" Matsumoto 726d8febf3 mruby-regexp: cache Pike VM state in pattern to avoid per-exec malloc
Pre-allocate visited array and thread lists at compile time and
reuse them across re_exec calls. A cache_in_use flag detects
re-entrancy and falls back to malloc when needed.

Eliminates 3 malloc + 3 free per NFA execution for the common
non-re-entrant case. Combined with the literal fast path, brings
literal match? from 4.7x to 2.5x vs CRuby.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:34 +09:00
Yukihiro "Matz" Matsumoto 034a64346c mruby-regexp: add literal pattern fast path bypassing NFA
Pure literal patterns (/hello/, /abc/) are detected at compile
time and matched using memchr+memcmp directly, completely
bypassing Pike VM setup (no malloc, no visited array, no thread
lists). Engine-only time drops from ~400ns to ~80ns.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:34 +09:00
Yukihiro "Matz" Matsumoto 337b5906a2 mruby-regexp: add first-byte bitmap for fast position skipping
Compute a 128-bit bitmap of bytes that could start a match.
For patterns like /cat|dog|fox/ the bitmap contains only {b,c,d,f},
skipping positions where no alternative can match. For /\d+/ only
{'0'-'9'} are set.

Used when no literal prefix is available (alternation, character
class patterns). Falls back gracefully when too many bytes match.

Key improvements vs CRuby ratio:
  alternation miss: 9.5x -> 3.6x
  \d+ medium string: 3.0x -> 1.4x

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 1641c8c06f mruby-regexp: add literal prefix skip for fast string search
Extract the leading literal bytes from compiled bytecode and use
memchr + memcmp to skip positions where the prefix cannot match.
Both Pike VM and backtracking engine benefit.

For /needle/ in a 2006-char string: 29x faster (1.97s -> 0.07s),
now on par with CRuby/Oniguruma.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 355c68f487 mruby-regexp: rename has_nongreedy to needs_backtrack
The flag is set not only for non-greedy quantifiers but also for
lookahead, lookbehind, and backreferences. The new name accurately
reflects its purpose: indicating that the backtracking engine is
required instead of the Pike VM.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:33 +09:00
Yukihiro "Matz" Matsumoto 101f8c69a1 mruby-regexp: implement fixed-length lookbehind assertions
Add (?<=...) positive and (?<!...) negative lookbehind support.
The sub-pattern must have a fixed byte length (no quantifiers or
alternation), computed at compile time and stored in the instruction.
At execution time, the engine backs up by that many bytes and runs
the sub-pattern forward. Maximum lookbehind length is 255 bytes.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:32 +09:00
Yukihiro "Matz" Matsumoto 78d761addf mruby-regexp: implement extended mode (x flag)
The x flag ignores unescaped whitespace and #comments in patterns,
making complex regexps more readable. Whitespace inside character
classes [...] remains literal. Implemented as a preprocessing step
that strips whitespace/comments before compilation.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:30 +09:00
Yukihiro "Matz" Matsumoto 3c68e49178 mruby-regexp: add lookahead assertions (?=...) and (?!...)
implement positive and negative lookahead in the backtracking engine:
- (?=pattern): succeeds if pattern matches at current position
  without consuming characters
- (?!pattern): succeeds if pattern does NOT match at current position

new bytecodes RE_LOOKAHEAD and RE_NEG_LOOKAHEAD implemented in
the backtracking engine via nested bt_match calls.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:29 +09:00
Yukihiro "Matz" Matsumoto 7283560215 mruby-regexp: add named captures (?<name>...)
support named capture groups in patterns:
- compiler parses (?<name>...) syntax and builds name table
- MatchData#[:name] and MatchData#["name"] access by name
- MatchData#named_captures returns {name => value} hash
- Regexp#named_captures returns {name => group_number} hash
- named captures stored in mrb_regexp_pattern for GC safety

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:28 +09:00
Yukihiro "Matz" Matsumoto 8d92379d7c mruby-regexp: fix non-greedy quantifiers (*?, +?, ??)
non-greedy patterns now correctly match the shortest possible
string. patterns with non-greedy quantifiers are dispatched to
the backtracking engine which naturally handles non-greedy
semantics.

the Pike VM continues to be used for purely greedy patterns
(O(n*m) guarantee).

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:28 +09:00
Yukihiro "Matz" Matsumoto 1cfa153ff3 mruby-regexp: add built-in regexp engine with Pike VM
implement a lightweight NFA-based regular expression engine for mruby:

engine (src/re_compile.c, src/re_exec.c, src/re_utf8.c):
- Pike VM (Thompson NFA simulation) with O(n*m) time guarantee
- ReDoS-resistant by design (no backtracking for basic patterns)
- supports: literals, ., *, +, ?, {n,m}, [], [^], |, ()
- character classes: \d, \w, \s and negations
- anchors: ^, $, \A, \z, \Z, \b, \B
- flags: i (ignorecase), m (multiline/dotall)
- captures with MatchData

Ruby API (src/regexp.c, mrblib/string_regexp.rb):
- Regexp.new, #match, #match?, #=~, #===, #source, #inspect
- Regexp.escape, Regexp::IGNORECASE/MULTILINE constants
- MatchData#[], #captures, #to_a, #begin, #end, #pre_match, #post_match
- String#match, #match?, #=~, #sub, #gsub, #scan, #split

~1700 lines of C + ~120 lines of Ruby. no external dependencies.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:27 +09:00