If you read the article about attribute handling for non-terminals (The Magical Power of Attributes in Spirit – Directives and Non-terminals) you might remember that Spirit’s non-terminals (rules and grammars) are somewhat special with regard to their attribute handling. In today’s ‘Tip of the Day’ I would like to revisit this topic as it still seems to be difficult to understand.
Spirit’s non-terminals can expose any attribute type, and the required type needs to be explicitly specified while declaring them. This is true for Qi parsers and Karma generators. The following example declares a rule exposing an (synthesized) attribute of type double (if you wonder why it uses the unusual function declaration syntax, i.e. double(), please read the article mentioned above):
namespace qi = boost::spirit::qi; std::string input("1.0"); std::string::const_iterator b = input.begin(); double result = 0; qi::rule<std::string::const_iterator, double()> r = qi::double_; qi::parse(b, input.end(), r, result);
The left hand side’s attribute (the result passed in by the user) is directly handed over to the right hand side of the rule. The parser created by qi::double_ will put its result into the very same double instance as passed in from the outside. No additional copies are created. We call this behavior auto attribute propagation. For rules it is enabled by default as long as no semantic actions are attached to the right hand side’s expression.
If you want to enforce auto attribute propagation even if the right hand side has semantic actions, you need to employ the ‘%=’ syntax as shown in the next Karma example:
namespace karma = boost::spirit::karma; typedef std::back_insert_iterator<std::string> output_iterator; std::string output; output_iterator sink(output); karma::rule<output_iterator, double()> r; r %= karma::double_[++karma::_1]; karma::generate(sink, r, 1.0); // will emit: 2.0
This code will increment the attribute (the 1.0) before emitting the result to the output iterator (remember, semantic actions in Karma are called before invoking the related generator). Note, we incremented the attribute of the generator karma::double_, not the left hand side’s attribute (which would have been ++karma::_val). This works as we enforced the auto attribute propagation using the ‘%=’.
There is one more important thing to remember: regardless of how the auto attribute propagation is enabled, either based on the default behavior of ‘=’ or by enforcing it using ‘%=’, the attribute types of the rule and the right hand side must be compatible. It is not possible to define Spirit’s ‘attribute compatibility’ in a short sentence, so we leave the topic for another day. But in the simplest case it means the attributes have to be convertible. In Qi the right hand side’s attribute must at least be convertible to the rule’s attribute, while in Karma the opposite needs to be true: the rule’s attribute should at least be convertible to the right hand side’s attribute.