精神V2和X3的状态性

Statefulness of Spirit V2 and X3

本文关键字:状态 X3 V2 精神      更新时间:2023-10-16

Spirit X3如此'无状态'的意图是什么?

精神V2中"状态"的坏方面

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

基本上,使你的语法 - 甚至任何单一规则 - 成为有状态有很多不好的方面:

  • 它可能会使您的语法不可重入;
  • 它可能会向语法实例添加线程不安全;
  • 自我管理的"旗帜"是一场灾难。

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

真的不需要状态吗?

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

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

qi::locals外部国家的好兆头。

程序员还可以将"外部状态"添加到他的语法中,在大多数情况下,它们只是做错了什么:

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])
)
;

上面是一些任意上下文相关语言的示例。在这里,我通过模拟子规则的"析构函数"来添加"上下文堆栈"。这可能是使用Nabialec Trick的特殊变体的一个很好的例子,其中end_of_macro_ctx_qi::symbols实例。

(参见 Boost.Spirit.Qi:在解析时动态创建"差异"解析器,了解可能的实现细节)

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

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

语法本身的外部状态

说到外部状态,程序员可能还想在他的语法中添加更基本的东西。

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,<未指定>) 模板<未指定><未指定>

> parse_rule(decltype(your_rule),<未指定>...) {<未指定>}

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

这意味着两件事:

  1. X3完全依靠ADL调用来parse_rule()
  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))

真?

您在"问题"文章中提出了许多未经证实的主张。

我认识到你的咆哮中闪耀的大部分情绪,但是当其中有太多值得商榷的地方时,我发现很难建设性地做出回应。

新的可能性

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

这根本不是真的。X3 不会这样做。可以说 X3促进了这种模式,以实现关键功能,例如

  • 递归语法
  • 跨翻译单元分离解析器

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

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

有状态解析器工厂

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

事实上,你可能"需要"x3::rule来实现属性强制(如qi::transfom_attr):

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

事实上,我已经使用这种模式来制定快速而肮脏的as<T>[]指令:了解 Boost.Spirit 中的列表运算符 (%)。

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

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

有状态语义操作

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

有状态指令

动态创建状态的唯一方法是扩展实际的上下文对象。x3::with<>指令支持这一点,例如 Boost Spirit X3 无法编译具有可变因子的重复指令

这可用于对无限数量的状态进行分类,例如,只需侧通道传递一个(智能)指针/引用到您的解析器状态。

自定义解析器

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

灵气:如何编写非终端解析器?

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

错误/成功处理

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

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

总结

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

如果你想了解更多关于X3让我失望的事情,请参阅该链接答案中的介绍性讨论,聊天中的一些讨论(如这个)。

我希望我的反咆哮可以帮助您学习 X3 的旅程。我试图尽可能多地证实一些事情,尽管我坦率地承认我有时仍然更喜欢Qi。