%{
#include <stdio.h>
#include <stdlib.h>
void yyerror(const char*);
%}
%token WORD
%token EOL
%%
input:
/* empty */
|
input line
;
line:
EOL { exit(1); }
| words EOL { printf("correct!\n"); }
;
words:
words WORD
| WORD
;
%%
void yyerror(const char *str)
{
fprintf(stderr," error: %s\n",str);
}
main()
{
yyparse();
}
有人可以帮我理解非终端符号和符号规则,例如行输入字expr
。是expr
一个保留字吗?
答案 0 :(得分:1)
看起来你是一个使用像野牛这样的解析器生成工具的完全初学者。虽然您有一个小型工作示例程序,但您希望对其进行扩展,但不完全了解它的工作原理或理解您正在使用的文档中使用的某些术语。我将在一个以代码扩展的教程中解释这些要点,这些代码用表达式扩展了你的例子。
为了使教程更具可读性,在您使用鼠标悬停之前,会隐藏一些解释。这使得任何人都只能阅读他们需要的位并使页面不再是文本墙。
野牛概述
关于使用野牛的问题,Bison Manual是一个很好的参考资料来源。首先要注意的是overall layout of bison code,它是这样的:
%{
序言
%}
野牛宣言
%%
语法规则
%%
结语
第一部分是Prologue,用于为生成的代码设置声明和导入(通常在 C 。
第二部分是bison declarations,它引入语法中使用的终端符号,并可选择指示语法的开始规则。
第三部分包含匹配规则时要采取的(无上下文)grammar rules and actions。这些规则通过bison工具转换为输出语言的解析器(通常为 C )。这通常是任何野牛档案的主体。
第四节和最后一节包含epilogue。这用于放置应复制到输出文件中的任何代码。它通常包含前面使用的规则操作所需的任何函数代码。
语法规则
了解野牛使用的语法规则涉及一些计算机科学研究。通常,没有学过计算机科学的程序员有困难,因此有时需要进一步解释。语法规则用于定义符号的哪些序列在语言中有效。解析是将输入与这些规则匹配的过程。 Bison的工作是构建一个实现这些规则的解析器。规则指定输入实体的序列,称为令牌,它们在野牛声明部分中定义。令牌和其他输入符号在语法中称为 terminal 符号。规则被赋予名称,规则的名称称为语法的非终端。规则的形式为:
rule: sequence of TERMINALS and non terminals ;
通过约定 TERMINAL名称大写,非终端名称不大写。
一个例子:
declaration : ID ":" ID ;
此规则定义非终端声明以匹配令牌ID序列,&#34;:&#34;,ID按该顺序排列。请注意,我们可以在该规则中包含字符串和标记名称。
这些规则可以添加操作,它们定义了规则匹配发生时要执行的代码。操作会放在每个规则末尾的{ ... }
对中,如下所示:
declaration : ID ":" ID { printf("Matched a declaration\n"); }
;
对于高级用法,我们还可以从令牌中提取信息以包含在我们的操作中:
declaration : ID ":" ID { printf("%s declared of type %s\n",getSymbol($1),getSymbol($3)); }
;
现在我们已经介绍了什么是终端和非终端符号的基础知识,让我们使用它来解码您使用的示例程序。
带注释的示例
%{
%{
是'%{...}%对的开头,它标记要复制的代码 进入野牛生成的C代码的序言。它允许声明 和要插入的导入。
#include <stdio.h>
#include <stdlib.h>
void yyerror(const char*);
我们导入标准C库,以便我们可以进行打印。我们声明我们使用yyerror
%}
结束了序言
%}
%token WORD
%token EOL
%%
这是bison声明部分,表示我们从词法分析器导入名为WORD和EOL的标记。 词法分析器代码在别处定义,因此我们不知道这些令牌代表什么字符序列。 我们可以从他们的名字中猜出来!
%%
标记声明的结束和规则部分的开始
input:
/* empty */
这定义了一个名为
input
的规则(或非终端) 该规则仅包含注释,实际上意味着空字符串或空文件是称为输入的规则的有效匹配。这意味着解析器可以解析任何东西!
|
input line
标记符号
|
表示或或非终端名称input
的替代规则。替代规则是input line
。这与写作相同:
input : input line
;
分号
;
标记行的语法规则(非终端)的结束。 对上下文无关语法的了解将向我们显示该规则匹配任意长度的可选序列。定义一条线的定义由非终端线'#39;定义。接下来。
line:
EOL { exit(1); }
| words EOL { printf("correct!\n"); }
;
这将非终端行定义为无限的行序列,每行可选地包含
words
并由令牌/终端EOL
终止。匹配一行时,将打印字符串correct
。不包含单词的行(空)将结束解析,解析器将退出。
words:
words WORD
| WORD
;
这定义了非终端被称为单词,它是令牌/终端的无限序列,称为WORD。
%%
这标志着野牛规则部分的结束。此点之后的所有代码都被bison复制到解析器代码文件中。它包含解析器(
yyerror
)调用的错误函数和调用解析器的主程序。
void yyerror(const char *str)
{
fprintf(stderr," error: %s\n",str);
}
main()
{
yyparse();
}
扩展解析器
你问过expr
期待它是我怀疑的野牛的某些命令或内置功能。目前尚不清楚你在问什么,因为你从未澄清过这一点。然而,根据我的经验,许多学生在一些课堂笔记中被给予一个简单的解析器,如你所说的那个,并通过练习要求扩展它以识别包含算术表达式的行。这就是我要假设的,并解释。
让我们想象一下,我们希望改变(或扩展)解析器以匹配简单的表达式,如WORD + WORD而不是单词行。
我们可以像这样重新定义规则line
:
line:
EOL { exit(1); }
| expr EOL { printf("correct!\n"); }
;
和规则expr
是这样的:
expr :
WORD '+' expr
| WORD
;
如果我们的词法分析器匹配NUMBER而不是WORD,我们甚至可以将它变成一个简单的计算器。 (词法分析器在别处定义)。 我们现在可以做以下事情:
line:
EOL { exit(1); }
| expr EOL { printf("expression value is = %d\n",$1); }
;
expr :
NUMBER '+' expr { $$ = $1 + $3 ; }
| NUMBER {$$ = $1 )
;
进一步阅读
如果你想更进一步,我有几个小时(不感兴趣)YouTube video tutorials来构建支持我的类的简单解析器。他们可能会有帮助。