예제 #1
0
def _VirtualFunctionCall(fname, f, pyname, abstract, postconvinit):
    """Generate virtual redirector call wrapper from AST.FuncDecl f."""
    name = f.name.cpp_name
    ret = astutils.FuncReturnType(f, true_cpp_type=True)
    arg = astutils.FuncParamStr(f, 'a', true_cpp_type=True)
    mod = ['']
    if f.cpp_const_method: mod.append('const')
    if f.cpp_noexcept: mod.append('noexcept')
    yield ''
    yield I + '%s %s%s%s override {' % (ret, fname, arg, ' '.join(mod))
    params = astutils.TupleStr(
        'std::move(a%i)' % i
        for i in range(len(f.params) + len(f.returns) - (ret != 'void')))
    yield I + I + 'SafeAttr impl(self(), "%s");' % f.name.native
    yield I + I + 'if (impl.get()) {'
    ret_st = 'return ' if ret != 'void' else ''
    yield I + I + I + '%s::clif::callback::Func<%s>(impl.get(), {%s})%s;' % (
        ret_st, ', '.join([ret] + list(astutils.Type(a) for a in f.params) +
                          list(astutils.FuncReturns(f))), ', '.join(
                              postconvinit(a.type) for a in f.params), params)
    yield I + I + '} else {'
    if abstract:
        # This is only called from C++. Since f has no info if it is pure virtual,
        # we can't always generate the call, so we always fail in an abstract class.
        yield I + I + I + (
            'Py_FatalError("@virtual method %s.%s has no Python '
            'implementation.");' % (pyname, f.name.native))
        # In Python 2 Py_FatalError is not marked __attribute__((__noreturn__)),
        # so to avoid -Wreturn-type warning add extra abort(). It does not hurt ;)
        yield I + I + I + 'abort();'
    else:
        yield I + I + I + ret_st + name + params + ';'
    yield I + I + '}'
    yield I + '}'
