/* rxgrep - a small grep-like example program built on regexx, demonstrating * the library across its three data modes (Section 9.3) and both its * matching and substitution API (Section 9.1). See README.md for usage. * * This is example code, not part of the library; it is not held to the * same naming-convention rule as regexx.c/regexx.h (concept.md 9.0), * since it is an ordinary application, not part of the `re`-mirroring * surface. */ #include "regexx.h" #include #include #include static void usage(const char *prog) { fprintf(stderr, "usage: %s [-i] [-n] [-c] [-o] [-v] [-m MODE] PATTERN FILE\n" " %s [-m MODE] --sub REPL PATTERN FILE\n" "\n" " -i ignore case (IGNORECASE)\n" " -n prefix each match with its 1-based line number\n" " -c print only a count of matching lines\n" " -o print only the matched text, not the whole line\n" " -v print lines that do NOT match\n" " -m MODE ascii (default), utf8, or binary\n" " --sub REPL replace every match with REPL and print the result\n", prog, prog); } static int mode_flag(const char *m) { if (strcmp(m, "ascii") == 0) return ASCII; if (strcmp(m, "utf8") == 0) return UTF8; if (strcmp(m, "binary") == 0) return BINARY; fprintf(stderr, "unknown mode '%s' (expected ascii, utf8, or binary)\n", m); exit(2); } typedef struct { long lineno; int with_lineno; } MatchLinePrint; static void print_one_match(void *ctx, const Match *m_const) { MatchLinePrint *p = ctx; Match *m = (Match *)m_const; const char *g; size_t glen; Match_group(m, NULL, 0, &g, &glen); if (p->with_lineno) printf("%ld:", p->lineno); fwrite(g, 1, glen, stdout); printf("\n"); } int main(int argc, char **argv) { int flags = 0; int opt_n = 0, opt_c = 0, opt_o = 0, opt_v = 0; const char *sub_repl = NULL; int mode = ASCII; int i = 1; for (; i < argc; i++) { const char *a = argv[i]; if (strcmp(a, "-m") == 0 && i + 1 < argc) { mode = mode_flag(argv[++i]); continue; } if (strcmp(a, "--sub") == 0 && i + 1 < argc) { sub_repl = argv[++i]; continue; } if (strcmp(a, "-h") == 0 || strcmp(a, "--help") == 0) { usage(argv[0]); return 0; } if (a[0] == '-' && a[1] != '-' && a[1] != '\0') { int bad = 0; for (const char *c = a + 1; *c; c++) { switch (*c) { case 'i': flags |= IGNORECASE; break; case 'n': opt_n = 1; break; case 'c': opt_c = 1; break; case 'o': opt_o = 1; break; case 'v': opt_v = 1; break; default: bad = 1; break; } } if (!bad) continue; } break; } if (argc - i != 2) { usage(argv[0]); return 2; } const char *pattern = argv[i], *path = argv[i + 1]; PatternError err; memset(&err, 0, sizeof err); Pattern *pat = re_compile(pattern, strlen(pattern), flags | mode, &err); if (!pat) { fprintf(stderr, "%s: pattern error at position %lld: %s\n", argv[0], (long long)err.pos, err.msg); return 2; } if (sub_repl) { Input *file = Input_from_file(path, &err); if (!file) { fprintf(stderr, "%s: cannot read '%s': %s\n", argv[0], path, err.msg ? err.msg : "unknown error"); Pattern_free(pat); return 2; } char *out; size_t outlen; int n; if (Pattern_subn(pat, file, sub_repl, NULL, NULL, 0, &out, &outlen, &n) < 0) { fprintf(stderr, "%s: matching failed (input too large/complex for this pattern, see README.md)\n", argv[0]); Input_free(file); Pattern_free(pat); return 1; } fwrite(out, 1, outlen, stdout); fprintf(stderr, "%d replacement(s)\n", n); free(out); Input_free(file); Pattern_free(pat); return n > 0 ? 0 : 1; } /* Line oriented search: split on raw 0x0A, which is what every one * of the three modes (BINARY, ASCII, UTF8) agrees is a line break * (concept.md 7.2 notes 0x0A never appears inside a multi-byte * UTF-8 sequence), then search within each line's [pos, endpos). * Input's internals are intentionally opaque outside regexx.c * (concept.md 9.0), so each line is handed to the library as its * own small Input built with Input_from_buffer. */ FILE *fp = fopen(path, "rb"); if (!fp) { fprintf(stderr, "%s: cannot reopen '%s'\n", argv[0], path); return 2; } fseek(fp, 0, SEEK_END); long fsize = ftell(fp); rewind(fp); char *filebuf = malloc((size_t)fsize > 0 ? (size_t)fsize : 1); size_t got = fread(filebuf, 1, (size_t)fsize, fp); fclose(fp); long lineno = 0; size_t line_start = 0; int matched_lines = 0; for (size_t pos = 0; pos <= got; pos++) { if (pos == got || filebuf[pos] == '\n') { lineno++; size_t line_len = pos - line_start; Input *lin = Input_from_buffer((const uint8_t *)(filebuf + line_start), line_len); Match m; memset(&m, 0, sizeof m); int r = Pattern_search(pat, lin, 0, -1, &m); int is_match = (r == 1); if (is_match) Match_free(&m); if (is_match != opt_v) { matched_lines++; if (!opt_c) { if (opt_o && is_match) { /* -o: every match on the line gets its own * output line, demonstrating Pattern_finditer. */ MatchLinePrint ctx = { lineno, opt_n }; Pattern_finditer(pat, lin, 0, -1, print_one_match, &ctx); } else { if (opt_n) printf("%ld:", lineno); fwrite(filebuf + line_start, 1, line_len, stdout); printf("\n"); } } } Input_free(lin); line_start = pos + 1; } } if (opt_c) printf("%d\n", matched_lines); free(filebuf); Pattern_free(pat); return matched_lines > 0 ? 0 : 1; }