Hi -

I've been working on a patch to sip 4.2 that adds support for templated
container types.

This lets sip automatically generate code to handle mapping between a
templated C++ container type and a Python collection of the appropriate
variety.

Some background: right now, if you want to map between, for example
std::vector<double> and a Python list, you have to explicitly write the
conversion code between the two.  Worse, you cannot reuse this code to
convert between a std::list<double> and a Python list.  So every kind of
templated container you use requires a pile of hand-written mapping
code, coupling drudgery with plenty of potential for error.

I've added to sip the ability to automatically map between C++
containers and Python collections.  You must still declare the C++
containers you want mapped, but now it's simply one line of code for
each, instead of a potentially large amount of source.

I've modified the grammar accepted by sip as follows.  In addition to
the current form of %MappedType ...

        %MappedType std::vector<blah> {
          // %TypeHeaderCode, %ConvertFromTypeCode and %ConvertToTypeCode
        };

sip now also accepts this:

        %MappedType std::vector<blah>;

It treats the lack of a body here as a cue to autogenerate the code that
will convert between a std::vector of blah and a Python collection.

If you want explicit control over the mapping code, you can still
specify a body, and sip will behave as it currently does.

For non-simple value types (the things inside the containers), you must
tell sip how to handle the value type, or it will produce an error
message.  No surprise there.

If you try to get sip to autogenerate code for an unsupported container
type (e.g. a std::pair), it will produce an error message.

I've attached a prerelease of the patch against sip 4.2, to get
feedback.  It does not affect any of the existing functionality of sip,
but simply adds the autogeneration feature.  I've attempted to follow
sip's coding style, so the patch will integrate cleanly.

The patch has the following caveats:

      * No support yet for Qt containers such as QValueList<>, which I
        don't use.  I don't even know if KDE has its own containers, so
        needless to say, there's no support for them, if they exist.
      * No support yet for std::map or related types.
      * I suspect that the error checking in some of the generated code
        is wrong in cases of memory allocation failure, but that should
        be easy to fix.
      * I haven't attached a diff against sipgen/parser.c, since the
        diff is huge.  You must use bison to regenerate parser.c from
        parser.y, because sip's build process doesn't create an explicit
        Makefile rule telling make to do this (so make will either do
        nothing or use yacc, both of which are wrong).

Here's a diffstat summary:

 gencode.c |  392 +++++++++++++++++++++++++++++++++++++++++++++-----------------
 parser.y  |   14 ++
 sip.h     |    7 +
 3 files changed, 310 insertions(+), 103 deletions(-)
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet sip-4.2/sipgen/gencode.c bk/buffy-runtime/sip/sipgen/gencode.c
--- sip-4.2/sipgen/gencode.c	2005-02-19 07:45:39 -08:00
+++ bk/buffy-runtime/sip/sipgen/gencode.c	2005-03-02 11:05:43 -08:00
@@ -113,6 +113,7 @@
 static void generateBinarySlotCall(overDef *od,char *op,FILE *fp);
 static void generateVariableHandler(varDef *,FILE *);
 static void generateObjToCppConversion(argDef *,char *,char *,FILE *);
+static void generateClassConversion(argDef *,char *,FILE *);
 static void generateVarClassConversion(varDef *,FILE *);
 static int generateVoidPointers(sipSpec *,classDef *,FILE *);
 static int generateChars(sipSpec *,classDef *,FILE *);
