编译器的自动生成工具LEX和YACC的使用方法
感谢原作者,转自http://blog.sina.com.cn/s/blog_58a84dcc01000bf6.html
1.2 lex源程序的格式
1.3 Lex用的正规式
正规式运算符则将基本的正规式组合成为复杂的正规式,表示字符串的集合。
几个特殊符号:
\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.闭包运算
[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、上下文相关性
7、重复和辅助定义
1.4 Lex源程序中的动作
例:滤掉输入申中所有空格、tab和回车换行符,相应的识别规则如下:
[ t n];
如果相邻的几条规则的动作都相同,则可以用|表示动作部分,它指出该规则的动作与下一条规则的动作相同。例如上倒也可以写成:
“ ”|
“ t”|
“ n”;
注意 t和 n中的双引号可以去掉。
[a-z]+ printf(“% s”, yytext);
动作printf(“%s”,yytext)就是将字符数组yytext的内容打印出来,这个动作用得很频繁,Lex提供了一个宏ECHO来表示它,因此上述识别规则可以写成:
[a-z]+ECHO;
请注意,上面说过缺省的动作就是将输入串原样抄到输出文件中,那么上述规则起什么作用呢?这一点将在“规则的二义性”一节中解释。
[a-zA-Z]+ {words++;
Chars+=yyleng;}
注意被匹配的字符串的第一个字符和最后一个字符分别是
yytext[0]和yytext[yyleng-1]
l.yymore()
例:假设一个语言规定它的字符串括在两个双引号之间,如果某字符串中含有双引号,则在它前面加上反斜线\。用一个正规式来表达该字符串的定义很不容易,不如用下面较简明的正规式与yymore()配合来识别:
" [∧"]*{
if(yytext[yyleng-1]
= =’ ’yymore( );
else
…normal user processing
}
当输入串为”abc\"def”时,上述规则首先与前五个字符”abc\匹配,然后调用yymore()使余下部分”def被添加在前一部分之后,注意作为字符串结尾标志的那个双引号由”normal user proessing”部分负责处理
2.yyless(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”);
yyless (yyleng-1);
…action for = …
}
3. yywrap ( )
1. 5识别规则的二义性
1)能匹配最多字符的规则优先
2)在能匹配相同数目的字符的规则中,先给出的规则优先
例:设有两规则按下面次序给出:
integer kegword action…
[a-z]+ identifier action…
如果输入是integers,则它将被当成标识符处理,因为规则integer只能匹配7个字符,而[a-z]+能匹配8个字符;如果输入串是integer,那么它将被当作关键字处理,因为两条规则都能与之匹配,但规则integer先给出。
1.6 lex源程序中的辅助定义部分
name translation
例如用名字IDENT来代表标识符的正规式的辅助定义为
IDENT [a-zA-Z][a-zA-Z0-9]*
辅助定义在识别规则中的使用方式是用运算符{ }将 name括起来,Lex自动地用 translation去替换它,例如上述标识符的辅助定义的使用为:
{IDENT}action for identifer…
下面我们用辅助定义的手段来写一段识别FORTRAN语言中整数和实数的Lex源程序:
D
E
%%
{D}+ printf(“integer”);
{D}+"."{D}*({E})?
{D}*"."{D}+({E})?
{D}+{E} printf( "real" );
1)以一个空格或tab起头的行,若不是 Lex的识别规则的一部分,则被照抄到 Lex产生的程序中去。如果这样的行出现在第一个%%之前,它所含的定义就是全局的,即Lex产生的程序中的所有函数都可使用它。如果这样的行紧接在第一个%%之后但在所有识别规则之前,它们就是局部的,将被抄到涉及它的动作相应的代码中去。注意这些行必须符合C语言的语法,并且必须出现在所有识别规则之前。
2)所有被界于两行%{和%}之间的行,无论出现在哪里也无论是什么内容都被照抄过去,要注意%{和%}必须分别单独占据一行.例如;
%{
# defineENDOFFILE 0
#include “head.h”
int flag
%}
提供上面的措施主要因为在C语言中有一些行如上例中的宏定义或文件蕴含行必须从第一列开始写。
3)出现在第二个%%之后的任何内容,不论其格式如何,均被照抄过去。
1.7 怎样在Unix系统中使用Lex假定已经写好了一个Lex源程序。怎样在Unix系统中从它得到一个词法分析器呢?
例,有一名叫source的Lex源程序,第一步用下面的命令将它转换成lex.yy.c:
$ lex source
($是 Unix的提示符)。Lex.yy.c再用下面的命令编译即得到可运行的目标代码 a.
out:
$cc lex.yy.c-ll
上面的命令行中的一11是调用Lex的库,是必须使用的,请参看[1]。
这一节内容请读者参看[4]中的lex(1)
Lex可以很方便地与Yacc配合使用,这将在下一章中介绍。
$1.8例子
这一节举两个例子看看Lex源程序的写法
1.将输入串中所有能被7整除的整数加3,其余部分照原样输出,先看下面的Lex源程序:
%%
int k;
[0-9]+{
scanf(-1, yytext,“%d”,&k);
if(k % 7 = =0)
printf(“%d”,k+3);
else
printf(“ % d”, k);
}
上面的程序还有不足的地方,如对负整数,只是将其绝对值加上3,而且象X7,49.63这样
的项也做了修改,下面对上面的源程序稍作修改就避免了这些问题。
%%
int k;
-?[0-9]+{
scanf(-1,yytext,“%d”,&k);
printf(“%d”,k%7= =0?k+3;k);
}
-?[0-9]+ ECHO;
[A-Za-z][A-Za-z0-9]+ ECHO;
2.下一个例子统计输人串中各种不同长度的单词的个数,统计结果在数组lengs中,单词定义为由字母组成的串,源程序如下;
int lengs [100];
%%
[a-z]+ lengs[yyleng]++;
"|
n ;
%%
yywrap ( )
{
int i ;
printf(“Length No.words n”) ;
for(i=0;i<100;i++)
if(lengs[i]>0)
ptintf(“%5d % 10d\n”,i, lengs[i];
return (1);
}
在上面的流程序中,当Lex读入输入串时,它只统计而不输出,到输入串读入完毕后,才在调用 yywrap()时输出统计结果,为此用户自己提供了yywrap(),注意yywrap()的最后一个语句是返回值1。
1.9 再谈上下文相关性的处理
1)使用标志来区分不同的上下文。
例:将输入串照原样输出,但对magic这个词,当它出现在以字母a开头的行中,将其改为first,出现在以b开头的行中将其改为second,出现在以c开头的行中则改为third。
使用标志flag的Lex源程序如下;
int flag ;
%%
∧a {flag='a';ECHO;}
∧b {flag='b'; ECHO;}
∧c {flag='c'; ECHO;}
n {flag=o; ECHO;}
magic{
switch (flag)
{
case 'a' : printf (“first”); break;
case 'b': printf (“second”); break;
case 'c' : printf (“third”); break;
default; ECHO; break;
}
}
2)使用开始条件来区分不同上下文
开始条件由用户在Lex源程序的“辅助定义部分”定义,语法是
%Start name1 name2 name3…
其中Start可以缩写成S或s。开始条件名字的顺序可以任意给出,有很多开始条件时也可以由多个%Start行来定义它们。
开始条件在识别规则中的使用方法是把它用尖括号括起来放在识别规则的正规式左边:
<name1>expression
要进入开始条件如Name1,在动作中用语句
BEGIN name1
它将Lex所处的当前开始条件改成 name1
要恢复正常状态,用语句
BEGIN 0
它将 Lex恢复到 Lex解释器的初始条件
一条规则也可以在几个开始条件下都起作用,如
<name1 ,name2,name3> rule
使rule在三个不同的开始条件下都起作用。要使一条规则在所有开始条件下都起作用,就不在它前面附加任何开始条件。
例:解决1)中的问题,这次用开始条件,Lex源程序如下:
%start AA BB CC
%%
∧a {ECHO; BEGIN AA;}
∧b {ECHO; BEGIN BB;}
∧c {ECHO; BEGIN CC;}
n {ECHO; BEGIN 0;}
<AA>magic printf (“first”) ;
<BB>magic Printf(“second”);
<CC>magic Printf(“third”)i
1.10 Lex源程序格式总结
为使用方便起见,将Lex源程序的格式,Lex的正规式的格式等总录于此.
Lex源程序的一般格式为:
{definitions}
%%
{rules}
%%
{user subroutines}
辅助定义部分包括以下项目;
1)辅助定义,格式为:
name translation
2)直接按照抄的代码,格式为:
空格 代码
3)直接照抄的代码,格式为:
%{
代码
%}
4)开始条件,格式为:
%S namel name2…
还有几个其他项目,不常使用故略去。
识别规则部分的格式是
expression action
其中expression必须与action用空格分开,动作如果多于一行,要用花括号括起来。
Lex用的正规式用的运算符有以下一些:
x 字符x
“x”字符x,若为运算符,则不起运算符作用
x 同上
[xy] 字符x或y
[x-z] 字符x,或y,或z
[∧x] 除x以外的所有字符
∧x 出现在一行开始处的x
<y>x 当 Lex处于开始条件 y时, x
X$ 出现在一行末尾处的x
x? 可有可无的 X
x* 0个或多个x
x+ 1个或多个x
x|y X或y
(x)字符x
x/y 字符x但仅当其后紧随y
{xx} 辅助定义XX的展开
x(m,n)m到n个x