import os.path from c_analyzer_common import files from c_analyzer_common.info import UNKNOWN from c_parser import declarations, info from .info import Symbol from .source import _find_symbol # XXX need tests: # * look_up_known_symbol() # * symbol_from_source() # * get_resolver() # * symbols_to_variables() def look_up_known_symbol(symbol, knownvars, *, match_files=(lambda f1, f2: f1 == f2), ): """Return the known variable matching the given symbol. "knownvars" is a mapping of common.ID to parser.Variable. "match_files" is used to verify if two filenames point to the same file. """ if not knownvars: return None if symbol.funcname == UNKNOWN: if not symbol.filename or symbol.filename == UNKNOWN: for varid in knownvars: if not varid.funcname: continue if varid.name == symbol.name: return knownvars[varid] else: return None else: for varid in knownvars: if not varid.funcname: continue if not match_files(varid.filename, symbol.filename): continue if varid.name == symbol.name: return knownvars[varid] else: return None elif not symbol.filename or symbol.filename == UNKNOWN: raise NotImplementedError else: return knownvars.get(symbol.id) def find_in_source(symbol, dirnames, *, _perfilecache={}, _find_symbol=_find_symbol, _iter_files=files.iter_files_by_suffix, ): """Return the Variable matching the given Symbol. If there is no match then return None. """ if symbol.filename and symbol.filename != UNKNOWN: filenames = [symbol.filename] else: filenames = _iter_files(dirnames, ('.c', '.h')) if symbol.funcname and symbol.funcname != UNKNOWN: raise NotImplementedError (filename, funcname, decl ) = _find_symbol(symbol.name, filenames, _perfilecache) if filename == UNKNOWN: return None return info.Variable.from_parts(filename, funcname, symbol.name, decl) def get_resolver(knownvars=None, dirnames=None, *, _look_up_known=look_up_known_symbol, _from_source=find_in_source, ): """Return a "resolver" func for the given known vars and dirnames. The func takes a single Symbol and returns a corresponding Variable. If the symbol was located then the variable will be valid, populated with the corresponding information. Otherwise None is returned. """ if knownvars: knownvars = dict(knownvars) # a copy def resolve_known(symbol): found = _look_up_known(symbol, knownvars) if found is None: return None elif symbol.funcname == UNKNOWN: knownvars.pop(found.id) elif not symbol.filename or symbol.filename == UNKNOWN: knownvars.pop(found.id) return found if dirnames: def resolve(symbol): found = resolve_known(symbol) if found is None: return None #return _from_source(symbol, dirnames) else: for dirname in dirnames: if not dirname.endswith(os.path.sep): dirname += os.path.sep if found.filename.startswith(dirname): break else: return None return found else: resolve = resolve_known elif dirnames: def resolve(symbol): return _from_source(symbol, dirnames) else: def resolve(symbol): return None return resolve def symbols_to_variables(symbols, *, resolve=(lambda s: look_up_known_symbol(s, None)), ): """Yield the variable the matches each given symbol. Use get_resolver() for a "resolve" func to use. """ for symbol in symbols: if isinstance(symbol, info.Variable): # XXX validate? yield symbol continue if symbol.kind != Symbol.KIND.VARIABLE: continue resolved = resolve(symbol) if resolved is None: #raise NotImplementedError(symbol) resolved = info.Variable( id=symbol.id, storage=UNKNOWN, vartype=UNKNOWN, ) yield resolved