Search code examples
c++boost-spirit

Spirit X3 composed attributes


I am trying to compose spirit rules but I cannot figure out what the attribute of this new rule would be.

The following code is working as I would expect it.

#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/tuple.hpp>
namespace ast{
    struct Record{
       int id;
       std::string name;
    };
    struct Document{
        Record rec;
        Record rec2;
        //std::vector<Record> rec;
        std::string name;
    };
    using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(ast::Record,
    name, id
)
BOOST_FUSION_ADAPT_STRUCT(ast::Document,
    rec, rec2, 
    //rec,
    name
)
namespace parser{
    namespace x3 = boost::spirit::x3;
    namespace ascii = boost::spirit::x3::ascii;
    using x3::lit;
    using x3::int_;
    using ascii::char_;

    const auto identifier = +char_("a-z");
    const x3::rule<class record, ast::Record> record = "record";
    const auto record_def = lit("record") >> identifier >> lit("{") >> int_ >> lit("}");
    const x3::rule<class document, ast::Document> document = "document";
    const auto document_def =
         record >> record
         //+record // This should generate a sequence
         >> identifier
         ;
    BOOST_SPIRIT_DEFINE(document, record);
}

namespace{
    constexpr char g_input[] = R"input(
                record foo{42}
                record bar{73}
                foobar
                )input";
}

int main(){
    using boost::spirit::x3::ascii::space;
    std::string str = g_input;
    ast::Document unit;
    bool r = phrase_parse(str.begin(), str.end(), parser::document, space, unit);
    std::cout << "Got: " << unit << "\n";
    return 0;
}

But when I change the rule to parse multiple records(instead of exactly 2) I would expect it to have a std::vector<Record> as an attribute. But all I get is a long compiler error that does not help me very much. Can someone point me to what I am doing wrong in order to compose the attributes correctly?


Solution

  • I think the whole reason it didn't compile is because you tried to print the result... and std::vector<Record> doesn't know how to be streamed:

    namespace ast {
        using boost::fusion::operator<<;
        static inline std::ostream& operator<<(std::ostream& os, std::vector<Record> const& rs) {
            os << "{ ";
            for (auto& r : rs) os << r << " ";
            return os << "}";
        }
    }
    

    Some more notes:

    • adding lexemes where absolutely required (!)
    • simplifying (no need to BOOST_SPIRIT_DEFINE unless recursive rules/separate TUs)
    • dropping redundant lit

    I arrived at

    Live On Coliru

    #include <iostream>
    #include <boost/spirit/home/x3.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    
    namespace ast {
        struct Record{
           int id;
           std::string name;
        };
        struct Document{
            std::vector<Record> rec;
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(ast::Record, name, id)
    BOOST_FUSION_ADAPT_STRUCT(ast::Document, rec, name)
    
    namespace ast {
        using boost::fusion::operator<<;
        static inline std::ostream& operator<<(std::ostream& os, std::vector<Record> const& rs) {
            os << "{ ";
            for (auto& r : rs) os << r << " ";
            return os << "}";
        }
    }
    
    namespace parser {
        namespace x3    = boost::spirit::x3;
        namespace ascii = x3::ascii;
    
        const auto identifier = x3::lexeme[+x3::char_("a-z")];
        const auto record     = x3::rule<class record, ast::Record> {"record"}
                              = x3::lexeme["record"] >> identifier >> "{" >> x3::int_ >> "}";
        const auto document   = x3::rule<class document, ast::Document> {"document"}
                              = +record
                              >> identifier
                              ;
    }
    
    int main(){
        std::string const str =  "record foo{42} record bar{73} foobar";
        auto f = str.begin(), l = str.end();
    
        ast::Document unit;
        if (phrase_parse(f, l, parser::document, parser::ascii::space, unit)) {
            std::cout << "Got: " << unit << "\n";
        } else {
            std::cout << "Parse failed\n";
        }
    
        if (f != l) {
            std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
        }
    }
    

    Prints

    Got: ({ (foo 42) (bar 73) } foobar)