The concept of semantic actions seems to be quite easy to understand. It appears to be at least easier to grasp than the concept of attribute propagation. This might be because semantic actions have been part of Spirit for almost a decade now. Additionally, with semantic actions data flow control is tightly connected to the component the semantic action is attached to, so the effect is highly localized and easy to spot.
Spirit has some new features related to semantics actions. That’s reason enough to talk about how attributes can be accessed from inside semantic actions.
First of all, let us revisit the concept of semantic actions.
Semantic actions may be attached to any point in the grammar specification. These actions are C++ functions or function objects that are called at certain points during the process of parsing or output generation. In Qi a semantic action is called whenever a part of the parser successfully recognizes a portion of the input. In Karma they are called whenever a part of the generator is about to be invoked.
Let us assume you have a component C, and a C++ function or function object
F, then you can make the component call
F by attaching it to the component:
The expression above links
F to the component
Even if it possible to utilize almost any separate C++ function as a semantic action, it is a lot simpler to write semantic actions using Boost.Phoenix. Phoenix enables to write semantic actions in a very straight forward way. The code to be executed is placed inline directly into the square brackets. Here is a very simple Qi example:
qi::int_[std::cout << qi::_1]
Here qi::_1 is a predefined (Phoenix) placeholder referencing the attribute of the parser the semantic actions has been attached to (the qi::int_). As the semantic action is invoked after the parser succeeded, the attribute will be set to the matched value. We can do something very similar in Karma as well:
int i = 3; karma::int_[karma::_1 = phoenix::ref(i)] // will emit: 3
Here we assign the value we want to be emitted to the (Phoenix) placeholder karma::_1 (phoenix::ref() is almost identical to boost::ref(), except that it integrates nicely with any Phoenix expression). This works as semantic actions in Karma are invoked before the generator does its job, allowing to pass the current value of the variable ‘i’ as the attribute for the generator karma::int_.
A lesser known feature of Spirit’s semantic actions is that they can be attached to arbitrary complex parser (or generator) expressions, which is particularly useful if the expression is a sequence. In this case predefined placeholders enable to access the attributes of the sequence elements separately:
(qi::int_ >> qi::double_)[std::cout << (qi::_1 + qi::_2)]
Here qi::_1 refers to the attribute of the component qi::int_, and qi::_2 refers to the attribute of qi::double_. This feature is very handy as the semantic action will be invoked only after both parser components succeeded making sure the results will be evaluated only if the input has been matched completely. This is something which did not work in Spirit.Classic. Many developers have been complaining about the lack of any related functionality.
Needless to say, similar constructs are available for Karma as well, even if this functionality is there not as important as in Qi.