![]() |
Home | Libraries | People | FAQ | More |
The Proto grammars we've seen so far are static. You can check at compile-time to see if an expression type matches a grammar, but that's it. Things get more interesting when you give them runtime behaviors. A grammar with embedded transforms is more than just a static grammar. It is a function object that accepts expressions that match the grammar and does something with them.
Below is a very simple grammar. It matches terminal expressions.
// A simple Proto grammar that matches all terminals proto::terminal< _ >
Here is the same grammar with a transform that extracts the value from the terminal:
// A simple Proto grammar that matches all terminals // *and* a function object that extracts the value from // the terminal proto::when< proto::terminal< _ > , proto::_value // <-- Look, a transform! >
You can read this as follows: when you match a terminal expression, extract
the value. The type proto::_value
is a so-called transform. Later we'll see what makes it a transform,
but for now just think of it as a kind of function object. Note the use
of proto::when<>
: the first template
parameter is the grammar to match and the second is the transform to
execute. The result is both a grammar that matches terminal expressions
and a function object that accepts terminal expressions and extracts
their values.
As with ordinary grammars, we can define an empty struct that inherits from a grammar+transform to give us an easy way to refer back to the thing we're defining, as follows:
// A grammar and a function object, as before struct Value : proto::when< proto::terminal< _ > , proto::_value > {}; // "Value" is a grammar that matches terminal expressions BOOST_MPL_ASSERT(( proto::matches< proto::terminal<int>::type, Value > )); // "Value" also defines a function object that accepts terminals // and extracts their value. proto::terminal<int>::type answer = {42}; Value get_value; int i = get_value( answer );
As already mentioned, Value
is a grammar that matches terminal expressions and a function object
that operates on terminal expressions. It would be an error to pass a
non-terminal expression to the Value
function object. This is a general property of grammars with transforms;
when using them as function objects, expressions passed to them must
match the grammar.
Proto grammars are valid TR1-style function objects. That means you can
use boost::result_of<>
to ask a grammar what its return type will be, given a particular expression
type. For instance, we can access the Value
grammar's return type as follows:
// We can use boost::result_of<> to get the return type // of a Proto grammar. typedef typename boost::result_of<Value(proto::terminal<int>::type)>::type result_type; // Check that we got the type we expected BOOST_MPL_ASSERT(( boost::is_same<result_type, int> ));
![]() |
Note |
---|---|
A grammar with embedded transforms is both a grammar and a function
object. Calling these things "grammars with transforms" would
get tedious. We could call them something like "active grammars",
but as we'll see every grammar that you can define
with Proto is "active"; that is, every grammar has some behavior
when used as a function object. So we'll continue calling these things
plain "grammars". The term "transform" is reserved
for the thing that is used as the second parameter to the |