Search code examples
c++parsingboost-spiritboost-spirit-x3

Using Spirit, how to populate vector within a struct of the AST?


I tried to adjust this example to make use of a variable number of elements using a group vector instead of hard coding 3 ints (n1, n2, n3), but to no avail.

Here is the example I've tried to modify. coliru.stacked-crooked.com/a/90110f91a4ac466a

Here's the code.

#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

namespace client { 
    namespace ast {

        struct number {
            //struct group { int n1, n2, n3; }; // instead of 3 ints ...
            struct group {
                std::vector<int> persons;       // get a variable number
                //bool dummy;
            };

            std::vector<group> groups;
            bool dummy;
        };

        struct comment {
            std::string text;
            bool dummy;
        };

        struct input {
            std::vector<comment> comments;  
            std::vector<number> numbers;
        }; 
    } 
}

BOOST_FUSION_ADAPT_STRUCT(client::ast::comment, text, dummy)
BOOST_FUSION_ADAPT_STRUCT(client::ast::number::group, persons)
BOOST_FUSION_ADAPT_STRUCT(client::ast::number, groups, dummy)
BOOST_FUSION_ADAPT_STRUCT(client::ast::input, comments, numbers)

namespace client {      
    namespace parser {

        namespace x3 = boost::spirit::x3;
        using namespace x3;

        typedef std::string::const_iterator It;

        using namespace x3;

        auto const comment = rule<struct _c, ast::comment> {"comment"} = lexeme[*(char_ - eol)] >> attr(false);
        auto const number  = rule<struct _n, ast::number> {"number"}   = *(int_ >> int_ >> int_) >> attr(false);

        auto lines = [](auto p) { return *(p >> eol); };

        auto const input = 
            repeat(1)[comment] >> eol >>
            lines(number);
    }
}

int main() {
    namespace x3 = boost::spirit::x3;

    std::string const iss("any char string here\n1 2 3\n1 2 3 4 5 6\n1 2 3 4 5 6 7 8 9\n");

    auto iter = iss.begin(), eof = iss.end();

    client::ast::input types;

    bool ok = phrase_parse(iter, eof, client::parser::input, x3::blank, types);

    if (iter != eof) {
        std::cout << "Remaining unparsed: '" << std::string(iter, eof) << "'\n";
    }
    std::cout << "Parsed: " << (100.0 * std::distance(iss.begin(), iter) / iss.size()) << "%\n";
    std::cout << "ok = " << ok << std::endl;

    for (auto &item : types.comments) {
        std::cout << "comment: " << boost::fusion::as_deque(item) << "\n";
    }
    /*for (auto& item : types.numbers) {
        std::cout << "number:  ";
        for (auto& g : item.groups)
            std::cout << boost::fusion::as_deque(g) << " ";
        std::cout << "\n";
    }*/
}

The error messages always look the same, deep in the templates. So instead of hard-coding the number of int's to be parsed, the number of int's should be variable, but still divisible by 3 (i.e., total number of ints on a line is 3, 6, 9, etc...).


Solution

  • Here's a vast simplification.

    If you just want to parse lines with n numbers:

    *int_
    

    If you want to validate that the number n is divisible by 3:

    (*int_) [is_valid_group]
    

    The semantic action can just do the check

    auto is_valid_group = [](auto& ctx) {
        _pass(ctx) = 0 == (_val(ctx).size() % 3);
    };
    

    Now the whole AST can simply be:

    using person = int;
    using groups_line = std::vector<person>;
    using comment_line = std::string;
    
    struct input {
        comment_line comments;
        std::vector<groups_line> numbers;
    }; 
    

    And the whole grammar:

    auto const comment_line 
        = lexeme[*(char_ - eol)];
    auto const groups_line  
        = rule<struct _n, ast::groups_line, true>  {"groups_line"}   
        = *int_ >> eps [ is_valid_group ];
    
    auto const input = 
        comment_line >> eol >>
        *(groups_line >> eol);
    

    DEMO

    Live On Coliru

    //#define BOOST_SPIRIT_X3_DEBUG
    #include <iostream>
    #include <boost/spirit/home/x3.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    
    namespace client { 
        namespace ast {
    
            using person = int;
            using groups_line = std::vector<person>;
            using comment_line = std::string;
    
            struct input {
                comment_line comments;
                std::vector<groups_line> numbers;
            }; 
        } 
    }
    
    BOOST_FUSION_ADAPT_STRUCT(client::ast::input, comments, numbers)
    
    namespace client {      
        namespace parser {
    
            namespace x3 = boost::spirit::x3;
            using namespace x3;
    
            typedef std::string::const_iterator It;
    
            using namespace x3;
    
            auto is_valid_group = [](auto& ctx) {
                _pass(ctx) = 0 == (_val(ctx).size() % 3);
            };
    
            auto const comment_line 
                //= rule<struct _c, ast::comment_line> {"comment_line"} 
                = lexeme[*(char_ - eol)];
            auto const groups_line  
                = rule<struct _n, ast::groups_line, true>  {"groups_line"}   
                = *int_ >> eps [ is_valid_group ];
    
            auto const input = 
                comment_line >> eol >>
                *(groups_line >> eol);
        }
    }
    
    int main() {
        namespace x3 = boost::spirit::x3;
    
        std::string const iss("any char string here\n1 2 3\n1 2 3 4 5 6\n1 2 3 4 5 6 7 8 9\n1 2 3 4");
    
        auto iter = iss.begin(), eof = iss.end();
    
        client::ast::input types;
    
        bool ok = phrase_parse(iter, eof, client::parser::input, x3::blank, types);
    
        if (iter != eof) {
            std::cout << "Remaining unparsed: '" << std::string(iter, eof) << "'\n";
        }
        std::cout << "Parsed: " << (100.0 * std::distance(iss.begin(), iter) / iss.size()) << "%\n";
        std::cout << "ok = " << ok << std::endl;
    
        std::cout << "comment: " << types.comments << "\n";
        for (auto& group : types.numbers) {
            std::cout << "number:  ";
            for (auto& person : group) std::cout << person << " ";
            std::cout << "\n";
        }
    }
    

    Prints

    Remaining unparsed: '1 2 3 4'
    Parsed: 89.0625%
    ok = 1
    comment: any char string here
    number:  1 2 3 
    number:  1 2 3 4 5 6 
    number:  1 2 3 4 5 6 7 8 9