In the grammar I want to implement are some keywords where the enum matters (storing the enum id of the concrete keyword inside ast's node) or even only the existence of the same specific keyword - hence optional in a boolean context. I like to have a self expressive parser expression and ast node, so I came up with the following (compileable) solution:
#include <iostream>
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ast {
enum class keyword_token {
UNSPECIFIED, FOO, BAR
};
struct buz {
bool foo;
int dummy;
};
}
BOOST_FUSION_ADAPT_STRUCT( ast::buz, foo, dummy )
namespace boost { namespace spirit { namespace x3 { namespace traits {
template <>
inline void
move_to(ast::keyword_token&& src, bool& dest) {
dest = static_cast<bool>(src);
}
} } } } // boost.spirit.x3.traits
namespace parser {
auto const FOO = x3::rule<struct _, ast::keyword_token> { "FOO" } =
x3::lexeme[ x3::no_case[ "foo" ] >> !(x3::alnum | '_') ]
>> x3::attr(ast::keyword_token::FOO);
auto const buz = x3::rule<struct _, ast::buz> { "buz" } =
-FOO >> x3::int_;
}
int main() {
for(std::string const str: {
"FOO 42",
"42"
}) {
auto iter = str.begin(), end = str.end();
ast::buz attr;
bool r = x3::phrase_parse(iter, end, parser::buz, x3::space, attr);
std::cout << "parse '" << str << "': ";
if (r && iter == end) {
std::cout << "succeeded:\n";
std::cout << (attr.foo ? "FOO " : "") << attr.dummy << "\n";
std::cout << "\n";
} else {
std::cout << "*** failed ***\n";
}
}
return 0;
}
Here the ast's buz node has a boolean attribute and the parser the 'optional' syntax. The idea behind is bool is default constructable and the standard guarantees that's initialiazed by 0 aka false which is intended. Further, I've a fallback solution of keyword::UNSPECIFIED
which (shall, since I'm not 100% sure for enum classes) be equal to 0 - which imo shouldn't never be triggered - also evaluating to false inside x3's move_to(...)
traits in the worst-case scenario.
Running this the parse stage succeed for both test case as expected, but the attribute of the 2nd test case isn't expected; obviously the 'boolean as optional' approach doesn't work as intended:
<buz>
<try>FOO 42</try>
<FOO>
<try>FOO 42</try>
<success> 42</success>
<attributes>1</attributes>
</FOO>
<success></success>
<attributes>[1, 42]</attributes>
</buz>
parse 'FOO 42': succeeded:
FOO 42
<buz>
<try>42</try>
<FOO>
<try>42</try>
<fail/>
</FOO>
<success></success>
<attributes>[1, 42]</attributes>
</buz>
parse '42': succeeded:
FOO 42
The debug mode shows the synthesized attributes [1, 42]
. So, are my consideration plausible and it could work, if yes, how to fix it to work as intended?
There may be another problem: Without defined BOOST_SPIRIT_X3_DEBUG
I get the warning:
warning: 'attr' may be used uninitialized in this function
...
warning: 'attr.ast::buz::dummy' may be used uninitialized in this function
at the cout line. Probably I don't understand the warning correct since the ast::buz is default constructible with the values I wan't to have as default (false, 0).
The brute-force solution is to write something like:
auto bool_attr = [](auto p) {
return x3::omit[ p ] >> x3::attr(true) | x3::attr(false);
};
and use it inside the rules, but I prefer the 'optional' syntax instead of writing bool_attr(FOO) >> x3::int_
etc.
The source is also at Coliru.
llonesmiz got the point with does POD class object initialization require constructor?; I have to explicit write the constructor for the ast node:
struct buz {
bool foo;
int dummy;
buz() : foo{}, dummy{}
{ }
};
than the attributes are as expected:
parse 'FOO 42': succeeded:
FOO 42
parse '42': succeeded:
42
With this, the warning mentioned above without defined BOOST_SPIRIT_X3_DEBUG
also disappeared and the warning makes more sense to me ...