Newer
Older
invertedlogic / InvertedLogic / iLFramework / toolkit / src / GL / PythonTests.cpp
@John Ryland John Ryland on 10 Nov 2019 13 KB rename
#include <string>
#include <vector>
#include <list>
#include <map>
#include <typeinfo>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/python/suite/indexing/map_indexing_suite.hpp>

#ifdef _DEBUG
#  undef _DEBUG
#  include <Python.h>
#  define _DEBUG
#else
#  include <Python.h>
#endif

#include "MemoryMapping.h"
#include "GL/PythonTests.h"


namespace { // Avoid cluttering the global namespace.

using namespace std;
class Shape
{
public:
	int x;
};

bool operator==(const Shape& a, const Shape& b) {
	return (a.x == b.x);
}

typedef std::vector<int> IntList;
typedef std::vector<Shape> Geometry;
typedef std::map<std::string,Geometry> Layer;

// https://wiki.python.org/moin/boost.python/StlContainers
// http://stackoverflow.com/questions/3240971/does-boost-python-support-a-function-returning-a-vector-by-ref-or-value


void IndexError() { PyErr_SetString(PyExc_IndexError, "Index out of range"); }


template<class T>
struct std_item
{
    typedef typename T::value_type V;
    static V& get(T& x, int i)
    {
		static V nothing;
        if( i<0 ) i+=x.size();
        if( i>=0 && i<(int)x.size() ) return x[i];
        IndexError();
		return nothing;
    }
    static void set(T& x, int i, V const& v)
    {
        if( i<0 ) i+=x.size();
        if( i>=0 && i<(int)x.size() ) x[i]=v;
        else IndexError();
    }
    static void del(T& x, int i)
    {
        if( i<0 ) i+=x.size();
        if( i>=0 && i<(int)x.size() ) x.erase(x.begin() + i);
        else IndexError();
    }
    static void add(T& x, V const& v)
    {
        x.push_back(v);
    }
	static bool in(T const& x, V const& v)
	{
		return find_eq(x.begin, x.end, v) != x.end();
	}
	static int index(T const& x, V const& v)
	{
			int i=0;
			for(typename T::const_iterator it=x.begin(); it!=x.end(); ++it,++i)
			  if( *it == v ) return i;
			return -1;
	}
};


void KeyError() { PyErr_SetString(PyExc_KeyError, "Key not found"); }

template<class T>
struct map_item
{
    typedef typename T::key_type K;
    typedef typename T::mapped_type V;
    static V& get(T& x, K const& i)
    {
		static V nothing;
        if( x.find(i) != x.end() ) return x[i];
        KeyError();
		return nothing;
    }
    static void set(T& x, K const& i, V const& v)
    {
        x[i]=v; // use map autocreation feature
    }
    static void del(T& x, K const& i)
    {
        if( x.find(i) != x.end() ) x.erase(i);
        else KeyError();
    }
	static bool in(T const& x, K const& i)
	{
		return x.find(i) != x.end();
	}
	static int index(T const& x, K const& k)
	{
			int i=0;
			for(typename T::const_iterator it=x.begin(); it!=x.end(); ++it,++i)
			  if( it->first == k ) return i;
			return -1;
	}
	static std::list<K> keys(T const& x)
	{
			std::list<K> t;
			for(T::const_iterator it=x.begin(); it!=x.end(); ++it)
				t.push_back(it->first);
			return t;
	}
	static std::list<V> values(T const& x)
	{
			std::list<V> t;
			for(T::const_iterator it=x.begin(); it!=x.end(); ++it)
				t.push_back(it->second);
			return t;
	}
	static std::list<std::pair<K,V> > items(T const& x)
	{
			std::list<std::pair<K,V> > t;
			for(T::const_iterator it=x.begin(); it!=x.end(); ++it)
				t.push_back(make_pair(it->first,it->second));
			return t;
	}
};


// A friendly class.
class hello
{
public:

	typedef std::map<std::string,std::string> StrStrMap;

