0

I'm attempting to write a (partial) CSS parser using Boost.Spirit X3.

I have the (very) basic setup working:

const auto declaration_block_def = '{' >> +declaration >> '}';
const auto declaration_def = property >> ':' >> value >> ';';
const auto property_def = +(char_ - ":");
const auto value_def = +(char_ - ";");

Here value is just a simple string parser, and property a symbol table of all the CSS property names to an enum listing all the properties. But now I wonder if I couldn't in some way encode all the possible key-value pairs, in a strongly typed manner? Concretely, I'd use symbols<enum_type> with matching symbol table entries for each property that has a fixed number of possibilities, and some custom rule for more complex properties like colors.

The thing is that the declaration rule has to have a certain attribute, and in CSS, the declaration block can contain any number of elements all with their own "attribute" type. In the end I'd like to end up with a struct I'd pass to BOOST_FUSION_ADAPT_STRUCT in the following form:

enum class align_content : std::uint8_t;
enum class align_items : std::uint8_t;
enum class align_self : std::uint8_t;

struct declaration_block
{
  css::align_content align_content{};
  css::align_items align_items{};
  css::align_self align_self{};
};

Which would then properly default initialize any unspecified members.

I see a few issues popping up for X3 that I don't know how to solve:

  1. Strongly typed rule attribute as mentioned above
  2. The fusion adapted struct expects all members being parsed, which rules out my idea of my simple approach actually working.

I have found what seems like a Boost.Spirit.Qi 2 implementation, but as X3 is so different and their end result seems unclear, I can't seem to find any help in that...

rubenvb
  • 74,642
  • 33
  • 187
  • 332

1 Answers1

1

It looks like you wish to generate your parser code from struct definition. You can, however you should probably use a code generator.

Here's how I know you can get reasonably close with Qi:

Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_auto.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <iostream>
#include <iomanip>
#include <type_traits>

namespace css {
    enum class align_content : std::uint8_t;
    enum class align_items   : std::uint8_t;
    enum class align_self    : std::uint8_t;
}

namespace qi = boost::spirit::qi;

template <typename T> static constexpr char const* name_of = nullptr;

template <> constexpr char const* name_of<css::align_content> = "content";
template <> constexpr char const* name_of<css::align_items> = "items";
template <> constexpr char const* name_of<css::align_self> = "self";

namespace {
    template <typename T> struct align_parser {
        static auto call() {
            return qi::copy(qi::lexeme[name_of<T>] >> ":" >> qi::int_ >> ';');
        };

        using type = decltype(call());
    };
}

namespace css {
    // grrr: https://stackoverflow.com/a/36568565/85371
    template<class T, bool = std::is_enum<T>::value> struct safe_underlying_type : std::underlying_type<T> {};
    template<class T> struct safe_underlying_type<T, false /* is_enum */> {};

    template <typename T, typename Underlying = typename safe_underlying_type<T>::type > std::ostream& operator<<(std::ostream& os, T v) {
        using Int = std::common_type_t<int, Underlying>;
        return os << name_of<T> << " -> " << static_cast<Int>(v);
    }
}

namespace boost::spirit::traits {
    template <> struct create_parser<css::align_content> : align_parser<css::align_content> {};
    template <> struct create_parser<css::align_items> : align_parser<css::align_items> {};
    template <> struct create_parser<css::align_self> : align_parser<css::align_self> {};
}

struct declaration_block {
    css::align_content align_content{};
    css::align_items   align_items{};
    css::align_self    align_self{};
};

BOOST_FUSION_ADAPT_STRUCT(declaration_block, align_content, align_items, align_self)

int main() {
    for (std::string const input : {
            "", 
            "self:42;",
            "content:7;items:99;self:42;",
            "content : 7 ; items : 99; self : 42; ",
            " self : 42; items : 99; content : 7 ; ",
        }) 
    {
        std::cout << " ==== Test: " << std::quoted(input) << "\n";
        auto f = input.begin(), l = input.end();

        declaration_block db;
        bool ok = qi::phrase_parse(f, l, (qi::auto_ ^ qi::auto_ ^ qi::auto_) | qi::eoi, qi::space, db);

        if (ok) {
            using boost::fusion::operator<<;
            std::cout << "Parsed: " << db << "\n";
        }
        else
            std::cout << "Failed\n";

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

Prints

 ==== Test: ""
Parsed: (content -> 0 items -> 0 self -> 0)
 ==== Test: "self:42;"
Parsed: (content -> 0 items -> 0 self -> 42)
 ==== Test: "content:7;items:99;self:42;"
Parsed: (content -> 7 items -> 99 self -> 42)
 ==== Test: "content : 7 ; items : 99; self : 42; "
Parsed: (content -> 7 items -> 99 self -> 42)
 ==== Test: " self : 42; items : 99; content : 7 ; "
Parsed: (content -> 7 items -> 99 self -> 42)

More Info/Ideas

This approach is seen in more detail:

I gave that question has an X3-style answer as well:

For more X3 inspiration I heartily recommend:

One pet peeve for me is that we should be able to use structured binding so we don't nee Phoenix anymore.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks for the input! I'm waddling along for now, and came across [this answer of yours](https://stackoverflow.com/a/33556723/256138) which links to some IMO [promising X3 code](http://coliru.stacked-crooked.com/a/c2db66e432ea9b72). I even think that approach pretty much covers everything except the "no repeat" properties for the CSS declaration block. Am I missing something in that approach? – rubenvb Nov 10 '18 at 13:55
  • This is what I have so far: https://github.com/skui-org/skui/blob/master/css/grammar.h%2B%2B, pretty much leans heavily on what I mentioned in my previous comment: https://github.com/skui-org/skui/blob/master/css/grammar/make_property.h%2B%2B. The only thing (except for 90% of CSS properties) this thing doesn't handle is that each property should only appear once. This isn't really a big deal so I'm fine with it for now, but X3 does not have the permutation operator (which I should be able to combine with `eoi` and the optional operator to guarantee uniqueness I think). Any thoughts on that? – rubenvb Nov 15 '18 at 21:17
  • hehe do I recognize a technique from [here](https://stackoverflow.com/a/45683933/85371) or [here](https://stackoverflow.com/questions/49722452/combining-rules-at-runtime-and-returning-rules/49722855#49722855) :) I'd probably force myself to create a custom parser that combines the subparsers with the desired logic. Not something I'd look forward to, but something that seems to be the Right Thing To Do. – sehe Nov 16 '18 at 01:16
  • For inspiration, you could make it work like Qi's `operator ^` in pretty much the same way that I [faked Phoenix support in X3 lambdas here](https://chat.stackoverflow.com/transcript/10?m=39787277#39787277) – sehe Nov 16 '18 at 01:18