#! /usr/bin/env python

# Produces a .api file for SciTE's identifier completion and calltip features.
# invoke as 
#   python tags2api.py tags >x.api
# before running this program, create a tags file with
#   ctags --excmd=number --c-types=pcdgstu <header files>

import fileinput
import string
import time

# Definitions that start with _ are often used for infrastructure
# like include guards and can be removed with
removePrivate=1

# The Windows headers include both ANSI and UniCode versions
# of many functions with macros that default to one or the other.
# When winMode is on, these 4 lines are replaced with either the
# ANSI or UniCode prototype.
winMode = 1
include="A" # Set to "W" if you want the UniCode prototypes.

class FileCache:
    '''Caches the contents of a set of files.
    Avoids reading files repeatedly from disk by holding onto the
    contents of each file as a list of strings.
    '''

    def __init__(self):
        self.filecache = {}
        
    def grabFile(self, filename):
        '''Return the contents of a file as a list of strings.
        New line characters are removed.
        '''
        if not self.filecache.has_key(filename):
            contents=[]
            f = open(filename)
            for line in f.readlines():
                if line[-1:] == '\n': line = line[:-1]
                contents.append(line)
            f.close()
            self.filecache[filename] = contents
        return self.filecache[filename]

def bracesDiff(s):
    ''' Counts the number of '(' and ')' in a string and returns the difference between the two.
    Used to work out when a function prototype is complete.
    '''
    diff = 0
    mode=0 # 0 <=> default, 1 <=> comment, 2 <=> string
    for i in range(len(s)):
        if mode == 0: # default mode
            if s[i]=='(':
                diff += 1
            elif s[i]==')':
                diff -= 1
            elif s[i]=='"':
                mode=2
            elif i>0 and s[i-1]=='/' and s[i]=='/':
                return diff
            elif i>0 and s[i-1]=='/' and s[i]=='*':
                mode=1
        elif mode == 1: # comment
            if i>0 and s[i-1]=='*' and s[i]=='/':
                mode=0
        elif mode == 2: # string
            if s[i]=='"':
                mode=0
    return diff

fc = FileCache()
apis = set()
for line in fileinput.input():
    if line[0] != '!':  # Not a comment.
        (entityName, fileName, lineNo, tagType) = string.split(line, "\t")[:4]
        curLineNo = string.atoi(lineNo[:-2]) - 1    # -1 because line numbers in tags file start at 1.
        contents = fc.grabFile(fileName)
        if (not removePrivate or entityName[0] != '_') and not entityName.startswith("operator "):
            if tagType[0] in "pf":  # Function prototype.
                try:
                    braces = bracesDiff(contents[curLineNo])
                    curDef = contents[curLineNo]
                    while braces > 0:   # Search for end of prototype.
                        curLineNo = curLineNo + 1
                        braces = braces + bracesDiff(contents[curLineNo])
                        curDef = curDef + contents[curLineNo]
                    # Normalise the appearance of the prototype.
                    curDef = string.strip(curDef)
                    # Replace whitespace sequences with a single space character.
                    curDef = string.join(string.split(curDef))
                    # Remove space around the '('.
                    curDef = string.replace(string.replace(curDef, " (", '('), "( ", '(')
                    # Remove trailing semicolon.
                    curDef = string.replace(curDef, ";", '')
                    # Remove implementation if present.
                    if "{" in curDef and "}" in curDef:
                        startImpl = curDef.find("{")
                        endImpl = curDef.find("}")
                        curDef = curDef[:startImpl] + curDef[endImpl+1:]
                    else:
                        # Remove trailing brace.
                        curDef = curDef.rstrip("{")
                    # Remove return type.
                    curDef = curDef[string.find(curDef, entityName):]
                    # Remove virtual indicator.
                    if curDef.replace(" ", "").endswith(")=0"):
                        curDef = curDef.rstrip("0 ")
                        curDef = curDef.rstrip("= ")
                    # Remove trailing space.
                    curDef = curDef.rstrip()
                    if winMode:
                        if string.find(curDef, "A(") >= 0:
                            if "A" in include:
                                apis.add(string.replace(curDef, "A(", '('))
                        elif string.find(curDef, "W(") >= 0:
                            if "W" in include:
                                apis.add(string.replace(curDef, "W(", '('))
                        else:   # A character set independent function.
                            apis.add(curDef)
                    else:
                        apis.add(curDef)
                except IndexError:
                    pass
            elif tagType[0] == 'd': # Macro definition.
                curDef = contents[curLineNo]
                if (not winMode) or (curDef[-1] not in "AW"):
                    apis.add(entityName)
            else:
                apis.add(entityName)
print "\n".join(sorted(list(apis)))

