mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	This is partly a cleanup of the code. It also is preparation for getting the variables from the source (cross-platform) rather than from the symbols. The change only touches the tool (and its tests).
		
			
				
	
	
		
			179 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import re
 | 
						|
 | 
						|
from ..common.info import UNKNOWN, ID
 | 
						|
 | 
						|
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 (varid, decl) 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 (varid, decl) for each variable 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 ID(filename, _funcname, name), decl
 | 
						|
            continue
 | 
						|
        name, decl = info
 | 
						|
 | 
						|
        if name is None:
 | 
						|
            continue
 | 
						|
        yield ID(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 varid, decl in _iter_variables(filename,
 | 
						|
                                    srccache=srccache,
 | 
						|
                                    parse_variable=parse_variable,
 | 
						|
                                    ):
 | 
						|
        if _match_varid(varid, name, funcname, ignored):
 | 
						|
            return varid, decl
 | 
						|
    else:
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
def find_variables(varids, filenames=None, *,
 | 
						|
                   srccache=_NOT_SET,
 | 
						|
                   parse_variable=None,
 | 
						|
                   _find_symbol=find_variable,
 | 
						|
                   ):
 | 
						|
    """Yield (varid, decl) 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 varid, UNKNOWN
 | 
						|
                continue
 | 
						|
            srcfiles = filenames
 | 
						|
        for filename in srcfiles:
 | 
						|
            varid, decl = _find_varid(filename, varid.funcname, varid.name,
 | 
						|
                                      ignored=used,
 | 
						|
                                      srccache=srccache,
 | 
						|
                                      parse_variable=parse_variable,
 | 
						|
                                      )
 | 
						|
            if varid:
 | 
						|
                yield varid, decl
 | 
						|
                used.add(varid)
 | 
						|
                break
 | 
						|
        else:
 | 
						|
            yield varid, UNKNOWN
 |