Commit 67eb4fc5 authored by David Gobbi's avatar David Gobbi

15995: Implement C++ templates as Python modules.

The help() function was not working on wrapped class templates, so the
PyVTKTemplate type has been re-imagined as a module that contains the
instantiations of the template, since the help() function works with
module docstrings. PyVTKTemplate still supports the mapping protocol
as before, which allows lookup of the instantiated templates via the
template arguments.
parent 331df4a5
Pipeline #7480 passed with stage
......@@ -18,12 +18,13 @@
This object is a container for instantiations of templated types.
Essentially, it is a "dict" that accepts template args as keys,
and provides the corresponding instantiation of the template.
It is implemented as a subclass of PyModule.
-----------------------------------------------------------------------*/
#include "PyVTKTemplate.h"
#include "vtkPythonUtil.h"
#include <structmember.h> // a python header
#include <string>
// Silence warning like
// "dereferencing type-punned pointer will break strict-aliasing rules"
......@@ -39,6 +40,11 @@ static const char *PyVTKTemplate_Doc =
"This is a dictionary for templates, provide the template args\n"
"in square brackets to get the desired kind of class.\n";
//--------------------------------------------------------------------
// Methods to help with name mangling and unmangling
PyObject *PyVTKTemplate_KeyFromName(PyObject *self, PyObject *arg);
PyObject *PyVTKTemplate_NameFromKey(PyObject *self, PyObject *key);
//--------------------------------------------------------------------
// Methods for python
......@@ -47,7 +53,14 @@ static PyObject *PyVTKTemplate_HasKey(PyObject *ob, PyObject *args)
PyObject *key = NULL;
if (PyArg_ParseTuple(args, "O:has_key", &key))
{
PyObject *rval = PyDict_GetItem(((PyVTKTemplate *)ob)->dict, key);
PyObject *rval = NULL;
PyObject *name = PyVTKTemplate_NameFromKey(ob, key);
if (name)
{
PyObject *dict = PyModule_GetDict(ob);
rval = PyDict_GetItem(dict, name);
Py_DECREF(name);
}
if (rval)
{
Py_DECREF(rval);
......@@ -67,7 +80,20 @@ static PyObject *PyVTKTemplate_Keys(PyObject *ob, PyObject *args)
{
if (PyArg_ParseTuple(args, ":keys"))
{
return PyDict_Keys(((PyVTKTemplate *)ob)->dict);
PyObject *dict = PyModule_GetDict(ob);
PyObject *l = PyList_New(0);
Py_ssize_t pos = 0;
PyObject *key, *value;
while (PyDict_Next(dict, &pos, &key, &value))
{
key = PyVTKTemplate_KeyFromName(ob, key);
if (key)
{
PyList_Append(l, key);
Py_DECREF(key);
}
}
return l;
}
return NULL;
}
......@@ -76,7 +102,20 @@ static PyObject *PyVTKTemplate_Values(PyObject *ob, PyObject *args)
{
if (PyArg_ParseTuple(args, ":values"))
{
return PyDict_Values(((PyVTKTemplate *)ob)->dict);
PyObject *dict = PyModule_GetDict(ob);
PyObject *l = PyList_New(0);
Py_ssize_t pos = 0;
PyObject *key, *value;
while (PyDict_Next(dict, &pos, &key, &value))
{
key = PyVTKTemplate_KeyFromName(ob, key);
if (key)
{
PyList_Append(l, value);
Py_DECREF(key);
}
}
return l;
}
return NULL;
}
......@@ -85,7 +124,24 @@ static PyObject *PyVTKTemplate_Items(PyObject *ob, PyObject *args)
{
if (PyArg_ParseTuple(args, ":items"))
{
return PyDict_Items(((PyVTKTemplate *)ob)->dict);
PyObject *dict = PyModule_GetDict(ob);
PyObject *l = PyList_New(0);
Py_ssize_t pos = 0;
PyObject *key, *value;
while (PyDict_Next(dict, &pos, &key, &value))
{
key = PyVTKTemplate_KeyFromName(ob, key);
if (key)
{
Py_INCREF(value);
PyObject *t = PyTuple_New(2);
PyTuple_SET_ITEM(t, 0, key);
PyTuple_SET_ITEM(t, 1, value);
PyList_Append(l, t);
Py_DECREF(t);
}
}
return l;
}
return NULL;
}
......@@ -96,7 +152,14 @@ static PyObject *PyVTKTemplate_Get(PyObject *ob, PyObject *args)
PyObject *def = Py_None;
if (PyArg_ParseTuple(args, "O|O:get", &key, &def))
{
PyObject *rval = PyDict_GetItem(((PyVTKTemplate *)ob)->dict, key);
PyObject *rval = NULL;
PyObject *dict = PyModule_GetDict(ob);
key = PyVTKTemplate_NameFromKey(ob, key);
if (key)
{
rval = PyDict_GetItem(dict, key);
Py_DECREF(key);
}
if (rval)
{
return rval;
......@@ -110,15 +173,6 @@ static PyObject *PyVTKTemplate_Get(PyObject *ob, PyObject *args)
return NULL;
}
static PyObject *PyVTKTemplate_Copy(PyObject *ob, PyObject *args)
{
if (PyArg_ParseTuple(args, ":copy"))
{
return PyDict_Copy(((PyVTKTemplate *)ob)->dict);
}
return NULL;
}
static PyMethodDef PyVTKTemplate_Methods[] = {
{"has_key", PyVTKTemplate_HasKey, METH_VARARGS,
......@@ -131,139 +185,55 @@ static PyMethodDef PyVTKTemplate_Methods[] = {
"T.items() -> list of (args,types) pairs."},
{"get", PyVTKTemplate_Get, METH_VARARGS,
"T.get(args) -> get instantiated template type or None."},
{"copy", PyVTKTemplate_Copy, METH_VARARGS,
"T.copy() -> get a shallow copy of T."},
{ NULL, NULL, 0, NULL }
};
//--------------------------------------------------------------------
// Members for python
static PyMemberDef PyVTKTemplate_Members[] = {
{(char *)"__doc__", T_OBJECT, offsetof(PyVTKTemplate, doc),
READONLY, NULL},
{(char *)"__name__", T_OBJECT, offsetof(PyVTKTemplate, name),
READONLY, NULL},
{(char *)"__module__", T_OBJECT, offsetof(PyVTKTemplate, module),
READONLY, NULL},
{(char *)"__bases__", T_OBJECT, offsetof(PyVTKTemplate, bases),
READONLY, NULL},
{ NULL, 0, 0, 0, NULL }
};
//--------------------------------------------------------------------
// Mapping protocol
static Py_ssize_t
PyVTKTemplate_Size(PyObject *ob)
{
ob = ((PyVTKTemplate *)ob)->dict;
return PyObject_Size(ob);
Py_ssize_t l = 0;
PyObject *dict = PyModule_GetDict(ob);
Py_ssize_t pos = 0;
PyObject *key, *value;
while (PyDict_Next(dict, &pos, &key, &value))
{
key = PyVTKTemplate_KeyFromName(ob, key);
if (key)
{
Py_DECREF(key);
l++;
}
}
return l;
}
static PyObject *
PyVTKTemplate_GetItem(PyObject *ob, PyObject *key)
{
static const char *typenames[] = {
"bool", "char",
"int8", "uint8", "int16", "uint16", "int32", "uint32",
"int", "uint", "int64", "uint64",
"float32", "float64",
"str", "unicode",
NULL };
static const char *typecodes[] = {
"?", "c",
"b", "B", "h", "H", "i", "I",
"l", "L", "q", "Q",
"f", "d",
NULL, NULL,
NULL };
Py_ssize_t nargs = 1;
bool multi = false;
if (PyTuple_Check(key))
PyObject *r = NULL;
PyObject *dict = PyModule_GetDict(ob);
PyObject *name = PyVTKTemplate_NameFromKey(ob, key);
if (name)
{
nargs = PyTuple_GET_SIZE(key);
multi = true;
}
PyObject *o = key;
PyObject *t = PyTuple_New(nargs);
for (Py_ssize_t i = 0; i < nargs; i++)
{
o = key;
if (multi)
// see if the named class is present
r = PyObject_GetItem(dict, name);
Py_DECREF(name);
if (r == 0)
{
o = PyTuple_GET_ITEM(key, i);
}
if (PyType_Check(o))
{
const char *cp =
vtkPythonUtil::StripModule(((PyTypeObject *)o)->tp_name);
size_t n = strlen(cp);
if (n == 5 && strcmp(cp, "float") == 0)
{
cp = "float64";
n += 2;
// clear the error (it will be set below)
PyErr_Clear();
}
while (n && cp[n-1] != '.') { --n; }
o = PyString_FromString(&cp[n]);
}
else
{
int typechar = '\0';
if (PyBytes_Check(o) && PyBytes_Size(o) == 1)
if (r == 0)
{
typechar = PyBytes_AS_STRING(o)[0];
}
#ifdef Py_USING_UNICODE
else if (PyUnicode_Check(o))
{
#if PY_VERSION_HEX >= 0x03030000
if (PyUnicode_GetLength(o) == 1)
{
typechar = PyUnicode_ReadChar(o, 0);
}
#else
if (PyUnicode_GetSize(o) == 1)
{
typechar = PyUnicode_AS_UNICODE(o)[0];
}
#endif
}
#endif
int j;
for (j = 0; typecodes[j]; j++)
{
if (typechar == typecodes[j][0])
{
o = PyString_FromString(typenames[j]);
break;
}
}
if (!typecodes[j])
{
Py_INCREF(o);
}
}
PyTuple_SET_ITEM(t, i, o);
}
PyObject *newkey = t;
if (!multi)
{
newkey = PyTuple_GET_ITEM(t, 0);
}
ob = ((PyVTKTemplate *)ob)->dict;
PyObject *r = PyObject_GetItem(ob, newkey);
// set a key error
PyObject *t = PyTuple_Pack(1, key);
PyErr_SetObject(PyExc_KeyError, t);
Py_DECREF(t);
}
return r;
}
......@@ -276,49 +246,9 @@ static PyMappingMethods PyVTKTemplate_AsMapping = {
};
//--------------------------------------------------------------------
static int PyVTKTemplate_Traverse(PyObject *o, visitproc visit, void *arg)
static PyObject *PyVTKTemplate_Repr(PyObject *self)
{
PyVTKTemplate *self = (PyVTKTemplate *)o;
Py_VISIT(self->dict);
Py_VISIT(self->doc);
Py_VISIT(self->name);
Py_VISIT(self->module);
Py_VISIT(self->bases);
return 0;
}
//--------------------------------------------------------------------
static void PyVTKTemplate_Delete(PyObject *op)
{
PyObject_GC_UnTrack(op);
Py_DECREF(((PyVTKTemplate *)op)->dict);
Py_DECREF(((PyVTKTemplate *)op)->doc);
Py_DECREF(((PyVTKTemplate *)op)->name);
Py_DECREF(((PyVTKTemplate *)op)->module);
Py_DECREF(((PyVTKTemplate *)op)->bases);
PyObject_GC_Del(op);
}
//--------------------------------------------------------------------
static PyObject *PyVTKTemplate_Repr(PyObject *op)
{
PyVTKTemplate *self = (PyVTKTemplate *)op;
#ifdef VTK_PY3K
return PyString_FromFormat("<%s %U.%U>",
Py_TYPE(op)->tp_name,
self->module,
self->name);
#else
return PyString_FromFormat("<%s %s.%s>",
Py_TYPE(op)->tp_name,
PyString_AS_STRING(self->module),
PyString_AS_STRING(self->name));
#endif
return PyString_FromFormat("<template %s>", PyModule_GetName(self));
}
//--------------------------------------------------------------------
......@@ -335,9 +265,9 @@ static PyObject *PyVTKTemplate_Call(PyObject *, PyObject *, PyObject *)
PyTypeObject PyVTKTemplate_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"vtkCommonCorePython.template", // tp_name
sizeof(PyVTKTemplate), // tp_basicsize
0, // tp_basicsize
0, // tp_itemsize
PyVTKTemplate_Delete, // tp_dealloc
0, // tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
......@@ -352,21 +282,18 @@ PyTypeObject PyVTKTemplate_Type = {
PyObject_GenericGetAttr, // tp_getattro
0, // tp_setattro
0, // tp_as_buffer
#ifndef VTK_PY3K
Py_TPFLAGS_CHECKTYPES |
#endif
Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_DEFAULT, // tp_flags
Py_TPFLAGS_DEFAULT, // tp_flags
PyVTKTemplate_Doc, // tp_doc
PyVTKTemplate_Traverse, // tp_traverse
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
PyVTKTemplate_Methods, // tp_methods
PyVTKTemplate_Members, // tp_members
0, // tp_members
0, // tp_getset
0, // tp_base
&PyModule_Type, // tp_base
0, // tp_dict
0, // tp_descr_get
0, // tp_descr_set
......@@ -385,83 +312,355 @@ PyTypeObject PyVTKTemplate_Type = {
};
//--------------------------------------------------------------------
// C API
//--------------------------------------------------------------------
PyObject *PyVTKTemplate_New(const char *name, const char *modulename,
const char *docstring[])
// Generate mangled name from the given template args
PyObject *PyVTKTemplate_NameFromKey(PyObject *self, PyObject *key)
{
PyVTKTemplate *op = PyObject_GC_New(PyVTKTemplate, &PyVTKTemplate_Type);
PyObject *self = (PyObject *)op;
// python type names
static const char *typenames[] = {
"bool", "char",
"int8", "uint8", "int16", "uint16", "int32", "uint32",
"int", "uint", "int64", "uint64",
"float32", "float64", "float",
"str", "unicode",
NULL };
op->dict = PyDict_New();
op->doc = vtkPythonUtil::BuildDocString(docstring);
op->name = PyString_FromString(name);
op->module = PyString_FromString(modulename);
op->bases = PyTuple_New(0);
// python type codes
static const char typecodes[] = {
'?', 'c',
'b', 'B', 'h', 'H', 'i', 'I',
'l', 'L', 'q', 'Q',
'f', 'd', 'd',
'\0', '\0',
'\0' };
PyObject_GC_Track(self);
// ia64 ABI type codes
static const char typechars[] = {
'b', 'c',
'a', 'h', 's', 't', 'i', 'j',
'l', 'm', 'x', 'y',
'f', 'd', 'd',
'\0', '\0',
'\0' };
return self;
// get name of the template (skip any namespaces)
const char *tname = PyModule_GetName(self);
for (const char *cp = tname; *cp != '\0'; cp++)
{
if (*cp == '.')
{
tname = cp + 1;
}
}
// begin constructing the mangled name
std::string name = tname;
name.push_back('_');
name.push_back('I');
// mangle the key using ia64 ABI for template args
Py_ssize_t nargs = 1;
bool multi = false;
if (PyTuple_Check(key))
{
nargs = PyTuple_GET_SIZE(key);
multi = true;
}
PyObject *o = key;
for (Py_ssize_t i = 0; i < nargs; i++)
{
o = key;
if (multi)
{
o = PyTuple_GET_ITEM(key, i);
}
tname = NULL;
if (PyType_Check(o))
{
// if type object, get the name of the type
Py_INCREF(o);
tname = ((PyTypeObject *)o)->tp_name;
for (const char *cp = tname; *cp != '\0'; cp++)
{
if (*cp == '.')
{
tname = cp + 1;
}
}
}
else
{
// else convert into an ASCII string
o = PyObject_Str(o);
if (PyBytes_Check(o))
{
tname = PyBytes_AS_STRING(o);
}
else if (PyUnicode_Check(o))
{
#if PY_VERSION_HEX >= 0x03030000
tname = PyUnicode_AsUTF8(o);
#else
PyObject *s = _PyUnicode_AsDefaultEncodedString(o, NULL);
tname = PyBytes_AS_STRING(s);
#endif
}
}
if ((*tname >= '0' && *tname <= '9') ||
(*tname == '-' && tname[1] >= '0' && tname[1] <= '9'))
{
// integer literal
name.push_back('L');
// guess the type based on available template instantiations
const char *trylist = (*tname == '-' ? "lxisa" : "lmxyijstah");
char typechar = 'l'; // C++ long is best fit for python int
int bestfit = 12;
PyObject *dict = PyModule_GetDict(self);
Py_ssize_t pos = 0;
PyObject *okey, *value;
// loop through all the wrapped template instances
while (PyDict_Next(dict, &pos, &okey, &value))
{
if (PyType_Check(value))
{
const char *cname = ((PyTypeObject *)value)->tp_name;
for (const char *cp = cname; *cp != '\0'; cp++)
{
if (*cp == '.')
{
cname = cp + 1;
}
}
if (strncmp(cname, name.data(), name.length()) == 0)
{
// compare this template instance against the typecode
char c = cname[name.length()];
for (int k = 0; trylist[k]; k++)
{
if (c == trylist[k] && k < bestfit)
{
typechar = c;
bestfit = k;
break;
}
}
}
}
}
// push the char that identifies the literal type
name.push_back(typechar);
if (*tname == '-')
{
name.push_back('n');
tname++;
}
while (*tname >= '0' && *tname <= '9')
{
name.push_back(*tname++);
}
name.push_back('E');
}
else
{
// check against known types
size_t n = strlen(tname);
char typechar = '\0';
for (int j = 0; typenames[j]; j++)
{
if (strcmp(typenames[j], tname) == 0)
{
typechar = typechars[j];
if (typechar == '\0')
{
if (n == 3 && strcmp(tname, "str") == 0)
{
tname = "vtkStdString";
n = 12;
}
else if (n == 7 && strcmp(tname, "unicode") == 0)
{
tname = "vtkUnicodeString";
n = 16;
}
}
break;
}
}
if (typechar == '\0' && n == 1)
{
for (int j = 0; typecodes[j]; j++)
{
if (tname[0] == typecodes[j])
{
typechar = typechars[j];
break;
}
}
}
if (typechar == 'l' || typechar == 'm')
{
// special compatibility code for 'long' (python 'int') to allow
// it to match either a 32-bit or a 64-bit integer
const char *trylist = (typechar == 'l' ? "lxi" : "myj");
int bestfit = 4;
PyObject *dict = PyModule_GetDict(self);
Py_ssize_t pos = 0;
PyObject *okey, *value;
// loop through all the wrapped template instances
while (PyDict_Next(dict, &pos, &okey, &value))
{
if (PyType_Check(value))
{
const char *cname = ((PyTypeObject *)value)->tp_name;
for (const char *cp = cname; *cp != '\0'; cp++)
{
if (*cp == '.')
{
cname = cp + 1;
}
}
if (strncmp(cname, name.data(), name.length()) == 0)
{
// compare this template instance against the typecode
char c = cname[name.length()];
for (int k = 0; trylist[k]; k++)
{
if (c == trylist[k] && k < bestfit)
{
typechar = c;
bestfit = k;
break;
}
}
}
}
}
}
if (typechar)
{
// for fundamental types, directly use the character code
name.push_back(typechar);
}
else if (n < 256)
{
// for all other types, write the type in full
if (n >= 100)
{
name.push_back('0' + static_cast<char>(n/100));
}
if (n >= 10)
{
name.push_back('0' + static_cast<char>((n % 100)/10));
}
name.push_back('0' + static_cast<char>(n % 10));
name += tname;
}
}
// free the python arg
Py_DECREF(o);
}
// close the list of template arguments
name.push_back('E');
#ifdef VTK_PY3K
return PyUnicode_FromStringAndSize(name.data(), name.length());
#else
return PyBytes_FromStringAndSize(name.data(), name.length());
#endif
}
//--------------------------------------------------------------------
int PyVTKTemplate_AddItem(PyObject *self, PyObject *val)
// Generate template args by unmangling the class name
PyObject *PyVTKTemplate_KeyFromName(PyObject *self, PyObject *o)
{
// convert arg to a C string
const char *name = NULL;
const char *cp;
char *dp;
const char *ptype;
PyObject *keys[16];
PyObject *key;
int i, n;
size_t j = 0;
if (PyBytes_Check(o))
{
name = PyBytes_AS_STRING(o);
}
else if (PyUnicode_Check(o))
{
#if PY_VERSION_HEX >= 0x03030000
name = PyUnicode_AsUTF8(o);
#else
PyObject *s = _PyUnicode_AsDefaultEncodedString(o, NULL);
name = PyBytes_AS_STRING(s);
#endif
}
if (PyType_Check(val))
if (!name)
{
name = ((PyTypeObject *)val)->tp_name;
// name must be a string
return NULL;
}
else
// get name of the template (skip any namespaces)
const char *tname = PyModule_GetName(self);
for (const char *cp = tname; *cp != '\0'; cp++)
{
PyErr_SetString(PyExc_TypeError, "value must be a class or type");
return -1;