diff options
Diffstat (limited to 'sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py')
-rw-r--r-- | sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py | 279 |
1 files changed, 211 insertions, 68 deletions
diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py index 72ca35757..204f37384 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py @@ -46,7 +46,9 @@ import types import keyword import functools from shibokensupport.signature.mapping import (type_map, update_mapping, - namespace, typing, _NotCalled) + namespace, typing, _NotCalled, ResultVariable, ArrayLikeVariable) +from shibokensupport.signature.lib.tool import (SimpleNamespace, + build_brace_pattern) _DEBUG = False LIST_KEYWORDS = False @@ -78,6 +80,23 @@ def dprint(*args, **kw): pprint.pprint(arg) sys.stdout.flush() + +_cache = {} + +def _parse_arglist(argstr): + # The following is a split re. The string is broken into pieces which are + # between the recognized strings. Because the re has groups, both the + # strings and the separators are returned, where the strings are not + # interesting at all: They are just the commata. + key = "_parse_arglist" + if key not in _cache: + regex = build_brace_pattern(level=3, separators=",") + _cache[key] = re.compile(regex, flags=re.VERBOSE) + split = _cache[key].split + # Note: this list is interspersed with "," and surrounded by "" + return [x.strip() for x in split(argstr) if x.strip() not in ("", ",")] + + def _parse_line(line): line_re = r""" ((?P<multi> ([0-9]+)) : )? # the optional multi-index @@ -86,38 +105,9 @@ def _parse_line(line): ( -> (?P<returntype> .*) )? # the optional return type $ """ - ret = re.match(line_re, line, re.VERBOSE).groupdict() - arglist = ret["arglist"] - # The following is a split re. The string is broken into pieces which are - # between the recognized strings. Because the re has groups, both the - # strings and the delimiters are returned, where the strings are not - # interesting at all: They are just the commata. - # Note that it is necessary to put the characters with special handling in - # the first group (comma, brace, angle bracket). - # Then they are not recognized there, and we can handle them differently - # in the following expressions. - arglist = list(x.strip() for x in re.split(r""" - ( - (?: # inner group is not capturing - [^,()<>] # no commas or braces or angle brackets - | - \( - (?: - [^()]* # or one brace pair - | - \( - [^()]* # or doubls nested pair - \) - )* - \) - | - < # or one angle bracket pair - [^<>]* - > - )+ # longest possible span - ) # this list is interspersed with "," and surrounded by "" - """, arglist, flags=re.VERBOSE) - if x.strip() not in ("", ",")) + ret = SimpleNamespace(**re.match(line_re, line, re.VERBOSE).groupdict()) + argstr = ret.arglist + arglist = _parse_arglist(argstr) args = [] for arg in arglist: name, ann = arg.split(":") @@ -131,15 +121,16 @@ def _parse_line(line): else: tup = name, ann args.append(tup) - ret["arglist"] = args - multi = ret["multi"] + ret.arglist = args + multi = ret.multi if multi is not None: - ret["multi"] = int(multi) - funcname = ret["funcname"] + ret.multi = int(multi) + funcname = ret.funcname parts = funcname.split(".") if parts[-1] in keyword.kwlist: - ret["funcname"] = funcname + "_" - return ret + ret.funcname = funcname + "_" + return vars(ret) + def make_good_value(thing, valtype): try: @@ -153,6 +144,7 @@ def make_good_value(thing, valtype): except Exception: pass + def try_to_guess(thing, valtype): if "." not in thing and "(" not in thing: text = "{}.{}".format(valtype, thing) @@ -172,9 +164,15 @@ def try_to_guess(thing, valtype): return ret return None + def _resolve_value(thing, valtype, line): if thing in ("0", "None") and valtype: - thing = "zero({})".format(valtype) + if valtype.startswith("PySide2."): + return None + map = type_map[valtype] + # typing.Any: '_SpecialForm' object has no attribute '__name__' + name = map.__name__ if hasattr(map, "__name__") else str(map) + thing = "zero({})".format(name) if thing in type_map: return type_map[thing] res = make_good_value(thing, valtype) @@ -192,40 +190,126 @@ def _resolve_value(thing, valtype, line): """.format(thing, line), RuntimeWarning) return thing + def _resolve_arraytype(thing, line): - thing = thing[:-2] - if thing.endswith("[]"): + search = re.search(r"\[(\d*)\]$", thing) + thing = thing[:search.start()] + if thing.endswith("]"): thing = _resolve_arraytype(thing, line) - # this mapping is in shiboken - thing = "QList[" + thing + "]" + if search.group(1): + # concrete array, use a tuple + nelem = int(search.group(1)) + thing = ", ".join([thing] * nelem) + thing = "Tuple[" + thing + "]" + else: + thing = "QList[" + thing + "]" return thing + def to_string(thing): if isinstance(thing, str): return thing if hasattr(thing, "__name__"): - dot = "." in str(type(thing)) + dot = "." in str(thing) return thing.__module__ + "." + thing.__name__ if dot else thing.__name__ # Note: This captures things from the typing module: return str(thing) -def _resolve_type(thing, line): - if thing.endswith("[]"): - thing = _resolve_arraytype(thing, line) + +matrix_pattern = "PySide2.QtGui.QGenericMatrix" + +def handle_matrix(arg): + n, m, typstr = tuple(map(lambda x:x.strip(), arg.split(","))) + assert typstr == "float" + result = "PySide2.QtGui.QMatrix{n}x{m}".format(**locals()) + return eval(result, namespace) + + +debugging_aid = """ +from inspect import currentframe + +def lno(level): + lineno = currentframe().f_back.f_lineno + spaces = level * " " + return "{lineno}{spaces}".format(**locals()) +""" + + +def _resolve_type(thing, line, level, var_handler): + # Capture total replacements, first. Happens in + # "PySide2.QtCore.QCborStreamReader.StringResult[PySide2.QtCore.QByteArray]" + if thing in type_map: + return type_map[thing] + # Now the nested structures are handled. if "[" in thing: + # handle primitive arrays + if re.search(r"\[\d*\]$", thing): + thing = _resolve_arraytype(thing, line) # Handle a container return type. (see PYSIDE-921 in cppgenerator.cpp) contr, thing = re.match(r"(.*?)\[(.*?)\]$", thing).groups() - contr = to_string(_resolve_type(contr, line)) - thing = to_string(_resolve_type(thing, line)) + # Special case: Handle the generic matrices. + if contr == matrix_pattern: + return handle_matrix(thing) + contr = var_handler(_resolve_type(contr, line, level+1, var_handler)) + if isinstance(contr, _NotCalled): + raise SystemError("Container types must exist:", repr(contr)) + contr = to_string(contr) + pieces = [] + for part in _parse_arglist(thing): + part = var_handler(_resolve_type(part, line, level+1, var_handler)) + if isinstance(part, _NotCalled): + # fix the tag (i.e. "Missing") by repr + part = repr(part) + pieces.append(to_string(part)) + thing = ", ".join(pieces) result = "{contr}[{thing}]".format(**locals()) - if not isinstance(thing, _NotCalled): - result = eval(result, namespace) - return result + return eval(result, namespace) return _resolve_value(thing, None, line) + +def _handle_generic(obj, repl): + """ + Assign repl if obj is an ArrayLikeVariable + + This is a neat trick. Example: + + obj repl result + ---------------------- -------- --------- + ArrayLikeVariable List List + ArrayLikeVariable(str) List List[str] + ArrayLikeVariable Sequence Sequence + ArrayLikeVariable(str) Sequence Sequence[str] + """ + if isinstance(obj, ArrayLikeVariable): + return repl[obj.type] + if isinstance(obj, type) and issubclass(obj, ArrayLikeVariable): + # was "if obj is ArrayLikeVariable" + return repl + return obj + + +def handle_argvar(obj): + """ + Decide how array-like variables are resolved in arguments + + Currently, the best approximation is types.Sequence. + We want to change that to types.Iterable in the near future. + """ + return _handle_generic(obj, typing.Sequence) + + +def handle_retvar(obj): + """ + Decide how array-like variables are resolved in results + + This will probably stay typing.List forever. + """ + return _handle_generic(obj, typing.List) + + def calculate_props(line): - res = _parse_line(line) - arglist = res["arglist"] + parsed = SimpleNamespace(**_parse_line(line.strip())) + arglist = parsed.arglist annotations = {} _defaults = [] for idx, tup in enumerate(arglist): @@ -236,27 +320,85 @@ def calculate_props(line): ann = 'NULL' # maps to None tup = name, ann arglist[idx] = tup - annotations[name] = _resolve_type(ann, line) + annotations[name] = _resolve_type(ann, line, 0, handle_argvar) if len(tup) == 3: default = _resolve_value(tup[2], ann, line) _defaults.append(default) defaults = tuple(_defaults) - returntype = res["returntype"] + returntype = parsed.returntype if returntype is not None: - annotations["return"] = _resolve_type(returntype, line) - props = {} - props["defaults"] = defaults - props["kwdefaults"] = {} - props["annotations"] = annotations - props["varnames"] = varnames = tuple(tup[0] for tup in arglist) - funcname = res["funcname"] - props["fullname"] = funcname + annotations["return"] = _resolve_type(returntype, line, 0, handle_retvar) + props = SimpleNamespace() + props.defaults = defaults + props.kwdefaults = {} + props.annotations = annotations + props.varnames = varnames = tuple(tup[0] for tup in arglist) + funcname = parsed.funcname + props.fullname = funcname shortname = funcname[funcname.rindex(".")+1:] - props["name"] = shortname - props["multi"] = res["multi"] - return props + props.name = shortname + props.multi = parsed.multi + fix_variables(props, line) + return vars(props) + + +def fix_variables(props, line): + annos = props.annotations + if not any(isinstance(ann, (ResultVariable, ArrayLikeVariable)) + for ann in annos.values()): + return + retvar = annos.get("return", None) + if retvar and isinstance(retvar, (ResultVariable, ArrayLikeVariable)): + # Special case: a ResultVariable which is the result will always be an array! + annos["return"] = retvar = typing.List[retvar.type] + fullname = props.fullname + varnames = list(props.varnames) + defaults = list(props.defaults) + diff = len(varnames) - len(defaults) + + safe_annos = annos.copy() + retvars = [retvar] if retvar else [] + deletions = [] + for idx, name in enumerate(varnames): + ann = safe_annos[name] + if isinstance(ann, ArrayLikeVariable): + ann = typing.Sequence[ann.type] + annos[name] = ann + if not isinstance(ann, ResultVariable): + continue + # We move the variable to the end and remove it. + retvars.append(ann.type) + deletions.append(idx) + del annos[name] + for idx in reversed(deletions): + # varnames: 0 1 2 3 4 5 6 7 + # defaults: 0 1 2 3 4 + # diff: 3 + del varnames[idx] + if idx >= diff: + del defaults[idx - diff] + else: + diff -= 1 + if retvars: + rvs = [] + retvars = list(handle_retvar(rv) if isinstance(rv, ArrayLikeVariable) else rv + for rv in retvars) + if len(retvars) == 1: + returntype = retvars[0] + else: + typestr = "typing.Tuple[{}]".format(", ".join(map(to_string, retvars))) + returntype = eval(typestr, namespace) + props.annotations["return"] = returntype + props.varnames = tuple(varnames) + props.defaults = tuple(defaults) + def fixup_multilines(lines): + """ + Multilines can collapse when certain distinctions between C++ types + vanish after mapping to Python. + This function fixes this by re-computing multiline-ness. + """ res = [] multi_lines = [] for line in lines: @@ -280,6 +422,7 @@ def fixup_multilines(lines): res.append(line) return res + def pyside_type_init(type_key, sig_strings): dprint() dprint("Initialization of type key '{}'".format(type_key)) |