mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
8001 lines
289 KiB
C++
8001 lines
289 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file interfaceMakerPythonNative.cxx
|
|
*/
|
|
|
|
#include "interfaceMakerPythonNative.h"
|
|
#include "interrogateBuilder.h"
|
|
#include "interrogate.h"
|
|
#include "functionRemap.h"
|
|
#include "parameterRemapUnchanged.h"
|
|
#include "typeManager.h"
|
|
|
|
#include "pnotify.h" // For nout
|
|
#include "interrogateDatabase.h"
|
|
#include "interrogateType.h"
|
|
#include "interrogateFunction.h"
|
|
#include "cppArrayType.h"
|
|
#include "cppConstType.h"
|
|
#include "cppEnumType.h"
|
|
#include "cppFunctionType.h"
|
|
#include "cppFunctionGroup.h"
|
|
#include "cppPointerType.h"
|
|
#include "cppTypeDeclaration.h"
|
|
#include "cppTypedefType.h"
|
|
#include "cppSimpleType.h"
|
|
#include "cppStructType.h"
|
|
#include "cppExpression.h"
|
|
#include "cppParameterList.h"
|
|
#include "lineStream.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
using std::dec;
|
|
using std::hex;
|
|
using std::max;
|
|
using std::min;
|
|
using std::oct;
|
|
using std::ostream;
|
|
using std::ostringstream;
|
|
using std::set;
|
|
using std::string;
|
|
|
|
extern InterrogateType dummy_type;
|
|
extern std::string EXPORT_IMPORT_PREFIX;
|
|
|
|
#define CLASS_PREFIX "Dtool_"
|
|
|
|
// Name Remapper... Snagged from ffi py code....
|
|
struct RenameSet {
|
|
const char *_from;
|
|
const char *_to;
|
|
int function_type;
|
|
};
|
|
|
|
RenameSet methodRenameDictionary[] = {
|
|
{ "operator ==" , "__eq__", 0 },
|
|
{ "operator !=" , "__ne__", 0 },
|
|
{ "operator << " , "__lshift__", 0 },
|
|
{ "operator >>" , "__rshift__", 0 },
|
|
{ "operator <" , "__lt__", 0 },
|
|
{ "operator >" , "__gt__", 0 },
|
|
{ "operator <=" , "__le__", 0 },
|
|
{ "operator >=" , "__ge__", 0 },
|
|
{ "operator =" , "assign", 0 },
|
|
{ "operator ()" , "__call__", 0 },
|
|
{ "operator []" , "__getitem__", 0 },
|
|
{ "operator ++unary", "increment", 0 },
|
|
{ "operator ++" , "increment", 0 },
|
|
{ "operator --unary", "decrement", 0 },
|
|
{ "operator --" , "decrement", 0 },
|
|
{ "operator ^" , "__xor__", 0 },
|
|
{ "operator %" , "__mod__", 0 },
|
|
{ "operator !" , "logicalNot", 0 },
|
|
{ "operator ~unary", "__invert__", 0 },
|
|
{ "operator &" , "__and__", 0 },
|
|
{ "operator &&" , "logicalAnd", 0 },
|
|
{ "operator |" , "__or__", 0 },
|
|
{ "operator ||" , "logicalOr", 0 },
|
|
{ "operator +" , "__add__", 0 },
|
|
{ "operator -" , "__sub__", 0 },
|
|
{ "operator -unary", "__neg__", 0 },
|
|
{ "operator *" , "__mul__", 0 },
|
|
{ "operator /" , "__div__", 0 },
|
|
{ "operator +=" , "__iadd__", 1 },
|
|
{ "operator -=" , "__isub__", 1 },
|
|
{ "operator *=" , "__imul__", 1 },
|
|
{ "operator /=" , "__idiv__", 1 },
|
|
{ "operator ," , "concatenate", 0 },
|
|
{ "operator |=" , "__ior__", 1 },
|
|
{ "operator &=" , "__iand__", 1 },
|
|
{ "operator ^=" , "__ixor__", 1 },
|
|
{ "operator ~=" , "bitwiseNotEqual", 0 },
|
|
{ "operator ->" , "dereference", 0 },
|
|
{ "operator <<=" , "__ilshift__", 1 },
|
|
{ "operator >>=" , "__irshift__", 1 },
|
|
{ "operator typecast bool", "__nonzero__", 0 },
|
|
{ "__nonzero__" , "__nonzero__", 0 },
|
|
{ "__reduce__" , "__reduce__", 0 },
|
|
{ "__reduce_persist__", "__reduce_persist__", 0 },
|
|
{ "__copy__" , "__copy__", 0 },
|
|
{ "__deepcopy__" , "__deepcopy__", 0 },
|
|
{ "print" , "Cprint", 0 },
|
|
{ "CInterval.set_t", "_priv__cSetT", 0 },
|
|
{ nullptr, nullptr, -1 }
|
|
};
|
|
|
|
const char *pythonKeywords[] = {
|
|
"and",
|
|
"as",
|
|
"assert",
|
|
"break",
|
|
"class",
|
|
"continue",
|
|
"def",
|
|
"del",
|
|
"elif",
|
|
"else",
|
|
"except",
|
|
"exec",
|
|
"finally",
|
|
"for",
|
|
"from",
|
|
"global",
|
|
"if",
|
|
"import",
|
|
"in",
|
|
"is",
|
|
"lambda",
|
|
"nonlocal",
|
|
"not",
|
|
"or",
|
|
"pass",
|
|
"print",
|
|
"raise",
|
|
"return",
|
|
"try",
|
|
"while",
|
|
"with",
|
|
"yield",
|
|
nullptr
|
|
};
|
|
|
|
std::string
|
|
checkKeyword(std::string &cppName) {
|
|
for (int x = 0; pythonKeywords[x] != nullptr; x++) {
|
|
if (cppName == pythonKeywords[x]) {
|
|
return std::string("_") + cppName;
|
|
}
|
|
}
|
|
return cppName;
|
|
}
|
|
|
|
std::string
|
|
classNameFromCppName(const std::string &cppName, bool mangle) {
|
|
if (!mangle_names) {
|
|
mangle = false;
|
|
}
|
|
|
|
// # initialize to empty string
|
|
std::string className = "";
|
|
|
|
// # These are the characters we want to strip out of the name
|
|
const std::string badChars("!@#$%^&*()<>,.-=+~{}? ");
|
|
|
|
bool nextCap = false;
|
|
bool nextUscore = false;
|
|
bool firstChar = true && mangle;
|
|
|
|
for (std::string::const_iterator chr = cppName.begin();
|
|
chr != cppName.end(); ++chr) {
|
|
if ((*chr == '_' || *chr == ' ') && mangle) {
|
|
nextCap = true;
|
|
|
|
} else if (badChars.find(*chr) != std::string::npos) {
|
|
nextUscore = !mangle;
|
|
|
|
} else if (nextCap || firstChar) {
|
|
className += toupper(*chr);
|
|
nextCap = false;
|
|
firstChar = false;
|
|
|
|
} else if (nextUscore) {
|
|
className += '_';
|
|
nextUscore = false;
|
|
className += *chr;
|
|
|
|
} else {
|
|
className += *chr;
|
|
}
|
|
}
|
|
|
|
if (className.empty()) {
|
|
std::string text = "** ERROR ** Renaming class: " + cppName + " to empty string";
|
|
printf("%s", text.c_str());
|
|
}
|
|
|
|
className = checkKeyword(className);
|
|
// # FFIConstants.notify.debug('Renaming class: ' + cppName + ' to: ' +
|
|
// className)
|
|
return className;
|
|
}
|
|
|
|
std::string
|
|
methodNameFromCppName(const std::string &cppName, const std::string &className, bool mangle) {
|
|
if (!mangle_names) {
|
|
mangle = false;
|
|
}
|
|
|
|
std::string origName = cppName;
|
|
|
|
if (origName.substr(0, 6) == "__py__") {
|
|
// By convention, a leading prefix of "__py__" is stripped. This
|
|
// indicates a Python-specific variant of a particular method.
|
|
origName = origName.substr(6);
|
|
}
|
|
|
|
std::string methodName;
|
|
const std::string badChars("!@#$%^&*()<>,.-=+~{}? ");
|
|
bool nextCap = false;
|
|
for (std::string::const_iterator chr = origName.begin();
|
|
chr != origName.end();
|
|
chr++) {
|
|
if ((*chr == '_' || *chr == ' ') && mangle) {
|
|
nextCap = true;
|
|
|
|
} else if (badChars.find(*chr) != std::string::npos) {
|
|
if (!mangle) {
|
|
methodName += '_';
|
|
}
|
|
|
|
} else if (nextCap) {
|
|
methodName += toupper(*chr);
|
|
nextCap = false;
|
|
|
|
} else {
|
|
methodName += *chr;
|
|
}
|
|
}
|
|
|
|
for (int x = 0; methodRenameDictionary[x]._from != nullptr; x++) {
|
|
if (origName == methodRenameDictionary[x]._from) {
|
|
methodName = methodRenameDictionary[x]._to;
|
|
}
|
|
}
|
|
|
|
// # Mangle names that happen to be python keywords so they are not anymore
|
|
methodName = checkKeyword(methodName);
|
|
return methodName;
|
|
}
|
|
|
|
std::string methodNameFromCppName(InterfaceMaker::Function *func, const std::string &className, bool mangle) {
|
|
std::string cppName = func->_ifunc.get_name();
|
|
if (func->_ifunc.is_unary_op()) {
|
|
cppName += "unary";
|
|
}
|
|
return methodNameFromCppName(cppName, className, mangle);
|
|
}
|
|
|
|
std::string methodNameFromCppName(FunctionRemap *remap, const std::string &className, bool mangle) {
|
|
std::string cppName = remap->_cppfunc->get_local_name();
|
|
if (remap->_ftype->_flags & CPPFunctionType::F_unary_op) {
|
|
cppName += "unary";
|
|
}
|
|
return methodNameFromCppName(cppName, className, mangle);
|
|
}
|
|
|
|
/**
|
|
* Determines whether this method should be mapped to one of Python's special
|
|
* slotted functions, those hard-coded functions that are assigned to
|
|
* particular function pointers within the object structure, for special
|
|
* functions like __getitem__ and __len__.
|
|
*
|
|
* Returns true if it has such a mapping, false if it is just a normal method.
|
|
* If it returns true, the SlottedFunctionDef structure is filled in with the
|
|
* important details.
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
|
|
SlottedFunctionDef &def) {
|
|
if (obj == nullptr) {
|
|
// Only methods may be slotted.
|
|
return false;
|
|
}
|
|
|
|
def._answer_location = string();
|
|
def._wrapper_type = WT_none;
|
|
def._min_version = 0;
|
|
def._keep_method = false;
|
|
|
|
string method_name = func->_ifunc.get_name();
|
|
bool is_unary_op = func->_ifunc.is_unary_op();
|
|
|
|
if (method_name == "operator +") {
|
|
def._answer_location = "nb_add";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator -" && is_unary_op) {
|
|
def._answer_location = "nb_negative";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator -") {
|
|
def._answer_location = "nb_subtract";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator *") {
|
|
def._answer_location = "nb_multiply";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator /") {
|
|
def._answer_location = "nb_divide";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator %") {
|
|
def._answer_location = "nb_remainder";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator <<") {
|
|
def._answer_location = "nb_lshift";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator >>") {
|
|
def._answer_location = "nb_rshift";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator ^") {
|
|
def._answer_location = "nb_xor";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator ~" && is_unary_op) {
|
|
def._answer_location = "nb_invert";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator &") {
|
|
def._answer_location = "nb_and";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator |") {
|
|
def._answer_location = "nb_or";
|
|
def._wrapper_type = WT_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__pow__") {
|
|
def._answer_location = "nb_power";
|
|
def._wrapper_type = WT_ternary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator +=") {
|
|
def._answer_location = "nb_inplace_add";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator -=") {
|
|
def._answer_location = "nb_inplace_subtract";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator *=") {
|
|
def._answer_location = "nb_inplace_multiply";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator /=") {
|
|
def._answer_location = "nb_inplace_divide";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator %=") {
|
|
def._answer_location = "nb_inplace_remainder";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator <<=") {
|
|
def._answer_location = "nb_inplace_lshift";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator >>=") {
|
|
def._answer_location = "nb_inplace_rshift";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator &=") {
|
|
def._answer_location = "nb_inplace_and";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator ^=") {
|
|
def._answer_location = "nb_inplace_xor";
|
|
def._wrapper_type = WT_inplace_binary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__ipow__") {
|
|
def._answer_location = "nb_inplace_power";
|
|
def._wrapper_type = WT_inplace_ternary_operator;
|
|
return true;
|
|
}
|
|
|
|
if (obj->_protocol_types & Object::PT_sequence) {
|
|
if (remap->_flags & FunctionRemap::F_getitem_int) {
|
|
def._answer_location = "sq_item";
|
|
def._wrapper_type = WT_sequence_getitem;
|
|
return true;
|
|
}
|
|
if (remap->_flags & FunctionRemap::F_setitem_int ||
|
|
remap->_flags & FunctionRemap::F_delitem_int) {
|
|
def._answer_location = "sq_ass_item";
|
|
def._wrapper_type = WT_sequence_setitem;
|
|
return true;
|
|
}
|
|
if (remap->_flags & FunctionRemap::F_size) {
|
|
def._answer_location = "sq_length";
|
|
def._wrapper_type = WT_sequence_size;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (obj->_protocol_types & Object::PT_mapping) {
|
|
if (remap->_flags & FunctionRemap::F_getitem) {
|
|
def._answer_location = "mp_subscript";
|
|
def._wrapper_type = WT_one_param;
|
|
return true;
|
|
}
|
|
if (remap->_flags & FunctionRemap::F_setitem ||
|
|
remap->_flags & FunctionRemap::F_delitem) {
|
|
def._answer_location = "mp_ass_subscript";
|
|
def._wrapper_type = WT_mapping_setitem;
|
|
return true;
|
|
}
|
|
if (remap->_flags & FunctionRemap::F_size) {
|
|
def._answer_location = "mp_length";
|
|
def._wrapper_type = WT_sequence_size;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (obj->_protocol_types & Object::PT_iter) {
|
|
if (method_name == "__iter__") {
|
|
def._answer_location = "tp_iter";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "next" || method_name == "__next__") {
|
|
def._answer_location = "tp_iternext";
|
|
def._wrapper_type = WT_iter_next;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (method_name == "__await__") {
|
|
def._answer_location = "am_await";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__aiter__") {
|
|
def._answer_location = "am_aiter";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__anext__") {
|
|
def._answer_location = "am_anext";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "operator ()") {
|
|
def._answer_location = "tp_call";
|
|
def._wrapper_type = WT_none;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__getattribute__") {
|
|
// Like __getattr__, but is called unconditionally, ie. does not try
|
|
// PyObject_GenericGetAttr first.
|
|
def._answer_location = "tp_getattro";
|
|
def._wrapper_type = WT_one_param;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__getattr__") {
|
|
def._answer_location = "tp_getattro";
|
|
def._wrapper_type = WT_getattr;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__setattr__") {
|
|
def._answer_location = "tp_setattro";
|
|
def._wrapper_type = WT_setattr;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__delattr__") {
|
|
// __delattr__ shares the slot with __setattr__, except that it takes only
|
|
// one argument.
|
|
def._answer_location = "tp_setattro";
|
|
def._wrapper_type = WT_setattr;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__nonzero__" || method_name == "__bool__") {
|
|
// Python 2 named it nb_nonzero, Python 3 nb_bool. We refer to it just as
|
|
// nb_bool.
|
|
def._answer_location = "nb_bool";
|
|
def._wrapper_type = WT_inquiry;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__getbuffer__") {
|
|
def._answer_location = "bf_getbuffer";
|
|
def._wrapper_type = WT_getbuffer;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__releasebuffer__") {
|
|
def._answer_location = "bf_releasebuffer";
|
|
def._wrapper_type = WT_releasebuffer;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__traverse__") {
|
|
def._answer_location = "tp_traverse";
|
|
def._wrapper_type = WT_traverse;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__clear__") {
|
|
def._answer_location = "tp_clear";
|
|
def._wrapper_type = WT_inquiry;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__repr__") {
|
|
def._answer_location = "tp_repr";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__str__") {
|
|
def._answer_location = "tp_str";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__cmp__" || (remap->_flags & FunctionRemap::F_compare_to) != 0) {
|
|
def._answer_location = "tp_compare";
|
|
def._wrapper_type = WT_compare;
|
|
def._keep_method = (method_name != "__cmp__");
|
|
return true;
|
|
}
|
|
|
|
if (method_name == "__hash__" || (remap->_flags & FunctionRemap::F_hash) != 0) {
|
|
def._answer_location = "tp_hash";
|
|
def._wrapper_type = WT_hash;
|
|
def._keep_method = (method_name != "__hash__");
|
|
return true;
|
|
}
|
|
|
|
if (remap->_type == FunctionRemap::T_typecast_method) {
|
|
// A typecast operator. Check for a supported low-level typecast type.
|
|
if (TypeManager::is_bool(remap->_return_type->get_orig_type())) {
|
|
// If it's a bool type, then we wrap it with the __nonzero__ slot
|
|
// method.
|
|
def._answer_location = "nb_bool";
|
|
def._wrapper_type = WT_inquiry;
|
|
return true;
|
|
|
|
} else if (TypeManager::is_integer(remap->_return_type->get_orig_type())) {
|
|
// An integer type.
|
|
def._answer_location = "nb_int";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
|
|
} else if (TypeManager::is_float(remap->_return_type->get_orig_type())) {
|
|
// A floating-point (or double) type.
|
|
def._answer_location = "nb_float";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
|
|
} else if (remap->_return_type->new_type_is_atomic_string()) {
|
|
// A string type.
|
|
def._answer_location = "tp_str";
|
|
def._wrapper_type = WT_no_params;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the slot occurs in the map of slotted functions, and if
|
|
* so, writes out a pointer to its wrapper. If not, writes out def (usually
|
|
* 0).
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_function_slot(ostream &out, int indent_level, const SlottedFunctions &slots,
|
|
const string &slot, const string &default_) {
|
|
|
|
SlottedFunctions::const_iterator rfi = slots.find(slot);
|
|
if (rfi == slots.end()) {
|
|
indent(out, indent_level) << default_ << ",";
|
|
if (default_ == "0") {
|
|
out << " // " << slot;
|
|
}
|
|
out << "\n";
|
|
return;
|
|
}
|
|
|
|
const SlottedFunctionDef &def = rfi->second;
|
|
|
|
// Add an #ifdef if there is a specific version requirement on this
|
|
// function.
|
|
if (def._min_version > 0) {
|
|
out << "#if PY_VERSION_HEX >= 0x" << hex << def._min_version << dec << "\n";
|
|
}
|
|
|
|
indent(out, indent_level) << "&" << def._wrapper_name << ",\n";
|
|
|
|
if (def._min_version > 0) {
|
|
out << "#else\n";
|
|
indent(out, indent_level) << default_ << ",\n";
|
|
out << "#endif\n";
|
|
}
|
|
}
|
|
|
|
void InterfaceMakerPythonNative::
|
|
get_valid_child_classes(std::map<std::string, CastDetails> &answer, CPPStructType *inclass, const std::string &upcast_seed, bool can_downcast) {
|
|
if (inclass == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (const CPPStructType::Base &base : inclass->_derivation) {
|
|
// if (base._vis <= V_public) can_downcast = false;
|
|
CPPStructType *base_type = TypeManager::resolve_type(base._base)->as_struct_type();
|
|
if (base_type != nullptr) {
|
|
std::string scoped_name = base_type->get_local_name(&parser);
|
|
|
|
if (answer.find(scoped_name) == answer.end()) {
|
|
answer[scoped_name]._can_downcast = can_downcast;
|
|
answer[scoped_name]._to_class_name = scoped_name;
|
|
answer[scoped_name]._structType = base_type;
|
|
|
|
if (base._is_virtual) {
|
|
answer[scoped_name]._can_downcast = false;
|
|
}
|
|
|
|
std::string local_upcast("(");
|
|
local_upcast += scoped_name + " *)"+ upcast_seed +"";
|
|
answer[scoped_name]._up_cast_string = local_upcast;
|
|
answer[scoped_name]._is_legal_py_class = is_cpp_type_legal(base_type);
|
|
} else {
|
|
answer[scoped_name]._can_downcast = false;
|
|
}
|
|
|
|
get_valid_child_classes(answer, base_type, answer[scoped_name]._up_cast_string, answer[scoped_name]._can_downcast);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_python_instance(ostream &out, int indent_level, const string &return_expr,
|
|
bool owns_memory, const InterrogateType &itype, bool is_const) {
|
|
out << std::boolalpha;
|
|
|
|
if (!isExportThisRun(itype._cpptype)) {
|
|
_external_imports.insert(TypeManager::resolve_type(itype._cpptype));
|
|
}
|
|
|
|
string class_name = itype.get_scoped_name();
|
|
|
|
// We don't handle final classes via DTool_CreatePyInstanceTyped since we
|
|
// know it can't be of a subclass type, so we don't need to do the downcast.
|
|
CPPStructType *struct_type = itype._cpptype->as_struct_type();
|
|
if (IsPandaTypedObject(struct_type) && !struct_type->is_final()) {
|
|
// We can't let DTool_CreatePyInstanceTyped do the NULL check since we
|
|
// will be grabbing the type index (which would obviously crash when
|
|
// called on a NULL pointer), so we do it here.
|
|
indent(out, indent_level)
|
|
<< "if (" << return_expr << " == nullptr) {\n";
|
|
indent(out, indent_level)
|
|
<< " Py_INCREF(Py_None);\n";
|
|
indent(out, indent_level)
|
|
<< " return Py_None;\n";
|
|
indent(out, indent_level)
|
|
<< "} else {\n";
|
|
// Special exception if we are returning TypedWritable, which might
|
|
// actually be a derived class that inherits from ReferenceCount.
|
|
if (!owns_memory && !is_const && class_name == "TypedWritable") {
|
|
indent(out, indent_level)
|
|
<< " ReferenceCount *rc = " << return_expr << "->as_reference_count();\n";
|
|
indent(out, indent_level)
|
|
<< " bool is_refcount = (rc != nullptr);\n";
|
|
indent(out, indent_level)
|
|
<< " if (is_refcount) {\n";
|
|
indent(out, indent_level)
|
|
<< " rc->ref();\n";
|
|
indent(out, indent_level)
|
|
<< " }\n";
|
|
indent(out, indent_level)
|
|
<< " return DTool_CreatePyInstanceTyped((void *)" << return_expr
|
|
<< ", *Dtool_Ptr_" << make_safe_name(class_name) << ", is_refcount, "
|
|
<< is_const << ", " << return_expr
|
|
<< "->get_type_index());\n";
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< " return DTool_CreatePyInstanceTyped((void *)" << return_expr
|
|
<< ", *Dtool_Ptr_" << make_safe_name(class_name) << ", "
|
|
<< owns_memory << ", " << is_const << ", "
|
|
<< return_expr << "->as_typed_object()->get_type_index());\n";
|
|
}
|
|
indent(out, indent_level)
|
|
<< "}\n";
|
|
} else {
|
|
// DTool_CreatePyInstance will do the NULL check.
|
|
indent(out, indent_level)
|
|
<< "return "
|
|
<< "DTool_CreatePyInstance((void *)" << return_expr << ", "
|
|
<< "*Dtool_Ptr_" << make_safe_name(class_name) << ", "
|
|
<< owns_memory << ", " << is_const << ");\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
InterfaceMakerPythonNative::
|
|
InterfaceMakerPythonNative(InterrogateModuleDef *def) :
|
|
InterfaceMakerPython(def)
|
|
{
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
InterfaceMakerPythonNative::
|
|
~InterfaceMakerPythonNative() {
|
|
}
|
|
|
|
/**
|
|
* Generates the list of function prototypes corresponding to the functions
|
|
* that will be output in write_functions().
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_prototypes(ostream &out_code, ostream *out_h) {
|
|
if (out_h != nullptr) {
|
|
*out_h << "#include \"py_panda.h\"\n\n";
|
|
}
|
|
|
|
/*
|
|
for (Function *func : _functions) {
|
|
if (!func->_itype.is_global() && is_function_legal(func)) {
|
|
write_prototype_for(out_code, func);
|
|
}
|
|
}
|
|
*/
|
|
|
|
Objects::iterator oi;
|
|
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
|
Object *object = (*oi).second;
|
|
if (object->_itype.is_class() || object->_itype.is_struct()) {
|
|
if (is_cpp_type_legal(object->_itype._cpptype)) {
|
|
if (isExportThisRun(object->_itype._cpptype)) {
|
|
write_prototypes_class(out_code, out_h, object);
|
|
} else {
|
|
// write_prototypes_class_external(out_code, object);
|
|
// _external_imports.insert(object->_itype._cpptype);
|
|
}
|
|
}
|
|
} else if (object->_itype.is_scoped_enum() && isExportThisRun(object->_itype._cpptype)) {
|
|
// Forward declare where we will put the scoped enum type.
|
|
string class_name = object->_itype._cpptype->get_local_name(&parser);
|
|
string safe_name = make_safe_name(class_name);
|
|
out_code << "static PyTypeObject *Dtool_Ptr_" << safe_name << " = nullptr;\n";
|
|
}
|
|
}
|
|
|
|
out_code << "/**\n";
|
|
out_code << " * Declarations for exported classes\n";
|
|
out_code << " */\n";
|
|
|
|
out_code << "static const Dtool_TypeDef exports[] = {\n";
|
|
|
|
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
|
Object *object = (*oi).second;
|
|
|
|
if (object->_itype.is_class() || object->_itype.is_struct()) {
|
|
CPPType *type = object->_itype._cpptype;
|
|
|
|
if (isExportThisRun(type) && is_cpp_type_legal(type)) {
|
|
string class_name = type->get_local_name(&parser);
|
|
string safe_name = make_safe_name(class_name);
|
|
|
|
out_code << " {\"" << class_name << "\", &Dtool_" << safe_name << "},\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
out_code << " {nullptr, nullptr},\n";
|
|
out_code << "};\n\n";
|
|
|
|
out_code << "/**\n";
|
|
out_code << " * Extern declarations for imported classes\n";
|
|
out_code << " */\n";
|
|
|
|
// Write out a table of the externally imported types that will be filled in
|
|
// upon module initialization.
|
|
if (!_external_imports.empty()) {
|
|
out_code << "#ifndef LINK_ALL_STATIC\n";
|
|
out_code << "static Dtool_TypeDef imports[] = {\n";
|
|
|
|
int idx = 0;
|
|
for (CPPType *type : _external_imports) {
|
|
string class_name = type->get_local_name(&parser);
|
|
string safe_name = make_safe_name(class_name);
|
|
|
|
out_code << " {\"" << class_name << "\", nullptr},\n";
|
|
out_code << "#define Dtool_Ptr_" << safe_name << " (imports[" << idx << "].type)\n";
|
|
++idx;
|
|
}
|
|
out_code << " {nullptr, nullptr},\n";
|
|
out_code << "};\n";
|
|
out_code << "#endif\n\n";
|
|
}
|
|
|
|
for (CPPType *type : _external_imports) {
|
|
string class_name = type->get_local_name(&parser);
|
|
string safe_name = make_safe_name(class_name);
|
|
|
|
out_code << "// " << class_name << "\n";
|
|
|
|
out_code << "#ifndef LINK_ALL_STATIC\n";
|
|
// out_code << "IMPORT_THIS struct Dtool_PyTypedObject Dtool_" <<
|
|
// safe_name << ";\n";
|
|
//if (has_get_class_type_function(type)) {
|
|
// out_code << "static struct Dtool_PyTypedObject *Dtool_Ptr_" << safe_name << ";\n";
|
|
//}
|
|
// out_code << "#define Dtool_Ptr_" << safe_name << " &Dtool_" <<
|
|
// safe_name << "\n"; out_code << "IMPORT_THIS void
|
|
// Dtool_PyModuleClassInit_" << safe_name << "(PyObject *module);\n";
|
|
|
|
// This is some really ugly code, because we have to store a pointer with
|
|
// a function of a signature that differs from class to class. If someone
|
|
// can think of an elegant way to do this without sacrificing perf, let me
|
|
// know.
|
|
int has_coerce = has_coerce_constructor(type->as_struct_type());
|
|
if (has_coerce > 0) {
|
|
if (TypeManager::is_reference_count(type)) {
|
|
out_code
|
|
<< "inline static bool Dtool_ConstCoerce_" << safe_name << "(PyObject *args, CPT(" << class_name << ") &coerced) {\n"
|
|
<< " nassertr(Dtool_Ptr_" << safe_name << " != nullptr, false);\n"
|
|
<< " nassertr(Dtool_Ptr_" << safe_name << "->_Dtool_ConstCoerce != nullptr, false);\n"
|
|
<< " return ((bool (*)(PyObject *, CPT(" << class_name << ") &))Dtool_Ptr_" << safe_name << "->_Dtool_ConstCoerce)(args, coerced);\n"
|
|
<< "}\n";
|
|
|
|
if (has_coerce > 1) {
|
|
out_code
|
|
<< "inline static bool Dtool_Coerce_" << safe_name << "(PyObject *args, PT(" << class_name << ") &coerced) {\n"
|
|
<< " nassertr(Dtool_Ptr_" << safe_name << " != nullptr, false);\n"
|
|
<< " nassertr(Dtool_Ptr_" << safe_name << "->_Dtool_Coerce != nullptr, false);\n"
|
|
<< " return ((bool (*)(PyObject *, PT(" << class_name << ") &))Dtool_Ptr_" << safe_name << "->_Dtool_Coerce)(args, coerced);\n"
|
|
<< "}\n";
|
|
}
|
|
} else {
|
|
out_code
|
|
<< "inline static " << class_name << " *Dtool_Coerce_" << safe_name << "(PyObject *args, " << class_name << " &coerced) {\n"
|
|
<< " nassertr(Dtool_Ptr_" << safe_name << " != nullptr, nullptr);\n"
|
|
<< " nassertr(Dtool_Ptr_" << safe_name << "->_Dtool_Coerce != nullptr, nullptr);\n"
|
|
<< " return ((" << class_name << " *(*)(PyObject *, " << class_name << " &))Dtool_Ptr_" << safe_name << "->_Dtool_Coerce)(args, coerced);\n"
|
|
<< "}\n";
|
|
}
|
|
}
|
|
out_code << "#else\n";
|
|
out_code << "extern struct Dtool_PyTypedObject Dtool_" << safe_name << ";\n";
|
|
out_code << "static struct Dtool_PyTypedObject *const Dtool_Ptr_" << safe_name << " = &Dtool_" << safe_name << ";\n";
|
|
|
|
if (has_coerce > 0) {
|
|
if (TypeManager::is_reference_count(type)) {
|
|
out_code << "extern bool Dtool_ConstCoerce_" << safe_name << "(PyObject *args, CPT(" << class_name << ") &coerced);\n";
|
|
if (has_coerce > 1) {
|
|
out_code << "extern bool Dtool_Coerce_" << safe_name << "(PyObject *args, PT(" << class_name << ") &coerced);\n";
|
|
}
|
|
} else {
|
|
out_code << "extern " << class_name << " *Dtool_Coerce_" << safe_name << "(PyObject *args, " << class_name << " &coerced);\n";
|
|
}
|
|
}
|
|
out_code << "#endif\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Output enough enformation to a declartion of a externally generated dtool
|
|
* type object
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_prototypes_class_external(ostream &out, Object *obj) {
|
|
std::string class_name = make_safe_name(obj->_itype.get_scoped_name());
|
|
std::string c_class_name = obj->_itype.get_true_name();
|
|
std::string preferred_name = obj->_itype.get_name();
|
|
|
|
|
|
out << "/**\n";
|
|
out << " * Forward declaration of class " << class_name << "\n";
|
|
out << " */\n";
|
|
|
|
// This typedef is necessary for class templates since we can't pass a comma
|
|
// to a macro function.
|
|
out << "typedef " << c_class_name << " " << class_name << "_localtype;\n";
|
|
out << "Define_Module_Class_Forward(" << _def->module_name << ", " << class_name << ", " << class_name << "_localtype, " << classNameFromCppName(preferred_name, false) << ");\n";
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_prototypes_class(ostream &out_code, ostream *out_h, Object *obj) {
|
|
std::string ClassName = make_safe_name(obj->_itype.get_scoped_name());
|
|
|
|
out_code << "/**\n";
|
|
out_code << " * Forward declarations for top-level class " << ClassName << "\n";
|
|
out_code << " */\n";
|
|
|
|
/*
|
|
for (Function *func : obj->_methods) {
|
|
write_prototype_for(out_code, func);
|
|
}
|
|
*/
|
|
|
|
/*
|
|
for (Function *func : obj->_constructors) {
|
|
std::string fname = "int Dtool_Init_" + ClassName + "(PyObject *self, PyObject *args, PyObject *kwds)";
|
|
write_prototype_for_name(out_code, obj, func, fname);
|
|
}
|
|
*/
|
|
|
|
write_class_declarations(out_code, out_h, obj);
|
|
}
|
|
|
|
/**
|
|
* Generates the list of functions that are appropriate for this interface.
|
|
* This function is called *before* write_prototypes(), above.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_functions(ostream &out) {
|
|
out << "/**\n";
|
|
out << " * Python wrappers for global functions\n" ;
|
|
out << " */\n";
|
|
FunctionsByIndex::iterator fi;
|
|
for (fi = _functions.begin(); fi != _functions.end(); ++fi) {
|
|
Function *func = (*fi).second;
|
|
if (!func->_itype.is_global() && is_function_legal(func)) {
|
|
write_function_for_top(out, nullptr, func);
|
|
}
|
|
}
|
|
|
|
Objects::iterator oi;
|
|
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
|
Object *object = (*oi).second;
|
|
if (object->_itype.is_class() || object->_itype.is_struct()) {
|
|
if (is_cpp_type_legal(object->_itype._cpptype)) {
|
|
if (isExportThisRun(object->_itype._cpptype)) {
|
|
write_class_details(out, object);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Objects::iterator oi;
|
|
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
|
Object *object = (*oi).second;
|
|
if (!object->_itype.get_outer_class()) {
|
|
if (object->_itype.is_class() || object->_itype.is_struct()) {
|
|
if (is_cpp_type_legal(object->_itype._cpptype)) {
|
|
if (isExportThisRun(object->_itype._cpptype)) {
|
|
write_module_class(out, object);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes out all of the wrapper methods necessary to export the given object.
|
|
* This is called by write_functions.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_class_details(ostream &out, Object *obj) {
|
|
// std::string cClassName = obj->_itype.get_scoped_name();
|
|
std::string ClassName = make_safe_name(obj->_itype.get_scoped_name());
|
|
std::string cClassName = obj->_itype.get_true_name();
|
|
|
|
out << "/**\n";
|
|
out << " * Python wrappers for functions of class " << cClassName << "\n" ;
|
|
out << " */\n";
|
|
|
|
// First write out all the wrapper functions for the methods.
|
|
for (Function *func : obj->_methods) {
|
|
if (func) {
|
|
// Write the definition of the generic wrapper function for this
|
|
// function.
|
|
write_function_for_top(out, obj, func);
|
|
}
|
|
}
|
|
|
|
// Now write out generated getters and setters for the properties.
|
|
for (Property *property : obj->_properties) {
|
|
write_getset(out, obj, property);
|
|
}
|
|
|
|
// Write the constructors.
|
|
std::string fname = "static int Dtool_Init_" + ClassName + "(PyObject *self, PyObject *args, PyObject *kwds)";
|
|
for (Function *func : obj->_constructors) {
|
|
string expected_params;
|
|
write_function_for_name(out, obj, func->_remaps, fname, expected_params, true, AT_keyword_args, RF_int);
|
|
}
|
|
if (obj->_constructors.size() == 0) {
|
|
// We still need to write a dummy constructor to prevent inheriting the
|
|
// constructor from a base class.
|
|
out << fname << " {\n"
|
|
" Dtool_Raise_TypeError(\"cannot init abstract class\");\n"
|
|
" return -1;\n"
|
|
"}\n\n";
|
|
}
|
|
|
|
CPPType *cpptype = TypeManager::resolve_type(obj->_itype._cpptype);
|
|
|
|
// If we have "coercion constructors", write a single wrapper to consolidate
|
|
// those.
|
|
int has_coerce = has_coerce_constructor(cpptype->as_struct_type());
|
|
if (has_coerce > 0) {
|
|
write_coerce_constructor(out, obj, true);
|
|
if (has_coerce > 1 && TypeManager::is_reference_count(obj->_itype._cpptype)) {
|
|
write_coerce_constructor(out, obj, false);
|
|
}
|
|
}
|
|
|
|
// Write make seqs: generated methods that return a sequence of items.
|
|
for (MakeSeq *make_seq : obj->_make_seqs) {
|
|
if (is_function_legal(make_seq->_length_getter) &&
|
|
is_function_legal(make_seq->_element_getter)) {
|
|
write_make_seq(out, obj, ClassName, cClassName, make_seq);
|
|
} else {
|
|
if (!is_function_legal(make_seq->_length_getter)) {
|
|
std::cerr << "illegal length function for MAKE_SEQ: " << make_seq->_length_getter->_name << "\n";
|
|
}
|
|
if (!is_function_legal(make_seq->_element_getter)) {
|
|
std::cerr << "illegal element function for MAKE_SEQ: " << make_seq->_element_getter->_name << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine which external imports we will need.
|
|
std::map<string, CastDetails> details;
|
|
std::map<string, CastDetails>::iterator di;
|
|
builder.get_type(TypeManager::unwrap(cpptype), false);
|
|
get_valid_child_classes(details, cpptype->as_struct_type());
|
|
for (di = details.begin(); di != details.end(); di++) {
|
|
// InterrogateType ptype =idb->get_type(di->first);
|
|
if (di->second._is_legal_py_class && !isExportThisRun(di->second._structType)) {
|
|
_external_imports.insert(TypeManager::resolve_type(di->second._structType));
|
|
}
|
|
// out << "IMPORT_THIS struct Dtool_PyTypedObject Dtool_" <<
|
|
// make_safe_name(di->second._to_class_name) << ";\n";
|
|
}
|
|
|
|
// Write support methods to cast from and to pointers of this type.
|
|
{
|
|
out << "static void *Dtool_UpcastInterface_" << ClassName << "(PyObject *self, Dtool_PyTypedObject *requested_type) {\n";
|
|
out << " Dtool_PyTypedObject *type = DtoolInstance_TYPE(self);\n";
|
|
out << " if (type != &Dtool_" << ClassName << ") {\n";
|
|
out << " printf(\"" << ClassName << " ** Bad Source Type-- Requesting Conversion from %s to %s\\n\", Py_TYPE(self)->tp_name, requested_type->_PyType.tp_name); fflush(nullptr);\n";;
|
|
out << " return nullptr;\n";
|
|
out << " }\n";
|
|
out << "\n";
|
|
out << " " << cClassName << " *local_this = (" << cClassName << " *)DtoolInstance_VOID_PTR(self);\n";
|
|
out << " if (requested_type == &Dtool_" << ClassName << ") {\n";
|
|
out << " return local_this;\n";
|
|
out << " }\n";
|
|
|
|
for (di = details.begin(); di != details.end(); di++) {
|
|
if (di->second._is_legal_py_class) {
|
|
out << " if (requested_type == Dtool_Ptr_" << make_safe_name(di->second._to_class_name) << ") {\n";
|
|
out << " return " << di->second._up_cast_string << " local_this;\n";
|
|
out << " }\n";
|
|
}
|
|
}
|
|
|
|
// Are there any implicit cast operators that can cast this object to our
|
|
// desired pointer?
|
|
for (Function *func : obj->_methods) {
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
if (remap->_type == FunctionRemap::T_typecast_method &&
|
|
is_remap_legal(remap) &&
|
|
!remap->_return_type->return_value_needs_management() &&
|
|
(remap->_cppfunc->_storage_class & CPPInstance::SC_explicit) == 0 &&
|
|
TypeManager::is_pointer(remap->_return_type->get_new_type())) {
|
|
|
|
CPPType *cast_type = remap->_return_type->get_orig_type();
|
|
CPPType *obj_type = TypeManager::unwrap(TypeManager::resolve_type(remap->_return_type->get_new_type()));
|
|
string return_expr = "(" + cast_type->get_local_name(&parser) + ")*local_this";
|
|
out << " // " << *remap->_cppfunc << "\n";
|
|
out << " if (requested_type == Dtool_Ptr_" << make_safe_name(obj_type->get_local_name(&parser)) << ") {\n";
|
|
out << " return (void *)(" << remap->_return_type->get_return_expr(return_expr) << ");\n";
|
|
out << " }\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
out << " return nullptr;\n";
|
|
out << "}\n\n";
|
|
|
|
out << "static void *Dtool_DowncastInterface_" << ClassName << "(void *from_this, Dtool_PyTypedObject *from_type) {\n";
|
|
out << " if (from_this == nullptr || from_type == nullptr) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n";
|
|
out << " if (from_type == Dtool_Ptr_" << ClassName << ") {\n";
|
|
out << " return from_this;\n";
|
|
out << " }\n";
|
|
for (di = details.begin(); di != details.end(); di++) {
|
|
if (di->second._can_downcast && di->second._is_legal_py_class) {
|
|
out << " if (from_type == Dtool_Ptr_" << make_safe_name(di->second._to_class_name) << ") {\n";
|
|
out << " " << di->second._to_class_name << "* other_this = (" << di->second._to_class_name << "*)from_this;\n" ;
|
|
out << " return (" << cClassName << "*)other_this;\n";
|
|
out << " }\n";
|
|
}
|
|
}
|
|
out << " return nullptr;\n";
|
|
out << "}\n\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_class_declarations(ostream &out, ostream *out_h, Object *obj) {
|
|
const InterrogateType &itype = obj->_itype;
|
|
std::string class_name = make_safe_name(obj->_itype.get_scoped_name());
|
|
std::string c_class_name = obj->_itype.get_true_name();
|
|
std::string preferred_name = itype.get_name();
|
|
std::string class_struct_name = std::string(CLASS_PREFIX) + class_name;
|
|
|
|
CPPType *type = obj->_itype._cpptype;
|
|
|
|
// This typedef is necessary for class templates since we can't pass a comma
|
|
// to a macro function.
|
|
out << "typedef " << c_class_name << " " << class_name << "_localtype;\n";
|
|
if (obj->_itype.has_destructor() ||
|
|
obj->_itype.destructor_is_inherited() ||
|
|
obj->_itype.destructor_is_implicit()) {
|
|
|
|
if (TypeManager::is_reference_count(type)) {
|
|
out << "Define_Module_ClassRef";
|
|
} else {
|
|
out << "Define_Module_Class";
|
|
}
|
|
} else {
|
|
if (TypeManager::is_reference_count(type)) {
|
|
out << "Define_Module_ClassRef_Private";
|
|
} else {
|
|
out << "Define_Module_Class_Private";
|
|
}
|
|
}
|
|
out << "(" << _def->module_name << ", " << class_name << ", " << class_name << "_localtype, " << classNameFromCppName(preferred_name, false) << ");\n";
|
|
|
|
out << "static struct Dtool_PyTypedObject *const Dtool_Ptr_" << class_name << " = &Dtool_" << class_name << ";\n";
|
|
out << "static void Dtool_PyModuleClassInit_" << class_name << "(PyObject *module);\n";
|
|
|
|
int has_coerce = has_coerce_constructor(type->as_struct_type());
|
|
if (has_coerce > 0) {
|
|
if (TypeManager::is_reference_count(type)) {
|
|
out << "bool Dtool_ConstCoerce_" << class_name << "(PyObject *args, CPT(" << c_class_name << ") &coerced);\n";
|
|
if (has_coerce > 1) {
|
|
out << "bool Dtool_Coerce_" << class_name << "(PyObject *args, PT(" << c_class_name << ") &coerced);\n";
|
|
}
|
|
} else {
|
|
out << "" << c_class_name << " *Dtool_Coerce_" << class_name << "(PyObject *args, " << c_class_name << " &coerced);\n";
|
|
}
|
|
}
|
|
|
|
out << "\n";
|
|
|
|
if (out_h != nullptr) {
|
|
*out_h << "extern \"C\" " << EXPORT_IMPORT_PREFIX << " struct Dtool_PyTypedObject Dtool_" << class_name << ";\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates whatever additional code is required to support a module file.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_sub_module(ostream &out, Object *obj) {
|
|
// Object * obj = _objects[_embeded_index] ;
|
|
string class_name = make_safe_name(obj->_itype.get_scoped_name());
|
|
string class_ptr;
|
|
|
|
if (!obj->_itype.is_typedef()) {
|
|
out << " // " << *(obj->_itype._cpptype) << "\n";
|
|
out << " Dtool_PyModuleClassInit_" << class_name << "(module);\n";
|
|
class_ptr = "&Dtool_" + class_name;
|
|
|
|
} else {
|
|
// Unwrap typedefs.
|
|
TypeIndex wrapped = obj->_itype._wrapped_type;
|
|
while (interrogate_type_is_typedef(wrapped)) {
|
|
wrapped = interrogate_type_wrapped_type(wrapped);
|
|
}
|
|
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
const InterrogateType &wrapped_itype = idb->get_type(wrapped);
|
|
|
|
class_name = make_safe_name(wrapped_itype.get_scoped_name());
|
|
|
|
out << " // typedef " << wrapped_itype.get_scoped_name()
|
|
<< " " << *(obj->_itype._cpptype) << "\n";
|
|
|
|
if (!isExportThisRun(wrapped_itype._cpptype)) {
|
|
_external_imports.insert(TypeManager::resolve_type(wrapped_itype._cpptype));
|
|
|
|
class_ptr = "Dtool_Ptr_" + class_name;
|
|
out << " assert(" << class_ptr << " != nullptr);\n";
|
|
} else {
|
|
class_ptr = "&Dtool_" + class_name;
|
|
|
|
// If this is a typedef to a class defined in the same module, make sure
|
|
// that the class is initialized before we try to define the typedef.
|
|
out << " Dtool_PyModuleClassInit_" << class_name << "(module);\n";
|
|
}
|
|
}
|
|
|
|
std::string export_class_name = classNameFromCppName(obj->_itype.get_name(), false);
|
|
std::string export_class_name2 = classNameFromCppName(obj->_itype.get_name(), true);
|
|
|
|
class_ptr = "(PyObject *)" + class_ptr;
|
|
|
|
// Note: PyModule_AddObject steals a reference, so we have to call Py_INCREF
|
|
// for every but the first time we add it to the module.
|
|
if (obj->_itype.is_typedef()) {
|
|
out << " Py_INCREF(" << class_ptr << ");\n";
|
|
}
|
|
|
|
out << " PyModule_AddObject(module, \"" << export_class_name << "\", " << class_ptr << ");\n";
|
|
if (export_class_name != export_class_name2) {
|
|
out << " Py_INCREF(Dtool_Ptr_" << class_name << ");\n";
|
|
out << " PyModule_AddObject(module, \"" << export_class_name2 << "\", " << class_ptr << ");\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_module_support(ostream &out, ostream *out_h, InterrogateModuleDef *def) {
|
|
out << "/**\n";
|
|
out << " * Module Object Linker ..\n";
|
|
out << " */\n";
|
|
|
|
Objects::iterator oi;
|
|
|
|
out << "void Dtool_" << def->library_name << "_RegisterTypes() {\n"
|
|
" TypeRegistry *registry = TypeRegistry::ptr();\n"
|
|
" nassertv(registry != nullptr);\n";
|
|
|
|
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
|
Object *object = (*oi).second;
|
|
if (object->_itype.is_class() || object->_itype.is_struct()) {
|
|
CPPType *type = object->_itype._cpptype;
|
|
if (is_cpp_type_legal(type) && isExportThisRun(type)) {
|
|
string class_name = object->_itype.get_scoped_name();
|
|
string safe_name = make_safe_name(class_name);
|
|
bool is_typed = has_get_class_type_function(type);
|
|
|
|
if (is_typed) {
|
|
out << " {\n";
|
|
if (has_init_type_function(type)) {
|
|
// Call the init_type function. This isn't necessary for all
|
|
// types as many of them are automatically initialized at static
|
|
// init type, but for some extension classes it's useful.
|
|
out << " " << type->get_local_name(&parser)
|
|
<< "::init_type();\n";
|
|
}
|
|
out << " TypeHandle handle = " << type->get_local_name(&parser)
|
|
<< "::get_class_type();\n";
|
|
out << " Dtool_" << safe_name << "._type = handle;\n";
|
|
out << " registry->record_python_type(handle, "
|
|
"(PyObject *)&Dtool_" << safe_name << ");\n";
|
|
out << " }\n";
|
|
} else {
|
|
if (IsPandaTypedObject(type->as_struct_type())) {
|
|
nout << object->_itype.get_scoped_name() << " derives from TypedObject, "
|
|
<< "but does not define a get_class_type() function.\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
out << "}\n\n";
|
|
|
|
out << "void Dtool_" << def->library_name << "_BuildInstants(PyObject *module) {\n";
|
|
out << " (void) module;\n";
|
|
|
|
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
|
Object *object = (*oi).second;
|
|
if (object->_itype.is_enum() && !object->_itype.is_nested() &&
|
|
isExportThisRun(object->_itype._cpptype)) {
|
|
int enum_count = object->_itype.number_of_enum_values();
|
|
|
|
if (object->_itype.is_scoped_enum()) {
|
|
// Convert as Python 3.4-style enum.
|
|
string class_name = object->_itype._cpptype->get_local_name(&parser);
|
|
string safe_name = make_safe_name(class_name);
|
|
|
|
CPPType *underlying_type = TypeManager::unwrap_const(object->_itype._cpptype->as_enum_type()->get_underlying_type());
|
|
string cast_to = underlying_type->get_local_name(&parser);
|
|
out << " // enum class " << object->_itype.get_scoped_name() << "\n";
|
|
out << " {\n";
|
|
out << " PyObject *members = PyTuple_New(" << enum_count << ");\n";
|
|
out << " PyObject *member;\n";
|
|
for (int xx = 0; xx < enum_count; xx++) {
|
|
out << " member = PyTuple_New(2);\n"
|
|
"#if PY_MAJOR_VERSION >= 3\n"
|
|
" PyTuple_SET_ITEM(member, 0, PyUnicode_FromString(\""
|
|
<< object->_itype.get_enum_value_name(xx) << "\"));\n"
|
|
"#else\n"
|
|
" PyTuple_SET_ITEM(member, 0, PyString_FromString(\""
|
|
<< object->_itype.get_enum_value_name(xx) << "\"));\n"
|
|
"#endif\n"
|
|
" PyTuple_SET_ITEM(member, 1, Dtool_WrapValue(("
|
|
<< cast_to << ")" << object->_itype.get_scoped_name() << "::"
|
|
<< object->_itype.get_enum_value_name(xx) << "));\n"
|
|
" PyTuple_SET_ITEM(members, " << xx << ", member);\n";
|
|
}
|
|
out << " Dtool_Ptr_" << safe_name << " = Dtool_EnumType_Create(\""
|
|
<< object->_itype.get_name() << "\", members, \""
|
|
<< _def->module_name << "\");\n";
|
|
out << " PyModule_AddObject(module, \"" << object->_itype.get_name()
|
|
<< "\", (PyObject *)Dtool_Ptr_" << safe_name << ");\n";
|
|
out << " }\n";
|
|
} else {
|
|
out << " // enum " << object->_itype.get_scoped_name() << "\n";
|
|
for (int xx = 0; xx < enum_count; xx++) {
|
|
string name1 = classNameFromCppName(object->_itype.get_enum_value_name(xx), false);
|
|
string name2 = classNameFromCppName(object->_itype.get_enum_value_name(xx), true);
|
|
string enum_value = "::" + object->_itype.get_enum_value_name(xx);
|
|
out << " PyModule_AddObject(module, \"" << name1 << "\", Dtool_WrapValue(" << enum_value << "));\n";
|
|
if (name1 != name2) {
|
|
// Also write the mangled name, for historical purposes.
|
|
out << " PyModule_AddObject(module, \"" << name2 << "\", Dtool_WrapValue(" << enum_value << "));\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
int num_manifests = idb->get_num_global_manifests();
|
|
for (int mi = 0; mi < num_manifests; mi++) {
|
|
ManifestIndex manifest_index = idb->get_global_manifest(mi);
|
|
const InterrogateManifest &iman = idb->get_manifest(manifest_index);
|
|
if (iman.has_getter()) {
|
|
FunctionIndex func_index = iman.get_getter();
|
|
record_function(dummy_type, func_index);
|
|
}
|
|
|
|
string name1 = classNameFromCppName(iman.get_name(), false);
|
|
string name2 = classNameFromCppName(iman.get_name(), true);
|
|
if (iman.has_int_value()) {
|
|
int value = iman.get_int_value();
|
|
out << " PyModule_AddIntConstant(module, \"" << name1 << "\", " << value << ");\n";
|
|
if (name1 != name2) {
|
|
// Also write the mangled name, for historical purposes.
|
|
out << " PyModule_AddIntConstant(module, \"" << name2 << "\", " << value << ");\n";
|
|
}
|
|
} else {
|
|
string value = iman.get_definition();
|
|
out << " PyModule_AddStringConstant(module, \"" << name1 << "\", \"" << value << "\");\n";
|
|
if (name1 != name2) {
|
|
out << " PyModule_AddStringConstant(module, \"" << name2 << "\", \"" << value << "\");\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
|
Object *object = (*oi).second;
|
|
if (!object->_itype.get_outer_class()) {
|
|
if (object->_itype.is_class() ||
|
|
object->_itype.is_struct() ||
|
|
object->_itype.is_typedef()) {
|
|
if (is_cpp_type_legal(object->_itype._cpptype)) {
|
|
if (isExportThisRun(object->_itype._cpptype)) {
|
|
write_sub_module(out, object);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out << "}\n\n";
|
|
|
|
bool force_base_functions = true;
|
|
|
|
out << "static PyMethodDef python_simple_funcs[] = {\n";
|
|
FunctionsByIndex::iterator fi;
|
|
for (fi = _functions.begin(); fi != _functions.end(); ++fi) {
|
|
Function *func = (*fi).second;
|
|
if (!func->_itype.is_global() && is_function_legal(func)) {
|
|
string name1 = methodNameFromCppName(func, "", false);
|
|
string name2 = methodNameFromCppName(func, "", true);
|
|
|
|
string flags;
|
|
string fptr = "&" + func->_name;
|
|
switch (func->_args_type) {
|
|
case AT_keyword_args:
|
|
flags = "METH_VARARGS | METH_KEYWORDS";
|
|
fptr = "(PyCFunction) " + fptr;
|
|
break;
|
|
|
|
case AT_varargs:
|
|
flags = "METH_VARARGS";
|
|
break;
|
|
|
|
case AT_single_arg:
|
|
flags = "METH_O";
|
|
break;
|
|
|
|
default:
|
|
flags = "METH_NOARGS";
|
|
break;
|
|
}
|
|
|
|
// Note: we shouldn't add METH_STATIC here, since both METH_STATIC and
|
|
// METH_CLASS are illegal for module-level functions.
|
|
|
|
out << " {\"" << name1 << "\", " << fptr
|
|
<< ", " << flags << ", (const char *)" << func->_name << "_comment},\n";
|
|
if (name1 != name2) {
|
|
out << " {\"" << name2 << "\", " << fptr
|
|
<< ", " << flags << ", (const char *)" << func->_name << "_comment},\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (force_base_functions) {
|
|
out << " // Support Function For Dtool_types ... for now in each module ??\n";
|
|
out << " {\"Dtool_BorrowThisReference\", &Dtool_BorrowThisReference, METH_VARARGS, \"Used to borrow 'this' pointer (to, from)\\nAssumes no ownership.\"},\n";
|
|
//out << " {\"Dtool_AddToDictionary\", &Dtool_AddToDictionary, METH_VARARGS, \"Used to add items into a tp_dict\"},\n";
|
|
}
|
|
|
|
out << " {nullptr, nullptr, 0, nullptr}\n" << "};\n\n";
|
|
|
|
if (_external_imports.empty()) {
|
|
out << "extern const struct LibraryDef " << def->library_name << "_moddef = {python_simple_funcs, exports, nullptr};\n";
|
|
} else {
|
|
out <<
|
|
"#ifdef LINK_ALL_STATIC\n"
|
|
"extern const struct LibraryDef " << def->library_name << "_moddef = {python_simple_funcs, exports, nullptr};\n"
|
|
"#else\n"
|
|
"extern const struct LibraryDef " << def->library_name << "_moddef = {python_simple_funcs, exports, imports};\n"
|
|
"#endif\n";
|
|
}
|
|
if (out_h != nullptr) {
|
|
*out_h << "extern const struct LibraryDef " << def->library_name << "_moddef;\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_module(ostream &out, ostream *out_h, InterrogateModuleDef *def) {
|
|
InterfaceMakerPython::write_module(out, out_h, def);
|
|
Objects::iterator oi;
|
|
|
|
out << "/**\n";
|
|
out << " * Module initialization functions for Python module \"" << def->module_name << "\"\n";
|
|
out << " */\n";
|
|
|
|
out << "#if PY_MAJOR_VERSION >= 3\n"
|
|
<< "static struct PyModuleDef python_native_module = {\n"
|
|
<< " PyModuleDef_HEAD_INIT,\n"
|
|
<< " \"" << def->module_name << "\",\n"
|
|
<< " nullptr,\n"
|
|
<< " -1,\n"
|
|
<< " nullptr,\n"
|
|
<< " nullptr, nullptr, nullptr, nullptr\n"
|
|
<< "};\n"
|
|
<< "\n"
|
|
<< "extern \"C\" EXPORT_CLASS PyObject *PyInit_" << def->module_name << "();\n"
|
|
<< "\n"
|
|
<< "PyObject *PyInit_" << def->module_name << "() {\n"
|
|
<< " LibraryDef *refs[] = {&" << def->library_name << "_moddef, nullptr};\n"
|
|
<< " PyObject *module = Dtool_PyModuleInitHelper(refs, &python_native_module);\n"
|
|
<< " Dtool_" << def->library_name << "_BuildInstants(module);\n"
|
|
<< " return module;\n"
|
|
<< "}\n"
|
|
<< "\n"
|
|
<< "#else // Python 2 case\n"
|
|
<< "\n"
|
|
<< "extern \"C\" EXPORT_CLASS void init" << def->module_name << "();\n"
|
|
<< "\n"
|
|
<< "void init" << def->module_name << "() {\n"
|
|
<< " LibraryDef *refs[] = {&" << def->library_name << "_moddef, nullptr};\n"
|
|
<< " PyObject *module = Dtool_PyModuleInitHelper(refs, \"" << def->module_name << "\");\n"
|
|
<< " Dtool_" << def->library_name << "_BuildInstants(module);\n"
|
|
<< "}\n"
|
|
<< "\n"
|
|
<< "#endif\n"
|
|
<< "\n";
|
|
}
|
|
/**
|
|
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_module_class(ostream &out, Object *obj) {
|
|
bool has_local_repr = false;
|
|
bool has_local_str = false;
|
|
bool has_local_richcompare = false;
|
|
bool has_local_getbuffer = false;
|
|
|
|
{
|
|
int num_nested = obj->_itype.number_of_nested_types();
|
|
for (int ni = 0; ni < num_nested; ni++) {
|
|
TypeIndex nested_index = obj->_itype.get_nested_type(ni);
|
|
if (_objects.count(nested_index) == 0) {
|
|
// Illegal type.
|
|
continue;
|
|
}
|
|
|
|
Object *nested_obj = _objects[nested_index];
|
|
assert(nested_obj != nullptr);
|
|
|
|
if (nested_obj->_itype.is_class() || nested_obj->_itype.is_struct()) {
|
|
write_module_class(out, nested_obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
std::string ClassName = make_safe_name(obj->_itype.get_scoped_name());
|
|
std::string cClassName = obj->_itype.get_true_name();
|
|
std::string export_class_name = classNameFromCppName(obj->_itype.get_name(), false);
|
|
|
|
bool is_runtime_typed = IsPandaTypedObject(obj->_itype._cpptype->as_struct_type());
|
|
if (!is_runtime_typed && has_get_class_type_function(obj->_itype._cpptype)) {
|
|
is_runtime_typed = true;
|
|
}
|
|
|
|
out << "/**\n";
|
|
out << " * Python method tables for " << ClassName << " (" << export_class_name << ")\n" ;
|
|
out << " */\n";
|
|
out << "static PyMethodDef Dtool_Methods_" << ClassName << "[] = {\n";
|
|
|
|
SlottedFunctions slots;
|
|
// function Table
|
|
bool got_copy = false;
|
|
bool got_deepcopy = false;
|
|
|
|
for (Function *func : obj->_methods) {
|
|
if (func->_name == "__copy__") {
|
|
got_copy = true;
|
|
} else if (func->_name == "__deepcopy__") {
|
|
got_deepcopy = true;
|
|
}
|
|
|
|
string name1 = methodNameFromCppName(func, export_class_name, false);
|
|
string name2 = methodNameFromCppName(func, export_class_name, true);
|
|
|
|
string flags;
|
|
string fptr = "&" + func->_name;
|
|
switch (func->_args_type) {
|
|
case AT_keyword_args:
|
|
flags = "METH_VARARGS | METH_KEYWORDS";
|
|
fptr = "(PyCFunction) " + fptr;
|
|
break;
|
|
|
|
case AT_varargs:
|
|
flags = "METH_VARARGS";
|
|
break;
|
|
|
|
case AT_single_arg:
|
|
flags = "METH_O";
|
|
break;
|
|
|
|
default:
|
|
flags = "METH_NOARGS";
|
|
break;
|
|
}
|
|
|
|
if (!func->_has_this) {
|
|
flags += " | METH_STATIC";
|
|
|
|
// Skip adding this entry if we also have a property with the same name.
|
|
// In that case, we will use a Dtool_StaticProperty to disambiguate
|
|
// access to this method. See GitHub issue #444.
|
|
for (const Property *property : obj->_properties) {
|
|
if (property->_has_this &&
|
|
property->_ielement.get_name() == func->_ifunc.get_name()) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool has_nonslotted = false;
|
|
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
if (!is_remap_legal(remap)) {
|
|
continue;
|
|
}
|
|
|
|
SlottedFunctionDef slotted_def;
|
|
|
|
if (get_slotted_function_def(obj, func, remap, slotted_def)) {
|
|
const string &key = slotted_def._answer_location;
|
|
if (slotted_def._wrapper_type == WT_none) {
|
|
slotted_def._wrapper_name = func->_name;
|
|
} else {
|
|
slotted_def._wrapper_name = func->_name + "_" + key;
|
|
}
|
|
if (slots.count(key)) {
|
|
slots[key]._remaps.insert(remap);
|
|
} else {
|
|
slots[key] = slotted_def;
|
|
slots[key]._remaps.insert(remap);
|
|
}
|
|
if (slotted_def._keep_method) {
|
|
has_nonslotted = true;
|
|
}
|
|
|
|
// Python 3 doesn't support nb_divide. It has nb_true_divide and also
|
|
// nb_floor_divide, but they have different semantics than in C++.
|
|
// Ugh. Make special slots to store the nb_divide members that take a
|
|
// float. We'll use this to build up nb_true_divide, so that we can
|
|
// still properly divide float vector types.
|
|
if (remap->_flags & FunctionRemap::F_divide_float) {
|
|
string true_key;
|
|
if (key == "nb_inplace_divide") {
|
|
true_key = "nb_inplace_true_divide";
|
|
} else {
|
|
true_key = "nb_true_divide";
|
|
}
|
|
if (slots.count(true_key) == 0) {
|
|
SlottedFunctionDef def;
|
|
def._answer_location = true_key;
|
|
def._wrapper_type = slotted_def._wrapper_type;
|
|
def._min_version = 0x03000000;
|
|
def._wrapper_name = func->_name + "_" + true_key;
|
|
slots[true_key] = def;
|
|
}
|
|
slots[true_key]._remaps.insert(remap);
|
|
}
|
|
} else {
|
|
has_nonslotted = true;
|
|
}
|
|
}
|
|
|
|
if (has_nonslotted) {
|
|
// This is a bit of a hack, as these methods should probably be going
|
|
// through the slotted function system. But it's kind of pointless to
|
|
// write these out, and a waste of space.
|
|
string fname = func->_ifunc.get_name();
|
|
if (fname == "operator <" ||
|
|
fname == "operator <=" ||
|
|
fname == "operator ==" ||
|
|
fname == "operator !=" ||
|
|
fname == "operator >" ||
|
|
fname == "operator >=") {
|
|
continue;
|
|
}
|
|
|
|
// This method has non-slotted remaps, so write it out into the function
|
|
// table.
|
|
out << " {\"" << name1 << "\", " << fptr
|
|
<< ", " << flags << ", (const char *)" << func->_name << "_comment},\n";
|
|
if (name1 != name2) {
|
|
out << " {\"" << name2 << "\", " << fptr
|
|
<< ", " << flags << ", (const char *)" << func->_name << "_comment},\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj->_protocol_types & Object::PT_make_copy) {
|
|
if (!got_copy) {
|
|
out << " {\"__copy__\", ©_from_make_copy, METH_NOARGS, nullptr},\n";
|
|
got_copy = true;
|
|
}
|
|
} else if (obj->_protocol_types & Object::PT_copy_constructor) {
|
|
if (!got_copy) {
|
|
out << " {\"__copy__\", ©_from_copy_constructor, METH_NOARGS, nullptr},\n";
|
|
got_copy = true;
|
|
}
|
|
}
|
|
|
|
if (got_copy && !got_deepcopy) {
|
|
out << " {\"__deepcopy__\", &map_deepcopy_to_copy, METH_VARARGS, nullptr},\n";
|
|
}
|
|
|
|
for (MakeSeq *make_seq : obj->_make_seqs) {
|
|
if (!is_function_legal(make_seq->_length_getter) ||
|
|
!is_function_legal(make_seq->_element_getter)) {
|
|
continue;
|
|
}
|
|
|
|
string seq_name = make_seq->_imake_seq.get_name();
|
|
|
|
string flags = "METH_NOARGS";
|
|
if (!make_seq->_length_getter->_has_this &&
|
|
!make_seq->_element_getter->_has_this) {
|
|
flags += " | METH_STATIC";
|
|
}
|
|
|
|
string name1 = methodNameFromCppName(seq_name, export_class_name, false);
|
|
string name2 = methodNameFromCppName(seq_name, export_class_name, true);
|
|
out << " {\"" << name1
|
|
<< "\", (PyCFunction) &" << make_seq->_name << ", " << flags << ", nullptr},\n";
|
|
if (name1 != name2) {
|
|
out << " { \"" << name2
|
|
<< "\", (PyCFunction) &" << make_seq->_name << ", " << flags << ", nullptr},\n";
|
|
}
|
|
}
|
|
|
|
out << " {nullptr, nullptr, 0, nullptr}\n"
|
|
<< "};\n\n";
|
|
|
|
int num_derivations = obj->_itype.number_of_derivations();
|
|
int di;
|
|
for (di = 0; di < num_derivations; di++) {
|
|
TypeIndex d_type_Index = obj->_itype.get_derivation(di);
|
|
if (!interrogate_type_is_unpublished(d_type_Index)) {
|
|
const InterrogateType &d_itype = idb->get_type(d_type_Index);
|
|
if (is_cpp_type_legal(d_itype._cpptype)) {
|
|
if (!isExportThisRun(d_itype._cpptype)) {
|
|
_external_imports.insert(TypeManager::resolve_type(d_itype._cpptype));
|
|
|
|
// out << "IMPORT_THIS struct Dtool_PyTypedObject Dtool_" <<
|
|
// make_safe_name(d_itype.get_scoped_name().c_str()) << ";\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<CPPType*> bases;
|
|
for (di = 0; di < num_derivations; di++) {
|
|
TypeIndex d_type_Index = obj->_itype.get_derivation(di);
|
|
if (!interrogate_type_is_unpublished(d_type_Index)) {
|
|
const InterrogateType &d_itype = idb->get_type(d_type_Index);
|
|
if (is_cpp_type_legal(d_itype._cpptype)) {
|
|
bases.push_back(d_itype._cpptype);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
SlottedFunctions::iterator rfi;
|
|
for (rfi = slots.begin(); rfi != slots.end(); rfi++) {
|
|
const SlottedFunctionDef &def = rfi->second;
|
|
|
|
// This is just for reporting. There might be remaps from multiple
|
|
// functions with different names mapped to the same slot.
|
|
string fname;
|
|
if (def._remaps.size() > 0) {
|
|
const FunctionRemap *first_remap = *def._remaps.begin();
|
|
fname = first_remap->_cppfunc->get_simple_name();
|
|
}
|
|
|
|
if (def._min_version > 0) {
|
|
out << "#if PY_VERSION_HEX >= 0x" << hex << def._min_version << dec << "\n";
|
|
}
|
|
|
|
switch (rfi->second._wrapper_type) {
|
|
case WT_no_params:
|
|
case WT_iter_next:
|
|
// PyObject *func(PyObject *self)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *" << def._wrapper_name << "(PyObject *self) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
|
|
int return_flags = RF_pyobject | RF_err_null;
|
|
if (rfi->second._wrapper_type == WT_iter_next) {
|
|
// If the function returns NULL, we should return NULL to indicate
|
|
// a StopIteration, rather than returning None.
|
|
return_flags |= RF_preserve_null;
|
|
}
|
|
string expected_params;
|
|
write_function_forset(out, def._remaps, 0, 0, expected_params, 2, true, true,
|
|
AT_no_args, return_flags, false);
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " return Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return nullptr;\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_one_param:
|
|
case WT_binary_operator:
|
|
case WT_inplace_binary_operator:
|
|
// PyObject *func(PyObject *self, PyObject *one)
|
|
{
|
|
int return_flags = RF_err_null;
|
|
if (rfi->second._wrapper_type == WT_inplace_binary_operator) {
|
|
return_flags |= RF_self;
|
|
} else {
|
|
return_flags |= RF_pyobject;
|
|
}
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *" << def._wrapper_name << "(PyObject *self, PyObject *arg) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
if (rfi->second._wrapper_type != WT_one_param) {
|
|
// WT_binary_operator means we must return NotImplemented, instead
|
|
// of raising an exception, if the this pointer doesn't match.
|
|
// This is for things like __sub__, which Python likes to call on
|
|
// the wrong-type objects.
|
|
out << " DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
|
|
out << " if (local_this == nullptr) {\n";
|
|
out << " Py_INCREF(Py_NotImplemented);\n";
|
|
out << " return Py_NotImplemented;\n";
|
|
} else {
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return nullptr;\n";
|
|
}
|
|
out << " }\n";
|
|
|
|
string expected_params;
|
|
write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
|
|
AT_single_arg, return_flags, false);
|
|
|
|
if (rfi->second._wrapper_type != WT_one_param) {
|
|
out << " Py_INCREF(Py_NotImplemented);\n";
|
|
out << " return Py_NotImplemented;\n";
|
|
} else {
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " return Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return nullptr;\n";
|
|
}
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_setattr:
|
|
// int func(PyObject *self, PyObject *one, PyObject *two = NULL)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static int " << def._wrapper_name << "(PyObject *self, PyObject *arg, PyObject *arg2) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
|
|
set<FunctionRemap*> setattr_remaps;
|
|
set<FunctionRemap*> delattr_remaps;
|
|
|
|
// This function handles both delattr and setattr. Fish out the
|
|
// remaps for both types.
|
|
for (FunctionRemap *remap : def._remaps) {
|
|
if (remap->_cppfunc->get_simple_name() == "__delattr__" && remap->_parameters.size() == 2) {
|
|
delattr_remaps.insert(remap);
|
|
|
|
} else if (remap->_cppfunc->get_simple_name() == "__setattr__" && remap->_parameters.size() == 3) {
|
|
setattr_remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
out << " // Determine whether to call __setattr__ or __delattr__.\n";
|
|
out << " if (arg2 != nullptr) { // __setattr__\n";
|
|
|
|
if (!setattr_remaps.empty()) {
|
|
out << " PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
|
|
string expected_params;
|
|
write_function_forset(out, setattr_remaps, 2, 2, expected_params, 4,
|
|
true, true, AT_varargs, RF_int | RF_decref_args, true);
|
|
|
|
out << " Py_DECREF(args);\n";
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 8, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
} else {
|
|
out << " PyErr_Format(PyExc_TypeError,\n";
|
|
out << " \"can't set attributes of built-in/extension type '%s'\",\n";
|
|
out << " Py_TYPE(self)->tp_name);\n";
|
|
}
|
|
out << " return -1;\n\n";
|
|
|
|
out << " } else { // __delattr__\n";
|
|
|
|
if (!delattr_remaps.empty()) {
|
|
string expected_params;
|
|
write_function_forset(out, delattr_remaps, 1, 1, expected_params, 4,
|
|
true, true, AT_single_arg, RF_int, true);
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 8, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
} else {
|
|
out << " PyErr_Format(PyExc_TypeError,\n";
|
|
out << " \"can't delete attributes of built-in/extension type '%s'\",\n";
|
|
out << " Py_TYPE(self)->tp_name);\n";
|
|
}
|
|
out << " return -1;\n";
|
|
out << " }\n";
|
|
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_getattr:
|
|
// PyObject *func(PyObject *self, PyObject *one) Specifically to
|
|
// implement __getattr__. First calls PyObject_GenericGetAttr(), and
|
|
// only calls the wrapper if it returns NULL. If one wants to override
|
|
// this completely, one should define __getattribute__ instead.
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *" << def._wrapper_name << "(PyObject *self, PyObject *arg) {\n";
|
|
out << " PyObject *res = PyObject_GenericGetAttr(self, arg);\n";
|
|
out << " if (res != nullptr) {\n";
|
|
out << " return res;\n";
|
|
out << " }\n";
|
|
out << " if (_PyErr_OCCURRED() != PyExc_AttributeError) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n";
|
|
out << " PyErr_Clear();\n\n";
|
|
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
|
|
string expected_params;
|
|
write_function_forset(out, def._remaps, 1, 1, expected_params, 2,
|
|
true, true, AT_single_arg,
|
|
RF_pyobject | RF_err_null, true);
|
|
|
|
// out << " PyErr_Clear();\n";
|
|
out << " return nullptr;\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_sequence_getitem:
|
|
// PyObject *func(PyObject *self, Py_ssize_t index)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *" << def._wrapper_name << "(PyObject *self, Py_ssize_t index) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
|
|
// This is a getitem or setitem of a sequence type. This means we
|
|
// *need* to raise IndexError if we're out of bounds. We have to
|
|
// assume the bounds are 0 .. this->size() (this is the same
|
|
// assumption that Python makes).
|
|
out << " if (index < 0 || index >= (Py_ssize_t) local_this->size()) {\n";
|
|
out << " PyErr_SetString(PyExc_IndexError, \"" << ClassName << " index out of range\");\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n";
|
|
|
|
string expected_params;
|
|
write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
|
|
AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " return Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return nullptr;\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_sequence_setitem:
|
|
// int_t func(PyObject *self, Py_ssize_t index, PyObject *value)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static int " << def._wrapper_name << "(PyObject *self, Py_ssize_t index, PyObject *arg) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
|
|
out << " if (index < 0 || index >= (Py_ssize_t) local_this->size()) {\n";
|
|
out << " PyErr_SetString(PyExc_IndexError, \"" << ClassName << " index out of range\");\n";
|
|
out << " return -1;\n";
|
|
out << " }\n";
|
|
|
|
set<FunctionRemap*> setitem_remaps;
|
|
set<FunctionRemap*> delitem_remaps;
|
|
|
|
// This function handles both delitem and setitem. Fish out the
|
|
// remaps for either one.
|
|
for (FunctionRemap *remap : def._remaps) {
|
|
if (remap->_flags & FunctionRemap::F_setitem_int) {
|
|
setitem_remaps.insert(remap);
|
|
|
|
} else if (remap->_flags & FunctionRemap::F_delitem_int) {
|
|
delitem_remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
out << " if (arg != nullptr) { // __setitem__\n";
|
|
write_function_forset(out, setitem_remaps, 2, 2, expected_params, 4,
|
|
true, true, AT_single_arg, RF_int, false, true, "index");
|
|
out << " } else { // __delitem__\n";
|
|
write_function_forset(out, delitem_remaps, 1, 1, expected_params, 4,
|
|
true, true, AT_single_arg, RF_int, false, true, "index");
|
|
out << " }\n\n";
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return -1;\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_sequence_size:
|
|
// Py_ssize_t func(PyObject *self)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static Py_ssize_t " << def._wrapper_name << "(PyObject *self) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
|
|
// This is a cheap cheat around all of the overhead of calling the
|
|
// wrapper function.
|
|
out << " return (Py_ssize_t) local_this->" << fname << "();\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_mapping_setitem:
|
|
// int func(PyObject *self, PyObject *one, PyObject *two)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static int " << def._wrapper_name << "(PyObject *self, PyObject *arg, PyObject *arg2) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
|
|
set<FunctionRemap*> setitem_remaps;
|
|
set<FunctionRemap*> delitem_remaps;
|
|
|
|
// This function handles both delitem and setitem. Fish out the
|
|
// remaps for either one.
|
|
for (FunctionRemap *remap : def._remaps) {
|
|
if (remap->_flags & FunctionRemap::F_setitem) {
|
|
setitem_remaps.insert(remap);
|
|
|
|
} else if (remap->_flags & FunctionRemap::F_delitem) {
|
|
delitem_remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
out << " if (arg2 != nullptr) { // __setitem__\n";
|
|
out << " PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
|
|
write_function_forset(out, setitem_remaps, 2, 2, expected_params, 4,
|
|
true, true, AT_varargs, RF_int | RF_decref_args, false);
|
|
out << " Py_DECREF(args);\n";
|
|
out << " } else { // __delitem__\n";
|
|
write_function_forset(out, delitem_remaps, 1, 1, expected_params, 4,
|
|
true, true, AT_single_arg, RF_int, false);
|
|
out << " }\n\n";
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return -1;\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_inquiry:
|
|
// int func(PyObject *self)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static int " << def._wrapper_name << "(PyObject *self) {\n";
|
|
|
|
// Find the remap. There should be only one.
|
|
FunctionRemap *remap = *def._remaps.begin();
|
|
const char *container = "";
|
|
|
|
if (remap->_has_this) {
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
container = "local_this";
|
|
}
|
|
|
|
vector_string params;
|
|
out << " return (int) " << remap->call_function(out, 4, false, container, params) << ";\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_getbuffer:
|
|
// int __getbuffer__(PyObject *self, Py_buffer *buffer, int flags) We
|
|
// map this directly, and assume that the arguments match. The whole
|
|
// point of this is to be fast, and we don't want to negate that by
|
|
// first wrapping and then unwrapping the arguments again. We also
|
|
// want to guarantee const correctness, since that will determine
|
|
// whether a read-only buffer is given.
|
|
{
|
|
has_local_getbuffer = true;
|
|
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static int " << def._wrapper_name << "(PyObject *self, Py_buffer *buffer, int flags) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
|
|
vector_string params_const(1);
|
|
vector_string params_nonconst(1);
|
|
FunctionRemap *remap_const = nullptr;
|
|
FunctionRemap *remap_nonconst = nullptr;
|
|
|
|
// Iterate through the remaps to find the one that matches our
|
|
// parameters.
|
|
for (FunctionRemap *remap : def._remaps) {
|
|
if (remap->_const_method) {
|
|
if ((remap->_flags & FunctionRemap::F_explicit_self) == 0) {
|
|
params_const.push_back("self");
|
|
}
|
|
remap_const = remap;
|
|
} else {
|
|
if ((remap->_flags & FunctionRemap::F_explicit_self) == 0) {
|
|
params_nonconst.push_back("self");
|
|
}
|
|
remap_nonconst = remap;
|
|
}
|
|
}
|
|
params_const.push_back("buffer");
|
|
params_const.push_back("flags");
|
|
params_nonconst.push_back("buffer");
|
|
params_nonconst.push_back("flags");
|
|
|
|
// We have to distinguish properly between const and nonconst,
|
|
// because the function may depend on it to decide whether to
|
|
// provide a writable buffer or a readonly buffer.
|
|
const string const_this = "(const " + cClassName + " *)local_this";
|
|
if (remap_const != nullptr && remap_nonconst != nullptr) {
|
|
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
out << " return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
|
|
out << " } else {\n";
|
|
out << " return " << remap_const->call_function(out, 4, false, const_this, params_const) << ";\n";
|
|
out << " }\n";
|
|
} else if (remap_nonconst != nullptr) {
|
|
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
out << " return " << remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst) << ";\n";
|
|
out << " } else {\n";
|
|
out << " Dtool_Raise_TypeError(\"Cannot call " << ClassName << ".__getbuffer__() on a const object.\");\n";
|
|
out << " return -1;\n";
|
|
out << " }\n";
|
|
} else if (remap_const != nullptr) {
|
|
out << " return " << remap_const->call_function(out, 4, false, const_this, params_const) << ";\n";
|
|
} else {
|
|
nout << ClassName << "::__getbuffer__ does not match the required signature.\n";
|
|
out << " return -1;\n";
|
|
}
|
|
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_releasebuffer:
|
|
// void __releasebuffer__(PyObject *self, Py_buffer *buffer) Same
|
|
// story as __getbuffer__ above.
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static void " << def._wrapper_name << "(PyObject *self, Py_buffer *buffer) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return;\n";
|
|
out << " }\n\n";
|
|
|
|
vector_string params_const(1);
|
|
vector_string params_nonconst(1);
|
|
FunctionRemap *remap_const = nullptr;
|
|
FunctionRemap *remap_nonconst = nullptr;
|
|
|
|
// Iterate through the remaps to find the one that matches our
|
|
// parameters.
|
|
for (FunctionRemap *remap : def._remaps) {
|
|
if (remap->_const_method) {
|
|
if ((remap->_flags & FunctionRemap::F_explicit_self) == 0) {
|
|
params_const.push_back("self");
|
|
}
|
|
remap_const = remap;
|
|
} else {
|
|
if ((remap->_flags & FunctionRemap::F_explicit_self) == 0) {
|
|
params_nonconst.push_back("self");
|
|
}
|
|
remap_nonconst = remap;
|
|
}
|
|
}
|
|
params_const.push_back("buffer");
|
|
params_nonconst.push_back("buffer");
|
|
|
|
string return_expr;
|
|
const string const_this = "(const " + cClassName + " *)local_this";
|
|
if (remap_const != nullptr && remap_nonconst != nullptr) {
|
|
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
return_expr = remap_nonconst->call_function(out, 4, false, "local_this", params_nonconst);
|
|
if (!return_expr.empty()) {
|
|
out << " " << return_expr << ";\n";
|
|
}
|
|
out << " } else {\n";
|
|
return_expr = remap_const->call_function(out, 4, false, const_this, params_const);
|
|
|
|
if (!return_expr.empty()) {
|
|
out << " " << return_expr << ";\n";
|
|
}
|
|
out << " }\n";
|
|
|
|
} else if (remap_nonconst != nullptr) {
|
|
// Doesn't matter if there's no const version. We *have* to call
|
|
// it or else we could leak memory.
|
|
return_expr = remap_nonconst->call_function(out, 2, false, "local_this", params_nonconst);
|
|
if (!return_expr.empty()) {
|
|
out << " " << return_expr << ";\n";
|
|
}
|
|
|
|
} else if (remap_const != nullptr) {
|
|
return_expr = remap_const->call_function(out, 2, false, const_this, params_const);
|
|
if (!return_expr.empty()) {
|
|
out << " " << return_expr << ";\n";
|
|
}
|
|
|
|
} else {
|
|
nout << ClassName << "::__releasebuffer__ does not match the required signature.\n";
|
|
out << " return;\n";
|
|
}
|
|
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_ternary_operator:
|
|
case WT_inplace_ternary_operator:
|
|
// PyObject *func(PyObject *self, PyObject *one, PyObject *two)
|
|
{
|
|
int return_flags = RF_err_null;
|
|
if (rfi->second._wrapper_type == WT_inplace_ternary_operator) {
|
|
return_flags |= RF_self;
|
|
} else {
|
|
return_flags |= RF_pyobject;
|
|
}
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *" << def._wrapper_name << "(PyObject *self, PyObject *arg, PyObject *arg2) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **)&local_this);\n";
|
|
out << " if (local_this == nullptr) {\n";
|
|
// WT_ternary_operator means we must return NotImplemented, instead
|
|
// of raising an exception, if the this pointer doesn't match. This
|
|
// is for things like __pow__, which Python likes to call on the
|
|
// wrong-type objects.
|
|
out << " Py_INCREF(Py_NotImplemented);\n";
|
|
out << " return Py_NotImplemented;\n";
|
|
out << " }\n";
|
|
|
|
set<FunctionRemap*> one_param_remaps;
|
|
set<FunctionRemap*> two_param_remaps;
|
|
|
|
for (FunctionRemap *remap : def._remaps) {
|
|
if (remap->_parameters.size() == 2) {
|
|
one_param_remaps.insert(remap);
|
|
|
|
} else if (remap->_parameters.size() == 3) {
|
|
two_param_remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
|
|
out << " if (arg2 != nullptr && arg2 != Py_None) {\n";
|
|
out << " PyObject *args = PyTuple_Pack(2, arg, arg2);\n";
|
|
write_function_forset(out, two_param_remaps, 2, 2, expected_params, 4,
|
|
true, true, AT_varargs, RF_pyobject | RF_err_null | RF_decref_args, true);
|
|
out << " Py_DECREF(args);\n";
|
|
out << " } else {\n";
|
|
write_function_forset(out, one_param_remaps, 1, 1, expected_params, 4,
|
|
true, true, AT_single_arg, RF_pyobject | RF_err_null, true);
|
|
out << " }\n\n";
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " return Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return nullptr;\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_traverse:
|
|
// int __traverse__(PyObject *self, visitproc visit, void *arg) This
|
|
// is a low-level function. Overloads are not supported.
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static int " << def._wrapper_name << "(PyObject *self, visitproc visit, void *arg) {\n";
|
|
|
|
// Find the remap. There should be only one.
|
|
FunctionRemap *remap = *def._remaps.begin();
|
|
const char *container = "";
|
|
|
|
if (remap->_has_this) {
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " DTOOL_Call_ExtractThisPointerForType(self, &Dtool_" << ClassName << ", (void **) &local_this);\n";
|
|
out << " if (local_this == nullptr) {\n";
|
|
out << " return 0;\n";
|
|
out << " }\n\n";
|
|
container = "local_this";
|
|
}
|
|
|
|
vector_string params((int)remap->_has_this);
|
|
params.push_back("visit");
|
|
params.push_back("arg");
|
|
|
|
out << " return " << remap->call_function(out, 2, false, container, params) << ";\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_compare:
|
|
// int func(PyObject *self, Py_ssize_t index)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static int " << def._wrapper_name << "(PyObject *self, PyObject *arg) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
|
|
string expected_params;
|
|
write_function_forset(out, def._remaps, 1, 1, expected_params, 2, true, true,
|
|
AT_single_arg, RF_compare, false, true);
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return -1;\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_hash:
|
|
// Py_hash_t func(PyObject *self)
|
|
{
|
|
out << "//////////////////\n";
|
|
out << "// A wrapper function to satisfy Python's internal calling conventions.\n";
|
|
out << "// " << ClassName << " slot " << rfi->second._answer_location << " -> " << fname << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static Py_hash_t " << def._wrapper_name << "(PyObject *self) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
|
|
FunctionRemap *remap = *def._remaps.begin();
|
|
vector_string params;
|
|
out << " return (Py_hash_t) " << remap->call_function(out, 4, false, "local_this", params) << ";\n";
|
|
out << "}\n\n";
|
|
}
|
|
break;
|
|
|
|
case WT_none:
|
|
// Nothing special about the wrapper function: just write it normally.
|
|
string fname = "static PyObject *" + def._wrapper_name + "(PyObject *self, PyObject *args, PyObject *kwds)\n";
|
|
|
|
std::vector<FunctionRemap *> remaps;
|
|
remaps.insert(remaps.end(), def._remaps.begin(), def._remaps.end());
|
|
string expected_params;
|
|
write_function_for_name(out, obj, remaps, fname, expected_params, true, AT_keyword_args, RF_pyobject | RF_err_null);
|
|
break;
|
|
}
|
|
|
|
if (def._min_version > 0) {
|
|
out << "#endif // PY_VERSION_HEX >= 0x" << hex << def._min_version << dec << "\n";
|
|
}
|
|
}
|
|
|
|
int need_repr = 0;
|
|
if (slots.count("tp_repr") == 0) {
|
|
need_repr = NeedsAReprFunction(obj->_itype);
|
|
}
|
|
if (need_repr > 0) {
|
|
out << "//////////////////\n";
|
|
out << "// A __repr__ function\n";
|
|
out << "// " << ClassName << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *Dtool_Repr_" << ClassName << "(PyObject *self) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
out << " std::ostringstream os;\n";
|
|
if (need_repr == 3) {
|
|
out << " invoke_extension(local_this).python_repr(os, \""
|
|
<< classNameFromCppName(ClassName, false) << "\");\n";
|
|
} else if (need_repr == 2) {
|
|
out << " local_this->output(os);\n";
|
|
} else {
|
|
out << " local_this->python_repr(os, \""
|
|
<< classNameFromCppName(ClassName, false) << "\");\n";
|
|
}
|
|
out << " std::string ss = os.str();\n";
|
|
out << " return Dtool_WrapValue(ss);\n";
|
|
out << "}\n\n";
|
|
has_local_repr = true;
|
|
}
|
|
|
|
int need_str = 0;
|
|
if (slots.count("tp_str") == 0) {
|
|
need_str = NeedsAStrFunction(obj->_itype);
|
|
}
|
|
if (need_str > 0) {
|
|
out << "//////////////////\n";
|
|
out << "// A __str__ function\n";
|
|
out << "// " << ClassName << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *Dtool_Str_" << ClassName << "(PyObject *self) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
out << " std::ostringstream os;\n";
|
|
if (need_str == 2) {
|
|
out << " local_this->write(os, 0);\n";
|
|
} else {
|
|
out << " local_this->write(os);\n";
|
|
}
|
|
out << " std::string ss = os.str();\n";
|
|
out << " return Dtool_WrapValue(ss);\n";
|
|
out << "}\n\n";
|
|
has_local_str = true;
|
|
}
|
|
}
|
|
|
|
if (NeedsARichCompareFunction(obj->_itype) || slots.count("tp_compare")) {
|
|
out << "//////////////////\n";
|
|
out << "// A rich comparison function\n";
|
|
out << "// " << ClassName << "\n";
|
|
out << "//////////////////\n";
|
|
out << "static PyObject *Dtool_RichCompare_" << ClassName << "(PyObject *self, PyObject *arg, int op) {\n";
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
|
|
for (Function *func : obj->_methods) {
|
|
std::set<FunctionRemap*> remaps;
|
|
if (!func) {
|
|
continue;
|
|
}
|
|
// We only accept comparison operators that take one parameter (besides
|
|
// 'this').
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
if (is_remap_legal(remap) && remap->_has_this && (remap->_args_type == AT_single_arg)) {
|
|
remaps.insert(remap);
|
|
}
|
|
}
|
|
const string &fname = func->_ifunc.get_name();
|
|
const char *op_type;
|
|
if (fname == "operator <") {
|
|
op_type = "Py_LT";
|
|
} else if (fname == "operator <=") {
|
|
op_type = "Py_LE";
|
|
} else if (fname == "operator ==") {
|
|
op_type = "Py_EQ";
|
|
} else if (fname == "operator !=") {
|
|
op_type = "Py_NE";
|
|
} else if (fname == "operator >") {
|
|
op_type = "Py_GT";
|
|
} else if (fname == "operator >=") {
|
|
op_type = "Py_GE";
|
|
} else {
|
|
continue;
|
|
}
|
|
if (!has_local_richcompare) {
|
|
out << " switch (op) {\n";
|
|
has_local_richcompare = true;
|
|
}
|
|
out << " case " << op_type << ":\n";
|
|
|
|
out << " {\n";
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1, expected_params, 6, true, false,
|
|
AT_single_arg, RF_pyobject | RF_err_null, false);
|
|
|
|
out << " break;\n";
|
|
out << " }\n";
|
|
}
|
|
|
|
if (has_local_richcompare) {
|
|
// End of switch block
|
|
out << " }\n\n";
|
|
out << " if (_PyErr_OCCURRED()) {\n";
|
|
out << " PyErr_Clear();\n";
|
|
out << " }\n\n";
|
|
}
|
|
|
|
if (slots.count("tp_compare")) {
|
|
// A lot of Panda code depends on comparisons being done via the
|
|
// compare_to function, which is mapped to the tp_compare slot, which
|
|
// Python 3 no longer has. So, we'll write code to fall back to that if
|
|
// no matching comparison operator was found.
|
|
out << " // All is not lost; we still have the compare_to function to fall back onto.\n";
|
|
out << " int cmpval = " << slots["tp_compare"]._wrapper_name << "(self, arg);\n";
|
|
out << " if (cmpval == -1 && _PyErr_OCCURRED()) {\n";
|
|
out << " if (PyErr_ExceptionMatches(PyExc_TypeError)) {\n";
|
|
out << " PyErr_Clear();\n";
|
|
out << " } else {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n";
|
|
out << " }\n";
|
|
out << " switch (op) {\n";
|
|
out << " case Py_LT:\n";
|
|
out << " return PyBool_FromLong(cmpval < 0);\n";
|
|
out << " case Py_LE:\n";
|
|
out << " return PyBool_FromLong(cmpval <= 0);\n";
|
|
out << " case Py_EQ:\n";
|
|
out << " return PyBool_FromLong(cmpval == 0);\n";
|
|
out << " case Py_NE:\n";
|
|
out << " return PyBool_FromLong(cmpval != 0);\n";
|
|
out << " case Py_GT:\n";
|
|
out << " return PyBool_FromLong(cmpval > 0);\n";
|
|
out << " case Py_GE:\n";
|
|
out << " return PyBool_FromLong(cmpval >= 0);\n";
|
|
out << " }\n";
|
|
has_local_richcompare = true;
|
|
}
|
|
|
|
out << " Py_INCREF(Py_NotImplemented);\n";
|
|
out << " return Py_NotImplemented;\n";
|
|
out << "}\n\n";
|
|
}
|
|
|
|
int num_getset = 0;
|
|
|
|
if (obj->_properties.size() > 0) {
|
|
// Write out the array of properties, telling Python which getter and
|
|
// setter to call when they are assigned or queried in Python code.
|
|
for (Property *property : obj->_properties) {
|
|
const InterrogateElement &ielem = property->_ielement;
|
|
if (!property->_has_this || property->_getter_remaps.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// Actually, if we have a conflicting static method with the same name,
|
|
// we will need to use Dtool_StaticProperty instead.
|
|
for (const Function *func : obj->_methods) {
|
|
if (!func->_has_this && func->_ifunc.get_name() == ielem.get_name()) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (num_getset == 0) {
|
|
out << "static PyGetSetDef Dtool_Properties_" << ClassName << "[] = {\n";
|
|
}
|
|
|
|
++num_getset;
|
|
|
|
string name1 = methodNameFromCppName(ielem.get_name(), "", false);
|
|
// string name2 = methodNameFromCppName(ielem.get_name(), "", true);
|
|
|
|
string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
|
|
string setter = "nullptr";
|
|
if (!ielem.is_sequence() && !ielem.is_mapping() && !property->_setter_remaps.empty()) {
|
|
setter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter";
|
|
}
|
|
|
|
out << " {(char *)\"" << name1 << "\", " << getter << ", " << setter;
|
|
|
|
if (ielem.has_comment()) {
|
|
out << ", (char *)\n";
|
|
output_quoted(out, 4, ielem.get_comment());
|
|
out << ",\n ";
|
|
} else {
|
|
out << ", nullptr, ";
|
|
}
|
|
|
|
// Extra void* argument; we don't make use of it.
|
|
out << "nullptr},\n";
|
|
|
|
/*if (name1 != name2 && name1 != "__dict__") {
|
|
// Add alternative spelling.
|
|
out << " {(char *)\"" << name2 << "\", " << getter << ", " << setter
|
|
<< ", (char *)\n"
|
|
<< " \"Alias of " << name1 << ", for consistency with old naming conventions.\",\n"
|
|
<< " NULL},\n";
|
|
}*/
|
|
}
|
|
|
|
if (num_getset != 0) {
|
|
out << " {nullptr},\n";
|
|
out << "};\n\n";
|
|
}
|
|
}
|
|
|
|
// These fields are inherited together. We should either write all of them
|
|
// or none of them so that they are inherited from DTOOL_SUPER_BASE.
|
|
bool has_hash_compare = (slots.count("tp_hash") != 0 ||
|
|
slots.count("tp_compare") != 0 ||
|
|
has_local_richcompare);
|
|
|
|
bool has_parent_class = (obj->_itype.number_of_derivations() != 0);
|
|
|
|
// Output the type slot tables.
|
|
out << "static PyNumberMethods Dtool_NumberMethods_" << ClassName << " = {\n";
|
|
write_function_slot(out, 2, slots, "nb_add");
|
|
write_function_slot(out, 2, slots, "nb_subtract");
|
|
write_function_slot(out, 2, slots, "nb_multiply");
|
|
out << "#if PY_MAJOR_VERSION < 3\n";
|
|
// Note: nb_divide does not exist in Python 3. We will probably need some
|
|
// smart mechanism for dispatching to either floor_divide or true_divide.
|
|
write_function_slot(out, 2, slots, "nb_divide");
|
|
out << "#endif\n";
|
|
write_function_slot(out, 2, slots, "nb_remainder");
|
|
write_function_slot(out, 2, slots, "nb_divmod");
|
|
write_function_slot(out, 2, slots, "nb_power");
|
|
write_function_slot(out, 2, slots, "nb_negative");
|
|
write_function_slot(out, 2, slots, "nb_positive");
|
|
write_function_slot(out, 2, slots, "nb_absolute");
|
|
write_function_slot(out, 2, slots, "nb_bool");
|
|
write_function_slot(out, 2, slots, "nb_invert");
|
|
write_function_slot(out, 2, slots, "nb_lshift");
|
|
write_function_slot(out, 2, slots, "nb_rshift");
|
|
write_function_slot(out, 2, slots, "nb_and");
|
|
write_function_slot(out, 2, slots, "nb_xor");
|
|
write_function_slot(out, 2, slots, "nb_or");
|
|
out << "#if PY_MAJOR_VERSION < 3\n";
|
|
write_function_slot(out, 2, slots, "nb_coerce");
|
|
out << "#endif\n";
|
|
write_function_slot(out, 2, slots, "nb_int");
|
|
out << " nullptr, // nb_long\n"; // removed in Python 3
|
|
write_function_slot(out, 2, slots, "nb_float");
|
|
out << "#if PY_MAJOR_VERSION < 3\n";
|
|
write_function_slot(out, 2, slots, "nb_oct");
|
|
write_function_slot(out, 2, slots, "nb_hex");
|
|
out << "#endif\n";
|
|
|
|
write_function_slot(out, 2, slots, "nb_inplace_add");
|
|
write_function_slot(out, 2, slots, "nb_inplace_subtract");
|
|
write_function_slot(out, 2, slots, "nb_inplace_multiply");
|
|
out << "#if PY_MAJOR_VERSION < 3\n";
|
|
write_function_slot(out, 2, slots, "nb_inplace_divide");
|
|
out << "#endif\n";
|
|
write_function_slot(out, 2, slots, "nb_inplace_remainder");
|
|
write_function_slot(out, 2, slots, "nb_inplace_power");
|
|
write_function_slot(out, 2, slots, "nb_inplace_lshift");
|
|
write_function_slot(out, 2, slots, "nb_inplace_rshift");
|
|
write_function_slot(out, 2, slots, "nb_inplace_and");
|
|
write_function_slot(out, 2, slots, "nb_inplace_xor");
|
|
write_function_slot(out, 2, slots, "nb_inplace_or");
|
|
|
|
write_function_slot(out, 2, slots, "nb_floor_divide");
|
|
write_function_slot(out, 2, slots, "nb_true_divide");
|
|
write_function_slot(out, 2, slots, "nb_inplace_floor_divide");
|
|
write_function_slot(out, 2, slots, "nb_inplace_true_divide");
|
|
|
|
out << "#if PY_VERSION_HEX >= 0x02050000\n";
|
|
write_function_slot(out, 2, slots, "nb_index");
|
|
out << "#endif\n";
|
|
|
|
out << "#if PY_VERSION_HEX >= 0x03050000\n";
|
|
write_function_slot(out, 2, slots, "nb_matrix_multiply");
|
|
write_function_slot(out, 2, slots, "nb_inplace_matrix_multiply");
|
|
out << "#endif\n";
|
|
|
|
out << "};\n\n";
|
|
|
|
// NB: it's tempting not to write this table when a class doesn't have them.
|
|
// But then Python won't inherit them from base classes either! So we
|
|
// always write this table for now even if it will be full of 0's, unless
|
|
// this type has no base classes at all.
|
|
if (has_parent_class || (obj->_protocol_types & Object::PT_sequence) != 0) {
|
|
out << "static PySequenceMethods Dtool_SequenceMethods_" << ClassName << " = {\n";
|
|
write_function_slot(out, 2, slots, "sq_length");
|
|
write_function_slot(out, 2, slots, "sq_concat");
|
|
write_function_slot(out, 2, slots, "sq_repeat");
|
|
write_function_slot(out, 2, slots, "sq_item");
|
|
out << " nullptr, // sq_slice\n"; // removed in Python 3
|
|
write_function_slot(out, 2, slots, "sq_ass_item");
|
|
out << " nullptr, // sq_ass_slice\n"; // removed in Python 3
|
|
write_function_slot(out, 2, slots, "sq_contains");
|
|
|
|
write_function_slot(out, 2, slots, "sq_inplace_concat");
|
|
write_function_slot(out, 2, slots, "sq_inplace_repeat");
|
|
out << "};\n\n";
|
|
}
|
|
|
|
// Same note applies as for the SequenceMethods.
|
|
if (has_parent_class || (obj->_protocol_types & Object::PT_mapping) != 0) {
|
|
out << "static PyMappingMethods Dtool_MappingMethods_" << ClassName << " = {\n";
|
|
write_function_slot(out, 2, slots, "mp_length");
|
|
write_function_slot(out, 2, slots, "mp_subscript");
|
|
write_function_slot(out, 2, slots, "mp_ass_subscript");
|
|
out << "};\n\n";
|
|
}
|
|
|
|
// Same note applies as above.
|
|
if (has_parent_class || has_local_getbuffer) {
|
|
out << "static PyBufferProcs Dtool_BufferProcs_" << ClassName << " = {\n";
|
|
out << "#if PY_MAJOR_VERSION < 3\n";
|
|
write_function_slot(out, 2, slots, "bf_getreadbuffer");
|
|
write_function_slot(out, 2, slots, "bf_getwritebuffer");
|
|
write_function_slot(out, 2, slots, "bf_getsegcount");
|
|
write_function_slot(out, 2, slots, "bf_getcharbuffer");
|
|
out << "#endif\n";
|
|
out << "#if PY_VERSION_HEX >= 0x02060000\n";
|
|
write_function_slot(out, 2, slots, "bf_getbuffer");
|
|
write_function_slot(out, 2, slots, "bf_releasebuffer");
|
|
out << "#endif\n";
|
|
out << "};\n\n";
|
|
}
|
|
|
|
bool have_async = false;
|
|
if (has_parent_class || slots.count("am_await") != 0 ||
|
|
slots.count("am_aiter") != 0 ||
|
|
slots.count("am_anext") != 0) {
|
|
out << "#if PY_VERSION_HEX >= 0x03050000\n";
|
|
out << "static PyAsyncMethods Dtool_AsyncMethods_" << ClassName << " = {\n";
|
|
write_function_slot(out, 2, slots, "am_await");
|
|
write_function_slot(out, 2, slots, "am_aiter");
|
|
write_function_slot(out, 2, slots, "am_anext");
|
|
out << "};\n";
|
|
out << "#endif\n\n";
|
|
have_async = true;
|
|
}
|
|
|
|
// Output the actual PyTypeObject definition.
|
|
out << "struct Dtool_PyTypedObject Dtool_" << ClassName << " = {\n";
|
|
out << " {\n";
|
|
out << " PyVarObject_HEAD_INIT(nullptr, 0)\n";
|
|
// const char *tp_name;
|
|
out << " \"" << _def->module_name << "." << export_class_name << "\",\n";
|
|
// Py_ssize_t tp_basicsize;
|
|
out << " sizeof(Dtool_PyInstDef),\n";
|
|
// Py_ssize_t tp_itemsize;
|
|
out << " 0, // tp_itemsize\n";
|
|
|
|
// destructor tp_dealloc;
|
|
out << " &Dtool_FreeInstance_" << ClassName << ",\n";
|
|
// printfunc tp_print;
|
|
write_function_slot(out, 4, slots, "tp_print");
|
|
// getattrfunc tp_getattr;
|
|
write_function_slot(out, 4, slots, "tp_getattr");
|
|
// setattrfunc tp_setattr;
|
|
write_function_slot(out, 4, slots, "tp_setattr");
|
|
|
|
// cmpfunc tp_compare; (reserved in Python 3)
|
|
out << "#if PY_VERSION_HEX >= 0x03050000\n";
|
|
if (have_async) {
|
|
out << " &Dtool_AsyncMethods_" << ClassName << ",\n";
|
|
} else {
|
|
out << " nullptr, // tp_as_async\n";
|
|
}
|
|
out << "#elif PY_MAJOR_VERSION >= 3\n";
|
|
out << " nullptr, // tp_reserved\n";
|
|
out << "#else\n";
|
|
if (has_hash_compare) {
|
|
write_function_slot(out, 4, slots, "tp_compare",
|
|
"&DtoolInstance_ComparePointers");
|
|
} else {
|
|
out << " nullptr, // tp_compare\n";
|
|
}
|
|
out << "#endif\n";
|
|
|
|
// reprfunc tp_repr;
|
|
if (has_local_repr) {
|
|
out << " &Dtool_Repr_" << ClassName << ",\n";
|
|
} else {
|
|
write_function_slot(out, 4, slots, "tp_repr");
|
|
}
|
|
|
|
// PyNumberMethods *tp_as_number;
|
|
out << " &Dtool_NumberMethods_" << ClassName << ",\n";
|
|
// PySequenceMethods *tp_as_sequence;
|
|
if (has_parent_class || (obj->_protocol_types & Object::PT_sequence) != 0) {
|
|
out << " &Dtool_SequenceMethods_" << ClassName << ",\n";
|
|
} else {
|
|
out << " nullptr, // tp_as_sequence\n";
|
|
}
|
|
// PyMappingMethods *tp_as_mapping;
|
|
if (has_parent_class || (obj->_protocol_types & Object::PT_mapping) != 0) {
|
|
out << " &Dtool_MappingMethods_" << ClassName << ",\n";
|
|
} else {
|
|
out << " nullptr, // tp_as_mapping\n";
|
|
}
|
|
|
|
// hashfunc tp_hash;
|
|
if (has_hash_compare) {
|
|
write_function_slot(out, 4, slots, "tp_hash", "&DtoolInstance_HashPointer");
|
|
} else {
|
|
out << " nullptr, // tp_hash\n";
|
|
}
|
|
|
|
// ternaryfunc tp_call;
|
|
write_function_slot(out, 4, slots, "tp_call");
|
|
|
|
// reprfunc tp_str;
|
|
if (has_local_str) {
|
|
out << " &Dtool_Str_" << ClassName << ",\n";
|
|
} else if (has_local_repr) {
|
|
out << " &Dtool_Repr_" << ClassName << ",\n";
|
|
} else {
|
|
write_function_slot(out, 4, slots, "tp_str");
|
|
}
|
|
|
|
// getattrofunc tp_getattro;
|
|
write_function_slot(out, 4, slots, "tp_getattro");
|
|
// setattrofunc tp_setattro;
|
|
write_function_slot(out, 4, slots, "tp_setattro");
|
|
|
|
// PyBufferProcs *tp_as_buffer;
|
|
if (has_parent_class || has_local_getbuffer) {
|
|
out << " &Dtool_BufferProcs_" << ClassName << ",\n";
|
|
} else {
|
|
out << " nullptr, // tp_as_buffer\n";
|
|
}
|
|
|
|
string gcflag;
|
|
if (obj->_protocol_types & Object::PT_python_gc) {
|
|
gcflag = " | Py_TPFLAGS_HAVE_GC";
|
|
}
|
|
|
|
// long tp_flags;
|
|
if (has_local_getbuffer) {
|
|
out << "#if PY_VERSION_HEX >= 0x02060000 && PY_VERSION_HEX < 0x03000000\n";
|
|
out << " Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_HAVE_NEWBUFFER" << gcflag << ",\n";
|
|
out << "#else\n";
|
|
out << " Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES" << gcflag << ",\n";
|
|
out << "#endif\n";
|
|
} else {
|
|
out << " Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES" << gcflag << ",\n";
|
|
}
|
|
|
|
// const char *tp_doc;
|
|
if (obj->_itype.has_comment()) {
|
|
out << "#ifdef NDEBUG\n";
|
|
out << " 0,\n";
|
|
out << "#else\n";
|
|
output_quoted(out, 4, obj->_itype.get_comment());
|
|
out << ",\n";
|
|
out << "#endif\n";
|
|
} else {
|
|
out << " nullptr, // tp_doc\n";
|
|
}
|
|
|
|
// traverseproc tp_traverse;
|
|
out << " nullptr, // tp_traverse\n";
|
|
//write_function_slot(out, 4, slots, "tp_traverse");
|
|
|
|
// inquiry tp_clear;
|
|
out << " nullptr, // tp_clear\n";
|
|
//write_function_slot(out, 4, slots, "tp_clear");
|
|
|
|
// richcmpfunc tp_richcompare;
|
|
if (has_local_richcompare) {
|
|
out << " &Dtool_RichCompare_" << ClassName << ",\n";
|
|
} else if (has_hash_compare) {
|
|
// All hashable types need to be comparable.
|
|
out << "#if PY_MAJOR_VERSION >= 3\n";
|
|
out << " &DtoolInstance_RichComparePointers,\n";
|
|
out << "#else\n";
|
|
out << " nullptr, // tp_richcompare\n";
|
|
out << "#endif\n";
|
|
} else {
|
|
out << " nullptr, // tp_richcompare\n";
|
|
}
|
|
|
|
// Py_ssize_t tp_weaklistoffset;
|
|
out << " 0, // tp_weaklistoffset\n";
|
|
|
|
// getiterfunc tp_iter;
|
|
write_function_slot(out, 4, slots, "tp_iter");
|
|
// iternextfunc tp_iternext;
|
|
write_function_slot(out, 4, slots, "tp_iternext");
|
|
|
|
// struct PyMethodDef *tp_methods;
|
|
out << " Dtool_Methods_" << ClassName << ",\n";
|
|
// struct PyMemberDef *tp_members;
|
|
out << " nullptr, // tp_members\n";
|
|
|
|
// struct PyGetSetDef *tp_getset;
|
|
if (num_getset > 0) {
|
|
out << " Dtool_Properties_" << ClassName << ",\n";
|
|
} else {
|
|
out << " nullptr, // tp_getset\n";
|
|
}
|
|
|
|
// struct _typeobject *tp_base;
|
|
out << " nullptr, // tp_base\n";
|
|
// PyObject *tp_dict;
|
|
out << " nullptr, // tp_dict\n";
|
|
// descrgetfunc tp_descr_get;
|
|
write_function_slot(out, 4, slots, "tp_descr_get");
|
|
// descrsetfunc tp_descr_set;
|
|
write_function_slot(out, 4, slots, "tp_descr_set");
|
|
// Py_ssize_t tp_dictoffset;
|
|
out << " 0, // tp_dictoffset\n";
|
|
// initproc tp_init;
|
|
out << " Dtool_Init_" << ClassName << ",\n";
|
|
// allocfunc tp_alloc;
|
|
out << " PyType_GenericAlloc,\n";
|
|
// newfunc tp_new;
|
|
out << " Dtool_new_" << ClassName << ",\n";
|
|
// freefunc tp_free;
|
|
if (obj->_protocol_types & Object::PT_python_gc) {
|
|
out << " PyObject_GC_Del,\n";
|
|
} else {
|
|
out << " PyObject_Del,\n";
|
|
}
|
|
// inquiry tp_is_gc;
|
|
out << " nullptr, // tp_is_gc\n";
|
|
// PyObject *tp_bases;
|
|
out << " nullptr, // tp_bases\n";
|
|
// PyObject *tp_mro;
|
|
out << " nullptr, // tp_mro\n";
|
|
// PyObject *tp_cache;
|
|
out << " nullptr, // tp_cache\n";
|
|
// PyObject *tp_subclasses;
|
|
out << " nullptr, // tp_subclasses\n";
|
|
// PyObject *tp_weaklist;
|
|
out << " nullptr, // tp_weaklist\n";
|
|
// destructor tp_del;
|
|
out << " nullptr, // tp_del\n";
|
|
// unsigned int tp_version_tag
|
|
out << "#if PY_VERSION_HEX >= 0x02060000\n";
|
|
out << " 0, // tp_version_tag\n";
|
|
out << "#endif\n";
|
|
// destructor tp_finalize
|
|
out << "#if PY_VERSION_HEX >= 0x03040000\n";
|
|
out << " nullptr, // tp_finalize\n";
|
|
out << "#endif\n";
|
|
out << " },\n";
|
|
|
|
// It's tempting to initialize the type handle here, but this causes static
|
|
// init ordering issues; this may run before init_type is called.
|
|
out << " TypeHandle::none(),\n";
|
|
out << " Dtool_PyModuleClassInit_" << ClassName << ",\n";
|
|
out << " Dtool_UpcastInterface_" << ClassName << ",\n";
|
|
out << " Dtool_DowncastInterface_" << ClassName << ",\n";
|
|
|
|
int has_coerce = has_coerce_constructor(obj->_itype._cpptype->as_struct_type());
|
|
if (has_coerce > 0) {
|
|
if (TypeManager::is_reference_count(obj->_itype._cpptype)) {
|
|
out << " (CoerceFunction)Dtool_ConstCoerce_" << ClassName << ",\n";
|
|
if (has_coerce > 1) {
|
|
out << " (CoerceFunction)Dtool_Coerce_" << ClassName << ",\n";
|
|
} else {
|
|
out << " nullptr,\n";
|
|
}
|
|
} else {
|
|
out << " nullptr,\n";
|
|
out << " (CoerceFunction)Dtool_Coerce_" << ClassName << ",\n";
|
|
}
|
|
} else {
|
|
out << " nullptr,\n";
|
|
out << " nullptr,\n";
|
|
}
|
|
|
|
out << "};\n\n";
|
|
|
|
out << "static void Dtool_PyModuleClassInit_" << ClassName << "(PyObject *module) {\n";
|
|
out << " (void) module; // Unused\n";
|
|
out << " static bool initdone = false;\n";
|
|
out << " if (!initdone) {\n";
|
|
out << " initdone = true;\n";
|
|
|
|
// Add bases.
|
|
out << " // Dependent objects\n";
|
|
if (bases.size() > 0) {
|
|
string baseargs;
|
|
for (CPPType *base : bases) {
|
|
string safe_name = make_safe_name(base->get_local_name(&parser));
|
|
|
|
if (isExportThisRun(base)) {
|
|
baseargs += ", (PyTypeObject *)&Dtool_" + safe_name;
|
|
out << " Dtool_PyModuleClassInit_" << safe_name << "(nullptr);\n";
|
|
|
|
} else {
|
|
baseargs += ", (PyTypeObject *)Dtool_Ptr_" + safe_name;
|
|
|
|
out << " assert(Dtool_Ptr_" << safe_name << " != nullptr);\n"
|
|
<< " assert(Dtool_Ptr_" << safe_name << "->_Dtool_ModuleClassInit != nullptr);\n"
|
|
<< " Dtool_Ptr_" << safe_name << "->_Dtool_ModuleClassInit(nullptr);\n";
|
|
}
|
|
}
|
|
|
|
out << " Dtool_" << ClassName << "._PyType.tp_bases = PyTuple_Pack(" << bases.size() << baseargs << ");\n";
|
|
} else {
|
|
out << " Dtool_" << ClassName << "._PyType.tp_base = (PyTypeObject *)Dtool_GetSuperBase();\n";
|
|
}
|
|
|
|
int num_nested = obj->_itype.number_of_nested_types();
|
|
int num_dict_items = 1;
|
|
|
|
// Go through once to estimate the number of elements the dict will hold.
|
|
for (int ni = 0; ni < num_nested; ni++) {
|
|
TypeIndex nested_index = obj->_itype.get_nested_type(ni);
|
|
if (_objects.count(nested_index) == 0) {
|
|
continue;
|
|
}
|
|
Object *nested_obj = _objects[nested_index];
|
|
assert(nested_obj != nullptr);
|
|
|
|
if (nested_obj->_itype.is_class() || nested_obj->_itype.is_struct()) {
|
|
num_dict_items += 2;
|
|
|
|
} else if (nested_obj->_itype.is_typedef()) {
|
|
++num_dict_items;
|
|
|
|
} else if (nested_obj->_itype.is_enum() && !nested_obj->_itype.is_scoped_enum()) {
|
|
CPPEnumType *enum_type = nested_obj->_itype._cpptype->as_enum_type();
|
|
num_dict_items += 2 * enum_type->_elements.size();
|
|
}
|
|
}
|
|
|
|
// Build type dictionary. The size is just an estimation.
|
|
if (num_dict_items > 5) {
|
|
out << " PyObject *dict = _PyDict_NewPresized(" << num_dict_items << ");\n";
|
|
} else {
|
|
out << " PyObject *dict = PyDict_New();\n";
|
|
}
|
|
out << " Dtool_" << ClassName << "._PyType.tp_dict = dict;\n";
|
|
out << " PyDict_SetItemString(dict, \"DtoolClassDict\", dict);\n";
|
|
|
|
// Now go through the nested types again to actually add the dict items.
|
|
for (int ni = 0; ni < num_nested; ni++) {
|
|
TypeIndex nested_index = obj->_itype.get_nested_type(ni);
|
|
if (_objects.count(nested_index) == 0) {
|
|
// Illegal type.
|
|
continue;
|
|
}
|
|
|
|
Object *nested_obj = _objects[nested_index];
|
|
assert(nested_obj != nullptr);
|
|
|
|
if (nested_obj->_itype.is_class() || nested_obj->_itype.is_struct()) {
|
|
std::string ClassName1 = make_safe_name(nested_obj->_itype.get_scoped_name());
|
|
std::string ClassName2 = make_safe_name(nested_obj->_itype.get_name());
|
|
out << " // Nested Object " << ClassName1 << ";\n";
|
|
out << " Dtool_PyModuleClassInit_" << ClassName1 << "(nullptr);\n";
|
|
string name1 = classNameFromCppName(ClassName2, false);
|
|
string name2 = classNameFromCppName(ClassName2, true);
|
|
out << " PyDict_SetItemString(dict, \"" << name1 << "\", (PyObject *)&Dtool_" << ClassName1 << ");\n";
|
|
if (name1 != name2) {
|
|
out << " PyDict_SetItemString(dict, \"" << name2 << "\", (PyObject *)&Dtool_" << ClassName1 << ");\n";
|
|
}
|
|
|
|
} else if (nested_obj->_itype.is_typedef()) {
|
|
// Unwrap typedefs.
|
|
TypeIndex wrapped = nested_obj->_itype._wrapped_type;
|
|
while (interrogate_type_is_typedef(wrapped)) {
|
|
wrapped = interrogate_type_wrapped_type(wrapped);
|
|
}
|
|
|
|
// Er, we can only export typedefs to structs.
|
|
if (!interrogate_type_is_struct(wrapped)) {
|
|
continue;
|
|
}
|
|
|
|
string ClassName1 = make_safe_name(interrogate_type_scoped_name(wrapped));
|
|
string ClassName2 = make_safe_name(interrogate_type_name(wrapped));
|
|
|
|
string name1 = classNameFromCppName(ClassName2, false);
|
|
out << " PyDict_SetItemString(dict, \"" << name1 << "\", (PyObject *)&Dtool_" << ClassName1 << ");\n";
|
|
// No need to support mangled names for nested typedefs; we only added
|
|
// support recently.
|
|
|
|
} else if (nested_obj->_itype.is_scoped_enum()) {
|
|
// Convert enum class as Python 3.4-style enum.
|
|
string class_name = nested_obj->_itype._cpptype->get_local_name(&parser);
|
|
string safe_name = make_safe_name(class_name);
|
|
|
|
int enum_count = nested_obj->_itype.number_of_enum_values();
|
|
CPPType *underlying_type = TypeManager::unwrap_const(nested_obj->_itype._cpptype->as_enum_type()->get_underlying_type());
|
|
string cast_to = underlying_type->get_local_name(&parser);
|
|
out << " // enum class " << nested_obj->_itype.get_scoped_name() << ";\n";
|
|
out << " {\n";
|
|
out << " PyObject *members = PyTuple_New(" << enum_count << ");\n";
|
|
out << " PyObject *member;\n";
|
|
for (int xx = 0; xx < enum_count; xx++) {
|
|
out << " member = PyTuple_New(2);\n"
|
|
"#if PY_MAJOR_VERSION >= 3\n"
|
|
" PyTuple_SET_ITEM(member, 0, PyUnicode_FromString(\""
|
|
<< nested_obj->_itype.get_enum_value_name(xx) << "\"));\n"
|
|
"#else\n"
|
|
" PyTuple_SET_ITEM(member, 0, PyString_FromString(\""
|
|
<< nested_obj->_itype.get_enum_value_name(xx) << "\"));\n"
|
|
"#endif\n"
|
|
" PyTuple_SET_ITEM(member, 1, Dtool_WrapValue(("
|
|
<< cast_to << ")" << nested_obj->_itype.get_scoped_name() << "::"
|
|
<< nested_obj->_itype.get_enum_value_name(xx) << "));\n"
|
|
" PyTuple_SET_ITEM(members, " << xx << ", member);\n";
|
|
}
|
|
out << " Dtool_Ptr_" << safe_name << " = Dtool_EnumType_Create(\""
|
|
<< nested_obj->_itype.get_name() << "\", members, \""
|
|
<< _def->module_name << "\");\n";
|
|
out << " PyDict_SetItemString(dict, \"" << nested_obj->_itype.get_name()
|
|
<< "\", (PyObject *)Dtool_Ptr_" << safe_name << ");\n";
|
|
out << " }\n";
|
|
|
|
} else if (nested_obj->_itype.is_enum()) {
|
|
out << " // enum " << nested_obj->_itype.get_scoped_name() << ";\n";
|
|
CPPEnumType *enum_type = nested_obj->_itype._cpptype->as_enum_type();
|
|
CPPEnumType::Elements::const_iterator ei;
|
|
for (ei = enum_type->_elements.begin(); ei != enum_type->_elements.end(); ++ei) {
|
|
string name1 = classNameFromCppName((*ei)->get_simple_name(), false);
|
|
string name2;
|
|
if (nested_obj->_itype.has_true_name()) {
|
|
name2 = classNameFromCppName((*ei)->get_simple_name(), true);
|
|
} else {
|
|
// Don't generate the alternative syntax for anonymous enums, since
|
|
// we added support for those after we started deprecating the
|
|
// alternative syntax.
|
|
name2 = name1;
|
|
}
|
|
string enum_value = obj->_itype.get_scoped_name() + "::" + (*ei)->get_simple_name();
|
|
out << " PyDict_SetItemString(dict, \"" << name1 << "\", Dtool_WrapValue(" << enum_value << "));\n";
|
|
if (name1 != name2) {
|
|
out << " PyDict_SetItemString(dict, \"" << name2 << "\", Dtool_WrapValue(" << enum_value << "));\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also add the static properties, which can't be added via getset.
|
|
for (Property *property : obj->_properties) {
|
|
const InterrogateElement &ielem = property->_ielement;
|
|
if (property->_getter_remaps.empty()) {
|
|
continue;
|
|
}
|
|
if (property->_has_this) {
|
|
// Actually, continue if we have a conflicting static method with the
|
|
// same name, which may still require use of Dtool_StaticProperty.
|
|
bool have_shadow = false;
|
|
for (const Function *func : obj->_methods) {
|
|
if (!func->_has_this && func->_ifunc.get_name() == ielem.get_name()) {
|
|
have_shadow = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!have_shadow) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
string name1 = methodNameFromCppName(ielem.get_name(), "", false);
|
|
// string name2 = methodNameFromCppName(ielem.get_name(), "", true);
|
|
|
|
string getter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter";
|
|
string setter = "nullptr";
|
|
if (!ielem.is_sequence() && !ielem.is_mapping() && !property->_setter_remaps.empty()) {
|
|
setter = "&Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter";
|
|
}
|
|
|
|
out << " static const PyGetSetDef def_" << name1 << " = {(char *)\"" << name1 << "\", " << getter << ", " << setter;
|
|
|
|
if (ielem.has_comment()) {
|
|
out << ", (char *)\n";
|
|
output_quoted(out, 4, ielem.get_comment());
|
|
out << ",\n ";
|
|
} else {
|
|
out << ", nullptr, ";
|
|
}
|
|
|
|
// Extra void* argument; we don't make use of it.
|
|
out << "nullptr};\n";
|
|
|
|
out << " PyDict_SetItemString(dict, \"" << name1 << "\", Dtool_NewStaticProperty(&Dtool_" << ClassName << "._PyType, &def_" << name1 << "));\n";
|
|
/* Alternative spelling:
|
|
out << " PyDict_SetItemString(\"" << name2 << "\", &def_" << name1 << ");\n";
|
|
*/
|
|
}
|
|
|
|
out << " if (PyType_Ready((PyTypeObject *)&Dtool_" << ClassName << ") < 0) {\n"
|
|
" Dtool_Raise_TypeError(\"PyType_Ready(" << ClassName << ")\");\n"
|
|
" return;\n"
|
|
" }\n"
|
|
" Py_INCREF((PyTypeObject *)&Dtool_" << ClassName << ");\n"
|
|
" }\n";
|
|
|
|
/*
|
|
* Also write out the explicit alternate names. int num_alt_names =
|
|
* obj->_itype.get_num_alt_names(); for (int i = 0; i < num_alt_names; ++i) {
|
|
* string alt_name = make_safe_name(obj->_itype.get_alt_name(i)); if
|
|
* (export_class_name != alt_name) { out << " PyModule_AddObject(module,
|
|
* \"" << alt_name << "\", (PyObject *)&Dtool_" << ClassName <<
|
|
* ".As_PyTypeObject());\n"; } }
|
|
*/
|
|
|
|
// out << " }\n";
|
|
out << "}\n\n";
|
|
}
|
|
|
|
/**
|
|
* This method should be overridden and redefined to return true for
|
|
* interfaces that require the implicit "this" parameter, if present, to be
|
|
* passed as the first parameter to any wrapper functions.
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
synthesize_this_parameter() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This method should be overridden and redefined to return true for
|
|
* interfaces that require overloaded instances of a function to be defined as
|
|
* separate functions (each with its own hashed name), or false for interfaces
|
|
* that can support overloading natively, and thus only require one wrapper
|
|
* function per each overloaded input function.
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
separate_overloading() {
|
|
// We used to return true here. Nowadays, some of the default arguments are
|
|
// handled in the PyArg_ParseTuple code, and some are still being considered
|
|
// as separate overloads (this depends on a bunch of factors, see
|
|
// collapse_default_remaps). This is all handled elsewhere.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the prefix string used to generate wrapper function names.
|
|
*/
|
|
string InterfaceMakerPythonNative::
|
|
get_wrapper_prefix() {
|
|
return "Dtool_";
|
|
}
|
|
|
|
/**
|
|
* Returns the prefix string used to generate unique symbolic names, which are
|
|
* not necessarily C-callable function names.
|
|
*/
|
|
string InterfaceMakerPythonNative::
|
|
get_unique_prefix() {
|
|
return "Dtool_";
|
|
}
|
|
|
|
/**
|
|
* Associates the function wrapper with its function in the appropriate
|
|
* structures in the database.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
record_function_wrapper(InterrogateFunction &ifunc, FunctionWrapperIndex wrapper_index) {
|
|
ifunc._python_wrappers.push_back(wrapper_index);
|
|
}
|
|
|
|
/**
|
|
* Writes the prototype for the indicated function.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_prototype_for(ostream &out, InterfaceMaker::Function *func) {
|
|
std::string fname = "PyObject *" + func->_name + "(PyObject *self, PyObject *args)";
|
|
write_prototype_for_name(out, func, fname);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_prototype_for_name(ostream &out, InterfaceMaker::Function *func, const std::string &function_namename) {
|
|
// Function::Remaps::const_iterator ri;
|
|
|
|
// for (ri = func->_remaps.begin(); ri != func->_remaps.end(); ++ri) {
|
|
// FunctionRemap *remap = (*ri);
|
|
if (!output_function_names) {
|
|
// If we're not saving the function names, don't export it from the
|
|
// library.
|
|
out << "static ";
|
|
} else {
|
|
out << "extern \"C\" ";
|
|
}
|
|
out << function_namename << ";\n";
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* Writes the definition for a function that will call the indicated C++
|
|
* function or method.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_function_for_top(ostream &out, InterfaceMaker::Object *obj, InterfaceMaker::Function *func) {
|
|
|
|
// First check if this function has non-slotted and legal remaps, ie. if we
|
|
// should even write it.
|
|
bool has_remaps = false;
|
|
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
if (!is_remap_legal(remap)) {
|
|
continue;
|
|
}
|
|
|
|
SlottedFunctionDef slotted_def;
|
|
if (!get_slotted_function_def(obj, func, remap, slotted_def) || slotted_def._keep_method) {
|
|
// It has a non-slotted remap, so we should write it.
|
|
has_remaps = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!has_remaps) {
|
|
// Nope.
|
|
return;
|
|
}
|
|
|
|
// This is a bit of a hack, as these methods should probably be going
|
|
// through the slotted function system. But it's kind of pointless to write
|
|
// these out, and a waste of space.
|
|
string fname = func->_ifunc.get_name();
|
|
if (fname == "operator <" ||
|
|
fname == "operator <=" ||
|
|
fname == "operator ==" ||
|
|
fname == "operator !=" ||
|
|
fname == "operator >" ||
|
|
fname == "operator >=") {
|
|
return;
|
|
}
|
|
|
|
if (func->_ifunc.is_unary_op()) {
|
|
assert(func->_args_type == AT_no_args);
|
|
}
|
|
|
|
string prototype = "static PyObject *" + func->_name + "(PyObject *";
|
|
|
|
// This will be NULL for static funcs, so prevent code from using it.
|
|
if (func->_has_this) {
|
|
prototype += "self";
|
|
}
|
|
|
|
switch (func->_args_type) {
|
|
case AT_keyword_args:
|
|
prototype += ", PyObject *args, PyObject *kwds";
|
|
break;
|
|
|
|
case AT_varargs:
|
|
prototype += ", PyObject *args";
|
|
break;
|
|
|
|
case AT_single_arg:
|
|
prototype += ", PyObject *arg";
|
|
break;
|
|
|
|
default:
|
|
prototype += ", PyObject *";
|
|
break;
|
|
}
|
|
prototype += ")";
|
|
|
|
string expected_params;
|
|
write_function_for_name(out, obj, func->_remaps, prototype, expected_params, true, func->_args_type, RF_pyobject | RF_err_null);
|
|
|
|
// Now synthesize a variable for the docstring.
|
|
ostringstream comment;
|
|
if (!expected_params.empty()) {
|
|
comment << "C++ Interface:\n"
|
|
<< expected_params;
|
|
}
|
|
|
|
if (func->_ifunc._comment.size() > 2) {
|
|
if (!expected_params.empty()) {
|
|
comment << "\n";
|
|
}
|
|
comment << func->_ifunc._comment;
|
|
}
|
|
|
|
out << "#ifndef NDEBUG\n";
|
|
out << "static const char *" << func->_name << "_comment =\n";
|
|
output_quoted(out, 2, comment.str());
|
|
out << ";\n";
|
|
out << "#else\n";
|
|
out << "static const char *" << func->_name << "_comment = nullptr;\n";
|
|
out << "#endif\n\n";
|
|
}
|
|
|
|
/**
|
|
* Writes the definition for a function that will call the indicated C++
|
|
* function or method.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_function_for_name(ostream &out, Object *obj,
|
|
const Function::Remaps &remaps,
|
|
const string &function_name,
|
|
string &expected_params,
|
|
bool coercion_allowed,
|
|
ArgsType args_type, int return_flags) {
|
|
std::map<int, std::set<FunctionRemap *> > map_sets;
|
|
std::map<int, std::set<FunctionRemap *> >::iterator mii;
|
|
std::set<FunctionRemap *>::iterator sii;
|
|
|
|
bool has_this = false;
|
|
Function::Remaps::const_iterator ri;
|
|
FunctionRemap *remap = nullptr;
|
|
int max_required_args = 0;
|
|
bool all_nonconst = true;
|
|
bool has_keywords = false;
|
|
|
|
out << "/**\n * Python function wrapper for:\n";
|
|
for (ri = remaps.begin(); ri != remaps.end(); ++ri) {
|
|
remap = (*ri);
|
|
if (is_remap_legal(remap)) {
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
if (remap->_has_this) {
|
|
has_this = true;
|
|
}
|
|
|
|
if (!remap->_has_this || remap->_const_method) {
|
|
all_nonconst = false;
|
|
}
|
|
|
|
if (remap->_args_type == AT_keyword_args) {
|
|
has_keywords = true;
|
|
}
|
|
|
|
max_required_args = max(max_num_args, max_required_args);
|
|
|
|
for (int i = min_num_args; i <= max_num_args; ++i) {
|
|
map_sets[i].insert(remap);
|
|
}
|
|
out << " * ";
|
|
remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
|
|
out << "\n";
|
|
} else {
|
|
out << " * Rejected Remap [";
|
|
remap->write_orig_prototype(out, 0);
|
|
out << "]\n";
|
|
}
|
|
}
|
|
|
|
out << " */\n";
|
|
|
|
if (has_this && obj == nullptr) {
|
|
assert(obj != nullptr);
|
|
}
|
|
|
|
out << function_name << " {\n";
|
|
|
|
if (has_this) {
|
|
std::string ClassName = make_safe_name(obj->_itype.get_scoped_name());
|
|
std::string cClassName = obj->_itype.get_true_name();
|
|
// string class_name = remap->_cpptype->get_simple_name();
|
|
|
|
// Extract pointer from 'self' parameter.
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
|
|
if (all_nonconst) {
|
|
// All remaps are non-const. Also check that this object isn't const.
|
|
out << " if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", "
|
|
<< "(void **)&local_this, \"" << classNameFromCppName(cClassName, false)
|
|
<< "." << methodNameFromCppName(remap, cClassName, false) << "\")) {\n";
|
|
|
|
} else {
|
|
out << " if (!DtoolInstance_GetPointer(self, local_this, Dtool_" << ClassName << ")) {\n";
|
|
}
|
|
|
|
error_return(out, 4, return_flags);
|
|
out << " }\n";
|
|
}
|
|
|
|
if (map_sets.empty()) {
|
|
error_return(out, 2, return_flags);
|
|
out << "}\n\n";
|
|
return;
|
|
}
|
|
|
|
if (args_type == AT_keyword_args && !has_keywords) {
|
|
// We don't actually take keyword arguments. Make sure we didn't get any.
|
|
out << " if (kwds != nullptr && PyDict_Size(kwds) > 0) {\n";
|
|
out << "#ifdef NDEBUG\n";
|
|
error_raise_return(out, 4, return_flags, "TypeError", "function takes no keyword arguments");
|
|
out << "#else\n";
|
|
error_raise_return(out, 4, return_flags, "TypeError",
|
|
methodNameFromCppName(remap, "", false) + "() takes no keyword arguments");
|
|
out << "#endif\n";
|
|
out << " }\n";
|
|
args_type = AT_varargs;
|
|
}
|
|
|
|
if (args_type == AT_keyword_args || args_type == AT_varargs) {
|
|
max_required_args = collapse_default_remaps(map_sets, max_required_args);
|
|
}
|
|
|
|
if (remap->_flags & FunctionRemap::F_explicit_args) {
|
|
// We have a remap that wants to handle the wrapper itself.
|
|
string expected_params;
|
|
write_function_instance(out, remap, 0, 0, expected_params, 2, true, true,
|
|
args_type, return_flags);
|
|
|
|
} else if (map_sets.size() > 1 && (args_type == AT_varargs || args_type == AT_keyword_args)) {
|
|
// We have more than one remap.
|
|
switch (args_type) {
|
|
case AT_keyword_args:
|
|
indent(out, 2) << "int parameter_count = (int)PyTuple_Size(args);\n";
|
|
indent(out, 2) << "if (kwds != nullptr) {\n";
|
|
indent(out, 2) << " parameter_count += (int)PyDict_Size(kwds);\n";
|
|
indent(out, 2) << "}\n";
|
|
break;
|
|
|
|
case AT_varargs:
|
|
indent(out, 2) << "int parameter_count = (int)PyTuple_Size(args);\n";
|
|
break;
|
|
|
|
case AT_single_arg:
|
|
// It shouldn't get here, but we'll handle these cases nonetheless.
|
|
indent(out, 2) << "const int parameter_count = 1;\n";
|
|
break;
|
|
|
|
default:
|
|
indent(out, 2) << "const int parameter_count = 0;\n";
|
|
break;
|
|
}
|
|
|
|
// Keep track of how many args this function actually takes for the error
|
|
// message. We add one to the parameter count for "self", following the
|
|
// Python convention.
|
|
int add_self = has_this ? 1 : 0;
|
|
set<int> num_args;
|
|
|
|
indent(out, 2) << "switch (parameter_count) {\n";
|
|
for (mii = map_sets.begin(); mii != map_sets.end(); ++mii) {
|
|
int max_args = mii->first;
|
|
int min_args = min(max_required_args, max_args);
|
|
|
|
for (int i = min_args; i <= max_args; ++i) {
|
|
indent(out, 2) << "case " << i << ":\n";
|
|
num_args.insert(i + add_self);
|
|
}
|
|
num_args.insert(max_args + add_self);
|
|
|
|
bool strip_keyword_args = false;
|
|
|
|
// Check whether any remap actually takes keyword arguments. If not,
|
|
// then we don't have to bother checking that for every remap.
|
|
if (args_type == AT_keyword_args && max_args > 0) {
|
|
strip_keyword_args = true;
|
|
|
|
std::set<FunctionRemap *>::iterator sii;
|
|
for (sii = mii->second.begin(); sii != mii->second.end(); ++sii) {
|
|
remap = (*sii);
|
|
size_t first_param = remap->_has_this ? 1u : 0u;
|
|
for (size_t i = first_param; i < remap->_parameters.size(); ++i) {
|
|
if (remap->_parameters[i]._has_name) {
|
|
strip_keyword_args = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strip_keyword_args) {
|
|
// None of the remaps take any keyword arguments, so let's check that
|
|
// we take none. This saves some checks later on.
|
|
indent(out, 4) << "if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {\n";
|
|
if (min_args == 1 && min_args == 1) {
|
|
indent(out, 4) << " PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
|
|
write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
|
|
coercion_allowed, true, AT_single_arg, return_flags, true, !all_nonconst);
|
|
} else {
|
|
write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
|
|
coercion_allowed, true, AT_varargs, return_flags, true, !all_nonconst);
|
|
}
|
|
} else if (min_args == 1 && max_args == 1 && args_type == AT_varargs) {
|
|
// We already checked that the args tuple has only one argument, so
|
|
// we might as well extract that from the tuple now.
|
|
indent(out, 4) << "{\n";
|
|
indent(out, 4) << " PyObject *arg = PyTuple_GET_ITEM(args, 0);\n";
|
|
|
|
write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
|
|
coercion_allowed, true, AT_single_arg, return_flags, true, !all_nonconst);
|
|
} else {
|
|
indent(out, 4) << "{\n";
|
|
write_function_forset(out, mii->second, min_args, max_args, expected_params, 6,
|
|
coercion_allowed, true, args_type, return_flags, true, !all_nonconst);
|
|
}
|
|
|
|
indent(out, 4) << "}\n";
|
|
indent(out, 4) << "break;\n";
|
|
}
|
|
|
|
// In NDEBUG case, fall through to the error at end of function.
|
|
out << "#ifndef NDEBUG\n";
|
|
|
|
indent(out, 2) << "default:\n";
|
|
|
|
// Format an error saying how many arguments we actually take. So much
|
|
// logic for such a silly matter. Sheesh.
|
|
ostringstream msg;
|
|
msg << methodNameFromCppName(remap, "", false) << "() takes ";
|
|
|
|
set<int>::iterator si = num_args.begin();
|
|
msg << *si;
|
|
if (num_args.size() == 2) {
|
|
msg << " or " << *(++si);
|
|
} else if (num_args.size() > 2) {
|
|
++si;
|
|
while (si != num_args.end()) {
|
|
int num = *si;
|
|
if ((++si) == num_args.end()) {
|
|
msg << " or " << num;
|
|
} else {
|
|
msg << ", " << num;
|
|
}
|
|
}
|
|
}
|
|
msg << " arguments (%d given)";
|
|
|
|
string count_var = "parameter_count";
|
|
if (add_self) {
|
|
count_var += " + 1";
|
|
}
|
|
|
|
error_raise_return(out, 4, return_flags, "TypeError",
|
|
msg.str(), count_var);
|
|
out << "#endif\n";
|
|
indent(out, 2) << "}\n";
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n"
|
|
<< " ";
|
|
if ((return_flags & ~RF_pyobject) == RF_err_null) {
|
|
out << "return ";
|
|
}
|
|
out << "Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n"
|
|
<< " }\n";
|
|
|
|
error_return(out, 2, return_flags);
|
|
|
|
} else {
|
|
mii = map_sets.begin();
|
|
|
|
// If no parameters are accepted, we do need to check that the argument
|
|
// count is indeed 0, since we won't check that in
|
|
// write_function_instance.
|
|
if (mii->first == 0 && args_type != AT_no_args) {
|
|
switch (args_type) {
|
|
case AT_keyword_args:
|
|
out << " if (!Dtool_CheckNoArgs(args, kwds)) {\n";
|
|
out << " int parameter_count = (int)PyTuple_Size(args);\n";
|
|
out << " if (kwds != nullptr) {\n";
|
|
out << " parameter_count += (int)PyDict_Size(kwds);\n";
|
|
out << " }\n";
|
|
break;
|
|
case AT_varargs:
|
|
out << " if (!Dtool_CheckNoArgs(args)) {\n";
|
|
out << " const int parameter_count = (int)PyTuple_GET_SIZE(args);\n";
|
|
break;
|
|
case AT_single_arg:
|
|
// Shouldn't happen, but let's handle this case nonetheless.
|
|
out << " {\n";
|
|
out << " const int parameter_count = 1;\n";
|
|
break;
|
|
case AT_no_args:
|
|
break;
|
|
case AT_unknown:
|
|
break;
|
|
}
|
|
|
|
out << "#ifdef NDEBUG\n";
|
|
error_raise_return(out, 4, return_flags, "TypeError", "function takes no arguments");
|
|
out << "#else\n";
|
|
error_raise_return(out, 4, return_flags, "TypeError",
|
|
methodNameFromCppName(remap, "", false) + "() takes no arguments (%d given)",
|
|
"parameter_count");
|
|
out << "#endif\n";
|
|
out << " }\n";
|
|
|
|
} else if (args_type == AT_keyword_args && max_required_args == 1 && mii->first == 1) {
|
|
// Check this to be sure, as we handle the case of only 1 keyword arg in
|
|
// write_function_forset (not using ParseTupleAndKeywords).
|
|
out << " int parameter_count = (int)PyTuple_Size(args);\n"
|
|
" if (kwds != nullptr) {\n"
|
|
" parameter_count += (int)PyDict_Size(kwds);\n"
|
|
" }\n"
|
|
" if (parameter_count != 1) {\n"
|
|
"#ifdef NDEBUG\n";
|
|
error_raise_return(out, 4, return_flags, "TypeError",
|
|
"function takes exactly 1 argument");
|
|
out << "#else\n";
|
|
error_raise_return(out, 4, return_flags, "TypeError",
|
|
methodNameFromCppName(remap, "", false) + "() takes exactly 1 argument (%d given)",
|
|
"parameter_count");
|
|
out << "#endif\n";
|
|
out << " }\n";
|
|
}
|
|
|
|
int min_args = min(max_required_args, mii->first);
|
|
write_function_forset(out, mii->second, min_args, mii->first, expected_params, 2,
|
|
coercion_allowed, true, args_type, return_flags, true, !all_nonconst);
|
|
|
|
// This block is often unreachable for many functions... maybe we can
|
|
// figure out a way in the future to better determine when it will be and
|
|
// won't be necessary to write this out.
|
|
if (args_type != AT_no_args) {
|
|
out << " if (!_PyErr_OCCURRED()) {\n"
|
|
<< " ";
|
|
if ((return_flags & ~RF_pyobject) == RF_err_null) {
|
|
out << "return ";
|
|
}
|
|
out << "Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n"
|
|
<< " }\n";
|
|
|
|
error_return(out, 2, return_flags);
|
|
}
|
|
}
|
|
|
|
out << "}\n\n";
|
|
}
|
|
|
|
/**
|
|
* Writes the definition for a coerce constructor: a special constructor that
|
|
* is called to implicitly cast a tuple or other type to a desired type. This
|
|
* is done by calling the appropriate constructor or static make() function.
|
|
* Constructors marked with the "explicit" keyword aren't considered, just
|
|
* like in C++.
|
|
*
|
|
* There are usually two coerce constructors: one for const pointers, one for
|
|
* non-const pointers. This is due to the possibility that a static make()
|
|
* function may return a const pointer.
|
|
*
|
|
* There are two variants of this: if the class in question is a
|
|
* ReferenceCount, the coerce constructor takes a reference to a PointerTo or
|
|
* ConstPointerTo to store the converted pointer in. Otherwise, it is a
|
|
* regular pointer, and an additional boolean indicates whether the caller is
|
|
* supposed to call "delete" on the coerced pointer or not.
|
|
*
|
|
* In all cases, the coerce constructor returns a bool indicating whether the
|
|
* conversion was possible. It does not raise exceptions when none of the
|
|
* constructors matched, but just returns false.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
|
|
std::map<int, std::set<FunctionRemap *> > map_sets;
|
|
std::map<int, std::set<FunctionRemap *> >::iterator mii;
|
|
|
|
int max_required_args = 0;
|
|
|
|
// Go through the methods and find appropriate static make() functions.
|
|
for (Function *func : obj->_methods) {
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
if (is_remap_legal(remap) && remap->_flags & FunctionRemap::F_coerce_constructor) {
|
|
nassertd(!remap->_has_this) continue;
|
|
|
|
// It's a static make() function.
|
|
CPPType *return_type = remap->_return_type->get_new_type();
|
|
|
|
if (!is_const && TypeManager::is_const_pointer_or_ref(return_type)) {
|
|
// If we're making the non-const coerce constructor, reject this
|
|
// remap if it returns a const pointer.
|
|
continue;
|
|
}
|
|
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
|
|
// Coerce constructor should take at least one argument.
|
|
nassertd(max_num_args > 0) continue;
|
|
min_num_args = max(min_num_args, 1);
|
|
|
|
max_required_args = max(max_num_args, max_required_args);
|
|
|
|
for (int i = min_num_args; i <= max_num_args; ++i) {
|
|
map_sets[i].insert(remap);
|
|
}
|
|
|
|
size_t parameter_size = remap->_parameters.size();
|
|
map_sets[parameter_size].insert(remap);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now go through the constructors that are suitable for coercion. This
|
|
// excludes copy constructors and ones marked "explicit".
|
|
for (Function *func : obj->_constructors) {
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
if (is_remap_legal(remap) && remap->_flags & FunctionRemap::F_coerce_constructor) {
|
|
nassertd(!remap->_has_this) continue;
|
|
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
|
|
// Coerce constructor should take at least one argument.
|
|
nassertd(max_num_args > 0) continue;
|
|
min_num_args = max(min_num_args, 1);
|
|
|
|
max_required_args = max(max_num_args, max_required_args);
|
|
|
|
for (int i = min_num_args; i <= max_num_args; ++i) {
|
|
map_sets[i].insert(remap);
|
|
}
|
|
|
|
size_t parameter_size = remap->_parameters.size();
|
|
map_sets[parameter_size].insert(remap);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string ClassName = make_safe_name(obj->_itype.get_scoped_name());
|
|
std::string cClassName = obj->_itype.get_true_name();
|
|
|
|
int return_flags = RF_coerced;
|
|
|
|
if (TypeManager::is_reference_count(obj->_itype._cpptype)) {
|
|
// The coercion works slightly different for reference counted types,
|
|
// since we can handle those a bit more nicely by taking advantage of the
|
|
// refcount instead of having to use a boolean to indicate that it should
|
|
// be managed.
|
|
if (is_const) {
|
|
out << "bool Dtool_ConstCoerce_" << ClassName << "(PyObject *args, CPT(" << cClassName << ") &coerced) {\n";
|
|
} else {
|
|
out << "bool Dtool_Coerce_" << ClassName << "(PyObject *args, PT(" << cClassName << ") &coerced) {\n";
|
|
}
|
|
|
|
// Note: this relies on the PT() being initialized to NULL. This is
|
|
// currently the case in all invocations, but this may not be true in the
|
|
// future.
|
|
out << " if (DtoolInstance_GetPointer(args, coerced.cheat(), Dtool_" << ClassName << ")) {\n";
|
|
out << " // The argument is already of matching type, no need to coerce.\n";
|
|
if (!is_const) {
|
|
out << " if (!DtoolInstance_IS_CONST(args)) {\n";
|
|
out << " // A non-const instance is required, which this is.\n";
|
|
out << " coerced->ref();\n";
|
|
out << " return true;\n";
|
|
out << " }\n";
|
|
} else {
|
|
out << " coerced->ref();\n";
|
|
out << " return true;\n";
|
|
}
|
|
return_flags |= RF_err_false;
|
|
|
|
} else {
|
|
out << cClassName << " *Dtool_Coerce_" << ClassName << "(PyObject *args, " << cClassName << " &coerced) {\n";
|
|
|
|
out << " " << cClassName << " *local_this;\n";
|
|
out << " if (DtoolInstance_GetPointer(args, local_this, Dtool_" << ClassName << ")) {\n";
|
|
out << " if (DtoolInstance_IS_CONST(args)) {\n";
|
|
out << " // This is a const object. Make a copy.\n";
|
|
out << " coerced = *(const " << cClassName << " *)local_this;\n";
|
|
out << " return &coerced;\n";
|
|
out << " }\n";
|
|
out << " return local_this;\n";
|
|
|
|
return_flags |= RF_err_null;
|
|
}
|
|
|
|
out << " }\n\n";
|
|
|
|
if (map_sets.empty()) {
|
|
error_return(out, 2, return_flags);
|
|
out << "}\n\n";
|
|
return;
|
|
}
|
|
|
|
// Coercion constructors are special cases in that they can take either a
|
|
// single value or a tuple. (They never, however, take a tuple containing a
|
|
// single value.)
|
|
string expected_params;
|
|
mii = map_sets.find(1);
|
|
if (mii != map_sets.end()) {
|
|
out << " if (!PyTuple_Check(args)) {\n";
|
|
out << " PyObject *arg = args;\n";
|
|
|
|
write_function_forset(out, mii->second, mii->first, mii->first, expected_params, 4, false, false,
|
|
AT_single_arg, return_flags, true, false);
|
|
|
|
if (map_sets.size() == 1) {
|
|
out << " }\n";
|
|
// out << " PyErr_Clear();\n";
|
|
error_return(out, 2, return_flags);
|
|
out << "}\n\n";
|
|
return;
|
|
}
|
|
|
|
// We take this one out of the map sets. There's not much value in
|
|
// coercing tuples containing just one value.
|
|
map_sets.erase(mii);
|
|
|
|
out << " } else {\n";
|
|
} else {
|
|
out << " if (PyTuple_Check(args)) {\n";
|
|
}
|
|
|
|
max_required_args = collapse_default_remaps(map_sets, max_required_args);
|
|
|
|
if (map_sets.size() > 1) {
|
|
indent(out, 4) << "switch (PyTuple_GET_SIZE(args)) {\n";
|
|
|
|
for (mii = map_sets.begin(); mii != map_sets.end(); ++mii) {
|
|
int max_args = mii->first;
|
|
int min_args = min(max_required_args, max_args);
|
|
|
|
// This is not called for tuples containing just one value or no values
|
|
// at all, so we should never have to consider that case.
|
|
if (min_args < 2) {
|
|
min_args = 2;
|
|
}
|
|
nassertd(max_args >= min_args) continue;
|
|
|
|
for (int i = min_args; i < max_args; ++i) {
|
|
if (i != 1) {
|
|
indent(out, 6) << "case " << i << ":\n";
|
|
}
|
|
}
|
|
indent(out, 6) << "case " << max_args << ": {\n";
|
|
|
|
write_function_forset(out, mii->second, min_args, max_args, expected_params, 8, false, false,
|
|
AT_varargs, return_flags, true, false);
|
|
|
|
indent(out, 8) << "break;\n";
|
|
indent(out, 6) << "}\n";
|
|
}
|
|
indent(out, 4) << "}\n";
|
|
|
|
} else {
|
|
mii = map_sets.begin();
|
|
|
|
int max_args = mii->first;
|
|
int min_args = min(max_required_args, max_args);
|
|
|
|
// This is not called for tuples containing just one value or no values at
|
|
// all, so we should never have to consider that case.
|
|
if (min_args < 2) {
|
|
min_args = 2;
|
|
}
|
|
nassertv(max_args >= min_args);
|
|
|
|
if (min_args == max_args) {
|
|
indent(out, 4) << "if (PyTuple_GET_SIZE(args) == " << mii->first << ") {\n";
|
|
} else {
|
|
indent(out, 4) << "Py_ssize_t size = PyTuple_GET_SIZE(args);\n";
|
|
// Not sure if this check really does any good. I guess it's a useful
|
|
// early-fail test.
|
|
indent(out, 4) << "if (size >= " << min_args << " && size <= " << max_args << ") {\n";
|
|
}
|
|
|
|
write_function_forset(out, mii->second, min_args, max_args, expected_params, 6, false, false,
|
|
AT_varargs, return_flags, true, false);
|
|
indent(out, 4) << "}\n";
|
|
}
|
|
|
|
out << " }\n\n";
|
|
// out << " PyErr_Clear();\n";
|
|
error_return(out, 2, return_flags);
|
|
out << "}\n\n";
|
|
}
|
|
|
|
/**
|
|
* Special case optimization: if the last map is a subset of the map before
|
|
* it, we can merge the cases. When this happens, we can make use of a
|
|
* special feature of PyArg_ParseTuple for handling of these last few default
|
|
* arguments.
|
|
*
|
|
* This isn't just to help reduce the amount of generated code; it also
|
|
* enables arbitrary selection of keyword arguments for many functions, ie.
|
|
* for this function:
|
|
*
|
|
* int func(int a=0, int b=0, bool c=false, string d="");
|
|
*
|
|
* Thanks to this mechanism, we can call it like so:
|
|
*
|
|
* func(c=True, d=".")
|
|
*/
|
|
int InterfaceMakerPythonNative::
|
|
collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
|
|
int max_required_args) {
|
|
if (map_sets.size() < 1) {
|
|
return max_required_args;
|
|
}
|
|
|
|
std::map<int, std::set<FunctionRemap *> >::reverse_iterator rmi, rmi_next;
|
|
rmi = map_sets.rbegin();
|
|
rmi_next = rmi;
|
|
for (++rmi_next; rmi_next != map_sets.rend();) {
|
|
if (std::includes(rmi_next->second.begin(), rmi_next->second.end(),
|
|
rmi->second.begin(), rmi->second.end())) {
|
|
|
|
// rmi_next has a superset of the remaps in rmi, and we are going to
|
|
// erase rmi_next, so put all the remaps in rmi.
|
|
|
|
max_required_args = rmi_next->first;
|
|
rmi = rmi_next;
|
|
++rmi_next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now erase the other remap sets. Reverse iterators are weird, we first
|
|
// need to get forward iterators and decrement them by one.
|
|
std::map<int, std::set<FunctionRemap *> >::iterator erase_begin, erase_end;
|
|
erase_begin = rmi.base();
|
|
erase_end = map_sets.rbegin().base();
|
|
--erase_begin;
|
|
--erase_end;
|
|
|
|
if (erase_begin == erase_end) {
|
|
return max_required_args;
|
|
}
|
|
|
|
// We're never erasing the map set with the highest number of args.
|
|
nassertr(erase_end != map_sets.end(), max_required_args);
|
|
|
|
// We know erase_begin is a superset of erase_end, but we want all the
|
|
// remaps in erase_end (which we aren't erasing). if (rmi ==
|
|
// map_sets.rbegin()) {
|
|
erase_end->second = erase_begin->second;
|
|
// }
|
|
|
|
map_sets.erase(erase_begin, erase_end);
|
|
|
|
assert(map_sets.size() >= 1);
|
|
|
|
return max_required_args;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
int get_type_sort(CPPType *type) {
|
|
int answer = 0;
|
|
// printf(" %s\n",type->get_local_name().c_str());
|
|
|
|
// The highest numbered one will be checked first.
|
|
if (TypeManager::is_nullptr(type)) {
|
|
return 15;
|
|
} else if (TypeManager::is_pointer_to_Py_buffer(type)) {
|
|
return 14;
|
|
} else if (TypeManager::is_pointer_to_PyTypeObject(type)) {
|
|
return 13;
|
|
} else if (TypeManager::is_pointer_to_PyObject(type)) {
|
|
return 12;
|
|
} else if (TypeManager::is_wstring(type)) {
|
|
return 11;
|
|
} else if (TypeManager::is_wchar_pointer(type)) {
|
|
return 10;
|
|
} else if (TypeManager::is_string(type)) {
|
|
return 9;
|
|
} else if (TypeManager::is_char_pointer(type)) {
|
|
return 8;
|
|
} else if (TypeManager::is_unsigned_longlong(type)) {
|
|
return 7;
|
|
} else if (TypeManager::is_longlong(type)) {
|
|
return 6;
|
|
} else if (TypeManager::is_integer(type) && !TypeManager::is_bool(type)) {
|
|
return 5;
|
|
} else if (TypeManager::is_double(type)) {
|
|
return 4;
|
|
} else if (TypeManager::is_float(type)) {
|
|
return 3;
|
|
} else if (TypeManager::is_pointer_to_simple(type)) {
|
|
return 2;
|
|
} else if (TypeManager::is_bool(type)) {
|
|
return 1;
|
|
} else if (TypeManager::is_pointer(type) ||
|
|
TypeManager::is_reference(type) ||
|
|
TypeManager::is_struct(type)) {
|
|
answer = 20;
|
|
int deepest = 0;
|
|
TypeIndex type_index = builder.get_type(TypeManager::unwrap(TypeManager::resolve_type(type)), false);
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
|
|
if (itype.is_class() || itype.is_struct()) {
|
|
int num_derivations = itype.number_of_derivations();
|
|
for (int di = 0; di < num_derivations; di++) {
|
|
TypeIndex d_type_Index = itype.get_derivation(di);
|
|
const InterrogateType &d_itype = idb->get_type(d_type_Index);
|
|
int this_one = get_type_sort(d_itype._cpptype);
|
|
if (this_one > deepest) {
|
|
deepest = this_one;
|
|
}
|
|
}
|
|
}
|
|
answer += deepest;
|
|
// printf(" Class Name %s %d\n",itype.get_name().c_str(),answer);
|
|
}
|
|
|
|
// printf(" Class Name %s %d\n",itype.get_name().c_str(),answer);
|
|
return answer;
|
|
}
|
|
|
|
// The Core sort function for remap calling orders..
|
|
bool RemapCompareLess(FunctionRemap *in1, FunctionRemap *in2) {
|
|
assert(in1 != nullptr);
|
|
assert(in2 != nullptr);
|
|
|
|
if (in1->_const_method != in2->_const_method) {
|
|
// Non-const methods should come first.
|
|
return in2->_const_method;
|
|
}
|
|
|
|
if (in1->_parameters.size() != in2->_parameters.size()) {
|
|
return (in1->_parameters.size() > in2->_parameters.size());
|
|
}
|
|
|
|
int pcount = in1->_parameters.size();
|
|
for (int x = 0; x < pcount; x++) {
|
|
CPPType *orig_type1 = in1->_parameters[x]._remap->get_orig_type();
|
|
CPPType *orig_type2 = in2->_parameters[x]._remap->get_orig_type();
|
|
|
|
int pd1 = get_type_sort(orig_type1);
|
|
int pd2 = get_type_sort(orig_type2);
|
|
if (pd1 != pd2) {
|
|
return (pd1 > pd2);
|
|
}
|
|
}
|
|
|
|
// ok maybe something to do with return strength..
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Writes out a set of function wrappers that handle all instances of a
|
|
* particular function with the same number of parameters. (Actually, in some
|
|
* cases relating to default argument handling, this may be called with remaps
|
|
* taking a range of parameters.)
|
|
*
|
|
* min_num_args and max_num_args are the range of parameter counts to respect
|
|
* for these functions. This is important for default argument handling.
|
|
*
|
|
* expected_params is a reference to a string that will be filled in with a
|
|
* list of overloads that this function takes, for displaying in the doc
|
|
* string and error messages.
|
|
*
|
|
* If coercion_allowed is true, it will attempt to convert arguments to the
|
|
* appropriate parameter type using the appropriate Dtool_Coerce function.
|
|
* This means it may write some remaps twice: once without coercion, and then
|
|
* it may go back and write it a second time to try parameter coercion.
|
|
*
|
|
* If report_errors is true, it will print an error and exit when one has
|
|
* occurred, instead of falling back to the next overload. This is
|
|
* automatically disabled when more than one function is passed.
|
|
*
|
|
* args_type indicates whether this function takes no args, a single PyObject*
|
|
* arg, an args tuple, or an args tuple and kwargs dictionary.
|
|
*
|
|
* return_flags indicates which value should be returned from the wrapper
|
|
* function and what should be returned on error.
|
|
*
|
|
* If check_exceptions is false, it will not check if the function raised an
|
|
* exception, except if it took PyObject* arguments. This should NEVER be
|
|
* false for C++ functions that call Python code, since that would block a
|
|
* meaningful exception like SystemExit or KeyboardInterrupt.
|
|
*
|
|
* If verify_const is set, it will write out a check to make sure that non-
|
|
* const functions aren't called for a const "this". This is usually only
|
|
* false when write_function_for_name has already done this check (which it
|
|
* does when *all* remaps are non-const).
|
|
*
|
|
* If first_pexpr is not empty, it represents the preconverted value of the
|
|
* first parameter. This is a special-case hack for one of the slot
|
|
* functions.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_function_forset(ostream &out,
|
|
const std::set<FunctionRemap *> &remapsin,
|
|
int min_num_args, int max_num_args,
|
|
string &expected_params, int indent_level,
|
|
bool coercion_allowed, bool report_errors,
|
|
ArgsType args_type, int return_flags,
|
|
bool check_exceptions, bool verify_const,
|
|
const string &first_pexpr) {
|
|
|
|
if (remapsin.empty()) {
|
|
return;
|
|
}
|
|
|
|
FunctionRemap *remap = nullptr;
|
|
std::set<FunctionRemap *>::iterator sii;
|
|
|
|
bool all_nonconst = false;
|
|
|
|
if (verify_const) {
|
|
// Check if all of the remaps are non-const. If so, we only have to check
|
|
// the constness of the self pointer once, rather than per remap.
|
|
all_nonconst = true;
|
|
|
|
for (sii = remapsin.begin(); sii != remapsin.end(); ++sii) {
|
|
remap = (*sii);
|
|
if (!remap->_has_this || remap->_const_method) {
|
|
all_nonconst = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_nonconst) {
|
|
// Yes, they do. Check that the parameter has the required constness.
|
|
indent(out, indent_level)
|
|
<< "if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
indent_level += 2;
|
|
verify_const = false;
|
|
}
|
|
}
|
|
|
|
string first_param_name;
|
|
bool same_first_param = false;
|
|
|
|
// If there's only one arg and all remaps have the same parameter name, we
|
|
// extract it from the dictionary, so we don't have to call
|
|
// ParseTupleAndKeywords.
|
|
if (first_pexpr.empty() && min_num_args == 1 && max_num_args == 1 &&
|
|
args_type == AT_keyword_args) {
|
|
sii = remapsin.begin();
|
|
remap = (*sii);
|
|
if (remap->_parameters[(int)remap->_has_this]._has_name) {
|
|
first_param_name = remap->_parameters[(int)remap->_has_this]._name;
|
|
same_first_param = true;
|
|
|
|
for (++sii; sii != remapsin.end(); ++sii) {
|
|
remap = (*sii);
|
|
if (remap->_parameters[(int)remap->_has_this]._name != first_param_name) {
|
|
same_first_param = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (same_first_param) {
|
|
// Yes, they all have the same argument name (or there is only one remap).
|
|
// Extract it from the dict so we don't have to call
|
|
// ParseTupleAndKeywords.
|
|
indent(out, indent_level) << "PyObject *arg;\n";
|
|
indent(out, indent_level) << "if (Dtool_ExtractArg(&arg, args, kwds, \"" << first_param_name << "\")) {\n";
|
|
indent_level += 2;
|
|
args_type = AT_single_arg;
|
|
}
|
|
|
|
if (remapsin.size() > 1) {
|
|
// There are multiple different overloads for this number of parameters.
|
|
// Sort them all into order from most-specific to least-specific, then try
|
|
// them one at a time.
|
|
std::vector<FunctionRemap *> remaps (remapsin.begin(), remapsin.end());
|
|
std::sort(remaps.begin(), remaps.end(), RemapCompareLess);
|
|
std::vector<FunctionRemap *>::const_iterator sii;
|
|
|
|
int num_coercion_possible = 0;
|
|
sii = remaps.begin();
|
|
while (sii != remaps.end()) {
|
|
remap = *(sii++);
|
|
|
|
if (coercion_allowed && is_remap_coercion_possible(remap)) {
|
|
if (++num_coercion_possible == 1 && sii == remaps.end()) {
|
|
// This is the last remap, and it happens to be the only one with
|
|
// coercion possible. So we might as well just break off now, and
|
|
// let this case be handled by the coercion loop, below. BUG: this
|
|
// remap doesn't get listed in expected_params.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (verify_const && (remap->_has_this && !remap->_const_method)) {
|
|
// If it's a non-const method, we only allow a non-const this.
|
|
indent(out, indent_level)
|
|
<< "if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< "{\n";
|
|
}
|
|
|
|
indent(out, indent_level) << " // -2 ";
|
|
remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
|
|
out << "\n";
|
|
|
|
// NB. We don't pass on report_errors here because we want it to
|
|
// silently drop down to the next overload.
|
|
|
|
write_function_instance(out, remap, min_num_args, max_num_args,
|
|
expected_params, indent_level + 2,
|
|
false, false, args_type, return_flags,
|
|
check_exceptions, first_pexpr);
|
|
|
|
indent(out, indent_level) << "}\n\n";
|
|
}
|
|
|
|
// Go through one more time, but allow coercion this time.
|
|
if (coercion_allowed) {
|
|
for (sii = remaps.begin(); sii != remaps.end(); sii ++) {
|
|
remap = (*sii);
|
|
if (!is_remap_coercion_possible(remap)) {
|
|
indent(out, indent_level)
|
|
<< "// No coercion possible: ";
|
|
remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
|
|
out << "\n";
|
|
continue;
|
|
}
|
|
|
|
if (verify_const && (remap->_has_this && !remap->_const_method)) {
|
|
indent(out, indent_level)
|
|
<< "if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< "{\n";
|
|
}
|
|
|
|
indent(out, indent_level) << " // -2 ";
|
|
remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
|
|
out << "\n";
|
|
|
|
string ignore_expected_params;
|
|
write_function_instance(out, remap, min_num_args, max_num_args,
|
|
ignore_expected_params, indent_level + 2,
|
|
true, false, args_type, return_flags,
|
|
check_exceptions, first_pexpr);
|
|
|
|
indent(out, indent_level) << "}\n\n";
|
|
}
|
|
}
|
|
} else {
|
|
// There is only one possible overload with this number of parameters.
|
|
// Just call it.
|
|
sii = remapsin.begin();
|
|
|
|
remap = (*sii);
|
|
indent(out, indent_level)
|
|
<< "// 1-" ;
|
|
remap->write_orig_prototype(out, 0, false, (max_num_args - min_num_args));
|
|
out << "\n";
|
|
|
|
write_function_instance(out, remap, min_num_args, max_num_args,
|
|
expected_params, indent_level,
|
|
coercion_allowed, report_errors,
|
|
args_type, return_flags,
|
|
check_exceptions, first_pexpr);
|
|
}
|
|
|
|
// Close the brace we opened earlier.
|
|
if (same_first_param) {
|
|
indent_level -= 2;
|
|
indent(out, indent_level) << "}\n";
|
|
}
|
|
|
|
// If we did a const check earlier, and we were asked to report errors,
|
|
// write out an else case raising an exception.
|
|
if (all_nonconst) {
|
|
if (report_errors) {
|
|
indent(out, indent_level - 2)
|
|
<< "} else {\n";
|
|
|
|
string class_name = remap->_cpptype->get_simple_name();
|
|
ostringstream msg;
|
|
msg << "Cannot call "
|
|
<< classNameFromCppName(class_name, false)
|
|
<< "." << methodNameFromCppName(remap, class_name, false)
|
|
<< "() on a const object.";
|
|
|
|
out << "#ifdef NDEBUG\n";
|
|
error_raise_return(out, indent_level, return_flags, "TypeError",
|
|
"non-const method called on const object");
|
|
out << "#else\n";
|
|
error_raise_return(out, indent_level, return_flags, "TypeError", msg.str());
|
|
out << "#endif\n";
|
|
}
|
|
indent_level -= 2;
|
|
indent(out, indent_level) << "}\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes out the code to handle a a single instance of an overloaded
|
|
* function. This will convert all of the arguments from PyObject* to the
|
|
* appropriate C++ type, call the C++ function, possibly check for errors, and
|
|
* construct a Python wrapper for the return value.
|
|
*
|
|
* return_flags indicates which value should be returned from the wrapper
|
|
* function and what should be returned on error.
|
|
*
|
|
* If coercion_possible is true, it will attempt to convert arguments to the
|
|
* appropriate parameter type using the appropriate Dtool_Coerce function.
|
|
*
|
|
* If report_errors is true, it will print an error and exit when one has
|
|
* occurred, instead of falling back to the next overload. This should be
|
|
* done if it is the only overload.
|
|
*
|
|
* If check_exceptions is false, it will not check if the function raised an
|
|
* exception, except if it took PyObject* arguments. This should NEVER be
|
|
* false for C++ functions that call Python code, since that would block a
|
|
* meaningful exception like SystemExit or KeyboardInterrupt.
|
|
*
|
|
* If first_pexpr is not empty, it represents the preconverted value of the
|
|
* first parameter. This is a special-case hack for one of the slot
|
|
* functions.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_function_instance(ostream &out, FunctionRemap *remap,
|
|
int min_num_args, int max_num_args,
|
|
string &expected_params, int indent_level,
|
|
bool coercion_possible, bool report_errors,
|
|
ArgsType args_type, int return_flags,
|
|
bool check_exceptions,
|
|
const string &first_pexpr) {
|
|
string format_specifiers;
|
|
string keyword_list;
|
|
string parameter_list;
|
|
string container;
|
|
string type_check;
|
|
string param_name;
|
|
bool has_keywords = false;
|
|
vector_string pexprs;
|
|
LineStream extra_convert;
|
|
ostringstream extra_param_check;
|
|
LineStream extra_cleanup;
|
|
int min_version = 0;
|
|
|
|
// This will be set if the function itself is suspected of possibly raising
|
|
// a TypeError.
|
|
bool may_raise_typeerror = false;
|
|
|
|
// This will be set to true if one of the things we're about to do *might*
|
|
// raise a TypeError that we may have to clear.
|
|
bool clear_error = false;
|
|
|
|
bool is_constructor = (remap->_type == FunctionRemap::T_constructor);
|
|
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
// Make one pass through the parameter list. We will output a one-line
|
|
// temporary variable definition for each parameter, while simultaneously
|
|
// building the ParseTuple() function call and also the parameter expression
|
|
// list for call_function().
|
|
|
|
expected_params += methodNameFromCppName(remap, "", false);
|
|
expected_params += "(";
|
|
|
|
int num_params = 0;
|
|
|
|
if ((remap->_flags & FunctionRemap::F_explicit_args) == 0) {
|
|
num_params = max_num_args;
|
|
if (remap->_has_this) {
|
|
num_params += 1;
|
|
}
|
|
if (num_params > (int)remap->_parameters.size()) {
|
|
// Limit to how many parameters this remap actually has.
|
|
num_params = (int)remap->_parameters.size();
|
|
max_num_args = num_params;
|
|
if (remap->_has_this) {
|
|
--max_num_args;
|
|
}
|
|
}
|
|
nassertv(num_params <= (int)remap->_parameters.size());
|
|
}
|
|
|
|
bool only_pyobjects = true;
|
|
|
|
int pn = 0;
|
|
|
|
if (remap->_has_this) {
|
|
// The first parameter is the 'this' parameter.
|
|
string expected_class_name = classNameFromCppName(remap->_cpptype->get_simple_name(), false);
|
|
if (remap->_const_method) {
|
|
expected_params += expected_class_name + " self";
|
|
string class_name = remap->_cpptype->get_local_name(&parser);
|
|
container = "(const " + class_name + "*)local_this";
|
|
} else {
|
|
expected_params += "const " + expected_class_name + " self";
|
|
container = "local_this";
|
|
}
|
|
pexprs.push_back(container);
|
|
++pn;
|
|
}
|
|
|
|
if (!first_pexpr.empty()) {
|
|
if (pn >= num_params) {
|
|
// first_pexpr was passed even though the function takes no arguments.
|
|
nassert_raise("pn < num_params");
|
|
} else {
|
|
// The first actual argument was already converted.
|
|
if (pn > 0) {
|
|
expected_params += ", ";
|
|
}
|
|
expected_params += first_pexpr;
|
|
pexprs.push_back(first_pexpr);
|
|
++pn;
|
|
}
|
|
}
|
|
|
|
if (remap->_flags & FunctionRemap::F_explicit_args) {
|
|
// The function handles the arguments by itself.
|
|
expected_params += "*args";
|
|
pexprs.push_back("args");
|
|
if (args_type == AT_keyword_args) {
|
|
expected_params += ", **kwargs";
|
|
pexprs.push_back("kwds");
|
|
}
|
|
num_params = 0;
|
|
}
|
|
|
|
// Now convert (the rest of the) actual arguments, one by one.
|
|
for (; pn < num_params; ++pn) {
|
|
ParameterRemap *param = remap->_parameters[pn]._remap;
|
|
CPPType *orig_type = param->get_orig_type();
|
|
CPPType *type = param->get_new_type();
|
|
CPPExpression *default_value = param->get_default_value();
|
|
param_name = remap->get_parameter_name(pn);
|
|
|
|
if (!is_cpp_type_legal(orig_type)) {
|
|
// We can't wrap this. We sometimes get here for default arguments.
|
|
// Just skip this parameter.
|
|
continue;
|
|
}
|
|
|
|
// Has this remap been selected to consider optional arguments for this
|
|
// parameter? We can do that by adding a vertical bar to the
|
|
// PyArg_ParseTuple format string, coupled with some extra logic in the
|
|
// argument handling, below.
|
|
bool is_optional = false;
|
|
if (remap->_has_this && !is_constructor) {
|
|
if (pn > min_num_args) {
|
|
is_optional = true;
|
|
if ((pn - 1) == min_num_args) {
|
|
format_specifiers += "|";
|
|
}
|
|
}
|
|
} else {
|
|
if (pn >= min_num_args) {
|
|
is_optional = true;
|
|
if (pn == min_num_args) {
|
|
format_specifiers += "|";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pn > 0) {
|
|
expected_params += ", ";
|
|
}
|
|
|
|
// This is the string to convert our local variable to the appropriate C++
|
|
// type. Normally this is just a cast.
|
|
string pexpr_string =
|
|
"(" + orig_type->get_local_name(&parser) + ")" + param_name;
|
|
|
|
string default_expr;
|
|
const char *null_assign = "";
|
|
|
|
if (is_optional) {
|
|
// If this is an optional argument, PyArg_ParseTuple will leave the
|
|
// variable unchanged if it has been omitted, so we have to initialize
|
|
// it to the desired default expression. Format it.
|
|
ostringstream default_expr_str;
|
|
default_expr_str << " = ";
|
|
default_value->output(default_expr_str, 0, &parser, false);
|
|
default_expr = default_expr_str.str();
|
|
null_assign = " = nullptr";
|
|
|
|
// We should only ever have to consider optional arguments for functions
|
|
// taking a variable number of arguments.
|
|
nassertv(args_type == AT_varargs || args_type == AT_keyword_args);
|
|
}
|
|
|
|
string reported_name = remap->_parameters[pn]._name;
|
|
if (!keyword_list.empty()) {
|
|
keyword_list += ", \"" + reported_name + "\"";
|
|
} else {
|
|
keyword_list = "\"" + reported_name + "\"";
|
|
}
|
|
if (remap->_parameters[pn]._has_name) {
|
|
has_keywords = true;
|
|
}
|
|
|
|
if (param->new_type_is_atomic_string()) {
|
|
|
|
if (TypeManager::is_char_pointer(orig_type)) {
|
|
indent(out, indent_level) << "char ";
|
|
if (TypeManager::is_const_char_pointer(orig_type)) {
|
|
out << "const ";
|
|
}
|
|
out << "*" << param_name << default_expr << ";\n";
|
|
format_specifiers += "z";
|
|
parameter_list += ", &" + param_name;
|
|
expected_params += "str";
|
|
|
|
} else if (TypeManager::is_wchar_pointer(orig_type)) {
|
|
out << "#if PY_VERSION_HEX >= 0x03020000\n";
|
|
indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
|
|
out << "#else\n";
|
|
indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n";
|
|
out << "#endif\n";
|
|
format_specifiers += "U";
|
|
parameter_list += ", &" + param_name;
|
|
|
|
if (is_optional) {
|
|
extra_convert
|
|
<< "wchar_t *" << param_name << "_str;\n"
|
|
<< "if (" << param_name << " != nullptr) {\n"
|
|
<< "#if PY_VERSION_HEX >= 0x03030000\n"
|
|
<< " " << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
|
|
<< "#else"
|
|
<< "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
|
|
<< " " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
|
|
<< "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
|
|
<< param_name << "_str[" << param_name << "_len] = 0;\n"
|
|
<< "#endif\n"
|
|
<< "} else {\n"
|
|
<< " " << param_name << "_str" << default_expr << ";\n"
|
|
<< "}\n";
|
|
} else {
|
|
extra_convert
|
|
<< "#if PY_VERSION_HEX >= 0x03030000\n"
|
|
<< "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
|
|
<< "#else"
|
|
<< "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
|
|
<< "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
|
|
<< "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
|
|
<< param_name << "_str[" << param_name << "_len] = 0;\n"
|
|
<< "#endif\n";
|
|
}
|
|
|
|
pexpr_string = param_name + "_str";
|
|
|
|
extra_cleanup
|
|
<< "#if PY_VERSION_HEX >= 0x03030000\n"
|
|
<< "PyMem_Free(" << param_name << "_str);\n"
|
|
<< "#endif\n";
|
|
|
|
expected_params += "unicode";
|
|
|
|
} else if (TypeManager::is_wstring(orig_type) ||
|
|
TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) {
|
|
out << "#if PY_VERSION_HEX >= 0x03020000\n";
|
|
indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
|
|
out << "#else\n";
|
|
indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n";
|
|
out << "#endif\n";
|
|
format_specifiers += "U";
|
|
parameter_list += ", &" + param_name;
|
|
|
|
if (is_optional) {
|
|
extra_convert
|
|
<< "Py_ssize_t " << param_name << "_len;\n"
|
|
<< "wchar_t *" << param_name << "_str;\n"
|
|
<< "std::wstring " << param_name << "_wstr;\n"
|
|
<< "if (" << param_name << " != nullptr) {\n"
|
|
<< "#if PY_VERSION_HEX >= 0x03030000\n"
|
|
<< " " << param_name << "_str = PyUnicode_AsWideCharString("
|
|
<< param_name << ", &" << param_name << "_len);\n"
|
|
<< "#else\n"
|
|
<< " " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
|
|
<< " " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
|
|
<< "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
|
|
<< "#endif\n"
|
|
<< " " << param_name << "_wstr.assign(" << param_name << "_str, " << param_name << "_len);\n"
|
|
<< "} else {\n"
|
|
<< " " << param_name << "_wstr" << default_expr << ";\n"
|
|
<< "}\n";
|
|
pexpr_string = "std::move(" + param_name + "_wstr)";
|
|
} else {
|
|
extra_convert
|
|
<< "#if PY_VERSION_HEX >= 0x03030000\n"
|
|
<< "Py_ssize_t " << param_name << "_len;\n"
|
|
<< "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString("
|
|
<< param_name << ", &" << param_name << "_len);\n"
|
|
<< "#else\n"
|
|
<< "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
|
|
<< "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
|
|
<< "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
|
|
<< "#endif\n";
|
|
pexpr_string = param_name + "_str, " + param_name + "_len";
|
|
}
|
|
|
|
extra_cleanup
|
|
<< "#if PY_VERSION_HEX >= 0x03030000\n"
|
|
<< "PyMem_Free(" << param_name << "_str);\n"
|
|
<< "#endif\n";
|
|
|
|
expected_params += "unicode";
|
|
|
|
} else { // A regular string.
|
|
if (is_optional) {
|
|
CPPExpression::Type expr_type = default_value->_type;
|
|
if (expr_type == CPPExpression::T_default_construct) {
|
|
// The default string constructor yields an empty string.
|
|
indent(out, indent_level) << "const char *" << param_name << "_str = \"\";\n";
|
|
indent(out, indent_level) << "Py_ssize_t " << param_name << "_len = 0;\n";
|
|
} else {
|
|
// We only get here for string literals, so this should be fine
|
|
indent(out, indent_level) << "const char *" << param_name << "_str"
|
|
<< default_expr << ";\n";
|
|
indent(out, indent_level) << "Py_ssize_t " << param_name << "_len = "
|
|
<< default_value->_str.size() << ";\n";
|
|
}
|
|
} else {
|
|
indent(out, indent_level) << "const char *" << param_name << "_str = nullptr;\n";
|
|
indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n";
|
|
}
|
|
|
|
if (args_type == AT_single_arg) {
|
|
out << "#if PY_MAJOR_VERSION >= 3\n";
|
|
indent(out, indent_level)
|
|
<< param_name << "_str = PyUnicode_AsUTF8AndSize(arg, &"
|
|
<< param_name << "_len);\n";
|
|
out << "#else\n"; // NB. PyString_AsStringAndSize also accepts a PyUnicode.
|
|
indent(out, indent_level) << "if (PyString_AsStringAndSize(arg, (char **)&"
|
|
<< param_name << "_str, &" << param_name << "_len) == -1) {\n";
|
|
indent(out, indent_level + 2) << param_name << "_str = nullptr;\n";
|
|
indent(out, indent_level) << "}\n";
|
|
out << "#endif\n";
|
|
|
|
extra_param_check << " && " << param_name << "_str != nullptr";
|
|
} else {
|
|
format_specifiers += "s#";
|
|
parameter_list += ", &" + param_name
|
|
+ "_str, &" + param_name + "_len";
|
|
}
|
|
|
|
//if (TypeManager::is_const_ptr_to_basic_string_char(orig_type)) {
|
|
// pexpr_string = "&std::string(" + param_name + "_str, " + param_name + "_len)";
|
|
//} else {
|
|
pexpr_string = param_name + "_str, " + param_name + "_len";
|
|
//}
|
|
expected_params += "str";
|
|
}
|
|
// Remember to clear the TypeError that any of the above methods raise.
|
|
clear_error = true;
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_vector_unsigned_char(type)) {
|
|
indent(out, indent_level) << "unsigned char *" << param_name << "_str = nullptr;\n";
|
|
indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n";
|
|
|
|
if (args_type == AT_single_arg) {
|
|
extra_param_check << " && PyBytes_AsStringAndSize(arg, (char **)&"
|
|
<< param_name << "_str, &" << param_name << "_len) >= 0";
|
|
} else {
|
|
format_specifiers += "\" FMTCHAR_BYTES \"#";
|
|
parameter_list += ", &" + param_name + "_str, &" + param_name + "_len";
|
|
}
|
|
|
|
pexpr_string = type->get_local_name(&parser);
|
|
pexpr_string += "(" + param_name + "_str, " + param_name + "_str + " + param_name + "_len" + ")";
|
|
expected_params += "bytes";
|
|
|
|
// Remember to clear the TypeError that any of the above methods raise.
|
|
clear_error = true;
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_scoped_enum(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name;
|
|
if (default_value != nullptr) {
|
|
out << " = nullptr";
|
|
}
|
|
out << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
|
|
CPPEnumType *enum_type = (CPPEnumType *)TypeManager::unwrap(type);
|
|
CPPType *underlying_type = enum_type->get_underlying_type();
|
|
underlying_type = TypeManager::unwrap_const(underlying_type);
|
|
|
|
//indent(out, indent_level);
|
|
//underlying_type->output_instance(out, param_name + "_val", &parser);
|
|
//out << default_expr << ";\n";
|
|
extra_convert << "long " << param_name << "_val";
|
|
|
|
if (default_value != nullptr) {
|
|
extra_convert << " = (long)";
|
|
default_value->output(extra_convert, 0, &parser, false);
|
|
extra_convert <<
|
|
";\nif (" << param_name << " != nullptr) {\n"
|
|
" " << param_name << "_val = Dtool_EnumValue_AsLong(" + param_name + ");\n"
|
|
"}";
|
|
} else {
|
|
extra_convert
|
|
<< ";\n"
|
|
<< param_name << "_val = Dtool_EnumValue_AsLong(" + param_name + ");\n";
|
|
}
|
|
|
|
pexpr_string = "(" + enum_type->get_local_name(&parser) + ")" + param_name + "_val";
|
|
expected_params += classNameFromCppName(enum_type->get_simple_name(), false);
|
|
extra_param_check << " && " << param_name << "_val != -1";
|
|
clear_error = true;
|
|
|
|
} else if (TypeManager::is_bool(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name;
|
|
if (is_optional) {
|
|
CPPExpression::Result res = default_value->evaluate();
|
|
if (res._type != CPPExpression::RT_error) {
|
|
// It's a compile-time constant. Write Py_True or Py_False.
|
|
out << " = " << (res.as_boolean() ? "Py_True" : "Py_False");
|
|
} else {
|
|
// Select Py_True or Py_False at runtime.
|
|
out << " = (";
|
|
default_value->output(out, 0, &parser, false);
|
|
out << ") ? Py_True : Py_False";
|
|
}
|
|
}
|
|
out << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
pexpr_string = "(PyObject_IsTrue(" + param_name + ") != 0)";
|
|
expected_params += "bool";
|
|
|
|
} else if (TypeManager::is_nullptr(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
type_check = "arg == Py_None";
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name << default_expr << ";\n";
|
|
extra_param_check << " && " << param_name << " == Py_None";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
pexpr_string = "nullptr";
|
|
expected_params += "NoneType";
|
|
|
|
} else if (TypeManager::is_char(type)) {
|
|
indent(out, indent_level) << "char " << param_name << default_expr << ";\n";
|
|
|
|
format_specifiers += "c";
|
|
parameter_list += ", &" + param_name;
|
|
|
|
// extra_param_check << " && isascii(" << param_name << ")";
|
|
pexpr_string = "(char) " + param_name;
|
|
expected_params += "char";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_wchar(type)) {
|
|
out << "#if PY_VERSION_HEX >= 0x03020000\n";
|
|
indent(out, indent_level) << "PyObject *" << param_name << ";\n";
|
|
out << "#else\n";
|
|
indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
|
|
out << "#endif\n";
|
|
format_specifiers += "U";
|
|
parameter_list += ", &" + param_name;
|
|
|
|
// We tell it to copy 2 characters, but make sure it only copied one, as
|
|
// a trick to check for the proper length in one go.
|
|
extra_convert << "wchar_t " << param_name << "_chars[2];\n";
|
|
extra_param_check << " && PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_chars, 2) == 1";
|
|
|
|
pexpr_string = param_name + "_chars[0]";
|
|
expected_params += "unicode char";
|
|
only_pyobjects = false;
|
|
clear_error = true;
|
|
|
|
} else if (TypeManager::is_ssize(type)) {
|
|
indent(out, indent_level) << "Py_ssize_t " << param_name << default_expr << ";\n";
|
|
format_specifiers += "n";
|
|
parameter_list += ", &" + param_name;
|
|
expected_params += "int";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_size(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
type_check = "PyLongOrInt_Check(arg)";
|
|
|
|
extra_convert <<
|
|
"size_t arg_val = PyLongOrInt_AsSize_t(arg);\n"
|
|
"#ifndef NDEBUG\n"
|
|
"if (arg_val == (size_t)-1 && _PyErr_OCCURRED()) {\n";
|
|
error_return(extra_convert, 2, return_flags);
|
|
extra_convert <<
|
|
"}\n"
|
|
"#endif\n";
|
|
|
|
pexpr_string = "arg_val";
|
|
|
|
} else {
|
|
// It certainly isn't the exact same thing as size_t, but Py_ssize_t
|
|
// should at least be the same size. The problem with mapping this to
|
|
// unsigned int is that that doesn't work well on 64-bit systems, on
|
|
// which size_t is a 64-bit integer.
|
|
indent(out, indent_level) << "Py_ssize_t " << param_name << default_expr << ";\n";
|
|
format_specifiers += "n";
|
|
parameter_list += ", &" + param_name;
|
|
|
|
extra_convert
|
|
<< "#ifndef NDEBUG\n"
|
|
<< "if (" << param_name << " < 0) {\n";
|
|
|
|
error_raise_return(extra_convert, 2, return_flags, "OverflowError",
|
|
"can't convert negative value %zd to size_t",
|
|
param_name);
|
|
extra_convert
|
|
<< "}\n"
|
|
<< "#endif\n";
|
|
}
|
|
expected_params += "int";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_longlong(type)) {
|
|
// It's not trivial to do overflow checking for a long long, so we
|
|
// simply don't do it.
|
|
if (TypeManager::is_unsigned_longlong(type)) {
|
|
indent(out, indent_level) << "unsigned PY_LONG_LONG " << param_name << default_expr << ";\n";
|
|
format_specifiers += "K";
|
|
} else {
|
|
indent(out, indent_level) << "PY_LONG_LONG " << param_name << default_expr << ";\n";
|
|
format_specifiers += "L";
|
|
}
|
|
parameter_list += ", &" + param_name;
|
|
expected_params += "long";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_unsigned_short(type) ||
|
|
TypeManager::is_unsigned_char(type) || TypeManager::is_signed_char(type)) {
|
|
|
|
if (args_type == AT_single_arg) {
|
|
type_check = "PyLongOrInt_Check(arg)";
|
|
extra_convert
|
|
<< "long " << param_name << " = PyLongOrInt_AS_LONG(arg);\n";
|
|
|
|
pexpr_string = "(" + type->get_local_name(&parser) + ")" + param_name;
|
|
} else {
|
|
indent(out, indent_level) << "long " << param_name << default_expr << ";\n";
|
|
format_specifiers += "l";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
|
|
// The "H" format code, unlike "h", does not do overflow checking, so we
|
|
// have to do it ourselves (except in release builds).
|
|
extra_convert
|
|
<< "#ifndef NDEBUG\n";
|
|
|
|
if (TypeManager::is_unsigned_short(type)) {
|
|
extra_convert << "if (" << param_name << " < 0 || " << param_name << " > USHRT_MAX) {\n";
|
|
error_raise_return(extra_convert, 2, return_flags, "OverflowError",
|
|
"value %ld out of range for unsigned short integer",
|
|
param_name);
|
|
} else if (TypeManager::is_unsigned_char(type)) {
|
|
extra_convert << "if (" << param_name << " < 0 || " << param_name << " > UCHAR_MAX) {\n";
|
|
error_raise_return(extra_convert, 2, return_flags, "OverflowError",
|
|
"value %ld out of range for unsigned byte",
|
|
param_name);
|
|
} else {
|
|
extra_convert << "if (" << param_name << " < SCHAR_MIN || " << param_name << " > SCHAR_MAX) {\n";
|
|
error_raise_return(extra_convert, 2, return_flags, "OverflowError",
|
|
"value %ld out of range for signed byte",
|
|
param_name);
|
|
}
|
|
|
|
extra_convert
|
|
<< "}\n"
|
|
<< "#endif\n";
|
|
|
|
expected_params += "int";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_short(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
type_check = "PyLongOrInt_Check(arg)";
|
|
|
|
// Perform overflow checking in debug builds.
|
|
extra_convert
|
|
<< "long arg_val = PyLongOrInt_AS_LONG(arg);\n"
|
|
<< "#ifndef NDEBUG\n"
|
|
<< "if (arg_val < SHRT_MIN || arg_val > SHRT_MAX) {\n";
|
|
|
|
error_raise_return(extra_convert, 2, return_flags, "OverflowError",
|
|
"value %ld out of range for signed short integer",
|
|
"arg_val");
|
|
extra_convert
|
|
<< "}\n"
|
|
<< "#endif\n";
|
|
|
|
pexpr_string = "(" + type->get_local_name(&parser) + ")arg_val";
|
|
|
|
} else {
|
|
indent(out, indent_level) << "short " << param_name << default_expr << ";\n";
|
|
format_specifiers += "h";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
expected_params += "int";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_unsigned_integer(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
// Windows has 32-bit longs, and Python 2 stores a C long for PyInt
|
|
// internally, so a PyInt wouldn't cover the whole range; that's why
|
|
// we have to accept PyLong as well here.
|
|
type_check = "PyLongOrInt_Check(arg)";
|
|
extra_convert
|
|
<< "unsigned long " << param_name << " = PyLong_AsUnsignedLong(arg);\n";
|
|
pexpr_string = "(" + type->get_local_name(&parser) + ")" + param_name;
|
|
} else {
|
|
indent(out, indent_level) << "unsigned long " << param_name << default_expr << ";\n";
|
|
format_specifiers += "k";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
|
|
// The "I" format code, unlike "i", does not do overflow checking, so we
|
|
// have to do it ourselves (in debug builds). Note that Python 2 stores
|
|
// longs internally, for ints, so we don't do it for Python 2 on
|
|
// Windows, where longs are the same size as ints. BUG: does not catch
|
|
// negative values on Windows when going through the PyArg_ParseTuple
|
|
// case.
|
|
if (!TypeManager::is_long(type)) {
|
|
extra_convert
|
|
<< "#if (SIZEOF_LONG > SIZEOF_INT) && !defined(NDEBUG)\n"
|
|
<< "if (" << param_name << " > UINT_MAX) {\n";
|
|
|
|
error_raise_return(extra_convert, 2, return_flags, "OverflowError",
|
|
"value %lu out of range for unsigned integer",
|
|
param_name);
|
|
|
|
extra_convert
|
|
<< "}\n"
|
|
<< "#endif\n";
|
|
}
|
|
expected_params += "int";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_long(type)) {
|
|
// Signed longs are equivalent to Python's int type.
|
|
if (args_type == AT_single_arg) {
|
|
pexpr_string = "PyLongOrInt_AS_LONG(arg)";
|
|
type_check = "PyLongOrInt_Check(arg)";
|
|
} else {
|
|
indent(out, indent_level) << "long " << param_name << default_expr << ";\n";
|
|
format_specifiers += "l";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
expected_params += "int";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_integer(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
type_check = "PyLongOrInt_Check(arg)";
|
|
|
|
// Perform overflow checking in debug builds. Note that Python 2
|
|
// stores longs internally, for ints, so we don't do it on Windows,
|
|
// where longs are the same size as ints.
|
|
extra_convert
|
|
<< "long arg_val = PyLongOrInt_AS_LONG(arg);\n"
|
|
<< "#if (SIZEOF_LONG > SIZEOF_INT) && !defined(NDEBUG)\n"
|
|
<< "if (arg_val < INT_MIN || arg_val > INT_MAX) {\n";
|
|
|
|
error_raise_return(extra_convert, 2, return_flags, "OverflowError",
|
|
"value %ld out of range for signed integer",
|
|
"arg_val");
|
|
|
|
extra_convert
|
|
<< "}\n"
|
|
<< "#endif\n";
|
|
|
|
pexpr_string = "(" + type->get_local_name(&parser) + ")arg_val";
|
|
|
|
} else {
|
|
indent(out, indent_level) << "int " << param_name << default_expr << ";\n";
|
|
format_specifiers += "i";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
expected_params += "int";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_double(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
pexpr_string = "PyFloat_AsDouble(arg)";
|
|
type_check = "PyNumber_Check(arg)";
|
|
} else {
|
|
indent(out, indent_level) << "double " << param_name << default_expr << ";\n";
|
|
format_specifiers += "d";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
expected_params += "double";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_float(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
pexpr_string = "(" + type->get_local_name(&parser) + ")PyFloat_AsDouble(arg)";
|
|
type_check = "PyNumber_Check(arg)";
|
|
} else {
|
|
indent(out, indent_level) << "float " << param_name << default_expr << ";\n";
|
|
format_specifiers += "f";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
expected_params += "float";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_const_char_pointer(type)) {
|
|
indent(out, indent_level) << "const char *" << param_name << default_expr << ";\n";
|
|
format_specifiers += "z";
|
|
parameter_list += ", &" + param_name;
|
|
expected_params += "buffer";
|
|
only_pyobjects = false;
|
|
|
|
} else if (TypeManager::is_pointer_to_PyTypeObject(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name << default_expr << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
pexpr_string = param_name;
|
|
}
|
|
extra_param_check << " && PyType_Check(" << param_name << ")";
|
|
pexpr_string = "(PyTypeObject *)" + param_name;
|
|
expected_params += "type";
|
|
|
|
// It's reasonable to assume that a function taking a PyTypeObject might
|
|
// also throw a TypeError if the type is incorrect.
|
|
may_raise_typeerror = true;
|
|
|
|
} else if (TypeManager::is_pointer_to_PyStringObject(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
// This is a single-arg function, so there's no need to convert
|
|
// anything.
|
|
param_name = "arg";
|
|
type_check = "PyString_Check(arg)";
|
|
pexpr_string = "(PyStringObject *)" + param_name;
|
|
} else {
|
|
indent(out, indent_level) << "PyStringObject *" << param_name << default_expr << ";\n";
|
|
format_specifiers += "S";
|
|
parameter_list += ", &" + param_name;
|
|
pexpr_string = param_name;
|
|
}
|
|
expected_params += "string";
|
|
|
|
} else if (TypeManager::is_pointer_to_PyUnicodeObject(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
// This is a single-arg function, so there's no need to convert
|
|
// anything.
|
|
param_name = "arg";
|
|
type_check = "PyUnicode_Check(arg)";
|
|
pexpr_string = "(PyUnicodeObject *)" + param_name;
|
|
} else {
|
|
indent(out, indent_level) << "PyUnicodeObject *" << param_name << default_expr << ";\n";
|
|
format_specifiers += "U";
|
|
parameter_list += ", &" + param_name;
|
|
pexpr_string = param_name;
|
|
}
|
|
expected_params += "unicode";
|
|
|
|
} else if (TypeManager::is_pointer_to_PyObject(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
// This is a single-arg function, so there's no need to convert
|
|
// anything.
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name << default_expr << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
pexpr_string = param_name;
|
|
expected_params += "object";
|
|
|
|
// It's reasonable to assume that a function taking a PyObject might
|
|
// also throw a TypeError if the type is incorrect.
|
|
may_raise_typeerror = true;
|
|
|
|
} else if (TypeManager::is_pointer_to_Py_buffer(type)) {
|
|
min_version = 0x02060000; // Only support this remap in version 2.6+.
|
|
|
|
if (args_type == AT_single_arg) {
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
indent(out, indent_level) << "Py_buffer " << param_name << "_view;\n";
|
|
|
|
if (is_optional) {
|
|
indent(out, indent_level) << "Py_buffer *" << param_name << "_viewp;\n";
|
|
|
|
extra_convert
|
|
<< "bool " << param_name << "_success;\n"
|
|
<< "if (" << param_name << " != nullptr) {\n"
|
|
<< " " << param_name << "_success = (PyObject_GetBuffer("
|
|
<< param_name << ", &" << param_name << "_view, PyBUF_FULL) == 0);\n"
|
|
<< " " << param_name << "_viewp = &" << param_name << "_view;\n"
|
|
<< "} else {\n"
|
|
<< " " << param_name << "_viewp" << default_expr << ";\n"
|
|
<< " " << param_name << "_success = true;\n"
|
|
<< "}\n";
|
|
|
|
extra_param_check << " && " << param_name << "_success";
|
|
pexpr_string = param_name + "_viewp";
|
|
extra_cleanup << "if (" << param_name << " != nullptr) PyBuffer_Release(&" << param_name << "_view);\n";
|
|
} else {
|
|
extra_param_check << " && PyObject_GetBuffer("
|
|
<< param_name << ", &"
|
|
<< param_name << "_view, PyBUF_FULL) == 0";
|
|
pexpr_string = "&" + param_name + "_view";
|
|
extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n";
|
|
}
|
|
expected_params += "buffer";
|
|
may_raise_typeerror = true;
|
|
clear_error = true;
|
|
|
|
} else if (TypeManager::is_pointer_to_simple(type)) {
|
|
if (args_type == AT_single_arg) {
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
indent(out, indent_level) << "Py_buffer " << param_name << "_view;\n";
|
|
|
|
// Unravel the type to determine its properties.
|
|
int array_len = -1;
|
|
bool is_const = true;
|
|
CPPSimpleType *simple = nullptr;
|
|
CPPType *unwrap = TypeManager::unwrap_const_reference(type);
|
|
if (unwrap != nullptr) {
|
|
CPPArrayType *array_type = unwrap->as_array_type();
|
|
CPPPointerType *pointer_type = unwrap->as_pointer_type();
|
|
|
|
if (array_type != nullptr) {
|
|
if (array_type->_bounds != nullptr) {
|
|
array_len = array_type->_bounds->evaluate().as_integer();
|
|
}
|
|
unwrap = array_type->_element_type;
|
|
} else if (pointer_type != nullptr) {
|
|
unwrap = pointer_type->_pointing_at;
|
|
}
|
|
|
|
CPPConstType *const_type = unwrap->as_const_type();
|
|
if (const_type != nullptr) {
|
|
unwrap = const_type->_wrapped_around;
|
|
} else {
|
|
is_const = false;
|
|
}
|
|
|
|
while (unwrap->get_subtype() == CPPDeclaration::ST_typedef) {
|
|
unwrap = unwrap->as_typedef_type()->_type;
|
|
}
|
|
simple = unwrap->as_simple_type();
|
|
}
|
|
|
|
// Determine the format, so we can check the type of the buffer we get.
|
|
char format_chr = 'B';
|
|
|
|
switch (simple->_type) {
|
|
case CPPSimpleType::T_char:
|
|
if (simple->_flags & CPPSimpleType::F_unsigned) {
|
|
format_chr = 'B';
|
|
} else if (simple->_flags & CPPSimpleType::F_signed) {
|
|
format_chr = 'b';
|
|
} else {
|
|
format_chr = 'c';
|
|
}
|
|
break;
|
|
|
|
case CPPSimpleType::T_int:
|
|
if (simple->_flags & CPPSimpleType::F_longlong) {
|
|
format_chr = 'q';
|
|
} else if (simple->_flags & CPPSimpleType::F_long) {
|
|
format_chr = 'l';
|
|
} else if (simple->_flags & CPPSimpleType::F_short) {
|
|
format_chr = 'h';
|
|
} else {
|
|
format_chr = 'i';
|
|
}
|
|
|
|
if (simple->_flags & CPPSimpleType::F_unsigned) {
|
|
format_chr &= 0x5f; // Uppercase
|
|
}
|
|
break;
|
|
|
|
case CPPSimpleType::T_float:
|
|
format_chr = 'f';
|
|
break;
|
|
|
|
case CPPSimpleType::T_double:
|
|
format_chr = 'd';
|
|
break;
|
|
|
|
default:
|
|
nout << "Warning: cannot determine buffer format string for type "
|
|
<< type->get_local_name(&parser)
|
|
<< " (simple type " << *simple << ")\n";
|
|
extra_param_check << " && false";
|
|
}
|
|
|
|
const char *flags;
|
|
if (format_chr == 'B') {
|
|
if (is_const) {
|
|
flags = "PyBUF_SIMPLE";
|
|
} else {
|
|
flags = "PyBUF_WRITABLE";
|
|
}
|
|
} else if (is_const) {
|
|
flags = "PyBUF_FORMAT";
|
|
} else {
|
|
flags = "PyBUF_FORMAT | PyBUF_WRITABLE";
|
|
}
|
|
|
|
extra_param_check << " && PyObject_GetBuffer(" << param_name << ", &"
|
|
<< param_name << "_view, " << flags << ") == 0";
|
|
|
|
if (format_chr != 'B') {
|
|
extra_param_check
|
|
<< " && " << param_name << "_view.format[0] == '" << format_chr << "'"
|
|
<< " && " << param_name << "_view.format[1] == 0";
|
|
}
|
|
if (array_len != -1) {
|
|
extra_param_check
|
|
<< " && " << param_name << "_view.len == " << array_len;
|
|
}
|
|
|
|
pexpr_string = "(" + simple->get_local_name(&parser) + " *)" +
|
|
param_name + "_view.buf";
|
|
|
|
extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n";
|
|
expected_params += "buffer";
|
|
clear_error = true;
|
|
|
|
} else if (TypeManager::is_pointer(type)) {
|
|
CPPType *obj_type = TypeManager::unwrap(TypeManager::resolve_type(type));
|
|
bool const_ok = !TypeManager::is_non_const_pointer_or_ref(orig_type);
|
|
|
|
if (TypeManager::is_const_pointer_or_ref(orig_type)) {
|
|
expected_params += "const ";
|
|
// } else { expected_params += "non-const ";
|
|
}
|
|
string expected_class_name = classNameFromCppName(obj_type->get_simple_name(), false);
|
|
expected_params += expected_class_name;
|
|
|
|
if (args_type == AT_single_arg) {
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
|
|
// If the default value is NULL, we also accept a None value.
|
|
bool maybe_none = false;
|
|
if (default_value != nullptr && (return_flags & RF_coerced) == 0 &&
|
|
TypeManager::is_pointer(orig_type)) {
|
|
CPPExpression::Result res = param->get_default_value()->evaluate();
|
|
if (res._type == CPPExpression::RT_integer ||
|
|
res._type == CPPExpression::RT_pointer) {
|
|
if (res.as_integer() == 0) {
|
|
maybe_none = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
string class_name = obj_type->get_local_name(&parser);
|
|
|
|
// need to a forward scope for this class..
|
|
if (!isExportThisRun(obj_type)) {
|
|
_external_imports.insert(TypeManager::resolve_type(obj_type));
|
|
}
|
|
|
|
string this_class_name;
|
|
string method_prefix;
|
|
if (remap->_cpptype) {
|
|
this_class_name = remap->_cpptype->get_simple_name();
|
|
method_prefix = classNameFromCppName(this_class_name, false) + string(".");
|
|
}
|
|
|
|
if (coercion_possible &&
|
|
has_coerce_constructor(obj_type->as_struct_type())) {
|
|
// Call the coercion function directly, which will try to extract the
|
|
// pointer directly before trying coercion.
|
|
string coerce_call;
|
|
|
|
if (TypeManager::is_reference_count(obj_type)) {
|
|
// We use a PointerTo to handle the management here. It's cleaner
|
|
// that way.
|
|
if (default_expr == " = 0" || default_expr == " = nullptr") {
|
|
default_expr.clear();
|
|
}
|
|
if (TypeManager::is_const_pointer_to_anything(type)) {
|
|
extra_convert
|
|
<< "CPT(" << class_name << ") " << param_name << "_this"
|
|
<< default_expr << ";\n";
|
|
|
|
coerce_call = "Dtool_ConstCoerce_" + make_safe_name(class_name) +
|
|
"(" + param_name + ", " + param_name + "_this)";
|
|
} else {
|
|
extra_convert
|
|
<< "PT(" << class_name << ") " << param_name << "_this"
|
|
<< default_expr << ";\n";
|
|
|
|
coerce_call = "Dtool_Coerce_" + make_safe_name(class_name) +
|
|
"(" + param_name + ", " + param_name + "_this)";
|
|
}
|
|
|
|
// Use move constructor when available for functions that take an
|
|
// actual PointerTo. This eliminates an unref()ref() pair.
|
|
pexpr_string = "std::move(" + param_name + "_this)";
|
|
|
|
} else {
|
|
// This is a move-assignable type, such as TypeHandle or LVecBase4.
|
|
obj_type->output_instance(extra_convert, param_name + "_local", &parser);
|
|
extra_convert << ";\n";
|
|
|
|
type->output_instance(extra_convert, param_name + "_this", &parser);
|
|
|
|
if (is_optional && maybe_none) {
|
|
extra_convert
|
|
<< default_expr << ";\n"
|
|
<< "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n"
|
|
<< " " << param_name << "_this";
|
|
} else if (is_optional) {
|
|
if (TypeManager::is_pointer(orig_type)) {
|
|
extra_convert << default_expr;
|
|
}
|
|
extra_convert
|
|
<< ";\n"
|
|
<< "if (" << param_name << " != nullptr) {\n"
|
|
<< " " << param_name << "_this";
|
|
} else if (maybe_none) {
|
|
extra_convert
|
|
<< " = nullptr;\n"
|
|
<< "if (" << param_name << " != Py_None) {\n"
|
|
<< " " << param_name << "_this";
|
|
}
|
|
|
|
extra_convert << " = Dtool_Coerce_" + make_safe_name(class_name) +
|
|
"(" + param_name + ", " + param_name + "_local);\n";
|
|
|
|
if (is_optional && !TypeManager::is_pointer(orig_type)) {
|
|
extra_convert
|
|
<< "} else {\n"
|
|
<< " " << param_name << "_local" << default_expr << ";\n"
|
|
<< " " << param_name << "_this = &" << param_name << "_local;\n"
|
|
<< "}\n";
|
|
} else if (is_optional || maybe_none) {
|
|
extra_convert << "}\n";
|
|
}
|
|
|
|
coerce_call = "(" + param_name + "_this != nullptr)";
|
|
|
|
pexpr_string = param_name + "_this";
|
|
}
|
|
|
|
if (report_errors) {
|
|
// We were asked to report any errors. Let's do it.
|
|
if (is_optional && maybe_none) {
|
|
extra_convert << "if (" << param_name << " != nullptr && " << param_name << " != Py_None && !" << coerce_call << ") {\n";
|
|
} else if (is_optional) {
|
|
extra_convert << "if (" << param_name << " != nullptr && !" << coerce_call << ") {\n";
|
|
} else if (maybe_none) {
|
|
extra_convert << "if (" << param_name << " != Py_None && !" << coerce_call << ") {\n";
|
|
} else {
|
|
extra_convert << "if (!" << coerce_call << ") {\n";
|
|
}
|
|
|
|
// Display error like: Class.func() argument 0 must be A, not B
|
|
if ((return_flags & ~RF_pyobject) == RF_err_null) {
|
|
// Dtool_Raise_ArgTypeError returns NULL already
|
|
extra_convert << " return ";
|
|
} else {
|
|
extra_convert << " ";
|
|
}
|
|
extra_convert
|
|
<< "Dtool_Raise_ArgTypeError(" << param_name << ", "
|
|
<< pn << ", \"" << method_prefix
|
|
<< methodNameFromCppName(remap, this_class_name, false)
|
|
<< "\", \"" << expected_class_name << "\");\n";
|
|
|
|
if ((return_flags & ~RF_pyobject) != RF_err_null) {
|
|
error_return(extra_convert, 2, return_flags);
|
|
}
|
|
extra_convert << "}\n";
|
|
|
|
} else if (is_optional && maybe_none) {
|
|
extra_param_check << " && (" << param_name << " == nullptr || " << param_name << " == Py_None || " << coerce_call << ")";
|
|
|
|
} else if (is_optional) {
|
|
extra_param_check << " && (" << param_name << " == nullptr || " << coerce_call << ")";
|
|
|
|
} else if (maybe_none) {
|
|
extra_param_check << " && (" << param_name << " == Py_None || " << coerce_call << ")";
|
|
|
|
} else {
|
|
extra_param_check << " && " << coerce_call;
|
|
}
|
|
|
|
} else { // The regular, non-coercion case.
|
|
type->output_instance(extra_convert, param_name + "_this", &parser);
|
|
if (is_optional && maybe_none) {
|
|
// This parameter has a default value of nullptr, so we need to also
|
|
// allow passing in None.
|
|
extra_convert
|
|
<< default_expr << ";\n"
|
|
<< "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n"
|
|
<< " " << param_name << "_this";
|
|
}
|
|
else if (is_optional && !TypeManager::is_pointer(orig_type) && !default_value->is_lvalue()) {
|
|
// Most annoying case, where we have to use an rvalue reference to
|
|
// extend the lifetime of the default argument. In this case, the
|
|
// default expression is invoked even if not used.
|
|
extra_convert << ";\n";
|
|
if (TypeManager::is_const_pointer_to_anything(type)) {
|
|
extra_convert << "const ";
|
|
obj_type->output_instance(extra_convert, "&" + param_name + "_ref", &parser);
|
|
} else {
|
|
obj_type->output_instance(extra_convert, "&&" + param_name + "_ref", &parser);
|
|
}
|
|
extra_convert
|
|
<< default_expr << ";\n"
|
|
<< "if (" << param_name << " == nullptr) {\n"
|
|
<< " " << param_name << "_this = &" << param_name << "_ref;\n"
|
|
<< "} else {\n"
|
|
<< " " << param_name << "_this";
|
|
}
|
|
else if (is_optional) {
|
|
// General case where the default argument is either an lvalue or a
|
|
// pointer.
|
|
extra_convert
|
|
<< ";\n"
|
|
<< "if (" << param_name << " == nullptr) {\n"
|
|
<< " " << param_name << "_this = ";
|
|
if (TypeManager::is_pointer(orig_type)) {
|
|
default_value->output(extra_convert, 0, &parser, false);
|
|
extra_convert << ";\n";
|
|
} else {
|
|
// The rvalue case was handled above, so this is an lvalue, which
|
|
// means we can safely take a reference to it.
|
|
extra_convert << "&(";
|
|
default_value->output(extra_convert, 0, &parser, false);
|
|
extra_convert << ");\n";
|
|
}
|
|
extra_convert
|
|
<< "} else {\n"
|
|
<< " " << param_name << "_this";
|
|
}
|
|
else if (maybe_none) {
|
|
// No default argument, but we still need to check for None.
|
|
extra_convert
|
|
<< " = nullptr;\n"
|
|
<< "if (" << param_name << " != Py_None) {\n"
|
|
<< " " << param_name << "_this";
|
|
}
|
|
|
|
if (const_ok && !report_errors) {
|
|
// This function does the same thing in this case and is slightly
|
|
// simpler. But maybe we should just reorganize these functions
|
|
// entirely?
|
|
extra_convert << " = nullptr;\n";
|
|
int indent_level = (is_optional || maybe_none) ? 2 : 0;
|
|
indent(extra_convert, indent_level)
|
|
<< "DtoolInstance_GetPointer(" << param_name
|
|
<< ", " << param_name << "_this"
|
|
<< ", *Dtool_Ptr_" << make_safe_name(class_name)
|
|
<< ");\n";
|
|
} else {
|
|
extra_convert << std::boolalpha
|
|
<< " = (" << class_name << " *)"
|
|
<< "DTOOL_Call_GetPointerThisClass(" << param_name
|
|
<< ", Dtool_Ptr_" << make_safe_name(class_name)
|
|
<< ", " << pn << ", \""
|
|
<< method_prefix << methodNameFromCppName(remap, this_class_name, false)
|
|
<< "\", " << const_ok << ", " << report_errors << ");\n";
|
|
}
|
|
|
|
if (is_optional && maybe_none) {
|
|
extra_convert << "}\n";
|
|
extra_param_check << " && (" << param_name << " == nullptr || " << param_name << " == Py_None || " << param_name << "_this != nullptr)";
|
|
} else if (is_optional) {
|
|
extra_convert << "}\n";
|
|
extra_param_check << " && (" << param_name << " == nullptr || " << param_name << "_this != nullptr)";
|
|
} else if (maybe_none) {
|
|
extra_convert << "}\n";
|
|
extra_param_check << " && (" << param_name << " == Py_None || " << param_name << "_this != nullptr)";
|
|
} else {
|
|
extra_param_check << " && " << param_name << "_this != nullptr";
|
|
}
|
|
|
|
pexpr_string = param_name + "_this";
|
|
}
|
|
|
|
} else {
|
|
// Ignore a parameter.
|
|
if (args_type == AT_single_arg) {
|
|
param_name = "arg";
|
|
} else {
|
|
indent(out, indent_level) << "PyObject *" << param_name << ";\n";
|
|
format_specifiers += "O";
|
|
parameter_list += ", &" + param_name;
|
|
}
|
|
expected_params += "any";
|
|
}
|
|
|
|
if (!reported_name.empty()) {
|
|
expected_params += " " + reported_name;
|
|
}
|
|
pexprs.push_back(pexpr_string);
|
|
}
|
|
expected_params += ")\n";
|
|
|
|
if (min_version > 0) {
|
|
out << "#if PY_VERSION_HEX >= 0x" << hex << min_version << dec << "\n";
|
|
}
|
|
|
|
// Track how many curly braces we've opened.
|
|
short open_scopes = 0;
|
|
|
|
if (!type_check.empty() && args_type == AT_single_arg) {
|
|
indent(out, indent_level)
|
|
<< "if (" << type_check << ") {\n";
|
|
|
|
++open_scopes;
|
|
indent_level += 2;
|
|
|
|
} else if (!format_specifiers.empty()) {
|
|
string method_name = methodNameFromCppName(remap, "", false);
|
|
|
|
switch (args_type) {
|
|
case AT_keyword_args:
|
|
// Wrapper takes a varargs tuple and a keyword args dict.
|
|
if (has_keywords) {
|
|
if (only_pyobjects && max_num_args == 1) {
|
|
// But we are only expecting one object arg, which is an easy common
|
|
// case we have implemented ourselves.
|
|
if (min_num_args == 1) {
|
|
indent(out, indent_level)
|
|
<< "if (Dtool_ExtractArg(&" << param_name << ", args, kwds, " << keyword_list << ")) {\n";
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< "if (Dtool_ExtractOptionalArg(&" << param_name << ", args, kwds, " << keyword_list << ")) {\n";
|
|
}
|
|
} else {
|
|
// We have to use the more expensive PyArg_ParseTupleAndKeywords.
|
|
clear_error = true;
|
|
indent(out, indent_level)
|
|
<< "static const char *keyword_list[] = {" << keyword_list << ", nullptr};\n";
|
|
indent(out, indent_level)
|
|
<< "if (PyArg_ParseTupleAndKeywords(args, kwds, \""
|
|
<< format_specifiers << ":" << method_name
|
|
<< "\", (char **)keyword_list" << parameter_list << ")) {\n";
|
|
}
|
|
|
|
} else if (only_pyobjects) {
|
|
// This function actually has no named parameters, so let's not take
|
|
// any keyword arguments.
|
|
if (max_num_args == 1) {
|
|
if (min_num_args == 1) {
|
|
indent(out, indent_level)
|
|
<< "if (Dtool_ExtractArg(&" << param_name << ", args, kwds)) {\n";
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< "if (Dtool_ExtractOptionalArg(&" << param_name << ", args, kwds)) {\n";
|
|
}
|
|
} else if (max_num_args == 0) {
|
|
indent(out, indent_level)
|
|
<< "if (Dtool_CheckNoArgs(args, kwds)) {\n";
|
|
} else {
|
|
clear_error = true;
|
|
indent(out, indent_level)
|
|
<< "if ((kwds == nullptr || PyDict_Size(kwds) == 0) && PyArg_UnpackTuple(args, \""
|
|
<< methodNameFromCppName(remap, "", false)
|
|
<< "\", " << min_num_args << ", " << max_num_args
|
|
<< parameter_list << ")) {\n";
|
|
}
|
|
|
|
} else {
|
|
clear_error = true;
|
|
indent(out, indent_level)
|
|
<< "if ((kwds == nullptr || PyDict_Size(kwds) == 0) && PyArg_ParseTuple(args, \""
|
|
<< format_specifiers << ":" << method_name
|
|
<< "\"" << parameter_list << ")) {\n";
|
|
}
|
|
|
|
++open_scopes;
|
|
indent_level += 2;
|
|
break;
|
|
|
|
case AT_varargs:
|
|
// Wrapper takes a varargs tuple.
|
|
if (only_pyobjects) {
|
|
// All parameters are PyObject*, so we can use the slightly more
|
|
// efficient PyArg_UnpackTuple function instead.
|
|
if (min_num_args == 1 && max_num_args == 1) {
|
|
indent(out, indent_level)
|
|
<< "if (PyTuple_GET_SIZE(args) == 1) {\n";
|
|
indent(out, indent_level + 2)
|
|
<< param_name << " = PyTuple_GET_ITEM(args, 0);\n";
|
|
} else {
|
|
clear_error = true;
|
|
indent(out, indent_level)
|
|
<< "if (PyArg_UnpackTuple(args, \""
|
|
<< methodNameFromCppName(remap, "", false)
|
|
<< "\", " << min_num_args << ", " << max_num_args
|
|
<< parameter_list << ")) {\n";
|
|
}
|
|
|
|
} else {
|
|
clear_error = true;
|
|
indent(out, indent_level)
|
|
<< "if (PyArg_ParseTuple(args, \""
|
|
<< format_specifiers << ":" << method_name
|
|
<< "\"" << parameter_list << ")) {\n";
|
|
}
|
|
++open_scopes;
|
|
indent_level += 2;
|
|
break;
|
|
|
|
case AT_single_arg:
|
|
// Single argument. If not a PyObject*, use PyArg_Parse.
|
|
if (!only_pyobjects && format_specifiers != "O") {
|
|
indent(out, indent_level)
|
|
<< "if (PyArg_Parse(arg, \"" << format_specifiers << ":"
|
|
<< method_name << "\"" << parameter_list << ")) {\n";
|
|
|
|
++open_scopes;
|
|
clear_error = true;
|
|
indent_level += 2;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (extra_convert.is_text_available()) {
|
|
string line = extra_convert.get_line();
|
|
if (line.size() == 0 || line[0] == '#') {
|
|
out << line << "\n";
|
|
} else {
|
|
indent(out, indent_level) << line << "\n";
|
|
}
|
|
}
|
|
|
|
string extra_param_check_str = extra_param_check.str();
|
|
if (!extra_param_check_str.empty()) {
|
|
indent(out, indent_level)
|
|
<< "if (" << extra_param_check_str.substr(4) << ") {\n";
|
|
|
|
++open_scopes;
|
|
indent_level += 2;
|
|
}
|
|
|
|
if (is_constructor && !remap->_has_this &&
|
|
(remap->_flags & FunctionRemap::F_explicit_self) != 0) {
|
|
// If we'll be passing "self" to the constructor, we need to pre-
|
|
// initialize it here. Unfortunately, we can't pre-load the "this"
|
|
// pointer, but the constructor itself can do this.
|
|
|
|
CPPType *orig_type = remap->_return_type->get_orig_type();
|
|
TypeIndex type_index = builder.get_type(TypeManager::unwrap(TypeManager::resolve_type(orig_type)), false);
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
|
|
indent(out, indent_level)
|
|
<< "// Pre-initialize self for the constructor\n";
|
|
|
|
if (!is_constructor || (return_flags & RF_int) == 0) {
|
|
// This is not a constructor, but somehow we landed up here at a static
|
|
// method requiring a 'self' pointer. This happens in coercion
|
|
// constructors in particular. We'll have to create a temporary
|
|
// PyObject instance to pass to it.
|
|
|
|
indent(out, indent_level)
|
|
<< "PyObject *self = Dtool_new_"
|
|
<< make_safe_name(itype.get_scoped_name()) << "(&"
|
|
<< CLASS_PREFIX << make_safe_name(itype.get_scoped_name())
|
|
<< "._PyType, nullptr, nullptr);\n";
|
|
|
|
extra_cleanup << "PyObject_Del(self);\n";
|
|
} else {
|
|
// XXX rdb: this isn't needed, is it, because tp_new already initializes
|
|
// the instance?
|
|
indent(out, indent_level)
|
|
<< "DTool_PyInit_Finalize(self, nullptr, &"
|
|
<< CLASS_PREFIX << make_safe_name(itype.get_scoped_name())
|
|
<< ", false, false);\n";
|
|
}
|
|
}
|
|
|
|
string return_expr;
|
|
|
|
if (remap->_blocking) {
|
|
// With SIMPLE_THREADS, it's important that we never release the
|
|
// interpreter lock.
|
|
out << "#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)\n";
|
|
indent(out, indent_level)
|
|
<< "PyThreadState *_save;\n";
|
|
indent(out, indent_level)
|
|
<< "Py_UNBLOCK_THREADS\n";
|
|
out << "#endif // HAVE_THREADS && !SIMPLE_THREADS\n";
|
|
}
|
|
if (track_interpreter) {
|
|
indent(out, indent_level) << "in_interpreter = 0;\n";
|
|
}
|
|
|
|
// If the function returns a pointer that we may need to manage, we store it
|
|
// in a temporary return_value variable and set this to true.
|
|
bool manage_return = false;
|
|
|
|
if (remap->_return_type->new_type_is_atomic_string()) {
|
|
// Treat strings as a special case. We don't want to format the return
|
|
// expression.
|
|
return_expr = remap->call_function(out, indent_level, false, container, pexprs);
|
|
CPPType *type = remap->_return_type->get_orig_type();
|
|
indent(out, indent_level);
|
|
type->output_instance(out, "return_value", &parser);
|
|
out << " = " << return_expr << ";\n";
|
|
manage_return = remap->_return_value_needs_management;
|
|
return_expr = "return_value";
|
|
|
|
} else if ((return_flags & RF_coerced) != 0 && !TypeManager::is_reference_count(remap->_cpptype)) {
|
|
// Another special case is the coerce constructor for a trivial type. We
|
|
// don't want to invoke "operator new" unnecessarily.
|
|
if (is_constructor && remap->_extension) {
|
|
// Extension constructors are a special case, as usual.
|
|
indent(out, indent_level)
|
|
<< remap->get_call_str("&coerced", pexprs) << ";\n";
|
|
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< "coerced = " << remap->get_call_str(container, pexprs) << ";\n";
|
|
}
|
|
return_expr = "&coerced";
|
|
|
|
} else {
|
|
// The general case; an ordinary constructor or function.
|
|
return_expr = remap->call_function(out, indent_level, true, container, pexprs);
|
|
|
|
if (return_flags & RF_self) {
|
|
// We won't be using the return value, anyway.
|
|
return_expr.clear();
|
|
}
|
|
|
|
if (!return_expr.empty()) {
|
|
manage_return = remap->_return_value_needs_management;
|
|
CPPType *type = remap->_return_type->get_temporary_type();
|
|
indent(out, indent_level);
|
|
type->output_instance(out, "return_value", &parser);
|
|
out << " = " << return_expr << ";\n";
|
|
return_expr = "return_value";
|
|
}
|
|
}
|
|
|
|
// Clean up any memory we might have allocate for parsing the parameters.
|
|
while (extra_cleanup.is_text_available()) {
|
|
string line = extra_cleanup.get_line();
|
|
if (line.size() == 0 || line[0] == '#') {
|
|
out << line << "\n";
|
|
} else {
|
|
indent(out, indent_level) << line << "\n";
|
|
}
|
|
}
|
|
|
|
if (track_interpreter) {
|
|
indent(out, indent_level) << "in_interpreter = 1;\n";
|
|
}
|
|
if (remap->_blocking) {
|
|
out << "#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)\n";
|
|
indent(out, indent_level)
|
|
<< "Py_BLOCK_THREADS\n";
|
|
out << "#endif // HAVE_THREADS && !SIMPLE_THREADS\n";
|
|
}
|
|
|
|
if (manage_return) {
|
|
// If a constructor returns NULL, that means allocation failed.
|
|
if (remap->_return_type->return_value_needs_management()) {
|
|
indent(out, indent_level) << "if (return_value == nullptr) {\n";
|
|
if ((return_flags & ~RF_pyobject) == RF_err_null) {
|
|
// PyErr_NoMemory returns NULL, so allow tail call elimination.
|
|
indent(out, indent_level) << " return PyErr_NoMemory();\n";
|
|
} else {
|
|
indent(out, indent_level) << " PyErr_NoMemory();\n";
|
|
error_return(out, indent_level + 2, return_flags);
|
|
}
|
|
indent(out, indent_level) << "}\n";
|
|
}
|
|
|
|
return_expr = manage_return_value(out, indent_level, remap, "return_value");
|
|
return_expr = remap->_return_type->temporary_to_return(return_expr);
|
|
}
|
|
|
|
// How could we raise a TypeError if we don't take any args?
|
|
if (args_type == AT_no_args || max_num_args == 0) {
|
|
may_raise_typeerror = false;
|
|
}
|
|
|
|
// If a function takes a PyObject* argument, it would be a good idea to
|
|
// always check for exceptions.
|
|
if (may_raise_typeerror) {
|
|
check_exceptions = true;
|
|
}
|
|
|
|
// Generated getters and setters don't raise exceptions or asserts since
|
|
// they don't contain any code.
|
|
if (remap->_type == FunctionRemap::T_getter ||
|
|
remap->_type == FunctionRemap::T_setter) {
|
|
check_exceptions = false;
|
|
}
|
|
|
|
// The most common case of the below logic is consolidated in a single
|
|
// function, as another way to reduce code bloat. Sigh.
|
|
if (check_exceptions && (!may_raise_typeerror || report_errors) &&
|
|
watch_asserts && (return_flags & RF_coerced) == 0) {
|
|
|
|
if (return_flags & RF_decref_args) {
|
|
indent(out, indent_level) << "Py_DECREF(args);\n";
|
|
return_flags &= ~RF_decref_args;
|
|
}
|
|
|
|
// An even specialer special case for functions with void return or bool
|
|
// return. We have our own functions that do all this in a single
|
|
// function call, so it should reduce the amount of code output while not
|
|
// being any slower.
|
|
bool return_null = (return_flags & RF_pyobject) != 0 &&
|
|
(return_flags & RF_err_null) != 0;
|
|
if (return_null && return_expr.empty()) {
|
|
indent(out, indent_level)
|
|
<< "return Dtool_Return_None();\n";
|
|
|
|
// Reset the return value bit so that the code below doesn't generate
|
|
// the return statement a second time.
|
|
return_flags &= ~RF_pyobject;
|
|
|
|
} else if (return_null && TypeManager::is_bool(remap->_return_type->get_new_type())) {
|
|
indent(out, indent_level)
|
|
<< "return Dtool_Return_Bool(" << return_expr << ");\n";
|
|
return_flags &= ~RF_pyobject;
|
|
|
|
} else if (return_null && TypeManager::is_pointer_to_PyObject(remap->_return_type->get_new_type())) {
|
|
indent(out, indent_level)
|
|
<< "return Dtool_Return(" << return_expr << ");\n";
|
|
return_flags &= ~RF_pyobject;
|
|
|
|
} else {
|
|
indent(out, indent_level)
|
|
<< "if (Dtool_CheckErrorOccurred()) {\n";
|
|
|
|
if (manage_return) {
|
|
delete_return_value(out, indent_level + 2, remap, return_expr);
|
|
}
|
|
error_return(out, indent_level + 2, return_flags);
|
|
|
|
indent(out, indent_level) << "}\n";
|
|
}
|
|
|
|
} else {
|
|
if (check_exceptions) {
|
|
// Check if a Python exception has occurred. We only do this when
|
|
// check_exception is set. If report_errors is set, this method must
|
|
// terminate on error.
|
|
if (!may_raise_typeerror || report_errors) {
|
|
indent(out, indent_level)
|
|
<< "if (_PyErr_OCCURRED()) {\n";
|
|
} else {
|
|
// If a method is some extension method that takes a PyObject*, and it
|
|
// raised a TypeError, continue. The documentation tells us not to
|
|
// compare the result of PyErr_Occurred against a specific exception
|
|
// type. However, in our case, this seems okay because we know that
|
|
// the TypeError we want to catch here is going to be generated by a
|
|
// PyErr_SetString call, not by user code.
|
|
indent(out, indent_level)
|
|
<< "PyObject *exception = _PyErr_OCCURRED();\n";
|
|
indent(out, indent_level)
|
|
<< "if (exception == PyExc_TypeError) {\n";
|
|
indent(out, indent_level)
|
|
<< " // TypeError raised; continue to next overload type.\n";
|
|
indent(out, indent_level)
|
|
<< "} else if (exception != nullptr) {\n";
|
|
}
|
|
|
|
if (manage_return) {
|
|
delete_return_value(out, indent_level + 2, remap, return_expr);
|
|
}
|
|
|
|
error_return(out, indent_level + 2, return_flags);
|
|
|
|
indent(out, indent_level)
|
|
<< "} else {\n";
|
|
|
|
++open_scopes;
|
|
indent_level += 2;
|
|
}
|
|
|
|
if (return_flags & RF_decref_args) {
|
|
indent(out, indent_level) << "Py_DECREF(args);\n";
|
|
return_flags &= ~RF_decref_args;
|
|
}
|
|
|
|
// Outputs code to check to see if an assertion has failed while the C++
|
|
// code was executing, and report this failure back to Python. Don't do
|
|
// this for coercion constructors since they are called by other wrapper
|
|
// functions which already check this on their own. Generated getters
|
|
// obviously can't raise asserts.
|
|
if (watch_asserts && (return_flags & (RF_coerced | RF_raise_keyerror)) == 0 &&
|
|
remap->_type != FunctionRemap::T_getter &&
|
|
remap->_type != FunctionRemap::T_setter) {
|
|
out << "#ifndef NDEBUG\n";
|
|
indent(out, indent_level)
|
|
<< "Notify *notify = Notify::ptr();\n";
|
|
indent(out, indent_level)
|
|
<< "if (UNLIKELY(notify->has_assert_failed())) {\n";
|
|
|
|
if (manage_return) {
|
|
// Output code to delete any temporary object we may have allocated.
|
|
delete_return_value(out, indent_level + 2, remap, return_expr);
|
|
}
|
|
|
|
if (return_flags & RF_err_null) {
|
|
// This function returns NULL, so we can pass it on.
|
|
indent(out, indent_level + 2)
|
|
<< "return Dtool_Raise_AssertionError();\n";
|
|
} else {
|
|
indent(out, indent_level + 2)
|
|
<< "Dtool_Raise_AssertionError();\n";
|
|
error_return(out, indent_level + 2, return_flags);
|
|
}
|
|
|
|
indent(out, indent_level)
|
|
<< "}\n";
|
|
out << "#endif\n";
|
|
}
|
|
}
|
|
|
|
// Okay, we're past all the error conditions and special cases. Now return
|
|
// the return type in the way that was requested.
|
|
if ((return_flags & RF_int) != 0 && (return_flags & RF_raise_keyerror) == 0) {
|
|
CPPType *orig_type = remap->_return_type->get_orig_type();
|
|
if (is_constructor) {
|
|
// Special case for constructor.
|
|
TypeIndex type_index = builder.get_type(TypeManager::unwrap(TypeManager::resolve_type(orig_type)), false);
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
indent(out, indent_level)
|
|
<< "return DTool_PyInit_Finalize(self, (void *)" << return_expr << ", &" << CLASS_PREFIX << make_safe_name(itype.get_scoped_name()) << ", true, false);\n";
|
|
|
|
} else if (TypeManager::is_bool(orig_type)) {
|
|
// It's an error return boolean, I guess. Return 0 on success.
|
|
indent(out, indent_level) << "return (" << return_expr << ") ? 0 : -1;\n";
|
|
|
|
} else if (TypeManager::is_integer(orig_type)) {
|
|
if ((return_flags & RF_compare) == RF_compare) {
|
|
// Make sure it returns -1, 0, or 1, or Python complains with:
|
|
// RuntimeWarning: tp_compare didn't return -1, 0 or 1
|
|
indent(out, indent_level) << "return (int)(" << return_expr << " > 0) - (int)(" << return_expr << " < 0);\n";
|
|
} else {
|
|
indent(out, indent_level) << "return " << return_expr << ";\n";
|
|
}
|
|
|
|
} else if (TypeManager::is_void(orig_type)) {
|
|
indent(out, indent_level) << "return 0;\n";
|
|
|
|
} else {
|
|
nout << "Warning: function has return type " << *orig_type
|
|
<< ", expected int or void:\n" << expected_params << "\n";
|
|
indent(out, indent_level) << "// Don't know what to do with return type "
|
|
<< *orig_type << ".\n";
|
|
indent(out, indent_level) << "return 0;\n";
|
|
}
|
|
|
|
} else if (return_flags & RF_self) {
|
|
indent(out, indent_level) << "Py_INCREF(self);\n";
|
|
indent(out, indent_level) << "return self;\n";
|
|
|
|
} else if (return_flags & RF_pyobject) {
|
|
if (return_expr.empty()) {
|
|
indent(out, indent_level) << "Py_INCREF(Py_None);\n";
|
|
indent(out, indent_level) << "return Py_None;\n";
|
|
|
|
} else if (return_flags & RF_preserve_null) {
|
|
indent(out, indent_level) << "if (" << return_expr << " == nullptr) {\n";
|
|
indent(out, indent_level) << " return nullptr;\n";
|
|
indent(out, indent_level) << "} else {\n";
|
|
pack_return_value(out, indent_level + 2, remap, return_expr, return_flags);
|
|
indent(out, indent_level) << "}\n";
|
|
|
|
} else {
|
|
pack_return_value(out, indent_level, remap, return_expr, return_flags);
|
|
}
|
|
|
|
} else if (return_flags & RF_coerced) {
|
|
// We were asked to assign the result to a "coerced" reference.
|
|
CPPType *return_type = remap->_cpptype;
|
|
CPPType *orig_type = remap->_return_type->get_orig_type();
|
|
|
|
// Special case for static make function that returns a pointer: cast the
|
|
// pointer to the right pointer type.
|
|
if (!is_constructor && (remap->_flags & FunctionRemap::F_coerce_constructor) != 0 &&
|
|
(TypeManager::is_pointer(orig_type) || TypeManager::is_pointer_to_base(orig_type))) {
|
|
|
|
CPPType *new_type = remap->_return_type->get_new_type();
|
|
|
|
if (TypeManager::is_const_pointer_to_anything(new_type)) {
|
|
return_type = CPPType::new_type(new CPPConstType(return_type));
|
|
}
|
|
|
|
if (IsPandaTypedObject(return_type->as_struct_type())) {
|
|
return_expr = "DCAST("
|
|
+ return_type->get_local_name(&parser)
|
|
+ ", " + return_expr + ")";
|
|
|
|
} else {
|
|
return_type = CPPType::new_type(new CPPPointerType(return_type));
|
|
return_expr = "(" + return_type->get_local_name(&parser) +
|
|
") " + return_expr;
|
|
}
|
|
}
|
|
|
|
if (return_expr == "coerced") {
|
|
// We already did this earlier...
|
|
indent(out, indent_level) << "return true;\n";
|
|
|
|
} else if (TypeManager::is_reference_count(remap->_cpptype)) {
|
|
indent(out, indent_level) << "coerced = std::move(" << return_expr << ");\n";
|
|
indent(out, indent_level) << "return true;\n";
|
|
|
|
} else {
|
|
indent(out, indent_level) << "return &coerced;\n";
|
|
}
|
|
|
|
} else if (return_flags & RF_raise_keyerror) {
|
|
CPPType *orig_type = remap->_return_type->get_orig_type();
|
|
|
|
if (TypeManager::is_bool(orig_type) || TypeManager::is_pointer(orig_type)) {
|
|
indent(out, indent_level) << "if (!" << return_expr << ") {\n";
|
|
} else if (TypeManager::is_unsigned_integer(orig_type)) {
|
|
indent(out, indent_level) << "if ((int)" << return_expr << " == -1) {\n";
|
|
} else if (TypeManager::is_integer(orig_type)) {
|
|
indent(out, indent_level) << "if (" << return_expr << " < 0) {\n";
|
|
} else {
|
|
indent(out, indent_level) << "if (false) {\n";
|
|
}
|
|
|
|
if (args_type == AT_single_arg) {
|
|
indent(out, indent_level) << " PyErr_SetObject(PyExc_KeyError, arg);\n";
|
|
} else {
|
|
indent(out, indent_level) << " PyErr_SetObject(PyExc_KeyError, key);\n";
|
|
}
|
|
error_return(out, indent_level + 2, return_flags);
|
|
indent(out, indent_level) << "}\n";
|
|
}
|
|
|
|
// Close the extra braces opened earlier.
|
|
while (open_scopes > 0) {
|
|
indent_level -= 2;
|
|
indent(out, indent_level) << "}\n";
|
|
|
|
--open_scopes;
|
|
}
|
|
|
|
if (clear_error && !report_errors) {
|
|
// We were asked not to report errors, so clear the active exception if
|
|
// this overload might have raised a TypeError.
|
|
indent(out, indent_level) << "PyErr_Clear();\n";
|
|
}
|
|
|
|
if (min_version > 0) {
|
|
// Close the #if PY_VERSION_HEX check.
|
|
out << "#endif\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Outputs the correct return statement that should be used in case of error
|
|
* based on the ReturnFlags.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
error_return(ostream &out, int indent_level, int return_flags) {
|
|
// if (return_flags & RF_coerced) { indent(out, indent_level) << "coerced =
|
|
// NULL;\n"; }
|
|
|
|
if (return_flags & RF_decref_args) {
|
|
indent(out, indent_level) << "Py_DECREF(args);\n";
|
|
}
|
|
|
|
if (return_flags & RF_int) {
|
|
indent(out, indent_level) << "return -1;\n";
|
|
|
|
} else if (return_flags & RF_err_notimplemented) {
|
|
indent(out, indent_level) << "Py_INCREF(Py_NotImplemented);\n";
|
|
indent(out, indent_level) << "return Py_NotImplemented;\n";
|
|
|
|
} else if (return_flags & RF_err_null) {
|
|
indent(out, indent_level) << "return nullptr;\n";
|
|
|
|
} else if (return_flags & RF_err_false) {
|
|
indent(out, indent_level) << "return false;\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Similar to error_return, except raises an exception before returning. If
|
|
* format_args are not the empty string, uses PyErr_Format instead of
|
|
* PyErr_SetString.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
error_raise_return(ostream &out, int indent_level, int return_flags,
|
|
const string &exc_type, const string &message,
|
|
const string &format_args) {
|
|
|
|
if (return_flags & RF_decref_args) {
|
|
indent(out, indent_level) << "Py_DECREF(args);\n";
|
|
return_flags &= ~RF_decref_args;
|
|
}
|
|
|
|
if (format_args.empty()) {
|
|
if (exc_type == "TypeError") {
|
|
if ((return_flags & RF_err_null) != 0) {
|
|
// This is probably an over-optimization, but why the heck not.
|
|
indent(out, indent_level) << "return Dtool_Raise_TypeError(";
|
|
output_quoted(out, indent_level + 29, message, false);
|
|
out << ");\n";
|
|
return;
|
|
} else {
|
|
indent(out, indent_level) << "Dtool_Raise_TypeError(";
|
|
output_quoted(out, indent_level + 22, message, false);
|
|
out << ");\n";
|
|
}
|
|
} else {
|
|
indent(out, indent_level) << "PyErr_SetString(PyExc_" << exc_type << ",\n";
|
|
output_quoted(out, indent_level + 16, message);
|
|
out << ");\n";
|
|
}
|
|
|
|
} else if ((return_flags & RF_err_null) != 0 &&
|
|
(return_flags & RF_pyobject) != 0) {
|
|
// PyErr_Format always returns NULL. Passing it on directly allows the
|
|
// compiler to make a tiny optimization, so why not.
|
|
indent(out, indent_level) << "return PyErr_Format(PyExc_" << exc_type << ",\n";
|
|
output_quoted(out, indent_level + 20, message);
|
|
out << ",\n";
|
|
indent(out, indent_level + 20) << format_args << ");\n";
|
|
return;
|
|
|
|
} else {
|
|
indent(out, indent_level) << "PyErr_Format(PyExc_" << exc_type << ",\n";
|
|
output_quoted(out, indent_level + 13, message);
|
|
out << ",\n";
|
|
indent(out, indent_level + 13) << format_args << ");\n";
|
|
}
|
|
|
|
error_return(out, indent_level, return_flags);
|
|
}
|
|
|
|
/**
|
|
* Outputs a command to pack the indicated expression, of the return_type
|
|
* type, as a Python return value.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
pack_return_value(ostream &out, int indent_level, FunctionRemap *remap,
|
|
string return_expr, int return_flags) {
|
|
|
|
ParameterRemap *return_type = remap->_return_type;
|
|
CPPType *orig_type = return_type->get_orig_type();
|
|
CPPType *type = return_type->get_new_type();
|
|
|
|
if (TypeManager::is_scoped_enum(type)) {
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
TypeIndex type_index = builder.get_type(TypeManager::unwrap(TypeManager::resolve_type(orig_type)), false);
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
string safe_name = make_safe_name(itype.get_scoped_name());
|
|
|
|
indent(out, indent_level)
|
|
<< "return PyObject_CallFunction((PyObject *)Dtool_Ptr_" << safe_name;
|
|
|
|
CPPType *underlying_type = ((CPPEnumType *)itype._cpptype)->get_underlying_type();
|
|
if (TypeManager::is_unsigned_integer(underlying_type)) {
|
|
out << ", \"k\", (unsigned long)";
|
|
} else {
|
|
out << ", \"l\", (long)";
|
|
}
|
|
out << "(" << return_expr << "));\n";
|
|
|
|
} else if (return_type->new_type_is_atomic_string() ||
|
|
TypeManager::is_simple(type) ||
|
|
TypeManager::is_char_pointer(type) ||
|
|
TypeManager::is_wchar_pointer(type) ||
|
|
TypeManager::is_pointer_to_PyObject(type) ||
|
|
TypeManager::is_pointer_to_Py_buffer(type) ||
|
|
TypeManager::is_vector_unsigned_char(type)) {
|
|
// Most types are now handled by the many overloads of Dtool_WrapValue,
|
|
// defined in py_panda.h.
|
|
indent(out, indent_level)
|
|
<< "return Dtool_WrapValue(" << return_expr << ");\n";
|
|
|
|
} else if (TypeManager::is_pointer(type)) {
|
|
bool is_const = TypeManager::is_const_pointer_to_anything(type);
|
|
bool owns_memory = remap->_return_value_needs_management;
|
|
|
|
// Note, we don't check for NULL here any more. This is now done by the
|
|
// appropriate CreateInstance(Typed) function.
|
|
|
|
if (manage_reference_counts && TypeManager::is_pointer_to_base(orig_type)) {
|
|
// Use a trick to transfer the reference count to avoid a pair of
|
|
// unnecessary ref() and unref() calls. Ideally we'd use move
|
|
// semantics, but py_panda.cxx cannot make use of PointerTo.
|
|
indent(out, indent_level) << "// Transfer ownership of return_value.\n";
|
|
indent(out, indent_level);
|
|
type->output_instance(out, "return_ptr", &parser);
|
|
out << " = " << return_expr << ";\n";
|
|
indent(out, indent_level) << "return_value.cheat() = nullptr;\n";
|
|
return_expr = "return_ptr";
|
|
}
|
|
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
if (TypeManager::is_struct(orig_type) || TypeManager::is_ref_to_anything(orig_type)) {
|
|
if (TypeManager::is_ref_to_anything(orig_type) || remap->_manage_reference_count) {
|
|
TypeIndex type_index = builder.get_type(TypeManager::unwrap(TypeManager::resolve_type(type)),false);
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
|
|
write_python_instance(out, indent_level, return_expr, owns_memory, itype, is_const);
|
|
|
|
} else {
|
|
TypeIndex type_index = builder.get_type(TypeManager::unwrap(TypeManager::resolve_type(orig_type)),false);
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
|
|
write_python_instance(out, indent_level, return_expr, owns_memory, itype, is_const);
|
|
}
|
|
} else if (TypeManager::is_struct(orig_type->remove_pointer())) {
|
|
TypeIndex type_index = builder.get_type(TypeManager::unwrap(TypeManager::resolve_type(orig_type)),false);
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
|
|
write_python_instance(out, indent_level, return_expr, owns_memory, itype, is_const);
|
|
|
|
} else {
|
|
indent(out, indent_level) << "Should Never Reach This InterfaceMakerPythonNative::pack_python_value";
|
|
// << "return Dtool_Integer((int) " << return_expr << ");\n";
|
|
}
|
|
|
|
} else {
|
|
// Return None.
|
|
indent(out, indent_level)
|
|
<< "return Py_BuildValue(\"\"); // Don't know how to wrap type.\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates the synthetic method described by the MAKE_SEQ() macro.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_make_seq(ostream &out, Object *obj, const std::string &ClassName,
|
|
const std::string &cClassName, MakeSeq *make_seq) {
|
|
|
|
out << "/*\n"
|
|
" * Python make_seq wrapper\n"
|
|
" */\n";
|
|
|
|
out << "static PyObject *" << make_seq->_name + "(PyObject *self, PyObject *) {\n";
|
|
|
|
// This used to return a list. But it should really be a tuple, I think,
|
|
// because it probably makes more sense for it to be immutable (as changes
|
|
// to it won't be visible on the C++ side anyway).
|
|
|
|
FunctionRemap *remap = make_seq->_length_getter->_remaps.front();
|
|
vector_string pexprs;
|
|
|
|
if (make_seq->_length_getter->_has_this) {
|
|
out <<
|
|
" " << cClassName << " *local_this = nullptr;\n"
|
|
" if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
|
|
" return nullptr;\n"
|
|
" }\n"
|
|
" Py_ssize_t count = (Py_ssize_t)" << remap->get_call_str("local_this", pexprs) << ";\n";
|
|
} else {
|
|
out << " Py_ssize_t count = (Py_ssize_t)" << remap->get_call_str("", pexprs) << ";\n";
|
|
}
|
|
|
|
Function *elem_getter = make_seq->_element_getter;
|
|
|
|
if ((elem_getter->_args_type & AT_varargs) == AT_varargs) {
|
|
// Fast way to create a temporary tuple to hold only a single item, under
|
|
// the assumption that the called method doesn't do anything with this
|
|
// tuple other than unpack it (which is a fairly safe assumption to make).
|
|
out << " PyTupleObject args;\n";
|
|
out << " (void)PyObject_INIT_VAR((PyVarObject *)&args, &PyTuple_Type, 1);\n";
|
|
}
|
|
|
|
out <<
|
|
" PyObject *tuple = PyTuple_New(count);\n"
|
|
"\n"
|
|
" for (Py_ssize_t i = 0; i < count; ++i) {\n"
|
|
" PyObject *index = Dtool_WrapValue(i);\n";
|
|
|
|
switch (elem_getter->_args_type) {
|
|
case AT_keyword_args:
|
|
out << " PyTuple_SET_ITEM(&args, 0, index);\n"
|
|
" PyObject *value = " << elem_getter->_name << "(self, (PyObject *)&args, nullptr);\n";
|
|
break;
|
|
|
|
case AT_varargs:
|
|
out << " PyTuple_SET_ITEM(&args, 0, index);\n"
|
|
" PyObject *value = " << elem_getter->_name << "(self, (PyObject *)&args);\n";
|
|
break;
|
|
|
|
case AT_single_arg:
|
|
out << " PyObject *value = " << elem_getter->_name << "(self, index);\n";
|
|
break;
|
|
|
|
default:
|
|
out << " PyObject *value = " << elem_getter->_name << "(self, nullptr);\n";
|
|
break;
|
|
}
|
|
|
|
out <<
|
|
" PyTuple_SET_ITEM(tuple, i, value);\n"
|
|
" Py_DECREF(index);\n"
|
|
" }\n"
|
|
"\n";
|
|
|
|
if ((elem_getter->_args_type & AT_varargs) == AT_varargs) {
|
|
out << " _Py_ForgetReference((PyObject *)&args);\n";
|
|
}
|
|
|
|
out <<
|
|
" if (Dtool_CheckErrorOccurred()) {\n"
|
|
" Py_DECREF(tuple);\n"
|
|
" return nullptr;\n"
|
|
" }\n"
|
|
" return tuple;\n"
|
|
"}\n"
|
|
"\n";
|
|
}
|
|
|
|
/**
|
|
* Generates the synthetic method described by the MAKE_PROPERTY() macro.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
write_getset(ostream &out, Object *obj, Property *property) {
|
|
// We keep around this empty vector for passing to get_call_str.
|
|
const vector_string pexprs;
|
|
|
|
string ClassName = make_safe_name(obj->_itype.get_scoped_name());
|
|
std::string cClassName = obj->_itype.get_true_name();
|
|
|
|
const InterrogateElement &ielem = property->_ielement;
|
|
|
|
FunctionRemap *len_remap = nullptr;
|
|
if (property->_length_function != nullptr) {
|
|
assert(!property->_length_function->_remaps.empty());
|
|
|
|
// This is actually a sequence. Wrap this with a special class.
|
|
len_remap = property->_length_function->_remaps.front();
|
|
|
|
out << "/**\n"
|
|
" * sequence length function for property " << ielem.get_scoped_name() << "\n"
|
|
" */\n"
|
|
"static Py_ssize_t Dtool_" + ClassName + "_" + ielem.get_name() + "_Len(PyObject *self) {\n";
|
|
if (property->_length_function->_has_this) {
|
|
out <<
|
|
" " << cClassName << " *local_this = nullptr;\n"
|
|
" if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
|
|
" return -1;\n"
|
|
" }\n"
|
|
" return (Py_ssize_t)" << len_remap->get_call_str("local_this", pexprs) << ";\n";
|
|
} else {
|
|
out << " return (Py_ssize_t)" << len_remap->get_call_str("", pexprs) << ";\n";
|
|
}
|
|
out << "}\n\n";
|
|
}
|
|
|
|
if (property->_getter_remaps.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (ielem.is_sequence()) {
|
|
assert(len_remap != nullptr);
|
|
out <<
|
|
"/**\n"
|
|
" * sequence getter for property " << ielem.get_scoped_name() << "\n"
|
|
" */\n"
|
|
"static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Sequence_Getitem(PyObject *self, Py_ssize_t index) {\n";
|
|
|
|
if (property->_has_this) {
|
|
out <<
|
|
" " << cClassName << " *local_this = nullptr;\n"
|
|
" if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
|
|
" return nullptr;\n"
|
|
" }\n";
|
|
}
|
|
|
|
// This is a getitem of a sequence type. This means we *need* to raise
|
|
// IndexError if we're out of bounds.
|
|
out << " if (index < 0 || index >= (Py_ssize_t)"
|
|
<< len_remap->get_call_str("local_this", pexprs) << ") {\n";
|
|
out << " PyErr_SetString(PyExc_IndexError, \"" << ClassName << "." << ielem.get_name() << "[] index out of range\");\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n";
|
|
|
|
/*if (property->_has_function != NULL) {
|
|
out << " if (!local_this->" << property->_has_function->_ifunc.get_name() << "(index)) {\n"
|
|
<< " Py_INCREF(Py_None);\n"
|
|
<< " return Py_None;\n"
|
|
<< " }\n";
|
|
}*/
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
|
|
// Extract only the getters that take one integral argument.
|
|
for (FunctionRemap *remap : property->_getter_remaps) {
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
if (min_num_args <= 1 && max_num_args >= 1 &&
|
|
TypeManager::is_integer(remap->_parameters[(size_t)remap->_has_this]._remap->get_new_type())) {
|
|
remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
|
|
AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " return Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n"
|
|
" }\n"
|
|
"}\n\n";
|
|
|
|
// Write out a setitem if this is not a read-only property.
|
|
if (!property->_setter_remaps.empty()) {
|
|
out << "static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Sequence_Setitem(PyObject *self, Py_ssize_t index, PyObject *arg) {\n";
|
|
if (property->_has_this) {
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
|
|
<< classNameFromCppName(cClassName, false) << "." << ielem.get_name() << "\")) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
}
|
|
|
|
out << " if (index < 0 || index >= (Py_ssize_t)"
|
|
<< len_remap->get_call_str("local_this", pexprs) << ") {\n";
|
|
out << " PyErr_SetString(PyExc_IndexError, \"" << ClassName << "." << ielem.get_name() << "[] index out of range\");\n";
|
|
out << " return -1;\n";
|
|
out << " }\n";
|
|
|
|
out << " if (arg == nullptr) {\n";
|
|
if (property->_deleter != nullptr) {
|
|
if (property->_deleter->_has_this) {
|
|
out << " local_this->" << property->_deleter->_ifunc.get_name() << "(index);\n";
|
|
} else {
|
|
out << " " << cClassName << "::" << property->_deleter->_ifunc.get_name() << "(index);\n";
|
|
}
|
|
out << " return 0;\n";
|
|
} else {
|
|
out << " Dtool_Raise_TypeError(\"can't delete " << ielem.get_name() << "[] attribute\");\n"
|
|
" return -1;\n";
|
|
}
|
|
out << " }\n";
|
|
|
|
if (property->_clear_function != nullptr) {
|
|
out << " if (arg == Py_None) {\n";
|
|
if (property->_clear_function->_has_this) {
|
|
out << " local_this->" << property->_clear_function->_ifunc.get_name() << "(index);\n";
|
|
} else {
|
|
out << " " << cClassName << "::" << property->_clear_function->_ifunc.get_name() << "(index);\n";
|
|
}
|
|
out << " return 0;\n"
|
|
<< " }\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
|
|
// Extract only the setters that take two arguments.
|
|
for (FunctionRemap *remap : property->_setter_remaps) {
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
if (min_num_args <= 2 && max_num_args >= 2 &&
|
|
TypeManager::is_integer(remap->_parameters[1]._remap->get_new_type())) {
|
|
remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 2, 2,
|
|
expected_params, 2, true, true, AT_single_arg,
|
|
RF_int, false, false, "index");
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return -1;\n";
|
|
out << "}\n\n";
|
|
}
|
|
|
|
// Finally, add the inserter, if one exists.
|
|
if (property->_inserter != nullptr) {
|
|
out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Sequence_insert(PyObject *self, size_t index, PyObject *arg) {\n";
|
|
if (property->_has_this) {
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
|
|
<< classNameFromCppName(cClassName, false) << "." << ielem.get_name() << "\")) {\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
remaps.insert(property->_inserter->_remaps.begin(),
|
|
property->_inserter->_remaps.end());
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 2, 2,
|
|
expected_params, 2, true, true, AT_single_arg,
|
|
RF_pyobject | RF_err_null, false, false, "index");
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return nullptr;\n";
|
|
out << "}\n\n";
|
|
}
|
|
}
|
|
|
|
|
|
// Write the getitem functions.
|
|
if (ielem.is_mapping()) {
|
|
out <<
|
|
"/**\n"
|
|
" * mapping getitem for property " << ielem.get_scoped_name() << "\n"
|
|
" */\n"
|
|
"static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Mapping_Getitem(PyObject *self, PyObject *arg) {\n";
|
|
|
|
// Before we do the has_function: if this is also a sequence, then we have
|
|
// to also handle the case here that we were passed an index.
|
|
if (ielem.is_sequence()) {
|
|
out <<
|
|
"#if PY_MAJOR_VERSION >= 3\n"
|
|
" if (PyLong_CheckExact(arg)) {\n"
|
|
"#else\n"
|
|
" if (PyLong_CheckExact(arg) || PyInt_CheckExact(arg)) {\n"
|
|
"#endif\n"
|
|
" return Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem(self, PyLongOrInt_AsSize_t(arg));\n"
|
|
" }\n\n";
|
|
}
|
|
|
|
if (property->_has_this) {
|
|
out <<
|
|
" " << cClassName << " *local_this = nullptr;\n"
|
|
" if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
|
|
" return nullptr;\n"
|
|
" }\n";
|
|
}
|
|
|
|
if (property->_has_function != nullptr) {
|
|
std::set<FunctionRemap*> remaps;
|
|
remaps.insert(property->_has_function->_remaps.begin(),
|
|
property->_has_function->_remaps.end());
|
|
|
|
out << " {\n";
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1, expected_params, 4, true, true,
|
|
AT_single_arg, RF_raise_keyerror | RF_err_null, false, true);
|
|
out << " }\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
// Extract only the getters that take one argument. Fish out the ones
|
|
// already taken by the sequence getter.
|
|
for (FunctionRemap *remap : property->_getter_remaps) {
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
if (min_num_args <= 1 && max_num_args >= 1 &&
|
|
(!ielem.is_sequence() || !TypeManager::is_integer(remap->_parameters[(size_t)remap->_has_this]._remap->get_new_type()))) {
|
|
remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
|
|
AT_single_arg, RF_pyobject | RF_err_null, false, true);
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " return Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n"
|
|
" }\n"
|
|
" return nullptr;\n"
|
|
"}\n\n";
|
|
|
|
// Write out a setitem if this is not a read-only property.
|
|
if (!property->_setter_remaps.empty()) {
|
|
out <<
|
|
"/**\n"
|
|
" * mapping setitem for property " << ielem.get_scoped_name() << "\n"
|
|
" */\n"
|
|
"static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Mapping_Setitem(PyObject *self, PyObject *key, PyObject *value) {\n";
|
|
|
|
if (property->_has_this) {
|
|
out <<
|
|
" " << cClassName << " *local_this = nullptr;\n"
|
|
" if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
|
|
<< classNameFromCppName(cClassName, false) << "." << ielem.get_name() << "\")) {\n"
|
|
" return -1;\n"
|
|
" }\n\n";
|
|
}
|
|
|
|
out << " if (value == nullptr) {\n";
|
|
if (property->_deleter != nullptr) {
|
|
out << " PyObject *arg = key;\n";
|
|
|
|
if (property->_has_function != nullptr) {
|
|
std::set<FunctionRemap*> remaps;
|
|
remaps.insert(property->_has_function->_remaps.begin(),
|
|
property->_has_function->_remaps.end());
|
|
|
|
out << " {\n";
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1, expected_params, 6, true, true,
|
|
AT_single_arg, RF_raise_keyerror | RF_int, false, true);
|
|
out << " }\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
remaps.insert(property->_deleter->_remaps.begin(),
|
|
property->_deleter->_remaps.end());
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1,
|
|
expected_params, 4, true, true, AT_single_arg,
|
|
RF_int, false, false);
|
|
out << " return -1;\n";
|
|
} else {
|
|
out << " Dtool_Raise_TypeError(\"can't delete " << ielem.get_name() << "[] attribute\");\n"
|
|
" return -1;\n";
|
|
}
|
|
out << " }\n";
|
|
|
|
if (property->_clear_function != nullptr) {
|
|
out << " if (value == Py_None) {\n"
|
|
<< " local_this->" << property->_clear_function->_ifunc.get_name() << "(key);\n"
|
|
<< " return 0;\n"
|
|
<< " }\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
remaps.insert(property->_setter_remaps.begin(),
|
|
property->_setter_remaps.end());
|
|
|
|
// We have to create an args tuple only to unpack it later, ugh.
|
|
out << " PyObject *args = PyTuple_New(2);\n"
|
|
<< " PyTuple_SET_ITEM(args, 0, key);\n"
|
|
<< " PyTuple_SET_ITEM(args, 1, value);\n"
|
|
<< " Py_INCREF(key);\n"
|
|
<< " Py_INCREF(value);\n";
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 2, 2,
|
|
expected_params, 2, true, true, AT_varargs,
|
|
RF_int | RF_decref_args, false, false);
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " Py_DECREF(args);\n";
|
|
out << " return -1;\n";
|
|
out << "}\n\n";
|
|
}
|
|
|
|
if (property->_getkey_function != nullptr) {
|
|
out <<
|
|
"/**\n"
|
|
" * mapping key-getter for property " << ielem.get_scoped_name() << "\n"
|
|
" */\n"
|
|
"static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Mapping_Getkey(PyObject *self, Py_ssize_t index) {\n";
|
|
|
|
if (property->_has_this) {
|
|
out <<
|
|
" " << cClassName << " *local_this = nullptr;\n"
|
|
" if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n"
|
|
" return nullptr;\n"
|
|
" }\n";
|
|
}
|
|
|
|
// We need to raise IndexError if we're out of bounds.
|
|
if (len_remap != nullptr) {
|
|
out << " if (index < 0 || index >= (Py_ssize_t)"
|
|
<< len_remap->get_call_str("local_this", pexprs) << ") {\n";
|
|
out << " PyErr_SetString(PyExc_IndexError, \"" << ClassName << "." << ielem.get_name() << "[] index out of range\");\n";
|
|
out << " return nullptr;\n";
|
|
out << " }\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
|
|
// Extract only the getters that take one integral argument.
|
|
for (FunctionRemap *remap : property->_getkey_function->_remaps) {
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
if (min_num_args <= 1 && max_num_args >= 1 &&
|
|
TypeManager::is_integer(remap->_parameters[(size_t)remap->_has_this]._remap->get_new_type())) {
|
|
remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1, expected_params, 2, true, true,
|
|
AT_no_args, RF_pyobject | RF_err_null, false, true, "index");
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " return Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n"
|
|
" }\n"
|
|
"}\n\n";
|
|
}
|
|
}
|
|
|
|
// Now write the actual getter wrapper. It will be a different wrapper
|
|
// depending on whether it's a mapping or a sequence.
|
|
out << "static PyObject *Dtool_" + ClassName + "_" + ielem.get_name() + "_Getter(PyObject *self, void *) {\n";
|
|
|
|
// Is this property shadowing a static method with the same name? This is a
|
|
// special case to handle WindowProperties::make -- see GH #444.
|
|
if (property->_has_this) {
|
|
for (const Function *func : obj->_methods) {
|
|
if (!func->_has_this && func->_ifunc.get_name() == ielem.get_name()) {
|
|
string flags;
|
|
string fptr = "&" + func->_name;
|
|
switch (func->_args_type) {
|
|
case AT_keyword_args:
|
|
flags = "METH_VARARGS | METH_KEYWORDS";
|
|
fptr = "(PyCFunction) " + fptr;
|
|
break;
|
|
case AT_varargs:
|
|
flags = "METH_VARARGS";
|
|
break;
|
|
case AT_single_arg:
|
|
flags = "METH_O";
|
|
break;
|
|
default:
|
|
flags = "METH_NOARGS";
|
|
break;
|
|
}
|
|
out << " if (self == nullptr) {\n"
|
|
<< " static PyMethodDef def = {\"" << ielem.get_name() << "\", "
|
|
<< fptr << ", " << flags << " | METH_STATIC, (const char *)"
|
|
<< func->_name << "_comment};\n"
|
|
<< " return PyCFunction_New(&def, nullptr);\n"
|
|
<< " }\n\n";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ielem.is_mapping()) {
|
|
if (property->_has_this) {
|
|
out << " nassertr(self != nullptr, nullptr);\n";
|
|
}
|
|
if (property->_setter_remaps.empty()) {
|
|
out << " Dtool_MappingWrapper *wrap = Dtool_NewMappingWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n";
|
|
} else {
|
|
out << " Dtool_MappingWrapper *wrap = Dtool_NewMutableMappingWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n";
|
|
}
|
|
out << " if (wrap != nullptr) {\n"
|
|
" wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getitem;\n";
|
|
if (!property->_setter_remaps.empty()) {
|
|
if (property->_has_this) {
|
|
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
} else {
|
|
out << " {\n";
|
|
}
|
|
out << " wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Setitem;\n";
|
|
out << " }\n";
|
|
}
|
|
if (property->_length_function != nullptr) {
|
|
out << " wrap->_keys._len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n";
|
|
if (property->_getkey_function != nullptr) {
|
|
out << " wrap->_keys._getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Mapping_Getkey;\n";
|
|
}
|
|
}
|
|
out << " }\n"
|
|
" return (PyObject *)wrap;\n"
|
|
"}\n\n";
|
|
|
|
} else if (ielem.is_sequence()) {
|
|
if (property->_has_this) {
|
|
out << " nassertr(self != nullptr, nullptr);\n";
|
|
}
|
|
if (property->_setter_remaps.empty()) {
|
|
out <<
|
|
" Dtool_SequenceWrapper *wrap = Dtool_NewSequenceWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n"
|
|
" if (wrap != nullptr) {\n"
|
|
" wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
|
|
" wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
|
|
} else {
|
|
out <<
|
|
" Dtool_MutableSequenceWrapper *wrap = Dtool_NewMutableSequenceWrapper(self, \"" << ClassName << "." << ielem.get_name() << "\");\n"
|
|
" if (wrap != nullptr) {\n"
|
|
" wrap->_len_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Len;\n"
|
|
" wrap->_getitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Getitem;\n";
|
|
if (!property->_setter_remaps.empty()) {
|
|
if (property->_has_this) {
|
|
out << " if (!DtoolInstance_IS_CONST(self)) {\n";
|
|
} else {
|
|
out << " {\n";
|
|
}
|
|
out << " wrap->_setitem_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_Setitem;\n";
|
|
if (property->_inserter != nullptr) {
|
|
out << " wrap->_insert_func = &Dtool_" << ClassName << "_" << ielem.get_name() << "_Sequence_insert;\n";
|
|
}
|
|
out << " }\n";
|
|
}
|
|
}
|
|
out << " }\n"
|
|
" return (PyObject *)wrap;\n"
|
|
"}\n\n";
|
|
|
|
} else {
|
|
// Write out a regular, unwrapped getter.
|
|
FunctionRemap *remap = property->_getter_remaps.front();
|
|
|
|
if (remap->_has_this) {
|
|
if (remap->_const_method) {
|
|
out << " const " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer(self, Dtool_" << ClassName << ", (void **)&local_this)) {\n";
|
|
} else {
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
|
|
<< classNameFromCppName(cClassName, false) << "." << ielem.get_name() << "\")) {\n";
|
|
}
|
|
out << " return nullptr;\n";
|
|
out << " }\n\n";
|
|
}
|
|
|
|
if (property->_has_function != nullptr) {
|
|
if (remap->_has_this) {
|
|
out << " if (!local_this->" << property->_has_function->_ifunc.get_name() << "()) {\n";
|
|
} else {
|
|
out << " if (!" << cClassName << "::" << property->_has_function->_ifunc.get_name() << "()) {\n";
|
|
}
|
|
out << " Py_INCREF(Py_None);\n"
|
|
<< " return Py_None;\n"
|
|
<< " }\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
remaps.insert(remap);
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 0, 0,
|
|
expected_params, 2, false, true, AT_no_args,
|
|
RF_pyobject | RF_err_null, false, false);
|
|
out << "}\n\n";
|
|
|
|
// Write out a setter if this is not a read-only property.
|
|
if (!property->_setter_remaps.empty()) {
|
|
out << "static int Dtool_" + ClassName + "_" + ielem.get_name() + "_Setter(PyObject *self, PyObject *arg, void *) {\n";
|
|
if (remap->_has_this) {
|
|
out << " " << cClassName << " *local_this = nullptr;\n";
|
|
out << " if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", (void **)&local_this, \""
|
|
<< classNameFromCppName(cClassName, false) << "." << ielem.get_name() << "\")) {\n";
|
|
out << " return -1;\n";
|
|
out << " }\n\n";
|
|
}
|
|
|
|
out << " if (arg == nullptr) {\n";
|
|
if (property->_deleter != nullptr && remap->_has_this) {
|
|
out << " local_this->" << property->_deleter->_ifunc.get_name() << "();\n"
|
|
<< " return 0;\n";
|
|
} else if (property->_deleter != nullptr) {
|
|
out << " " << cClassName << "::" << property->_deleter->_ifunc.get_name() << "();\n"
|
|
<< " return 0;\n";
|
|
} else {
|
|
out << " Dtool_Raise_TypeError(\"can't delete " << ielem.get_name() << " attribute\");\n"
|
|
" return -1;\n";
|
|
}
|
|
out << " }\n";
|
|
|
|
if (property->_clear_function != nullptr) {
|
|
out << " if (arg == Py_None) {\n";
|
|
if (remap->_has_this) {
|
|
out << " local_this->" << property->_clear_function->_ifunc.get_name() << "();\n";
|
|
} else {
|
|
out << " " << cClassName << "::" << property->_clear_function->_ifunc.get_name() << "();\n";
|
|
}
|
|
out << " return 0;\n"
|
|
<< " }\n";
|
|
}
|
|
|
|
std::set<FunctionRemap*> remaps;
|
|
|
|
// Extract only the setters that take one argument.
|
|
for (FunctionRemap *remap : property->_setter_remaps) {
|
|
int min_num_args = remap->get_min_num_args();
|
|
int max_num_args = remap->get_max_num_args();
|
|
if (min_num_args <= 1 && max_num_args >= 1) {
|
|
remaps.insert(remap);
|
|
}
|
|
}
|
|
|
|
string expected_params;
|
|
write_function_forset(out, remaps, 1, 1,
|
|
expected_params, 2, true, true, AT_single_arg,
|
|
RF_int, false, false);
|
|
|
|
out << " if (!_PyErr_OCCURRED()) {\n";
|
|
out << " Dtool_Raise_BadArgumentsError(\n";
|
|
output_quoted(out, 6, expected_params);
|
|
out << ");\n";
|
|
out << " }\n";
|
|
out << " return -1;\n";
|
|
out << "}\n\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Records the indicated type, which may be a struct type, along with all of
|
|
* its associated methods, if any.
|
|
*/
|
|
InterfaceMaker::Object *InterfaceMakerPythonNative::
|
|
record_object(TypeIndex type_index) {
|
|
if (type_index == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
Objects::iterator oi = _objects.find(type_index);
|
|
if (oi != _objects.end()) {
|
|
return (*oi).second;
|
|
}
|
|
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
const InterrogateType &itype = idb->get_type(type_index);
|
|
|
|
if (!is_cpp_type_legal(itype._cpptype)) {
|
|
return nullptr;
|
|
}
|
|
|
|
Object *object = new Object(itype);
|
|
bool inserted = _objects.insert(Objects::value_type(type_index, object)).second;
|
|
assert(inserted);
|
|
|
|
Function *function;
|
|
|
|
int num_constructors = itype.number_of_constructors();
|
|
for (int ci = 0; ci < num_constructors; ci++) {
|
|
function = record_function(itype, itype.get_constructor(ci));
|
|
if (is_function_legal(function)) {
|
|
object->_constructors.push_back(function);
|
|
}
|
|
}
|
|
|
|
int num_methods = itype.number_of_methods();
|
|
int mi;
|
|
for (mi = 0; mi < num_methods; mi++) {
|
|
function = record_function(itype, itype.get_method(mi));
|
|
if (is_function_legal(function)) {
|
|
object->_methods.push_back(function);
|
|
}
|
|
}
|
|
|
|
int num_casts = itype.number_of_casts();
|
|
for (mi = 0; mi < num_casts; mi++) {
|
|
function = record_function(itype, itype.get_cast(mi));
|
|
if (is_function_legal(function)) {
|
|
object->_methods.push_back(function);
|
|
}
|
|
}
|
|
|
|
int num_derivations = itype.number_of_derivations();
|
|
for (int di = 0; di < num_derivations; di++) {
|
|
TypeIndex d_type_Index = itype.get_derivation(di);
|
|
idb->get_type(d_type_Index);
|
|
|
|
if (!interrogate_type_is_unpublished(d_type_Index)) {
|
|
if (itype.derivation_has_upcast(di)) {
|
|
function = record_function(itype, itype.derivation_get_upcast(di));
|
|
if (is_function_legal(function)) {
|
|
object->_methods.push_back(function);
|
|
}
|
|
}
|
|
/*if (itype.derivation_has_downcast(di)) {
|
|
// Downcasts are methods of the base class, not the child class.
|
|
TypeIndex base_type_index = itype.get_derivation(di);
|
|
|
|
const InterrogateType &base_type = idb->get_type(base_type_index);
|
|
function = record_function(base_type, itype.derivation_get_downcast(di));
|
|
|
|
if (is_function_legal(function)) {
|
|
Object *pobject = record_object(base_type_index);
|
|
if (pobject != NULL) {
|
|
pobject->_methods.push_back(function);
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
|
|
int num_elements = itype.number_of_elements();
|
|
for (int ei = 0; ei < num_elements; ei++) {
|
|
ElementIndex element_index = itype.get_element(ei);
|
|
|
|
Property *property = record_property(itype, element_index);
|
|
if (property != nullptr) {
|
|
object->_properties.push_back(property);
|
|
} else {
|
|
// No use exporting a property without a getter.
|
|
delete property;
|
|
}
|
|
}
|
|
|
|
int num_make_seqs = itype.number_of_make_seqs();
|
|
for (int msi = 0; msi < num_make_seqs; msi++) {
|
|
MakeSeqIndex make_seq_index = itype.get_make_seq(msi);
|
|
const InterrogateMakeSeq &imake_seq = idb->get_make_seq(make_seq_index);
|
|
|
|
string class_name = itype.get_scoped_name();
|
|
string clean_name = InterrogateBuilder::clean_identifier(class_name);
|
|
string wrapper_name = "MakeSeq_" + clean_name + "_" + imake_seq.get_name();
|
|
|
|
MakeSeq *make_seq = new MakeSeq(wrapper_name, imake_seq);
|
|
make_seq->_length_getter = record_function(itype, imake_seq.get_length_getter());
|
|
make_seq->_element_getter = record_function(itype, imake_seq.get_element_getter());
|
|
object->_make_seqs.push_back(make_seq);
|
|
}
|
|
|
|
object->check_protocols();
|
|
|
|
int num_nested = itype.number_of_nested_types();
|
|
for (int ni = 0; ni < num_nested; ni++) {
|
|
TypeIndex nested_index = itype.get_nested_type(ni);
|
|
record_object(nested_index);
|
|
}
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
InterfaceMaker::Property *InterfaceMakerPythonNative::
|
|
record_property(const InterrogateType &itype, ElementIndex element_index) {
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
const InterrogateElement &ielement = idb->get_element(element_index);
|
|
if (!ielement.has_getter()) {
|
|
// A property needs at the very least a getter.
|
|
return nullptr;
|
|
}
|
|
|
|
Property *property;
|
|
{
|
|
FunctionIndex func_index = ielement.get_getter();
|
|
if (func_index != 0) {
|
|
const InterrogateFunction &ifunc = idb->get_function(func_index);
|
|
property = new Property(ielement);
|
|
|
|
InterrogateFunction::Instances::const_iterator ii;
|
|
for (ii = ifunc._instances->begin(); ii != ifunc._instances->end(); ++ii) {
|
|
CPPInstance *cppfunc = (*ii).second;
|
|
FunctionRemap *remap =
|
|
make_function_remap(itype, ifunc, cppfunc, 0);
|
|
|
|
if (remap != nullptr && is_remap_legal(remap)) {
|
|
property->_getter_remaps.push_back(remap);
|
|
property->_has_this |= remap->_has_this;
|
|
}
|
|
}
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (ielement.has_setter()) {
|
|
FunctionIndex func_index = ielement.get_setter();
|
|
if (func_index != 0) {
|
|
const InterrogateFunction &ifunc = idb->get_function(func_index);
|
|
|
|
InterrogateFunction::Instances::const_iterator ii;
|
|
for (ii = ifunc._instances->begin(); ii != ifunc._instances->end(); ++ii) {
|
|
CPPInstance *cppfunc = (*ii).second;
|
|
FunctionRemap *remap =
|
|
make_function_remap(itype, ifunc, cppfunc, 0);
|
|
|
|
if (remap != nullptr && is_remap_legal(remap)) {
|
|
property->_setter_remaps.push_back(remap);
|
|
property->_has_this |= remap->_has_this;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ielement.has_has_function()) {
|
|
FunctionIndex func_index = ielement.get_has_function();
|
|
Function *has_function = record_function(itype, func_index);
|
|
if (is_function_legal(has_function)) {
|
|
property->_has_function = has_function;
|
|
property->_has_this |= has_function->_has_this;
|
|
}
|
|
}
|
|
|
|
if (ielement.has_clear_function()) {
|
|
FunctionIndex func_index = ielement.get_clear_function();
|
|
Function *clear_function = record_function(itype, func_index);
|
|
if (is_function_legal(clear_function)) {
|
|
property->_clear_function = clear_function;
|
|
property->_has_this |= clear_function->_has_this;
|
|
}
|
|
}
|
|
|
|
if (ielement.has_del_function()) {
|
|
FunctionIndex func_index = ielement.get_del_function();
|
|
Function *del_function = record_function(itype, func_index);
|
|
if (is_function_legal(del_function)) {
|
|
property->_deleter = del_function;
|
|
property->_has_this |= del_function->_has_this;
|
|
}
|
|
}
|
|
|
|
if (ielement.is_sequence() || ielement.is_mapping()) {
|
|
FunctionIndex func_index = ielement.get_length_function();
|
|
if (func_index != 0) {
|
|
property->_length_function = record_function(itype, func_index);
|
|
}
|
|
}
|
|
|
|
if (ielement.is_sequence() && ielement.has_insert_function()) {
|
|
FunctionIndex func_index = ielement.get_insert_function();
|
|
Function *insert_function = record_function(itype, func_index);
|
|
if (is_function_legal(insert_function)) {
|
|
property->_inserter = insert_function;
|
|
property->_has_this |= insert_function->_has_this;
|
|
}
|
|
}
|
|
|
|
if (ielement.is_mapping() && ielement.has_getkey_function()) {
|
|
FunctionIndex func_index = ielement.get_getkey_function();
|
|
assert(func_index != 0);
|
|
Function *getkey_function = record_function(itype, func_index);
|
|
if (is_function_legal(getkey_function)) {
|
|
property->_getkey_function = getkey_function;
|
|
property->_has_this |= getkey_function->_has_this;
|
|
}
|
|
}
|
|
|
|
return property;
|
|
}
|
|
|
|
/**
|
|
* Walks through the set of functions in the database and generates wrappers
|
|
* for each function, storing these in the database. No actual code should be
|
|
* output yet; this just updates the database with the wrapper information.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
generate_wrappers() {
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
// We use a while loop rather than a simple for loop, because we might
|
|
// increase the number of types recursively during the traversal.
|
|
|
|
int ti = 0;
|
|
while (ti < idb->get_num_all_types()) {
|
|
TypeIndex type_index = idb->get_all_type(ti);
|
|
record_object(type_index);
|
|
++ti;
|
|
}
|
|
|
|
int num_global_elements = idb->get_num_global_elements();
|
|
for (int gi = 0; gi < num_global_elements; ++gi) {
|
|
TypeIndex type_index = idb->get_global_element(gi);
|
|
record_object(type_index);
|
|
}
|
|
|
|
int num_functions = idb->get_num_global_functions();
|
|
for (int fi = 0; fi < num_functions; fi++) {
|
|
FunctionIndex func_index = idb->get_global_function(fi);
|
|
record_function(dummy_type, func_index);
|
|
}
|
|
|
|
int num_manifests = idb->get_num_global_manifests();
|
|
for (int mi = 0; mi < num_manifests; mi++) {
|
|
ManifestIndex manifest_index = idb->get_global_manifest(mi);
|
|
const InterrogateManifest &iman = idb->get_manifest(manifest_index);
|
|
if (iman.has_getter()) {
|
|
FunctionIndex func_index = iman.get_getter();
|
|
record_function(dummy_type, func_index);
|
|
}
|
|
}
|
|
|
|
int num_elements = idb->get_num_global_elements();
|
|
for (int ei = 0; ei < num_elements; ei++) {
|
|
ElementIndex element_index = idb->get_global_element(ei);
|
|
const InterrogateElement &ielement = idb->get_element(element_index);
|
|
if (ielement.has_getter()) {
|
|
FunctionIndex func_index = ielement.get_getter();
|
|
record_function(dummy_type, func_index);
|
|
}
|
|
if (ielement.has_setter()) {
|
|
FunctionIndex func_index = ielement.get_setter();
|
|
record_function(dummy_type, func_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
is_cpp_type_legal(CPPType *in_ctype) {
|
|
if (in_ctype == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
string name = in_ctype->get_local_name(&parser);
|
|
|
|
if (builder.in_ignoretype(name)) {
|
|
return false;
|
|
}
|
|
|
|
if (builder.in_forcetype(name)) {
|
|
return true;
|
|
}
|
|
|
|
// bool answer = false;
|
|
CPPType *type = TypeManager::resolve_type(in_ctype);
|
|
if (TypeManager::is_rvalue_reference(type)) {
|
|
return false;
|
|
}
|
|
|
|
type = TypeManager::unwrap(type);
|
|
|
|
if (TypeManager::is_void(type)) {
|
|
return true;
|
|
} else if (TypeManager::is_basic_string_char(type)) {
|
|
return true;
|
|
} else if (TypeManager::is_basic_string_wchar(type)) {
|
|
return true;
|
|
} else if (TypeManager::is_vector_unsigned_char(in_ctype)) {
|
|
return true;
|
|
} else if (TypeManager::is_simple(type)) {
|
|
return true;
|
|
} else if (TypeManager::is_pointer_to_simple(type)) {
|
|
return true;
|
|
} else if (builder.in_forcetype(type->get_local_name(&parser))) {
|
|
return true;
|
|
} else if (TypeManager::is_exported(type)) {
|
|
return true;
|
|
} else if (TypeManager::is_pointer_to_PyObject(in_ctype)) {
|
|
return true;
|
|
} else if (TypeManager::is_pointer_to_Py_buffer(in_ctype)) {
|
|
return true;
|
|
}
|
|
|
|
// if (answer == false) printf(" -------------------- Bad Type ??
|
|
// %s\n",type->get_local_name().c_str());
|
|
|
|
return false;
|
|
}
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
isExportThisRun(CPPType *ctype) {
|
|
if (builder.in_forcetype(ctype->get_local_name(&parser))) {
|
|
return true;
|
|
}
|
|
|
|
if (!TypeManager::is_exported(ctype)) {
|
|
return false;
|
|
}
|
|
|
|
if (TypeManager::is_local(ctype)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
isExportThisRun(Function *func) {
|
|
if (func == nullptr || !is_function_legal(func)) {
|
|
return false;
|
|
}
|
|
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
return isExportThisRun(remap->_cpptype);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
is_remap_legal(FunctionRemap *remap) {
|
|
if (remap == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
// return must be legal and managable..
|
|
if (!is_cpp_type_legal(remap->_return_type->get_orig_type())) {
|
|
// printf(" is_remap_legal Return Is Bad %s\n",remap->_return_type->get_orig_
|
|
// type()->get_fully_scoped_name().c_str());
|
|
return false;
|
|
}
|
|
|
|
// We don't currently support returning pointers, but we accept them as
|
|
// function parameters. But const char * is an exception.
|
|
if (!remap->_return_type->new_type_is_atomic_string() &&
|
|
TypeManager::is_pointer_to_simple(remap->_return_type->get_orig_type())) {
|
|
return false;
|
|
}
|
|
|
|
// ouch .. bad things will happen here .. do not even try..
|
|
if (remap->_ForcedVoidReturn) {
|
|
return false;
|
|
}
|
|
|
|
// all non-optional params must be legal
|
|
for (size_t pn = 0; pn < remap->_parameters.size(); pn++) {
|
|
ParameterRemap *param = remap->_parameters[pn]._remap;
|
|
CPPType *orig_type = param->get_orig_type();
|
|
if (param->get_default_value() == nullptr && !is_cpp_type_legal(orig_type)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ok all looks ok.
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
int InterfaceMakerPythonNative::
|
|
has_coerce_constructor(CPPStructType *type) {
|
|
if (type == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
// It is convenient to set default-constructability and move-assignability
|
|
// as requirement for non-reference-counted objects, since it simplifies the
|
|
// implementation and it holds for all classes we need it for.
|
|
if (!TypeManager::is_reference_count(type) &&
|
|
(!type->is_default_constructible() || !type->is_move_assignable())) {
|
|
return 0;
|
|
}
|
|
|
|
CPPScope *scope = type->get_scope();
|
|
if (scope == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
int result = 0;
|
|
|
|
CPPScope::Functions::iterator fgi;
|
|
for (fgi = scope->_functions.begin(); fgi != scope->_functions.end(); ++fgi) {
|
|
CPPFunctionGroup *fgroup = fgi->second;
|
|
for (CPPInstance *inst : fgroup->_instances) {
|
|
CPPFunctionType *ftype = inst->_type->as_function_type();
|
|
if (ftype == nullptr) {
|
|
continue;
|
|
}
|
|
if (inst->_storage_class & CPPInstance::SC_explicit) {
|
|
// Skip it if it is marked not to allow coercion.
|
|
continue;
|
|
}
|
|
|
|
if (inst->_vis > min_vis) {
|
|
// Not published.
|
|
continue;
|
|
}
|
|
|
|
CPPParameterList::Parameters ¶ms = ftype->_parameters->_parameters;
|
|
if (params.size() == 0) {
|
|
// It's useless if it doesn't take any parameters.
|
|
continue;
|
|
}
|
|
|
|
if (ftype->_flags & CPPFunctionType::F_constructor) {
|
|
if (ftype->_flags & (CPPFunctionType::F_copy_constructor |
|
|
CPPFunctionType::F_move_constructor)) {
|
|
// Skip a copy and move constructor.
|
|
continue;
|
|
} else {
|
|
return 2;
|
|
}
|
|
|
|
} else if (fgroup->_name == "make" && (inst->_storage_class & CPPInstance::SC_static) != 0) {
|
|
if (TypeManager::is_const_pointer_or_ref(ftype->_return_type)) {
|
|
result = 1;
|
|
} else {
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
is_remap_coercion_possible(FunctionRemap *remap) {
|
|
if (remap == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
size_t pn = 0;
|
|
if (remap->_has_this) {
|
|
// Skip the "this" parameter. It's never coercible.
|
|
++pn;
|
|
}
|
|
while (pn < remap->_parameters.size()) {
|
|
CPPType *type = remap->_parameters[pn]._remap->get_new_type();
|
|
|
|
if (TypeManager::is_char_pointer(type)) {
|
|
} else if (TypeManager::is_wchar_pointer(type)) {
|
|
} else if (TypeManager::is_pointer_to_PyObject(type)) {
|
|
} else if (TypeManager::is_pointer_to_Py_buffer(type)) {
|
|
} else if (TypeManager::is_pointer_to_simple(type)) {
|
|
} else if (TypeManager::is_pointer(type)) {
|
|
// This is a pointer to an object, so we might be able to coerce a
|
|
// parameter to it.
|
|
CPPType *obj_type = TypeManager::unwrap(TypeManager::resolve_type(type));
|
|
if (has_coerce_constructor(obj_type->as_struct_type()) > 0) {
|
|
// It has a coercion constructor, so go for it.
|
|
return true;
|
|
}
|
|
}
|
|
++pn;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
is_function_legal(Function *func) {
|
|
for (FunctionRemap *remap : func->_remaps) {
|
|
if (is_remap_legal(remap)) {
|
|
// printf(" Function Is Marked Legal %s\n",func->_name.c_str());
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// printf(" Function Is Marked Illegal %s\n",func->_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
IsRunTimeTyped(const InterrogateType &itype) {
|
|
TypeIndex ptype_id = itype.get_outer_class();
|
|
if (ptype_id > 0) {
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
InterrogateType ptype = idb->get_type(ptype_id);
|
|
return IsRunTimeTyped(ptype);
|
|
}
|
|
|
|
if (itype.get_name() == "TypedObject") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
DoesInheritFromIsClass(const CPPStructType *inclass, const std::string &name) {
|
|
if (inclass == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
std::string scoped_name = inclass->get_fully_scoped_name();
|
|
if (scoped_name == name) {
|
|
return true;
|
|
}
|
|
|
|
for (const CPPStructType::Base &base : inclass->_derivation) {
|
|
CPPStructType *base_type = TypeManager::resolve_type(base._base)->as_struct_type();
|
|
if (base_type != nullptr) {
|
|
if (DoesInheritFromIsClass(base_type, name)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
has_get_class_type_function(CPPType *type) {
|
|
while (type->get_subtype() == CPPDeclaration::ST_typedef) {
|
|
type = type->as_typedef_type()->_type;
|
|
}
|
|
|
|
CPPStructType *struct_type = type->as_struct_type();
|
|
if (struct_type == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
CPPScope *scope = struct_type->get_scope();
|
|
|
|
return scope->_functions.find("get_class_type") != scope->_functions.end();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
has_init_type_function(CPPType *type) {
|
|
while (type->get_subtype() == CPPDeclaration::ST_typedef) {
|
|
type = type->as_typedef_type()->_type;
|
|
}
|
|
|
|
CPPStructType *struct_type = type->as_struct_type();
|
|
if (struct_type == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
CPPScope *scope = struct_type->get_scope();
|
|
CPPScope::Functions::const_iterator it = scope->_functions.find("init_type");
|
|
if (it == scope->_functions.end()) {
|
|
return false;
|
|
}
|
|
const CPPFunctionGroup *group = it->second;
|
|
|
|
for (const CPPInstance *cppinst : group->_instances) {
|
|
const CPPFunctionType *cppfunc = cppinst->_type->as_function_type();
|
|
|
|
if (cppfunc != nullptr &&
|
|
cppfunc->_parameters != nullptr &&
|
|
cppfunc->_parameters->_parameters.size() == 0 &&
|
|
(cppinst->_storage_class & CPPInstance::SC_static) != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns -1 if the class does not define write() (and therefore cannot
|
|
* support a __str__ function).
|
|
*
|
|
* Returns 1 if the class defines write(ostream).
|
|
*
|
|
* Returns 2 if the class defines write(ostream, int).
|
|
*
|
|
* Note that if you want specific behavior for Python str(), you should just
|
|
* define a __str__ function, which maps directly to the appropriate type
|
|
* slot.
|
|
*/
|
|
int InterfaceMakerPythonNative::
|
|
NeedsAStrFunction(const InterrogateType &itype_class) {
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
int num_methods = itype_class.number_of_methods();
|
|
int mi;
|
|
for (mi = 0; mi < num_methods; ++mi) {
|
|
FunctionIndex func_index = itype_class.get_method(mi);
|
|
const InterrogateFunction &ifunc = idb->get_function(func_index);
|
|
if (ifunc.get_name() == "write") {
|
|
if (ifunc._instances != nullptr) {
|
|
InterrogateFunction::Instances::const_iterator ii;
|
|
for (ii = ifunc._instances->begin();
|
|
ii != ifunc._instances->end();
|
|
++ii) {
|
|
CPPInstance *cppinst = (*ii).second;
|
|
CPPFunctionType *cppfunc = cppinst->_type->as_function_type();
|
|
|
|
if (cppfunc != nullptr) {
|
|
if (cppfunc->_parameters != nullptr &&
|
|
cppfunc->_return_type != nullptr &&
|
|
TypeManager::is_void(cppfunc->_return_type)) {
|
|
if (cppfunc->_parameters->_parameters.size() == 1) {
|
|
CPPInstance *inst1 = cppfunc->_parameters->_parameters[0];
|
|
if (TypeManager::is_pointer_to_ostream(inst1->_type)) {
|
|
// write(ostream)
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (cppfunc->_parameters->_parameters.size() == 2) {
|
|
CPPInstance *inst1 = cppfunc->_parameters->_parameters[0];
|
|
if (TypeManager::is_pointer_to_ostream(inst1->_type)) {
|
|
inst1 = cppfunc->_parameters->_parameters[1];
|
|
if (inst1->_initializer != nullptr) {
|
|
// write(ostream, int = 0)
|
|
return 1;
|
|
}
|
|
|
|
if (TypeManager::is_integer(inst1->_type)) {
|
|
// write(ostream, int)
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Returns -1 if the class does not define output() or python_repr() (and
|
|
* therefore cannot support a __repr__ function).
|
|
*
|
|
* Returns 1 if the class defines python_repr(ostream, string).
|
|
*
|
|
* Returns 2 if the class defines output(ostream).
|
|
*
|
|
* Returns 3 if the class defines an extension function for
|
|
* python_repr(ostream, string).
|
|
*
|
|
* Note that defining python_repr is deprecated in favor of defining a
|
|
* __repr__ that returns a string, which maps directly to the appropriate type
|
|
* slot.
|
|
*/
|
|
int InterfaceMakerPythonNative::
|
|
NeedsAReprFunction(const InterrogateType &itype_class) {
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
int num_methods = itype_class.number_of_methods();
|
|
int mi;
|
|
for (mi = 0; mi < num_methods; ++mi) {
|
|
FunctionIndex func_index = itype_class.get_method(mi);
|
|
const InterrogateFunction &ifunc = idb->get_function(func_index);
|
|
if (ifunc.get_name() == "python_repr") {
|
|
if (ifunc._instances != nullptr) {
|
|
InterrogateFunction::Instances::const_iterator ii;
|
|
for (ii = ifunc._instances->begin();
|
|
ii != ifunc._instances->end();
|
|
++ii) {
|
|
CPPInstance *cppinst = (*ii).second;
|
|
CPPFunctionType *cppfunc = cppinst->_type->as_function_type();
|
|
|
|
if (cppfunc != nullptr) {
|
|
if (cppfunc->_parameters != nullptr &&
|
|
cppfunc->_return_type != nullptr &&
|
|
TypeManager::is_void(cppfunc->_return_type)) {
|
|
if (cppfunc->_parameters->_parameters.size() == 2) {
|
|
CPPInstance *inst1 = cppfunc->_parameters->_parameters[0];
|
|
if (TypeManager::is_pointer_to_ostream(inst1->_type)) {
|
|
inst1 = cppfunc->_parameters->_parameters[1];
|
|
if (TypeManager::is_string(inst1->_type) ||
|
|
TypeManager::is_char_pointer(inst1->_type)) {
|
|
// python_repr(ostream, string)
|
|
if ((cppinst->_storage_class & CPPInstance::SC_extension) != 0) {
|
|
return 3;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (mi = 0; mi < num_methods; ++mi) {
|
|
FunctionIndex func_index = itype_class.get_method(mi);
|
|
const InterrogateFunction &ifunc = idb->get_function(func_index);
|
|
if (ifunc.get_name() == "output") {
|
|
if (ifunc._instances != nullptr) {
|
|
InterrogateFunction::Instances::const_iterator ii;
|
|
for (ii = ifunc._instances->begin();
|
|
ii != ifunc._instances->end();
|
|
++ii) {
|
|
CPPInstance *cppinst = (*ii).second;
|
|
CPPFunctionType *cppfunc = cppinst->_type->as_function_type();
|
|
|
|
if (cppfunc != nullptr) {
|
|
if (cppfunc->_parameters != nullptr &&
|
|
cppfunc->_return_type != nullptr &&
|
|
TypeManager::is_void(cppfunc->_return_type)) {
|
|
if (cppfunc->_parameters->_parameters.size() == 1) {
|
|
CPPInstance *inst1 = cppfunc->_parameters->_parameters[0];
|
|
if (TypeManager::is_pointer_to_ostream(inst1->_type)) {
|
|
// output(ostream)
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
if (cppfunc->_parameters->_parameters.size() >= 2) {
|
|
CPPInstance *inst1 = cppfunc->_parameters->_parameters[0];
|
|
if (TypeManager::is_pointer_to_ostream(inst1->_type)) {
|
|
inst1 = cppfunc->_parameters->_parameters[1];
|
|
if (inst1->_initializer != nullptr) {
|
|
// output(ostream, foo = bar, ...)
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the class defines a rich comparison operator.
|
|
*/
|
|
bool InterfaceMakerPythonNative::
|
|
NeedsARichCompareFunction(const InterrogateType &itype_class) {
|
|
InterrogateDatabase *idb = InterrogateDatabase::get_ptr();
|
|
|
|
int num_methods = itype_class.number_of_methods();
|
|
int mi;
|
|
for (mi = 0; mi < num_methods; ++mi) {
|
|
FunctionIndex func_index = itype_class.get_method(mi);
|
|
const InterrogateFunction &ifunc = idb->get_function(func_index);
|
|
if (ifunc.get_name() == "operator <") {
|
|
return true;
|
|
}
|
|
if (ifunc.get_name() == "operator <=") {
|
|
return true;
|
|
}
|
|
if (ifunc.get_name() == "operator ==") {
|
|
return true;
|
|
}
|
|
if (ifunc.get_name() == "operator !=") {
|
|
return true;
|
|
}
|
|
if (ifunc.get_name() == "operator >") {
|
|
return true;
|
|
}
|
|
if (ifunc.get_name() == "operator >=") {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Outputs the indicated string as a single quoted, multi-line string to the
|
|
* generated C++ source code. The output point is left on the last line of
|
|
* the string, following the trailing quotation mark.
|
|
*/
|
|
void InterfaceMakerPythonNative::
|
|
output_quoted(ostream &out, int indent_level, const std::string &str,
|
|
bool first_line) {
|
|
indent(out, (first_line ? indent_level : 0))
|
|
<< '"';
|
|
std::string::const_iterator si;
|
|
for (si = str.begin(); si != str.end();) {
|
|
switch (*si) {
|
|
case '"':
|
|
case '\\':
|
|
out << '\\' << *si;
|
|
break;
|
|
|
|
case '\n':
|
|
out << "\\n\"";
|
|
if (++si == str.end()) {
|
|
return;
|
|
}
|
|
out << "\n";
|
|
indent(out, indent_level)
|
|
<< '"';
|
|
continue;
|
|
|
|
default:
|
|
if (!isprint(*si)) {
|
|
out << "\\" << oct << std::setw(3) << std::setfill('0') << (unsigned int)(*si)
|
|
<< dec;
|
|
} else {
|
|
out << *si;
|
|
}
|
|
}
|
|
++si;
|
|
}
|
|
out << '"';
|
|
}
|