Hi,

I spent some time on thinking how one could make the traversal of a proto 
expression tree easier. I know want to share my findings with you all, and 
propose a new addition to proto.

My first idea was to decouple grammars from transforms, to follow the idea of 
separation of data and algorithm. Currently, a transform is only applyable 
when a certain expression is matched, this is good and wanted, though 
sometimes you get yourself into a situation that requires you to reformulate 
your grammar, and just exchange the transform part.
Let me give you an example of a comma separated list of proto expression you 
want to treat like an associative sequence:

    opt1 = 1, opt2 = 2, opt3 = 3

A proto grammar matching this expression would look something like this:

   using namespace proto;

   struct not_found {};

   template <int N>
   struct opt {};

   struct term : terminal<opt<_> > {};
   struct spec : when<assign<term, terminal<_> >, _value(_right)>
   struct pack :
       or_<
           when<
                comma<pack, spec>
              , if_<
                    matches<_left(_right), _state>()
                  , spec(_right)
                  , pack(_left)
                >
           >
         , when<
               spec
             , if_<
                    matches<_left, _state>()
                  , spec
                  , not_found()
                >
           >
       >
    {};

    proto::terminal<opt<0> >::type const opt1;
    proto::terminal<opt<1> >::type const opt2;
    proto::terminal<opt<2> >::type const opt3;

    template <typename Expr, typename Opt>
    typename boost::result_of<pack(Expr const&, Opt const&)>::type
    extract(Expr const& expr, Opt const& opt)
    {
        return pack()(expr, opt);
    }

This code works and everybody is happy, right?
Ok, what will happen if we want to calculate the number of options we 
provided in our expression?
The answer is that we most likely need to repeat almost everything from 
pack. except the transform part:

   struct size :
       or_<
           when<
                comma<pack, spec>
              , mpl::plus<size(_left), size(_right)>()
           >
         , when<
               spec
             , mpl::int_<1>()
           >
       >
    {};

Now think of it if you are having a very complicated grammar, c&p the whole 
grammar, or even just parts of it no fun.

My solution to this problem is proto::visitor (see attached):

the signature looks like the following:

    template <
        template <typename> class Visitor
      , template <typename> class Grammar
    >
    struct visitor;

So, visitor is implemented in terms of proto::switch_, What it does is the 
following: Every tag that is encountered gets dispatched to the Grammar 
template specified by the user, is that grammar matched the expression, the 
transform Visitor<Tag> is called.

Let me demonstrate this with our little example above:

   template <typename Tag>
   struct option_grammar // provide a default, don't match anything
      : proto::not_
   {};

   template <template <typename> Visitor>
   struct option_visitor
      : proto::visitor<Visitor, option_grammar>
   {};

   template <>
   struct option_grammar<proto::tag::terminal>
      : proto::terminal<proto::_>
   {};

   template <>
   struct option_grammar<proto::tag::assign>
      : proto::assign<term, proto::terminal<proto::_> >
   {};

   template <>
   struct option_grammar<proto::tag::comma>
      : proto::comma<option_grammar, spec >
   {};

With the defintions of spec and term from the above, this is the grammar  
which matches our expressions. the defintion of our two proto transforms now 
becomes the following:

   template <typename Tag> struct option_eval : proto::_ {};
   
   typedef option_visitor<option_eval> pack;

   template <>
   struct option_eval<proto::tag::terminal>
      : proto::_value
   {};

   template <>
   struct option_eval<proto::tag::assign>
       : if_<
           matches<_left, _state>()
         , pack(_right)
         , not_found()
       >
   {};

   template <>
   struct option_eval<proto::tag::comma>
      : if_<
          matches<_left(_right), _state>()
        , pack(_right(_right))
        , pack(_left)
      >
   {};

How the size calculation would look like is left as an exercise ;)

I hope the motivation and the need for what i called proto::visitor gets 
clear :)

I hear you people scream: what about compile times, when i define my DSL and 
different transforms to the proto trees, i most of the time only need a tiny 
subset of my actual grammar, won't compile times be even higher with this 
approach?
The short answer is no. It seems compile time of proto transforms is not 
really dependent on the grammar's complexity but on the complexity of the 
expressions (I did some basic tests for this, if you don't believe me, i can 
deliver it ;)).

The next thing i wanted to share is about traversals. While working with 
this visitor idea i searched for a good and easy way to specify traversals 
of proto trees. My search was soon over, because, calling transforms 
recursively is what makes proto traverse the tree. I will try to formulate 
some traversal orders in one of my next mails.

Greetings, Thomas
/*******************************************************************************
 *               Copyright 2010 Thomas Heller
 *
 *          Distributed under the Boost Software License, Version 1.0.
 *                 See accompanying file LICENSE.txt or copy at
 *                     http://www.boost.org/LICENSE_1_0.txt
 ******************************************************************************/

#ifndef BOOST_PROTO_VISITOR_HPP
#define BOOST_PROTO_VISITOR_HPP

#include <boost/proto/proto_fwd.hpp>
#include <boost/proto/matches.hpp>

namespace boost { namespace proto {

    namespace detail
    {
        template <
            template <typename> class Visitor
          , template <typename> class Grammar
        >
        struct visitor_cases
        {
            template <typename Tag>
            struct case_
                : proto::when<
                    Grammar<Tag>
                  , Visitor<Tag>
                >
            {};
        };
    }

    template <
        template <typename> class Visitor
      , template <typename> class Grammar
    >
    struct visitor
        : proto::switch_<
            detail::visitor_cases<Visitor, Grammar>
        >
    {};

    template <
        template <typename> class Visitor
      , template <typename> class Grammar
    >
    struct is_callable<visitor<Visitor, Grammar> >
        : mpl::true_
    {};

}}

#endif
_______________________________________________
proto mailing list
proto@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/proto

Reply via email to