4

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?

Filippo
  • 361
  • 1
  • 5
  • 16
  • 1
    [I've tried](https://wandbox.org/permlink/xfNV2Gk7ZPeLppK7) to solve this using the [`fusion::map` approach](https://github.com/boostorg/spirit/blob/develop/test/x3/fusion_map.cpp) that seems to be an alternative to Qi's permutation parser.Sadly I've had to make some changes to your example in order to make it work: I've grouped the properties that can be reordered in a nested struct (I think this is required with this approach) and I've added a `;` after each property (this is absolutely not required but simplifies which skipper to use). If you are interested I can make this into an answer. – llonesmiz Aug 14 '17 at 13:22
  • @llonesmiz sure! thank you – Filippo Aug 14 '17 at 13:40
  • 1
    @llonesmiz very smooth. I've tried my hand at combating things with more dynamism. Both approaches have weak points for now, IMO. – sehe Aug 14 '17 at 22:31

1 Answers1

3

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 
        << "}\n";
}

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 << "\n";
    else
        std::cout << "Parse failed\n";

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

Prints

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

Wit debug info: Live On Coliru

<layer_info>
  <try>Layer "L1" {\n    num</try>
  <name>
    <try>Layer "L1" {\n    num</try>
    <quoted>
      <try> "L1" {\n    number =</try>
      <success> {\n    number = 23\n </success>
      <attributes>[L, 1]</attributes>
    </quoted>
    <success> {\n    number = 23\n </success>
    <attributes>LayerInfo "L1"{number=0 color= visible=false}
</attributes>
  </name>
  <number>
    <try>\n    number = 23\n   </try>
    <success>\n    color = green\n </success>
    <attributes>LayerInfo "L1"{number=23 color= visible=false}
</attributes>
  </number>
  <number>
    <try>\n    color = green\n </try>
    <fail/>
  </number>
  <color>
    <try>\n    color = green\n </try>
    <success>\n    visible = true\n</success>
    <attributes>LayerInfo "L1"{number=23 color=green visible=false}
</attributes>
  </color>
  <number>
    <try>\n    visible = true\n</try>
    <fail/>
  </number>
  <color>
    <try>\n    visible = true\n</try>
    <fail/>
  </color>
  <visible>
    <try>\n    visible = true\n</try>
    <success>\n}</success>
    <attributes>LayerInfo "L1"{number=23 color=green visible=true}
</attributes>
  </visible>
  <number>
    <try>\n}</try>
    <fail/>
  </number>
  <color>
    <try>\n}</try>
    <fail/>
  </color>
  <visible>
    <try>\n}</try>
    <fail/>
  </visible>
  <success></success>
  <attributes>LayerInfo "L1"{number=23 color=green visible=true}
</attributes>
</layer_info>
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I'm trying to compile the exact code you provided on VS2017 and I'm getting an "Attribute does not have the expected size" error. On Coliru everything is alright and the code compiles. What may possibly be the cause for this error? – Filippo Aug 16 '17 at 11:50
  • The compiler - it's not in the supported set for Spirit X3, AFAIK. X3 is still experimental – sehe Aug 16 '17 at 17:24