DSL challenge¶
Introduction¶
More or less formal definition of DSL could be found here.
Py++ has been created to solve single and well-defined problem: to create Python bindings for C++ projects. The good news - Py++ achieved the goal, the bad news - users are forced to read the documentation. DSL cannot completely solve the problem, but it can eliminate the need to read documentation in 80% of the cases.
Py++ user interface¶
I will use the following C++ code as an example:
namespace geometry{
struct Point{
Point();
Point(int x, int y);
Point( const Point& );
Point* create_new(){ return *this; }
int x, y;
int private_data;
};
}
In order to export this class, we need:
- to set “call policies” to
create_new
member function - to exclude
private_data
member variable - to rename
x
andy
toX
andY
Today, in order to configure this class, the user has to write the following code:
mb = module_builder_t( ... )
Point = mb.class_( 'Point' )
Point.member_function( 'create_new' ).call_policies = ...
Point.member_variable( 'private_data' ).exclude()
Point.member_variable( 'x' ).rename( 'X' )
Point.member_variable( 'Y' ).rename( 'Y' )
#or
for mvar in Point.member_variables():
mvar.rename( mvar.name.upper() )
If class Point
is not unique, than user will have to write a little bit
different code:
Point = mb.global_ns.namespace('geometry').class( 'Point' )
The current approach is pretty readable and simple. The drawbacks of this approach are:
- before the user starts with Py++ he is forced to read a lot of documentation
- verbosity - in order to complete the task, the user have to write “a lot” of code
Better API (B-API)¶
mb = module_builder_t( ... )
Point = mb.module.geometry.Point
Point.create_new.call_policies = ...
Point.private_data.exclude()
Point.x.rename( 'X' )
Point.y.rename( 'Y' )
What you see here is DSL!
Comparison¶
I don’t argue, that the second way is better. I would like to expose you to few problems it has.
Rule based approach¶
B-API does not allow one to use “rule based” approach and to work on the whole declarations tree!
Special syntax¶
Special syntax should be introduce to support
template instantiations
B-API does not work for template instantiated classes and functions. If we change class
Point
to be template, the special syntax should be introduced:template < class Numeric > struct Point{ ... };
PointTmpl = mb.module.template('Point') Point = PointTmpl( 'int' )
This is a trivial example, which is why it looks great. Consider the following class:
template< class String, class Allocator > class regex{ ... }
The code the user will need to write is:
regex_tmpl = mb.module.geometry.template( 'regex' ) #white spaces and scope resolution( :: ) are important regex_std_string = regex_tmpl( '::std::basic_string<char,std::char_traits<char>,std::allocator<char> >' , '::std::allocator<char>' )
Using current Py++ interface the user can get reference to the class instantiation in one line of code:
regex_std_string = mb.class_( lambda decl: decl.name.startswith( 'regex' ) and 'wchar_t' not in decl.name )
overloaded functions resolution
There are use cases, when overloaded functions should be treated differently. It is not possible to distinguish between different functions, using B-API syntax.
C++ operators
They also require special syntax.
Readability counts¶
It is not clear from the script, on how many and on what declarations configuration is applied. It is possible to introduce a bug. Using current Py++ API the user always states, whether he expects a declaration to be unique or not and its type.
Full name¶
Using B-API the user is forced to write full declaration name, otherwise he faces the following problem:
Point = mb.module.Point
Lets analyze what the Point
value:
It could be reference to a declaration, that has name “Point” and it is defined under global namespace.
It could be a set of declarations that has “Point” as a name from all classes and namespaces. In our case it will contain at lease reference to class “Point” declaration and its constructors.
There are a lot of use cases, when the user has to add some code to the class:
Point.add_registration_code( ... )
Constructor declaration does not define
add_registration_code
method. According to Python rules: “Errors should never pass silently”, exception should be raised.
Another Python rule says: “In the face of ambiguity, refuse the temptation to guess”.
Action item¶
I think, it should be obvious to you, that we cannot drop current Py++ user interface. The only solution I see, is to build B-API on top of it. The reason the project does not have B-API is simple. I don’t feel comfortable to introduce it, while I am aware to all these problems.
The title of this section should be Your action item :-). I will be glad to implement B-API, if we can solve all the problems. Consider to contribute your experience and knowledge to fix the situation. I am sure together we will build very powerful and easy to use code generator.