我正在尝试使用Boost spirit x3将树解析器的结果存储到递归结构中。
我的语法很好,AST也很好。但是我很难理解它们是如何联系在一起的。
下面是AST:
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <optional>
namespace ast
{
struct node
{
// are the optional necessary here?
std::optional<std::vector<node>> children;
std::optional<std::string> name;
std::optional<double> length;
};
}
BOOST_FUSION_ADAPT_STRUCT(ast::node, children, name, length)
然后,这是用来合成这些节点的语法:(我添加了一些注解,说明我认为非终结符解析器正在合成的内容)。
#include <boost/spirit/home/x3.hpp>
#include "ast.h"
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/std_pair.hpp>
namespace parser
{
namespace x3 = boost::spirit::x3;
// synthesize: it should be a simple ast::node, but the grammar says otherwise???
x3::rule<class tree, ast::node> const tree{"tree"};
x3::rule<class branch> branch{"branch"};
// synthesize: std::string
auto name = x3::lexeme[x3::alpha >> *x3::alnum]; // to be improved later
// synthesize: double
auto length = ':' >> x3::double_;
// synthesize: std::optional<std::string>
auto leaf = -name;
// synthesize: std::pair< std::vector<...>, std::optional<std::string> >
auto internal = '(' >> (branch % ',') >> ')' >> -name;
// synthesized attribute: std::variant<node, std::optional<std::string>
auto subtree = internal | leaf;
// synthesize: std::pair<node, std::optional<double>>
auto const tree_def = x3::skip(x3::blank)[subtree >> -length >> ';' >> x3::eoi];
// synthesize: std::pair<node, std::optional<double>>
auto const branch_def = subtree >> -length;
BOOST_SPIRIT_DEFINE(branch, tree);
} // end namespace parser
// What is that for?
auto tree()
{
return parser::tree;
}
尽管多次尝试“修复”它,但它无法用/boost/spirit/home/x3/operator/detail/sequence.hpp:143:9: error: static_assert failed due to requirement 'actual_size <= expected_size' "Size of the passed attribute is bigger than expected."
编译
我的猜测是AST与语法不兼容(我没有看到语法中出现任何类似简单tuple<vector<node>, string, double>
的东西)。如果是这样,我应该将AST复杂化,或者找到一种简单的方法来表达语法,还是将其作为语义动作的用例?
1条答案
按热度按时间e0uiprwp1#
首先,事情很少是语义动作的用例(Boost Spirit: "Semantic actions are evil"?)¹。
其次,我的直觉告诉我,
optional
对于向量/字符串字段来说可能是不必要的。在我看来,序列容器已经是optional
的泛化(其中optional就像最大大小为1的容器)。第三,据我所知,
std::optional
支持可能有限制。* 似乎我记错了variant
支持将Boost Spirit解析器从boost::variant转换为std::variant*。传播规则
我建议你记住两件事
1.总是尽可能地将AST声明与规则声明匹配。特别是,AST不区分叶节点和内部节点,这会导致混乱,因为您希望两个规则都“神奇地”兼容。
x3::rule
就是这样做的,您可以显式地使用它。我经常使用的x3设备之一是as<T>[p]
(有时是它的功能版本:as<T>(p, name)
):请注意,您的某些(大多数?)注解行
不是很准确。例如,
alpha >> *alnum
的属性类型为fusion::deque<char, std::string> >
。请注意,传播机制在将x3::move_to
示例化到实际绑定引用时可能会消除细微的差异。事实证明确实如此,请参阅下面的简化、清理
但随着是,这涟漪落了下来:
leaf
现在不是optional<string>
了等等。因为您定义了
branch
,但没有属性类型,所以它的属性类型实际上是x3::unused_type
。这意味着subtree
也不包含vector<...>
。我必须检查,所以做了一个工具:而现在我们可以看到:
但是对于公开
unused_type
的解析器:看到我说的保持紧密控制的意思了吗?连锁React使得属性类型与你所期望的完全不同。
此外,
pair<node, optional<double>>
将不与您的任何AST类型兼容(当然,它碰巧 * 只是 *ast::node
)。展示,不要告诉别人
我知道你学得很快,但我不知道如何最好地解释我一步一步地进行的转换,我将向你展示将上述原则应用于问题代码的结果:
你可以自己期望:
要知道,它的内部机制是多么的微妙,平心而论,灵气的可预测性和包容性,比X3要高得多,我怀疑这其中的差别,可能是有利于可维护性和编译时间?
测试
从Grammar借用一些测试用例,使用Boost Spirit X3解析树失败,并调试打印解析后的AST:
打印
简化、清理
在完成所有工作后,我通常会删除不必要的规则示例化。
毫无疑问,AST与规则1:1的匹配使得所有内容都可以简单兼容,而无需强制。在您的例子中,我可能还会删除
optional<double>
,如果您感兴趣的话,让我向您展示一下:规则变成:
为了更好地衡量,让我们重新编写
checks
,以便仅验证预期的兼容性:完整列表
具有相同的输出(除了调试输出中抑制的
0.0
长度)。公平地说,在X3中,我会更高兴地考虑它,因为写它们已经变得更自然了。