#!/usr/bin/python
# -*- coding: utf8 -*-

# © 2010 Rafaël Carré <rafael.carre@gmail>

import sys
import os
import subprocess
import re


# remove static inlines

def find_func_end(s, start):
    n = 0
    i = start
    while i < len(s):
        if s[i] == '{':
            n += 1
        elif s[i] == '}':
            n -= 1
            if n == 0:
                break
        i += 1
    return i + 2 # FIXME why +2 ??

# find first '{', or ';' if it's a declaration
def func_start(s, start):
    i = start
    while i < len(s):
        if s[i] == '{' or s[i] == ';':
            return i
        i += 1

def func_find_name(s):
    search = re.search("([^\s\*])*[^\(]\([^\(]", s)
    end = search.end() - 2
    while s[end - 1] == ' ':
        end -= 1
    return s[search.start():end]

def find_inlines(s):
    funcs = []
    decls = []

    inline_exp = re.compile('static[\s]+inline[\s]+')
    for match in inline_exp.finditer(s):
        start = match.start()
        decl_end = func_start(s, start)
        if s[decl_end] == ';':
            decls += [ (start, decl_end) ]
        else:
            name = func_find_name(s[start:decl_end])
            func_end = find_func_end(s, decl_end)
            funcs += [ (name, start, func_end) ]

    return (funcs, decls)



def remove(funcs, start, end):
    len = end + 1 - start
    newfuncs = []
    for (name, fstart, fend) in funcs:
        if fstart > start:
            fstart -= len
            fend   -= len
        newfuncs += [(name, fstart, fend)]
    return newfuncs


def remove_static_inline(c):
    c = re.sub('( *\n *)+', ' ', c) # remove (some) whitespace

    (funcs, decls) = find_inlines(c)
    for (start,end) in decls:
        funcs = remove(funcs, start, end)
        c = c[:start] + c[end:]

    idx = 0
    while idx < len(funcs):
        (name, s, e) = funcs[idx]
        #print( name + ' -> ' + c[s:e] + ' <-')
        n = len(re.findall(name + '\s*\(', c))
        if n == 1: # function is not used
            funcs = remove(funcs, s, e)
            c = c[:s] + c[e:]
        idx += 1

    return c

def run_gcc(args):
    os.execv(args[0], args) # run real gcc


def debug(str):
    sys.stderr.write(str + '\n')
    return



# extract file names from gcc command line
def filenames(args):
    files = []
    option = False
    options_args = [ '-include', '-x', '-o' ]
    stop_args = False # all items after -- are files
    for arg in args:
        if not stop_args: 
            if option:
                option = False
                continue
            if arg == '--':
                stop_args = True
                continue
            for opt in options_args:
                if arg == opt:
                    option = True
        if stop_args or arg[0] != '-':
            files.append(arg)
    return files



def only_c_files(files):
    for f in files:
        if f[-2:] != '.c':
            return False
    return True



global dbg_output_file
dbg_output_file = ''



def has_asm(args):
    cpp_args = args + ['-E'] # run preprocessor

    idx = cpp_args.index('-o') # remove output file: use stdout
    global dbg_output_file
    dbg_output_file = str(cpp_args[idx+1])
    del cpp_args[idx:idx+2]

    # run cpp
    cpp_out = subprocess.Popen(cpp_args, stdout=subprocess.PIPE).communicate()[0]

    if re.search('asm', cpp_out) == None:
        return False

    # try harder
    if re.search('asm', remove_static_inline(cpp_out)) == None:
        return False

    return True


##### main


args=sys.argv[1:]   # remove script path

try:
    args.index('-E') # are we just running preprocessor?
    run_gcc(args) # XXX : add mthumb for cpp ?
except:
    try:
        args.index('-MM') # are we making deps?
        run_gcc(args)
    except:
        try:
            args.index('-v') # just want the version ?
            run_gcc(args)
        except:
            pass



args.append('-mthumb-interwork')
if not only_c_files(filenames(args[1:])):
    run_gcc(args)

if has_asm(args + ['-mthumb']):
    debug("-> SKIPPING " + re.split('/',dbg_output_file)[-1])
else:
    args.append('-mthumb')

run_gcc(args)
