Phoenix

The preceding chapter introduced Phoenix as a means to implementing your semantic actions. We shall look a little bit more into this important library with focus on how you can use it handily with Spirit. This chapter is by no means a thorough discourse of the library. For more information on Phoenix, please take some time to read the Phoenix User's Guide. If you just want to use it quickly, this chapter will probably suffice. Rather than taking you to the theories and details of the library, we shall try to provide you with annotated exemplars instead. Hopefully, this will get you into high gear quickly.

Semantic actions in Spirit can be just about any function or function object (functor) as long as it can satisfy the required signature. For example, uint_p requires a signature of void F(T), where T is the type of the integer (typically unsigned int). Plain vanilla actions are of the void F(IterT, IterT) variety. You can code your actions in plain C++. Calls to C++ functions or functors will thus be of the form P[&F] or P[F()] etc. (see Semantic Actions). Phoenix on the other hand, attempts to mimic C++ such that you can define the function body inlined in the code.

C++ in C++?

In as much as Spirit attempts to mimic EBNF in C++, Phoenix attempts to mimic C++ in C++!!!

var

Remember the boost::ref? We discussed that in the Parametric Parsers chapter. Phoenix has a similar, but more flexible, counterpart. It's called var. The usage is similar to boost::ref and you can use it as a direct replacement. However, unlike boost::ref, you can use it to form more complex expressions. Here are some examples:

    var(x) += 3
    var(x) = var(y) + var(z)
    var(x) = var(y) + (3 * var(z))
    var(x) = var(y)[var(i)] // assuming y is indexable and i is an index

Let's start with a simple example. We'll want to parse a comma separated list of numbers and report the sum of all the numbers. Using phoenix's var, we do not have to write external semantic actions. We simply inline the code inside the semantic action slots. Here's the complete grammar with our phoenix actions (see sum.cpp in the examples):

    real_p[var(n) = arg1] >> *(',' >> real_p[var(n) += arg1]) 

The full source code can be viewed here. This is part of the Spirit distribution.

argN

Notice the expression: var(n) = arg1 . What is arg1 and what is it doing there? arg1 is an argument placeholder. Remember that real_p (see Numerics) reports the parsed number to its attached semantic action. arg1 is a placeholder for the first argument passed to the semantic action by the parser. If there are more than one arguments passed in, these arguments can be referred to using arg1..argN. For instance, generic semantic actions (transduction interface; see Semantic Actions) are passed 2 arguments: the iterators (first/last) to the matching portion of the input stream. You can refer to first and last through arg1 and arg2, respectively.

Like var, argN is also composable. Here are some examples:

    var(x) += arg1
    var(x) = arg1 + var(z)
    var(x) = arg1 + (3 * arg2)
    var(x) = arg1[arg2] // assuming arg1 is indexable and arg2 is an index

val

Note the expression: 3 * arg2. This expression is actually a short-hand equivalent to: val(3) * arg2. We shall see later why, in some cases, we need to explicitly wrap constants and literals inside the val. Again, like var and argN, val is also composable.

Functions

Remember our very first example? In the Quick Start chapter, we presented a parser that parses a comma separated list and stuffs the parsed numbers in a vector (see number_list.cpp) . For simplicity, we used Spirit's pre-defined actors (see Predefined Actors). In the example, we used push_back_a:

    real_p[push_back_a(v)] >> *(',' >> real_p[push_back_a(v)])

Phoenix allows you to write more powerful polymorphic functions, similar to push_back_a, easily. See stuff_vector.cpp. The example is similar to number_list.cpp in functionality, but this time, using phoenix a function to actually implement the push_back function:

    struct push_back_impl
    {
        template <typename Container, typename Item>
        struct result
        {
            typedef void type;
        };

        template <typename Container, typename Item>
        void operator()(Container& c, Item const& item) const
        {
            c.push_back(item);
        }
    };

    function<push_back_impl> const push_back = push_back_impl();

The full source code can be viewed here. This is part of the Spirit distribution.

Predefined Phoenix Functions

A future version of Phoenix will include an extensive set of predefined functions covering the whole of STL containers, iterators and algorithms. push_back, will be part of this suite.

push_back_impl is a simple wrapper over the push_back member function of STL containers. The extra scaffolding is there to provide phoenix with additional information that otherwise cannot be directly deduced. result relays to phoenix the return type of the functor (operator()) given its argument types (Container and Item) . In this case, the return type is always, simply void.

push_back is a phoenix function object. This is the actual function object that we shall use. The beauty behind phoenix function objects is that the actual use is strikingly similar to a normal C++ function call. Here's the number list parser rewritten using our phoenix function object:

    real_p[push_back(var(v), arg1)] >> *(',' >> real_p[push_back(var(v), arg1)])

