On 10/20/2010 7:49 AM, Thomas Heller wrote:
> I worked a little on trying to simplify that whole grammar with 
> rules that have thing a bit. Forgive me, but i changed the name to Visitor. 
> Why? Simply because i think this is what is done here. We visit a specific 
> node which happened to match our rule.

Most tree traversal algorithms "visit" each node, but the term "Visitor"
means something very, very specific: the Gang-of-Four Visitor Design
Pattern. It implies an OO hierarchy, a virtual "Dispatch" member that
accepts a visitor, and a 2nd dispatch to an "Accept" member of the
visitor object. It is used to "add" members to an OO hierarchy post-hoc
by putting them in a visitor instead of having to change every type in
your hierarchy.

If you don't have something like that, then don't call it Visitor. If
you do, then we need to reformulate this design to make that pattern
more explicit. Otherwise, it will cause no end of confusion.

Historical note: I originally called the "data" parameter of Proto
transforms "visitors". During the review, there was such a hue and cry
(for good reason) that I had to change it. True story.

> Here it goes:
>     namespace detail
>     {
>         template <
>             typename Grammar, typename Visitor, typename IsRule = void>
>         struct algorithm_case
>             : Grammar
>         {};

Why inherit from Grammar here instead of:

  : proto::when<
        Grammar
      , typename Visitor::template visit<Grammar>
    >

?

>         template <
>             typename Rule, typename Visitor, int RulesSize = Rule::size>
>         struct algorithm_case_rule;
> 
>         template <typename Rule, typename Visitor>
>         struct algorithm_case_rule<Rule, Visitor, 1>
>             : proto::when<
>                   typename Rule::rule0
>                 , typename Visitor::template visit<typename Rule::rule0>
>               >
>         {};
> 
>         // add more ...
> 
>         template <typename Grammar, typename Visitor>
>         struct algorithm_case<Grammar, Visitor, typename Grammar::is_rule>
>             : algorithm_case_rule<Grammar, Visitor>
>         {};
> 
>         // algorithm is what 
>         template <typename Cases, typename Visitor>
>         struct algorithm
>             : proto::switch_<algorithm<Cases, Visitor> >
>         {
>             template <typename Tag>
>             struct case_
>                 : algorithm_case<
>                       typename Cases::template case_<Tag>
>                     , Visitor
>                   >
>             {};
>         };

Well that's pretty clever. I like the CRTP with switch_ and sticking the
cases right there. I almost looks like a regular switch statement. Why
didn't I think of that?


>         template <typename Grammar>
>         struct rule
>         {
>             typedef void is_rule;
> 
>             static int const size = 1;
>             typedef Grammar rule0;
>         };
> 
>         template <
>             typename Grammar0 = void
>           , typename Grammar1 = void
>           , typename Grammar2 = void
>           , typename Dummy = void>
>         struct rules;
> 
>         template <typename Grammar>
>         struct rules<Grammar>
>         {
>             typedef void is_rule;
> 
>             static int const size = 1;
>             typedef Grammar rule0;
>         };
> 
>          // add more ... 
>     }
> 
> Making your example:
> 
> // A collection of actions indexable by rules.
> struct MyActions
> {
>     template<typename Rule>
>     struct action;
> };
> 
> // An easier way to dispatch to a tag-specific sub-grammar
> template<typename Tag, typename Actions>
> struct MyCases
>   : proto::not< proto::_ >
> {};
> 
> struct MyCasesImpl
> {
>     template<typename Tag>
>     struct case_
>       : MyCases<Tag, Actions>
>     {};
> };
> 
> // Define an openly extensible grammar using switch_
> template<typename Actions = MyActions>
> struct MyGrammar
>   : detail::algorithm< MyCasesImpl, Actions>
> {};
> 
> // Define a grammar rule for int terminals
> struct IntTerminalRule
>   : proto::terminal<int>
> {};
> 
> // Define a grammar rule for char terminals
> struct CharTerminalRule
>   : proto::terminal<char>
> {};
> 
> // OK, handle the terminals we allow:
> template<>
> struct MyCases<proto::tag::terminal>
>   : proto::rules<
>         IntTerminalRule
>       , CharTerminalRule
>     >
> {};
> 
> // Now, populate the MyActions metafunction class
> // with the default actions:
> 
> template<>
> struct MyActions::action< IntTerminalRule >
>   : DoIntAction
> {};
> 
> template<>
> struct MyActions::action< CharTerminalRule >
>   : DoCharAction
> {};


It took a while, but I see what you're up to. You've pushed complexity
into the generic "algorithm" class. (Not the greatest name, but I can't
do better at the moment.) The benefit here is the cleaner separation
between rules and actions, and the nicer syntax for specifying the rules
associated with a tag. E.g.:

  rules<A,B,C>

