I'm migrating my code from Boost Qi to X3. I have non-terminal rule expression
that is compiling to structure ast::Expression
. For the minimal reproducing example I left 2 statements that forms expression
. value
creates new while '(' expression ')'
should allow use brackets and just propogate result of expression
to parent definition.
In the Qi I had %=
operator that copied attribute to value. So the question - how to get rid of lambda e2e
in the following code?
#include <iostream>
#include <tuple>
#include <string>
#include <variant>
#include <vector>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
namespace x3 = boost::spirit::x3;
namespace ast
{
enum class Cmd : int
{
const_val = 42
};
using data_t = x3::variant<int, double>;
struct Expression
{
Cmd _cmd;
std::string _args;
};
}//ast
BOOST_FUSION_ADAPT_STRUCT(ast::Expression, _cmd, _args)
inline std::ostream& operator << (std::ostream& os, const ast::Expression& val)
{
os << "{" << (int)val._cmd << ':' << val._args << "}";
return os;
}
namespace client
{
namespace x3 = boost::spirit::x3;
x3::rule<class expression, ast::Expression> const expression("expression_stmt");
x3::rule<class value, ast::data_t> const value("value_stmt");
auto const value_def =
x3::int_
| x3::double_
;
// construct expression form plain constant
auto val_to_expr = [](auto& ctx) {
auto& a1 = _attr(ctx);
_val(ctx) = ast::Expression{
ast::Cmd::const_val,
std::to_string(boost::get<int>(a1))
};
};
// copy attribute to result value
auto e2e = [](auto& ctx) {
auto& a1 = _attr(ctx);
_val(ctx) = a1;
};
auto const expression_def =
value[val_to_expr]
// !!!! why I need e2e to copy attr to value?
| ('(' > expression > ')') [e2e]
;
BOOST_SPIRIT_DEFINE(expression, value);
}//ns:client
auto parse_text(const std::string& s)
{
namespace x3 = boost::spirit::x3;
auto iter = s.cbegin();
auto end_iter = s.cend();
ast::Expression res;
x3::ascii::space_type space;
bool success = x3::phrase_parse(iter, end_iter,
client::expression, space,
res);
if (success && iter == end_iter)
{
std::cout << "Success\n";
}
else {
std::cout << "Parse failure\n{";
while (iter != end_iter)
std::cout << *iter++;
std::cout << "}\n";
}
return res;
}
int main()
{
auto test = +[](const std::string& s) {
std::cout << "parsing " << s << '\n';
auto res = parse_text(s);
std::cout << "'" << res << "'\n";
};
;
test("123");
test("(123)");
}
You can set the force_attribute
template argument on x3::rule
:
x3::rule<class expression, ast::Expression, true> const expression{"expression"};
However, this means that all attribute assignments will be forced, so you have to facilitate val_to_expr
. You easily can, using the relevant constructor:
struct Expression {
Cmd _cmd;
std::string _args;
Expression(value_t v = {}) //
: _cmd(Cmd::const_val)
, _args(std::to_string(get<int>(v))) {}
};
Also, in order for the constructor to be used, you should remove the Fusion adaptation, which you weren't using anyways. Now everything fits in roughly half the size of the code:
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <iomanip>
#include <iostream>
#include <optional>
namespace x3 = boost::spirit::x3;
namespace ast {
using boost::get;
enum class Cmd : int { const_val = 42 };
using value_t = x3::variant<int, double>;
struct Expression {
Cmd _cmd;
std::string _args;
Expression(value_t v = {}) //
: _cmd(Cmd::const_val)
, _args(std::to_string(get<int>(v))) {}
friend std::ostream& operator<<(std::ostream& os, Expression const& e) {
os << "{" << static_cast<int>(e._cmd) << ':' << e._args << "}";
return os;
}
};
} // namespace ast
namespace client {
x3::rule<class expression, ast::Expression, true> const expression{"expression"};
x3::rule<class value, ast::value_t> const value{"value"};
auto const value_def = x3::int_ | x3::double_;
auto const expression_def = value | ('(' > expression > ')');
BOOST_SPIRIT_DEFINE(expression, value)
} // namespace client
std::optional<ast::Expression> parse_text(std::string_view s) {
if (ast::Expression res;
phrase_parse(s.cbegin(), s.cend(), client::expression >> x3::eoi, x3::space, res))
return res;
else
return std::nullopt;
}
int main() {
for (std::string_view s : {"123", "(123)"})
if (auto e = parse_text(s))
std::cout << quoted(s) << " -> " << *e << "\n";
else
std::cout << quoted(s) << " failed\n";
}
Printing
"123" -> {42:123}
"(123)" -> {42:123}
Ironically, at the end of that, you no longer need force_attribute
to begin with, because you don't use any semantic actions (see Boost Spirit: "Semantic actions are evil"?).
Also, the production int_ | double_
will never match doubles (see e.g. Why doesn't this boost::spirit::qi rule successfully parse?).
It looks as though you're crafting the classical expression parser. As such, you'd very much expect values to be expressions:
struct Command {
enum Code : int { const_val = 42 };
Code _cmd;
std::string _args;
};
using Expression = boost::variant<int, double, Command>;
Now you can enjoy no-semantic-action Fusion adaptation bliss:
x3::rule<class command, ast::Command> const command{"command"};
x3::rule<class expr, ast::Expression> const expr{"expr"};
auto dbl = x3::real_parser<double, x3::strict_real_policies<double>>{};
auto value = dbl | x3::int_;
auto command_def = '(' > x3::attr(ast::Command::const_val) > x3::raw[value % ','] > ')';
auto expr_def = command | value;
Printing Live On Coliru:
"123" -> 123
"(123)" -> {42:123}
Having reached this point, it would become more obvious that it is probably NOT necessary to put _args
as a string. I bet you'd rather recurse with std::vector<Expression>
?
struct Command {
enum Code : int { const_val = 42 };
Code _cmd;
std::vector<Expression> _args;
};
And a minor change of the Command
rule:
auto const command_def = '(' > x3::attr(ast::Command::const_val) > (expr % ',') > ')';
Now you can parse arbitrarily nested expressions, which do not even flatten to strings in the AST:
// #define BOOST_SPIRIT_X3_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <iostream>
#include <optional>
namespace x3 = boost::spirit::x3;
namespace ast {
struct Command;
using Expression = boost::variant<int, double, boost::recursive_wrapper<Command>>;
struct Command {
enum Code : int { const_val = 42 };
Code _cmd;
std::vector<Expression> _args;
friend std::ostream& operator<<(std::ostream& os, std::vector<Expression> const& ee) {
for (auto sep ="["; auto& e : ee)
os << std::exchange(sep, ",") << e;
return ee.empty() ? os : os << "]";
}
friend std::ostream& operator<<(std::ostream& os, Command const& e) {
os << "{" << e._cmd << ':' << e._args << "}";
return os;
}
friend std::ostream& operator<<(std::ostream& os, Code const& c) {
switch (c) {
case const_val: return os << "const_val";
default: return os << "?";
}
}
};
} // namespace ast
BOOST_FUSION_ADAPT_STRUCT(ast::Command, _cmd, _args)
namespace client {
x3::rule<class command, ast::Command> const command{"command"};
x3::rule<class expr, ast::Expression> const expr{"expr"};
auto const dbl = x3::real_parser<double, x3::strict_real_policies<double>>{};
auto const value = dbl | x3::int_;
auto const command_def = '(' > x3::attr(ast::Command::const_val) > (expr % ',') > ')';
auto const expr_def = command | value;
BOOST_SPIRIT_DEFINE(command, expr)
} // namespace client
std::optional<ast::Expression> parse_text(std::string_view s) {
if (ast::Expression e;
phrase_parse(s.cbegin(), s.cend(), client::expr >> x3::eoi, x3::space, e))
return e;
else
return std::nullopt;
}
int main() {
for (std::string_view s : {
"123",
"(123)",
"(123, 234, 345)",
"(123, 234, (345, 42.7e-3), 456)",
})
if (auto e = parse_text(s))
std::cout << std::setw(34) << quoted(s) << " -> " << *e << "\n";
else
std::cout << std::setw(34) << quoted(s) << " failed\n";
}
Printing:
"123" -> 123
"(123)" -> {const_val:[123]}
"(123, 234, 345)" -> {const_val:[123,234,345]}
"(123, 234, (345, 42.7e-3), 456)" -> {const_val:[123,234,{const_val:[345,0.0427]},456]}