@@ -2131,6 +2132,249 @@
 
 
 /*
+ * Generate code to convert from a C++ object to a Python object.
+ */
+static void generateConvertFrom(argDef *arg,char *name,FILE *fp)
+{
+	switch (arg->atype) {
+	case mapped_type:
+		prcode(fp,
+"		valobj = sipConvertFrom_%T(%s);\n"
+		       ,arg,name);
+		break;
+
+	case class_type:
+		generateClassConversion(arg,name,fp);
+		break;
+
+	case bool_type:
+		prcode(fp,
+"		valobj = PyBool_FromLong(%s);\n"
+		       ,name);
+
+		break;
+
+	case ustring_type:
+	case string_type:
+		if (arg->nrderefs == 0)
+			prcode(fp,
+"		valobj = PyString_FromStringAndSize(%s&%s,1);\n"
+			       ,(arg->atype == ustring_type) ? "(char *)" : "",
+			       name);
+		else
+			prcode(fp,
+"		valobj = PyString_FromString(%s%s);\n"
+			       ,(arg->atype == ustring_type) ? "(char *)" : "",
+			       name);
+
+		break;
+
+	case float_type:
+	case cfloat_type:
+	case double_type:
+	case cdouble_type:
+		prcode(fp,
+"		valobj = PyFloat_FromDouble((double) %s);\n"
+		       ,name);
+		break;
+
+
+	case enum_type:
+		if (arg -> u.ed -> fqcname != NULL)
+		{
+			prcode(fp,
+"		valobj = sipConvertFromNamedEnum(%s,sipEnum_%C);\n"
+			       ,name,arg -> u.ed -> fqcname);
+
+			break;
+		}
+
+		break;
+
+	case ushort_type:
+	case short_type:
+	case uint_type:
+	case cint_type:
+	case int_type:
+	case ulong_type:
+		prcode(fp,
+"		valobj = PyInt_FromLong((long)%s);\n"
+		       ,name);
+		break;
+
+	case long_type:
+		prcode(fp,
+"		valobj = PyInt_FromLong(%s);\n"
+		       ,name);
+		break;
+
+	case struct_type:
+	case void_type:
+		prcode(fp,
+"		valobj = sipConvertFromVoidPtr(%s);\n"
+		       ,name);
+		break;
+
+	case pyobject_type:
+		prcode(fp,
+"		Py_XINCREF(%s);\n"
+		       ,name);
+		break;
+	}
+}
+
+
+
+static const containerMap containers[] = {
+	{ "vector", "PyList", },
+	{ "list", "PyList", },
+	{ "slist", "PyList", },
+	{ "deque", "PyList", },
+	{ NULL, NULL, },
+};
+
+containerMap *supportedContainer(mappedTypeDef *mtd)
+{
+	scopedNameDef *sn;
+	int i;
+
+	if (mtd -> type.atype != template_type)
+		return NULL;
+	
+	sn = mtd -> type.u.td -> fqname;
+
+	if (sn -> name == NULL)
+		return NULL;
+
+	if (strcmp(sn -> name, "std") != 0)
+		return NULL;
+
+	if ((sn = sn -> next) == NULL)
+		return NULL;
+	
+	for (i = 0; containers[i].cppName != NULL; i++) {
+		if (strcmp(sn -> name, containers[i].cppName) == 0)
+			return &containers[i];
+	}
+
+	return NULL;
+}
+
+
+static void generateContainerIncludes(mappedTypeDef *mtd,FILE *fp)
+{
+	containerMap *cm = supportedContainer(mtd);
+	
+	prcode(fp,
+"#include <%s>\n"
+	       ,cm -> cppName);
+}
+
+
+/*
+ * A "small type" is anything other than a class or struct type,
+ * i.e. something cheap to copy.
+ */
+static int isSmallType(argDef *ad)
+{
+	switch (ad -> atype) {
+	case class_type:
+	case struct_type:
+		return isReference(ad) || ad -> nrderefs > 0;
+	default:
+		return 1;
+	}
+}
+
+
+/*
+ * Autogenerate code to convert a supported class.
+ */
+static void generateConvertFromContainer(mappedTypeDef *mtd,FILE *fp)
+{
+	argDef *item = &mtd -> type.u.td -> types.args[0];
+	containerMap *cm = supportedContainer(mtd);
+	int small = isSmallType(item);
+	
+	prcode(fp,
+"	if (!sipCpp)\n"
+"		return NULL;\n"
+"	int size = static_cast<int>(sipCpp->size());\n"
+"	PyObject *l;\n"
+"\n"
+"\n"
+"	if ((l = %s_New(size)) == NULL)\n"
+"		return NULL;\n"
+"	%b::iterator i, end;\n"
+"	int j;\n"
+"	for (i = sipCpp -> begin(), end = sipCpp -> end(), j = 0; i != end; ++i, ++j)\n"
+"	{\n"
+	       ,cm -> pyName,&mtd -> type);
+	prcode(fp,
+"		");
+		
+	generateNamedBaseType(item,"val",fp);
+
+	prcode(fp,
+" = *i;\n"
+"		PyObject *valobj;\n"
+		);
+
+	generateConvertFrom(mtd -> type.u.td -> types.args,"val",fp);
+
+	prcode(fp,
+"		if (%s_SetItem(l, j, valobj) == -1)\n"
+"		{\n"
+"			Py_DECREF(l);\n"
+"			return NULL;\n"
+"		}\n"
+"	}\n"
+"\n"
+"	return l;\n"
+	       ,cm -> pyName);
+}
+
+
+/*
+ * Autogenerate code to convert a supported class.
+ */
+static void generateConvertToContainer(mappedTypeDef *mtd,FILE *fp)
+{
+	argDef *item = &mtd -> type.u.td -> types.args[0];
+	containerMap *cm = supportedContainer(mtd);
+	int small = isSmallType(item);
+
+	prcode(fp,
+"	if (sipIsErr == NULL)\n"
+"		return %s_Check(sipPy);\n"
+"\n"
+"	if (sipPy == Py_None) {\n"
+"		*sipCppPtr = NULL;\n"
+"		return 0;\n"
+"	}\n"
+"\n"
+"	%b *vals = new %b;\n"
+"	for (int i = 0, size = %s_Size(sipPy); i < size; i++) {\n"
+"		PyObject *valobj = %s_GetItem(sipPy, i);\n"
+"		int iserr;\n"
+"		"
+	       ,cm -> pyName,&mtd -> type,&mtd -> type,cm -> pyName,cm -> pyName		);
+	generateBaseType(item,fp);
+	prcode(fp, " ");
+	if (!small)
+		prcode(fp, "*");
+	prcode(fp,"val;\n",&mtd -> type);
+	generateObjToCppConversion(item,"val","\t\t",fp);
+	prcode(fp,
+"		vals->push_back(%sval);\n"
+"	}\n"
+"	*sipCppPtr = vals;\n"
+"	return 1;\n"
+	       ,small ? "" : "*");
+}
+
+
+/*
  * Generate the C++ code for a mapped type version.
  */
 static void generateMappedTypeCpp(mappedTypeDef *mtd,FILE *fp)
