问题
I'm trying to write an expression parser with boost spirit x3. I based my new code on old code that I written years ago (and worked well) with Spirit 2.x (qi).
The core of my code is:
//Make new rule(s) for expression
auto term = factor >> *(('*' >> factor) | ('/' >> factor));
auto expression = term >> *(('+' >> term) | ('-' >> term));
auto group = '(' >> expression >> ')';
auto factor = lexeme["double_"] | group;
string s="12.4 + 3.2";
auto first = s.begin();
auto last = s.end();
bool r = x3::phrase_parse(
first,
last,
// Begin grammar
expression,
// End grammar
x3::space);
I obtain two errors (Visual Studio 2019): Error C2338 BOOST_SPIRIT_DEFINE undefined for this rule. Error C2039 'parse': is not a member of 'boost::spirit::x3::unused_type'
Why?
回答1:
BOOST_SPIRIT_DEFINE is used to associate a static rule-tag with a definition (i.e. an instantiation of the parse function template for that rule).
The good news is that this is frequently unnecessary, and rules can be defined in-line without any macros.
In general, the following reasons exist to use x3::rule
:
When rules use recursion. The rule(s) that are invoked recursively need to have the undefined rule (uninitialized
rule<>
object) to refer to (much like a forward declaration).To coerce the exposed attribute type (in my exeperience the need for this is a bit more common in X3 than it was in Qi: Understanding the List Operator (%) in Boost.Spirit, or e.g. boost::spirit::x3 attribute compatibility rules, intuition or code?).
When you want to spread your rule definitions across translation units (i.e. have external definitions). Note this also requires you to know the context and iterator type(s) you need to support so you can instantiate appropriately.
To benefit from the builtin rule debugging (
#define BOOST_SPIRIT_X3_DEBUG
). This is the only reason I know off to use theBOOST_SPIRIT_DEFINE
family of macrosThis frequently gives rise to hard-to diagnose linker errors so I recommend against this in most cases: X3 parsers compile fast enough in practice that I can afford to keep them in a single translation unit
Your Sample
Only expression
is used recursively. For the rest it suffices to simply reorder them:
namespace parser {
x3::rule<struct expression_> expression{"expression"};
auto group = '(' >> expression >> ')';
auto factor = x3::lexeme["double_"] | group;
auto term = factor >> *(('*' >> factor) | ('/' >> factor));
auto expression_def = term >> *(('+' >> term) | ('-' >> term));
BOOST_SPIRIT_DEFINE(expression)
}
See it Live On Compiler Explorer printing:
<expression>
<try>12.4 + 3.2</try>
<fail/>
</expression>
------ 12.4 + 3.2
r: false
remaining input: '12.4 + 3.2'
It is clear that your factor
rule should be back to normal:
auto factor = x3::double_ | group;
See it Live On Compiler Explorer printing:
<expression>
<try>12.4 + 3.2</try>
<success></success>
</expression>
------ 12.4 + 3.2
r: true
remaining input: ''
BONUS: Attributes
Adding attribute propagation will highlight what I meant under 2. above:
namespace Ast {
struct binop;
using expression = boost::make_recursive_variant<
double,
boost::recursive_wrapper<binop>,
boost::recursive_variant_
>::type;
struct binop {
char op;
expression lhs, rhs;
};
}
That's the simplest thing that could work. Your rules are pretty efficient for constructing the ast from semantic actions¹:
namespace parser {
x3::rule<struct expression_, Ast::expression> expression{"expression"};
auto assign = [](auto& ctx) { _val(ctx) = _attr(ctx); };
auto make_binop = [](auto& ctx) {
using boost::fusion::at_c;
auto& op = at_c<0>(_attr(ctx));
auto& rhs = at_c<1>(_attr(ctx));
_val(ctx) = Ast::binop { op, _val(ctx), rhs };
};
auto group
= x3::rule<struct group_, Ast::expression> {"group"}
= '(' >> expression >> ')';
auto factor
= x3::rule<struct factor_, Ast::expression> {"factor"}
= x3::double_ | group;
auto term
= x3::rule<struct term_, Ast::expression> {"term"}
= factor [assign] >> *(x3::char_("*/") >> factor) [make_binop];
auto expression_def
= term [assign] >> *(x3::char_("-+") >> term) [make_binop];
BOOST_SPIRIT_DEFINE(expression)
}
See it Live On Compiler Explorer:
int main() {
for (std::string const s : {
"12.4 + 3.2",
})
{
auto f = s.begin(), l = s.end();
Ast::expression e;
bool r = x3::phrase_parse(f, l, parser::expression, x3::space, e);
std::cout
<< "------ " << s << "\n"
<< "r: " << std::boolalpha << r << "\n";
if (r)
std::cout << "e: " << e << "\n";
if (f!=l)
std::cout << "remaining input: '" << std::string(f,l) << "'\n";
}
}
Printing
------ 12.4 + 3.2
r: true
e: (12.4 + 3.2)
And the debug output:
<expression>
<try>12.4 + 3.2</try>
<term>
<try>12.4 + 3.2</try>
<factor>
<try>12.4 + 3.2</try>
<success> + 3.2</success>
<attributes>12.4</attributes>
</factor>
<success> + 3.2</success>
<attributes>12.4</attributes>
</term>
<term>
<try> 3.2</try>
<factor>
<try> 3.2</try>
<success></success>
<attributes>3.2</attributes>
</factor>
<success></success>
<attributes>3.2</attributes>
</term>
<success></success>
<attributes>(12.4 + 3.2)</attributes>
</expression>
¹ Here I don't mention my usual screed because using automatic propagation in this kind of grammar tends to lead to a lot of backtracking in the grammar, which makes it inefficient
来源:https://stackoverflow.com/questions/65566480/boost-spirit-define-not-understand