#!/usr/bin/env python
"""
Generate a Python module from a C file of macro definitions.

Uses simple regexp transformations, so weird headers are likely to confuse it.
Skips #include lines, struct and union declarations, single-line typedefs
and externs.  Transforms comments. Does not yet handle multiline externs
or variable declarations.

Yes, SWIG can do something like this.  But it's heavyweight and would introduce
another dependency.
"""

import sys, re

patterns = (
    (re.compile(r"/\*(.*)\*/"), r"#\1"),
    (re.compile(r"^/\*"), r"#"),
    (re.compile(r"^ *\*/"), r"#"),
    (re.compile(r"^ \*"), r"#"),
    (re.compile(r"^#define\s+([A-Z0-9_]+[ \t]+)(.*)"), r"\1= \2"), 
    (re.compile(r"^#define\s+([A-Z0-9_]+)\(([a-z_, ]*)\)"),
     r"def \1(\2): return"), 
    )

skipstarters = ("struct", "union", "typedef struct", "typedef union")

skippers = (
    re.compile(r"^#include"), 
    re.compile(r"^typedef.*;\s*$"), 
    re.compile(r"^extern.*;\d*$"), 
    )

impossibles = ("sizeof", "offsetof")

def pythonize(rfp, wfp):
    "Pythonize a specified C header file."
    wfp.write("# This file was generated by pythonize-header;\n")
    wfp.write("# DO NOT HAND-HACK IT!\n")
    skipto = False
    ignoring = False
    for line in rfp:
        # The ignore logic
        if "pythonize-header" in line:
            if "start" in line:
                ignoring = True
            elif "stop" in line:
                ignoring = False
            continue
        if ignoring:
            continue
        # Omit include lines
        skipit = False
        for skipline in skippers:
            if skipline.match(line):
                skipit = True
                break
        if skipit:
            continue
        # Omit structure and union declarations
        for skipstart in skipstarters:
            if line.startswith(skipstart):
                skipto = True
                break
        if line.startswith("};"):
            skipto = False
            continue
        # Hack remaining content
        for (regexp, replacement) in patterns:
            line = regexp.sub(replacement, line)
        # Omit some expression elements Python cannot evaluate
        for word in impossibles:
            if word in line:
                line = "# " + line
        # We're done
        if not skipto:
            wfp.write(line)

if __name__ == '__main__':
    if len(sys.argv) == 1:
        pythonize(sys.stdin, sys.stdout)
    else:
        for path in sys.argv[1:]:
            with open(path) as rfp:
                sys.stdout.write("#\n# Definitions from %s begin\n#\n" % path)
                pythonize(rfp, sys.stdout)

# end
