我正在关注以下示例:https://github.com/boostorg/spirit/blob/develop/example/x3/calc/calc9/expression_def.hpp

我要完成的工作是编写一个规则,该规则可以像min {x} {y}那样进行解析和生成。通常,代码使用x + y之类的表达式语法,但是现在我想将两个操作数放置并解析为运算符的rhs。

我在expression_def.hpp文件中添加了以下代码:

    ...
    x3::symbols<ast::optoken> additive_op;
    x3::symbols<ast::optoken> multiplicative_op;
    x3::symbols<ast::optoken> binarypost_op;
    x3::symbols<ast::optoken> unary_op;
    x3::symbols<> keywords;
    ...

    binarypost_op.add
        ("min", ast::op_divide) // Dummy operation usage for now
        ;
    ...
    struct binarypost_expr_class;
    struct unary_expr_class;
    ...
    typedef x3::rule<binarypost_expr_class, ast::expression>
    binarypost_expr_type;
    ...
    binarypost_expr_type const binarypost_expr = "binarypost_expr";
    ...

    auto const multiplicative_expr_def =
    binarypost_expr
    >> *(multiplicative_op > binarypost_expr)
    ;
    auto const binarypost_expr_def =           // See the chaining operation
    ('{' > unary_expr > '}')
    >> *(binarypost_op > ('{' > unary_expr > '}'))
    ;
    auto const unary_expr_def =
        primary_expr
    |   (unary_op > primary_expr)
    ;

这很好。但是它只能解析类似{x} min {y}的内容。我希望能够解析最小{x} {y}。我尝试了许多组合,例如:

binarypost_op >>('{'> unary_expr>'}')>('{'> unary_expr>'}')等。但是我似乎无法弄清楚写这个的正确方法是什么?有什么建议/意见吗?

最佳答案

好的,这是更改。困难的部分实际上是代码生成内置函数。

解析中

步骤1:扩展AST

始终从AST开始。我们想要可以是函数调用的操作数:

在ast.hpp中:

struct function_call;  // ADDED LINE

// ...

struct operand :
    x3::variant<
        nil
      , unsigned int
      , variable
      , x3::forward_ast<unary>
      , x3::forward_ast<expression>
      , x3::forward_ast<function_call> // ADDED LINE
    >
{
    using base_type::base_type;
    using base_type::operator=;
};

// ...

enum funtoken
{
    fun_min,
    fun_max,
};

// ...

struct function_call : x3::position_tagged
{
    funtoken fun;
    std::list<operand> args;
};

在ast_adapted.hpp中:
BOOST_FUSION_ADAPT_STRUCT(client::ast::function_call,
    fun, args
)

步骤2:扩展语法

(全部在expression_def.hpp中)

让我们通用一些,因此使用符号表来解析函数名称标记:
x3::symbols<ast::funtoken> functions;

我们必须在add_keywords中初始化它:
functions.add
    ("min", ast::fun_min)
    ("max", ast::fun_max)
    ;

现在声明一个函数调用规则:
struct function_call_class;
typedef x3::rule<function_call_class, ast::function_call>    function_call_type;
function_call_type const function_call = "function_call";

都是红色的磁带。 “有趣的事情”是规则定义:
auto const function_call_def =
        functions
    >>  '(' >> expression % ',' >> ')'
    ;

好。真是令人难以置信。让我们整合到我们的主要表达规则中:
auto const primary_expr_def =
        uint_
    |   bool_
    |   function_call
    |   (!keywords >> identifier)
    |   ('(' > expression > ')')
    ;



另外,让AST注释对我们的节点起作用:
struct function_call_class : x3::annotate_on_success {};

代码生成

很容易找到在何处添加对新AST节点的支持:

在compile.hpp中:
 bool operator()(ast::function_call const& x) const;

现在是困难的部分。



在compile.cpp中:
bool compiler::operator()(ast::function_call const& x) const
{
    auto choice = [&](int opcode) {
        BOOST_ASSERT(x.args.size() == 2); // TODO FIXME hardcoded binary builtin
        auto it = x.args.begin();

        auto& a = *it++;
        if (!boost::apply_visitor(*this, a))
            return false;

        auto& b = *it++;
        if (!boost::apply_visitor(*this, b))
            return false;
        program.op(opcode); // the binary fold operation

        program.op(op_jump_if, 0);
        size_t const branch = program.size()-1;

        if (!boost::apply_visitor(*this, a))
            return false;
        program.op(op_jump, 0);
        std::size_t continue_ = program.size()-1;

        program[branch] = int(program.size()-branch);
        if (!boost::apply_visitor(*this, b))
            return false;

        program[continue_] = int(program.size()-continue_);
        return true;
    };

    switch (x.fun) {
        case ast::fun_min: return choice(op_lt);
        case ast::fun_max: return choice(op_gt);
        default: BOOST_ASSERT(0); return false;
    }
    return true;
}

我刚刚从周围的代码中获得了有关如何生成跳转标签的灵感。

尝试一下
  • 一个简单的例子是:var x = min(1,3);
    Assembler----------------
    
    local       x, @0
    start:
          op_stk_adj  1
          op_int      1
          op_int      3
          op_lt
          op_jump_if  13
          op_int      1
          op_jump     15
    13:
          op_int      3
    15:
          op_store    x
    end:
    -------------------------
    Results------------------
    
        x: 1
    -------------------------
    
  • 使用一些随机的人为输入运行它:
    ./test <<< "var a=$(($RANDOM % 100)); var
    

    b = $((($ RANDOM%100)); var contrived = min(max(27,2 * a),100 + b);“

    打印例如:
    Assembler----------------
    
    local       a, @0
    local       b, @1
    local       contrived, @2
    start:
          op_stk_adj  3
          op_int      31
          op_store    a
          op_int      71
          op_store    b
          op_int      27
          op_int      2
          op_load     a
          op_mul
          op_gt
          op_jump_if  24
          op_int      27
          op_jump     29
    24:
          op_int      2
          op_load     a
          op_mul
    29:
          op_int      100
          op_load     b
          op_add
          op_lt
          op_jump_if  58
          op_int      27
          op_int      2
          op_load     a
          op_mul
          op_gt
          op_jump_if  51
          op_int      27
          op_jump     56
    51:
          op_int      2
          op_load     a
          op_mul
    56:
          op_jump     63
    58:
          op_int      100
          op_load     b
          op_add
    63:
          op_store    contrived
    end:
    -------------------------
    Results------------------
    
        a: 31
        b: 71
        contrived: 62
    -------------------------
    
  • 关于c++ - 如何使用Boost Spirit x3用两个后操作数语法编写二进制运算符?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44194944/

    10-12 19:27