diff --git a/Lua/lbp.c b/Lua/lbp.c index b062973..c1c3f61 100644 --- a/Lua/lbp.c +++ b/Lua/lbp.c @@ -33,8 +33,10 @@ static void push_match(lua_State *L, match_t *m, const char *start); lua_State *cur_state = NULL; -static void match_error(const char *msg) +static void match_error(pat_t *pat, const char *msg) { + (void)pat; + recycle_all_matches(); lua_pushstring(cur_state, msg); lua_error(cur_state); } @@ -139,19 +141,6 @@ static void push_match(lua_State *L, match_t *m, const char *start) lua_setfield(L, -2, "after"); } -typedef struct { - lua_State *L; - const char *text; -} lua_match_context_t; - -static bp_match_behavior handle_match(match_t *m, int matchnum, void *data) -{ - (void)matchnum; - lua_match_context_t *ctx = data; - push_match(ctx->L, m, ctx->text); - return BP_STOP; -} - static int Lmatch(lua_State *L) { if (lua_isstring(L, 1)) { @@ -178,29 +167,17 @@ static int Lmatch(lua_State *L) if (index > (lua_Integer)strlen(text)+1) return 0; + match_t *m = NULL; + int ret = 0; cur_state = L; - lua_match_context_t ctx = {.L=L, .text=text}; - bp_errhand_t prev_handler = set_match_error_handler(match_error); - int ret = each_match(handle_match, &ctx, text+index-1, &text[textlen], pat, builtins, NULL, false); - (void)set_match_error_handler(prev_handler); + if (next_match_safe(&m, text+index-1, &text[textlen], pat, builtins, NULL, false, match_error)) { + push_match(L, m, text); + stop_matching(&m); + ret = 1; + } return ret; } -typedef struct { - FILE *out; - const char *prev, *text; -} lua_replace_context_t; - -static bp_match_behavior handle_replacement(match_t *m, int matchnum, void *data) -{ - (void)matchnum; - lua_replace_context_t *ctx = data; - fwrite(ctx->prev, sizeof(char), (size_t)(m->start - ctx->prev), ctx->out); - fprint_match(ctx->out, ctx->text, m, NULL); - ctx->prev = m->end; - return BP_CONTINUE; -} - static int Lreplace(lua_State *L) { if (lua_isstring(L, 1)) { @@ -228,14 +205,16 @@ static int Lreplace(lua_State *L) char *buf = NULL; size_t size = 0; FILE *out = open_memstream(&buf, &size); + int replacements = 0; const char *prev = text; pat_t *rep_pat = maybe_replacement.value.pat; cur_state = L; - - lua_replace_context_t ctx = {.out=out, .prev=text, .text=text}; - bp_errhand_t prev_handler = set_match_error_handler(match_error); - int replacements = each_match(handle_replacement, &ctx, text, &text[textlen], rep_pat, builtins, NULL, false); - (void)set_match_error_handler(prev_handler); + for (match_t *m = NULL; next_match_safe(&m, text, &text[textlen], rep_pat, builtins, NULL, false, match_error); ) { + fwrite(prev, sizeof(char), (size_t)(m->start - prev), out); + fprint_match(out, text, m, NULL); + prev = m->end; + ++replacements; + } fwrite(prev, sizeof(char), (size_t)(&text[textlen] - prev), out); fflush(out); lua_pushlstring(L, buf, size); diff --git a/Makefile b/Makefile index fb36a07..1ccd361 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME=bp CC=cc PREFIX=/usr/local SYSCONFDIR=/etc -CFLAGS=-std=c99 -Werror -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -flto=auto +CFLAGS=-std=c99 -Werror -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -flto CWARN=-Wall -Wextra # -Wpedantic -Wsign-conversion -Wtype-limits -Wunused-result -Wnull-dereference \ # -Waggregate-return -Walloc-zero -Walloca -Warith-conversion -Wcast-align -Wcast-align=strict \ diff --git a/bp.c b/bp.c index ba4473b..b863e24 100644 --- a/bp.c +++ b/bp.c @@ -60,9 +60,6 @@ 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; @@ -91,7 +88,6 @@ 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 @@ -211,30 +207,34 @@ static int is_text_file(const char *filename) // // Print matches in JSON format. // -static bp_match_behavior print_match_as_json(match_t *m, int matchnum, void *data) +static int print_matches_as_json(file_t *f, pat_t *pattern, pat_t *defs) { - file_t *f = (file_t*)data; - if (++matchnum > 1) - printf(",\n"); - printf("{\"match\":"); - json_match(f->start, m, options.verbose); - printf("}"); - return BP_CONTINUE; + 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; } // // Print matches in a visual explanation style // -static bp_match_behavior print_match_explanation(match_t *m, int matchnum, void *data) +static int explain_matches(file_t *f, pat_t *pattern, pat_t *defs) { - 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; + 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; } // @@ -346,54 +346,40 @@ 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) { - print_userdata_t userdata = {.out = out, .f = f, .prev = NULL}; - userdata.print_opts = (print_options_t){.fprint_between = _fprint_between, .on_nl = on_nl}; + static int printed_filenames = 0; + int matches = 0; + const char *prev = NULL; 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) { - 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"; + 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; } - int matches = each_match_file(print_single_match, &userdata, f, pattern, defs); // Print trailing context if needed: if (matches > 0) { - fprint_context(out, f, userdata.prev, NULL); + fprint_context(out, f, prev, NULL); if (last_line_num < 0) { // Hacky fix to ensure line number gets printed for `bp -p '$$'` - fprint_linenum(out, f, f->nlines, userdata.print_opts.normal_color); + fprint_linenum(out, f, f->nlines, print_opts.normal_color); fputc('\n', out); } } @@ -418,14 +404,21 @@ static int process_file(const char *filename, pat_t *pattern, pat_t *defs) int matches = 0; if (options.mode == MODE_EXPLAIN) { - matches += each_match_file(print_match_explanation, f, f, pattern, defs); + matches += explain_matches(f, pattern, defs); } else if (options.mode == MODE_LISTFILES) { - matches += each_match_file((bp_match_callback)(BP_STOP), f, f, pattern, defs); + 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); } else if (options.mode == MODE_JSON) { - matches += each_match_file(print_match_as_json, f, f, pattern, defs); + matches += print_matches_as_json(f, pattern, defs); } else if (options.mode == MODE_INPLACE) { - if (each_match_file((bp_match_callback)(BP_STOP), f, f, pattern, defs) == 0) - return 0; + 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; // Ensure the file is resident in memory: if (f->mmapped) { @@ -448,6 +441,8 @@ 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; @@ -687,6 +682,7 @@ 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; diff --git a/match.c b/match.c index cbb5f83..0ff6243 100644 --- a/match.c +++ b/match.c @@ -9,7 +9,6 @@ #include #include #include -#include #include "match.h" #include "pattern.h" @@ -50,15 +49,12 @@ typedef struct match_ctx_s { static match_t *unused_matches = NULL; static match_t *in_use_matches = NULL; -static void default_error_handler(const char *msg) { +static void default_error_handler(pat_t *pat, const char *msg) { + (void)pat; 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} @@ -66,20 +62,17 @@ __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,1,2))) -static inline void match_error(const char *fmt, ...) +__attribute__((format(printf,2,3))) +static inline void match_error(pat_t *pat, const char *fmt, ...) { - if (error_message) free(error_message); va_list args; va_start(args, fmt); - vasprintf(&error_message, fmt, args); + char buf[256]; + vsnprintf(buf, sizeof(buf)-1, fmt, args); va_end(args); - longjmp(error_jump, 1); + if (error_handler) + error_handler(pat, buf); } static match_t *clone_match(match_t *m) @@ -233,8 +226,7 @@ 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 { - free_all_matches(); - match_error("Invalid pattern type in definitions"); + match_error(defs, "Invalid pattern type in definitions"); return NULL; } } @@ -675,8 +667,7 @@ 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) { - free_all_matches(); - match_error("Unknown pattern: '%.*s'", (int)pat->args.ref.len, pat->args.ref.name); + match_error(pat, "Unknown pattern: '%.*s'", (int)pat->args.ref.len, pat->args.ref.name); return NULL; } @@ -763,8 +754,7 @@ static match_t *match(match_ctx_t *ctx, const char *str, pat_t *pat) return new_match(pat, str, str, NULL); } default: { - free_all_matches(); - match_error("Unknown pattern type: %u", pat->type); + match_error(pat, "Unknown pattern type: %u", pat->type); return NULL; } } @@ -849,11 +839,23 @@ size_t free_all_matches(void) // // Iterate over matches. +// Usage: for (match_t *m = NULL; next_match(&m, ...); ) {...} // -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) +bool next_match(match_t **m, const char *start, const char *end, pat_t *pat, pat_t *defs, pat_t *skip, bool ignorecase) { - if (!callback || !start || !pat) return -1; - if (!end) end = start + strlen(start); + 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; + } match_ctx_t ctx = { .cache = &(cache_t){0}, @@ -862,55 +864,20 @@ int each_match(bp_match_callback callback, void *userdata, const char *start, co .ignorecase = ignorecase, .defs = defs, }; - - 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; - } - - bool hit_error = setjmp(error_jump) != 0; - if (!hit_error) { - 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(); - } - } + *m = (pos <= end) ? _next_match(&ctx, pos, pat, skip) : NULL; cache_destroy(&ctx); - free_all_matches(); - - if (hit_error && error_handler) { - error_handler(error_message ? error_message : "An unknown error occurred"); - } - - if (error_message) { - free(error_message); - error_message = NULL; - } - - return num_matches; + return *m != NULL; } -bp_errhand_t set_match_error_handler(bp_errhand_t errhand) +// +// 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 old_errhand = errhand; error_handler = errhand; - return old_errhand; + bool ret = next_match(m, start, end, pat, defs, skip, ignorecase); + error_handler = default_error_handler; + return ret; } // @@ -945,7 +912,13 @@ static match_t *_get_numbered_capture(match_t *m, int *n) match_t *get_numbered_capture(match_t *m, int n) { if (n <= 0) return m; - return _get_numbered_capture(m, &n); + if (m->children) { + for (int i = 0; m->children[i]; i++) { + match_t *cap = _get_numbered_capture(m->children[i], &n); + if (cap) return cap; + } + } + return NULL; } // diff --git a/match.h b/match.h index 9e5d050..cf4f977 100644 --- a/match.h +++ b/match.h @@ -25,13 +25,15 @@ typedef struct match_s { struct match_s *_children[3]; } match_t; -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); +typedef void (*bp_errhand_t)(pat_t *pat, const char *err_msg); +__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))