如何使用中间规则操作在野牛中创建短路评估

How to create a short circuit evaluation in bison using mid-rules action?

本文关键字:创建 短路 评估 何使用 中间 规则 操作      更新时间:2023-10-16

我被指派编写一个计算器,该计算器还支持变量定义和赋值,布尔表达式和递减/递增运算。
我的计算器还应该支持条件(三元)运算? :,与某些编程语言支持的相同,其用法如下:

condition ? value_if_true : value_if_false ;

或者正如我的语法规则中所定义的那样:

bool_expression ? int_expression : int_expression ;

问题是,它必须是短路评估,这意味着如果bool_expression的计算结果为true,则只应评估第一个int_expression,否则,如果bool_expression的计算结果为false,则只应评估第二个int_expression

我被告知在中间规则功能中使用野牛的动作,但我看不出这有什么帮助。

我还在网上环顾四周,发现这是最接近我想要完成的事情,但不幸的是,那里的答案并不完全是我想要的。那里的答案确实描述了应该做什么,但没有具体说明如何做(全局标志是不可能的,当你看到代码时,你会明白为什么)。

这是我到目前为止所拥有的一小部分:

%{
    #include<stdio.h>
    #include<iostream>
    #include <string>
    #include <map>
    using namespace std;
    map<string, int> symbolTable;
    int yylex();
    void yyerror(const char*);
%}
%union{
    int     int_val;
    char*   string_val;
    bool b;
}

%token <int_val> T_NUMBER
%token <string_val> T_VAR
%type  <int_val> expr
%type  <b>bool_expression
%nonassoc ':'
%nonassoc T_EQUALS
%left '+' '-'
%left '*' '/'
%left UMINUS
%left '$' '~'

%%
commands:   
        /* eps rule */      {cout<<"create commands"<<endl;}
        | commands command  {cout<<"add command"<<endl;};

command:    expr 'n'       {cout<<"Expression value: "<<$1<<endl;}
    |   T_VAR '=' expr 'n' {cout<<"Assignment: "<<$1<<"="<<$3<<endl; symbolTable[$1] = $3;}
    |   bool_expression 'n'    {$1 ? cout<<"true"<<endl : cout<<"false"<<endl;}
    ;
expr:
        T_NUMBER        {$$ = $1;}
    |   T_VAR           {$$ = symbolTable[$1];}
    |   expr '+' expr       {$$ = $1 + $3;}
    |   expr '-' expr       {$$ = $1 - $3;}
    |   expr '*' expr       {$$ = $1 * $3;}
    |   expr '/' expr       {if ($3 == 0) {yyerror ("Division by zero!"); return 1;} $$ = $1 / $3;}
    |   '$' T_VAR       {$$ = ++symbolTable[$2]; }
    |   '~' T_VAR       {$$ = --symbolTable[$2]; }
    |   '-' expr %prec UMINUS   {$$=-$2;}
    |   bool_expression '?'     {/* this is a mid-rule; what should go here... */}
        expr ':' expr       {/* in order to evaluates only one of these two, depending on bool_expression */}
    ;
bool_expression:  expr T_EQUALS expr        {$$=($1==$3);}
%%  
void yyerror(const char* errorInfo){
    cout<<errorInfo<<endl;
}
int main(){
    yyparse();
}

您在那里看到的"$"运算符与 C 中的运算符 ++ 具有相同的含义,而"~"运算符与 --.

回到我的问题:以上只是相关的代码,为了可读性,我省略了更多规则。这也意味着在这里使用一些全局标志不是我的最佳选择,因为它需要在我的文件中的每个规则中检查它。现在,考虑输入:

a=5
1==1 ?$a : $a

将上述规则定义为:

|   bool_expression '?' expr ':' expr {$1 ? $$=$3 : $$=$5 ;}

此代码导致值为 7 而不是 6 的"a"。
那么,如何在这里使用"中间规则中的操作"来防止第一个 expr 在bool_expression为假时被移动/减少,并在bool_expression为真时防止第二个 expr 被移动/减少?

你需要一个全局标志eval,在解析应评估的代码时设置(true)。 虽然它是错误的,但解析器应该跳过事情,而不是实际评估任何东西。 因此,您的分配规则变为:

command: T_VAR '=' expr 'n' {
    if (eval) {
        cout << "Assignment: " << $1 << "=" << $3 << endl;
        symbolTable[$1] = $3; } }
同样,对于

可能具有副作用的所有其他规则(对于没有副作用的规则,您可以无条件运行并生成将被忽略的值,也可以类似地保护它。

然后,您的条件规则将变为:

expr: expr { if ($$ = eval) eval = $1; } '?'
      expr { if ($2) eval = !eval; } ':'
      expr { if (eval = $2) $$ = $1 ? $4 : $7; }

这会将 eval 标志保存在第一个内联操作的结果中,在最后一个操作中还原它,如果已设置,则为应计算的表达式而不是另一个表达式设置它。

这样做的一个大问题是错误恢复变得更加困难,因为您需要弄清楚何时/何地恢复eval标志。

编辑

有了你所拥有的东西,$~实际上什么都不做。 如果您希望它们等效于 ++-- ,则需要:

expr: `$` T_VAR { $$ = eval ? symbolTable[$2]++ : 0; }
    | `~` T_VAR { $$ = eval ? symbolTable[$2]-- : 0; }