Boost Spirit X3中AST与非终结符解析器合成属性适配问题
Boost Spirit X3递归结构体解析适配问题解决
问题背景
尝试使用Boost Spirit X3将树解析结果存储到递归结构体中,已编写AST和语法,但二者无法对接,编译报错:
/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代码
#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; }
问题分析
核心问题是AST结构与解析器生成的属性结构完全不匹配:
- 你通过
BOOST_FUSION_ADAPT_STRUCT将ast::node适配成了三元组(children,name,length),要求解析器生成的属性必须能映射到这个三元组。 - 但你的解析器中,
subtree生成的是变体类型:内部节点是std::pair<std::vector<node>, std::optional<std::string>>,叶子节点是std::optional<std::string>;再和-length组合后,最终生成的是std::pair<变体类型, std::optional<double>>,完全无法和ast::node的三元组结构对应,这就是编译触发静态断言失败的原因。
解决方案
方案1:调整语法属性,直接匹配AST结构
修改语法规则,让解析器生成的属性直接对应ast::node的三元组结构,无需额外转换:
namespace parser { namespace x3 = boost::spirit::x3; // 明确branch的属性为ast::node x3::rule<class tree, ast::node> const tree{"tree"}; x3::rule<class branch, ast::node> const branch{"branch"}; auto name = x3::lexeme[x3::alpha >> *x3::alnum]; auto length = ':' >> x3::double_; // 叶子节点:补全children和length的默认空值,生成ast::node的三元组 auto leaf = x3::attr(std::nullopt) >> -name >> x3::attr(std::nullopt); // 内部节点:children是branch的vector,补全length的默认空值,生成ast::node的三元组 auto internal = '(' >> (branch % ',') >> ')' >> -name >> x3::attr(std::nullopt); // subtree直接生成ast::node auto subtree = internal | leaf; // 用-length替换默认的空length,最终生成完整的ast::node auto const tree_def = x3::skip(x3::blank)[subtree >> -length >> ';' >> x3::eoi]; auto const branch_def = subtree >> -length; BOOST_SPIRIT_DEFINE(branch, tree); }
这里通过x3::attr为AST中缺失的可选字段提供默认值,让subtree生成的属性正好匹配ast::node的三元组结构,后续的-length会覆盖默认的空值,完美适配AST。
方案2:使用语义动作手动填充AST
如果不想调整语法的属性生成逻辑,可以用语义动作将解析得到的中间属性手动映射到ast::node,适合语法与AST差异较大的场景:
namespace parser { namespace x3 = boost::spirit::x3; x3::rule<class tree, ast::node> const tree{"tree"}; x3::rule<class branch, ast::node> const branch{"branch"}; auto name = x3::lexeme[x3::alpha >> *x3::alnum]; auto length = ':' >> x3::double_; // 处理内部节点的语义动作:将pair转为ast::node auto internal_action = [](auto& ctx) { auto& [children, name] = x3::_attr(ctx); ast::node node; node.children = children; node.name = name; x3::_val(ctx) = node; }; // 处理叶子节点的语义动作:将optional<string>转为ast::node auto leaf_action = [](auto& ctx) { ast::node node; node.name = x3::_attr(ctx); x3::_val(ctx) = node; }; // 处理length的语义动作:给node添加length属性 auto add_length_action = [](auto& ctx) { auto& [node, length] = x3::_attr(ctx); ast::node result = node; result.length = length; x3::_val(ctx) = result; }; auto internal = ('(' >> (branch % ',') >> ')' >> -name)[internal_action]; auto leaf = (-name)[leaf_action]; auto subtree = internal | leaf; auto const tree_def = x3::skip(x3::blank)[(subtree >> -length)[add_length_action] >> ';' >> x3::eoi]; auto const branch_def = (subtree >> -length)[add_length_action]; BOOST_SPIRIT_DEFINE(branch, tree); }
关于AST中optional的必要性
你的AST中使用std::optional是完全合理的:
- 叶子节点没有子节点,
children设为可选 - 节点可以没有名称,
name设为可选 - 节点可以没有长度,
length设为可选
完全符合树结构的实际需求,不需要修改AST结构。
内容的提问来源于stack exchange,提问作者WaterFox
相关产品推荐
相关产品推荐

