【问题标题】:Parsing CSS with Boost.Spirit X3使用 Boost.Spirit X3 解析 CSS
【发布时间】:2018-11-07 21:24:32
【问题描述】:

我正在尝试使用 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 如此不同,而且它们的最终结果似乎不清楚,我似乎找不到任何帮助...

【问题讨论】:

  • 我不敢相信这还没有完成,并且存在一些库。你找了吗?
  • @Jesper 与 Boost Spirit X3?我还没有找到任何东西。

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


【解决方案1】:

看起来您希望从结构定义生成解析器代码。你可以,但是你应该使用代码生成器。

以下是我知道你可以与 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。

【讨论】:

  • 感谢您的意见!我现在正在蹒跚学步,遇到了this answer of yours,它链接到一些IMO promising X3 code。我什至认为这种方法几乎涵盖了除了 CSS 声明块的“不重复”属性之外的所有内容。我在这种方法中遗漏了什么吗?
  • 这是我目前所拥有的:github.com/skui-org/skui/blob/master/css/grammar.h%2B%2B,很大程度上依赖于我在之前的评论中提到的内容:github.com/skui-org/skui/blob/master/css/grammar/…。唯一不能处理的事情(除了 90% 的 CSS 属性)是每个属性只能出现一次。这并不是什么大问题,所以我现在可以接受,但 X3 没有置换运算符(我认为我应该能够将其与 eoi 和可选运算符结合使用以保证唯一性)。对此有什么想法吗?
  • 嘿嘿,我知道herehere 的技术吗 :) 我可能会强迫自己创建一个自定义解析器,将子解析器与所需的逻辑结合起来。不是我期待的事情,而是似乎是正确的事情。
  • 为了获得灵感,你可以让它像 Qi 的 operator ^ 一样工作,与我 faked Phoenix support in X3 lambdas here 的方式几乎相同
猜你喜欢
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多