#!/usr/bin/env python3
#-*- coding: utf-8 -*-
##########################################################################
#                                                                        #
#  This file is part of Frama-C.                                         #
#                                                                        #
#  Copyright (C) 2007-2020                                               #
#    CEA (Commissariat à l'énergie atomique et aux énergies              #
#         alternatives)                                                  #
#                                                                        #
#  you can redistribute it and/or modify it under the terms of the GNU   #
#  Lesser General Public License as published by the Free Software       #
#  Foundation, version 2.1.                                              #
#                                                                        #
#  It is distributed in the hope that it will be useful,                 #
#  but WITHOUT ANY WARRANTY; without even the implied warranty of        #
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
#  GNU Lesser General Public License for more details.                   #
#                                                                        #
#  See the GNU Lesser General Public License version 2.1                 #
#  for more details (enclosed in the file licenses/LGPLv2.1).            #
#                                                                        #
##########################################################################

# This script finds files containing likely declarations and definitions
# for a given function name, via heuristic syntactic matching.

import argparse
import sys
import os
import re
import glob
import function_finder

MIN_PYTHON = (3, 5) # for glob(recursive)
if sys.version_info < MIN_PYTHON:
    sys.exit("Python %s.%s or later is required.\n" % MIN_PYTHON)

parser = argparse.ArgumentParser(description="""
Looks for likely declarations/definitions of a function
in files with extensions '.c', '.h' and '.i'.
If any directories are specified, looks inside them,
otherwise looks inside PWD and /usr/include.
Subdirectories are always considered recursively.""")

parser.add_argument('--directory', '-C', metavar='DIR', default=".", nargs=1,
                    help='print paths relative to directory DIR (default: .)')
parser.add_argument('funcname', help='function name to search')
parser.add_argument('dir', nargs='*', help='directories where to search (if empty: PWD /usr/include)')
args = vars(parser.parse_args())

reldir = args["directory"][0]
fname = args["funcname"]
dirs = args["dir"]

if re.match('[a-zA-Z_][a-zA-Z0-9_]*$', fname) == None:
    print("error: function name contains invalid characters: %s" % fname)
    print("       (only letters/digits/underscore allowed)")
    sys.exit(1)

dirs = set(dirs)
if not dirs:
    pwd = os.getcwd()
    dirs = [pwd, "/usr/include"]
else:
    dirs = set(sys.argv[2:])

files = []
for d in dirs:
    files += glob.glob(d + "/**/*.[ich]", recursive=True)

print("Looking for '%s' inside %d file(s)..." % (fname, len(files)))

possible_declarators = []
possible_definers = []
re_fun = function_finder.prepare(fname)
for f in files:
    found = function_finder.find(re_fun, f)
    if found:
        if found == 1:
            possible_declarators.append(f)
        else:
            possible_definers.append(f)

def relative_path_to(start):
    return lambda p: os.path.relpath(p, start=start)

if possible_declarators == [] and possible_definers == []:
    print("No declaration/definition found for function '%s'" % fname)
else:
    if reldir != ".":
        reldir_msg = f" (relative to '{reldir}')"
    else:
        reldir_msg = ""
    relative_path = relative_path_to(reldir)
    if possible_declarators != []:
        print(f"Possible declarations for function '{fname}' in the following file(s){reldir_msg}:")
        print("  " + "\n  ".join([os.path.relpath(path, start=reldir) for path in possible_declarators]))
    if possible_definers != []:
        print(f"Possible definitions for function '{fname}' in the following file(s){reldir_msg}:")
        print("  " + "\n  ".join([os.path.relpath(path, start=reldir) for path in possible_definers]))
