mkaes
mkaes

Reputation: 14119

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?

Upvotes: 1

Views: 366

Answers (1)

sehe
sehe

Reputation: 392999

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)

Upvotes: 1

Related Questions