#!/usr/bin/env python
######################################################################
# This tool parses header files inside a directory and stores info
# about the array size of function arguments into a file for later
# retrieval.
#
# usage: createarrayinfo.py [options] <headerpath>
#
# options:
#   -h, --help            show this help message and exit
#   -o FILENAME, --output=FILENAME
#                         Output file name (default: stdout)
#   -a ARGSTRING, --cppargs=ARGSTRING
#                         Additional C preproceesor arguments
#
# Author: Matthias Baas (baas@ira.uka.de)
######################################################################

import sys, os, os.path, glob, optparse
from cpptokenize import *

# Parser
class Parser:
    """Parser class.

    This class contains the token eater method that processes the tokens
    generated by cpptokenize.
    Whenever a function signature is parsed a line is written to the output.
    """
    
    def __init__(self, headerpath, output=sys.stdout):
        """Constructor.
        """
        
        self.headerpath = os.path.normpath(headerpath)
        self.output = output

        # Buffer for the last NAME token (which might be a function name)
        self.lastname = None
        # The current state
        self.state = "Outside"

        self.classname = None
        self.funcname = None
        self.args = None
        self.arraysize = None
        self.no_arg = False
        self.firstline = None
    
    def tokeater(self, type, s, start, end, line, filename):
        """Token eater."""
        if type==WHITESPACE or type==NEWLINE:
            return

        method = getattr(self, "state%s"%self.state)
        method(type, s, start, end, line, filename)

    # The state methods. They are called by the token eater and must take
    # the same arguments than the token eater.

    def stateOutside(self, type, s, start, end, line, filename):
        if type==NAME and s=="class":
            self.state = "ClassName"
        if type==NAME:
            self.firstline = start[0]
            self.lastname = s
        elif self.lastname=="operator":
            self.lastname += s
        elif type==OPERATOR and s=="(":
            self.funcname = self.lastname
            self.args = []
            self.arraysize = None
            self.no_arg = True
            self.state = "Args"

    def stateClassName(self, type, s, start, end, line, filename):
        if s.upper()==s:
            return
        self.classname = s
        self.state = "Outside"

    def stateArgs(self, type, s, start, end, line, filename):
        if s==")":
            if not self.no_arg:
                self.args.append(self.arraysize)
            self.state = "End"
        elif s==",":
            self.args.append(self.arraysize)
            self.arraysize = None
        elif s=="[":
            self.state = "ArgsSize"
        self.no_arg = False

    def stateArgsSize(self, type, s, start, end, line, filename):
        if s=="]":
            self.state = "Args"
        else:
            self.arraysize = int(s)
            self.state = "Args"

    def stateEnd(self, type, s, start, end, line, filename):
        if s==";":
            if os.path.normpath(os.path.dirname(filename))==self.headerpath:
                self.onFuncComplete(self.classname, self.funcname, self.args, self.firstline, end[0], filename)
            self.state = "Outside"
        

    def onFuncComplete(self, classname, funcname, args, firstline, lastline, filename):
        """Callback that is called when one function is completely processed.
        """
        print >>self.output, "%s;%d;%d;%s;%s;%s"%(filename, firstline, lastline, classname, funcname, args)


# parseHeader
def parseHeader(filename, cpp="cpp", cppargs="", output=sys.stdout):
    """Parse a header file.

    filename is the header file name and cppargs is a string with
    additional arguments for the invocation of the preprocessor 'cpp'.
    output is the output stream.
    """
    # Run the file through the preprocessor...
    filename = os.path.abspath(filename)
    print >>sys.stderr, "Parsing",filename
    cmd = '%s %s "%s" >_tmp.h'%(cpp, cppargs, filename.replace(" ", "\\ "))
    print >>sys.stderr, cmd
    os.system(cmd)

    # Parse the preprocessed file...
    parser = Parser(os.path.dirname(filename), output)
    tokenize(file("_tmp.h").readline, parser.tokeater)

######################################################################

# Preprocessor
cpp = "cpp"
# Preprocessor arguments
cppargs = ""
# Output stream
output = sys.stdout

usage = "usage: %prog [options] <headerpath>"
op = optparse.OptionParser(usage)
op.add_option("-o", "--output", metavar="FILENAME",
              help="Output file name")
op.add_option("-a", "--cppargs", metavar="ARGSTRING", default="",
              help="Additional C preproceesor arguments")

options, args = op.parse_args()

if len(args)==0:
    op.print_help()
    sys.exit(1)

if options.output!=None:
    print >>sys.stderr, "Output file: %s"%options.output
    output = file(options.output, "wt")

headerpath = args[0]
headernames = os.path.join(headerpath, "*.h")
print >>sys.stderr, "Input files: %s"%headernames

cppargs = options.cppargs
print >>sys.stderr, "Preprocessor args: %s"%cppargs

headers = glob.glob(headernames)
print >>sys.stderr, "%d header files found"%len(headers)
for header in headers:
    parseHeader(header, cpp, cppargs, output)