    hello(const std::string& a_country)
	{
		m_country = a_country;
		m_countryCodeMap["Australia"] = "au";
		m_countryCodeMap["Great Britian"] = "gb";
		m_countryCodeMap["America"] = "us";
		m_countryCodeMap["France"] = "fr";
		m_countryCodeMap["Japan"] = "jp";
	}

    std::string greet() const { return "Hello from " + m_country; }
    
	StrStrMap& getMap() { return m_countryCodeMap; }

    void setMap(StrStrMap& newMap) { m_countryCodeMap = newMap; }
    void setMap2(boost::python::dict& pyMap) {  }

	std::string debugMap()
	{ 
		std::string ret;
		for (auto it = m_countryCodeMap.begin(); it != m_countryCodeMap.end(); ++it)
			ret += (*it).first + " = " + (*it).second + "\n";
		return ret;
	}

private:
    std::string m_country;
	StrStrMap   m_countryCodeMap;
};

// A function taking a hello object as an argument.
std::string invite(const hello& w)
{
	return w.greet() + "! Please come soon!";
}


// boost::python::dict dict;

#define PyExposeStruct(cls)		class_<cls>(#cls)
#define PyExposeVector(cls)		class_<cls>(#cls).def(vector_indexing_suite<cls>())
#define PyExposeMap(cls)		class_<cls>(#cls).def(map_indexing_suite<cls>())

BOOST_PYTHON_MODULE(hello_ext)
{
	using namespace boost::python;
    // def("greet", greet);
    //def("invite", invite);

	/*
	class_<Shape>("Shape");

	class_<Geometry>("Geometry")
	  .def("__len__", &Geometry::size)
	  .def("clear",   &Geometry::clear)
	  .def("append",  &std_item<Geometry>::add,
			with_custodian_and_ward<1,2>()) // to let container keep value
	  .def("__getitem__", &std_item<Geometry>::get,
			return_value_policy<copy_non_const_reference>())
	  .def("__setitem__", &std_item<Geometry>::set,
			with_custodian_and_ward<1,2>()) // to let container keep value
	  .def("__delitem__", &std_item<Geometry>::del)
	  .def("__index", &std_item<Geometry>::index)
	  .def("__iter__", boost::python::iterator<Geometry>())
	  //.def("__iter__", std::iterator<Geometry>())
	;
  
	class_<Layer>("Layer")
	  .def("__len__", &Layer::size)
	  .def("clear", &Layer::clear)
	  .def("__getitem__", &map_item<Layer>::get,
			return_value_policy<copy_non_const_reference>())
	  .def("__setitem__", &map_item<Layer>::set,
			with_custodian_and_ward<1,2>()) // to let container keep value
	  .def("__delitem__", &map_item<Layer>::del)
	  .def("__contains__", &map_item<Layer>::in)
	  .def("__index", &map_item<Layer>::index)
	  .def("keys", &map_item<Layer>::keys)
	  .def("values", &map_item<Layer>::values)
	  .def("items", &map_item<Layer>::items)
	  ;

	class_<hello::StrStrMap>("hello::StrStrMap")
	  .def("__len__", &hello::StrStrMap::size)
	  .def("clear", &hello::StrStrMap::clear)
	  .def("__getitem__", &map_item<hello::StrStrMap>::get,
			return_value_policy<copy_non_const_reference>())
	  .def("__setitem__", &map_item<hello::StrStrMap>::set,
			with_custodian_and_ward<1,2>()) // to let container keep value
	  .def("__delitem__", &map_item<hello::StrStrMap>::del)
	  .def("__contains__", &map_item<hello::StrStrMap>::in)
	  .def("__index", &map_item<hello::StrStrMap>::index)
	  .def("keys", &map_item<hello::StrStrMap>::keys)
	  .def("values", &map_item<hello::StrStrMap>::values)
	  .def("items", &map_item<hello::StrStrMap>::items)
	  ;
	*/

	PyExposeStruct(Shape);
	PyExposeVector(Geometry);
	PyExposeMap(Layer);
	PyExposeMap(hello::StrStrMap);
	
    class_<hello>("hello", init<std::string>())
        // Add a regular member function.
        .def("greet", &hello::greet)
        // Add invite() as a member of hello!
        .def("invite", invite)
		.def("getMap", &hello::getMap, return_value_policy<copy_non_const_reference>())
		.def("setMap", &hello::setMap2)
		.def("debugMap", &hello::debugMap)
        ;

    // Also add invite() as a regular function to the module.
    def("invite", invite);
}

}


