From b08463f8de7ec7d698a046ede60b293b3cb4d6f7 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 16 Aug 2022 11:41:46 -0500 Subject: [PATCH] Reworked scripts/pretty_asserts.py a bit - Renamed explode_asserts.py -> pretty_asserts.py, this name is hopefully a bit more descriptive - Small cleanup of the parser rules - Added recognization of memcmp/strcmp => 0 statements and generate the relevant memory inspecting assert messages I attempted to fix the incorrect column numbers for the generated asserts, but unfortunately this didn't go anywhere and I don't think it's actually possible. There is no column control analogous to the #line directive. I thought you might be able to intermix #line directives to put arguments at the right column like so: assert(a == b); __PRETTY_ASSERT_INT_EQ( #line 1 a, #line 1 b); But this doesn't work as preprocessor directives are not allowed in macros arguments in standard C. Unfortunately this is probably not possible to fix without better support in the language. --- Makefile | 4 +- scripts/explode_asserts.py | 391 ---------------------------------- scripts/pretty_asserts.py | 426 +++++++++++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+), 393 deletions(-) delete mode 100755 scripts/explode_asserts.py create mode 100755 scripts/pretty_asserts.py diff --git a/Makefile b/Makefile index e6ccd854..bd829dd7 100644 --- a/Makefile +++ b/Makefile @@ -175,10 +175,10 @@ $(BUILDDIR)%.s: %.c $(CC) -S $(CFLAGS) $< -o $@ $(BUILDDIR)%.a.c: %.c - ./scripts/explode_asserts.py $< -o $@ + ./scripts/pretty_asserts.py -p LFS_ASSERT $< -o $@ $(BUILDDIR)%.a.c: $(BUILDDIR)%.c - ./scripts/explode_asserts.py $< -o $@ + ./scripts/pretty_asserts.py -p LFS_ASSERT $< -o $@ $(BUILDDIR)%.t.c: %.toml ./scripts/test.py -c $< $(TESTCFLAGS) -o $@ diff --git a/scripts/explode_asserts.py b/scripts/explode_asserts.py deleted file mode 100755 index e49dbc0c..00000000 --- a/scripts/explode_asserts.py +++ /dev/null @@ -1,391 +0,0 @@ -#!/usr/bin/env python3 - -import re -import sys - -PATTERN = ['LFS_ASSERT', 'assert'] -PREFIX = 'LFS' -MAXWIDTH = 16 - -ASSERT = "__{PREFIX}_ASSERT_{TYPE}_{COMP}" -FAIL = """ -__attribute__((unused)) -static void __{prefix}_assert_fail_{type}( - const char *file, int line, const char *comp, - {ctype} lh, size_t lsize, - {ctype} rh, size_t rsize) {{ - printf("%s:%d:assert: assert failed with ", file, line); - __{prefix}_assert_print_{type}(lh, lsize); - printf(", expected %s ", comp); - __{prefix}_assert_print_{type}(rh, rsize); - printf("\\n"); - fflush(NULL); - raise(SIGABRT); -}} -""" - -COMP = { - '==': 'eq', - '!=': 'ne', - '<=': 'le', - '>=': 'ge', - '<': 'lt', - '>': 'gt', -} - -TYPE = { - 'int': { - 'ctype': 'intmax_t', - 'fail': FAIL, - 'print': """ - __attribute__((unused)) - static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ - (void)size; - printf("%"PRIiMAX, v); - }} - """, - 'assert': """ - #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) - do {{ - __typeof__(lh) _lh = lh; - __typeof__(lh) _rh = (__typeof__(lh))rh; - if (!(_lh {op} _rh)) {{ - __{prefix}_assert_fail_{type}(file, line, "{comp}", - (intmax_t)_lh, 0, (intmax_t)_rh, 0); - }} - }} while (0) - """ - }, - 'bool': { - 'ctype': 'bool', - 'fail': FAIL, - 'print': """ - __attribute__((unused)) - static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ - (void)size; - printf("%s", v ? "true" : "false"); - }} - """, - 'assert': """ - #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) - do {{ - bool _lh = !!(lh); - bool _rh = !!(rh); - if (!(_lh {op} _rh)) {{ - __{prefix}_assert_fail_{type}(file, line, "{comp}", - _lh, 0, _rh, 0); - }} - }} while (0) - """ - }, - 'mem': { - 'ctype': 'const void *', - 'fail': FAIL, - 'print': """ - __attribute__((unused)) - static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ - const uint8_t *s = v; - printf("\\\""); - for (size_t i = 0; i < size && i < {maxwidth}; i++) {{ - if (s[i] >= ' ' && s[i] <= '~') {{ - printf("%c", s[i]); - }} else {{ - printf("\\\\x%02x", s[i]); - }} - }} - if (size > {maxwidth}) {{ - printf("..."); - }} - printf("\\\""); - }} - """, - 'assert': """ - #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh, size) - do {{ - const void *_lh = lh; - const void *_rh = rh; - if (!(memcmp(_lh, _rh, size) {op} 0)) {{ - __{prefix}_assert_fail_{type}(file, line, "{comp}", - _lh, size, _rh, size); - }} - }} while (0) - """ - }, - 'str': { - 'ctype': 'const char *', - 'fail': FAIL, - 'print': """ - __attribute__((unused)) - static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ - __{prefix}_assert_print_mem(v, size); - }} - """, - 'assert': """ - #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) - do {{ - const char *_lh = lh; - const char *_rh = rh; - if (!(strcmp(_lh, _rh) {op} 0)) {{ - __{prefix}_assert_fail_{type}(file, line, "{comp}", - _lh, strlen(_lh), _rh, strlen(_rh)); - }} - }} while (0) - """ - } -} - -def openio(path, mode='r'): - if path == '-': - if 'r' in mode: - return os.fdopen(os.dup(sys.stdin.fileno()), 'r') - else: - return os.fdopen(os.dup(sys.stdout.fileno()), 'w') - else: - return open(path, mode) - -def mkdecls(outf, maxwidth=16): - outf.write("#include \n") - outf.write("#include \n") - outf.write("#include \n") - outf.write("#include \n") - outf.write("#include \n") - - for type, desc in sorted(TYPE.items()): - format = { - 'type': type.lower(), 'TYPE': type.upper(), - 'ctype': desc['ctype'], - 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), - 'maxwidth': maxwidth, - } - outf.write(re.sub('\s+', ' ', - desc['print'].strip().format(**format))+'\n') - outf.write(re.sub('\s+', ' ', - desc['fail'].strip().format(**format))+'\n') - - for op, comp in sorted(COMP.items()): - format.update({ - 'comp': comp.lower(), 'COMP': comp.upper(), - 'op': op, - }) - outf.write(re.sub('\s+', ' ', - desc['assert'].strip().format(**format))+'\n') - -def mkassert(type, comp, lh, rh, size=None): - format = { - 'type': type.lower(), 'TYPE': type.upper(), - 'comp': comp.lower(), 'COMP': comp.upper(), - 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), - 'lh': lh.strip(' '), - 'rh': rh.strip(' '), - 'size': size, - } - if size: - return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh}, {size})') - .format(**format)) - else: - return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh})') - .format(**format)) - - -# simple recursive descent parser -LEX = { - 'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'], - 'assert': PATTERN, - 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"], - 'arrow': ['=>'], - 'paren': ['\(', '\)'], - 'op': ['strcmp', 'memcmp', '->'], - 'comp': ['==', '!=', '<=', '>=', '<', '>'], - 'logic': ['\&\&', '\|\|'], - 'sep': [':', ';', '\{', '\}', ','], -} - -class ParseFailure(Exception): - def __init__(self, expected, found): - self.expected = expected - self.found = found - - def __str__(self): - return "expected %r, found %s..." % ( - self.expected, repr(self.found)[:70]) - -class Parse: - def __init__(self, inf, lexemes): - p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l)) - for n, l in lexemes.items()) - p = re.compile(p, re.DOTALL) - data = inf.read() - tokens = [] - while True: - m = p.search(data) - if m: - if m.start() > 0: - tokens.append((None, data[:m.start()])) - tokens.append((m.lastgroup, m.group())) - data = data[m.end():] - else: - tokens.append((None, data)) - break - self.tokens = tokens - self.off = 0 - - def lookahead(self, *pattern): - if self.off < len(self.tokens): - token = self.tokens[self.off] - if token[0] in pattern or token[1] in pattern: - self.m = token[1] - return self.m - self.m = None - return self.m - - def accept(self, *patterns): - m = self.lookahead(*patterns) - if m is not None: - self.off += 1 - return m - - def expect(self, *patterns): - m = self.accept(*patterns) - if not m: - raise ParseFailure(patterns, self.tokens[self.off:]) - return m - - def push(self): - return self.off - - def pop(self, state): - self.off = state - -def passert(p): - def pastr(p): - p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') - p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') - lh = pexpr(p) ; p.accept('ws') - p.expect(',') ; p.accept('ws') - rh = pexpr(p) ; p.accept('ws') - p.expect(')') ; p.accept('ws') - comp = p.expect('comp') ; p.accept('ws') - p.expect('0') ; p.accept('ws') - p.expect(')') - return mkassert('str', COMP[comp], lh, rh) - - def pamem(p): - p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') - p.expect('memcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') - lh = pexpr(p) ; p.accept('ws') - p.expect(',') ; p.accept('ws') - rh = pexpr(p) ; p.accept('ws') - p.expect(',') ; p.accept('ws') - size = pexpr(p) ; p.accept('ws') - p.expect(')') ; p.accept('ws') - comp = p.expect('comp') ; p.accept('ws') - p.expect('0') ; p.accept('ws') - p.expect(')') - return mkassert('mem', COMP[comp], lh, rh, size) - - def paint(p): - p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') - lh = pexpr(p) ; p.accept('ws') - comp = p.expect('comp') ; p.accept('ws') - rh = pexpr(p) ; p.accept('ws') - p.expect(')') - return mkassert('int', COMP[comp], lh, rh) - - def pabool(p): - p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') - lh = pexprs(p) ; p.accept('ws') - p.expect(')') - return mkassert('bool', 'eq', lh, 'true') - - def pa(p): - return p.expect('assert') - - state = p.push() - lastf = None - for pa in [pastr, pamem, paint, pabool, pa]: - try: - return pa(p) - except ParseFailure as f: - p.pop(state) - lastf = f - else: - raise lastf - -def pexpr(p): - res = [] - while True: - if p.accept('('): - res.append(p.m) - while True: - res.append(pexprs(p)) - if p.accept('sep'): - res.append(p.m) - else: - break - res.append(p.expect(')')) - elif p.lookahead('assert'): - res.append(passert(p)) - elif p.accept('assert', 'ws', 'string', 'op', None): - res.append(p.m) - else: - return ''.join(res) - -def pexprs(p): - res = [] - while True: - res.append(pexpr(p)) - if p.accept('comp', 'logic', ','): - res.append(p.m) - else: - return ''.join(res) - -def pstmt(p): - ws = p.accept('ws') or '' - lh = pexprs(p) - if p.accept('=>'): - rh = pexprs(p) - return ws + mkassert('int', 'eq', lh, rh) - else: - return ws + lh - - -def main(args): - with openio(args.input or '-', 'r') as inf: - with openio(args.output or '-', 'w') as outf: - lexemes = LEX.copy() - if args.pattern: - lexemes['assert'] = args.pattern - p = Parse(inf, lexemes) - - # write extra verbose asserts - mkdecls(outf, maxwidth=args.maxwidth) - if args.input: - outf.write("#line %d \"%s\"\n" % (1, args.input)) - - # parse and write out stmt at a time - try: - while True: - outf.write(pstmt(p)) - if p.accept('sep'): - outf.write(p.m) - else: - break - except ParseFailure as f: - pass - - for i in range(p.off, len(p.tokens)): - outf.write(p.tokens[i][1]) - -if __name__ == "__main__": - import argparse - parser = argparse.ArgumentParser( - description="Cpp step that increases assert verbosity") - parser.add_argument('input', nargs='?', - help="Input C file after cpp.") - parser.add_argument('-o', '--output', required=True, - help="Output C file.") - parser.add_argument('-p', '--pattern', action='append', - help="Patterns to search for starting an assert statement.") - parser.add_argument('--maxwidth', default=MAXWIDTH, type=int, - help="Maximum number of characters to display for strcmp and memcmp.") - main(parser.parse_args()) diff --git a/scripts/pretty_asserts.py b/scripts/pretty_asserts.py new file mode 100755 index 00000000..ceb206ca --- /dev/null +++ b/scripts/pretty_asserts.py @@ -0,0 +1,426 @@ +#!/usr/bin/env python3 + +import re +import sys + +# NOTE the use of macros here helps keep a consistent stack depth which +# tools may rely on. +# +# If compilation errors are noisy consider using -ftrack-macro-expansion=0. +# + +LIMIT = 16 + +CMP = { + '==': 'eq', + '!=': 'ne', + '<=': 'le', + '>=': 'ge', + '<': 'lt', + '>': 'gt', +} + +LEXEMES = { + 'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'], + 'assert': ['assert'], + 'arrow': ['=>'], + 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"], + 'paren': ['\(', '\)'], + 'cmp': CMP.keys(), + 'logic': ['\&\&', '\|\|'], + 'sep': [':', ';', '\{', '\}', ','], +} + + +def openio(path, mode='r'): + if path == '-': + if 'r' in mode: + return os.fdopen(os.dup(sys.stdin.fileno()), 'r') + else: + return os.fdopen(os.dup(sys.stdout.fileno()), 'w') + else: + return open(path, mode) + +def write_header(f, limit=LIMIT): + f.writeln("// Generated by %s:" % sys.argv[0]) + f.writeln("//") + f.writeln("// %s" % ' '.join(sys.argv)) + f.writeln("//") + f.writeln() + + f.writeln("#include ") + f.writeln("#include ") + f.writeln("#include ") + f.writeln("#include ") + f.writeln("#include ") + f.writeln() + + # write print macros + f.writeln("__attribute__((unused))") + f.writeln("static void __pretty_assert_print_bool(") + f.writeln(" const void *v, size_t size) {") + f.writeln(" (void)size;") + f.writeln(" printf(\"%s\", *(const bool*)v ? \"true\" : \"false\");") + f.writeln("}") + f.writeln() + f.writeln("__attribute__((unused))") + f.writeln("static void __pretty_assert_print_int(") + f.writeln(" const void *v, size_t size) {") + f.writeln(" (void)size;") + f.writeln(" printf(\"%\"PRIiMAX, *(const intmax_t*)v);") + f.writeln("}") + f.writeln() + f.writeln("__attribute__((unused))") + f.writeln("static void __pretty_assert_print_mem(") + f.writeln(" const void *v, size_t size) {") + f.writeln(" const uint8_t *v_ = v;") + f.writeln(" printf(\"\\\"\");") + f.writeln(" for (size_t i = 0; i < size && i < %d; i++) {" % limit) + f.writeln(" if (v_[i] >= ' ' && v_[i] <= '~') {") + f.writeln(" printf(\"%c\", v_[i]);") + f.writeln(" } else {") + f.writeln(" printf(\"\\\\x%02x\", v_[i]);") + f.writeln(" }") + f.writeln(" }") + f.writeln(" if (size > %d) {" % limit) + f.writeln(" printf(\"...\");") + f.writeln(" }") + f.writeln(" printf(\"\\\"\");") + f.writeln("}") + f.writeln() + f.writeln("__attribute__((unused))") + f.writeln("static void __pretty_assert_print_str(") + f.writeln(" const void *v, size_t size) {") + f.writeln(" __pretty_assert_print_mem(v, size);") + f.writeln("}") + f.writeln() + f.writeln("__attribute__((unused, noinline))") + f.writeln("static void __pretty_assert_fail(") + f.writeln(" const char *file, int line,") + f.writeln(" void (*type_print_cb)(const void*, size_t),") + f.writeln(" const char *cmp,") + f.writeln(" const void *lh, size_t lsize,") + f.writeln(" const void *rh, size_t rsize) {") + f.writeln(" printf(\"%s:%d:assert: assert failed with \", file, line);") + f.writeln(" type_print_cb(lh, lsize);") + f.writeln(" printf(\", expected %s \", cmp);") + f.writeln(" type_print_cb(rh, rsize);") + f.writeln(" printf(\"\\n\");") + f.writeln(" fflush(NULL);") + f.writeln(" raise(SIGABRT);") + f.writeln("}") + f.writeln() + + # write assert macros + for op, cmp in sorted(CMP.items()): + f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\" + % cmp.upper()) + f.writeln(" bool _lh = !!(lh); \\") + f.writeln(" bool _rh = !!(rh); \\") + f.writeln(" if (!(_lh %s _rh)) { \\" % op) + f.writeln(" __pretty_assert_fail( \\") + f.writeln(" __FILE__, __LINE__, \\") + f.writeln(" __pretty_assert_print_bool, \"%s\", \\" + % cmp) + f.writeln(" &_lh, 0, \\") + f.writeln(" &_rh, 0); \\") + f.writeln(" } \\") + f.writeln("} while (0)") + for op, cmp in sorted(CMP.items()): + f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\" + % cmp.upper()) + f.writeln(" __typeof__(lh) _lh = lh; \\") + f.writeln(" __typeof__(lh) _rh = rh; \\") + f.writeln(" if (!(_lh %s _rh)) { \\" % op) + f.writeln(" __pretty_assert_fail( \\") + f.writeln(" __FILE__, __LINE__, \\") + f.writeln(" __pretty_assert_print_int, \"%s\", \\" + % cmp) + f.writeln(" &(intmax_t){_lh}, 0, \\") + f.writeln(" &(intmax_t){_rh}, 0); \\") + f.writeln(" } \\") + f.writeln("} while (0)") + for op, cmp in sorted(CMP.items()): + f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\" + % cmp.upper()) + f.writeln(" const void *_lh = lh; \\") + f.writeln(" const void *_rh = rh; \\") + f.writeln(" if (!(memcmp(_lh, _rh, size) %s 0)) { \\" % op) + f.writeln(" __pretty_assert_fail( \\") + f.writeln(" __FILE__, __LINE__, \\") + f.writeln(" __pretty_assert_print_mem, \"%s\", \\" + % cmp) + f.writeln(" _lh, size, \\") + f.writeln(" _rh, size); \\") + f.writeln(" } \\") + f.writeln("} while (0)") + for op, cmp in sorted(CMP.items()): + f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\" + % cmp.upper()) + f.writeln(" const char *_lh = lh; \\") + f.writeln(" const char *_rh = rh; \\") + f.writeln(" if (!(strcmp(_lh, _rh) %s 0)) { \\" % op) + f.writeln(" __pretty_assert_fail( \\") + f.writeln(" __FILE__, __LINE__, \\") + f.writeln(" __pretty_assert_print_str, \"%s\", \\" + % cmp) + f.writeln(" _lh, strlen(_lh), \\") + f.writeln(" _rh, strlen(_rh)); \\") + f.writeln(" } \\") + f.writeln("} while (0)") + f.writeln() + f.writeln() + +def mkassert(type, cmp, lh, rh, size=None): + if size is not None: + return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)" + % (type.upper(), cmp.upper(), lh, rh, size)) + else: + return ("__PRETTY_ASSERT_%s_%s(%s, %s)" + % (type.upper(), cmp.upper(), lh, rh)) + + +# simple recursive descent parser +class ParseFailure(Exception): + def __init__(self, expected, found): + self.expected = expected + self.found = found + + def __str__(self): + return "expected %r, found %s..." % ( + self.expected, repr(self.found)[:70]) + +class Parser: + def __init__(self, in_f, lexemes=LEXEMES): + p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l)) + for n, l in lexemes.items()) + p = re.compile(p, re.DOTALL) + data = in_f.read() + tokens = [] + line = 1 + col = 0 + while True: + m = p.search(data) + if m: + if m.start() > 0: + tokens.append((None, data[:m.start()], line, col)) + tokens.append((m.lastgroup, m.group(), line, col)) + data = data[m.end():] + else: + tokens.append((None, data, line, col)) + break + self.tokens = tokens + self.off = 0 + + def lookahead(self, *pattern): + if self.off < len(self.tokens): + token = self.tokens[self.off] + if token[0] in pattern or token[1] in pattern: + self.m = token[1] + return self.m + self.m = None + return self.m + + def accept(self, *patterns): + m = self.lookahead(*patterns) + if m is not None: + self.off += 1 + return m + + def expect(self, *patterns): + m = self.accept(*patterns) + if not m: + raise ParseFailure(patterns, self.tokens[self.off:]) + return m + + def push(self): + return self.off + + def pop(self, state): + self.off = state + +def p_assert(p): + state = p.push() + + # assert(memcmp(a,b,size) cmp 0)? + try: + p.expect('assert') ; p.accept('ws') + p.expect('(') ; p.accept('ws') + p.expect('memcmp') ; p.accept('ws') + p.expect('(') ; p.accept('ws') + lh = p_expr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + rh = p_expr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + size = p_expr(p) ; p.accept('ws') + p.expect(')') ; p.accept('ws') + cmp = p.expect('cmp') ; p.accept('ws') + p.expect('0') ; p.accept('ws') + p.expect(')') + return mkassert('mem', CMP[cmp], lh, rh, size) + except ParseFailure: + p.pop(state) + + # assert(strcmp(a,b) cmp 0)? + try: + p.expect('assert') ; p.accept('ws') + p.expect('(') ; p.accept('ws') + p.expect('strcmp') ; p.accept('ws') + p.expect('(') ; p.accept('ws') + lh = p_expr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + rh = p_expr(p) ; p.accept('ws') + p.expect(')') ; p.accept('ws') + cmp = p.expect('cmp') ; p.accept('ws') + p.expect('0') ; p.accept('ws') + p.expect(')') + return mkassert('str', CMP[cmp], lh, rh) + except ParseFailure: + p.pop(state) + + # assert(a cmp b)? + try: + p.expect('assert') ; p.accept('ws') + p.expect('(') ; p.accept('ws') + lh = p_expr(p) ; p.accept('ws') + cmp = p.expect('cmp') ; p.accept('ws') + rh = p_expr(p) ; p.accept('ws') + p.expect(')') + return mkassert('int', CMP[cmp], lh, rh) + except ParseFailure: + p.pop(state) + + # assert(a)? + p.expect('assert') ; p.accept('ws') + p.expect('(') ; p.accept('ws') + lh = p_exprs(p) ; p.accept('ws') + p.expect(')') + return mkassert('bool', 'eq', lh, 'true') + +def p_expr(p): + res = [] + while True: + if p.accept('('): + res.append(p.m) + while True: + res.append(p_exprs(p)) + if p.accept('sep'): + res.append(p.m) + else: + break + res.append(p.expect(')')) + elif p.lookahead('assert'): + state = p.push() + try: + res.append(p_assert(p)) + except ParseFailure: + p.pop(state) + res.append(p.expect('assert')) + elif p.accept('string', None, 'ws'): + res.append(p.m) + else: + return ''.join(res) + +def p_exprs(p): + res = [] + while True: + res.append(p_expr(p)) + if p.accept('cmp', 'logic', ','): + res.append(p.m) + else: + return ''.join(res) + +def p_stmt(p): + ws = p.accept('ws') or '' + + # memcmp(lh,rh,size) => 0? + if p.lookahead('memcmp'): + state = p.push() + try: + p.expect('memcmp') ; p.accept('ws') + p.expect('(') ; p.accept('ws') + lh = p_expr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + rh = p_expr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + size = p_expr(p) ; p.accept('ws') + p.expect(')') ; p.accept('ws') + p.expect('=>') ; p.accept('ws') + p.expect('0') ; p.accept('ws') + return ws + mkassert('mem', 'eq', lh, rh, size) + except ParseFailure: + p.pop(state) + + # strcmp(lh,rh) => 0? + if p.lookahead('strcmp'): + state = p.push() + try: + p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') + lh = p_expr(p) ; p.accept('ws') + p.expect(',') ; p.accept('ws') + rh = p_expr(p) ; p.accept('ws') + p.expect(')') ; p.accept('ws') + p.expect('=>') ; p.accept('ws') + p.expect('0') ; p.accept('ws') + return ws + mkassert('str', 'eq', lh, rh) + except ParseFailure: + p.pop(state) + + # lh => rh? + lh = p_exprs(p) + if p.accept('=>'): + rh = p_exprs(p) + return ws + mkassert('int', 'eq', lh, rh) + else: + return ws + lh + +def main(input=None, output=None, pattern=[], limit=LIMIT): + with openio(input or '-', 'r') as in_f: + # create parser + lexemes = LEXEMES.copy() + lexemes['assert'] += pattern + p = Parser(in_f, lexemes) + + with openio(output or '-', 'w') as f: + def writeln(s=''): + f.write(s) + f.write('\n') + f.writeln = writeln + + # write extra verbose asserts + write_header(f, limit=limit) + if input is not None: + f.writeln("#line %d \"%s\"" % (1, input)) + + # parse and write out stmt at a time + try: + while True: + f.write(p_stmt(p)) + if p.accept('sep'): + f.write(p.m) + else: + break + except ParseFailure as f: + pass + + for i in range(p.off, len(p.tokens)): + f.write(p.tokens[i][1]) + + +if __name__ == "__main__": + import argparse + import sys + parser = argparse.ArgumentParser( + description="Preprocessor that makes asserts easy to debug.") + parser.add_argument('input', + help="Input C file.") + parser.add_argument('-o', '--output', required=True, + help="Output C file.") + parser.add_argument('-p', '--pattern', action='append', + help="Regex patterns to search for starting an assert statement. This" + " implicitly includes \"assert\" and \"=>\".") + parser.add_argument('-l', '--limit', default=LIMIT, type=int, + help="Maximum number of characters to display in strcmp and memcmp.") + sys.exit(main(**{k: v + for k, v in vars(parser.parse_args()).items() + if v is not None}))