#!/usr/bin/env python3 # Simple C PreProcessor for cffi # (c) 2017 Taeyeon Mori import re import functools def parse_macro(mac): name = [] arglist = None body = [] it = iter(mac) for c in it: if c == "(": arglist = [] break elif c.isspace(): break else: name.append(c) name = "".join(name).strip() if arglist is not None: thisarg = [] for c in it: if c == ")": if thisarg: arglist.append("".join(thisarg).strip()) break elif c == ",": if thisarg: arglist.append("".join(thisarg).strip()) thisarg.clear() else: thisarg.append(c) body = "".join(it) if arglist: argrep = re.compile(r"\b(%s)\b" % "|".join(arglist)) fn = lambda args: argrep.sub(lambda m: args[arglist.index(m.group(1))], body) return name + "()", fn return name, body.strip() def_re = re.compile(r"\b\w+\b") def def_sub(defs, m): token = m.group(0) if token in defs: return " " + defs[token] + " " else: return token def make_fnmacrocall_pattern(definitions): fns = [x[:-2] for x in definitions.keys() if x[-2:] == "()"] if fns: return re.compile(r"\b(%s)\(" % "|".join(fns)) return None def sub_macros(definitions, fnpattern, line): if fnpattern: m = fnpattern.search(line) while m: args = [] bracket_level = 0 current = [] argslen = 1 for c in line[m.end(1)+1:]: argslen += 1 if c == "," and bracket_level == 0: args.append("".join(current)) current.clear() else: if c in "([{": bracket_level += 1 elif c in ")]}": bracket_level -= 1 if bracket_level < 0: if current: args.append("".join(current)) break current.append(c) line = line[:m.start(1)] + definitions[m.group(1) + "()"](args) + line[m.end(1) + argslen:] m = fnpattern.search(line) return def_re.sub(functools.partial(def_sub, definitions), line) def simple_cpp(lines, definitions=None): """ Very simple C preprocessor """ ifs = [] definitions = definitions if definitions is not None else {} fnpattern = make_fnmacrocall_pattern(definitions) prevline = None for line in lines: # Continuation if prevline: line = prevline + line.strip() prevline = None if line.lstrip("\r\n").endswith("\\"): prevline = line.lstrip("\r\n")[:-1] continue # comments while "/*" in line and "*/" in line: line = line[:line.index("/*")] + line[line.index("*/") + 2:] if "//" in line: line = line[:line.index("//")] if line.strip(): line += "\n" if "/*" in line: prevline = line.lstrip("\r\n") continue # Preprocessor directives if line.rstrip().startswith("#"): # Process cpp_line = line.rstrip()[1:].strip().split(maxsplit=1) directive = cpp_line[0] args = cpp_line[1] if len(cpp_line) > 1 else None if directive == "ifdef": ifs.append(args in definitions or args + "()" in definitions) elif directive == "ifndef": ifs.append(args not in definitions and args + "()" not in definitions) elif directive == "if": print("Simple CPP Warning: #if is unsupported: %s" % args) ifs.append(False) elif directive == "elif": print("Simple CPP Warning: #elif is unsupported: %s" % args) ifs[-1] = False elif directive == "else": ifs[-1] = not ifs[-1] elif directive == "endif": ifs.pop() elif all(ifs): if directive == "define": name, value = parse_macro(args) definitions[name] = value if name[-2:] == "()": fnpattern = make_fnmacrocall_pattern(definitions) elif value: yield "#define %s ...\n" % name else: print("Simple CPP Warning: Ignoring unknown directive %s" % ((directive, args),)) elif all(ifs): yield sub_macros(definitions, fnpattern, line) if prevline: raise ValueError("Line continuation on last line!")