Spirit V2 和 X3 的状态
Statefulness of Spirit V2 and X3
Spirit X3这么多'stateless'是什么用意?
Spirit V2'states' 的缺点
回顾 Spirit V2,"grammar" 在很多方面都是概念上有状态的。这是因为语法是一个 class 实例。
基本上,要使您的语法(甚至任何单个规则)成为有状态的,有很多不好的方面:
- 它可能会使您的语法不可重入;
- 它可能会给您的语法实例增加线程不安全性;
- 自我管理 'flag' 是一场灾难。
从理论上讲,添加外部状态会使您的语法变得不平凡。
真的不需要状态吗?
相反,您可以说任何解析器都是有状态的(因为它解析当前上下文,而上下文就是状态)。下面是程序员添加的附加 '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。
这意味着两件事:
- X3 完全依赖于对
parse_rule()
的 ADL 调用。
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的学习之旅有所帮助。我试图尽可能多地证实一些事情,尽管我承认我有时仍然更喜欢气。
Spirit X3这么多'stateless'是什么用意?
Spirit V2'states' 的缺点
回顾 Spirit V2,"grammar" 在很多方面都是概念上有状态的。这是因为语法是一个 class 实例。
基本上,要使您的语法(甚至任何单个规则)成为有状态的,有很多不好的方面:
- 它可能会使您的语法不可重入;
- 它可能会给您的语法实例增加线程不安全性;
- 自我管理 'flag' 是一场灾难。
从理论上讲,添加外部状态会使您的语法变得不平凡。
真的不需要状态吗?
相反,您可以说任何解析器都是有状态的(因为它解析当前上下文,而上下文就是状态)。下面是程序员添加的附加 '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。
这意味着两件事:
- X3 完全依赖于对
parse_rule()
的 ADL 调用。 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 让我失望的内容,请参阅
希望我的逆袭对你X3的学习之旅有所帮助。我试图尽可能多地证实一些事情,尽管我承认我有时仍然更喜欢气。