On 10/13/2010 11:08 PM, Thomas Heller wrote:
> Let me try again ...
> I tried to solve the problem that when writing a proto grammar+transform you 
> might want to dispatch your transform based on tags. 

You mean expression tags, right? First time I read this, I accepted this
statement as fact. Now I wonder.

*Why* do you want to dispatch your transforms based on tags? You're
thinking of proto::switch_, which dispatches grammars based on
expression tags, right? But it doesn't follow that transforms are best
dispatched on expression tags, too. We are close to the bare
requirements, but we're not there yet. In my understanding, the
requirements are as I stated before (and which you agreed with):

A) Have an openly extensible grammar.
B) Have an equally extensible set of transforms.
C) Be able to substitute out a whole other (extensible) set of transforms.

I have an alternate solution that meets these requirements and doesn't
dispatch to transforms based on expression tags. It dispatches to
transforms using grammar rules. This has a number of nice properties.
Keep reading...

> Additionally you would 
> like to replace that particular transform which evaluates your expression by 
> another one.
> proto::switch_ allows you to dispatch grammar + transform based on a specific 
> tag already, I think we agree that .
> The major usecase i had in mind for this was phoenix.
> First use case is, when defining some generic grammar like this:
> 
> struct phoenix_cases
> {
>       template <typename Tag>
>       struct case_
>            : proto::otherwise<evaluate<Tag>(proto::_, proto::_state)>
> /*call the phoenix evaluator, how? My proposed solution is through tag 
> dispatching! */
>       {};
> };
> 
> struct phoenix_grammar
>    : switch_<phoenix_cases>
> {};
> 
> So far so good. I hope we agree that this might be a good way to define the 
> phoenix grammar. 

My solution works differently. Which one is better is still an open
question.

> The need to further define how a valid phoenix grammar can 
> look like is through specialising phoenix_cases::case_ (*).
> Ok, I think this is a good solution already.
> 
> But now, think about, that people might want to evaluate a phoenix 
> expression differently!
> One example to this is that i want to generate a valid C++ string out of my 
> phoenix expression, save it for later and compile it again with my native 
> C++ compiler. I might even want to be able to evaluate a phoenix expression 
> to openCL/cuda/glsl/hlsl whatever. Another thing i might want to add to the 
> evaluation process is autoparallelization. I hope you agree that these might 
> be valid usecases!
> 
> With the definition of the phoenix grammar above, this is not easily 
> possible! I would have to repeat the phoenix grammar all over again (maybe 
> this is the point where i am totally of the line).

I'm with you here.

