You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 05:20:32