Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
387 views
in Technique[技术] by (71.8m points)

c++ - Boost Spirit x3: parse into structs

From the Boost Spirit X3 tutorial:

First, let's create a struct representing an employee:

namespace client { namespace ast
{
   struct employee
   {
       int age;
       std::string surname;
       std::string forename;
       double salary;
   };
}}

Then, we need to tell Boost.Fusion about our employee struct to make it a first-class fusion citizen that the grammar can utilize.

BOOST_FUSION_ADAPT_STRUCT(
    client::ast::employee,
    (int, age)
    (std::string, surname)
    (std::string, forename)
    (double, salary)
)`

[...] In fusion's view, a struct is just a form of a tuple. You can adapt any struct to be a fully conforming fusion tuple. [...] Applying our collapsing rules above, the RHS has an attribute of: fusion::vector<int, std::string, std::string, double> The struct employee IS compatible with fusion::vector. So, the RHS of start uses start's attribute (a struct employee) in-situ when it does its work.

If I well understood, this logic heavily relies on the order of the attributes.

Now, I am in a situation where I need to parse something like

Layer "L1" {
    number = 23
    color = green
    visible = true
}

into a struct

struct LayerInfo
{
    std::string layerName;
    int layerNumber;
    std::string color;
    bool visible;
}

The problem is, the order of the layer properties can change, which is in opposition with the logic seen above.

Which is the correct way to parse into a struct like this? Do I need necessarily need to use semantic actions?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I love @llonesmiz's approach in the comment.

I "had to" try my favorite approach with X3 using functional composition too, though. Here's a sketch of the approach which does parse and propagate the values.

Missing are checks on property presence/uniqueness. (I think such a thing is doable using a x3::with<> context addition that basically contains a std::set<V T::*>. Of course such a thing needs (implementation dependent?) casts or an erasure wrapper).

For now, presented without comment:

Live On Coliru

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

struct LayerInfo
{
    std::string layerName;
    int layerNumber = 0;
    std::string color;
    bool visible = false;
};

namespace Parser {
    namespace x3 = boost::spirit::x3;

    // custom type parsers
    auto quoted = rule<std::string>("quoted", x3::lexeme [ '"' >> *('\' >> x3::char_ | ~x3::char_('"')) >> '"' ]);
    struct colors_type : x3::symbols<char> {
        colors_type() {
            this->add("red")("blue")("green")("black");
        }
    } static const colors;

    namespace detail {
        template <typename T> auto propagate(T member) {
            return [=](auto& ctx){ x3::traits::move_to(x3::_attr(ctx), x3::_val(ctx).*member); };
        }

        template <typename T> auto make_member_parser(int T::* const member) { return x3::int_ [propagate(member)]; }
        template <typename T> auto make_member_parser(bool T::* const member) { return x3::bool_ [propagate(member)]; }
        template <typename T> auto make_member_parser(std::string T::* const member) { return x3::raw[colors] [propagate(member)]; }

        template <typename T = LayerInfo, typename P>
            auto rule(const char* debug, P p) { return x3::rule<struct _, T> {debug} = x3::skip(x3::space)[p]; };

        auto property = [](auto label, auto member) {
            return rule(label, x3::as_parser(label) >> '=' >> make_member_parser(member));
        };
    }

    using detail::rule;
    using detail::propagate;
    using detail::property;

    auto name       = rule("name", "Layer" >> quoted [propagate(&LayerInfo::layerName)]);

    auto number     = property("number", &LayerInfo::layerNumber);
    auto color      = property("color", &LayerInfo::color);
    auto visible    = property("visible", &LayerInfo::visible);

    auto layer_info = name >> '{' >> +(number | color | visible) >> '}';

    auto grammar    = rule("layer_info", layer_info);
}

std::ostream& operator<<(std::ostream& os, LayerInfo const& li) {
    return os << "LayerInfo "" << li.layerName << ""{"
        << "number="  << li.layerNumber   << " "
        << "color="   << li.color         << " "
        << "visible=" << std::boolalpha << li.visible 
        << "}
";
}

int main() {
    std::string const sample = R"(Layer "L1" {
    number = 23
    color = green
    visible = true
})";

    LayerInfo v;
    auto f = sample.begin(), l = sample.end();
    bool ok = parse(f, l, Parser::grammar, v);


    if (ok)
        std::cout << "Parsed: " << v << "
";
    else
        std::cout << "Parse failed
";

    if (f!=l)
        std::cout << "Remaining unparsed: '" << std::string(f,l) << "'
";
}

Prints

Parsed: LayerInfo "L1"{number=23 color=green visible=true}

Wit debug info: Live On Coliru

<layer_info>
  <try>Layer "L1" {
    num</try>
  <name>
    <try>Layer "L1" {
    num</try>
    <quoted>
      <try> "L1" {
    number =</try>
      <success> {
    number = 23
 </success>
      <attributes>[L, 1]</attributes>
    </quoted>
    <success> {
    number = 23
 </success>
    <attributes>LayerInfo "L1"{number=0 color= visible=false}
</attributes>
  </name>
  <number>
    <try>
    number = 23
   </try>
    <success>
    color = green
 </success>
    <attributes>LayerInfo "L1"{number=23 color= visible=false}
</attributes>
  </number>
  <number>
    <try>
    color = green
 </try>
    <fail/>
  </number>
  <color>
    <try>
    color = green
 </try>
    <success>
    visible = true
</success>
    <attributes>LayerInfo "L1"{number=23 color=green visible=false}
</attributes>
  </color>
  <number>
    <try>
    visible = true
</try>
    <fail/>
  </number>
  <color>
    <try>
    visible = true
</try>
    <fail/>
  </color>
  <visible>
    <try>
    visible = true
</try>
    <success>
}</success>
    <attributes>LayerInfo "L1"{number=23 color=green visible=true}
</attributes>
  </visible>
  <number>
    <try>
}</try>
    <fail/>
  </number>
  <color>
    <try>
}</try>
    <fail/>
  </color>
  <visible>
    <try>
}</try>
    <fail/>
  </visible>
  <success></success>
  <attributes>LayerInfo "L1"{number=23 color=green visible=true}
</attributes>
</layer_info>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...