![]() |
Home | Libraries | People | FAQ | More |
In the last section, we saw that we can pass a second parameter to grammars with transforms: an accumulation variable or state that gets updated as your transform executes. There are times when your transforms will need to access auxiliary data that does not accumulate, so bundling it with the state parameter is impractical. Instead, you can pass auxiliary data as a third parameter, known as the data parameter.
Let's modify our previous example so that it writes each terminal to
std::cout
before it puts it into a list.
This could be handy for debugging your transforms, for instance. We can
make it general by passing a std::ostream
into the transform in the data parameter. Within the transform itself,
we can retrieve the ostream
with the proto::_data
transform. The strategy is as follows: use the proto::and_<>
transform to chain two actions. The second action will create the fusion::cons<>
node as before. The first action, however, will display the current expression.
For that, we first construct an instance of proto::functional::display_expr
and then call
it.
// Fold the terminals in output statements like // "cout_ << 42 << '\n'" into a Fusion cons-list. struct FoldToList : proto::or_< // Don't add the ostream terminal to the list proto::when< proto::terminal< std::ostream & > , proto::_state > // Put all other terminals at the head of the // list that we're building in the "state" parameter , proto::when< proto::terminal<_> , proto::and_< // First, write the terminal to an ostream passed // in the data parameter proto::lazy< proto::make<proto::functional::display_expr(proto::_data)>(_) > // Then, constuct the new cons list. , fusion::cons<proto::_value, proto::_state>( proto::_value, proto::_state ) > > // For left-shift operations, first fold the right // child to a list using the current state. Use // the result as the state parameter when folding // the left child to a list. , proto::when< proto::shift_left<FoldToList, FoldToList> , FoldToList( proto::_left , FoldToList(proto::_right, proto::_state, proto::_data) , proto::_data ) > > {};
This is a lot to take in, no doubt. But focus on the second when
clause above. It says: when you
find a terminal, first display the terminal using the ostream
you find in the data parameter, then take the value of the terminal and
the current state to build a new cons
list. The function object display_expr
does the job of printing the terminal, and proto::and_<>
chains the actions together and
executes them in sequence, returning the result of the last one.
![]() |
Note |
---|---|
Also new is |
We can use the above transform as before, but now we can pass an ostream
as the third parameter and
get to watch the transform in action. Here's a sample usage:
proto::terminal<std::ostream &>::type const cout_ = {std::cout}; // This is the type of the list we build below typedef fusion::cons< int , fusion::cons< double , fusion::cons< char , fusion::nil > > > result_type; // Fold an output expression into a Fusion list, using // fusion::nil as the initial state of the transformation. // Pass std::cout as the data parameter so that we can track // the progress of the transform on the console. FoldToList to_list; result_type args = to_list(cout_ << 1 << 3.14 << '\n', fusion::nil(), std::cout); // Now "args" is the list: {1, 3.14, '\n'}
This code displays the following:
terminal( ) terminal(3.14) terminal(1)
This is a rather round-about way of demonstrating that you can pass extra data to a transform as a third parameter. There are no restrictions on what this parameter can be, and, unlike the state parameter, Proto will never mess with it.
![]() |
Note |
---|---|
This is an advanced topic. Feel free to skip if you are new to Proto. |
The example above uses the data parameter as a transport mechanism for
an unstructured blob of data; in this case, a reference to an ostream
. As your Proto algorithms become
more sophisticated, you may find that an unstructured blob of data isn't
terribly convenient to work with. Different parts of your algorithm may
be interested in different bits of data. What you want, instead, is a
way to pass in a collection of environment variables
to a transform, like a collection of key/value pairs. Then, you can easily
get at the piece of data you want by asking the data parameter for the
value associated with a particular key. Proto's transform environments
give you just that.
Let's start by defining a key.
BOOST_PROTO_DEFINE_ENV_VAR(mykey_type, mykey);
This defines a global constant mykey
with the type mykey_type
.
We can use mykey
to store
a piece of assiciated data in a transform environment, as so:
// Call the MyEval algorithm with a transform environment containing // two key/value pairs: one for proto::data and one for mykey MyEval()( expr, state, (proto::data = 42, mykey = "hello world") );
The above means to invoke the MyEval
algorithm with three parameters: an expression, an initial state, and
a transform environment containing two key/value pairs.
From within a Proto algorithm, you can access the values associated with
different keys using the proto::_env_var<>
transform. For instance, proto::_env_var<mykey_type>
would fetch the value "hello world"
from the transform
environment created above.
The proto::_data
transform has some additional
smarts. Rather than always returning the third parameter regarless of
whether it is a blob or a transform environment, it checks first to see
if it's a blob or not. If so, that's what gets returned. If not, it returns
the value associated with the proto::data
key. In the above example, that would be the value 42
.
There's a small host of functions, metafunction, and classes that you
can use to create and manipulate transform environments, some for testing
whether an object is a transform environment, some for coercing an object
to be a transform environment, and some for querying a transform environment
whether or not is has a value for a particular key. For an exhaustive
treatment of the topic, check out the reference for the boost/proto/transform/env.hpp
header.