Files
regexx/regexx.h
T
retoorandClaude Sonnet 5 a9ca517981 Bring concept.md and file-header comments in line with the memoization fix
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
2026-09-14 08:53:19 +00:00

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 */