From d71d7527a8be8654560867d761036598408bfe14 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Mon, 29 Jun 2026 19:14:03 +0200 Subject: implemented tere_set_is_interrupted_cb() Allows you to set up a callback that's invoked repeatedly during matching. If it returns non-0 matching will abort and REG_EINTR is returned. Allows you to interrupt long-running regexp executions that would otherwise block the UI or hang the entire application. --- README.md | 8 +++----- regcomp.c | 11 +++++++++++ rege_dfa.c | 12 ++++++++++++ regerrs.h | 1 + regex.h | 2 ++ regexec.c | 5 +++++ regguts.h | 5 +++++ 7 files changed, 39 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c35480e..6dbfbca 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,12 @@ Compared to hsrex, this library has the following changes: `tere_comp()` compilation flag. * Support the `REG_ANCHORED` flag for `tere_exec()`. * `tere_free()` ignores nullified `regex_t` objects. +* `tere_set_is_interrupted_cb()` allows configuring a repeatedly + invoked callback. If it returns true (non-null) matching aborts + immediately and `tere_exec()` will return `REG_EINTR`. ## TODO -* Hook into the matching algorithm. - Even though runtime cannot be as catastrophically bad as in pure - backtracking engines like PCRE, `tere_exec()` calls can still be slow - on extremely large texts. - The hook allows interruptions. * Expose enough API to swap out the regular expression lexer. Useful for custom DSLs like glob patterns or TECO patterns. * Support splitting the subject string into two halves, so we can diff --git a/regcomp.c b/regcomp.c index bd332dc..e6c9f94 100644 --- a/regcomp.c +++ b/regcomp.c @@ -272,6 +272,17 @@ static struct fns functions = { rfree, /* regfree insides */ }; +/* + - tere_set_is_interrupted_cb - set is_interrupted callback + ^ void tere_set_is_interrupted_cb(regex_t *, int (*cb)(void)); + */ +void +tere_set_is_interrupted_cb( + regex_t *re, int (*cb)(void)) +{ + ((struct fns *)re->re_fns)->is_interrupted = cb; +} + /* - compile - compile regular expression ^ int compile(regex_t *, const chr *, size_t, int); diff --git a/rege_dfa.c b/rege_dfa.c index a055921..8c6f2fc 100644 --- a/rege_dfa.c +++ b/rege_dfa.c @@ -101,6 +101,9 @@ longest( if (ss->flags & NOPROGRESS && v->eflags & REG_ANCHORED) { return NULL; } + if (IS_INTERRUPTED(v->re)) { + return NULL; + } cp = nextchr(v, cp); ss->lastseen = cp; css = ss; @@ -119,6 +122,9 @@ longest( if (ss->flags & NOPROGRESS && v->eflags & REG_ANCHORED) { return NULL; } + if (IS_INTERRUPTED(v->re)) { + return NULL; + } cp = nextchr(v, cp); ss->lastseen = cp; css = ss; @@ -240,6 +246,9 @@ shortest( if (ss->flags & NOPROGRESS && v->eflags & REG_ANCHORED) { return NULL; } + if (IS_INTERRUPTED(v->re)) { + return NULL; + } cp = nextchr(v, cp); ss->lastseen = cp; css = ss; @@ -261,6 +270,9 @@ shortest( if (ss->flags & NOPROGRESS && v->eflags & REG_ANCHORED) { return NULL; } + if (IS_INTERRUPTED(v->re)) { + return NULL; + } cp = nextchr(v, cp); ss->lastseen = cp; css = ss; diff --git a/regerrs.h b/regerrs.h index 259c0cb..6d23e42 100644 --- a/regerrs.h +++ b/regerrs.h @@ -17,3 +17,4 @@ { REG_MIXED, "REG_MIXED", "character widths of regex and string differ" }, { REG_BADOPT, "REG_BADOPT", "invalid embedded option" }, { REG_ETOOBIG, "REG_ETOOBIG", "nfa has too many states" }, +{ REG_EINTR, "REG_EINTR", "interrupted" }, diff --git a/regex.h b/regex.h index 5568ed7..d48f20e 100644 --- a/regex.h +++ b/regex.h @@ -290,6 +290,7 @@ typedef struct { #define REG_MIXED 17 /* character widths of regex and string differ */ #define REG_BADOPT 18 /* invalid embedded option */ #define REG_ETOOBIG 19 /* nfa has too many states */ +#define REG_EINTR 20 /* interrupted by is_interrupted() callback */ /* two specials for debugging and testing */ #define REG_ATOI 101 /* convert error-code name to number */ #define REG_ITOA 102 /* convert error-code number to name */ @@ -320,6 +321,7 @@ MODULE_SCOPE int __REG_WIDE_EXEC(regex_t *, __REG_CONST __REG_WIDE_T *, size_t, #endif MODULE_SCOPE re_void regfree(regex_t *); MODULE_SCOPE size_t regerror(int, __REG_CONST regex_t *, char *, size_t); +MODULE_SCOPE void tere_set_is_interrupted_cb(regex_t *, int (*cb)(void)); /* automatically gathered by fwd; do not hand-edit */ /* =====^!^===== end forwards =====^!^===== */ diff --git a/regexec.c b/regexec.c index aadb382..ae6ca77 100644 --- a/regexec.c +++ b/regexec.c @@ -328,6 +328,11 @@ exec( memcpy(VS(pmatch), VS(v->pmatch), n*sizeof(regmatch_t)); } + if (IS_INTERRUPTED(re)) { + /* normalize return code in case of interruptions */ + st = REG_EINTR; + } + /* * Clean up. */ diff --git a/regguts.h b/regguts.h index 0fd46c4..e941c28 100644 --- a/regguts.h +++ b/regguts.h @@ -385,8 +385,13 @@ struct subre { struct fns { VOID FUNCPTR(free, (regex_t *)); + int FUNCPTR(is_interrupted, (void)); }; +#define IS_INTERRUPTED(re) \ + (((struct fns *)(re)->re_fns)->is_interrupted && \ + (*((struct fns *)(re)->re_fns)->is_interrupted)()) + /* * the insides of a regex_t, hidden behind a void * */ -- cgit v1.2.3