1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# author: Ole Schuett
5
6import re, sys
7from os import path
8from os.path import dirname, basename, normpath
9import os
10
11# pre-compiled regular expressions
12re_module  = re.compile(r"(?:^|\n)\s*module\s+(\w+)\s.*\n\s*end\s*module",re.DOTALL)
13re_useonly = re.compile(r"\n\s*use\s+(\w+)\s*,\s*only\s*:(.*)(?=\n)")
14re_use     = re.compile(r"\n\s*use\s+(\w+)\s*(?=\n)")
15re_pub     = re.compile(r"\n\s*public\s*::\s*(.*)(?=\n)")
16
17
18#=============================================================================
19def main():
20    src_dir = "../src"
21    parsed_files = {}
22    for root, _, files in os.walk(src_dir):
23        if "preprettify" in root:
24            continue
25        for fn in files:
26            if not fn.endswith(".F"):
27                continue
28            absfn = path.join(root, fn)
29
30            parsed_files[absfn] =  parse_file(absfn)
31
32    all_used_symboles = set()
33    for p in parsed_files.values():
34        for m, syms in p['use']:
35            for s in syms:
36                all_used_symboles.add(m+"@"+s)
37
38    n = 0
39    for fn, p in parsed_files.items():
40        if(len(p['mod']) != 1):
41            continue
42        m = p['mod'][0]
43        if(m+"@*" in all_used_symboles):
44            continue
45        unused = []
46        for s in p['pub']:
47            if(m+"@"+s not in all_used_symboles):
48                unused.append(s)
49                n += 1
50        if(len(unused) > 0):
51            #print("%s USElessly declares PUBLIC: "%fn+ ", ".join(unused)+"\n")
52            clean_publics(fn, unused)
53
54    #print("Found %d unUSEd PUBLIC symbols."%n)
55
56
57#=============================================================================
58def clean_publics(fn, unused):
59    content = open(fn).read()
60    new_content = ""
61    active = False
62    protected = False
63    new_public = []
64    for line in content.split("\n"):
65        if(line.strip().startswith("!API")):
66            protected = True
67        if(re.match(r"\s*public\s*::.*", line, re.IGNORECASE)):
68            if(protected):
69                protected = False
70            else:
71                active = True
72
73        if(not active):
74            new_content += line + "\n"
75            continue
76
77        prefix, symbols, comment = re.match("^(.*::)?([^!]*)(!.*)?$", line).groups()
78        old_symbols = symbols.strip(" &,").split(",")
79        new_symbols = []
80        for s in old_symbols:
81            s = s.strip()
82            if(s.lower() not in unused):
83                new_symbols.append(s)
84
85        if(len(new_symbols) > 0):
86            new_public.append( (", ".join(new_symbols), comment) )
87
88        without_comment = re.sub("!.*", "", line).strip()
89        if(len(without_comment) > 0 and without_comment[-1] != "&"):
90
91            #flush new_public
92            for i, entry in enumerate(new_public):
93                if(i==0):
94                    new_content += "  PUBLIC :: "
95                else:
96                    new_content += "            "
97                new_content += entry[0]
98                if(i < len(new_public)-1):
99                    new_content += ",&"
100                if(entry[1]):
101                    new_content += "  " + entry[1]
102                new_content += "\n"
103
104            active = False
105            new_public = []
106
107    new_content = new_content[:-1] # remove last line break
108
109    if(new_content != content):
110        print("Fixed: ", fn)
111        f = open(fn, "w")
112        f.write(new_content)
113
114
115#=============================================================================
116def parse_file(fn):
117    # print("parsing "+fn)
118    content = open(fn).read()
119    # re.IGNORECASE is horribly expensive. Converting to lower-case upfront
120    content = content.lower()
121    content = re.sub("!.*\n", "\n", content)
122    content = re.sub("&\s*\n", "", content)
123    content = re.sub("&", " ", content)
124
125    mods = re_module.findall(content)
126
127    uses = []
128    matches = re_use.findall(content)
129    for m in matches:
130        uses.append((m.strip(), ("*",)))
131        if(m.strip() not in ("iso_c_binding", "f77_blas", )):
132            print("missing ONLY-clause: ", fn, m)
133
134    matches = re_useonly.findall(content)
135    for m in matches:
136        syms = [p.split("=>")[-1].strip() for p in m[1].split(",")]
137        uses.append((m[0].strip(), syms))
138
139    publics = []
140    matches = re_pub.findall(content)
141    for m in matches:
142        publics += [p.strip() for p in m.split(",")]
143
144    return({"mod":mods, "use":uses, "pub":publics})
145
146#===============================================================================
147main()
148