@@ -2153,7 +2397,10 @@
 		,&mtd -> type
 		,&mtd -> type,&mtd -> type);
 
-	generateCppCodeBlock(mtd -> convfromcode,fp);
+	if (mtd -> convfromcode)
+		generateCppCodeBlock(mtd -> convfromcode,fp);
+	else
+		generateConvertFromContainer(mtd,fp);
 
 	prcode(fp,
 "}\n"
@@ -2461,7 +2708,7 @@
 
 	/* Generate the type convertors. */
 
-	if (convtocode != NULL)
+	if (convtocode != NULL || cd == NULL)
 	{
 		int need_ptr;
 
@@ -2470,7 +2717,9 @@
 		 * flag, so check if we actually need everything so that we
 		 * can avoid compiler warnings.
 		 */
-		need_ptr = (strstr(convtocode -> frag,"sipCppPtr") != NULL);
+		need_ptr = ((convtocode != NULL &&
+			     strstr(convtocode -> frag,"sipCppPtr") != NULL) ||
+			    cd == NULL);
 
 		prcode(fp,
 "\n"
@@ -2485,7 +2734,10 @@
 "\n"
 				,&type,&type);
 
-		generateCppCodeBlock(convtocode,fp);
+		if (cd == NULL)
+			generateConvertToContainer(mtd,fp);
+		else
+			generateCppCodeBlock(convtocode,fp);
 
 		prcode(fp,
 "}\n"
@@ -2502,7 +2754,7 @@
 "\n"
 		,&type);
 
-	if (convtocode != NULL)
+	if (convtocode != NULL || cd == NULL)
 		prcode(fp,
 "	if (convertTo_%T(valobj,NULL,NULL))\n"
 "	{\n"
@@ -2615,96 +2867,8 @@
 "\n"
 		,scopedNameTail(vd -> fqcname));
 
-	switch (atype)
-	{
-	case mapped_type:
-		prcode(fp,
-"		valobj = sipConvertFrom_%T(val);\n"
-			,&vd -> type);
-
-		break;
-
-	case class_type:
-		generateVarClassConversion(vd,fp);
-		break;
-
-	case bool_type:
-		prcode(fp,
-"		valobj = PyBool_FromLong(val);\n"
-			);
-
-		break;
-
-	case ustring_type:
-	case string_type:
-		if (vd -> type.nrderefs == 0)
-			prcode(fp,
-"		valobj = PyString_FromStringAndSize(%s&val,1);\n"
-				,(atype == ustring_type) ? "(char *)" : "");
-		else
-			prcode(fp,
-"		valobj = PyString_FromString(%sval);\n"
-				,(atype == ustring_type) ? "(char *)" : "");
-
-		break;
-
-	case float_type:
-	case cfloat_type:
-		prcode(fp,
-"		valobj = PyFloat_FromDouble((double)val);\n"
-			);
-		break;
-
-	case double_type:
-	case cdouble_type:
-		prcode(fp,
-"		valobj = PyFloat_FromDouble(val);\n"
-			);
-		break;
-
-	case enum_type:
-		if (vd -> type.u.ed -> fqcname != NULL)
-		{
-			prcode(fp,
-"		valobj = sipConvertFromNamedEnum(val,sipEnum_%C);\n"
-				,vd -> type.u.ed -> fqcname);
-
-			break;
-		}
-
-		/* Drop through. */
-
-	case ushort_type:
-	case short_type:
-	case uint_type:
-	case cint_type:
-	case int_type:
-	case ulong_type:
-		prcode(fp,
-"		valobj = PyInt_FromLong((long)val);\n"
-			);
-		break;
-
-	case long_type:
-		prcode(fp,
-"		valobj = PyInt_FromLong(val);\n"
-			);
-		break;
-
-	case struct_type:
-	case void_type:
-		prcode(fp,
-"		valobj = sipConvertFromVoidPtr(val);\n"
-			);
-		break;
-
-	case pyobject_type:
-		prcode(fp,
-"		Py_XINCREF(val);\n"
-			);
-		break;
-	}
-
+	generateConvertFrom(&vd -> type,"val",fp);
+	
 	prcode(fp,
 "\n"
 "		return val%s;\n"
@@ -2770,9 +2934,8 @@
 
 
 /*
- * Generate an variable class conversion fragment.
+ * Generate a variable class conversion fragment.
  */
-
 static void generateVarClassConversion(varDef *vd,FILE *fp)
 {
 	classDef *cd = vd -> type.u.cd;
@@ -2781,7 +2944,7 @@
 "		valobj = sipMapCppToSelf%s(",(cd -> subbase != NULL ? "SubClass" : ""));
 
 	if (isConstArg(&vd -> type))
-		prcode(fp,"const_cast<%b *>(val)",&vd -> type);
+		prcode(fp,"const_cast<%b *>(val)", vd -> type);
 	else
 		prcode(fp,"val");
 
@@ -2791,6 +2954,26 @@
 
 
 /*
+ * Generate a class conversion fragment.
+ */
+static void generateClassConversion(argDef *type,char *name,FILE *fp)
+{
+	classDef *cd = type -> u.cd;
+
+	prcode(fp,
+"		valobj = sipMapCppToSelf%s(",(cd -> subbase != NULL ? "SubClass" : ""));
+
+	if (isConstArg(type))
+		prcode(fp,"const_cast<%b *>(&%s)", type,name);
+	else
+		prcode(fp,"&%s",name);
+
+	prcode(fp,",sipClass_%C);\n"
+		,classFQCName(cd));
+}
+
+
+/*
  * Generate the declaration of a variable that is initialised from a Python
  * object.
  */
@@ -4728,6 +4911,9 @@
 	if (genused)
 		generateUsedIncludes(mtd -> iff -> used,fp);
 
+	if (mtd -> convfromcode == NULL)
+		generateContainerIncludes(mtd,fp);
+	
 	prcode(fp,
 "\n"
 "#define	sipForceConvertTo_%T	sipMappedType_%T.mt_fcto\n"
@@ -7646,20 +7832,22 @@
 	for (a = 0; a < sd -> nrArgs; ++a)
 	{
 		argDef *ad = &sd -> args[a];
-		codeBlock *convtocode;
+		int convert = 0;
 
 		if (!isInArg(ad))
 			continue;
 
 		if (ad -> atype == class_type)
-			convtocode = ad -> u.cd -> convtocode;
+			convert = ad -> u.cd -> convtocode != NULL;
 		else if (ad -> atype == mapped_type)
-			convtocode = ad -> u.mtd -> convtocode;
+		{
+			convert = 1;
+		}
 		else
 			continue;
 
 		/* Delete temporary instances created by class convertors. */
-		if (convtocode != NULL)
+		if (convert)
 		{
 			prcode(fp,
 "\n"
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet sip-4.2/sipgen/parser.y bk/buffy-runtime/sip/sipgen/parser.y
--- sip-4.2/sipgen/parser.y	2005-02-19 07:45:39 -08:00
+++ bk/buffy-runtime/sip/sipgen/parser.y	2005-03-02 11:05:43 -08:00
@@ -301,7 +301,19 @@
 
 				currentMappedType = newMappedType(currentSpec,&$2);
 			}
-		} '{' mtbody '}' ';' {
+		} mtrest
+	;
+
+mtrest:		';' {
+			if (notSkipping()) {
+				if (currentMappedType -> type.atype != template_type)
+					yyerror("%MappedType cannot autogenerate code for non-template types");
+				if (supportedContainer(currentMappedType) == NULL)
+					yyerror("%MappedType does not support this container type");
+				currentMappedType = NULL;
+			}
+		}
+	|	'{' mtbody '}' ';' {
 			if (notSkipping())
 			{
 				if (currentMappedType -> convfromcode == NULL)
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet sip-4.2/sipgen/sip.h bk/buffy-runtime/sip/sipgen/sip.h
--- sip-4.2/sipgen/sip.h	2005-02-19 07:45:39 -08:00
+++ bk/buffy-runtime/sip/sipgen/sip.h	2005-03-02 11:05:43 -08:00
@@ -866,4 +866,11 @@
 	optFlag		flags[MAX_NR_FLAGS];	/* Each flag. */
 } optFlags;
 
+typedef struct {
+	char *cppName;	/* C++ container type. */
+	char *pyName;	/* Corresponding Python type. */
+} containerMap;
+	
+containerMap *supportedContainer(mappedTypeDef *);
+
 #endif
_______________________________________________
PyKDE mailing list    [email protected]
http://mats.imk.fraunhofer.de/mailman/listinfo/pykde

Reply via email to