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:

  1. to set “call policies” to create_new member function
  2. to exclude private_data member variable
  3. to rename x and y to X and Y

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:

  1. before the user starts with Py++ he is forced to read a lot of documentation
  2. 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:

  1. It could be reference to a declaration, that has name “Point” and it is defined under global namespace.

  2. 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.