BEGIN_NAMESPACE


static int numargs = 0;


long rawFunctionTest(int a, char *buf, int c)
{
	return a + 10 + c; // 16
}




template<typename T>
const char* rawTypeName(T const& t)
{
	return typeid(T).raw_name();
}

template<typename T>
const char* typeName(T const& t)
{
	return typeid(T).name();
	/*
  std::cout
    << pVarName
    << ": type: "     << 
    << ", raw type: " << typeid(t).raw_name()
    << std::endl;
	*/
}


// Test for embedding a method in python
/* Return the number of arguments of the application command line */
static PyObject* emb_numargs(PyObject *self, PyObject *args)
{
	int a = 0, c = 0;
	char *buf;

	// These 4 ways work, even with RTTI disabled!!!

	const char* signature1 = typeName(rawFunctionTest);
	const char* signature2 = rawTypeName(rawFunctionTest);

	const char* signature3 = typeid(rawFunctionTest).name();
	const char* signature4 = typeid(rawFunctionTest).raw_name();

	//typedef std::type_info const* base_id_t;
    //base_id_t m_base_type;
	//std::type_info::name(rawFunctionTest);

    if (!PyArg_ParseTuple(args, "i|s|i:numargs", &a, &buf, &c))
        return NULL;
    return PyLong_FromLong(numargs);
}


template<typename T>
static PyObject* pyCallWrapper(PyObject *self, PyObject *args)
{
	int a = 0, c = 0;
	char *buf;

	const char* signature = typeid(rawFunctionTest).name();

	//typedef std::type_info const* base_id_t;
    //base_id_t m_base_type;
	//std::type_info::name(rawFunctionTest);

    if (!PyArg_ParseTuple(args, "i|s|i:numargs", &a, &buf, &c))
        return NULL;
    return PyLong_FromLong(numargs);
}



#define PREPROCESSOR_CONCAT(a, b)		a ## b


