使用Boost.Spirit X3解析CSS

时间:2018-11-07 21:24:33

标签: c++ css boost boost-spirit boost-spirit-x3

我正在尝试使用Boost.Spirit X3编写(部分)CSS解析器。

我有(非常)基本的设置工作:

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

这里value只是一个简单的字符串解析器,而property则是一个包含所有CSS属性名称的符号表,该枚举列出了所有属性。但是现在我想知道我是否不能以某种强类型化的方式对所有可能的键值对进行编码?具体来说,对于每个具有固定数量可能性的属性,我将使用symbols<enum_type>和匹配的符号表条目,并为一些更复杂的属性(例如颜色)使用一些自定义规则。

问题是declaration规则必须具有特定的属性,在CSS中,声明块可以包含任意数量的元素,这些元素都具有自己的“属性”类型。最后,我想得到一个结构,然后以以下形式传递给BOOST_FUSION_ADAPT_STRUCT:

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{};
};

然后将正确默认使用哪个初始化未指定的成员。

我看到X3出现了一些我不知道如何解决的问题:

  1. 如上所述的强类型规则属性
  2. 融合适应的结构期望所有成员都被解析,这排除了我对我的简单方法实际起作用的想法。

我已经找到了一个Boost.Spirit.Qi 2 implementation,但由于X3如此不同,并且最终结果似乎不清楚,因此我似乎找不到任何帮助...

1 个答案:

答案 0 :(得分:0)

您似乎希望从结构定义生成解析器代码。您可以,但是您可能应该使用代码生成器。

这就是我知道您可以和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";
    }
}

打印

 ==== 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)

更多信息/想法

可以更详细地了解这种方法:

我给这个问题也提供了X3样式的答案:

对于更多X3灵感,我衷心推荐:

让我烦恼的是,我们应该能够使用结构化绑定,这样我们就不再需要Phoenix了。