c - 非终端符号和符号规则说明,尤其是expr是否保留?

标签 c bison yacc

 %{
 #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();
 }


有人可以帮助我理解非终结符和符号规则,例如行输入词exprexpr是保留词吗?

最佳答案

看来您是使用bison等解析器生成工具的完整入门者。尽管您的示例程序很小,但是您想对其进行扩展,但是并不能完全理解它的工作原理,也不了解您正在使用的文档中使用的一些术语。我将在以代码结尾的教程中解释这些要点,该代码使用表达式扩展了您的示例。

为了使本教程更具可读性,在您将鼠标悬停之前,以下解释被隐藏了。这样一来,任何人都只能读取所需的位,从而使页面少一堆文字墙。

野牛概述

对于使用野牛的问题,Bison Manual是很好的参考资料。首先要注意的是overall layout of bison code,如下所示:



%{



   序幕


%}



      野牛宣言


%%



      语法规则


%%



      结语




第一部分是Prologue,用于放置所生成代码的声明和导入(通常在C语言中。

第二部分是bison declarations,介绍语法中使用的终止符,并可选地指示语法的开始规则。

第三部分包含匹配规则时要采用的(无上下文)grammar rules and actions。野牛工具会将这些规则转换为输出语言(通常为C)的解析器。这通常是任何野牛文件的主体。

第四部分也是最后一部分包含epilogue。这用于放置应复制到输出文件中的所有代码。它通常包含先前使用的规则操作中所需功能的任何代码。

语法规则

了解野牛使用的语法规则涉及一些计算机科学研究。这是未学习计算机科学的程序员经常遇到的问题之一,因此有时需要进一步解释。语法规则用于定义语言中哪些符号序列有效。解析是将输入与这些规则进行匹配的过程。 Bison的工作是构建实现这些规则的解析器。规则指定在野牛声明部分中定义的称为标记的输入实体的序列。标记和其他输入符号在语法中被称为终端符号。规则被赋予名称,规则的名称被称为语法的非终结符。规则的形式为:

rule: sequence of TERMINALS and non terminals ;


按照惯例,TERMINAL名称使用大写,非终端名称则不使用。

一个例子:

declaration : ID ":" ID ;



   此规则定义了非终结符声明,以按顺序匹配令牌ID“:” 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
 %%



   这是野牛声明部分,指示我们从词法分析器导入称为WORD和EOL的标记。
 词法分析器代码在其他地方定义,因此我们不知道这些标记表示什么字符序列。
 我们可以从他们的名字中猜出来!
 %%标记声明的结尾和规则部分的开始


 input:
 /* empty */



   这定义了称为input的规则(或非终止规则)。
 该规则仅包含注释,这实际上意味着空字符串或空文件是与称为输入的规则的有效匹配。这意味着允许解析器不解析任何内容!


 |
 input line



   棒形符号|表示或,或称为input的非终端的替代规则。替代规则是input line。这与编写相同:

 input : input line


 ;



   分号;标记行的语法规则(非末尾)的结尾。
 上下文无关语法的知识将向我们表明,此规则与任意长度的可选行序列匹配。定义线的内容由后面的非终端“线”定义。


 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来构建支持我的类的简单解析器。他们可能会有所帮助。

关于c - 非终端符号和符号规则说明,尤其是expr是否保留?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30845843/

相关文章:

c - 具有多个主要功能的Makefile

c++ - 我想在 C++ 项目中包含一种脚本语言。 Lua 与 Bison/Yacc

c++ - 使用 Bison 的文件指针追加字符串

c - flex 'yyval' 未声明

c++ - 解析 C/C++ 源代码 : How are token boundaries/interactions specified in lex/yacc?

c - 为什么我的 yacc 规则不能在这里减少?

java - Apache Thrift C GLib -> Java 反序列化问题

c - 不断出现段错误(核心已转储)

c - Exec 给定字符串变量 args 并在 c 中输入 args

bison - 使用 Bison 解析树