where A, B, and C are simple Proto rules without semantic actions,
instead of:

  proto::or_<
      rules_with_actions<A, Actions>
    , rules_with_actions<B, Actions>
    , rules_with_actions<C, Actions>
  >

... which conflates grammar with actions in an unpleasant way. That's a
significant improvement. I ported my mini-Phoenix to use this, and I
like it. (See attached.)

Now the outstanding question is: does this control flow really mirror
the Visitor design pattern, and if so can we jigger this to more closely
match that pattern? If so, we could make this easier to use and understand.

-- 
Eric Niebler
BoostPro Computing
http://www.boostpro.com
#include <boost/proto/proto.hpp>
#include <boost/preprocessor/arithmetic/inc.hpp>
#include <boost/preprocessor/repetition/enum.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_params_with_a_default.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <iostream>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
namespace fusion = boost::fusion;
using proto::_;

#ifdef _MSC_VER
#pragma warning(disable: 4355)
#endif

// Implementation details for generic extensible Proto algorithms. This is not
// specific to Phoenix at all and can move into Proto.
namespace detail
{
    template<typename Rules, typename Actions, int RuleSize = Rules::count>
    struct algorithm_case_impl;

#define M0(Z, N, DATA)                                                          
                    \
        proto::when<                                                            
                    \
            typename Rules::BOOST_PP_CAT(rule, N)                               
                    \
          , typename Actions::template action<typename 
Rules::BOOST_PP_CAT(rule, N), Actions>       \
        >                                                                       
                    \
        /**/

#define M1(Z, N, DATA)                                                          
                    \
    template<typename Rules, typename Actions>                                  
                    \
    struct algorithm_case_impl<Rules, Actions, N>                               
                    \
      : proto::or_<BOOST_PP_ENUM_ ## Z(N, M0, ~) >                              
                    \
    {};                                                                         
                    \
    /**/

    BOOST_PP_REPEAT(BOOST_PROTO_MAX_LOGICAL_ARITY, M1, ~)
    #undef M1
    #undef M0

    template<typename Grammar, typename Actions, typename IsRule = void>
    struct algorithm_case
      : proto::when<
            Grammar
          , typename Actions::template action<Grammar, Actions>
        >
    {};

    template<typename Grammar, typename Actions>
    struct algorithm_case<Grammar, Actions, typename Grammar::is_rule>
      : algorithm_case_impl<Grammar, Actions>
    {};
}

// A collection of grammar rules. Really, we could just use proto::or_ for this 
purpose.
template<BOOST_PP_ENUM_PARAMS_WITH_A_DEFAULT(BOOST_PROTO_MAX_LOGICAL_ARITY, 
typename Rule, void), typename Dummy = void>
struct rules;

#define M0(Z, N, DATA)                                                          
                    \
    typedef BOOST_PP_CAT(Rule, N) BOOST_PP_CAT(rule, N);                        
                    \
    /**/

#define M1(Z, N, DATA)                                                          
                    \
    template<BOOST_PP_ENUM_PARAMS_Z(Z, N, typename Rule)>                       
                    \
    struct rules<BOOST_PP_ENUM_PARAMS_Z(Z, N, Rule)>                            
                    \
    {                                                                           
                    \
        typedef void is_rule;                                                   
                    \
        static int const count = N;                                             
                    \
        BOOST_PP_REPEAT_ ## Z(N, M0, ~)                                         
                    \
    };                                                                          
                    \
    /**/

BOOST_PP_REPEAT(BOOST_PROTO_MAX_LOGICAL_ARITY, M1, ~)
#undef M1
#undef M0

// By default, just use the actions that have already been
// associated with the grammar rules. Could be in Proto.
struct default_actions
{
    template<typename Rule, typename Actions>
    struct action
      : Grammar
    {};
};

// Generic implementation of an extensible Proto algorithm, where the grammar
// and transforms are openly extensible, and the actions can be specified
// separate from the grammar rules, and are looked up by the grammar rules.
// This could live in Proto.
template<typename Cases, typename Actions = default_actions>
struct algorithm
  : proto::switch_<algorithm<Cases, Actions> >
{
    template<typename Tag>
    struct case_
      : detail::algorithm_case<
            typename Cases::template case_<Tag>
          , Actions
        >
    {};
};

// A collection of semantic actions, indexed by phoenix grammar rules
struct PhoenixDefaultActions
{
    template<typename Rule, typename Actions = PhoenixDefaultActions>
    struct action;
};

// The phoenix grammar and evaluator algorithm, parameterized
// by a collection of semantic actions.
template<typename Actions = PhoenixDefaultActions>
struct PhoenixEval;

// This is the grammar of Phoenix expressions. It is
// independent of the semantic actions.
typedef PhoenixEval<> PhoenixGrammar;

