Spirit V2 和 X3 的状态

Statefulness of Spirit V2 and X3

Spirit X3这么多'stateless'是什么用意?

Spirit V2'states' 的缺点

回顾 Spirit V2,"grammar" 在很多方面都是概念上有状态的。这是因为语法是一个 class 实例。

基本上,要使您的语法(甚至任何单个规则)成为有状态的,有很多不好的方面:

从理论上讲,添加外部状态会使您的语法变得不平凡。

真的不需要状态吗?

相反,您可以说任何解析器都是有状态的(因为它解析当前上下文,而上下文就是状态)。下面是程序员添加的附加 'context' 的一个很好的例子:

quoted_string_ = as_string [omit [char_("'\"") [_a = _1]] >> *(char_ - lit(_a)) >> lit(_a)]

qi::locals 外部状态的好兆头。

还有 'external states' 程序员可以添加到他的语法中,在大多数情况下他们只是做错了什么:

func_call_ = func_name_ >> lit('(') >> eps [ref(is_inside_function_call) = true] >> ...

但是,仍然存在一些外部状态有用的极端情况。

macro_type_1_ =
    lit("{{{") [PUSH_STATE(macro_ctx, Macro::Type1)] >> (
        ((any_expr_ - end_of_macro_ctx_) >> lit("}}}") >> eps [POP_STATE(macro_ctx)]) |
        (eps [POP_STATE(macro_ctx)] >> eps [_pass = false])
    )
;
macro_type_2_ =
    lit("[[[") [PUSH_STATE(macro_ctx, Macro::Type2)] >> (
        ((any_expr_ - end_of_macro_ctx_) >> lit("]]]") >> eps [POP_STATE(macro_ctx)]) |
        (eps [POP_STATE(macro_ctx)] >> eps [_pass = false])
    )
;

以上是一些任意上下文相关语言的示例。在这里,我通过为子规则模拟 'destructor' 添加 'context stack'。这可能是使用 Nabialec Trick 的特殊变体的好例子,其中 end_of_macro_ctx_qi::symbols 实例。

(有关可能的实施细节,请参阅 Boost.Spirit.Qi: dynamically create "difference" parser at parse time

你不能在这里使用qi::locals,因为不能保证qi::locals的生命周期。所以你应该使用一个全局变量(即语法class实例的成员变量)。

继承属性?可能是。如果您愿意将相同的变量传递给每个规则。

语法本身的外部状态

谈到外部状态,程序员可能希望将更多基本内容添加到他的语法中。

on_error<fail>(root_, phx::bind(&my_logger, &MyLogger::error, _1, _2, _3, _4));

您不能再在 X3 上执行此操作。

X3 无状态

X3 期望用户在命名空间范围内使用自动常量实例定义他的每条规则。

好了,现在我们来看看BOOST_SPIRIT_DEFINE的实现。它基本上只做一件事:

#define BOOST_SPIRIT_DEFINE(your_rule, <unspecified>) template <unspecified> <unspecified> parse_rule(decltype(your_rule), <unspecified>...) { <unspecified> }

parse_rule() 的第一个参数是给定规则的唯一类型的 decltype-d。

这意味着两件事:

  1. X3 完全依赖于对 parse_rule() 的 ADL 调用。
  2. parse_rule() 必须在命名空间范围内定义。

您不能为实例特化模板函数。无法告诉 X3 使用我的任何实例变量。

我撒谎了。如果你愿意,你可以这样做:

static inline MyLogger& use_my_logger_please() {
    static MyLogger instance; return instance;
}

#define MY_BOOST_SPIRIT_DEFINE(my_rule, <unspecified>, my_logger_f) <unspecified>
MY_BOOST_SPIRIT_DEFINE(rule_1_, ..., std::bind([] (MyLogger& l, std::string const& msg) { l << msg; }, this->logger_instance_, std::placeholders::_1))

真的吗?

您在 "question" 文章中提出了一些未经证实的说法。

我能从你的咆哮中看出很多情绪,但我发现当其中有太多值得商榷的地方时,我很难做出建设性的回应。

新的可能性

X3 is expecting an user to define his every single rule in namespace scope, with auto-consted instance.

这根本不是真的。 X3 不会那样做。可以说 X3 促进 该模式以启用

等关键功能
  • 递归文法
  • 跨翻译单元的解析器分离

另一方面,并​​不总是需要这些。

X3 的价值导向性使新模式能够实现目标。我非常喜欢能够做这样的事情:

有状态解析器工厂

auto make_parser(char delim) {
     return lexeme [ delim >> *('\' >> char_ | ~char_(delim)) >> delim ];
}

确实,您可能 "need" x3::rule 实现属性强制转换(如 qi::transfom_attr):

auto make_parser(char delim) {
     return rule<struct _, std::string> {} = lexeme [ delim >> *('\' >> char_ | ~char_(delim)) >> delim ];
}

事实上,我已经使用这种模式制作了快捷的 as<T>[] 指令:.

auto make_parser(char delim) {
     return as<std::string> [ lexeme [ delim >> *('\' >> char_ | ~char_(delim)) >> delim ] ];
}

没有什么能阻止您使用动态解析器​​工厂来使用来自周围状态的上下文。

状态语义动作

语义动作按值复制,但可以自由引用外部状态。使用工厂函数时,他们可以再次使用周围状态。

状态指令

动态创建状态的唯一方法是扩展实际的上下文对象。 x3::with<> 指令支持这一点,例如

这可用于对无限量的状态进行归类,例如通过将(智能)pointer/reference 旁路传递给您的解析器状态。

自定义解析器

自定义解析器是在 X3 中获得强大功能的一种非常简单的方法。请参阅示例:

我个人认为自定义解析器比 BOOST_SPIRIT_DECLARE/_DEFINE/_INSTANTIATE 舞蹈之类的东西更优雅。我承认我还没有在纯 X3 中创建任何需要多 TU 解析器的东西(我倾向于将 X3 用于小型、独立的解析器),但我直觉上更喜欢从 x3::parser_base 构建我自己的 TU 分离逻辑构建而不是"blessed" 上面提到的宏。另请参阅此讨论:Design/structure X3 parser more like Qi parser

Error/success处理

编译器教程展示了如何使用标记 base-class 为规则标记类型触发特定规则的处理程序。有一天我弄清楚了机制,但遗憾的是我不记得所有的细节而且 LiveCoding.tv 似乎已经失去了关于这个话题的直播。

我鼓励您查看编译器示例(它们仅在源代码树中)。

总结

我可以看出你是如何注意到负面差异的。重要的是要意识到 X3 不太成熟,旨在更轻量级,所以有些东西根本没有实现。另请注意,X3 以比以前更优雅的方式启用 许多 事物。大多数事物与 c++14 核心语言功能的交互更自然这一事实是一个很大的福音。

如果您想了解更多有关 X3 让我失望的内容,请参阅 , some discussions in chat (like this one 中的介绍性讨论。

希望我的逆袭对你X3的学习之旅有所帮助。我试图尽可能多地证实一些事情,尽管我承认我有时仍然更喜欢气。