Python CFFI bindings for image format libraries
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

161 lines
4.8 KiB

7 years ago
#!/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!")