The Python constructors are now parameter oriented instead of attribute oriented. The main motivation for this change is that Python bindings become broken with new Object types being introduced that do not have a default constructor (constructor without parameters). Another reason is to shorten the gap between C++ and Python, API-wise--the more similar they are, the less confusing it becomes.
427 lines
18 KiB
Python
427 lines
18 KiB
Python
import re
|
|
|
|
from pybindgen.typehandlers import base as typehandlers
|
|
from pybindgen import ReturnValue, Parameter
|
|
from pybindgen.cppmethod import CustomCppMethodWrapper, CustomCppConstructorWrapper
|
|
from pybindgen.typehandlers.codesink import MemoryCodeSink
|
|
from pybindgen.typehandlers import ctypeparser
|
|
from pybindgen import cppclass
|
|
import warnings
|
|
|
|
from pybindgen.typehandlers.base import CodeGenerationError
|
|
|
|
import sys
|
|
|
|
class SmartPointerTransformation(typehandlers.TypeTransformation):
|
|
"""
|
|
This class provides a "type transformation" that tends to support
|
|
NS-3 smart pointers. Parameters such as "Ptr<Foo> foo" are
|
|
transformed into something like Parameter.new("Foo*", "foo",
|
|
transfer_ownership=False). Return values such as Ptr<Foo> are
|
|
transformed into ReturnValue.new("Foo*",
|
|
caller_owns_return=False). Since the underlying objects have
|
|
reference counting, PyBindGen does the right thing.
|
|
"""
|
|
def __init__(self):
|
|
super(SmartPointerTransformation, self).__init__()
|
|
self.rx = re.compile(r'(ns3::|::ns3::|)Ptr<([^>]+)>\s*$')
|
|
|
|
def _get_untransformed_type_traits(self, name):
|
|
m = self.rx.match(name)
|
|
is_const = False
|
|
if m is None:
|
|
return None, False
|
|
else:
|
|
name1 = m.group(2).strip()
|
|
if name1.startswith('const '):
|
|
name1 = name1[len('const '):]
|
|
is_const = True
|
|
if name1.endswith(' const'):
|
|
name1 = name1[:-len(' const')]
|
|
is_const = True
|
|
new_name = name1+' *'
|
|
|
|
if new_name.startswith('::'):
|
|
new_name = new_name[2:]
|
|
return new_name, is_const
|
|
|
|
def get_untransformed_name(self, name):
|
|
new_name, dummy_is_const = self._get_untransformed_type_traits(name)
|
|
return new_name
|
|
|
|
def create_type_handler(self, type_handler, *args, **kwargs):
|
|
if issubclass(type_handler, Parameter):
|
|
kwargs['transfer_ownership'] = False
|
|
elif issubclass(type_handler, ReturnValue):
|
|
kwargs['caller_owns_return'] = False
|
|
else:
|
|
raise AssertionError
|
|
|
|
## fix the ctype, add ns3:: namespace
|
|
orig_ctype, is_const = self._get_untransformed_type_traits(args[0])
|
|
if is_const:
|
|
correct_ctype = 'ns3::Ptr< %s const >' % orig_ctype[:-2]
|
|
else:
|
|
correct_ctype = 'ns3::Ptr< %s >' % orig_ctype[:-2]
|
|
args = tuple([correct_ctype] + list(args[1:]))
|
|
|
|
handler = type_handler(*args, **kwargs)
|
|
handler.set_tranformation(self, orig_ctype)
|
|
return handler
|
|
|
|
def untransform(self, type_handler, declarations, code_block, expression):
|
|
return 'const_cast<%s> (ns3::PeekPointer (%s))' % (type_handler.untransformed_ctype, expression)
|
|
|
|
def transform(self, type_handler, declarations, code_block, expression):
|
|
assert type_handler.untransformed_ctype[-1] == '*'
|
|
return 'ns3::Ptr< %s > (%s)' % (type_handler.untransformed_ctype[:-1], expression)
|
|
|
|
## register the type transformation
|
|
transf = SmartPointerTransformation()
|
|
typehandlers.return_type_matcher.register_transformation(transf)
|
|
typehandlers.param_type_matcher.register_transformation(transf)
|
|
del transf
|
|
|
|
|
|
class ArgvParam(Parameter):
|
|
"""
|
|
Converts a python list-of-strings argument to a pair of 'int argc,
|
|
char *argv[]' arguments to pass into C.
|
|
|
|
One Python argument becomes two C function arguments -> it's a miracle!
|
|
|
|
Note: this parameter type handler is not registered by any name;
|
|
must be used explicitly.
|
|
"""
|
|
|
|
DIRECTIONS = [Parameter.DIRECTION_IN]
|
|
CTYPES = []
|
|
|
|
def convert_c_to_python(self, wrapper):
|
|
raise NotImplementedError
|
|
|
|
def convert_python_to_c(self, wrapper):
|
|
py_name = wrapper.declarations.declare_variable('PyObject*', 'py_' + self.name)
|
|
argc_var = wrapper.declarations.declare_variable('int', 'argc')
|
|
name = wrapper.declarations.declare_variable('char**', self.name)
|
|
idx = wrapper.declarations.declare_variable('Py_ssize_t', 'idx')
|
|
wrapper.parse_params.add_parameter('O!', ['&PyList_Type', '&'+py_name], self.name)
|
|
|
|
#wrapper.before_call.write_error_check('!PyList_Check(%s)' % py_name) # XXX
|
|
|
|
wrapper.before_call.write_code("%s = (char **) malloc(sizeof(char*)*PyList_Size(%s));"
|
|
% (name, py_name))
|
|
wrapper.before_call.add_cleanup_code('free(%s);' % name)
|
|
wrapper.before_call.write_code('''
|
|
for (%(idx)s = 0; %(idx)s < PyList_Size(%(py_name)s); %(idx)s++)
|
|
{
|
|
''' % vars())
|
|
wrapper.before_call.sink.indent()
|
|
wrapper.before_call.write_code('''
|
|
PyObject *item = PyList_GET_ITEM(%(py_name)s, %(idx)s);
|
|
''' % vars())
|
|
#wrapper.before_call.write_error_check('item == NULL')
|
|
wrapper.before_call.write_error_check(
|
|
'!PyString_Check(item)',
|
|
failure_cleanup=('PyErr_SetString(PyExc_TypeError, '
|
|
'"argument %s must be a list of strings");') % self.name)
|
|
wrapper.before_call.write_code(
|
|
'%s[%s] = PyString_AsString(item);' % (name, idx))
|
|
wrapper.before_call.sink.unindent()
|
|
wrapper.before_call.write_code('}')
|
|
wrapper.before_call.write_code('%s = PyList_Size(%s);' % (argc_var, py_name))
|
|
|
|
wrapper.call_params.append(argc_var)
|
|
wrapper.call_params.append(name)
|
|
|
|
|
|
class CallbackImplProxyMethod(typehandlers.ReverseWrapperBase):
|
|
"""
|
|
Class that generates a proxy virtual method that calls a similarly named python method.
|
|
"""
|
|
|
|
def __init__(self, return_value, parameters):
|
|
super(CallbackImplProxyMethod, self).__init__(return_value, parameters)
|
|
|
|
def generate_python_call(self):
|
|
"""code to call the python method"""
|
|
build_params = self.build_params.get_parameters(force_tuple_creation=True)
|
|
if build_params[0][0] == '"':
|
|
build_params[0] = '(char *) ' + build_params[0]
|
|
args = self.before_call.declare_variable('PyObject*', 'args')
|
|
self.before_call.write_code('%s = Py_BuildValue(%s);'
|
|
% (args, ', '.join(build_params)))
|
|
self.before_call.add_cleanup_code('Py_DECREF(%s);' % args)
|
|
self.before_call.write_code('py_retval = PyObject_CallObject(m_callback, %s);' % args)
|
|
self.before_call.write_error_check('py_retval == NULL')
|
|
self.before_call.add_cleanup_code('Py_DECREF(py_retval);')
|
|
|
|
|
|
|
|
|
|
def generate_callback_classes(out, callbacks):
|
|
for callback_impl_num, template_parameters in enumerate(callbacks):
|
|
sink = MemoryCodeSink()
|
|
cls_name = "ns3::Callback< %s >" % ', '.join(template_parameters)
|
|
#print >> sys.stderr, "***** trying to register callback: %r" % cls_name
|
|
class_name = "PythonCallbackImpl%i" % callback_impl_num
|
|
sink.writeln('''
|
|
class %s : public ns3::CallbackImpl<%s>
|
|
{
|
|
public:
|
|
PyObject *m_callback;
|
|
%s(PyObject *callback)
|
|
{
|
|
Py_INCREF(callback);
|
|
m_callback = callback;
|
|
}
|
|
virtual ~%s()
|
|
{
|
|
Py_DECREF(m_callback);
|
|
m_callback = NULL;
|
|
}
|
|
|
|
virtual bool IsEqual(ns3::Ptr<const ns3::CallbackImplBase> other_base) const
|
|
{
|
|
const %s *other = dynamic_cast<const %s*> (ns3::PeekPointer (other_base));
|
|
if (other != NULL)
|
|
return (other->m_callback == m_callback);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
''' % (class_name, ', '.join(template_parameters), class_name, class_name, class_name, class_name))
|
|
sink.indent()
|
|
callback_return = template_parameters[0]
|
|
return_ctype = ctypeparser.parse_type(callback_return)
|
|
if ('const' in return_ctype.remove_modifiers()):
|
|
kwargs = {'is_const': True}
|
|
else:
|
|
kwargs = {}
|
|
try:
|
|
return_type = ReturnValue.new(str(return_ctype), **kwargs)
|
|
except (typehandlers.TypeLookupError, typehandlers.TypeConfigurationError), ex:
|
|
warnings.warn("***** Unable to register callback; Return value '%s' error (used in %s): %r"
|
|
% (callback_return, cls_name, ex),
|
|
Warning)
|
|
continue
|
|
|
|
arguments = []
|
|
ok = True
|
|
callback_parameters = [arg for arg in template_parameters[1:] if arg != 'ns3::empty']
|
|
for arg_num, arg_type in enumerate(callback_parameters):
|
|
arg_name = 'arg%i' % (arg_num+1)
|
|
|
|
param_ctype = ctypeparser.parse_type(arg_type)
|
|
if ('const' in param_ctype.remove_modifiers()):
|
|
kwargs = {'is_const': True}
|
|
else:
|
|
kwargs = {}
|
|
try:
|
|
arguments.append(Parameter.new(str(param_ctype), arg_name, **kwargs))
|
|
except (typehandlers.TypeLookupError, typehandlers.TypeConfigurationError), ex:
|
|
warnings.warn("***** Unable to register callback; parameter '%s %s' error (used in %s): %r"
|
|
% (arg_type, arg_name, cls_name, ex),
|
|
Warning)
|
|
ok = False
|
|
if not ok:
|
|
continue
|
|
|
|
wrapper = CallbackImplProxyMethod(return_type, arguments)
|
|
wrapper.generate(sink, 'operator()', decl_modifiers=[])
|
|
|
|
sink.unindent()
|
|
sink.writeln('};\n')
|
|
sink.flush_to(out)
|
|
|
|
class PythonCallbackParameter(Parameter):
|
|
"Class handlers"
|
|
CTYPES = [cls_name]
|
|
#print >> sys.stderr, "***** registering callback handler: %r" % ctypeparser.normalize_type_string(cls_name)
|
|
DIRECTIONS = [Parameter.DIRECTION_IN]
|
|
PYTHON_CALLBACK_IMPL_NAME = class_name
|
|
TEMPLATE_ARGS = template_parameters
|
|
|
|
def convert_python_to_c(self, wrapper):
|
|
"parses python args to get C++ value"
|
|
assert isinstance(wrapper, typehandlers.ForwardWrapperBase)
|
|
|
|
if self.default_value is None:
|
|
py_callback = wrapper.declarations.declare_variable('PyObject*', self.name)
|
|
wrapper.parse_params.add_parameter('O', ['&'+py_callback], self.name)
|
|
wrapper.before_call.write_error_check(
|
|
'!PyCallable_Check(%s)' % py_callback,
|
|
'PyErr_SetString(PyExc_TypeError, "parameter \'%s\' must be callbale");' % self.name)
|
|
callback_impl = wrapper.declarations.declare_variable(
|
|
'ns3::Ptr<%s>' % self.PYTHON_CALLBACK_IMPL_NAME,
|
|
'%s_cb_impl' % self.name)
|
|
wrapper.before_call.write_code("%s = ns3::Create<%s> (%s);"
|
|
% (callback_impl, self.PYTHON_CALLBACK_IMPL_NAME, py_callback))
|
|
wrapper.call_params.append(
|
|
'ns3::Callback<%s> (%s)' % (', '.join(self.TEMPLATE_ARGS), callback_impl))
|
|
else:
|
|
py_callback = wrapper.declarations.declare_variable('PyObject*', self.name, 'NULL')
|
|
wrapper.parse_params.add_parameter('O', ['&'+py_callback], self.name, optional=True)
|
|
value = wrapper.declarations.declare_variable(
|
|
'ns3::Callback<%s>' % ', '.join(self.TEMPLATE_ARGS),
|
|
self.name+'_value',
|
|
self.default_value)
|
|
|
|
wrapper.before_call.write_code("if (%s) {" % (py_callback,))
|
|
wrapper.before_call.indent()
|
|
|
|
wrapper.before_call.write_error_check(
|
|
'!PyCallable_Check(%s)' % py_callback,
|
|
'PyErr_SetString(PyExc_TypeError, "parameter \'%s\' must be callbale");' % self.name)
|
|
|
|
wrapper.before_call.write_code("%s = ns3::Callback<%s> (ns3::Create<%s> (%s));"
|
|
% (value, ', '.join(self.TEMPLATE_ARGS),
|
|
self.PYTHON_CALLBACK_IMPL_NAME, py_callback))
|
|
|
|
wrapper.before_call.unindent()
|
|
wrapper.before_call.write_code("}") # closes: if (py_callback) {
|
|
|
|
wrapper.call_params.append(value)
|
|
|
|
|
|
def convert_c_to_python(self, wrapper):
|
|
raise typehandlers.NotSupportedError("Reverse wrappers for ns3::Callback<...> types "
|
|
"(python using callbacks defined in C++) not implemented.")
|
|
|
|
|
|
# def write_preamble(out):
|
|
# pybindgen.write_preamble(out)
|
|
# out.writeln("#include \"ns3/everything.h\"")
|
|
|
|
|
|
|
|
def Simulator_customizations(module):
|
|
Simulator = module['ns3::Simulator']
|
|
|
|
## Simulator::Schedule(delay, callback, ...user..args...)
|
|
Simulator.add_custom_method_wrapper("Schedule", "_wrap_Simulator_Schedule",
|
|
flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
|
|
|
|
|
|
## Simulator::ScheduleNow(callback, ...user..args...)
|
|
Simulator.add_custom_method_wrapper("ScheduleNow", "_wrap_Simulator_ScheduleNow",
|
|
flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
|
|
|
|
|
|
## Simulator::ScheduleDestroy(callback, ...user..args...)
|
|
Simulator.add_custom_method_wrapper("ScheduleDestroy", "_wrap_Simulator_ScheduleDestroy",
|
|
flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
|
|
|
|
Simulator.add_custom_method_wrapper("Run", "_wrap_Simulator_Run",
|
|
flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
|
|
|
|
|
|
def CommandLine_customizations(module):
|
|
CommandLine = module['ns3::CommandLine']
|
|
CommandLine.add_method('Parse', None, [ArgvParam(None, 'argv')],
|
|
is_static=False)
|
|
CommandLine.add_custom_method_wrapper("AddValue", "_wrap_CommandLine_AddValue",
|
|
flags=["METH_VARARGS", "METH_KEYWORDS"])
|
|
|
|
|
|
def Object_customizations(module):
|
|
## ---------------------------------------------------------------------
|
|
## Here we generate custom constructor code for all classes that
|
|
## derive from ns3::Object. The custom constructors are needed in
|
|
## order to support kwargs only and to translate kwargs into ns3
|
|
## attributes, etc.
|
|
## ---------------------------------------------------------------------
|
|
Object = module['ns3::Object']
|
|
|
|
|
|
## add a GetTypeId method to all generatd helper classes
|
|
def helper_class_hook(helper_class):
|
|
decl = """
|
|
static ns3::TypeId GetTypeId (void)
|
|
{
|
|
static ns3::TypeId tid = ns3::TypeId ("%s")
|
|
.SetParent< %s > ()
|
|
;
|
|
return tid;
|
|
}""" % (helper_class.name, helper_class.class_.full_name)
|
|
|
|
helper_class.add_custom_method(decl)
|
|
helper_class.add_post_generation_code(
|
|
"NS_OBJECT_ENSURE_REGISTERED (%s);" % helper_class.name)
|
|
Object.add_helper_class_hook(helper_class_hook)
|
|
|
|
def ns3_object_instance_creation_function(cpp_class, code_block, lvalue,
|
|
parameters, construct_type_name):
|
|
assert lvalue
|
|
assert not lvalue.startswith('None')
|
|
if cpp_class.cannot_be_constructed:
|
|
raise CodeGenerationError("%s cannot be constructed (%s)"
|
|
% cpp_class.full_name)
|
|
if cpp_class.incomplete_type:
|
|
raise CodeGenerationError("%s cannot be constructed (incomplete type)"
|
|
% cpp_class.full_name)
|
|
code_block.write_code("%s = new %s(%s);" % (lvalue, construct_type_name, parameters))
|
|
code_block.write_code("%s->Ref ();" % (lvalue))
|
|
code_block.write_code("ns3::CompleteConstruct(%s);" % (lvalue, ))
|
|
|
|
Object.set_instance_creation_function(ns3_object_instance_creation_function)
|
|
|
|
|
|
def Attribute_customizations(module):
|
|
# Fix up for the "const AttributeValue &v = EmptyAttribute()"
|
|
# case, as used extensively by helper classes.
|
|
|
|
# Here's why we need to do this: pybindgen.gccxmlscanner, when
|
|
# scanning parameter default values, is only provided with the
|
|
# value as a simple C expression string. (py)gccxml does not
|
|
# report the type of the default value.
|
|
|
|
# As a workaround, here we iterate over all parameters of all
|
|
# methods of all classes and tell pybindgen what is the type of
|
|
# the default value for attributes.
|
|
|
|
for cls in module.classes:
|
|
for meth in cls.get_all_methods():
|
|
for param in meth.parameters:
|
|
if isinstance(param, cppclass.CppClassRefParameter):
|
|
if param.cpp_class.name == 'AttributeValue' \
|
|
and param.default_value is not None \
|
|
and param.default_value_type is None:
|
|
param.default_value_type = 'ns3::EmptyAttributeValue'
|
|
|
|
|
|
def TypeId_customizations(module):
|
|
TypeId = module['ns3::TypeId']
|
|
TypeId.add_custom_method_wrapper("LookupByNameFailSafe", "_wrap_TypeId_LookupByNameFailSafe",
|
|
flags=["METH_VARARGS", "METH_KEYWORDS", "METH_STATIC"])
|
|
|
|
|
|
def add_std_ofstream(module):
|
|
module.add_include('<fstream>')
|
|
ostream = module.add_class('ostream', foreign_cpp_namespace='::std')
|
|
ostream.set_cannot_be_constructed("abstract base class")
|
|
ofstream = module.add_class('ofstream', foreign_cpp_namespace='::std', parent=ostream)
|
|
ofstream.add_enum('openmode', [
|
|
('app', 'std::ios_base::app'),
|
|
('ate', 'std::ios_base::ate'),
|
|
('binary', 'std::ios_base::binary'),
|
|
('in', 'std::ios_base::in'),
|
|
('out', 'std::ios_base::out'),
|
|
('trunc', 'std::ios_base::trunc'),
|
|
])
|
|
ofstream.add_constructor([Parameter.new("const char *", 'filename'),
|
|
Parameter.new("::std::ofstream::openmode", 'mode', default_value="std::ios_base::out")])
|
|
ofstream.add_method('close', None, [])
|
|
|
|
def add_ipv4_address_tp_hash(module):
|
|
module.body.writeln('''
|
|
long
|
|
_ns3_Ipv4Address_tp_hash (PyObject *obj)
|
|
{
|
|
PyNs3Ipv4Address *addr = reinterpret_cast<PyNs3Ipv4Address *> (obj);
|
|
return static_cast<long> (ns3::Ipv4AddressHash () (*addr->obj));
|
|
}
|
|
''')
|
|
module.header.writeln('long _ns3_Ipv4Address_tp_hash (PyObject *obj);')
|
|
module['Ipv4Address'].pytype.slots['tp_hash'] = "_ns3_Ipv4Address_tp_hash"
|