And, unlike predefined actors, they can be composed. See the pattern? Here are some examples:

    push_back(var(v), arg1 + 2)
    push_back(var(v), var(x) + arg1)
    push_back(var(v)[arg1], arg2) // assuming v is a vector of vectors and arg1 is an index

push_back does not have a return type. Say, for example, we wrote another phoenix function sin, we can use it in expressions as well:

    push_back(var(v), sin(arg1) * 2)

Construct

Sometimes, we wish to construct an object. For instance, we might want to create a std::string given the first/last iterators. For instance, say we want to parse a list of identifiers instead. Our grammar, without the actions, is:

    (+alpha_p) >> *(',' >> (+alpha_p))

construct_ is a predefined phoenix function that, you guessed it, constructs an object, from the arguments passed in. The usage is:

    construct_<T>(arg1, arg2,... argN)

where T is the desired type and arg1..argN are the constructor arguments. For example, we can construct a std::string from the first/last iterator pair this way:

    construct_<std::string>(arg1, arg2)

Now, we attach the actions to our grammar:

    (+alpha_p)
    [
        push_back(var(v), construct_<std::string>(arg1, arg2))
    ]
    >>
    *(',' >>
        (+alpha_p)
        [
            push_back(var(v), construct_<std::string>(arg1, arg2))
        ]
    )

The full source code can be viewed here. This is part of the Spirit distribution.

Lambda expressions

All these phoenix expressions we see above are lambda expressions. The important thing to note is that these expressions are not evaluated immediately. At grammar construction time, when the actions are attached to the productions, a lambda expression actually generates an unnamed function object that is evaluated later, at parse time. In other words, lambda expressions are lazily evaluated.

Lambda Expressions?

Lambda expressions are actually unnamed partially applied functions where placeholders (e.g. arg1, arg2) are provided in place of some of the arguments. The reason this is called a lambda expression is that traditionally, such placeholders are written using the Greek letter lambda .

Phoenix uses tricks not unlike those used by Spirit to mimic C++ such that you can define the function body inlined in the code. It's weird, but as mentioned, Phoenix actually mimicks C++ in C++ using expression templates. Surely, there are limitations...

All components in a Phoenix expression must be an actor (in phoenix parlance) in the same way that components in Spirit should be a parser. In Spirit, you can write:

    r = ch_p('x') >> 'y';

But not:

    r = 'x' >> 'y';

In essence, parser >> char is a parser, but char >> char is a char (the char shift-right by another char).

The same restrictions apply to Phoenix. For instance:

    int x = 1;
    cout << var(x) << "pizza"

is a well formed Phoenix expression that's lazily evaluated. But:

    cout << x << "pizza"

is not. Such expressions are immediately executed. C++ syntax dictates that at least one of the operands must be a Phoenix actor type. This also applies to compound expressions. For example:

    cout << var(x) << "pizza" << "man"

This is evaluated as:

    (((cout << var(x)) << "pizza") << "man")

Since (cout << var(x)) is an actor, at least one of the operands is a phoenix actor, ((cout << var(x)) << "pizza") is also a Phoenix actor, and the whole expression is thus also an actor.

Sometimes, it is safe to write:

    cout << var(x) << val("pizza") << val("man")

just to make it explicitly clear what we are dealing with, especially with complex expressions, in the same way as we explicitly wrap literal strings in str_p("lit") in Spirit.

Phoenix (and Spirit) also deals with unary operators. In such cases, we have no choice. The operand must be a Phoenix actor (or Spirit parser). Examples:

Spirit:

    *ch_p('z')  // good
    *('z') // bad

Phoenix:

    *var(x) // good (lazy)
    *x // bad (immediate)

Also, in Phoenix, for assignments and indexing to be lazily evaluated, the object acted upon should be a Phoenix actor. Examples:

    var(x) = 123 // good (lazy)
    x = 123 // bad (immediate)
    var(x)[0] // good (lazy)
    x[0] // bad, immediate
    var(x)[var(i)] // good (lazy)
    x[var(i)] // bad and illegal (x is not an actor)
    var(x[var(i)]) // bad and illegal (x is not an actor)

Wrapping up

Well, there you have it. I hope with this jump-start chapter, you may be able to harness the power of lambda expressions. By all means, please read the phoenix manual to learn more about the nitty gritty details. Surely, you'll get to know a lot more than just by reading this chapter. There are a lot of things still to be touched. There won't be enough space here to cover all the features of Phoenix even in brief.

The next chapter, Closures, we'll see more of phoenix. Stay tuned.