This question has come up before, but it seems that none of the answers provide alternatives with boost
-style generic programming.
Like many I use boost
Actually what you have there is more akin to an expression grammar. I'd suggest writing a grammar/parser for that instead of (ab?)using program_options for this.
If your program takes options: use program options.
If your program takes an expression: use an expression parser.
An example:
Live On Coliru
// #define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted/struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
struct Operation {
enum Kind { add, multiply } kind;
double operand;
friend std::ostream& operator<<(std::ostream& os, Kind k) {
switch (k) {
case add: return os << "--add";
case multiply: return os << "--multiply";
};
return os << "??";
}
};
BOOST_FUSION_ADAPT_STRUCT(Operation, (Operation::Kind,kind)(double,operand))
template <typename It, typename Skipper = qi::blank_type>
struct expression_grammar : qi::grammar<It, std::vector<Operation>(), Skipper> {
expression_grammar() : expression_grammar::base_type(start) {
using namespace qi;
opkinds.add
("-a", Operation::add)
("--add", Operation::add)
("-m", Operation::multiply)
("--multiply", Operation::multiply)
;
option = opkinds > eol > double_;
start = *(option > eol);
BOOST_SPIRIT_DEBUG_NODES((start)(option))
}
private:
qi::symbols<char, Operation::Kind> opkinds;
qi::rule<It, Operation(), Skipper> option;
qi::rule<It, std::vector<Operation>(), Skipper> start;
};
int main(int argc, char const** argv) {
std::stringstream iss;
if (argc)
std::copy(argv+1, argv+argc, std::ostream_iterator<const char*>(iss, "\n"));
typedef boost::spirit::istream_iterator It;
expression_grammar<It> grammar;
It first(iss >> std::noskipws), last;
std::vector<Operation> operations;
bool ok = qi::phrase_parse(first, last, grammar, qi::blank, operations);
if (ok)
{
std::cout << "Parse success\n";
for (auto const& op : operations)
std::cout << boost::fusion::as_vector(op) << "\n";
} else
std::cout << "Parse failed\n";
if (first!=last)
std::cout << "Remaining input: '" << std::string(first,last) << "'\n";
}
Note
eol
as the option separator. You might want to use '\0'
instead. This was easiest because the blank
skipper already skips whitespace /except/ eol
. I'm lazy :)You might wnat to mix-and-match (not treat all parameters as part of the expression). A common pattern would be
myprogram -x option1 -v -o filename -- my expression grammar follows
A common alternative pattern is to make the expression a single parameter:
myprogram -e 'add 5; multiply 32;' -x option1
See this approach Live on Coliru too
I was lazy again with the "Parse success" printing (I didn't want to implement operator<<
for the Operation
type)
enable debug info with #define BOOST_SPIRIT_DEBUG
(uncomment the first line)
<start>
<try>-a\n8\n-m\n7\n-a\n32\n</try>
<option>
<try>-a\n8\n-m\n7\n-a\n32\n</try>
<success>\n-m\n7\n-a\n32\n</success>
<attributes>[[--add, 8]]</attributes>
</option>
<option>
<try>-m\n7\n-a\n32\n</try>
<success>\n-a\n32\n</success>
<attributes>[[--multiply, 7]]</attributes>
</option>
<option>
<try>-a\n32\n</try>
<success>\n</success>
<attributes>[[--add, 32]]</attributes>
</option>
<option>
<try></try>
<fail/>
</option>
<success></success>
<attributes>[[[--add, 8], [--multiply, 7], [--add, 32]]]</attributes>
</start>
Parse success
(--add 8)
(--multiply 7)
(--add 32)