#!/usr/bin/python
import os
import sys

def find_line(lineno, file, words):
    ch = words[0][0]
    while 1:
        line = file.readline()
        if not line:
            return None, None
        lineno += 1
        if line[0] != ch:
            continue
        w = line.split()
        if w and w[0] in words: # removed "and len(w) == 1" b/c of lines "}  /* comment */"
            break
    return lineno, w

def find_inlines(filename):
    #print filename
    lineno = 0
    file = open(filename)
    while 1:
        line = file.readline()
        if not line:
            break
        lineno += 1

        words = line.split()
        # Usually, "static inline" are the first words.
        # If "static" isn't first, we usually are looking at a weird macro.
        # However, this is seen in the wild:
        #notrace static inline u64 vgetsns(int *mode)
        #notrace static int __always_inline do_realtime(struct timespec *ts)
        if words and words[0] == "notrace":
            del words[0]
        if words and words[0] != "static":
            continue
        while words:
            w = words[0]
            if w == "inline" or w == "__always_inline" or w == "__inline__" or w == "__inline":
                break
            del words[0]
        if not words:
            continue # next line

        # found "static ... inline"
        inline_line = lineno
        while 1:
            while not words:
                line = file.readline()
                if not line:
                    break
                lineno += 1
                words = line.split()
            if not words: # eof
                break
            w = words[0]
            if "(" in w:
                break
            del words[0]
        if not words:
            continue # next line

        # found "... func(..."
        funcline = lineno
        # TODO: deal with "func (..."
        funcname = w[:w.index("(")]
        # deal with "... char *func(..."
        while funcname.startswith("*"):
            funcname = funcname[1:]
        # detect null inlines with "{ }" or "{}" bodies on the same line as funcname
        # detect forward inlines: "inline f();"
        while words:
            if words[0].startswith("{"): # not really exact match...
                break
            if ";" in words[0]: # works only if it's on the same line. Counterexample: net/ipv4/netfilter/nf_nat_snmp_basic.c::mangle_address()
                break
            del words[0]
        if words:
            continue # next line
	# detect a macroized function (such as name##_readl)
	if not funcname.replace("_", "a").isalnum():
            continue # next line

        lineno, words = find_line(lineno, file, ["{", "{}"])
        if not lineno: #eof
            sys.stderr.write("{0}:{1}:{2}: can't find start\n".format(filename, funcline, funcname))
            break
        start = lineno
        if words[0] == "{}" or (len(words) > 1 and words[1] == "}"):
            # the body is "{ }" or "{}" on a separate line
            inline_len = 0
        else:
            lineno, words = find_line(lineno, file, ["}", "};"])
            if not lineno: #eof
                sys.stderr.write("{0}:{1}:{2}: can't find end\n".format(filename, funcline, funcname))
                break
            if words[0] == "};":
                sys.stderr.write("{0}:{1}: possibly bogus semicolon\n".format(filename, lineno))
            inline_len = lineno-1 - start
        sys.stdout.write("{0}:{1}:{2}:{3}\n".format(filename, inline_line, funcname, inline_len))

for dirname, dirnames, filenames in os.walk('.'):
    dirnames.sort()
    filenames.sort()
    if dirname.startswith("./"):
        dirname = dirname[2:]

    for filename in filenames:
        if filename.endswith(".h"):
            find_inlines(os.path.join(dirname, filename))

    for subdirname in dirnames:
        #print os.path.join(dirname, subdirname)
        pass

    # Advanced usage:
    # editing the 'dirnames' list will stop os.walk() from recursing into there.
    if '.git' in dirnames:
        # don't go into any .git directories.
        dirnames.remove('.git')
