import re from c_analyzer_common.info import UNKNOWN from .info import Variable from .preprocessor import _iter_clean_lines _NOT_SET = object() def get_srclines(filename, *, cache=None, _open=open, _iter_lines=_iter_clean_lines, ): """Return the file's lines as a list. Each line will have trailing whitespace removed (including newline). If a cache is given the it is used. """ if cache is not None: try: return cache[filename] except KeyError: pass with _open(filename) as srcfile: srclines = [line for _, line in _iter_lines(srcfile) if not line.startswith('#')] for i, line in enumerate(srclines): srclines[i] = line.rstrip() if cache is not None: cache[filename] = srclines return srclines def parse_variable_declaration(srcline): """Return (name, decl) for the given declaration line.""" # XXX possible false negatives... decl, sep, _ = srcline.partition('=') if not sep: if not srcline.endswith(';'): return None, None decl = decl.strip(';') decl = decl.strip() m = re.match(r'.*\b(\w+)\s*(?:\[[^\]]*\])?$', decl) if not m: return None, None name = m.group(1) return name, decl def parse_variable(srcline, funcname=None): """Return a Variable for the variable declared on the line (or None).""" line = srcline.strip() # XXX Handle more than just static variables. if line.startswith('static '): if '(' in line and '[' not in line: # a function return None, None return parse_variable_declaration(line) else: return None, None def iter_variables(filename, *, srccache=None, parse_variable=None, _get_srclines=get_srclines, _default_parse_variable=parse_variable, ): """Yield a Variable for each in the given source file.""" if parse_variable is None: parse_variable = _default_parse_variable indent = '' prev = '' funcname = None for line in _get_srclines(filename, cache=srccache): # remember current funcname if funcname: if line == indent + '}': funcname = None continue else: if '(' in prev and line == indent + '{': if not prev.startswith('__attribute__'): funcname = prev.split('(')[0].split()[-1] prev = '' continue indent = line[:-len(line.lstrip())] prev = line info = parse_variable(line, funcname) if isinstance(info, list): for name, _funcname, decl in info: yield Variable.from_parts(filename, _funcname, name, decl) continue name, decl = info if name is None: continue yield Variable.from_parts(filename, funcname, name, decl) def _match_varid(variable, name, funcname, ignored=None): if ignored and variable in ignored: return False if variable.name != name: return False if funcname == UNKNOWN: if not variable.funcname: return False elif variable.funcname != funcname: return False return True def find_variable(filename, funcname, name, *, ignored=None, srccache=None, # {filename: lines} parse_variable=None, _iter_variables=iter_variables, ): """Return the matching variable. Return None if the variable is not found. """ for variable in _iter_variables(filename, srccache=srccache, parse_variable=parse_variable, ): if _match_varid(variable, name, funcname, ignored): return variable else: return None def find_variables(varids, filenames=None, *, srccache=_NOT_SET, parse_variable=None, _find_symbol=find_variable, ): """Yield a Variable for each ID. If the variable is not found then its decl will be UNKNOWN. That way there will be one resulting Variable per given ID. """ if srccache is _NOT_SET: srccache = {} used = set() for varid in varids: if varid.filename and varid.filename != UNKNOWN: srcfiles = [varid.filename] else: if not filenames: yield Variable(varid, UNKNOWN, UNKNOWN) continue srcfiles = filenames for filename in srcfiles: found = _find_varid(filename, varid.funcname, varid.name, ignored=used, srccache=srccache, parse_variable=parse_variable, ) if found: yield found used.add(found) break else: yield Variable(varid, UNKNOWN, UNKNOWN)