Parsing Bison中的操作符优先级是如何工作的?

Parsing Bison中的操作符优先级是如何工作的?,parsing,bison,yacc,Parsing,Bison,Yacc,这可能是一个简单的问题,已经有人问过了,但我很难理解野牛,尤其是操作符的优先级。如果我有这个代码并且+有左关联 %left '+' %% S: S E ’\n’ { printf("%d\n", $2); } | ; E: num { $$ = $1; } | E '+' E {$$ = $1 + $3;} | E '*' E {$$ = $1 * $3;} ; %% 输入为2+3+4*5,输出为25。我的回答是45 有人能一步一步地告诉我野牛是干什么的吗?我的意思是如何将

这可能是一个简单的问题,已经有人问过了,但我很难理解野牛,尤其是操作符的优先级。如果我有这个代码并且
+
左关联

%left '+'
%%
S:
   S E ’\n’ { printf("%d\n", $2); }
|
; 

E:
   num { $$ = $1; }
| E '+' E {$$ = $1 + $3;}
| E '*'  E {$$ = $1 * $3;}
; 
%%
输入为
2+3+4*5
,输出为25。我的回答是45


有人能一步一步地告诉我野牛是干什么的吗?我的意思是如何将元素推送到堆栈中,以及如何和何时减少元素。如果可能的话,甚至可以使用解析树。

查看语法中发生了什么最简单的方法是启用bison的跟踪功能,如中所述。在读取跟踪时,将状态机放在手边很有用,因为跟踪提供了通过状态机的路径。要查看状态机,请使用bison的
-v
选项,该选项将创建一个扩展名为
.output
的文件

$ cat minimal.y
%{
#include <stdio.h>
#include <ctype.h>
int yylex(void);
void yyerror(const char* msg) {
  fprintf(stderr, "%s\n", msg);
}
%}
%token num
%left '+'
%%
S: S E '\n' { printf("%d\n", $2); }
 |
E: num { $$ = $1; }
 | E '+' E {$$ = $1 + $3;}
 | E '*'  E {$$ = $1 * $3;}
%%
int yylex(void) {
  int c;
  do c = getchar(); while (c == ' ');
  if (isdigit(c)) {
    yylval = c - '0';
    return num;
  }
  return c == EOF ? 0 : c;
}
int main(int argc, char* argv[]) {
#if YYDEBUG
  yydebug = 1;
#endif
  return yyparse();
}
以下是来自
最小输出
的状态8的定义,包括shift-reduce冲突(由不采取的操作周围的方括号表示)和默认解决方案:

State 8

    4 E: E . '+' E
    4  | E '+' E .
    5  | E . '*' E

    '*'  shift, and go to state 7

    '*'       [reduce using rule 4 (E)]
    $default  reduce using rule 4 (E)
以下是完整的跟踪(尽管我强烈建议您在自己的机器上进行实验):


查看语法中发生了什么最简单的方法是启用bison的跟踪功能,如中所述。在读取跟踪时,将状态机放在手边很有用,因为跟踪提供了通过状态机的路径。要查看状态机,请使用bison的
-v
选项,该选项将创建一个扩展名为
.output
的文件

$ cat minimal.y
%{
#include <stdio.h>
#include <ctype.h>
int yylex(void);
void yyerror(const char* msg) {
  fprintf(stderr, "%s\n", msg);
}
%}
%token num
%left '+'
%%
S: S E '\n' { printf("%d\n", $2); }
 |
E: num { $$ = $1; }
 | E '+' E {$$ = $1 + $3;}
 | E '*'  E {$$ = $1 * $3;}
%%
int yylex(void) {
  int c;
  do c = getchar(); while (c == ' ');
  if (isdigit(c)) {
    yylval = c - '0';
    return num;
  }
  return c == EOF ? 0 : c;
}
int main(int argc, char* argv[]) {
#if YYDEBUG
  yydebug = 1;
#endif
  return yyparse();
}
以下是来自
最小输出
的状态8的定义,包括shift-reduce冲突(由不采取的操作周围的方括号表示)和默认解决方案:

State 8

    4 E: E . '+' E
    4  | E '+' E .
    5  | E . '*' E

    '*'  shift, and go to state 7

    '*'       [reduce using rule 4 (E)]
    $default  reduce using rule 4 (E)
以下是完整的跟踪(尽管我强烈建议您在自己的机器上进行实验):



