yacc程序怎么编译
① 编译原理入门之 lex, flex,yacc,bison等工具了解
Lex,Flex,Yacc,bison是编译原理中常用的工具,分别用于词法分析和语法分析。Lex(或Flex)生成词法分析器,将字符流转换为标记;Yacc(或bison)生成语法分析器,执行语法规则解析。使用场景主要在编译器前端阶段,分别进行词法和语法分析。工作原理分别是通过正则表达式和BNF来描述规则并生成代码。
Lex与Flex相似,后者生成的扫描器具有可重入性,适用于多线程环境。Yacc与bison等效,后者具备更多功能与优化的错误报告,同样支持多线程,通过BNF描述语法规则生成代码。
综上,Lex和Flex用于生成词法分析器,Yacc和bison用于生成语法分析器,共同构成编译器的核心部分。这些工具通过将词法或语法规则转化为C语言代码,实现源代码到目标代码的转换。
拓展内容:Lex文件通常包含三部分:定义、规则和C代码。以下是一个简单的Lex文件示例,用于将输入文本分割成单词和数字,并输出它们。将此文件保存为`lexer.l`,使用Lex工具生成词法分析器。步骤如下:编写Lex文件,使用`lex lexer.l`生成C文件`lex.yy.c`,通过C编译器编译文件`gcc lex.yy.c -o lexer`,最后运行生成的程序`./lexer`。
② Lex与YACC详解
只要你在Unix环境中写过程序,你必定会邂逅神秘的Lex&YACC,就如GNU/Linux用户所熟知的Flex&Bison,这里的Flex就是由Vern Paxon实现的一个Lex,Bison则是GNU版本的YACC。这些程序实用性极广,但如同你的C编译器一样,在其主页上并没有描述它们,也没有关于怎样使用的信息。然而,Lex和YACC可以让你轻易的解析复杂的语言,当你需要读取一个配置文件时,或者你需要编写一个你自己使用的语言的编译器时,这对于你来说是莫大的裨益。
Lex会生成一个叫做‘词法分析器’的程序。这是一个函数,它带有一个字符流传入参数,词法分析器函数看到一组字符就会去匹配一个关键字(key),采取相应措施。一个非常简单的例子如下:
词法分析器会等待输入一些数据,每次输入一些不匹配的命令(非’stop’和’start’),它会将你输入的字符再次输出。你若输入’stop’,它将输出’Stop command received’。用一个EOF(^D)来结束程序。
Lex的常规表达式是一种使用元语言的模式描述。表达式由符号组成。符号一般是字符和数字,还有一些具有特殊含义的其他标记。例如,常规表达式可以匹配“[0123456789]+”或“[a-zA-Z][a-zA-Z0-9]*”。其中,后者匹配一个变量名,必须以字母开头,可以在后续字符中用数字。
YACC可以解析输入流中的标识符(token),这就清楚的描述了YACC和LEX的关系。YACC并不知道‘输入流’为何物,它需要事先就将输入流预加工成标识符,虽然你可以自己手工写一个Tokenizer,但我们将这些工作留给LEX来做。YACC用来为编译器解析输入数据,即程序代码。这些用编程语言写成的程序代码一点也不模棱两可——它们只有一个意思。正因为如此,YACC才不会去对付那些有歧义的语法,并且会抱怨shift/rece或者rece/rece冲突。
Lex和YACC可以生成C++代码的解析器。虽然LEX和YACC的历史要早于C++,但是还是可以用它们来生成一个C++解析器。我们用LEX来生成C++的词法分析器,YACC并不知道如何直接来处理这些,所以我们不打算这么做。比较好的做法是,要做一个C++解析器,就需要LEX生成一个C文件,并且让YACC来生成C++代码。然而,在这个过程中,你会遇到一些问题,因为C++代码默认情况下并不能找到C的函数,除非你将那些函数定义为extern “C”。为解决此问题,我们在YACC代码中编写一个C开头。
在YACC文件中,你定义了你自己的main()函数,它在某个点上调用了yyparse()。YACC会创建你的yyparse()函数,并在y.tab.c中结束该函数。yyparse()函数读取一个‘标识符/值对’(token/value pairs)流,这些流需要事先就提供,这些流可以是你自己手写的代码提供的,也可以是LEX生成的。在我们的示例中,我们把这个工作丢给了LEX。LEX生成的yylex()函数从文件参数FILE *file中读取字符(文件名为yyin)。
递归是YACC一个极其重要的特性。没有递归的话,你就确定一个文件是由一系列独立的命令组成还是由语句组成。由于YACC自身的特性,它只对第一个规则或那个你将其设计为‘起始规则’的规则感兴趣。起始规则用’%start’符号标记。YACC中的递归以两种形式出现,左递归和右递归。左递归是你应该经常使用的,它们看起来如下:
在解析长的语句时,务必使用左递归,例如整个文件。但有时难以避免右递归,不过,如果你的语句并不太长,你就没有必要越轨使用左递归。正确的做法是使用左递归。
为了匹配所有丢给它的东西,避免了将那些不匹配的输入输出到标准输出的默认行为,我们可以通过字符作为速记法来作为标识符的数字ID,我们可以这样来重写我们的词法分析器。
我们需要定义yylval的类型。但是这并不一直恰如其当。我们可能会多次这样做,因为需要处理多种数据类型。例如,我们需要定义yylval为一个union,它可以存储字符串,也可以存储整数,但并不是同时存储。我们可以定义YYSTYPE为一个union,YACC中有一种简便的方法来实现,即%union语句。
我们不希望从标准输入解析,而希望解析给定的字符串。实现方法是自定义实现YY_INPUT。
YACC中有许多调试反馈信息。这些调试信息的代价有点高,所以你需要提供一些开关来打开它。当你运行那个生成的二进制文件,它将输出很多运行时信息。里面包含当前所运行的状态机以及读取到的一些标识符。你可以用Emacs中那个非常好的‘pinfo’工具阅读.info文件。
YACC解析器在内部运行的是一个‘状态机’,该状态机可以有多种转台。接着有多个规则来管制状态间的相互转化。任何内容都是从‘root’规则开始。状态机不断递减演化,直到它遇到某些它能理解的东西。