#define DECLARE_PYTHON_FUNCTION3(name, argTypes, argT0, argT1, argT2) \
	static PyObject* PREPROCESSOR_CONCAT(python_embedded_, name)(PyObject *self, PyObject *pyArgs) \
	{ \
		void* args[5]; \
		if (!PyArg_ParseTuple(pyArgs, argTypes ":" #name, &args[0], &args[1], &args[2], &args[3], &args[4], &args[5])) \
			return NULL; \
		return PyLong_FromLong(name((argT0)args[0], (argT1)args[1], (argT2)args[2])); \
	}


DECLARE_PYTHON_FUNCTION3(rawFunctionTest, "i|s|i", int, char*, int)


template<typename T>
std::string getArgTypesFromFunc()
{
	std::string s = typeid(T).name();
	// TODO: some processing
	return s;
}

/*
template<typename retT, typename func, typename argT0, typename argT1, typename argT2>
static PyObject* python_embedded_generic_3args(PyObject *self, PyObject *pyArgs)
{
	void* args[3];
	std::string argTypes = getArgTypesFromFunc<func>() + ":";
	if (!PyArg_ParseTuple(pyArgs, argTypes + #name, &args[0], &args[1], &args[2]))
		return NULL;
	return PyLong_FromLong(name((argT0)args[0], (argT1)args[1], (argT2)args[2]));
}
*/


class Python_Method_Collection
{
	std::vector<PyMethodDef> m_methods;
public:
	void addMethod(const char* a_funcName, PyCFunction a_funcPtr, const char* a_desc)
	{
		PyMethodDef newMethod = { a_funcName, a_funcPtr, METH_VARARGS, a_desc };
		m_methods.push_back(newMethod);
	}
	PyMethodDef* finalize()
	{
		PyMethodDef nullMethod = { NULL, NULL, 0, NULL };
		m_methods.push_back(nullMethod);
		return m_methods.data();
	}
};


/*

class Python_Module_Base
{
	void preInit();
	static PyObject* Create(const char* a_moduleName, Python_Method_Collection& a_methods)
	{
		static PyModuleDef moduledef = {
			PyModuleDef_HEAD_INIT, a_moduleName, NULL, -1, a_methods.finalize(),
			NULL, NULL, NULL, NULL
		};
		return PyModule_Create(&moduledef);
	}
public:
	Python_Module_Base(const char* a_moduleName)
	{
		preInit();
		PyImport_AppendInittab(a_moduleName, &Python_Module_Base::Create);
	}
};

Python_Module_Base s_run_once_init_module_name;
void Python_Module_Base::preInit()

*/




#define PYTHON_MODULE(name) \
  class PREPROCESSOR_CONCAT(python_module_, name) { \
	static Python_Method_Collection s_methods; \
	static void preInit(); \
	static PyObject* init() { \
		static PyModuleDef moduledef = { \
			PyModuleDef_HEAD_INIT, #name, NULL, -1, s_methods.finalize(), \
			NULL, NULL, NULL, NULL \
		}; \
		return PyModule_Create(&moduledef); \
	} \
	static void addMethod(const char* a_funcName, PyCFunction a_funcPtr, const char* a_desc) { \
		s_methods.addMethod(a_funcName, a_funcPtr, a_desc); \
	} \
  public: \
	PREPROCESSOR_CONCAT(python_module_, name)() { \
		preInit();\
		PyImport_AppendInittab(#name, &PREPROCESSOR_CONCAT(python_module_, name)::init); \
	} \
  }; \
  Python_Method_Collection PREPROCESSOR_CONCAT(python_module_, name)::s_methods; \
  PREPROCESSOR_CONCAT(python_module_, name) PREPROCESSOR_CONCAT(g_python_module_, name); \
  void PREPROCESSOR_CONCAT(python_module_, name)::preInit()


  
PYTHON_MODULE(emb)
{
	addMethod("numargs", emb_numargs, "Return the number of arguments received by the process.");

	
	std::string functionSig = typeid(rawFunctionTest).name();
	std::string classSig = typeid(hello).name();
	//std::string classSig2 = typeid(hello::greet).name();
	std::string classSig3 = typeid(&hello::greet).name();



	/*
    class_<hello>("hello", init<std::string>())
        // Add a regular member function.
        .def("greet", &hello::greet)
        // Add invite() as a member of hello!
        .def("invite", invite)
        ;
    // Also add invite() as a regular function to the module.
    def("invite", invite);
	*/
}


/*
static PyMethodDef EmbMethods[] = {
    {"numargs", emb_numargs, METH_VARARGS, "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};

static PyModuleDef EmbModule = {
    PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
    NULL, NULL, NULL, NULL
};

static PyObject* PyInit_emb(void)
{
    return PyModule_Create(&EmbModule);
}
*/



void PythonTests()
{
  numargs = 2;

  PyImport_AppendInittab("hello_ext", &PyInit_hello_ext);

  Py_SetProgramName(L"Blah Prog Name");  /* optional but recommended */
  //Py_Main(0,0);
  Py_Initialize();

  MemoryMapping mmf("test.py");
  char* text = (char*)mmf.address();
  
  // Here we make a copy of the contents just so we can append a null at the end to be safe
  std::vector<char> buf;
  buf.reserve((size_t)mmf.size() + 1);
  memcpy(buf.data(), mmf.address(), (size_t)mmf.size());
  buf.data()[mmf.size()] = '\0';
  text = buf.data();

  PyRun_SimpleString(text);
  /*
					"import emb\n"
					 "test = 5\n"
					 "import hello_ext\n"
					 "obj = hello_ext.hello(\"me\")\n"
					 "print(obj.greet())\n"
					 "print(hello_ext.invite(obj))\n"
					 "print(obj.debugMap())\n"
					 "print(\"Number of arguments\", emb.numargs(1, \"blah\", test))\n"
					 "from time import time,ctime\n"
                     "print('Today is', ctime(time()))\n");
  */
  Py_Finalize();

  getchar();
  //exit(-1);
}


END_NAMESPACE