// The Phoenix "default" rule, which handles most operands by
// delegating to proto::_default.
struct PhoenixRuleDefault
  : proto::_default< PhoenixGrammar >
{};

// Add to the collection of semantic actions the thing to
// do in the "default" case.
template<typename Actions>
struct PhoenixDefaultActions::action<PhoenixRuleDefault, Actions>
  : proto::_default< PhoenixEval<Actions> >
{};

// An openly extensible collection of phoenix rules and actions.
// "Tag" here is an expression tag.
struct PhoenixGrammarCases
{
    template<typename Tag>
    struct case_
      : rules< PhoenixRuleDefault >
    {};
};

// The phoenix grammar and evaluator is just a
// collection of phoenix rules, parameterized by
// the actions to take for each rule.
template<typename Actions>
struct PhoenixEval
  : algorithm< PhoenixGrammarCases, Actions >
{};

// Define placeholders
template<typename I>
struct placeholder
{
    typedef I type;
    typedef typename I::value_type value_type;
    static value_type const value = I::value;
};

// Here is the arg1 placeholder
proto::terminal<placeholder<mpl::int_<0> > >::type const arg1 = {};

// Simple TR1-style function object for indexing a Fusion sequence
struct at : proto::callable
{
    template <typename Sig>
    struct result;

    template <typename This, typename N, typename Env>
    struct result<This(N, Env)>
      : fusion::result_of::at_c<
            typename boost::remove_reference<Env>::type
          , boost::remove_reference<N>::type::value
        >
    {};

    template<typename N, typename Env>
    typename fusion::result_of::at_c<Env, N::value>::type
    operator()(N, Env &env) const
    {
        return fusion::at_c<N::value>(env);
    }
};

// The phoenix rule for matching placeholder
// (Can later be generalized with boost::is_placeholer)
struct PhoenixRulePlaceholder
  : proto::terminal<placeholder<_> >
{};

// The action to take for placeholders
template<typename Actions>
struct PhoenixDefaultActions::action<PhoenixRulePlaceholder, Actions>
  : proto::call<at(proto::_value, proto::_state)>
{};

// Customization point for special terminal handling
template<typename T>
struct is_custom_terminal
  : mpl::false_
{};

// Customization point for special terminal handling
template<typename T>
struct get_custom_terminal;

// Phoenix rule for matching custom terminals
struct PhoenixRuleCustomTerminal
  : proto::if_<is_custom_terminal<proto::_value>()>
{};

// Phoenix action to take for custom terminals
template<typename Actions>
struct PhoenixDefaultActions::action<PhoenixRuleCustomTerminal, Actions>
  : proto::lazy<get_custom_terminal<proto::_value>(proto::_value)>
{};

// Rules for terminals
template<>
struct PhoenixGrammarCases::case_<proto::tag::terminal>
  : rules<
        PhoenixRulePlaceholder
      , PhoenixRuleCustomTerminal
      , PhoenixRuleDefault
    >
{};

// Create a phoenix terminal, stored by value
template<typename T>
typename proto::terminal<T>::type val(T t)
{
    return proto::terminal<T>::type::make(t);
}

// Create a phoenix terminal, stored by reference
template<typename T>
typename proto::terminal<boost::reference_wrapper<T> >::type ref(T &t)
{
    return proto::terminal<boost::reference_wrapper<T> 
>::type::make(boost::ref(t));
}

// Create a phoenix terminal, stored by const reference
template<typename T>
typename proto::terminal<boost::reference_wrapper<T const> >::type cref(T const 
&t)
{
    return proto::terminal<boost::reference_wrapper<T const> 
>::type::make(boost::cref(t));
}

// Call out boost::reference_wrapper for special handling
template<typename T>
struct is_custom_terminal<boost::reference_wrapper<T> >
  : mpl::true_
{};

// Special handling for boost::reference_wrapper
template<typename T>
struct get_custom_terminal<boost::reference_wrapper<T> >
{
    typedef T &result_type;

    T &operator()(boost::reference_wrapper<T> r) const
    {
        return r;
    }
};

//-----
// Begin if_/then_/else_ implementation
//-----

// Define some tags for additional statements
namespace tag
{
    struct if_then {};
    struct if_then_else {};
}

template<typename Cond, typename Then, typename Else>
struct if_then_else
  : proto::nary_expr<tag::if_then_else, Cond, Then, Else>
{};

template<typename Cond, typename Then>
struct else_gen
{
    else_gen(Cond const& cond, Then const& then)
      : cond(cond)
      , then(then)
    {}

    template <typename Else>
    typename if_then_else<Cond, Then, Else>::type const
    operator[](Else const &else_) const
    {
        return if_then_else<Cond, Then, Else>::type::make(cond, then, else_);
    }

