I have a use case where i need to accept multiple commands on a CLI. Each command has its own set of parameters. To date, have used an ad-hoc regex style parser, want to do a bit better using boost spirit x3.
In the following cpp file - i am trying to parse cmd1 and cmd2 - how do i get the parsed results into the appropriate ASTs? How do i even specify multiple ASTs into phrase_parse?
begineer with boost-spirit cobbled together this basic experimental program from the examples - finding it a bit difficult to close the gap!
#include <iostream>
#include <tuple>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/std_tuple.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/char/char_parser.hpp>
#include <fmt/format.h>
namespace x3 = boost::spirit::x3;
namespace client::ast
{
struct cmd1
{
double param1;
double param2;
};
struct cmd2
{
std::string param1;
};
}
BOOST_FUSION_ADAPT_STRUCT(client::ast::cmd1, param1, param2);
BOOST_FUSION_ADAPT_STRUCT(client::ast::cmd2, param1);
namespace parser
{
using x3::lit;
using x3::lexeme;
using x3::char_;
using x3::double_;
using x3::phrase_parse;
using x3::ascii::space;
x3::rule<class cmd1_class, client::ast::cmd1> const cmd1 = "cmd1";
x3::rule<class cmd2_class, client::ast::cmd2> const cmd2 = "cmd2";
auto const quoted_string = lexeme['"' >> +(char_ - '"') >> '"'];
auto const cmd1_def = lit("cmd1") >> lit("param1") >> lit("=") >> double_ >> lit("param2") >> lit("=") >> double_;
auto const cmd2_def = lit("cmd2") >> lit("param1") >> lit("=") >> quoted_string;
BOOST_SPIRIT_DEFINE(cmd1, cmd2);
}
template <typename Iterator>
bool parse_line(Iterator first, Iterator last)
{
using x3::lit;
using x3::lexeme;
using x3::char_;
using x3::double_;
using x3::phrase_parse;
using x3::ascii::space;
using parser::cmd1;
using parser::cmd2;
client::ast::cmd1 cmd1_ast;
client::ast::cmd2 cmd2_ast;
std::variant<client::ast::cmd1, client::ast::cmd2> cmd_ast;
/// how do i parse the appropriate rule into the correct AST
bool r = phrase_parse(
first, // Start Iterator
last, // End Iterator
cmd1 | cmd2,
space // The Skip-Parser
/// cmd_ast); // The AST
);
if (first != last) // fail if we did not get a full match
return false;
return r;
}
int main()
{
/// read a line from stdin
while (true)
{
std::string line;
std::getline(std::cin, line);
if (line.empty())
break;
fmt::print("line: {}\n", line);
bool r = parse_line(line.begin(), line.end());
fmt::print("parse_line: {}\n", r);
}
return 0;
}
Expecting cmd1 and cmd2 parse into the appropriate ASTs
You are close. I'd use boost::variant:
I'd also simplify the rules and add one for
cmd1 | cmd2:Now you can make a function that returns the parsed command if any:
See it Live On Coliru with test cases:
Printing
To make it stricter, you can make it throw on unrecognized input as well:
Printing (Live):
The expectation_failure can be used to create some more helpful diagnostic output (you may search my answers for many examples)
Other Ideas
std::variantdoes not currently have good support in Spirit, see e.g.