SWIG with preprocessor macro from forcing preprocessor

I am using an enum with a ToString implementation that was suggested here: How do I convert an enum variable to a string? As far as I can tell, it works and works great.

My problems arise when I try to wrap and export a macro in a Python library wrapped in SWIG. Similar question: SWIG errors due to preprocessor directive There was a solution to add headers / declarations to the SWIG interface. Until I have achieved this. Chances are I just don't know what I should add.

Tried:

%include <boost/preprocessor/config/config.hpp>
%include <boost/preprocessor/stringize.hpp>
%include <boost/preprocessor/seq/for_each.hpp>
%include <boost/preprocessor/seq/enum.hpp>

      

MWE:

minimal.h

#ifndef MINIMAL_H
#define MINIMAL_H
#include <boost/preprocessor.hpp>

//Found this here: /questions/10298/how-to-convert-an-enum-type-variable-to-a-string

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

DEFINE_ENUM_WITH_STRING_CONVERSIONS(my_enum, (A)(B))
#endif

      

minimal.cpp

#include <iostream> 
#include "minimal.h"

int main(){
    using namespace std;
    cout << A << ": " << ToString(A) << endl;
    cout << B << ": " << ToString(B) << endl;

}

      

minimal.i

%module minimal
%{
#include "minimal.h"
%}
%include "minimal.h"

      

The error is not very indicative. Line 29 is the actual definition of my_enum.

matthias@rp3deb:~/dvl/swig_boost_minimal$ swig minimal.i
minimal.h:29: Error: Syntax error in input(1).

      

Any advice on how I can wrap this up?

+3


source to share


1 answer


If you wanted to do SWIG read boost / preprocessor.hpp you would do it with

%module minimal
%{
#include "minimal.h"
%}
%include <boost/preprocessor.hpp>
%include "minimal.h"

      

Because SWIG doesn't execute directives by default #include

. (You can also use -includeall

to make him follow them). In this case, while I think the creation of the SWIG preprocessor does some sense of the crazy magic that the Boost preprocessor library uses is a lost cause.

Instead, we can try to get something with the equally nice but "Pythonic" syntax. Basically what we're going to do is write a completely different version DEFINE_ENUM_WITH_STRING_CONVERSIONS

just for the SWIG wrappers. It will be compatible with the definitions seen by C ++ though.

To do this, I'm going to start by splitting the minimum.h file into two files. One with the definition of the macro and the one that uses it. (We could do this in different ways, for example by wrapping macros with #ifndef DEFINE_ENUM_WITH_STRING_CONVERSIONS

or #ifndef SWIG

, which would be peer-to-peer solutions.)

So now we have enum.hh:

#ifndef ENUM_H
#define ENUM_H
#include <boost/preprocessor.hpp>

//Found this here: https://stackoverflow.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string
#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }
#endif

      

And minimal.h:

#ifndef MINIMAL_H
#define MINIMAL_H
#include "enum.h"

DEFINE_ENUM_WITH_STRING_CONVERSIONS(my_enum, (A)(B))
#endif

      

So, your minimal.cpp file continues to work as before, but now we can write a SWIG module that at least compiles, even if it's not useful yet:

%module minimal
%{
#include "minimal.h"
%}
%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%enddef
%include "minimal.h"

      

We currently have an unfinished SWIG macro that we are going to fill out. It's a little ugly the way I did it, simply because I am trying to avoid changing the way I define / use an existing macro.

What I created as a starting point is another file, enum.i:

%include <std_vector.i>
%include <std_string.i>

%{
#include <vector>
#include <string>
#include <tuple>
%}

%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%{
  typedef std::tuple<name,std::string> name ## _entry;
  struct name ## _helper {
    std::vector<name ## _entry> list;
    name ## _helper(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
    }
    name ## _helper operator()(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
      return *this;
    }
  };

  static const std::vector<name ## _entry> name ## _list = name ## _helper enumerators . list;
%}

