The problem you are facing is that the attribute of your combined rule is basically:
tuple< tuple<size_t,bool,size_t>, std::string, std::string >
and by putting your variables one by one on the call to phrase_parse you have basically:
tuple< size_t, bool, size_t, std::string, std::string >
Because of the way attribute propagation works in spirit this is what is happening:
the whole tuple<size_t,bool,size_t>
is assigned to your num1 (ignoring the bool and second size_t), after that spirit tries to assign the first string to your bool, resulting in the error you have.
I believe the cleanest way to solve this is creating a custom struct to hold your result that reflects the structure of your rules.
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
struct optional_command_line_options
{
int num1;
bool bool1;
int num2;
};
struct command_line_options
{
optional_command_line_options opt;
std::string str1;
std::string str2;
};
BOOST_FUSION_ADAPT_STRUCT(
optional_command_line_options,
(int, num1)
(bool, bool1)
(int, num2)
)
BOOST_FUSION_ADAPT_STRUCT(
command_line_options,
(optional_command_line_options, opt)
(std::string, str1)
(std::string, str2)
)
bool parse_line( const std::string&str )
{
bool rc=false;
namespace qi = boost::spirit::qi;
using boost::spirit::ascii::space;
using boost::spirit::ascii::char_;
std::string::const_iterator iter( str.begin() );
command_line_options options;
options.opt.num1=88;
options.opt.bool1=false;
options.opt.num2=88;
qi::rule< std::string::const_iterator, std::string() > rstring=+~space;
qi::rule<std::string::const_iterator, boost::spirit::ascii::space_type,optional_command_line_options() > trule;
trule=
( qi::lit( "-p" ) >> qi::int_ ) ^
( qi::lit( "-j" ) >> qi::attr(true) ) ^
( qi::lit( "--jobs" ) >> qi::int_ )
;
qi::rule< std::string::const_iterator, boost::spirit::ascii::space_type, command_line_options() >arule;
arule = -trule >> rstring >> rstring;
bool result=qi::phrase_parse( iter,str.end(),
arule,
space,
options
);
if(result && iter==str.end())
{
std::cout << "Parse successful." << std::endl;
rc=true;
}
else
{
std::cerr<<"syntax error: "<<std::string(iter,str.end())<<"!
";
}
std::cout << std::boolalpha;
std::cout << "num1:" << options.opt.num1 << std::endl;
std::cout << "bool1:"<< options.opt.bool1 << std::endl;
std::cout << "num2:" << options.opt.num2 << std::endl;
std::cout << "str1:" << options.str1 << std::endl;
std::cout << "str2:" << options.str2 << std::endl;
return rc;
}
int main( int /*argc*/,char**/*argv*/ )
{
std::vector< std::string > testData;
testData.push_back( "-p 100 -j ifile ofile" );
testData.push_back( "-j -p 100 --jobs 16 ifile ofile" );
testData.push_back( "--jobs 16 -j -p 100 ifile ofile" );
testData.push_back( "--jobs 16 -p 100 ifile ofile" );
testData.push_back( "ifile ofile" );
for( std::vector< std::string >::const_iterator it=testData.begin();
it!=testData.end(); ++it )
{
std::cout << "
parsing string:" << *it << std::endl;
parse_line( *it );
}
return 0;
}
Running on LWS.
PS: You can't assign directly to rules declared with auto
that have literals embedded in them (strings or numbers for example) without using boost::proto::deep_copy;
auto trule = boost::proto::deep_copy(qi::lit( "-p" ) >> qi::int_);
There is a macro called BOOST_SPIRIT_AUTO that makes it easier to use:
#define BOOST_SPIRIT_AUTO(domain_, name, expr)
typedef boost::proto::result_of::
deep_copy<BOOST_TYPEOF(expr)>::type name##_expr_type;
BOOST_SPIRIT_ASSERT_MATCH(
boost::spirit::domain_::domain, name##_expr_type);
BOOST_AUTO(name, boost::proto::deep_copy(expr));
BOOST_SPIRIT_AUTO(qi,trule,qi::lit( "-p" ) >> qi::int_);