diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2021-01-17 13:33:10 -0800 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2021-01-17 13:33:10 -0800 |
| commit | 3cc645f2d0393e6e2d1d03a099a0c3320acb2a20 (patch) | |
| tree | db9a9574928075404af4fb64640be8fa096dd3d1 | |
| parent | a4f0b93836fa84c80d68ffdab4e36dd026a498f0 (diff) | |
Improved argument parsing and added support for prompting user for a
pattern if none is provided
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | bp.1 | 1 | ||||
| -rw-r--r-- | bp.c | 79 | ||||
| -rw-r--r-- | definitions.c | 7 | ||||
| -rw-r--r-- | definitions.h | 4 | ||||
| -rw-r--r-- | match.c | 1 | ||||
| -rw-r--r-- | pattern.c | 36 | ||||
| -rw-r--r-- | pattern.h | 4 | ||||
| -rw-r--r-- | types.h | 1 |
9 files changed, 67 insertions, 67 deletions
@@ -16,7 +16,6 @@ It's written in pure C with no dependencies. * `-j` `--json` print matches as JSON objects * `-l` `--list-files` print only filenames containing matches * `-p` `--pattern <pat>` provide a pattern (equivalent to `bp '\(<pat>)'`) -* `-P` `--pattern-string <pat>` provide a string pattern (equivalent to `bp '<pat>'`, but may be useful if `'<pat>'` begins with a '-') * `-r` `--replace <replacement>` replace the input pattern with the given replacement * `-c` `--context <N>` change how many lines of context are printed (`0`: no context, `all`: the whole file, `<N>` matching lines and `<N-1>` lines before/after) * `-g` `--grammar <grammar file>` use the specified file as a grammar @@ -14,7 +14,6 @@ bp \- Bruce's Parsing Expression Grammar tool [\fI-I\fR|\fI--inplace\fR] [\fI-C\fR|\fI--confirm\fR] [\fI-p\fR|\fI--pattern\fR \fI<pattern>\fR] -[\fI-P\fR|\fI--pattern-string\fR \fI<string-pattern>\fR] [\fI-r\fR|\fI--replace\fR \fI<replacement>\fR] [\fI-g\fR|\fI--grammar\fR \fI<grammar file>\fR] [\fI-G\fR|\fI--git\fR] @@ -40,7 +40,6 @@ static const char *usage = ( " -C --confirm ask for confirmation on each replacement\n" " -l --list-files list filenames only\n" " -p --pattern <pat> provide a pattern (equivalent to bp '\\(<pat>)')\n" - " -P --pattern-string <pat> provide a string pattern (may be useful if '<pat>' begins with a '-')\n" " -r --replace <replacement> replace the input pattern with the given replacement\n" " -c --context <context> set number of lines of context to print (all: the whole file, 0: only the match, 1: the line, N: N lines of context)\n" " -g --grammar <grammar file> use the specified file as a grammar\n"); @@ -387,14 +386,7 @@ int main(int argc, char *argv[]) def_t *defs = NULL; file_t *loaded_files = NULL; - - // Define a pattern that is just a reference to the rule `pattern` - file_t *pat_file = spoof_file(&loaded_files, "<pattern>", "pattern"); - pat_t *pattern = bp_pattern(loaded_files, pat_file->contents); - - // Define a pattern that is just a reference to the rule `replacement` - file_t *rep_file = spoof_file(&loaded_files, "<replacement>", "replacement"); - pat_t *replacement = bp_pattern(rep_file, rep_file->contents); + pat_t *pattern = NULL; // Load builtins: file_t *xdg_file = load_file(&loaded_files, "/etc/xdg/"BP_NAME"/builtins.bp"); @@ -402,8 +394,7 @@ int main(int argc, char *argv[]) file_t *local_file = load_file(&loaded_files, "%s/.config/"BP_NAME"/builtins.bp", getenv("HOME")); if (local_file) defs = load_grammar(defs, local_file); - check(argc > 1, "%s", usage); - int i, npatterns = 0, git = 0; + int i, git = 0; for (i = 1; i < argc; i++) { if (streq(argv[i], "--")) { ++i; @@ -428,13 +419,12 @@ int main(int argc, char *argv[]) } else if (BOOLFLAG("-l") || BOOLFLAG("--list-files")) { mode = MODE_LISTFILES; } else if (FLAG("-r") || FLAG("--replace")) { + check(pattern, "No pattern has been defined for replacement to operate on"); // TODO: spoof file as sprintf("pattern => '%s'", flag) // except that would require handling edge cases like quotation marks etc. file_t *replace_file = spoof_file(&loaded_files, "<replace argument>", flag); - pat_t *rep = bp_replacement(replace_file, pattern, replace_file->contents); - check(rep, "Replacement failed to compile: %s", flag); - defs = with_def(defs, replace_file, strlen("replacement"), "replacement", rep); - pattern = replacement; + pattern = bp_replacement(replace_file, pattern, replace_file->contents); + check(pattern, "Replacement failed to compile: %s", flag); } else if (FLAG("-g") || FLAG("--grammar")) { file_t *f = load_file(&loaded_files, flag); if (f == NULL) @@ -446,11 +436,10 @@ int main(int argc, char *argv[]) } else if (FLAG("-p") || FLAG("--pattern")) { file_t *arg_file = spoof_file(&loaded_files, "<pattern argument>", flag); for (const char *str = arg_file->contents; str < arg_file->end; ) { - def_t *d = bp_definition(arg_file, str); + def_t *d = bp_definition(defs, arg_file, str); if (d) { - d->next = defs; defs = d; - str = d->pat->end; + str = after_spaces(d->pat->end); } else { pat_t *p = bp_pattern(arg_file, str); if (!p) { @@ -458,20 +447,10 @@ int main(int argc, char *argv[]) "Failed to compile this part of the argument"); return 1; } - check(npatterns == 0, "Cannot define multiple patterns"); - defs = with_def(defs, arg_file, strlen("pattern"), "pattern", p); - ++npatterns; - str = p->end; + pattern = chain_together(arg_file, pattern, p); + str = after_spaces(p->end); } - str = after_spaces(str); - str = strchr(str, ';') ? strchr(str, ';') + 1 : str; } - } else if (FLAG("-P") || FLAG("--pattern-string")) { - file_t *arg_file = spoof_file(&loaded_files, "<pattern argument>", flag); - pat_t *p = bp_stringpattern(arg_file, arg_file->contents); - check(p, "Pattern failed to compile: %s", flag); - defs = with_def(defs, arg_file, strlen("pattern"), "pattern", p); - ++npatterns; } else if (FLAG("-c") || FLAG("--context")) { if (streq(flag, "all")) context_lines = ALL_CONTEXT; @@ -483,13 +462,11 @@ int main(int argc, char *argv[]) printf("Unrecognized flag: -%c\n\n%s\n", argv[i][1], usage); return 1; } else if (argv[i][0] != '-') { - if (npatterns > 0) break; - // TODO: spoof file with quotation marks for better debugging + if (pattern != NULL) break; file_t *arg_file = spoof_file(&loaded_files, "<pattern argument>", argv[i]); pat_t *p = bp_stringpattern(arg_file, arg_file->contents); check(p, "Pattern failed to compile: %s", argv[i]); - defs = with_def(defs, arg_file, strlen("pattern"), "pattern", p); - ++npatterns; + pattern = chain_together(arg_file, pattern, p); } else { printf("Unrecognized flag: %s\n\n%s\n", argv[i], usage); return 1; @@ -516,11 +493,43 @@ int main(int argc, char *argv[]) // User input/output is handled through /dev/tty so that normal unix pipes // can work properly while simultaneously asking for user input. - if (confirm == CONFIRM_ASK) { + if (confirm == CONFIRM_ASK || pattern == NULL) { tty_in = fopen("/dev/tty", "r"); tty_out = fopen("/dev/tty", "w"); } + if (pattern == NULL) { // If no pattern argument, then ask the user for a pattern + fprintf(tty_out, "\033[1mPattern> \033[0m"); + fflush(tty_out); + char *patstr = NULL; + size_t len = 0; + check(getline(&patstr, &len, tty_in) > 0, "No pattern provided"); + file_t *arg_file = spoof_file(&loaded_files, "<pattern argument>", patstr); + for (const char *str = arg_file->contents; str < arg_file->end; ) { + def_t *d = bp_definition(defs, arg_file, str); + if (d) { + defs = d; + str = after_spaces(d->pat->end); + } else { + pat_t *p = bp_pattern(arg_file, str); + if (!p) { + fprint_line(stdout, arg_file, str, arg_file->end, + "Failed to compile this part of the argument"); + return 1; + } + pattern = chain_together(arg_file, pattern, p); + str = after_spaces(p->end); + } + } + free(patstr); + } + + // To ensure recursion (and left recursion in particular) works properly, + // we need to define a rule called "pattern" with the value of whatever + // pattern the args specified, and use `pattern` as the thing being matched. + defs = with_def(defs, strlen("pattern"), "pattern", pattern); // TODO: this is a bit hacky + pattern = bp_pattern(loaded_files, "pattern"); + int found = 0; if (mode == MODE_JSON) printf("["); if (git) { // Get the list of files from `git --ls-files ...` diff --git a/definitions.c b/definitions.c index 19971b2..a19d3b8 100644 --- a/definitions.c +++ b/definitions.c @@ -13,11 +13,10 @@ // // Return a new list of definitions with one added to the front // -def_t *with_def(def_t *defs, file_t *f, size_t namelen, const char *name, pat_t *pat) +def_t *with_def(def_t *defs, size_t namelen, const char *name, pat_t *pat) { def_t *def = new(def_t); def->next = defs; - def->file = f; def->namelen = namelen; def->name = name; def->pat = pat; @@ -40,7 +39,7 @@ def_t *load_grammar(def_t *defs, file_t *f) check(matchchar(&src, ':'), "Expected ':' in definition"); pat_t *pat = bp_pattern(f, src); if (pat == NULL) break; - defs = with_def(defs, f, namelen, name, pat); + defs = with_def(defs, namelen, name, pat); src = pat->end; src = after_spaces(src); if (matchchar(&src, ';')) @@ -74,7 +73,7 @@ def_t *with_backref(def_t *defs, file_t *f, const char *name, match_t *m) backref->end = m->end; backref->len = -1; // TODO: maybe calculate this? (nontrivial because of replacements) backref->args.backref = m; - return with_def(defs, f, strlen(name), name, backref); + return with_def(defs, strlen(name), name, backref); } // diff --git a/definitions.h b/definitions.h index 5a006b4..8c3ae87 100644 --- a/definitions.h +++ b/definitions.h @@ -7,8 +7,8 @@ #include "files.h" #include "types.h" -__attribute__((nonnull(2,4,5), returns_nonnull)) -def_t *with_def(def_t *defs, file_t *f, size_t namelen, const char *name, pat_t *pat); +__attribute__((nonnull(3,4), returns_nonnull)) +def_t *with_def(def_t *defs, size_t namelen, const char *name, pat_t *pat); __attribute__((nonnull(2,3,4), returns_nonnull)) def_t *with_backref(def_t *defs, file_t *f, const char *name, match_t *m); __attribute__((nonnull(2))) @@ -433,7 +433,6 @@ match_t *match(def_t *defs, file_t *f, const char *str, pat_t *pat, unsigned int def_t defs2 = { .namelen = def->namelen, .name = def->name, - .file = def->file, .pat = &rec_op, .next = defs, }; @@ -7,6 +7,7 @@ #include <string.h> #include <unistd.h> +#include "definitions.h" #include "pattern.h" #include "utils.h" @@ -18,8 +19,6 @@ __attribute__((nonnull)) static pat_t *expand_choices(file_t *f, pat_t *first); __attribute__((nonnull)) static pat_t *_bp_simplepattern(file_t *f, const char *str); -__attribute__((nonnull(1))) -static pat_t *chain_together(file_t *f,pat_t *first, pat_t *second); __attribute__((nonnull(1,2,3,6))) static pat_t *new_range(file_t *f, const char *start, const char *end, ssize_t min, ssize_t max, pat_t *repeating, pat_t *sep); @@ -136,7 +135,7 @@ static pat_t *expand_choices(file_t *f, pat_t *first) // Given two patterns, return a new pattern for the first pattern followed by // the second. If either pattern is NULL, return the other. // -static pat_t *chain_together(file_t *f, pat_t *first, pat_t *second) +pat_t *chain_together(file_t *f, pat_t *first, pat_t *second) { if (first == NULL) return second; if (second == NULL) return first; @@ -518,18 +517,18 @@ pat_t *bp_stringpattern(file_t *f, const char *str) const char *after_escape; unsigned char e = unescapechar(&str[1], &after_escape); - if (e != str[1]) { - str = after_escape - 1; - continue; + // If there is not a special escape sequence (\n, \x0A, etc.) + // or \\, \", \', \`, then check for an interpolated value: + // The special cases for single and double quotes aren't + // needed, but there's no known legitimate use case for + // interpolating a literal string, and users might escape + // quotes out of paranoia, and we want to support that. String + // literal interpolations can be done with \("...") anyways. + if (e == str[1] && e != '\'' && e != '"' && e != '\\' && e != '`') { + interp = bp_simplepattern(f, str + 1); + if (interp) break; } - if (str[1] == '\\') { - ++str; - continue; - } - interp = bp_simplepattern(f, str + 1); - if (interp == NULL) - file_err(f, str, str+1, "This isn't a valid escape sequence or pattern."); - break; + str = after_escape - 1; // Otherwise treat as a literal character } } // End of string @@ -595,7 +594,7 @@ pat_t *bp_pattern(file_t *f, const char *str) // // Match a definition (id__`:__pattern) // -def_t *bp_definition(file_t *f, const char *str) +def_t *bp_definition(def_t *defs, file_t *f, const char *str) { const char *name = after_spaces(str); str = after_name(name); @@ -605,12 +604,7 @@ def_t *bp_definition(file_t *f, const char *str) pat_t *defpat = bp_pattern(f, str); if (!defpat) return NULL; matchchar(&defpat->end, ';'); // TODO: verify this is safe to mutate - def_t *def = new(def_t); - def->file = f; - def->namelen = namelen; - def->name = name; - def->pat = defpat; - return def; + return with_def(defs, namelen, name, defpat); } // @@ -15,10 +15,12 @@ __attribute__((nonnull(1,2))) pat_t *bp_stringpattern(file_t *f, const char *str); __attribute__((nonnull(1,2))) pat_t *bp_replacement(file_t *f, pat_t *replacepat, const char *replacement); +__attribute__((nonnull(1))) +pat_t *chain_together(file_t *f, pat_t *first, pat_t *second); __attribute__((nonnull)) pat_t *bp_pattern(file_t *f, const char *str); __attribute__((nonnull)) -def_t *bp_definition(file_t *f, const char *str); +def_t *bp_definition(def_t *defs, file_t *f, const char *str); __attribute__((nonnull)) void destroy_pat(pat_t *pat); @@ -100,7 +100,6 @@ typedef struct match_s { typedef struct def_s { size_t namelen; const char *name; - file_t *file; pat_t *pat; struct def_s *next; } def_t; |