struct name ## _entry {
  %extend {
    const unsigned long value {
      return std::get<0>(*$self);
    }
    const std::string& label {
      return std::get<1>(*$self);
    }
  }
};

%template(name ## vec) std::vector<name ## _entry>;

const std::vector<name ## _entry> name ## _list;

%enddef

      

Thus, the minimum. i just have to become:

%module minimal

%{
#include "minimal.h"
%}

%include "enum.i"
%include "minimal.h"

      



Everything the macro does takes a value enumerators

that would be something like, (A)(B)

and generates some code that is completely standard (if fancy) C ++ that extends it to std::vector<std::tuple<my_enum,std::string>>

. This is done by mapping the first member of the enum to a constructor call, and the rest to the overloaded one operator()

. We use the ToString()

provided enum.h to find the string representation. Finally, our macro has enough information to wrap a vector of tuples in a way that makes sense from within Python.

With this, we can do something like:

import minimal
print ", ".join(("%s(%d)" % (x.label,x.value) for x in minimal.my_enum_list))

      

Which, when compiled and run, gives:

A(0), B(1)

      

those. enough to start writing Python code that knows both the label and the C ++ enum value.

But don't stop! Why did I deliberately name the resulting vector my_enum_list

instead my_enum

? Because there is more we can do now.

Python 2.7 doesn't have "enum-ish" by default, but that doesn't stop us from wrapping it up as something both pythonic and natural for people who know about enums. I made Python 2.7 enum support by reading this other answer . To begin with, I added some generic enumeration support routines to the file using %pythoncode

, (marked # 1 in the final source), but outside of the SWIG macro, since there is no need to modify it. I also added %pythoncode

inside a SWIG macro (marked # 2) that calls this once per actual enum. To make this work, I had to transformconst std::vector

from the previous version to the function so that it is available on the right side of the generated Python. Finally, I had to show the SWIG declaration of a real enum to convince him to accept it as a function argument. Final result:

%include <std_vector.i>
%include <std_string.i>

%{
#include <vector>
#include <string>
#include <tuple>
%}

// #1
%pythoncode %{
class EnumValue(int):
  def __new__(cls,v,l):
    result = super(EnumValue,cls).__new__(cls,v)
    result._value = l
    return result
  def __str__(self):
    return self._value

def make_enum(name,enums):
    return type(name, (), enums)
%}

%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%{
  typedef std::tuple<name,std::string> name ## _entry;
  struct name ## _helper {
    std::vector<name ## _entry> list;
    name ## _helper(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
    }
    name ## _helper operator()(const name value) {
      list.push_back(std::make_tuple(value,ToString(value)));
      return *this;
    }
  };

  static const std::vector<name ## _entry> name ## _list() {
    return name ## _helper enumerators . list;
  }
%}

struct name ## _entry {
  %extend {
    const unsigned long value {
      return std::get<0>(*$self);
    }
    const std::string& label {
      return std::get<1>(*$self);
    }
  }
};

%template(name ## vec) std::vector<name ## _entry>;

const std::vector<name ## _entry> name ## _list();

// #2
%pythoncode %{
  name = make_enum('name', {x.label: EnumValue(x.value, x.label) for x in name ## _list()})
%}

enum name;

%enddef

      

I added the minimum.i function to prove that it actually works:

%module minimal

%{
#include "minimal.h"
%}

%include "enum.i"
%include "minimal.h"

%inline %{
  void foo(const my_enum& v) {
    std::cerr << "GOT: " << v << "\n";
  }
%}

      

And finally, test it with

import minimal
print minimal.my_enum
print minimal.my_enum.A
print minimal.my_enum.B

minimal.foo(minimal.my_enum.B)

      

Which you will be glad to see that it worked, and the result was:

<class 'minimal.my_enum'>
A
B
GOT: 1

      

If you're using Python 3, there is probably a better way to represent enums , but I'll leave that as an exercise for the reader for now. You can obviously customize the Python 2.7 fake enums to your liking.

+3


source







All Articles