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