concept.md described the bounded backtracking engine (7.3) as plain recursive backtracking, with the Regular engine's linear-time guarantee reserved for patterns the streaming Pike VM (7.2) handles. v1 has no Pike VM yet, so every pattern actually runs on 7.3's engine; the previous commit added memoization and precomputed skip-ahead tables to that engine specifically because plain backtracking search was quadratic even for ordinary patterns, not just exponential for pathological ones. Added Section 7.5 recording that finding precisely: what the two techniques buy (linear time for the backreference-free majority, quadratic instead of exponential for the textbook (a+)+b shape), what they do not buy (7.2's bounded, input-length-independent memory guarantee, still the correct target for that axis), and citations to the published techniques they match. Added a short note to the Section 10 complexity table pointing at 7.5 so the table is read as the two engines' designed guarantees, not conflated with what a specific implementation measures. Updated regexx.c/regexx.h's top-of-file comments, which still described a plain backtracking engine. No code changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EjuMk8kY9SDus1wWe2K9xY
138 lines
7.0 KiB
C
138 lines
7.0 KiB
C
/* regexx.h - public API for regexx, a single-file C regex interpreter
|
|
* with Python `re` semantics. See concept.md for the design rationale
|
|
* and Section 9 in particular for the naming convention this header
|
|
* follows: every identifier with a direct Python `re` counterpart uses
|
|
* that counterpart's exact spelling.
|
|
*
|
|
* Implementation status relative to concept.md: this is the v1
|
|
* implementation. It provides the full public API and pattern syntax
|
|
* described below, executed by a single recursive backtracking engine
|
|
* (concept.md Section 7.3) over a fully materialized copy of the
|
|
* input, augmented with memoization and precomputed skip-ahead tables
|
|
* (regexx.c: run_memo, compute_maxrun, compute_next_prevmatch) that
|
|
* make the search-family functions (Pattern_search/finditer/split/sub)
|
|
* run in linear time for the common, backreference-free case rather
|
|
* than the quadratic time a plain backtracking search would have. The
|
|
* streaming, bounded-memory regular engine of Section 7.2 is still not
|
|
* implemented; see README.md "Implementation status" for the complete,
|
|
* itemized, measured account of what v1 does and does not cover.
|
|
*/
|
|
#ifndef REGEXX_H
|
|
#define REGEXX_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* ---- Flags (re.compile flags; bitwise OR, exact Python names) ---- */
|
|
#define IGNORECASE 0x0001
|
|
#define MULTILINE 0x0002
|
|
#define DOTALL 0x0004
|
|
#define VERBOSE 0x0008
|
|
#define ASCII 0x0010
|
|
#define UNICODE 0x0020 /* default for UTF8 mode; explicit for clarity */
|
|
#define LOCALE 0x0040
|
|
#define DEBUG 0x0080
|
|
|
|
/* Encoding mode, not a Python flag (Section 9.3): exactly one required. */
|
|
#define BINARY 0x0100
|
|
#define UTF8 0x0200
|
|
/* ASCII (0x0010) doubles as the ASCII *encoding* mode when neither
|
|
* BINARY nor UTF8 is given; see regexx.c encoding-mode resolution. */
|
|
|
|
/* ---- Types ---- */
|
|
|
|
typedef struct Pattern Pattern; /* re.Pattern */
|
|
typedef struct Match Match; /* re.Match */
|
|
typedef struct Input Input; /* no Python counterpart, see concept.md 9.0/9.2 */
|
|
|
|
typedef void (*MatchIterCb)(void *ctx, const Match *m);
|
|
typedef void (*MatchSubCb)(void *ctx, const Match *m, char **out, size_t *outlen);
|
|
|
|
typedef struct PatternError PatternError; /* re.error / re.PatternError */
|
|
struct PatternError {
|
|
const char *msg; /* re.error.msg */
|
|
const char *pattern; /* re.error.pattern */
|
|
int64_t pos; /* re.error.pos */
|
|
int64_t lineno; /* re.error.lineno */
|
|
int64_t colno; /* re.error.colno */
|
|
};
|
|
/* msg and pattern are heap allocated by whichever call filled this
|
|
* struct in; call PatternError_free once you are done reading it
|
|
* (safe to call on a zero-initialized or already-freed PatternError). */
|
|
void PatternError_free(PatternError *err);
|
|
|
|
struct Pattern {
|
|
const char *pattern; /* re.Pattern.pattern */
|
|
int flags; /* re.Pattern.flags */
|
|
int groups; /* re.Pattern.groups */
|
|
void *groupindex; /* re.Pattern.groupindex, name -> group number */
|
|
void *program; /* compiled bytecode, private */
|
|
};
|
|
|
|
struct Match {
|
|
Pattern *re; /* re.Match.re */
|
|
Input *string; /* re.Match.string */
|
|
int64_t pos, endpos; /* re.Match.pos, re.Match.endpos */
|
|
int lastindex; /* re.Match.lastindex */
|
|
const char *lastgroup; /* re.Match.lastgroup */
|
|
void *slots; /* private */
|
|
};
|
|
|
|
/* ---- Input construction (no Python counterpart) ---- */
|
|
Input *Input_from_buffer(const uint8_t *buf, size_t len); /* does not copy or take ownership */
|
|
Input *Input_from_file(const char *path, PatternError *err); /* seekable or not (pipes, "-" via /dev/stdin, etc. all work) */
|
|
void Input_free(Input *in);
|
|
|
|
/* ---- Pattern methods (bound to an already compiled Pattern) ---- */
|
|
int Pattern_match(Pattern *self, Input *string, int64_t pos, int64_t endpos, Match *out);
|
|
int Pattern_fullmatch(Pattern *self, Input *string, int64_t pos, int64_t endpos, Match *out);
|
|
int Pattern_search(Pattern *self, Input *string, int64_t pos, int64_t endpos, Match *out);
|
|
int Pattern_finditer(Pattern *self, Input *string, int64_t pos, int64_t endpos, MatchIterCb cb, void *ctx);
|
|
int Pattern_findall(Pattern *self, Input *string, int64_t pos, int64_t endpos, MatchIterCb cb, void *ctx);
|
|
/* cb fires once per element of the list re.split() would return, in
|
|
* order; read each element's text via Match_group(m, NULL, 0, ...). */
|
|
int Pattern_split(Pattern *self, Input *string, int maxsplit, MatchIterCb cb, void *ctx);
|
|
int Pattern_sub(Pattern *self, Input *string, const char *repl, MatchSubCb cb, void *ctx, int count, char **out, size_t *outlen);
|
|
int Pattern_subn(Pattern *self, Input *string, const char *repl, MatchSubCb cb, void *ctx, int count, char **out, size_t *outlen, int *n);
|
|
void Pattern_free(Pattern *self);
|
|
|
|
/* Looks up a named group in re.Pattern.groupindex; returns its 1-based
|
|
* group number, or -1 if no group by that name exists. There is no
|
|
* enumeration function for groupindex as a whole in this build: a
|
|
* caller that needs every name must track the names it used to build
|
|
* the pattern itself, since Pattern->groupindex is otherwise opaque. */
|
|
int Pattern_groupindex_lookup(Pattern *self, const char *name);
|
|
|
|
/* ---- Match accessors ---- */
|
|
int Match_group(Match *self, const char *name_or_null, int index, const char **out, size_t *outlen);
|
|
int64_t Match_start(Match *self, int group);
|
|
int64_t Match_end(Match *self, int group);
|
|
void Match_span(Match *self, int group, int64_t *start, int64_t *end);
|
|
int64_t Match_start_byte(Match *self, int group);
|
|
int64_t Match_end_byte(Match *self, int group);
|
|
void Match_span_byte(Match *self, int group, int64_t *start, int64_t *end);
|
|
void Match_free(Match *self);
|
|
|
|
/* ---- Module level functions ---- */
|
|
Pattern *re_compile(const char *pattern, size_t len, int flags, PatternError *err);
|
|
int re_match(const char *pattern, size_t len, int flags, Input *string, Match *out);
|
|
int re_fullmatch(const char *pattern, size_t len, int flags, Input *string, Match *out);
|
|
int re_search(const char *pattern, size_t len, int flags, Input *string, Match *out);
|
|
int re_finditer(const char *pattern, size_t len, int flags, Input *string, MatchIterCb cb, void *ctx);
|
|
int re_findall(const char *pattern, size_t len, int flags, Input *string, MatchIterCb cb, void *ctx);
|
|
int re_split(const char *pattern, size_t len, int flags, Input *string, int maxsplit, MatchIterCb cb, void *ctx);
|
|
int re_sub(const char *pattern, size_t len, int flags, Input *string, const char *repl, MatchSubCb cb, void *ctx, int count, char **out, size_t *outlen);
|
|
int re_subn(const char *pattern, size_t len, int flags, Input *string, const char *repl, MatchSubCb cb, void *ctx, int count, char **out, size_t *outlen, int *n);
|
|
void re_escape(const char *in, size_t len, char **out, size_t *outlen);
|
|
void re_purge(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* REGEXX_H */
|