i have a comma separated list of id pairs, empty list is also valid
a b, c d
my rule is a expected sequence of id > id
that works
but when i try to add an optional integer value in front like -qi::int_ > id > id
a b, 2 c d
the empty list is not allowed anymore and i get an expectation exception asking for an id
Example: Live on Coliru
// #define BOOST_SPIRIT_DEBUG
#include <boost/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <optional>
namespace qi = boost::spirit::qi;
namespace Ast
{
struct Identifier : std::string
{
using std::string::string;
using std::string::operator=;
};
struct Test
{
std::optional<int> a;
Identifier b, c;
};
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT( Ast::Test, a, b, c )
using It = std::string_view::const_iterator;
template <typename Attr, typename Skipper>
void run_tests( qi::rule<It, Attr(), Skipper> const& rule_, std::vector<std::string> const& tests )
{
using boost::core::demangle;
std::cout << "======[ " << demangle( typeid( Attr ).name() ) << " ]======" << std::endl;
for( std::string_view test : tests )
{
It f = test.begin(), l = test.end();
try
{
std::cout << "Parsing: " << quoted( test ) << " -> ";
Attr v;
bool ok;
if constexpr( std::is_same_v<Skipper, qi::unused_type> )
ok = parse( f, l, qi::eps > rule_ > qi::eoi, v );
else
ok = phrase_parse( f, l, qi::eps > rule_ > qi::eoi, Skipper{}, v );
std::cout << ( ok ? "OK" : "FAIL" ) //
<< " Remaining: " << quoted( std::string( f, l ) ) //
<< "\n";
}
catch( qi::expectation_failure<It> const& ef )
{
auto p = ef.first - test.begin();
auto bol = test.find_last_of( "\r\n", p ) + 1;
auto line = std::count( f, f + bol, '\n' ) + 1;
auto eol = test.find_first_of( "\r\n", p );
std::cout << "EXPECTED " << ef.what_ << " in line:" << line << " col:" << ( p - bol ) << "\n"
<< " " << test.substr( bol, eol - bol ) << "\n"
<< " " << std::setw( p - bol ) << "" << "^--- here\n";
}
}
}
template <typename Attr>
using Rule = qi::rule<It, Attr(), qi::space_type>;
int main()
{
qi::rule<It, Ast::Identifier()> id = qi::char_( "a-zA-Z_" ) >> *qi::char_( "a-zA-Z0-9_" );
Rule<Ast::Test> test = -qi::int_ > id > id;
Rule<std::vector<Ast::Test>> list = -( test % ',' );
BOOST_SPIRIT_DEBUG_NODES( (id)(test)( list ) )
run_tests( list, { "a b, c d", "" } );
}
Output:
Parsing: "a b, c d" -> OK Remaining: ""
Parsing: "" -> EXPECTED <id> in line:1 col:0
i don't see why adding the optional int breaks the rule of -( test % ',' )
that it can be also empty
You have to tell -qi::int_
to NOT match if nothing follows:
Rule<Ast::Test> test = (!qi::eoi >> -qi::int_) > id > id;
See Live
Printing
Parsing: "a b, 2 c d" -> OK Remaining: ""
Parsing: "" -> OK Remaining: ""
Alternatively, be less overstrict with expectations. After all, your expectation WASN'T "if no integer was matched, require an id":
Rule<Ast::Test> test = -qi::int_ >> id >> id;
Same output, Live