    Cond const &cond;
    Then const &then;
};

template<typename Expr>
struct if_expr
  : Expr
{
    if_expr(Expr const& expr)
      : Expr(expr)
      , else_(proto::child_c<0>(*this), proto::child_c<1>(*this))
    {}

    typedef typename proto::result_of::child_c<Expr, 0>::type cond_type;
    typedef typename proto::result_of::child_c<Expr, 1>::type then_type;
    else_gen<cond_type, then_type> else_;
};

template<typename Cond, typename Then>
struct if_then
  : proto::binary_expr<tag::if_then, Cond, Then>
{};

template<typename Cond>
struct if_gen
{
    explicit if_gen(Cond const& cond)
      : cond(cond)
    {}

    template<typename Then>
    if_expr<typename if_then<Cond const &, Then const &>::type> const
    operator[](Then const &then) const
    {
        return if_then<Cond const &, Then const &>::type::make(cond, then);
    }

    Cond const &cond;
};

template <typename Cond>
if_gen<Cond> const if_(Cond const &cond)
{
    return if_gen<Cond>(cond);
}

// Callable for evaluating if_/then_ statements
template<typename Actions>
struct if_then_eval : proto::callable
{
    typedef void result_type;

    template <typename Cond, typename Then, typename Env>
    result_type operator()(Cond const& cond, Then const& then, Env& env) const
    {
        if(PhoenixEval<Actions>()(cond, env))
        {
            PhoenixEval<Actions>()(then, env);
        }
    }
};

// Phoenix rule for matching if_/then_ statements
struct PhoenixRuleIfThen
  : if_then<PhoenixGrammar, PhoenixGrammar>
{};

// Phoenix action to take for if_/then_ statements
template<typename Actions>
struct PhoenixDefaultActions::action<PhoenixRuleIfThen, Actions>
  : proto::call<if_then_eval<Actions>(proto::_child0, proto::_child1, 
proto::_state)>
{};

// Bind actions to rules for if_/then_ statements
template<>
struct PhoenixGrammarCases::case_<tag::if_then>
  : rules< PhoenixRuleIfThen >
{};

// Callable for evaluating if_/then_/else_ statements
template<typename Actions>
struct if_then_else_eval : proto::callable
{
    typedef void result_type;

    template <typename Cond, typename Then, typename Else, typename Env>
    result_type operator()(Cond const& cond, Then const& then, Else const& 
else_, Env& env) const
    {
        if(PhoenixEval<Actions>()(cond, env))
        {
            PhoenixEval<Actions>()(then, env);
        }
        else
        {
            PhoenixEval<Actions>()(else_, env);
        }
    }
};

// Phoenix rule for matching custom terminals
struct PhoenixRuleIfThenElse
  : if_then_else<PhoenixGrammar, PhoenixGrammar, PhoenixGrammar>
{};

// Phoenix action to take for custom terminals
template<typename Actions>
struct PhoenixDefaultActions::action<PhoenixRuleIfThenElse, Actions>
  : proto::call<if_then_else_eval<Actions>(proto::_child0, proto::_child1, 
proto::_child2, proto::_state)>
{};

// Bind actions to rules for terminals
template<>
struct PhoenixGrammarCases::case_<tag::if_then_else >
  : rules< PhoenixRuleIfThenElse >
{};

//-----
// End if_/then_/else_ implementation
//-----

// With this set of semantic actions, we can easily force
// arg1 to always evaluate to int(0). Nice little example
// of how the Phoenix expression evaluator can be customized.
struct MyActions
{
    template<typename Rule, typename Actions = MyActions>
    struct action
      : PhoenixDefaultActions::action<Rule, MyActions>
    {};

    template<typename Actions>
    struct action<PhoenixRulePlaceholder, Actions>
      : proto::make<int()>
    {};
};

int main()
{
    PhoenixEval<> phoenix_eval;
    fusion::vector1<int> env(42);
    proto::literal<int> i(1), j(2);

    int k = phoenix_eval(i + j);
    std::cout << k << std::endl; // should be 3

    k = phoenix_eval(i + arg1, env);
    std::cout << k << std::endl; // should be 43

    k = phoenix_eval(cref(7), env);
    std::cout << k << std::endl; // should be 7

    phoenix_eval( if_(i == 1)[ ++i ], env );
    std::cout << i.get() << std::endl; // should be 2

    phoenix_eval( if_(i == 1)[ ++i ].else_[ --i ], env );
    std::cout << i.get() << std::endl; // should be 1

    // Test for custom semantic actions
    PhoenixEval<MyActions> my_eval;
    k = my_eval( arg1 + 7, env );
    std::cout << k << std::endl; // should be 7
}
_______________________________________________
proto mailing list
proto@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/proto

Reply via email to