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

Boost X3迁移:移除identity lambda以传递属性至值

从Boost Qi迁移到X3:移除属性复制的Lambda表达式

问题描述

我正在将代码从Boost Qi迁移至Boost X3。现有一个非终结符规则expression,其对应AST结构为ast::Expression。示例中expression包含两种情况:

  • value:用于创建新的ast::Expression对象
  • '( expression )':支持括号语法,需要将内部expression的结果直接传递给父规则

在Boost Qi中,我通过%=运算符即可自动完成属性复制,但在X3中不得不使用lambda表达式e2e手动复制属性。请问如何移除这个e2e?

复现代码

#include <iostream>
#include <tuple>
#include <string>
#include <variant>
#include <vector>

#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>

namespace x3 = boost::spirit::x3;

namespace ast
{
    enum class Cmd  : int
    {
        const_val = 42
    };
    using data_t = x3::variant<int, double>;
    struct Expression
    {
        Cmd _cmd;
        std::string _args;
    };
}//ast

BOOST_FUSION_ADAPT_STRUCT(ast::Expression, _cmd, _args)

inline std::ostream& operator << (std::ostream& os, const ast::Expression& val)
{
    os << "{" << (int)val._cmd << ':' << val._args << "}";
    return os;
}

namespace client
{
    namespace x3 = boost::spirit::x3;

    x3::rule<class expression, ast::Expression> const expression("expression_stmt");
    x3::rule<class value, ast::data_t> const value("value_stmt");

    auto const value_def =
        x3::int_
        | x3::double_
        ;

    // 从常量构造expression
    auto val_to_expr = [](auto& ctx) {
        auto& a1 = _attr(ctx);
        _val(ctx) = ast::Expression{ 
            ast::Cmd::const_val, 
            std::to_string(boost::get<int>(a1)) 
        };
        };

    // 手动复制属性到结果
    auto e2e = [](auto& ctx) {
        auto& a1 = _attr(ctx);
        _val(ctx) = std::get<1>(a1);
        };

    auto const expression_def =
        value[val_to_expr]
// !!!! 为什么需要e2e来复制属性?
        | ('(' > expression > ')') [e2e]
        ;

    BOOST_SPIRIT_DEFINE(expression, value);
}//ns:client

auto parse_text(const std::string& s)
{
    namespace x3 = boost::spirit::x3;
    auto iter = s.cbegin();
    auto end_iter = s.cend();

    ast::Expression res;
    x3::ascii::space_type space;

    bool success = x3::phrase_parse(iter, end_iter,
        client::expression, space,
        res);
    if (success && iter == end_iter)
    {
        std::cout << "Success\n";
    }
    else {
        std::cout << "Parse failure\n{";
        while (iter != end_iter)
            std::cout << *iter++;
        std::cout << "}\n";
    }
    return res;
}

int main()
{
    auto test = [](const std::string& s) {
            std::cout << "parsing " << s << '\n';
            auto res = parse_text(s);
            std::cout << "'" << res << "'\n";
        };
    test("123");
    test("(123)");
}

解决方案

问题根源

在X3中,'( expression )'是三个解析器的组合:'('、expression、')',其属性是一个三元tuple(包含左括号字符、expression的属性、右括号字符),而外层expression规则的属性是ast::Expression,类型不匹配,因此无法自动赋值,必须用lambda手动提取tuple中的有效属性。

移除e2e的方法

使用x3::omit忽略括号的属性,让整个分支的属性直接等于内部expression的属性,这样X3就能自动完成属性匹配,无需手动复制:

修改expression_def部分:

auto const expression_def =
    value[val_to_expr]
    | (x3::omit['('] > expression > x3::omit[')'])
    ;

更简洁的写法

X3中,当解析器的属性被忽略时,>运算符会自动跳过无属性的解析器,因此也可以写成:

auto const expression_def =
    value[val_to_expr]
    | (x3::omit['('] >> expression >> x3::omit[')'])
    ;

修改后,括号部分不再产生额外属性,整个分支的属性与expression规则的属性完全一致,X3会自动完成属性赋值,e2e lambda就可以彻底移除了。

内容的提问来源于stack exchange,提问作者Dewfy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 07:17:02