如前所述,该语法无法编译。您需要在第一行的
+
周围加引号;关闭结尾附近
*
的引号;您需要为
*
添加优先级规则,以避免移位减少冲突。如果您的示例未编译,则意味着存在错误,无法解释“它是如何工作的”。它不起作用,因此你的意图不够清楚。例如,缺少
*
的优先级声明意味着您的解析器有一个通过不同机制解决的移位-减少冲突;如果不详细说明这个不同的机制,就不可能解释“发生了什么”,这可能与你对这个机制的理解无关。当它达到第二个+,它确实会减少。如果它没有减少,你就不会得到(2+3)。(2+3)是减少的结果。正如您自己所说,bison/yacc的默认解决方案是更愿意改变。如果没有适用的优先级声明,则默认解决方案适用。@Mattia:可能是我的“解释”错误;与其说是更高的优先级,不如说是…默认处理。我不会尝试使用如图所示的语法。如果我看到它进行代码审查,它将在进一步审查之前返回进行修复。你很不幸,你的考官认为这样复杂的编码对考试有好处。当代码可以被清晰地写出来时,需要知道语言最黑暗的角落是令人讨厌的,因为写出来的语法无法编译。您需要在第一行的
+
周围加引号;关闭结尾附近
*
的引号;您需要为
*
添加优先级规则,以避免移位减少冲突。如果您的示例未编译,则意味着存在错误,无法解释“它是如何工作的”。它不起作用,因此你的意图不够清楚。例如,缺少
*
的优先级声明意味着您的解析器有一个通过不同机制解决的移位-减少冲突;如果不详细说明这个不同的机制,就不可能解释“发生了什么”,这可能与你对这个机制的理解无关。当它达到第二个+,它确实会减少。如果它没有减少,你就不会得到(2+3)。(2+3)是减少的结果。正如您自己所说,bison/yacc的默认解决方案是更愿意改变。如果没有适用的优先级声明,则默认解决方案适用。@Mattia:可能是我的“解释”错误;与其说是更高的优先级,不如说是…默认处理。我不会尝试使用如图所示的语法。如果我看到它进行代码审查,它将在进一步审查之前返回进行修复。你很不幸,你的考官认为这样复杂的编码对考试有好处。当代码可以写得很清楚的时候,需要知道语言最黑暗的角落是很糟糕的。谢谢!这些步骤将有所帮助。如何打印状态?我如何解释圆点?这意味着它已经读到了这一点,所以它不应该只是E<代码>*
E因为E
+
E被缩减为E?明天我会测试一下,看看效果如何goes@Mattia:如我所说,您可以使用
-v
选项生成
.output
文件,然后使用文本编辑器查看它。任何描述LR解析的教科书都会解释dot。查找“LR items”。语法是S->se,E->num,E->E+E,E->E*E吗?如果我构造LALR(1),它的状态是否与yacc创建的状态相同?我总是看到S->E而不是S->SE。最后是E,后面跟着$EOF,我不明白它如何匹配规则S->SE来减少它的开始symbol@mattia:
S
有两个产品:
S->se
S->
。第二个允许递归终止。Yacc/bison通过添加一个开始符号
$start->S$endStarting parse
Entering state 0
Reducing stack by rule 2 (line 14):
-> $$ = nterm S ()
Stack now 0
Entering state 1
Reading a token: Next token is token num ()
Shifting token num ()
Entering state 3
Reducing stack by rule 3 (line 16):
   $1 = token num ()
-> $$ = nterm E ()
Stack now 0 1
Entering state 4
Reading a token: Next token is token '+' ()
Shifting token '+' ()
Entering state 5
Reading a token: Next token is token num ()
Shifting token num ()
Entering state 3
Reducing stack by rule 3 (line 16):
   $1 = token num ()
-> $$ = nterm E ()
Stack now 0 1 4 5
Entering state 8
Reading a token: Next token is token '+' ()
Reducing stack by rule 4 (line 17):
   $1 = nterm E ()
   $2 = token '+' ()
   $3 = nterm E ()
-> $$ = nterm E ()
Stack now 0 1
Entering state 4
Next token is token '+' ()
Shifting token '+' ()
Entering state 5
Reading a token: Next token is token num ()
Shifting token num ()
Entering state 3
Reducing stack by rule 3 (line 16):
   $1 = token num ()
-> $$ = nterm E ()
Stack now 0 1 4 5
Entering state 8
Reading a token: Next token is token '*' ()
Shifting token '*' ()
Entering state 7
Reading a token: Next token is token num ()
Shifting token num ()
Entering state 3
Reducing stack by rule 3 (line 16):
   $1 = token num ()
-> $$ = nterm E ()
Stack now 0 1 4 5 8 7
Entering state 9
Reading a token: Next token is token '\n' ()
Reducing stack by rule 5 (line 18):
   $1 = nterm E ()
   $2 = token '*' ()
   $3 = nterm E ()
-> $$ = nterm E ()
Stack now 0 1 4 5
Entering state 8
Next token is token '\n' ()
Reducing stack by rule 4 (line 17):
   $1 = nterm E ()
   $2 = token '+' ()
   $3 = nterm E ()
-> $$ = nterm E ()
Stack now 0 1
Entering state 4
Next token is token '\n' ()
Shifting token '\n' ()
Entering state 6
Reducing stack by rule 1 (line 13):
   $1 = nterm S ()
   $2 = nterm E ()
   $3 = token '\n' ()
25
-> $$ = nterm S ()
Stack now 0
Entering state 1
Reading a token: Now at end of input.
Shifting token $end ()
Entering state 2
Stack now 0 1 2
Cleanup: popping token $end ()
Cleanup: popping nterm S ()