예제 #2
0
def FunctionCall(pyname,
                 wrapper,
                 doc,
                 catch,
                 call,
                 postcall_init,
                 typepostconversion,
                 func_ast,
                 lineno,
                 prepend_self=None):
    """Generate PyCFunction wrapper from AST.FuncDecl func_ast.

  Args:
    pyname: str - Python function name (may be special: ends with @)
    wrapper: str - generated function name
    doc: str - C++ sinature
    catch: bool - catch C++ exceptions
    call: str | [str] - C++ command(s) to call the wrapped function
      (without "(params);" part).
    postcall_init: str - C++ command; to (re)set ret0.
    typepostconversion: dict(pytype, index) to convert to pytype
    func_ast: AST.FuncDecl protobuf
    lineno: int - .clif line number where func_ast defined
    prepend_self: AST.Param - Use self as 1st parameter.

  Yields:
     Source code for wrapped function.

  Raises:
    ValueError: for non-supported default arguments
  """
    ctxmgr = pyname.endswith('@')
    if ctxmgr:
        ctxmgr = pyname
        assert ctxmgr in ('__enter__@',
                          '__exit__@'), ('Invalid context manager name ' +
                                         pyname)
        pyname = pyname.rstrip('@')
    nret = len(func_ast.returns)
    return_type = astutils.FuncReturnType(
        func_ast)  # Can't use cpp_exact_type.
    # return_type mangled to FQN and drop &, sadly it also drop const.
    void_return_type = 'void' == return_type
    # Has extra func parameters for output values.
    xouts = nret > (0 if void_return_type else 1)
    params = []  # C++ parameter names.
    nargs = len(func_ast.params)
    yield ''
    if func_ast.classmethod:
        yield '// @classmethod ' + doc
        arg0 = 'cls'  # Extra protection that generated code does not use 'self'.
    else:
        yield '// ' + doc
        arg0 = 'self'
    yield 'static PyObject* %s(PyObject* %s%s) {' % (
        wrapper, arg0, ', PyObject* args, PyObject* kw' if nargs else '')
    if prepend_self:
        yield I + _CreateInputParameter(pyname + ' line %d' % lineno,
                                        prepend_self, 'arg0', params)
        yield I + 'if (!Clif_PyObjAs(self, &arg0)) return nullptr;'
    minargs = sum(1 for p in func_ast.params if not p.default_value)
    if nargs:
        yield I + 'PyObject* a[%d]%s;' % (nargs,
                                          '' if minargs == nargs else '{}')
        yield I + 'char* names[] = {'
        for p in func_ast.params:
            yield I + I + I + 'C("%s"),' % p.name.native
        yield I + I + I + 'nullptr'
        yield I + '};'
        yield I + (
            'if (!PyArg_ParseTupleAndKeywords(args, kw, "%s:%s", names, %s)) '
            'return nullptr;' %
            ('O' * nargs if minargs == nargs else 'O' * minargs + '|' + 'O' *
             (nargs - minargs), pyname, ', '.join('&a[%d]' % i
                                                  for i in range(nargs))))
        if minargs < nargs and not xouts:
            yield I + 'int nargs;  // Find how many args actually passed in.'
            yield I + 'for (nargs = %d; nargs > %d; --nargs) {' % (nargs,
                                                                   minargs)
            yield I + I + 'if (a[nargs-1] != nullptr) break;'
            yield I + '}'
        # Convert input parameters from Python.
        for i, p in enumerate(func_ast.params):
            n = i + 1
            arg = 'arg%d' % n
            yield I + _CreateInputParameter(pyname + ' line %d' % lineno, p,
                                            arg, params)
            cvt = (
                'if (!Clif_PyObjAs(a[{i}], &{cvar}{postconv})) return ArgError'
                '("{func_name}", names[{i}], "{ctype}", a[{i}]);').format(
                    i=i,
                    cvar=arg,
                    func_name=pyname,
                    ctype=astutils.Type(p),
                    # Add post conversion parameter for std::function.
                    postconv='' if p.type.cpp_type else ', {%s}' % ', '.join(
                        postconv.Initializer(t.type, typepostconversion)
                        for t in p.type.callable.params))
            if i < minargs:
                # Non-default parameter.
                yield I + cvt
            else:
                if xouts:
                    _I = ''  # pylint: disable=invalid-name
                else:
                    _I = I  # pylint: disable=invalid-name
                    yield I + 'if (nargs > %d) {' % i
                # Check if we're passed kw args, skipping some default C++ args.
                # In this case we must substitute missed default args with default_value
                if (p.default_value ==
                        'default'  # Matcher could not find the default.
                        or 'inf' in p.default_value):  # W/A for b/29437257
                    if xouts:
                        raise ValueError(
                            "Can't supply the default for C++ function"
                            ' argument. Drop =default in def %s(%s).' %
                            (pyname, p.name.native))
                    if n < nargs:
                        yield I + I + (
                            'if (!a[{i}]) return DefaultArgMissedError('
                            '"{}", names[{i}]);'.format(pyname, i=i))
                    yield I + I + cvt
                elif (p.default_value and params[-1].startswith('&')
                      and p.type.cpp_raw_pointer):
                    # Special case for a pointer to an integral type param (like int*).
                    raise ValueError(
                        'A default for integral type pointer argument is '
                        ' not supported. Drop =default in def %s(%s).' %
                        (pyname, p.name.native))
                else:
                    # C-cast takes care of the case where |arg| is an enum value, while
                    # the matcher would return an integral literal. Using static_cast
                    # would be ideal, but its argument should be an expression, which a
                    # struct value like {1, 2, 3} is not.
                    yield _I + I + 'if (!a[%d]) %s = (%s)%s;' % (
                        i, arg, astutils.Type(p), p.default_value)
                    yield _I + I + 'else ' + cvt
                if not xouts:
                    yield I + '}'
    # Create input parameters for extra return values.
    for n, p in enumerate(func_ast.returns):
        if n or void_return_type:
            yield I + '%s ret%d{};' % (astutils.Type(p), n)
            params.append('&ret%d' % n)
    yield I + '// Call actual C++ method.'
    if isinstance(call, list):
        for s in call[:-1]:
            yield I + s
        call = call[-1]
    if not func_ast.py_keep_gil:
        if nargs:
            yield I + 'Py_INCREF(args);'
            yield I + 'Py_XINCREF(kw);'
        yield I + 'PyThreadState* _save;'
        yield I + 'Py_UNBLOCK_THREADS'
    optional_ret0 = False
    if (minargs < nargs or catch) and not void_return_type:
        if func_ast.returns[0].type.cpp_has_def_ctor:
            yield I + return_type + ' ret0;'
        else:
            # Using optional<> requires T be have T(x) and T::op=(x) available.
            # While we need only t=x, implementing it will be a pain we skip for now.
            yield I + '::gtl::optional<%s> ret0;' % return_type
            optional_ret0 = True
    if catch:
        for s in _GenExceptionTry():
            yield s
    if minargs < nargs and not xouts:
        if not void_return_type:
            call = 'ret0 = ' + call
        yield I + 'switch (nargs) {'
        for n in range(minargs, nargs + 1):
            yield I + 'case %d:' % n
            yield I + I + '%s; break;' % (call + astutils.TupleStr(params[:n]))
        yield I + '}'
    else:
        call += astutils.TupleStr(params)
        _I = I if catch else ''  # pylint: disable=invalid-name
        if void_return_type:
            yield _I + I + call + ';'
        elif catch:
            yield _I + I + 'ret0 = ' + call + ';'
        else:
            yield _I + I + return_type + ' ret0 = ' + call + ';'
    if catch:
        for s in _GenExceptionCatch():
            yield s
    if postcall_init:
        if void_return_type:
            yield I + postcall_init
        else:
            yield I + 'ret0' + postcall_init
    if not func_ast.py_keep_gil:
        yield I + 'Py_BLOCK_THREADS'
        if nargs:
            yield I + 'Py_DECREF(args);'
            yield I + 'Py_XDECREF(kw);'
    if catch:
        for s in _GenExceptionRaise():
            yield s
    if func_ast.postproc == '->self':
        func_ast.postproc = ''
        return_self = True
        assert nret == 0, '-> self must have no other output parameters'
    else:
        return_self = False
    # If ctxmgr, force return self on enter, None on exit.
    if nret > 1 or (func_ast.postproc or ctxmgr) and nret:
        yield I + '// Convert return values to Python.'
        yield I + 'PyObject* p, * result_tuple = PyTuple_New(%d);' % nret
        yield I + 'if (result_tuple == nullptr) return nullptr;'
        for i in range(nret):
            yield I + 'if ((p=Clif_PyObjFrom(std::move(ret%d), %s)) == nullptr) {' % (
                i,
                postconv.Initializer(func_ast.returns[i].type,
                                     typepostconversion))
            yield I + I + 'Py_DECREF(result_tuple);'
            yield I + I + 'return nullptr;'
            yield I + '}'
            yield I + 'PyTuple_SET_ITEM(result_tuple, %d, p);' % i
        if func_ast.postproc:
            yield I + 'PyObject* pyproc = ImportFQName("%s");' % func_ast.postproc
            yield I + 'if (pyproc == nullptr) {'
            yield I + I + 'Py_DECREF(result_tuple);'
            yield I + I + 'return nullptr;'
            yield I + '}'
            yield I + 'p = PyObject_CallObject(pyproc, result_tuple);'
            yield I + 'Py_DECREF(pyproc);'
            yield I + 'Py_CLEAR(result_tuple);'
            if ctxmgr:
                yield I + 'if (p == nullptr) return nullptr;'
                yield I + 'Py_DECREF(p);  // Not needed by the context manager.'
            else:
                yield I + 'result_tuple = p;'
        if ctxmgr == '__enter__@':
            yield I + 'Py_XDECREF(result_tuple);'
            yield I + 'Py_INCREF(self);'
            yield I + 'return self;'
        elif ctxmgr == '__exit__@':
            yield I + 'Py_XDECREF(result_tuple);'
            yield I + 'Py_RETURN_NONE;'
        else:
            yield I + 'return result_tuple;'
    elif nret:
        yield I + 'return Clif_PyObjFrom(std::move(ret0%s), %s);' % (
            ('.value()' if optional_ret0 else ''),
            postconv.Initializer(func_ast.returns[0].type, typepostconversion))
    elif return_self or ctxmgr == '__enter__@':
        yield I + 'Py_INCREF(self);'
        yield I + 'return self;'
    else:
        yield I + 'Py_RETURN_NONE;'
    yield '}'