Moving to each_match() API

This commit is contained in:
Bruce Hill 2022-10-26 20:12:18 -04:00
parent ed737a76ba
commit 9380a54d7c
4 changed files with 131 additions and 100 deletions

View File

@ -36,7 +36,6 @@ lua_State *cur_state = NULL;
static void match_error(pat_t *pat, const char *msg)
{
(void)pat;
recycle_all_matches();
lua_pushstring(cur_state, msg);
lua_error(cur_state);
}

116
bp.c
View File

@ -60,6 +60,9 @@ static const char *usage = (
#define ALL_CONTEXT -2
#define NO_CONTEXT -1
#define each_match_file(callback, userdata, f, pat, defs) \
each_match(callback, userdata, (f)->start, (f)->end, pat, defs, options.skip, options.ignorecase)
// Flag-configurable options:
static struct {
int context_before, context_after;
@ -88,6 +91,7 @@ const char *LINE_FORMATS[] = {
// If a file is partly through being modified when the program exits, restore it from backup.
static FILE *modifying_file = NULL;
static file_t *backup_file;
static int files_printed = 0;
//
// Helper function to reduce code duplication
@ -207,34 +211,30 @@ static int is_text_file(const char *filename)
//
// Print matches in JSON format.
//
static int print_matches_as_json(file_t *f, pat_t *pattern, pat_t *defs)
static bp_match_behavior print_match_as_json(match_t *m, int matchnum, void *data)
{
int nmatches = 0;
for (match_t *m = NULL; next_match(&m, f->start, f->end, pattern, defs, options.skip, options.ignorecase); ) {
if (++nmatches > 1)
printf(",\n");
printf("{\"filename\":\"%s\",\"match\":", f->filename);
json_match(f->start, m, options.verbose);
printf("}");
}
return nmatches;
file_t *f = (file_t*)data;
if (++matchnum > 1)
printf(",\n");
printf("{\"match\":");
json_match(f->start, m, options.verbose);
printf("}");
return BP_CONTINUE;
}
//
// Print matches in a visual explanation style
//
static int explain_matches(file_t *f, pat_t *pattern, pat_t *defs)
static bp_match_behavior print_match_explanation(match_t *m, int matchnum, void *data)
{
int nmatches = 0;
for (match_t *m = NULL; next_match(&m, f->start, f->end, pattern, defs, options.skip, options.ignorecase); ) {
if (++nmatches == 1) {
if (options.print_filenames)
fprint_filename(stdout, f->filename);
} else
printf("\n\n");
explain_match(m);
}
return nmatches;
file_t *f = (file_t*)data;
if (matchnum == 0) {
if (options.print_filenames)
fprint_filename(stdout, f->filename);
} else
printf("\n\n");
explain_match(m);
return BP_CONTINUE;
}
//
@ -346,40 +346,54 @@ static void on_nl(FILE *out)
}
}
typedef struct {
file_t *f;
FILE *out;
const char *prev;
print_options_t print_opts;
} print_userdata_t;
//
// Print the text of a single match with the chosen context info
//
static bp_match_behavior print_single_match(match_t *m, int matchnum, void *data)
{
print_userdata_t *info = data;
if (matchnum == 0 && options.print_filenames) {
if (files_printed++ > 0)
fprintf(info->out, "\n");
fprint_filename(info->out, info->f->filename);
}
fprint_context(info->out, info->f, info->prev, m->start);
if (info->print_opts.normal_color) fprintf(info->out, "%s", info->print_opts.normal_color);
fprint_match(info->out, info->f->start, m, &info->print_opts);
if (info->print_opts.normal_color) fprintf(info->out, "%s", info->print_opts.normal_color);
info->prev = m->end;
return BP_CONTINUE;
}
//
// Print all the matches in a file.
//
static int print_matches(FILE *out, file_t *f, pat_t *pattern, pat_t *defs)
{
static int printed_filenames = 0;
int matches = 0;
const char *prev = NULL;
print_userdata_t userdata = {.out = out, .f = f, .prev = NULL};
userdata.print_opts = (print_options_t){.fprint_between = _fprint_between, .on_nl = on_nl};
printing_file = f;
last_line_num = -1;
print_options_t print_opts = {.fprint_between = _fprint_between, .on_nl = on_nl};
if (options.format == FORMAT_FANCY) {
print_opts.match_color = "\033[0;31;1m";
print_opts.replace_color = "\033[0;34;1m";
print_opts.normal_color = "\033[m";
}
for (match_t *m = NULL; next_match(&m, f->start, f->end, pattern, defs, options.skip, options.ignorecase); ) {
if (++matches == 1 && options.print_filenames) {
if (printed_filenames++ > 0) printf("\n");
fprint_filename(out, f->filename);
}
fprint_context(out, f, prev, m->start);
if (print_opts.normal_color) fprintf(out, "%s", print_opts.normal_color);
fprint_match(out, f->start, m, &print_opts);
if (print_opts.normal_color) fprintf(out, "%s", print_opts.normal_color);
prev = m->end;
userdata.print_opts.match_color = "\033[0;31;1m";
userdata.print_opts.replace_color = "\033[0;34;1m";
userdata.print_opts.normal_color = "\033[m";
}
int matches = each_match_file(print_single_match, &userdata, f, pattern, defs);
// Print trailing context if needed:
if (matches > 0) {
fprint_context(out, f, prev, NULL);
fprint_context(out, f, userdata.prev, NULL);
if (last_line_num < 0) { // Hacky fix to ensure line number gets printed for `bp -p '$$'`
fprint_linenum(out, f, f->nlines, print_opts.normal_color);
fprint_linenum(out, f, f->nlines, userdata.print_opts.normal_color);
fputc('\n', out);
}
}
@ -404,21 +418,14 @@ static int process_file(const char *filename, pat_t *pattern, pat_t *defs)
int matches = 0;
if (options.mode == MODE_EXPLAIN) {
matches += explain_matches(f, pattern, defs);
matches += each_match_file(print_match_explanation, f, f, pattern, defs);
} else if (options.mode == MODE_LISTFILES) {
match_t *m = NULL;
if (next_match(&m, f->start, f->end, pattern, defs, options.skip, options.ignorecase)) {
printf("%s\n", f->filename);
matches += 1;
}
stop_matching(&m);
matches += each_match_file((bp_match_callback)(BP_STOP), f, f, pattern, defs);
} else if (options.mode == MODE_JSON) {
matches += print_matches_as_json(f, pattern, defs);
matches += each_match_file(print_match_as_json, f, f, pattern, defs);
} else if (options.mode == MODE_INPLACE) {
match_t *m = NULL;
bool found = next_match(&m, f->start, f->end, pattern, defs, options.skip, options.ignorecase);
stop_matching(&m);
if (!found) return 0;
if (each_match_file((bp_match_callback)(BP_STOP), f, f, pattern, defs) == 0)
return 0;
// Ensure the file is resident in memory:
if (f->mmapped) {
@ -441,8 +448,6 @@ static int process_file(const char *filename, pat_t *pattern, pat_t *defs)
}
fflush(stdout);
if (recycle_all_matches() != 0)
fprintf(stderr, "\033[33;1mMemory leak: there should no longer be any matches in use at this point.\033[m\n");
destroy_file(&f);
(void)fflush(stdout);
return matches;
@ -682,7 +687,6 @@ int main(int argc, char *argv[])
// This code frees up all residual heap-allocated memory. Since the program
// is about to exit, this step is unnecessary. However, it is useful for
// tracking down memory leaks.
free_all_matches();
free_all_pats();
while (loaded_files) {
file_t *next = loaded_files->next;

100
match.c
View File

@ -9,6 +9,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include "match.h"
#include "pattern.h"
@ -49,12 +50,15 @@ typedef struct match_ctx_s {
static match_t *unused_matches = NULL;
static match_t *in_use_matches = NULL;
static void default_error_handler(pat_t *pat, const char *msg) {
(void)pat;
static void default_error_handler(const char *msg) {
errx(EXIT_FAILURE, "%s", msg);
}
// In case of errors, jump out of everything, clean up memory, and call the error handler.
// Errors in this case are things like referencing a rule that isn't defined.
static jmp_buf error_jump;
static bp_errhand_t error_handler = default_error_handler;
static char *error_message = NULL;
#define MATCHES(...) (match_t*[]){__VA_ARGS__, NULL}
@ -62,17 +66,19 @@ __attribute__((hot, nonnull(1,2,3)))
static match_t *match(match_ctx_t *ctx, const char *str, pat_t *pat);
__attribute__((returns_nonnull))
static match_t *new_match(pat_t *pat, const char *start, const char *end, match_t *children[]);
__attribute__((nonnull))
static void recycle_match(match_t **at_m);
static size_t free_all_matches(void);
static size_t recycle_all_matches(void);
__attribute__((format(printf,2,3)))
static inline void match_error(pat_t *pat, const char *fmt, ...)
__attribute__((format(printf,1,2)))
static inline void match_error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
char buf[256];
vsnprintf(buf, sizeof(buf)-1, fmt, args);
vasprintf(&error_message, fmt, args);
va_end(args);
if (error_handler)
error_handler(pat, buf);
longjmp(error_jump, 1);
}
static match_t *clone_match(match_t *m)
@ -226,7 +232,8 @@ static pat_t *_lookup_def(pat_t *defs, const char *name, size_t namelen)
return defs->args.def.meaning;
defs = defs->args.def.next_def;
} else {
match_error(defs, "Invalid pattern type in definitions");
free_all_matches();
match_error("Invalid pattern type in definitions");
return NULL;
}
}
@ -667,7 +674,8 @@ static match_t *match(match_ctx_t *ctx, const char *str, pat_t *pat)
pat_t *ref = lookup_ctx(ctx, pat->args.ref.name, pat->args.ref.len);
if (ref == NULL) {
match_error(pat, "Unknown pattern: '%.*s'", (int)pat->args.ref.len, pat->args.ref.name);
free_all_matches();
match_error("Unknown pattern: '%.*s'", (int)pat->args.ref.len, pat->args.ref.name);
return NULL;
}
@ -754,7 +762,8 @@ static match_t *match(match_ctx_t *ctx, const char *str, pat_t *pat)
return new_match(pat, str, str, NULL);
}
default: {
match_error(pat, "Unknown pattern type: %u", pat->type);
free_all_matches();
match_error("Unknown pattern type: %u", pat->type);
return NULL;
}
}
@ -839,23 +848,11 @@ size_t free_all_matches(void)
//
// Iterate over matches.
// Usage: for (match_t *m = NULL; next_match(&m, ...); ) {...}
//
bool next_match(match_t **m, const char *start, const char *end, pat_t *pat, pat_t *defs, pat_t *skip, bool ignorecase)
int each_match(bp_match_callback callback, void *userdata, const char *start, const char *end, pat_t *pat, pat_t *defs, pat_t *skip, bool ignorecase)
{
const char *pos;
if (*m) {
// Make sure forward progress is occurring, even after zero-width matches:
pos = ((*m)->end > (*m)->start) ? (*m)->end : (*m)->end+1;
recycle_match(m);
} else {
pos = start;
}
if (!pat) {
error_handler = default_error_handler;
return false;
}
if (!callback || !start || !pat) return -1;
if (!end) end = start + strlen(start);
match_ctx_t ctx = {
.cache = &(cache_t){0},
@ -864,20 +861,53 @@ bool next_match(match_t **m, const char *start, const char *end, pat_t *pat, pat
.ignorecase = ignorecase,
.defs = defs,
};
*m = (pos <= end) ? _next_match(&ctx, pos, pat, skip) : NULL;
int num_matches = 0;
pat_t *first = get_prerequisite(&ctx, pat);
// Don't bother looping if this can only match at the start/end:
if (first->type == BP_START_OF_FILE || first->type == BP_END_OF_FILE) {
match_t *m = match(&ctx, first->type == BP_START_OF_FILE ? start : end, pat);
if (m) {
if ((size_t)callback > 3)
(void)callback(m, num_matches, userdata);
++num_matches;
}
return num_matches;
}
if (setjmp(error_jump) == 0) {
for (const char *str = start; str <= end; ) {
match_t *m = _next_match(&ctx, str, pat, skip);
if (!m) break;
else if (callback == (void*)BP_STOP)
break;
else if (callback != (void*)BP_CONTINUE && callback(m, num_matches++, userdata) == BP_STOP)
break;
else if (str == m->start && str == end)
break;
str = m->end > str ? m->end : next_char(str, end);
recycle_all_matches();
}
} else {
if (error_handler)
error_handler(error_message ? error_message : "An unknown error occurred");
}
if (error_message) {
free(error_message);
error_message = NULL;
}
cache_destroy(&ctx);
return *m != NULL;
free_all_matches();
return num_matches;
}
//
// Wrapper for next_match() that sets an error handler
//
bool next_match_safe(match_t **m, const char *start, const char *end, pat_t *pat, pat_t *defs, pat_t *skip, bool ignorecase, bp_errhand_t errhand)
bp_errhand_t set_match_error_handler(bp_errhand_t errhand)
{
bp_errhand_t old_errhand = errhand;
error_handler = errhand;
bool ret = next_match(m, start, end, pat, defs, skip, ignorecase);
error_handler = default_error_handler;
return ret;
return old_errhand;
}
//

14
match.h
View File

@ -25,15 +25,13 @@ typedef struct match_s {
struct match_s *_children[3];
} match_t;
typedef void (*bp_errhand_t)(pat_t *pat, const char *err_msg);
typedef void (*bp_errhand_t)(const char *err_msg);
typedef enum {BP_STOP = 0, BP_CONTINUE} bp_match_behavior;
typedef bp_match_behavior (*bp_match_callback)(match_t *m, int matchnum, void *userdata);
int each_match(bp_match_callback fn, void *userdata, const char *start, const char *end, pat_t *pat, pat_t *defs, pat_t *skip, bool ignorecase);
bp_errhand_t set_match_error_handler(bp_errhand_t errhand);
__attribute__((nonnull))
void recycle_match(match_t **at_m);
size_t free_all_matches(void);
size_t recycle_all_matches(void);
bool next_match(match_t **m, const char *start, const char *end, pat_t *pat, pat_t *defs, pat_t *skip, bool ignorecase);
#define stop_matching(m) next_match(m, NULL, NULL, NULL, NULL, NULL, 0)
bool next_match_safe(match_t **m, const char *start, const char *end, pat_t *pat, pat_t *defs, pat_t *skip, bool ignorecase, bp_errhand_t errhand);
__attribute__((nonnull))
match_t *get_numbered_capture(match_t *m, int n);
__attribute__((nonnull, pure))