编译器的自动生成工具LEX和YACC的使用方法一、 词法分析程序产生器LEX的用法1.1 Lex概述Lex是一个词法分析器(扫描器)的自动产生系统,它的示意图如图1.1Lex源程序是用一种面向问题的语言写成的这个语言的核心是正规表达式,用它描述输入串的词法结构在这个语言中用户还可以描述当某一个词形被识别出来时要完成的动作,例如在高级语言的词法分析器中,当识别出一个关键字时,它应该向语法分析器返回该关键字的内部编码Lex并不是一个完整的语言,它只是某种高级语言(称为lex的宿主语言)的扩充,因此lex没有为描述动作设计新的语言,而是借助其宿主语言来描述动作我们只介绍C作为lex的宿主语言时的使用方法,在Unix系统中, FORTRAN语言的一种改进形式Ratfor也可以做lex的宿主语言图1.1 Lex示意图 Lex自动地表示把输入串词法结构的正规式及相应的动作转换成一个宿主语言的程序,即词法分析程序,它有一个固定的名字yylex,在这里yylex是一个C语言的程序yylex将识别出输入串中的词形,并且在识别出某词形时完成指定的动作 看一个简单的例子:写一个lex源程序,将输入串中的小写字母转换成相应的大写字母。
程序如下:%%[a-z] printf(“%c”,yytext[0]+'A'-'a');上述程序中的第一行%%是一个分界符,表示识别规则的开始第二行就是识别规则左边是识别小写字母的正规式右边就是识别出小写字母时采取的动作:将小写字母转换成相应的大写字母 Lex的工作原理是将源程序中的正规式转换成相应的确定有限自动机,而相应的动作则插入到yylex中适当的地方,控制流由该确定有限自动机的解释器掌握,对于不同的源程序,这个解释器是相同的1.2 lex源程序的格式lex源程序的一般格式是:{辅助定义的部分} %% {识别规则部分}%% {用户子程序部分}其中用花括号起来的各部分都不是必须有的当没有“用户子程序部分”时,第二个%%也可以省去第一个%%是必须的,因为它标志着识别规则部分的开始,最短的合法的lex源程序是: %% 它的作用是将输入串照原样抄到输出文件中 识别规则部分是Lex源程序的核心它是一张表,左边一列是正规式,右边一列是相应的动作下面是一条典型的识别规则:integer printf("found keywcrd INT");这条规则的意思是在输入串中寻找词形“integer”,每当与之匹配成功时,就打印出 “found keyword INT”这句话。
注意在识别规则中,正规式与动作之间必须用空格分隔开动作部分如果只是一个简单的C表达式,则可以写在正规式右边同一行中,如果动作需要占两行以上,则须用花括号括起来,否则会出错上例也可以写成:integer {printf("found keyword INT");} 下面先介绍识别规则部分的写法,再介绍其余部分1.3 Lex用的正规式一个正规式表示一个字符串的集合正规式由正文字符与正规式运算符组成正文字符组成基本的正规式,表示某一个符号串;正规式运算符则将基本的正规式组合成为复杂的正规式,表示字符串的集合例如:ab仅表示字符串ab,而(a b)+表示字符串的集合:{ab,abab,ababab,…}Lex中的正规式运算符有下列十六种: “ \ [ ] ^ - ? • * + | () / $ { } % < > 上述运算符需要作为正文字符出现在正规式中时,必须借助于双引号”或反斜线\,具体用法是;xyz“++”或xyz\+\+表示字符串xyz++为避免死记上述十多个运算符,建议在使用非数字或字母字符时都用双引号或反斜线要表示双引号本身可用\”,要表示反外线用”\”或\\前面说过,在识别规则中空格表示正规式的结束,因此要在正规式中引进空格必须借助双引号或反斜线,但出现在方括号[]之内的空格是例外。
几个特殊符号:\n是回车换行(newline)\t是tab \b是退格(back space)下面按照运算符的功能分别介绍上述正规式运算符1. 字符的集合用方括号对可以表示字符的集合正规式 [a b c]与单个字符a或b或c匹配在方括号中大多数的运算符都不起作用,只有\、- 和 ^ 例外运算符 - 表示字符的范围,例如[a-z 0-9 <>_ ]表示由所有小写字母,所有数字、尖括号及下划线组成的字符集合如果某字符集合中包括-在内,则必须把它写在第一个或最后一个位置上,如[-+0-9]与所有数字和正负号匹配在字符集合中,运算符^必须写在第一个位置即紧接在左方括号之后,它的作用是求方括号中除^之外的字符组成的字符集合相对于计算机的字符集的补集,例如[^abc]与除去a、b和c以外的任何符号匹配运算符\在方括号中同样发挥解除运算符作用的功能2. 与任意字符匹配的正规式 运算符•形成的正规式与除回车换行符以外的任意字符匹配在lex的正规式中,也可以用八进制数字与\一起表示字符,如[\40-\176]与ASCII字符集中所有在八进制 40(空格)到八进制176(~)之间的可打印字符匹配3. 可有可无的表达式运算得?指出正规式中可有可无的子式,例如ab?c与ac或abc匹配,即b是可有可无的。
4. 闭包运算运算符*和十是 Lex正规式中的闭包运算符,它们表示正规式中某子式的重复,例如a*表示由0个或多个a组成的字符串的集合,而a+表示由1个或多个a组成的字符串的集合,下面两个正规式是常用的:[a-z]+[A-Za-z][A-Za-z 0-9]*第一个是所有由小写字母组成的字符串的集合,第二个是由字母开头的字母数字串组成的集合5、选择和字符组运算符|表示选择:(ab|cd)与ab或cd匹配运算符()表示一组字符,注意()与[ ]的区别ab)表示字符串ab,而[ab]则表示单个字符a或b圆括号()用于表示复杂的正规式,例如:(ab|cd+)?(ef)*与abefef, efef, cdef, cddd匹配,但不与abc, abcd或abcdef匹配6、上下文相关性lex可以识别一定范围的上下文,因此可在一定程度上表示上下文相关性 若某正规式的第一个字符是∧,则仅当该正规式出现在一行的开始处时才被匹配,一行的开始处是指整个输入串的开始或者紧接在一个回车换行之后,注意∧还有另一个作用即求补,∧的这两种用法不可能发生矛盾 若某正规式的最后一个字符是$,则仅当该表达式出现在一行的结尾处时才被匹配,一行的结尾处是指该表达式之后紧接一个回车换行。
运算符/指出某正规式是否被匹配取决于它的后文,例如ab/cd仅在ab之后紧接cd的情况下才与ab匹配其实是/的一个特殊情形,例如下面两个正规式等价:ab$,ab/\ n 某正规式是否被匹配,或者匹配后执行什么样的动作也可能取决于该表达式的前文,前文相关性的处理方法在后面专门讨论,将用到运算符<> 7、重复和辅助定义当被{ }括起来的是数字对时,{ }表示重复;当它括起来的是一个名字时,则表示辅助定义的展开例如:a{1,5}表示集合{a.aa.aaa.aaaa.aaaaa}{digit}则与预先定义的名叫dight的串匹配,并将有定义插入到它在正规式中出现的位置上,辅助定义在后面专门讨论最后,符号%的作用是作为lex源程序的段间分隔符1.4 Lex源程序中的动作 前面说过当Lex识别出一个词形时,要完成相应的动作这一节叙述Lex为描述动作提供的帮助首先应指出,输入串中那些不与任何识别规则中的正规式匹配的字符串将被原样照抄到输出文件中去因此如果用户不仅仅是希望照抄输出,就必须为每一个可能的词形提供识别规则,并在其中提供相应的动作用lex为工具写程序语言的词法分析器时尤其要注意最简单的一种动作是滤掉输入中的某些字符串,这种动作用C的空语句“;”来实现。
例:滤掉输入串中所有空格、tab和回车换行符,相应的识别规则如下:[\t\n];如果相邻的几条规则的动作都相同,则可以用|表示动作部分,它指出该规则的动作与下一条规则的动作相同例如上倒也可以写成:“ ”| “\t”|“\n”;注意\t和\n中的双引号可以去掉外部字符数组yytext的内容是当前被某规则匹配的字符串,例如正规式[a-z]+与所有由小写字母组成的字符串匹配,要想知道与它匹配的具体字符串是什么,可用下述规则:[a-z]+ printf(“% s”, yytext);动作printf(“%s”,yytext)就是将字符数组yytext的内容打印出来,这个动作用得很频繁,Lex提供了一个宏ECHO来表示它,因此上述识别规则可以写成:[a-z]+ ECHO;请注意,上面说过缺省的动作就是将输入串原样抄到输出文件中,那么上述规则起什么作用呢?这一点将在“规则的二义性”一节中解释有时有必要知道被匹配的字符串中的字符个数,外部变量yyleng就表示当前yytext中字符的个数例如要对输入串中单词的个数和字符的个数进行计数(单词假定是由大写或小写字母组成的字符串),可用下述规则:[a-zA-Z]+ {words++;Chars+=yyleng;}注意被匹配的字符串的第一个字符和最后一个字符分别是yytext[0]和yytext[yyleng-1]下面介绍三个Lex提供的在写动作时可能用到的C函数l.yymore()当需下一次被匹配的字符串被添加在当前识别出的字符串后面,即不使下一次的输入替换yytext中已有的内容而是接在它的内容之后,必须在当前的动作中调用yymore( )。
例:假设一个语言规定它的字符串括在两个双引号之间,如果某字符串中含有双引号,则在它前面加上反斜线\用一个正规式来表达该字符串的定义很不容易,不如用下面较简明的正规式与yymore()配合来识别:\" [∧"]* { if(yytext[yyleng-1]= =’\\’yymore( );else…normal user processing;}当输入串为”abc\"def”时,上述规则首先与前五个字符”abc\匹配,然后调用yymore( )使余下部分”def被添加在前一部分之后,注意作为字符串结尾标志的那个双引号由”normal user proessing”部分负责处理2.yyless(n)如果当前匹配的字符串的末尾部分需要重新处理,那么可以调用 yyless(n)将这部分子串“退回”给输入串,下次再匹配处理yyless(n)中的n是不退回的字符个数,即退回的字符个数是yyleng-n例;在C语言中串“=-a”具有二义性,假定要把它解释为“=-a”同时给出信息,可用下面的识别规则:=-[a-zA-Z] {printf(“Operator(=-)ambiguous\n”);yyless(yyleng-1);…action for=-…}上面的规则先打印出一条说明出现二义性的信息,将运算符后面的字母返回给输入串,最后将运算符按“=-”处理.另外,如果希望把“=- a”解释为”=- a”,这只需要把负号与字母一起退回给输入串等候下次处理,用下面的规则即可:=-[a-zA-Z] { printf(“Operator(=-) ambiguous\n”);。