aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2021-01-17 13:33:10 -0800
committerBruce Hill <bruce@bruce-hill.com>2021-01-17 13:33:10 -0800
commit3cc645f2d0393e6e2d1d03a099a0c3320acb2a20 (patch)
treedb9a9574928075404af4fb64640be8fa096dd3d1
parenta4f0b93836fa84c80d68ffdab4e36dd026a498f0 (diff)
Improved argument parsing and added support for prompting user for a
pattern if none is provided
-rw-r--r--README.md1
-rw-r--r--bp.11
-rw-r--r--bp.c79
-rw-r--r--definitions.c7
-rw-r--r--definitions.h4
-rw-r--r--match.c1
-rw-r--r--pattern.c36
-rw-r--r--pattern.h4
-rw-r--r--types.h1
9 files changed, 67 insertions, 67 deletions
diff --git a/README.md b/README.md
index 1050f72..d11e534 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/bp.1 b/bp.1
index ef56199..55e4396 100644
--- a/bp.1
+++ b/bp.1
@@ -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]
diff --git a/bp.c b/bp.c
index 3165b12..2b57a36 100644
--- a/bp.c
+++ b/bp.c
@@ -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)))
diff --git a/match.c b/match.c
index 8294dc1..237418a 100644
--- a/match.c
+++ b/match.c
@@ -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,
};
diff --git a/pattern.c b/pattern.c
index 6c2c52d..b378bc3 100644
--- a/pattern.c
+++ b/pattern.c
@@ -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);
}
//
diff --git a/pattern.h b/pattern.h
index c0a5ed4..c237b50 100644
--- a/pattern.h
+++ b/pattern.h
@@ -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);
diff --git a/types.h b/types.h
index 1631fd6..58b65d2 100644
--- a/types.h
+++ b/types.h
@@ -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;