记录我从零开始学lex & yacc的历程
从Oreily《Lex与Yacc(第二版)》开始
LEX(LEXical compiler)
YACC(Yet Another Compiler Compiler)
20世纪70年代,贝尔实验室。Stephen C.Johnson开发的yacc,为了与yacc一起工作,Mike Lest和Eric Schmidt开发了Lex。标准的UNIX实用程序。
GNU工程组发布了bison,是yacc的替代品,由Robert Corbett和Rechard Stallman编写。
BSD和GNU工程组还发布了flex,最初由Jef Poskanzer编写,Vern Paxson 和 Van Jacobson对它做了重大改善。
lex和yacc的关系
lex和yacc可以帮助你编写程序转换结构化输入。在具有结构化输入的程序中,反复出现的两个任务是:将输入分隔成有意义的单元,然后找出这些单元之间的关系。划分单元(通常成为标记)也称为词法分析(lexical analysis–>lexing)。lex使用一系列对可能标记的描述,产生一个能识别那些标记的C例程(我们称为词法分析器,词法分析程序(lexer),或称为扫描程序)。赋予lex的那些描述称为lex规范,
lex使用的标记描述称为正则表达式,它将这些正则表达式转变为词法分析程序能够用来极快的扫描输入文本的形式。
当将输入拆分成标记时,程序通常需要建立标记之间的关系,这个任务称为分析,即定义程序能够理解的关系的规则列表,也就是语法。yacc采用简明的语法描述并产生一个能分析语法的C例程,即语法分析程序。
[下载例程图片]
#flex -o ch1-01.yy.c ch1-01.l
#c -o ch1-01 ch1-01.yy.c -ll
标记特指从词法分析程序返回的符号。
lex和yacc的通信
语法分析程序(parser)是较高级别的例程。当它需要来自输入的标记时,就调用词法分析程序yylex()。然后词法分析程序从头到尾扫描输入识别标记。它一找到对语法分析程序有意义的标记就返回到语法分析程序,将返回标记的代码作为yylex()的值。
yacc可以生成包含所有标记定义的C头文件。可以把这个文件(UNIX:y.tab.h,MS_DOS:ytab.h或yytab.h)包含在词法分析程序中,并且在词法分析程序动作代码中采用这些预处理程序符号。
每次语法分析程序调用yylex()时,都在它停止的那一点进行处理。
yacc语法分析程序的结构类似于lex测法分析程序的结构(非偶然)。第一部分定义段:C注释和单个包含文件。第二部分是规则。第三部分是用户子例程。
规则段
将实际的语法描述为一套产生式规则。每条规则都由”:”操作符左侧的一个名字、右侧的符号列表和动作代码以及规则结尾的分号组成。默认情况下,第一条规则是最高级别的。规则右侧的表达式是零个或多个名字的列表。在语法中使用特殊字符”|”,它引入和前一条规则相同的左侧规则。规则的动作部分由C块组成,以”{“开始并以”}”结束。只要规则匹配,语法分析程序就在规则结尾处执行一个动作。
#flex -o ch1-06.yy.c ch1-06.l
#yacc -o ch1-06y.tab.c -d ch1-06.y
#cc -c ch1-06y.tab.c ch1-06.yy.c
#cc -o ch1-06-m.n ch1-06.yy.o ch1-06y.tab.o -ll
Lex
文件结构
- 第一部分定义段,将拷贝到最终程序中的原始C程序代码(比如头文件)。%%标记这一部分结束。
- 第二部分规则段。每个规则有两部分:模式和动作(识别模式执行相应动作),由空格分开。空格后的”|”是动作,表示下一个模式应用相同的动作.
lex简单消除歧义的规则:1. 只匹配输入字符或字符串一次;2. 执行当前输入的最长可能匹配的动作。 - 最后部分是用户子例程段,由任意合法的C代码组成。lex生成代码结束之后,lex将它复制到C文件。
lex内部变量
- yyleng,包含词法分析程序是别的字符串长度。
- yyin,标准I/O文件,默认是stdin
当yylex()到达输入文件尾端时,它调用yywrap()。如果值为1,那么程序完成而且没有输入,如果值为0,那么词法分析程序假设yywrap()已经打开了它要读取的另一个文件,而且继续读取yyin。默认总是返回1。 - yytext
分析命令行
lex程序会读取文件,使用预定义宏input()从输入中得到下一个字符,使用unput()将一个字符放回逻辑输入流。词法分析程序有时需要使用unput()在输入流中提前取字符。
input()程序处理来自词法分析程序的获取字符的调用。当前的参数用尽时,他就移到下一个参数(如果有一个的话)并继续扫描。如果没有参数,就把它作为词法分析程序的文件尾条件并返回一个零字节。
unput()程序处理来自词法分析程序的将字符”推回”输入流的调用。
起始状态
定义部分添加代码 %s FNAME
规则部分添加以””开始的规则,这些规则只在词法分析程序处于FNAME状态时被识别。没有明确状态的任何规则无论当前是什么状态都会进行匹配。
规则段的动作中的代码改变当前状态。用BEGIN语句输入一条新状态。改变为FNAME状态:”BEGIN FNAME;”,改回默认状态:”BEGIN 0”(默认地,状态零也称为INITIAL)。
yacc
语法
statement->NAME=expression
expression->NUMBER_NUMBER|NUMBER-NUMBER
“|”意味着同一个符号有两种可能性,规则左边:LHS,规则右边:RHS。是规则左边相同的情况的速记符号。
语法分析树
[下载分析树图片]
每个语法都包括起始符号,这些起始符号必须位于语法分析树的根部。
递归规则
规则可以直接或间接地引用本身
移进/归约分析
yacc语法分析程序通过寻找可以匹配目前为止所看到的标记的规则来工作。yacc处理语法分析程序时创建了一组状态,每个状态都反映一个或多个部分地被分析的规则中的一个可能的位置。当语法分析程序读取标记时,每次它读取一个没完成规则的标记,就把它压入内部堆栈中并切换到一种反映它刚刚读取的标记的新状态。这个动作称为移进(shift)。当它发现组成某条规则右侧的全部符号时,它就把右侧符号弹出堆栈,而将左侧符号压入堆栈中,并且切换到反应堆栈上新符号的新状态。这个动作称为归约(reduction)。
结构
定义段
语法中使用的标记的描述、分析程序堆栈中使用的值的类型和其他的东西。它还包括文字块、由%{ %}封闭的C代码。声明两个符号标记:
token NAME NUMBER
规则段
规则左侧和右侧之间使用”:”,并且在每个规则的尾端都有一个分号。语句可以是纯表达式,也可以是一个赋值。
符号值和动作
yacc语法分析程序中的每个符号都有一个值,该值给出了符号的特定实例的额外信息。符号可以代表一个数字,这个值就是某个数字;如果代表一个文字性的文本串,这个值可能就是一个指向该文本串拷贝的指针;如果它表示程序中的一个变量,这个值就是指向描述这个变量的符号表项的指针;有些标记没有有用的值。