Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext
Passing Auxiliary Data to Transforms

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] Note

Also new is proto::lazy<>. Sometimes you don't have a ready-made callable object to execute. Instead, you want to first make one and then execute it. Above, we need to create a display_expr, initializing it with our ostream. After that, we want to invoke it by passing it the current expression. It's as if we were doing display_expr(std::cout)(the-expr). We achieve this two-phase evaluation using proto::lazy<>. If this doesn't make sense yet, don't worry about it.

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.

Transform Environment Variables
[Note] 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.


PrevUpHomeNext