/* GNU SED, a batch stream editor. Copyright (C) 2018 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; If not, see . */ /* Written by Assaf Gordon. */ /* debug.c: debugging functions */ #include "sed.h" #include "basicdefs.h" #include #include #include #include #include #include /* indentation level when printing the program */ static int block_level = 0; void debug_print_char (char c) { if (ISPRINT (c) && c != '\\') { putchar (c); return; } putchar ('\\'); switch (c) { case '\a': putchar ('a'); break; case '\f': putchar ('f'); break; case '\r': putchar ('r'); break; case '\t': putchar ('t'); break; case '\v': putchar ('v'); break; case '\n': putchar ('n'); break; case '\\': putchar ('\\'); break; default: printf ("o%03o", (unsigned int) c); } } static void debug_print_regex_pattern (const char *pat, size_t len) { const char *p = pat; while (len--) { if (*p == '/') fputs ("\\/", stdout); else debug_print_char (*p); ++p; } } static void debug_print_regex_flags (const struct regex *r, bool addr) { if (!r) return; #ifdef REG_PERL if (r->flags & REG_DOTALL) /* REG_PERL */ putchar ('s'); if (r->flags & REG_EXTENDED) /* REG_PERL */ putchar ('x'); #endif if (r->flags & REG_ICASE) putchar (addr ? 'I' : 'i'); if (r->flags & REG_NEWLINE) putchar (addr ? 'M' : 'm'); } static void debug_print_regex (const struct regex *r) { if (!r) { /* Previous Regex */ fputs ("//", stdout); return; } putchar ('/'); debug_print_regex_pattern (r->re, r->sz); putchar ('/'); } static void debug_print_addr (const struct addr *a) { if (!a) return; switch (a->addr_type) { case ADDR_IS_NULL: fputs ("[ADDR-NULL]", stdout); break; case ADDR_IS_REGEX: debug_print_regex (a->addr_regex); debug_print_regex_flags (a->addr_regex, true); break; case ADDR_IS_NUM: printf ("%lu", a->addr_number); break; case ADDR_IS_NUM_MOD: printf ("%lu~%lu", a->addr_number, a->addr_step); break; case ADDR_IS_STEP: printf ("+%lu", a->addr_step); break; case ADDR_IS_STEP_MOD: printf ("~%lu", a->addr_step); break; case ADDR_IS_LAST: putchar ('$'); break; } } static void debug_print_subst_replacement (const struct replacement *r) { enum replacement_types last_repl_type = REPL_ASIS; if (!r) return; const struct replacement *p = r; while (p) { if (p->repl_type != last_repl_type) { /* Special GNU replacements \E\U\u\L\l should be printed BEFORE the 'prefix' .... the 'prefix' refers to being before the backreference. */ putchar ('\\'); if (p->repl_type == 0) putchar ('E'); else if (p->repl_type == REPL_UPPERCASE) putchar ('U'); else if (p->repl_type == REPL_LOWERCASE) putchar ('L'); else if ((p->repl_type & REPL_MODIFIERS) == REPL_UPPERCASE_FIRST) putchar ('u'); else if ((p->repl_type & REPL_MODIFIERS) == REPL_LOWERCASE_FIRST) putchar ('l'); last_repl_type = p->repl_type; } if (p->prefix_length) fwrite (p->prefix, 1, p->prefix_length, stdout); if (p->subst_id != -1) { if (p->subst_id == 0) putchar ('&'); else printf ("\\%d", p->subst_id); } p = p->next; } } static void debug_print_output_file (const struct output *o) { if (!o) return; fputs (o->name, stdout); } static void debug_print_subst (const struct subst *s) { if (!s) return; debug_print_regex (s->regx); debug_print_subst_replacement (s->replacement); putchar ('/'); debug_print_regex_flags (s->regx, false); if (s->global) putchar ('g'); if (s->eval) putchar ('e'); if (s->print) putchar ('p'); if (s->numb) printf ("%lu", s->numb); if (s->outf) { putchar ('w'); debug_print_output_file (s->outf); } } static void debug_print_translation (const struct sed_cmd *sc) { unsigned int i; if (mb_cur_max > 1) { /* multibyte translation */ putchar ('/'); for (i = 0; sc->x.translatemb[2 * i] != NULL; i++) fputs (sc->x.translatemb[2 * i], stdout); putchar ('/'); for (i = 0; sc->x.translatemb[2 * i] != NULL; i++) fputs (sc->x.translatemb[2 * i + 1], stdout); putchar ('/'); } else { /* unibyte translation */ putchar ('/'); for (i = 0; i < 256; ++i) if (sc->x.translate[i] != (unsigned char) i) putchar ((unsigned char) i); putchar ('/'); for (i = 0; i < 256; ++i) if (sc->x.translate[i] != (unsigned char) i) putchar (sc->x.translate[i]); putchar ('/'); } } static void debug_print_function (const struct vector *program, const struct sed_cmd *sc) { if (!sc) return; putchar (sc->cmd); switch (sc->cmd) /* LCOV_EXCL_BR */ { case '=': break; case ':': printf ("%s", sc->x.label_name); break; case '{': break; case '}': break; case '#': /* LCOV_EXCL_LINE */ /* should not happen - discarded during compilation. */ assert (0); /* LCOV_EXCL_LINE */ case 'a': case 'c': case 'i': fputs ("\\", stdout); if (sc->x.cmd_txt.text_length) fwrite (sc->x.cmd_txt.text, 1, sc->x.cmd_txt.text_length, stdout); break; case 'b': case 't': case 'T': { if (sc->x.jump_index < program->v_length) { const char *label_name = program->v[sc->x.jump_index].x.label_name; if (label_name) printf (" %s", label_name); } } break; case 'D': break; case 'd': break; case 'e': putchar (' '); fwrite (sc->x.cmd_txt.text, 1, sc->x.cmd_txt.text_length, stdout); break; case 'F': break; case 'g': break; case 'G': break; case 'h': break; case 'H': break; /* 'i' is lumped above with 'a' and 'c' */ case 'L': case 'l': case 'q': case 'Q': if (sc->x.int_arg != -1) printf (" %d", sc->x.int_arg); break; case 'n': break; case 'N': break; case 'P': break; case 'p': break; /* 'q','Q' are lumped above with 'L' and 'l' */ case 'r': putchar (' '); fputs (sc->x.fname, stdout); break; case 'R': putchar (' '); fputs (sc->x.inf->name, stdout); break; case 's': debug_print_subst (sc->x.cmd_subst); break; /* 't','T' are lumped above with 'b' */ case 'v': /* LCOV_EXCL_LINE */ /* should not happen - handled during compilation then discarded. */ assert (0); /* LCOV_EXCL_LINE */ case 'W': debug_print_output_file (sc->x.outf); break; case 'w': debug_print_output_file (sc->x.outf); break; case 'x': break; case 'y': debug_print_translation (sc); break; case 'z': break; default: /* LCOV_EXCL_LINE */ /* should not happen - unless missed a sed command. */ assert (0); /* LCOV_EXCL_LINE */ } } void debug_print_command (const struct vector *program, const struct sed_cmd *sc) { bool addr_bang; if (!program) return; if (sc->cmd == '}') --block_level; for (int j = 0; j < block_level; ++j) fputs (" ", stdout); debug_print_addr (sc->a1); if (sc->a2) putchar (','); debug_print_addr (sc->a2); addr_bang = sc->addr_bang; /* Implmentation detail: GNU Sed implements beginning of block by negating the matched address and jumping if there's no match. */ if (sc->cmd == '{') addr_bang = !addr_bang; if (addr_bang) putchar ('!'); if (sc->a1 || sc->a2) putchar (' '); debug_print_function (program, sc); putchar ('\n'); if (sc->cmd == '{') ++block_level; } void debug_print_program (const struct vector *program) { if (!program) return; block_level = 1; puts ("SED PROGRAM:"); for (size_t i = 0; i < program->v_length; ++i) debug_print_command (program, &program->v[i]); block_level = 0; }