grep: teach --debug option to dump the parse tree

Our "grep" allows complex boolean expressions to be formed to match each individual line with operators like --and, '(', ')' and --not. Introduce the "--debug" option to show the parse tree to help people who want to debug and enhance it. Also "log" learns "--grep-debug" option to do the same. The command line parser to the log family is a lot more limited than the general "git grep" parser, but it has special handling for header matching (e.g. "--author"), and a parse tree is valuable when working on it. Note that "--all-match" is *not* any individual node in the parse tree. It is an instruction to the evaluator to check all the nodes in the top-level backbone have matched and reject a document as non-matching otherwise. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Sep 13, 2012 at 14:21 UTC 17bf35a3c7b46df7131681bcc5bee5f12e1caec4
4 files changed +96 -2
builtin/grep.c
+3
@@ -772,6 +772,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
772 "indicate hit with exit status without output"),
773 OPT_BOOLEAN(0, "all-match", &opt.all_match,
774 "show only matches from files that match all patterns"),
775 + { OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
776 + "show parse tree for grep expression",
777 + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 },
778 OPT_GROUP(""),
779 { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
780 "pager", "show matching files in the pager",
grep.c
+90 -2
@@ -332,6 +332,87 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
332 return compile_pattern_or(list);
333 }
334
335 +static void indent(int in)
336 +{
337 + while (in-- > 0)
338 + fputc(' ', stderr);
339 +}
340 +
341 +static void dump_grep_pat(struct grep_pat *p)
342 +{
343 + switch (p->token) {
344 + case GREP_AND: fprintf(stderr, "*and*"); break;
345 + case GREP_OPEN_PAREN: fprintf(stderr, "*(*"); break;
346 + case GREP_CLOSE_PAREN: fprintf(stderr, "*)*"); break;
347 + case GREP_NOT: fprintf(stderr, "*not*"); break;
348 + case GREP_OR: fprintf(stderr, "*or*"); break;
349 +
350 + case GREP_PATTERN: fprintf(stderr, "pattern"); break;
351 + case GREP_PATTERN_HEAD: fprintf(stderr, "pattern_head"); break;
352 + case GREP_PATTERN_BODY: fprintf(stderr, "pattern_body"); break;
353 + }
354 +
355 + switch (p->token) {
356 + default: break;
357 + case GREP_PATTERN_HEAD:
358 + fprintf(stderr, "<head %d>", p->field); break;
359 + case GREP_PATTERN_BODY:
360 + fprintf(stderr, "<body>"); break;
361 + }
362 + switch (p->token) {
363 + default: break;
364 + case GREP_PATTERN_HEAD:
365 + case GREP_PATTERN_BODY:
366 + case GREP_PATTERN:
367 + fprintf(stderr, "%.*s", (int)p->patternlen, p->pattern);
368 + break;
369 + }
370 + fputc('\n', stderr);
371 +}
372 +
373 +static void dump_grep_expression_1(struct grep_expr *x, int in)
374 +{
375 + indent(in);
376 + switch (x->node) {
377 + case GREP_NODE_TRUE:
378 + fprintf(stderr, "true\n");
379 + break;
380 + case GREP_NODE_ATOM:
381 + dump_grep_pat(x->u.atom);
382 + break;
383 + case GREP_NODE_NOT:
384 + fprintf(stderr, "(not\n");
385 + dump_grep_expression_1(x->u.unary, in+1);
386 + indent(in);
387 + fprintf(stderr, ")\n");
388 + break;
389 + case GREP_NODE_AND:
390 + fprintf(stderr, "(and\n");
391 + dump_grep_expression_1(x->u.binary.left, in+1);
392 + dump_grep_expression_1(x->u.binary.right, in+1);
393 + indent(in);
394 + fprintf(stderr, ")\n");
395 + break;
396 + case GREP_NODE_OR:
397 + fprintf(stderr, "(or\n");
398 + dump_grep_expression_1(x->u.binary.left, in+1);
399 + dump_grep_expression_1(x->u.binary.right, in+1);
400 + indent(in);
401 + fprintf(stderr, ")\n");
402 + break;
403 + }
404 +}
405 +
406 +void dump_grep_expression(struct grep_opt *opt)
407 +{
408 + struct grep_expr *x = opt->pattern_expression;
409 +
410 + if (opt->all_match)
411 + fprintf(stderr, "[all-match]\n");
412 + dump_grep_expression_1(x, 0);
413 + fflush(NULL);
414 +}
415 +
416 static struct grep_expr *grep_true_expr(void)
417 {
418 struct grep_expr *z = xcalloc(1, sizeof(*z));
@@ -395,7 +476,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
476 return header_expr;
477 }
478
398 -void compile_grep_patterns(struct grep_opt *opt)
479 +static void compile_grep_patterns_real(struct grep_opt *opt)
480 {
481 struct grep_pat *p;
482 struct grep_expr *header_expr = prep_header_patterns(opt);
@@ -415,7 +496,7 @@ void compile_grep_patterns(struct grep_opt *opt)
496
497 if (opt->all_match || header_expr)
498 opt->extended = 1;
418 - else if (!opt->extended)
499 + else if (!opt->extended && !opt->debug)
500 return;
501
502 p = opt->pattern_list;
@@ -435,6 +516,13 @@ void compile_grep_patterns(struct grep_opt *opt)
516 opt->all_match = 1;
517 }
518
519 +void compile_grep_patterns(struct grep_opt *opt)
520 +{
521 + compile_grep_patterns_real(opt);
522 + if (opt->debug)
523 + dump_grep_expression(opt);
524 +}
525 +
526 static void free_pattern_expr(struct grep_expr *x)
527 {
528 switch (x->node) {
grep.h
+1
@@ -90,6 +90,7 @@ struct grep_opt {
90 int word_regexp;
91 int fixed;
92 int all_match;
93 + int debug;
94 #define GREP_BINARY_DEFAULT 0
95 #define GREP_BINARY_NOMATCH 1
96 #define GREP_BINARY_TEXT 2
revision.c
+2
@@ -1578,6 +1578,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
1578 } else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
1579 add_message_grep(revs, optarg);
1580 return argcount;
1581 + } else if (!strcmp(arg, "--grep-debug")) {
1582 + revs->grep_filter.debug = 1;
1583 } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
1584 revs->grep_filter.regflags |= REG_EXTENDED;
1585 } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {