• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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