3 from pybindgen.typehandlers import base as typehandlers
4 from pybindgen import ReturnValue, Parameter
5 from pybindgen.cppmethod import CustomCppMethodWrapper, CustomCppConstructorWrapper
6 from pybindgen.typehandlers.codesink import MemoryCodeSink
7 from pybindgen.typehandlers import ctypeparser
8 from pybindgen import cppclass
11 from pybindgen.typehandlers.base import CodeGenerationError
15 class SmartPointerTransformation(typehandlers.TypeTransformation):
17 This class provides a "type transformation" that tends to support
18 NS-3 smart pointers. Parameters such as "Ptr<Foo> foo" are
19 transformed into something like Parameter.new("Foo*", "foo",
20 transfer_ownership=False). Return values such as Ptr<Foo> are
21 transformed into ReturnValue.new("Foo*",
22 caller_owns_return=False). Since the underlying objects have
23 reference counting, PyBindGen does the right thing.
26 super(SmartPointerTransformation, self).__init__()
27 self.rx = re.compile(r'(ns3::|::ns3::|)Ptr<([^>]+)>')
29 def _get_untransformed_type_traits(self, name):
30 m = self.rx.match(name)
35 name1 = m.group(2).strip()
36 if name1.startswith('const '):
37 name1 = name1[len('const '):]
39 if name1.endswith(' const'):
40 name1 = name1[:-len(' const')]
44 if new_name.startswith('::'):
45 new_name = new_name[2:]
46 return new_name, is_const
48 def get_untransformed_name(self, name):
49 new_name, dummy_is_const = self._get_untransformed_type_traits(name)
52 def create_type_handler(self, type_handler, *args, **kwargs):
53 if issubclass(type_handler, Parameter):
54 kwargs['transfer_ownership'] = False
55 elif issubclass(type_handler, ReturnValue):
56 kwargs['caller_owns_return'] = False
60 ## fix the ctype, add ns3:: namespace
61 orig_ctype, is_const = self._get_untransformed_type_traits(args[0])
63 correct_ctype = 'ns3::Ptr< %s const >' % orig_ctype[:-2]
65 correct_ctype = 'ns3::Ptr< %s >' % orig_ctype[:-2]
66 args = tuple([correct_ctype] + list(args[1:]))
68 handler = type_handler(*args, **kwargs)
69 handler.set_tranformation(self, orig_ctype)
72 def untransform(self, type_handler, declarations, code_block, expression):
73 return 'const_cast<%s> (ns3::PeekPointer (%s))' % (type_handler.untransformed_ctype, expression)
75 def transform(self, type_handler, declarations, code_block, expression):
76 assert type_handler.untransformed_ctype[-1] == '*'
77 return 'ns3::Ptr< %s > (%s)' % (type_handler.untransformed_ctype[:-1], expression)
79 ## register the type transformation
80 transf = SmartPointerTransformation()
81 typehandlers.return_type_matcher.register_transformation(transf)
82 typehandlers.param_type_matcher.register_transformation(transf)
86 class ArgvParam(Parameter):
88 Converts a python list-of-strings argument to a pair of 'int argc,
89 char *argv[]' arguments to pass into C.
91 One Python argument becomes two C function arguments -> it's a miracle!
93 Note: this parameter type handler is not registered by any name;
94 must be used explicitly.
97 DIRECTIONS = [Parameter.DIRECTION_IN]
100 def convert_c_to_python(self, wrapper):
101 raise NotImplementedError
103 def convert_python_to_c(self, wrapper):
104 py_name = wrapper.declarations.declare_variable('PyObject*', 'py_' + self.name)
105 argc_var = wrapper.declarations.declare_variable('int', 'argc')
106 name = wrapper.declarations.declare_variable('char**', self.name)
107 idx = wrapper.declarations.declare_variable('Py_ssize_t', 'idx')
108 wrapper.parse_params.add_parameter('O!', ['&PyList_Type', '&'+py_name], self.name)
110 #wrapper.before_call.write_error_check('!PyList_Check(%s)' % py_name) # XXX
112 wrapper.before_call.write_code("%s = (char **) malloc(sizeof(char*)*PyList_Size(%s));"
114 wrapper.before_call.add_cleanup_code('free(%s);' % name)
115 wrapper.before_call.write_code('''
116 for (%(idx)s = 0; %(idx)s < PyList_Size(%(py_name)s); %(idx)s++)
119 wrapper.before_call.sink.indent()
120 wrapper.before_call.write_code('''
121 PyObject *item = PyList_GET_ITEM(%(py_name)s, %(idx)s);
123 #wrapper.before_call.write_error_check('item == NULL')
124 wrapper.before_call.write_error_check(
125 '!PyString_Check(item)',
126 failure_cleanup=('PyErr_SetString(PyExc_TypeError, '
127 '"argument %s must be a list of strings");') % self.name)
128 wrapper.before_call.write_code(
129 '%s[%s] = PyString_AsString(item);' % (name, idx))
130 wrapper.before_call.sink.unindent()
131 wrapper.before_call.write_code('}')
132 wrapper.before_call.write_code('%s = PyList_Size(%s);' % (argc_var, py_name))
134 wrapper.call_params.append(argc_var)
135 wrapper.call_params.append(name)
138 class CallbackImplProxyMethod(typehandlers.ReverseWrapperBase):
140 Class that generates a proxy virtual method that calls a similarly named python method.
143 def __init__(self, return_value, parameters):
144 super(CallbackImplProxyMethod, self).__init__(return_value, parameters)
146 def generate_python_call(self):
147 """code to call the python method"""
148 build_params = self.build_params.get_parameters(force_tuple_creation=True)
149 if build_params[0][0] == '"':
150 build_params[0] = '(char *) ' + build_params[0]
151 args = self.before_call.declare_variable('PyObject*', 'args')
152 self.before_call.write_code('%s = Py_BuildValue(%s);'
153 % (args, ', '.join(build_params)))
154 self.before_call.add_cleanup_code('Py_DECREF(%s);' % args)
155 self.before_call.write_code('py_retval = PyObject_CallObject(m_callback, %s);' % args)
156 self.before_call.write_error_check('py_retval == NULL')
157 self.before_call.add_cleanup_code('Py_DECREF(py_retval);')
162 def generate_callback_classes(out, callbacks):
163 for callback_impl_num, template_parameters in enumerate(callbacks):
164 sink = MemoryCodeSink()
165 cls_name = "ns3::Callback< %s >" % ', '.join(template_parameters)
166 #print >> sys.stderr, "***** trying to register callback: %r" % cls_name
167 class_name = "PythonCallbackImpl%i" % callback_impl_num
169 class %s : public ns3::CallbackImpl<%s>
172 PyObject *m_callback;
173 %s(PyObject *callback)
176 m_callback = callback;
180 Py_DECREF(m_callback);
184 virtual bool IsEqual(ns3::Ptr<const ns3::CallbackImplBase> other_base) const
186 const %s *other = dynamic_cast<const %s*> (ns3::PeekPointer (other_base));
188 return (other->m_callback == m_callback);
193 ''' % (class_name, ', '.join(template_parameters), class_name, class_name, class_name, class_name))
195 callback_return = template_parameters[0]
196 return_ctype = ctypeparser.parse_type(callback_return)
197 if ('const' in return_ctype.remove_modifiers()):
198 kwargs = {'is_const': True}
202 return_type = ReturnValue.new(str(return_ctype), **kwargs)
203 except (typehandlers.TypeLookupError, typehandlers.TypeConfigurationError), ex:
204 warnings.warn("***** Unable to register callback; Return value '%s' error (used in %s): %r"
205 % (callback_return, cls_name, ex),
211 callback_parameters = [arg for arg in template_parameters[1:] if arg != 'ns3::empty']
212 for arg_num, arg_type in enumerate(callback_parameters):
213 arg_name = 'arg%i' % (arg_num+1)
215 param_ctype = ctypeparser.parse_type(arg_type)
216 if ('const' in param_ctype.remove_modifiers()):
217 kwargs = {'is_const': True}
221 arguments.append(Parameter.new(str(param_ctype), arg_name, **kwargs))
222 except (typehandlers.TypeLookupError, typehandlers.TypeConfigurationError), ex:
223 warnings.warn("***** Unable to register callback; parameter '%s %s' error (used in %s): %r"
224 % (arg_type, arg_name, cls_name, ex),
230 wrapper = CallbackImplProxyMethod(return_type, arguments)
231 wrapper.generate(sink, 'operator()', decl_modifiers=[])
237 class PythonCallbackParameter(Parameter):
240 #print >> sys.stderr, "***** registering callback handler: %r" % ctypeparser.normalize_type_string(cls_name)
241 DIRECTIONS = [Parameter.DIRECTION_IN]
242 PYTHON_CALLBACK_IMPL_NAME = class_name
243 TEMPLATE_ARGS = template_parameters
245 def convert_python_to_c(self, wrapper):
246 "parses python args to get C++ value"
247 assert isinstance(wrapper, typehandlers.ForwardWrapperBase)
249 py_callback = wrapper.declarations.declare_variable('PyObject*', self.name)
250 wrapper.parse_params.add_parameter('O', ['&'+py_callback], self.name)
251 wrapper.before_call.write_error_check(
252 '!PyCallable_Check(%s)' % py_callback,
253 'PyErr_SetString(PyExc_TypeError, "parameter \'%s\' must be callbale");' % self.name)
254 callback_impl = wrapper.declarations.declare_variable(
255 'ns3::Ptr<%s>' % self.PYTHON_CALLBACK_IMPL_NAME,
256 '%s_cb_impl' % self.name)
257 wrapper.before_call.write_code("%s = ns3::Create<%s> (%s);"
258 % (callback_impl, self.PYTHON_CALLBACK_IMPL_NAME, py_callback))
259 wrapper.call_params.append(
260 'ns3::Callback<%s> (%s)' % (', '.join(self.TEMPLATE_ARGS), callback_impl))
262 def convert_c_to_python(self, wrapper):
263 raise typehandlers.NotSupportedError("Reverse wrappers for ns3::Callback<...> types "
264 "(python using callbacks defined in C++) not implemented.")
267 # def write_preamble(out):
268 # pybindgen.write_preamble(out)
269 # out.writeln("#include \"ns3/everything.h\"")
273 def Simulator_customizations(module):
274 Simulator = module['ns3::Simulator']
276 ## Simulator::Schedule(delay, callback, ...user..args...)
277 Simulator.add_custom_method_wrapper("Schedule", "_wrap_Simulator_Schedule",
278 flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
281 ## Simulator::ScheduleNow(callback, ...user..args...)
282 Simulator.add_custom_method_wrapper("ScheduleNow", "_wrap_Simulator_ScheduleNow",
283 flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
286 ## Simulator::ScheduleDestroy(callback, ...user..args...)
287 Simulator.add_custom_method_wrapper("ScheduleDestroy", "_wrap_Simulator_ScheduleDestroy",
288 flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
290 Simulator.add_custom_method_wrapper("Run", "_wrap_Simulator_Run",
291 flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
294 def CommandLine_customizations(module):
295 CommandLine = module['ns3::CommandLine']
296 CommandLine.add_method('Parse', None, [ArgvParam(None, 'argv')],
298 CommandLine.add_custom_method_wrapper("AddValue", "_wrap_CommandLine_AddValue",
299 flags=["METH_VARARGS", "METH_KEYWORDS"])
302 def Object_customizations(module):
303 ## ---------------------------------------------------------------------
304 ## Here we generate custom constructor code for all classes that
305 ## derive from ns3::Object. The custom constructors are needed in
306 ## order to support kwargs only and to translate kwargs into ns3
308 ## ---------------------------------------------------------------------
309 Object = module['ns3::Object']
312 ## add a GetTypeId method to all generatd helper classes
313 def helper_class_hook(helper_class):
315 static ns3::TypeId GetTypeId (void)
317 static ns3::TypeId tid = ns3::TypeId ("%s")
321 }""" % (helper_class.name, helper_class.class_.full_name)
323 helper_class.add_custom_method(decl)
324 helper_class.add_post_generation_code(
325 "NS_OBJECT_ENSURE_REGISTERED (%s);" % helper_class.name)
326 Object.add_helper_class_hook(helper_class_hook)
328 ## Replace all class constructors with a generic constructor based on CreateObjectWithAttributes<T> (AttributeList)
329 module.header.writeln('''
332 void PythonCompleteConstruct (Ptr<Object> object, TypeId typeId, const AttributeList &attributes);
334 template <typename T>
335 Ptr<T> CreateObjectPython (PyObject *pyobj, const AttributeList &attributes)
337 Ptr<T> p = Ptr<T> (new T (), false);
338 p->set_pyobj (pyobj);
339 PythonCompleteConstruct (p, T::GetTypeId (), attributes);
347 for cls in module.classes:
348 if not cls.is_subclass(Object):
350 cls.constructors = [] # clear the list of constructors
352 ## add our own custom constructor, if possible
354 construct_name = cls.get_construct_name()
355 except CodeGenerationError:
356 construct_name = None
358 if construct_name and not cls.helper_class:
360 ns3::Ptr< %(CONSTRUCT_NAME)s > obj = ns3::CreateObjectWithAttributes< %(CONSTRUCT_NAME)s > (attrList);
362 self->obj = ns3::PeekPointer (obj);
363 ''' % dict (CONSTRUCT_NAME=construct_name)
365 elif not construct_name and not cls.helper_class:
368 elif not construct_name and cls.helper_class:
370 if (self->ob_type != &%(PYTYPESTRUCT)s)
372 ns3::Ptr< %(HELPER_CLASS_NAME)s > obj = ns3::CreateObjectPython< %(HELPER_CLASS_NAME)s > ((PyObject *)self, attrList);
374 self->obj = ns3::PeekPointer (obj);
376 PyErr_SetString(PyExc_TypeError, "Class cannot be constructed (unless subclassed)");
378 PyObject *exc_type, *traceback;
379 PyErr_Fetch(&exc_type, return_exception, &traceback);
380 Py_XDECREF(exc_type);
381 Py_XDECREF(traceback);
385 ''' % dict (CONSTRUCT_NAME=construct_name, HELPER_CLASS_NAME=cls.helper_class.name,
386 PYTYPESTRUCT=cls.pytypestruct)
388 elif construct_name and cls.helper_class:
390 if (self->ob_type != &%(PYTYPESTRUCT)s)
392 ns3::Ptr< %(HELPER_CLASS_NAME)s > obj = ns3::CreateObjectPython< %(HELPER_CLASS_NAME)s > ((PyObject *)self, attrList);
394 self->obj = ns3::PeekPointer (obj);
396 ns3::Ptr< %(CONSTRUCT_NAME)s > obj = ns3::CreateObjectWithAttributes< %(CONSTRUCT_NAME)s > (attrList);
398 self->obj = ns3::PeekPointer (obj);
400 ''' % dict (CONSTRUCT_NAME=construct_name, HELPER_CLASS_NAME=cls.helper_class.name,
401 PYTYPESTRUCT=cls.pytypestruct)
405 wrapper_name = "_wrap_create_object_%s" % (cls.mangled_full_name,)
407 static int %(WRAPPER_NAME)s (%(PYSTRUCT)s *self, PyObject *args, PyObject *kwargs, PyObject **return_exception)
409 if (PyTuple_Size(args)) {
410 PyErr_SetString(PyExc_TypeError, "positional arguments not supported "
411 "for ns3.Object constructors, only keyword arguments"
412 " should be used (AttributeName=Value)");
414 PyObject *exc_type, *traceback;
415 PyErr_Fetch(&exc_type, return_exception, &traceback);
416 Py_XDECREF(exc_type);
417 Py_XDECREF(traceback);
421 ns3::AttributeList attrList;
422 if (kwargs && KwargsToAttributeList(kwargs, %(CLASS_NAME)s::GetTypeId(), attrList)) {
424 PyObject *exc_type, *traceback;
425 PyErr_Fetch(&exc_type, return_exception, &traceback);
426 Py_XDECREF(exc_type);
427 Py_XDECREF(traceback);
432 PyNs3ObjectBase_wrapper_registry[(void *) self->obj] = (PyObject *) self;
435 ''' % dict(WRAPPER_NAME=wrapper_name, PYSTRUCT=cls.pystruct, CLASS_NAME=cls.full_name,
436 CONSTRUCT_CODE=construct_code, PURE_VIRTUALS=cls.have_pure_virtual_methods)
437 cls.add_constructor(CustomCppConstructorWrapper(wrapper_name, constructor))
440 # Generate conversion function from PyObject* to AttributeValue
443 # Ptr<AttributeValue> AttributeValueFromPyObject (PyObject *obj)
445 # // note: needs to check for bool first, because bool is a subclass of int
446 # if (PyBool_Check(obj)) {
447 # return Create<BooleanValue>(PyObject_IsTrue(obj));
448 # } else if (PyInt_Check(obj)) {
449 # return Create<IntegerValue>(PyInt_AsLong(obj));
450 # } else if (PyLong_Check(obj)) {
451 # return Create<IntegerValue>(PyLong_AsLongLong(obj));
452 # } else if (PyFloat_Check(obj)) {
453 # return Create<DoubleValue>(PyFloat_AsDouble(obj));
460 ## ---------------------------------------------------------------------
461 ## -------------- write the KwargsToAttributeList function -------------
462 ## ---------------------------------------------------------------------
463 Attribute = module['ns3::AttributeValue']
464 module.after_forward_declarations.writeln(
465 'int KwargsToAttributeList(PyObject *kwargs, ns3::TypeId tid, ns3::AttributeList &oAttrList);')
469 int KwargsToAttributeList(PyObject *kwargs, ns3::TypeId tid, ns3::AttributeList &oAttrList)
471 PyObject *key, *value;
474 while (PyDict_Next(kwargs, &pos, &key, &value)) {
475 if (!PyString_Check(key)) {
476 PyErr_SetString(PyExc_TypeError, "kwargs keys must be strings");
479 if (PyObject_IsInstance(value, (PyObject*) &%s)) {
480 oAttrList.SetWithTid(tid, PyString_AsString(key), *((%s *) value)->obj);''' \
481 % (Attribute.pytypestruct, Attribute.pystruct))
483 for conversion_source in Attribute.get_all_implicit_conversions():
484 module.body.writeln('''
485 } else if (PyObject_IsInstance(value, (PyObject*) &%s)) {
486 oAttrList.SetWithTid(tid, PyString_AsString(key), *((%s *) value)->obj);''' \
487 % (conversion_source.pytypestruct, conversion_source.pystruct))
489 possible_type_names = ", ".join([cls.name for cls in [Attribute] + Attribute.get_all_implicit_conversions()])
490 module.body.writeln('''
492 PyErr_Format(PyExc_TypeError, \"parameter must an instance of one of the types (%s), not %%s\", value->ob_type->tp_name);
494 }''' % (possible_type_names))
504 def Attribute_customizations(module):
505 # Fix up for the "const AttributeValue &v = EmptyAttribute()"
506 # case, as used extensively by helper classes.
508 # Here's why we need to do this: pybindgen.gccxmlscanner, when
509 # scanning parameter default values, is only provided with the
510 # value as a simple C expression string. (py)gccxml does not
511 # report the type of the default value.
513 # As a workaround, here we iterate over all parameters of all
514 # methods of all classes and tell pybindgen what is the type of
515 # the default value for attributes.
517 for cls in module.classes:
518 for meth in cls.get_all_methods():
519 for param in meth.parameters:
520 if isinstance(param, cppclass.CppClassRefParameter):
521 if param.cpp_class.name == 'AttributeValue' \
522 and param.default_value is not None \
523 and param.default_value_type is None:
524 param.default_value_type = 'ns3::EmptyAttributeValue'
527 def TypeId_customizations(module):
528 TypeId = module['ns3::TypeId']
529 TypeId.add_custom_method_wrapper("LookupByNameFailSafe", "_wrap_TypeId_LookupByNameFailSafe",
530 flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
533 def add_std_ofstream(module):
534 module.add_include('<fstream>')
535 ostream = module.add_class('ostream', foreign_cpp_namespace='::std')
536 ostream.set_cannot_be_constructed("abstract base class")
537 ofstream = module.add_class('ofstream', foreign_cpp_namespace='::std', parent=ostream)
538 ofstream.add_enum('openmode', [
539 ('app', 'std::ios_base::app'),
540 ('ate', 'std::ios_base::ate'),
541 ('binary', 'std::ios_base::binary'),
542 ('in', 'std::ios_base::in'),
543 ('out', 'std::ios_base::out'),
544 ('trunc', 'std::ios_base::trunc'),
546 ofstream.add_constructor([Parameter.new("const char *", 'filename'),
547 Parameter.new("::std::ofstream::openmode", 'mode', default_value="std::ios_base::out")])
548 ofstream.add_method('close', None, [])