Search code examples
c++boost-spirit-x3

Boost Spirit: how to count occurences of certain characters and then put the result in AST?


I would like to parse the following text:

group RGB
group RRGB
group GBBB
group RRGGG

The resulting AST would be a struct that represents counts of each character:

struct group
{
    int r;
    int g;
    int b;
};

For the inputs above it would be 1,1,1, 2,1,1, 0,1,3, 2,3,0.

I can't come with any grammar that would conveniently count characters and enforce their order (GBR should fail the parse).

https://www.boost.org/doc/libs/develop/libs/spirit/doc/x3/html/spirit_x3/quick_reference/directive.html

There is x3::repeat parser but in only enfoces certain count of characters, it's attribute is a container.

x3::matches[a] has bool attribute but I don't know how many times a character may appear

There is no parser that would count appearance and return number of matches. I want a grammar like x3::lit("group") >> count['R'] >> count['G'] >> count['B'], but have no idea how count should be defined.

Right now the only working solution I can think of would be x3::lit("group") >> (*x3::char_['R'] >> *x3::char_['G'] >> *x3::char_['B'])[func] which then calls func that would just operate on the string. IMO this is not a clean solution and it requires semantic actions and creates unnecessary strings.


Solution

  • Slightly modifying "x3/directive/matches.hpp" you can get something like this:

    #include <boost/spirit/home/x3/core/parser.hpp>
    #include <boost/spirit/home/x3/support/traits/move_to.hpp>
    #include <boost/spirit/home/x3/support/unused.hpp>
    
    namespace not_boost { namespace not_spirit { namespace not_x3
    {
        template <typename Subject>
        struct count_directive : boost::spirit::x3::unary_parser<Subject, count_directive<Subject>>
        {
            using base_type = boost::spirit::x3::unary_parser<Subject, count_directive<Subject>>;
            static bool const has_attribute = true;
            using attribute_type = int;
    
            count_directive(Subject const& subject) : base_type(subject) {}
    
            template <typename Iterator, typename Context
              , typename RContext, typename Attribute>
            bool parse(Iterator& first, Iterator const& last
              , Context const& context, RContext& rcontext, Attribute& attr) const
            {
                int count=0;
                while(this->subject.parse(first, last, context, rcontext, boost::spirit::x3::unused))
                {
                    count++;
                }
                boost::spirit::x3::traits::move_to(count, attr);
                return true;
            }
        };
    
        struct count_gen
        {
            template <typename Subject>
            count_directive<typename boost::spirit::x3::extension::as_parser<Subject>::value_type>
            operator[](Subject const& subject) const
            {
                return { boost::spirit::x3::as_parser(subject) };
            }
        };
    
        auto const count = count_gen{};
    }}}
    

    Running on Wandbox