1import argparse 2import re 3import sys 4 5from c_analyzer.common import show 6from c_analyzer.common.info import UNKNOWN 7 8from . import SOURCE_DIRS 9from .find import supported_vars 10from .known import ( 11 from_file as known_from_file, 12 DATA_FILE as KNOWN_FILE, 13 ) 14from .supported import IGNORED_FILE 15 16 17def _check_results(unknown, knownvars, used): 18 def _match_unused_global(variable): 19 found = [] 20 for varid in knownvars: 21 if varid in used: 22 continue 23 if varid.funcname is not None: 24 continue 25 if varid.name != variable.name: 26 continue 27 if variable.filename and variable.filename != UNKNOWN: 28 if variable.filename == varid.filename: 29 found.append(varid) 30 else: 31 found.append(varid) 32 return found 33 34 badknown = set() 35 for variable in sorted(unknown): 36 msg = None 37 if variable.funcname != UNKNOWN: 38 msg = f'could not find global symbol {variable.id}' 39 elif m := _match_unused_global(variable): 40 assert isinstance(m, list) 41 badknown.update(m) 42 elif variable.name in ('completed', 'id'): # XXX Figure out where these variables are. 43 unknown.remove(variable) 44 else: 45 msg = f'could not find local symbol {variable.id}' 46 if msg: 47 #raise Exception(msg) 48 print(msg) 49 if badknown: 50 print('---') 51 print(f'{len(badknown)} globals in known.tsv, but may actually be local:') 52 for varid in sorted(badknown): 53 print(f'{varid.filename:30} {varid.name}') 54 unused = sorted(varid 55 for varid in set(knownvars) - used 56 if varid.name != 'id') # XXX Figure out where these variables are. 57 if unused: 58 print('---') 59 print(f'did not use {len(unused)} known vars:') 60 for varid in unused: 61 print(f'{varid.filename:30} {varid.funcname or "-":20} {varid.name}') 62 raise Exception('not all known symbols used') 63 if unknown: 64 print('---') 65 raise Exception('could not find all symbols') 66 67 68# XXX Move this check to its own command. 69def cmd_check_cache(cmd, *, 70 known=KNOWN_FILE, 71 ignored=IGNORED_FILE, 72 _known_from_file=known_from_file, 73 _find=supported_vars, 74 ): 75 known = _known_from_file(known) 76 77 used = set() 78 unknown = set() 79 for var, supported in _find(known=known, ignored=ignored): 80 if supported is None: 81 unknown.add(var) 82 continue 83 used.add(var.id) 84 _check_results(unknown, known['variables'], used) 85 86 87def cmd_check(cmd, *, 88 known=KNOWN_FILE, 89 ignored=IGNORED_FILE, 90 _find=supported_vars, 91 _show=show.basic, 92 _print=print, 93 ): 94 """ 95 Fail if there are unsupported globals variables. 96 97 In the failure case, the list of unsupported variables 98 will be printed out. 99 """ 100 unsupported = [] 101 for var, supported in _find(known=known, ignored=ignored): 102 if not supported: 103 unsupported.append(var) 104 105 if not unsupported: 106 #_print('okay') 107 return 108 109 _print('ERROR: found unsupported global variables') 110 _print() 111 _show(sorted(unsupported)) 112 _print(f' ({len(unsupported)} total)') 113 sys.exit(1) 114 115 116def cmd_show(cmd, *, 117 known=KNOWN_FILE, 118 ignored=IGNORED_FILE, 119 skip_objects=False, 120 _find=supported_vars, 121 _show=show.basic, 122 _print=print, 123 ): 124 """ 125 Print out the list of found global variables. 126 127 The variables will be distinguished as "supported" or "unsupported". 128 """ 129 allsupported = [] 130 allunsupported = [] 131 for found, supported in _find(known=known, 132 ignored=ignored, 133 skip_objects=skip_objects, 134 ): 135 if supported is None: 136 continue 137 (allsupported if supported else allunsupported 138 ).append(found) 139 140 _print('supported:') 141 _print('----------') 142 _show(sorted(allsupported)) 143 _print(f' ({len(allsupported)} total)') 144 _print() 145 _print('unsupported:') 146 _print('------------') 147 _show(sorted(allunsupported)) 148 _print(f' ({len(allunsupported)} total)') 149 150 151############################# 152# the script 153 154COMMANDS = { 155 'check': cmd_check, 156 'show': cmd_show, 157 } 158 159PROG = sys.argv[0] 160PROG = 'c-globals.py' 161 162 163def parse_args(prog=PROG, argv=sys.argv[1:], *, _fail=None): 164 common = argparse.ArgumentParser(add_help=False) 165 common.add_argument('--ignored', metavar='FILE', 166 default=IGNORED_FILE, 167 help='path to file that lists ignored vars') 168 common.add_argument('--known', metavar='FILE', 169 default=KNOWN_FILE, 170 help='path to file that lists known types') 171 #common.add_argument('dirs', metavar='DIR', nargs='*', 172 # default=SOURCE_DIRS, 173 # help='a directory to check') 174 175 parser = argparse.ArgumentParser( 176 prog=prog, 177 ) 178 subs = parser.add_subparsers(dest='cmd') 179 180 check = subs.add_parser('check', parents=[common]) 181 182 show = subs.add_parser('show', parents=[common]) 183 show.add_argument('--skip-objects', action='store_true') 184 185 if _fail is None: 186 def _fail(msg): 187 parser.error(msg) 188 189 # Now parse the args. 190 args = parser.parse_args(argv) 191 ns = vars(args) 192 193 cmd = ns.pop('cmd') 194 if not cmd: 195 _fail('missing command') 196 197 return cmd, ns 198 199 200def main(cmd, cmdkwargs=None, *, _COMMANDS=COMMANDS): 201 try: 202 cmdfunc = _COMMANDS[cmd] 203 except KeyError: 204 raise ValueError( 205 f'unsupported cmd {cmd!r}' if cmd else 'missing cmd') 206 207 cmdfunc(cmd, **cmdkwargs or {}) 208 209 210if __name__ == '__main__': 211 cmd, cmdkwargs = parse_args() 212 main(cmd, cmdkwargs) 213