如何打印shift或reduce语法规则?(弯曲,野牛)



我正在尝试用lex&并展示了还原过程。我必须在左边打印令牌列表,在右边打印shift或reduce规则。类似:

2*4+4/2 //iniput

2                shift 2                 2
2                reduce I -> F           2
2                reduce F -> T           2
2                reduce F -> T           2
*                shift *                 2  *
4                shift 4                 2  * 4
4                reduce I -> F           2  * 4
2 * 4            reduce T*F -> T         2  * 4
8                reduce T -> E           2  * 4
8                reduce T -> E           2  * 4
+                shift +                 2  * 4  +
4                shift 4                 2  * 4  + 4
4                reduce I -> F           2  * 4  + 4
4                reduce F -> T           2  * 4  + 4
4                reduce F -> T           2  * 4  + 4
/                shift /                 2  * 4  + 4  /
2                shift 2                 2  * 4  + 4  / 2
2                reduce I -> F           2  * 4  + 4  / 2
4 / 2            reduce T/F -> T         2  * 4  + 4  / 2
8 + 2            reduce E+T -> E         2  * 4  + 4  / 2

end of parsing : 2  * 4  + 4  / 2  = 10

我对lex&yacc,我不知道如何打印出程序。欢迎任何帮助。

你可以很容易地让Bison向你展示它在做什么。但它不会看起来像你的图表。您必须通读跟踪并将其压缩为所需的格式。但这并不太难,当你第一次调试语法时,你会很感激学会了如何做到这一点。

我不会在这里解释如何编写语法,也不会谈论太多关于编写扫描仪的内容。如果你还没有这样做,我建议你通读野牛手册中的简单例子,然后是关于语义值的章节。这将解释以下内容的许多背景。

Bison有一些非常有用的工具来可视化语法和解析。第一个是当您为bison提供--report=all命令行选项时生成的状态/转换表。

你可以使用-v,这是人们通常会告诉你的。但我认为--report=all对新手来说是值得的,因为它更接近你在课堂上看到的内容。-v列表只显示每个状态下的核心项,因此省略了开头带点的项。它不会显示你的长相。由于它确实向您显示了所有的操作条目,包括GOTO操作,因此您可以很容易地了解其他所有内容。但是,至少在一开始,最好能看到所有的细节。

你可以让野牛绘制状态机。它以Graphviz("Dot")语法生成图形,因此您需要安装Graphvi兹来查看图形。任何非琐碎语法的状态机都不适合放在A4纸或电脑屏幕上,所以它们实际上只对玩具语法有用。如果你想尝试一下,请阅读手册,了解如何告诉Bison输出Graphviz图。

当您试图理解跟踪时,您可能需要参考状态机。

您可以通过手动运行状态机,使用Bison向您展示的操作来编写解析操作。但是阅读野牛的踪迹还有很多话要说。而且生产起来真的不是很难。您只需要在调用bison时再添加一个命令行选项,并且需要在语法源文件中添加几行。这里的所有信息,以及更多信息,都可以在bison手册中关于语法调试的章节中找到

选项为-t--debug。这告诉Bison生成额外的代码来生成跟踪。但是,它不支持跟踪;您仍然需要将全局变量yydebug的值设置为1(或其他非零值)。不幸的是,除非指定了--debug选项,否则不会定义变量yydebug,因此,如果只将yydebug = 1;添加到main()中,则除非使用--debug选项运行bison,否则程序将不再编译。这很烦人,所以值得在代码中再添加几行。最简单的几行是:(可以略高于main的定义):

#if YYDEBUG
extern int yydebug;
#else
static int yydebug = 0;
#endif

这确保了yydebugmain中被定义和可用,无论您在运行bison时是否请求了调试解析器。

但这仍然无法实现跟踪。要做到这一点,你需要(至少)再加一行,你可以把它放在main:的顶部

yydebug = 1;

或者,您可以稍微复杂一点,通过检查命令行参数,可以运行带有或不带有跟踪的解析器。解析命令行参数的一个好方法是使用getopt,但对于只有一个命令行参数、快速且脏的可执行文件,您可以使用下面的示例代码,该代码仅在以-d作为其第一个命令行自变量调用可执行文件时设置yydebug

这可能与你得到的(或写的)语法非常相似,只是我对非终端使用了更长的名称。

/*  FILE: simple_expr.l   */
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int yylex(void);
void yyerror(const char* msg);
%}
%token NUMBER
%printer { fprintf(yyo, "%d", $$); } NUMBER
%%
expr  : term
| expr '+' term
| expr '-' term
term  : factor
| term '*' factor
| term '/' factor
factor: NUMBER
| '(' expr ')'
%%
#if YYDEBUG
extern int yydebug;
#else
static int yydebug = 0;
#endif
int main(int argc, char* argv[]) {
if (argc > 1 && strcmp(argv[1], "-d") == 0) yydebug = 1;
return yyparse();
}
void yyerror(const char* msg) {
fprintf(stderr, "%sn", msg);
}

