【问题标题】:How to print shift or reduce grammar rule? (flex,bison)如何打印移位或减少语法规则? (弯曲,野牛)
【发布时间】:2021-04-21 02:12:11
【问题描述】:

我正在尝试使用 lex 和 yacc 实现一个 C 语法分析器并展示缩减过程。 我必须在左侧打印令牌列表并在右侧移动或减少规则。 喜欢:

  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,也不知道如何打印出程序。 欢迎任何帮助。

【问题讨论】:

  • 野牛生成的解析器运行的每个动作都对应于底层解析器自动机中的“减少”,但没有简单的方法来挂钩。
  • 你确定你已经理解你的任务了吗? yacc -v 已经这样做了。

标签: parsing bison flex-lexer yacc lex


【解决方案1】:

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

我不会在这里解释如何编写语法,也不会过多谈论如何编写扫描仪。如果您还没有这样做,我建议您阅读bison manual 中的简单示例,然后阅读有关语义值的章节。这将解释下面的很多背景。

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

您可以使用-v,人们通常会告诉您这样做。但我认为--report=all 对于新手来说是值得的,因为它更接近你在课堂上看到的内容。 -v 列表仅显示每个状态的核心项目,因此它忽略了开头带有点的项目。而且它不会向您显示前瞻。由于它确实向您显示了所有动作条目,包括 GOTO 动作,您可以很容易地弄清楚其他所有内容。但是,至少在开始时,最好查看所有细节。

你可以让 bison 画出状态机。它以 Graphviz(“点”)语法生成图形,因此您需要安装 Graphviz 才能查看图形。任何重要语法的状态机都不适合 A4 纸或计算机屏幕,因此它们实际上只对玩具语法有用。如果您想尝试一下,请阅读手册以了解如何告诉 Bison 输出 Graphviz 图。

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

您可以使用 Bison 向您显示的操作手动运行状态机来编写解析操作。但是对于阅读野牛的踪迹有很多话要说。而且制作起来真的不是很困难。您只需要在调用 bison 时再添加一个命令行选项,并且您需要在语法源文件中添加几行。此处的所有信息以及更多信息都可以在bison manual chapter on grammar debugging

中找到

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

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

这确保yydebug 已在main 中定义并可使用,无论您在运行bison 时是否请求调试解析器。

但这仍然无法启用跟踪。为此,您需要多一行(至少),您可以将其放在main 的顶部:

yydebug = 1;

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

这可能与您给定(或编写)的语法非常相似,只是我为非终结符使用了更长的名称。

    /*  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, "%s\n", msg);
}

我们还需要一个词法扫描器。这是一个非常简单的:(有关您不了解的任何详细信息,请参阅flex manual。)

    /*  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。在那里你会找到野牛状态机报告。

现在我们在启用跟踪的情况下运行程序。 (&lt;&lt;&lt; 是 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 声明非常方便。但是您需要阅读手册以了解如何正确使用它们。)

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

归约后,堆栈仅包含状态 0,即 GOTO 操作所咨询的状态(在非终端 factor 上,刚刚被归约)。该操作将我们带到状态 5。再次,使用规则 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 ()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    • 2021-12-24
    相关资源
    最近更新 更多