#! /usr/bin/env python
from __future__ import with_statement
import errno
import os
import re
import sys
import string

if __name__ == "__main__":
    _base = sys.argv[0]
else:
    _base = __file__

_script_home = os.path.abspath(os.path.dirname(_base))

srcdir = os.path.dirname(os.path.dirname(_script_home))

EXCLUDES = ["bitset.h", "cStringIO.h", "graminit.h", "grammar.h",
            "longintrepr.h", "metagrammar.h",
            "node.h", "opcode.h", "osdefs.h", "pgenheaders.h",
            "py_curses.h", "parsetok.h", "symtable.h", "token.h"]


def list_headers():
    """Return a list of headers."""
    incdir = os.path.join(srcdir, "Include")
    return [os.path.join(incdir, fn) for fn in os.listdir(incdir)
            if fn.endswith(".h") and fn not in EXCLUDES]


def matcher(pattern):
    return re.compile(pattern).search

MATCHERS = [
    # XXX this should also deal with ctypedesc, cvardesc and cmemberdesc
    matcher(r"\\begin\{cfuncdesc\}\{(?P<result>[^}]*)\}\{(?P<sym>[^}]*)\}{(?P<params>[^}]*)\}"),
    matcher(r"\\cfuncline\{(?P<result>[^})]*)\}\{(?P<sym>[^}]*)\}{(?P<params>[^}]*)\}"),
    ]

def list_documented_items():
    """Return a list of everything that's already documented."""
    apidir = os.path.join(srcdir, "Doc", "api")
    files = [fn for fn in os.listdir(apidir) if fn.endswith(".tex")]
    L = []
    for fn in files:
        fullname = os.path.join(apidir, fn)
	data = open(fullname).read()
        for matcher in MATCHERS:
            pos = 0
            while 1:
                m = matcher(data, pos)
                if not m: break
                pos = m.end()
                sym = m.group("sym")
                result = m.group("result")
                params = m.group("params")
		# replace all whitespace with a single one
		params = " ".join(params.split()) 
                L.append((sym, result, params, fn))
    return L

def normalize_type(t):
    t = t.strip()
    s = t.rfind("*")
    if s != -1:
        # strip everything after the pointer name
        t = t[:s+1]
    # Drop the variable name
    s = t.split()
    typenames = 1
    if len(s)>1 and s[0]=='unsigned' and s[1]=='int':
        typenames = 2
    if len(s) > typenames and s[-1][0] in string.letters:
        del s[-1]
    if not s:
       print "XXX", t
       return ""
    # Drop register
    if s[0] == "register":
        del s[0]
    # discard all spaces
    return ''.join(s)
    
def compare_type(t1, t2):
    t1 = normalize_type(t1)
    t2 = normalize_type(t2)
    if t1 == r'\moreargs' and t2 == '...':
        return False
    if t1 != t2:
        #print "different:", t1, t2
        return False
    return True


def compare_types(ret, params, hret, hparams):
    if not compare_type(ret, hret):
        return False
    params = params.split(",")
    hparams = hparams.split(",")
    if not params and hparams == ['void']:
        return True
    if not hparams and params == ['void']:
        return True
    if len(params) != len(hparams):
        return False
    for p1, p2 in zip(params, hparams):
        if not compare_type(p1, p2):
            return False
    return True

def main():
    headers = list_headers()
    documented = list_documented_items()

    lines = []
    for h in headers:
        data = open(h).read()
        data, n = re.subn(r"PyAPI_FUNC\(([^)]*)\)", r"\1", data)
        name = os.path.basename(h)
        with open(name, "w") as f:
            f.write(data)
        cmd = ("ctags -f - --file-scope=no --c-kinds=p --fields=S "
               "-Istaticforward -Istatichere=static " + name)
        with os.popen(cmd) as f:
            lines.extend(f.readlines())
        os.unlink(name)
    L = {}
    prevsym = None
    for line in lines:
        if not line:
            break
        sym, filename, signature = line.split(None, 2)
        if sym == prevsym:
            continue
	expr = "\^(.*)%s" % sym
	m = re.search(expr, signature)
        if not m:
    	    print "Could not split",signature, "using",expr
	rettype = m.group(1).strip()
	m = re.search("signature:\(([^)]*)\)", signature)
	if not m:
	    print "Could not get signature from", signature
	params = m.group(1)
	L[sym] = (rettype, params)

    for sym, ret, params, fn in documented:
        if sym not in L:
           print "No declaration for '%s'" % sym
           continue
        hret, hparams = L[sym]
        if not compare_types(ret, params, hret, hparams):
           print "Declaration error for %s (%s):" % (sym, fn)
           print ret+": "+params
           print hret+": "+hparams

if __name__ == "__main__":
    main()