> The next, logical (IMHO) step is to parameterize our phoenix_grammar to 
> allow an arbitrary transform to be dispatched on the tag basis (Again, there 
> might be a different solution, I can't see any though).

Dispatch to transforms on grammar rules.

> Note: This is the step where the name "Visitor" came in, because it is what 
> it does: it visits an expression and transforms it, somehow.
> 
> So, the design will change to this:
> 
> template <template <typename> Visitor>
> struct phoenix_cases
> {
>       template <typename Tag>
>       struct case_
>            : proto::otherwise<Visitor<Tag>(proto::_, proto::_state)>
> /*call the phoenix evaluator, how? My proposed solution is through tag 
> dispatching! */
>       {};
> };
> 
> template <template <typename> Visitor>
> struct phoenix_grammar
>    : switch_<phoenix_cases<Visitor>
> {};
> 
> // bring our default evaluator back into the game:
> typedef phoenix_grammar<evaluate> evaluator;
> 
> I hope this still makes sense.

I have followed your logic this far, although our solutions have diverged.

> The next step involved the "problem" of defining what valid phoenix 
> expressions look like (We discussed this in (*)). So for the sake of 
> simplicity, why not parameterize our phoenix_grammar on a Grammar as well, 
> so narrowing down the validity of phoenix expressions becomes the act of 
> specializing one simple struct, instead of this phoenix_case beast.

Here's where you lose me. I didn't know there was a problem with
defining what a valid phoenix expression looks like. Proto::switch_
seems to be good enough. To allow the phoenix grammar to be openly
extensible.

> So, the whole thing becomes:
> 
> template <template <typename> Visitor, template <typename> Grammar>
> struct phoenix_cases
> {
>       template <typename Tag>
>       struct case_
>            : proto::when<Grammar<Tag>, Visitor<Tag>(proto::_, 
> proto::_state)>
> /*call the phoenix evaluator, how? My proposed solution is through tag 
> dispatching! */
>       {};
> };
> 
> template <template <typename> Visitor, template <typename> Grammar>
> struct phoenix_grammar
>    : switch_<phoenix_cases<Visitor, Grammar>
> {};
> 
> // bring our default evaluator back into the game:
> typedef phoenix_grammar<evaluate, grammar> evaluator;

So, you're allowing both the grammar and the semantic actions to float,
and they both depend on expression tags. I get it. I see problems with
this design (see below), but the largest is that the phoenix_grammar is
now so flexible as to be meaningless. Anybody can change anything. The
very simple question of "what is a valid phoenix expression?" now
becomes *very* difficult to wrap your head around. But there's a more
practical consideration that I'll discuss later.

> So, Visitor is now some transform that gets called with the expression and 
> state. However, what if someone wants to have data as well or doesn't care 
> about the current expression. No problem, let Visitor<Tag> be just like any 
> other regular transform!
> So the final design evolved to this:
> 
> template <template <typename> Visitor, template <typename> Grammar>
> struct phoenix_cases
> {
>       template <typename Tag>
>       struct case_
>            : proto::when<Grammar<Tag>, Visitor<Tag> >
> /*call the phoenix evaluator, how? My proposed solution is through tag 
> dispatching! */
>       {};
> };
> 
> template <template <typename> Visitor, template <typename> Grammar>
> struct phoenix_grammar
>    : switch_<phoenix_cases<Visitor, Grammar>
> {};
> 
> template <typename Tag>
> struct evaluate : proto::_default {};
> 
> template <typename Tag>
> struct grammar : proto::_ {};
> 
> // bring our default evaluator back into the game:
> typedef phoenix_grammar<evaluate, grammar> evaluator;
> 
> This is where Joel Falcou came in and said: "Hey this is a nice abstraction 
> of the stuff i already had done!" (paraphrasing here)
> 
> Thus I generalized what is now phoenix_grammar and called it proto::visitor. 
> And proposed it to this very list.

I understand now. Thanks. "Visitor" is really a generalization of
proto::switch_. Switch_ accepts one argument and treats it as a
collection of grammars that are indexed on expression tag. Visitor
accepts two arguments---a collection of grammars and another collection
of transforms---both of which are indexed on expression tags. Really,
visitor could just be a further generalization of switch_.

But I don't think that's a good idea. More below.

> I hope this makes more sense now. Please point out where I am missing 
> something, or where you can not follow the line of thought.
> 
> If this still doesn't help I am sorry to have wasted your time. I couldn't 
> think of another solution. I would be very happy to see an alternative 
> approach to solve this very problem (I like what you did with the split 
> example).

First, let me say that everything you've done is perfectly reasonable,
and I (finally!) understand what you've done and why you've done it. Whew!

Here's the fundamental problem with dispatching to transforms based on
expression tags: the tags are insufficient to pick the right transform.
Consider:

// Define an openly extensible grammar using switch_
struct MyGrammar
  : proto::switch_< struct MyCases >
{};

struct MyCases
{
    template<typename Tag>
    struct case_
      : proto::not< proto::_ >
    {};
};

// OK, handle the terminals we allow:
template<>
struct MyCases::case_< proto::tag::terminal >
  : proto::or_<
        proto::when< proto::terminal<int>,  DoIntAction >
      , proto::when< proto::terminal<char>, DoCharAction >
    >
{};

// ... define other case_ specializations ...

The important thing to note is that tag::terminal is not enough to
determine whether a particular terminal is valid for MyGrammar, so we
have to use proto::or_ to further specialize.

Also note that the int and char terminals have different semantic
actions. In this case, dispatching to a transform based on expression
tag is fundamentally too weak to pick the right one. AFAICT, there would
be no way to use your visitor to handle this situation.

What's the alternative? Rather than dispatching to transforms based on
expression tags, dispatch on grammar rules. Then document which rules in
your grammar are customization points.

The above example becomes this:

// A collection of actions indexable by rules.
struct MyActions
{
    template<typename Rule>
    struct action;
};

// A helper that finds the appropriate action by looking it
// up by rule
template<typename Rule, typename Actions>
struct MyWhen
  : proto::when< Rule, typename Actions::template action<Rule> >
{};

// An easier way to dispatch to a tag-specific sub-grammar
template<typename Tag, typename Actions>
struct MyCases
  : proto::not< proto::_ >
{};

template<typename Actions>
struct MyCasesImpl
{
    template<typename Tag>
    struct case_
      : MyCases<Tag, Actions>
    {};
};

// Define an openly extensible grammar using switch_
template<typename Actions = MyActions>
struct MyGrammar
  : proto::switch_< 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<typename Actions>
struct MyCases< proto::tag::terminal, Actions >
  : proto::or_<
        MyWhen< IntTerminalRule,  Actions >
      , MyWhen< CharTerminalRule, Actions >
    >
{};

// Now, populate the MyActions metafunction class
// with the default actions:

template<>
struct MyActions::action< IntTerminalRule >
  : DoIntAction
{};

template<>
struct MyActions::action< CharTerminalRule >
  : DoCharAction
{};


Some things to note about this solution:

- MyGrammar can be parameterized by a set of actions.

- The actions are accessed by indexing via a well-defined
  and openly extensible set of grammar rules.

- Although the grammar is extensible, it is not mutable.
  That is, new constructs can be added, but existing ones
  cannot be changed. That makes it easier to understand
  what it means for an expression to match the grammar.
  (If a different grammar is desired, a new one needs to be
  defined. It can reuse parts of MyGrammar, but MyGrammar
  itself remains unchanged. This, I think, is desirable.)

- The Actions parameter has *no* effect on what expressions
  match the MyGrammar. MyGrammar<MyActions> and MyGrammar<
  YourActions> both match exactly the same set of expressions.

- A new set of actions can be created easily by delegating
  to MyActions::action by default, and specializing only those
  rules that need custom handling.

I'm attaching the beginnings of a Phoenix implementation that is built
using this technique. It is obviously just a shell. No nice actor
wrappers or anything. This is just to demonstrate that the technique
works. It builds an extensible core, handles placeholders, terminals
(including reference_wrapped terminals), and if_/then_/else_ built as an
extension to the core.

Comments?

-- 
Eric Niebler
BoostPro Computing
http://www.boostpro.com
#include "stdafx.h"
#include <iostream>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
namespace fusion = boost::fusion;
using proto::_;

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

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

// Simple utility for binding a semantic action to a rule
template<typename Rule, typename Actions>
struct rule_with_action
  : proto::when<Rule, typename Actions::template action<Rule> >
{};

// The phoenix grammar and evaluator algorithm, parameterized
// by a collection of semantic actions.
template<typename Actions = PhoenixActions>
struct 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<>
struct PhoenixActions::action<PhoenixRuleDefault>
  : proto::_default<PhoenixGrammar<> >
{};

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

// Simple wrapper for PhoenixAlgorithmCases to make it
// suitable for use with proto::switch_
template<typename Actions>
struct PhoenixAlgorithmCasesImpl
{
    template<typename Tag>
    struct case_
      : PhoenixAlgorithmCases<Tag, Actions>
    {};
};

// 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 PhoenixGrammar
  : proto::switch_<PhoenixAlgorithmCasesImpl<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<>
struct PhoenixActions::action<PhoenixRulePlaceholder>
  : 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<>
struct PhoenixActions::action<PhoenixRuleCustomTerminal>
  : proto::lazy<get_custom_terminal<proto::_value>(proto::_value)>
{};

// Bind actions to rules for terminals
template<typename Actions>
struct PhoenixAlgorithmCases<proto::tag::terminal, Actions>
  : proto::or_<
        rule_with_action< PhoenixRulePlaceholder,    Actions >
      , rule_with_action< PhoenixRuleCustomTerminal, Actions >
      , rule_with_action< PhoenixRuleDefault,        Actions >
    >
{};

// 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(PhoenixGrammar<Actions>()(cond, env))
        {
            PhoenixGrammar<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<>
struct PhoenixActions::action<PhoenixRuleIfThen>
  : proto::call<if_then_eval<PhoenixActions>(proto::_child0, proto::_child1, 
proto::_state)>
{};

// Bind actions to rules for if_/then_ statements
template<typename Actions>
struct PhoenixAlgorithmCases<tag::if_then, Actions>
  : rule_with_action< PhoenixRuleIfThen, Actions >
{};

// 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(PhoenixGrammar<Actions>()(cond, env))
        {
            PhoenixGrammar<Actions>()(then, env);
        }
        else
        {
            PhoenixGrammar<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<>
struct PhoenixActions::action<PhoenixRuleIfThenElse>
  : proto::call<if_then_else_eval<PhoenixActions>(proto::_child0, 
proto::_child1, proto::_child2, proto::_state)>
{};

// Bind actions to rules for terminals
template<typename Actions>
struct PhoenixAlgorithmCases<tag::if_then_else, Actions>
  : rule_with_action< PhoenixRuleIfThenElse, Actions >
{};

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


int main()
{
    PhoenixGrammar<> 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;

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

    k = phoenix_eval(cref(42), env);
    std::cout << k << std::endl;

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

    phoenix_eval( if_(i == 1)[ ++i ].else_[ --i ], env );
    std::cout << i.get() << std::endl;
}
_______________________________________________
proto mailing list
proto@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/proto

Reply via email to