我们还需要一个词汇扫描仪。这里有一个非常简单的例子:(任何你不理解的细节,请参阅flex手册。)

/*  FILE: simple_expr.l   */
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "simple_expr.tab.h"
%}
%option noinput nounput noyywrap nodefault
%%
[[:space:]]+        ;
[[:digit:]]+        { yylval = atoi(yytext); return NUMBER; }
.                   return yytext[0];

编译(Makefile在这里会很有用。或者你用来构建项目的任何东西):

$ bison -o simple_expr.tab.c -d --debug --report=all simple_expr.y
$ flex -o simple_expr.lex.c simple_expr.l
$ gcc -Wall -o simple_expr simple_expr.tab.c simple_expr.lex.c

在这一点上,您应该看看simple_expr.output。在那里你可以找到野牛状态机的报告。

现在我们在启用了跟踪的情况下运行程序。(<<<就是Bash所说的"此处字符串"。它接受一个参数,并将其作为标准输入提供给可执行文件。这对于调试解析器来说非常方便。)

跟踪相当长,因为正如我所说,Bison没有试图压缩信息。它是如何开始的:

$ ./simple_expr -d <<< '2 * 3 + 12 / 4'
Starting parse
Entering state 0
Reading a token: Next token is token NUMBER (2)
Shifting token NUMBER (2)
Entering state 1
Reducing stack by rule 7 (line 22):
$1 = token NUMBER (2)
-> $$ = nterm factor ()
Stack now 0
Entering state 5
Reducing stack by rule 4 (line 19):
$1 = nterm factor ()
-> $$ = nterm term ()
Stack now 0
Entering state 4

因此,它首先移位令牌2(它是NUMBER)。(注意:我在语法文件中偷偷添加了一个%printer声明,这样bison就可以打印出NUMBER令牌的语义值。如果我没有这样做,它只会告诉我它读取了一个NUMBER,让我猜测它读取了哪个NUMBER。所以%printer声明真的很方便。但你需要阅读手册,看看如何正确使用它们。)

换档操作使其进入状态1。当默认减少不依赖于前瞻性时,Bison会立即进行减少,因此解析器现在使用规则factor: NUMBER立即减少堆栈。(您需要状态机或带有行号的代码列表来查看"规则7"是什么。这也是我们生成报告的原因之一。)

在缩减之后,堆栈仅包含状态0,这是GOTO操作(在非终端factor上,它刚刚被缩减)所咨询的状态。这一行动使我们进入第五状态。同样,使用规则4(term: factor)可以立即减少。在减少之后,堆栈再次减少到刚刚开始的状态,GOTO操作将我们带到状态4。在这一点上,另一个象征实际上是必要的。您可以阅读下面的跟踪的其余部分;希望你能看到发生了什么。

Reading a token: Next token is token '*' ()
Shifting token '*' ()
Entering state 10
Reading a token: Next token is token NUMBER (3)
Shifting token NUMBER (3)
Entering state 1
Reducing stack by rule 7 (line 22):
$1 = token NUMBER (3)
-> $$ = nterm factor ()
Stack now 0 4 10
Entering state 15
Reducing stack by rule 5 (line 20):
$1 = nterm term ()
$2 = token '*' ()
$3 = nterm factor ()
-> $$ = nterm term ()
Stack now 0
Entering state 4
Reading a token: Next token is token '+' ()
Reducing stack by rule 1 (line 16):
$1 = nterm term ()
-> $$ = nterm expr ()
Stack now 0
Entering state 3
Next token is token '+' ()
Shifting token '+' ()
Entering state 8
Reading a token: Next token is token NUMBER (12)
Shifting token NUMBER (12)
Entering state 1
Reducing stack by rule 7 (line 22):
$1 = token NUMBER (12)
-> $$ = nterm factor ()
Stack now 0 3 8
Entering state 5
Reducing stack by rule 4 (line 19):
$1 = nterm factor ()
-> $$ = nterm term ()
Stack now 0 3 8
Entering state 13
Reading a token: Next token is token '/' ()
Shifting token '/' ()
Entering state 11
Reading a token: Next token is token NUMBER (4)
Shifting token NUMBER (4)
Entering state 1
Reducing stack by rule 7 (line 22):
$1 = token NUMBER (4)
-> $$ = nterm factor ()
Stack now 0 3 8 13 11
Entering state 16
Reducing stack by rule 6 (line 21):
$1 = nterm term ()
$2 = token '/' ()
$3 = nterm factor ()
-> $$ = nterm term ()
Stack now 0 3 8
Entering state 13
Reading a token: Now at end of input.
Reducing stack by rule 2 (line 17):
$1 = nterm expr ()
$2 = token '+' ()
$3 = nterm term ()
-> $$ = nterm expr ()
Stack now 0
Entering state 3
Now at end of input.
Shifting token $end ()
Entering state 7
Stack now 0 3 7
Cleanup: popping token $end ()
Cleanup: popping nterm expr ()

最新更新