/* empty_match_rule - demonstrates a correctness feature rather than a * performance one: Pattern_finditer/Pattern_split reproduce CPython's own * undocumented empty-match behavior exactly, not the simpler "skip forward * one position after an empty match" rule a first implementation attempt * would reach for. * * A real CPython interpreter, if the match found at a given start position * is empty, additionally searches for and reports a second, non-empty * match at that same start position before moving on, rather than only * reporting the empty one. This was found by directly probing a real * Python interpreter (docs/API.md Section 3.5-3.6, README.md "Testing"), * not by reading CPython's documentation, which does not describe this * rule; an earlier revision of this library's Pattern_finditer/split did * not implement it and was corrected once the discrepancy against real * CPython output was found by the combinatorial test suite. * * The canonical example: \d*? (a lazy, possibly-empty run of digits) * against "123abc456" finds 16 matches with a real CPython interpreter, * not 9 (which is what "one match per start position, non-overlapping" * alone would produce for a 9-character subject). */ #include "regexx.h" #include #include typedef struct { int n; } Ctx; static void print_match(void *ctx, const Match *m_const) { Ctx *c = ctx; Match *m = (Match *)m_const; const char *g; size_t glen; Match_group(m, NULL, 0, &g, &glen); int64_t s, e; Match_span(m, 0, &s, &e); printf(" match %2d: span=[%lld,%lld) text='%.*s'\n", c->n, (long long)s, (long long)e, (int)glen, g); c->n++; } static void print_split_elem(void *ctx, const Match *m_const) { Ctx *c = ctx; Match *m = (Match *)m_const; const char *g; size_t glen; int r = Match_group(m, NULL, 0, &g, &glen); if (r == 1) printf(" elem %2d: '%.*s'\n", c->n, (int)glen, g); else printf(" elem %2d: (None)\n", c->n); c->n++; } int main(void) { const char *pattern = "\\d*?"; const char *subject = "123abc456"; PatternError err; memset(&err, 0, sizeof err); Pattern *pat = re_compile(pattern, strlen(pattern), ASCII, &err); if (!pat) { fprintf(stderr, "compile error: %s\n", err.msg); PatternError_free(&err); return 1; } Input *in = Input_from_buffer((const uint8_t *)subject, strlen(subject)); printf("Pattern_finditer(\"%s\", \"%s\"):\n", pattern, subject); Ctx ctx = { 0 }; int n = Pattern_finditer(pat, in, 0, -1, print_match, &ctx); printf("total: %d matches (a real CPython interpreter reports 16 for\n" "this exact pattern/subject pair, not 9; this build matches it)\n\n", n); printf("Pattern_split(\"\\\\s*,\\\\s*\", \"a, b,c\") for contrast (an ordinary,\n" "always-nonempty separator, no empty-match retry needed): \n"); { const char *sp = "\\s*,\\s*"; PatternError err2; memset(&err2, 0, sizeof err2); Pattern *pat2 = re_compile(sp, strlen(sp), ASCII, &err2); const char *subj2 = "a, b,c"; Input *in2 = Input_from_buffer((const uint8_t *)subj2, strlen(subj2)); Ctx ctx2 = { 0 }; Pattern_split(pat2, in2, 0, print_split_elem, &ctx2); Input_free(in2); Pattern_free(pat2); } printf("\nPattern_split(\"x*\", \"-abc-\") (a pattern that CAN match empty,\n" "showing the same retry rule apply during split):\n"); { const char *sp = "x*"; PatternError err3; memset(&err3, 0, sizeof err3); Pattern *pat3 = re_compile(sp, strlen(sp), ASCII, &err3); const char *subj3 = "-abc-"; Input *in3 = Input_from_buffer((const uint8_t *)subj3, strlen(subj3)); Ctx ctx3 = { 0 }; Pattern_split(pat3, in3, 0, print_split_elem, &ctx3); Input_free(in3); Pattern_free(pat3); } Input_free(in); Pattern_free(pat); return 0; }