aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py
diff options
context:
space:
mode:
Diffstat (limited to 'sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py')
-rw-r--r--sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py279
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))