diff --git a/mrbgems/mruby-regexp/include/re_internal.h b/mrbgems/mruby-regexp/include/re_internal.h index 6b4d3fe22..7f2bd9439 100644 --- a/mrbgems/mruby-regexp/include/re_internal.h +++ b/mrbgems/mruby-regexp/include/re_internal.h @@ -70,6 +70,8 @@ typedef struct mrb_regexp_pattern { uint16_t num_named; mrb_bool has_backref; /* true if pattern uses \1-\9 */ mrb_bool needs_backtrack; /* true if pattern needs backtracking engine */ + uint8_t *prefix; /* literal prefix bytes for fast skip (or NULL) */ + uint8_t prefix_len; /* length of prefix (0 = no prefix) */ } mrb_regexp_pattern; /* Regexp flags */ diff --git a/mrbgems/mruby-regexp/src/re_compile.c b/mrbgems/mruby-regexp/src/re_compile.c index e9b6e310c..e003b2b6d 100644 --- a/mrbgems/mruby-regexp/src/re_compile.c +++ b/mrbgems/mruby-regexp/src/re_compile.c @@ -776,6 +776,29 @@ re_compile(mrb_state *mrb, const char *pattern, mrb_int len, uint32_t flags) pat->has_backref = c.has_backref; pat->needs_backtrack = c.needs_backtrack; + /* Extract literal prefix for fast search skip. + Walk bytecode from the start, skipping SAVE, collecting RE_CHAR. */ + { + uint8_t pbuf[256]; + int plen = 0; + for (uint32_t i = 0; i < pat->code_len && plen < 255; i++) { + if (pat->code[i].op == RE_SAVE) continue; + if (pat->code[i].op == RE_CHAR) { + pbuf[plen++] = pat->code[i].a; + } + else break; + } + if (plen > 0) { + pat->prefix = (uint8_t*)mrb_malloc(mrb, plen); + memcpy(pat->prefix, pbuf, plen); + pat->prefix_len = (uint8_t)plen; + } + else { + pat->prefix = NULL; + pat->prefix_len = 0; + } + } + if (c.stripped) mrb_free(mrb, c.stripped); return pat; } @@ -787,6 +810,7 @@ re_free(mrb_state *mrb, mrb_regexp_pattern *pat) mrb_free(mrb, pat->code); mrb_free(mrb, pat->classes); mrb_free(mrb, pat->named_captures); + mrb_free(mrb, pat->prefix); mrb_free(mrb, pat); } } diff --git a/mrbgems/mruby-regexp/src/re_exec.c b/mrbgems/mruby-regexp/src/re_exec.c index 43ddfc759..965e22daa 100644 --- a/mrbgems/mruby-regexp/src/re_exec.c +++ b/mrbgems/mruby-regexp/src/re_exec.c @@ -10,6 +10,30 @@ #include "re_internal.h" #include +/* + * Skip to the next position where the pattern's literal prefix could match. + * Uses memchr on the first byte for fast scanning, then verifies the rest. + * Returns the found position, or NULL if no match is possible. + */ +static const char* +skip_to_prefix(const mrb_regexp_pattern *pat, const char *sp, const char *str_end) +{ + if (pat->prefix_len == 0) return sp; + + uint8_t first = pat->prefix[0]; + int plen = pat->prefix_len; + + while (sp + plen <= str_end) { + const char *found = (const char*)memchr(sp, first, str_end - sp); + if (!found || found + plen > str_end) return NULL; + if (plen == 1 || memcmp(found + 1, pat->prefix + 1, plen - 1) == 0) { + return found; + } + sp = found + 1; + } + return NULL; +} + /* Check if character matches a character class */ static mrb_bool class_match(const re_charclass *cc, uint8_t ch) @@ -226,6 +250,12 @@ pike_vm(mrb_state *mrb, const mrb_regexp_pattern *pat, for (; sp <= str_end; sp++) { if (!s.matched) { + /* Skip ahead using literal prefix when no active threads */ + if (pat->prefix_len > 0 && curr.count == 0) { + const char *skip = skip_to_prefix(pat, sp, str_end); + if (!skip) break; /* prefix not found anywhere; no match possible */ + sp = skip; + } int slot = match_only ? 0 : pool_alloc(&s); if (!match_only) memset(CAP(&s, slot), -1, sizeof(int) * ncap); s.gen++; @@ -493,6 +523,12 @@ backtrack_exec(mrb_state *mrb, const mrb_regexp_pattern *pat, int *caps = (int*)mrb_malloc(mrb, sizeof(int) * ncap); for (const char *sp = str + start; sp <= str_end; sp++) { + /* Skip ahead using literal prefix */ + if (pat->prefix_len > 0) { + const char *skip = skip_to_prefix(pat, sp, str_end); + if (!skip) break; + sp = skip; + } memset(caps, -1, sizeof(int) * ncap); int steps = 0;