词法分析器flex中文手册.docx
- 文档编号:15066076
- 上传时间:2023-06-30
- 格式:DOCX
- 页数:26
- 大小:30.16KB
词法分析器flex中文手册.docx
《词法分析器flex中文手册.docx》由会员分享,可在线阅读,更多相关《词法分析器flex中文手册.docx(26页珍藏版)》请在冰点文库上搜索。
词法分析器flex中文手册
FLEX中文手册
这是flex手册的部分中文翻译,仅供参考
∙一些简单的例子
∙输入文件的格式
∙模式
∙如何匹配输入
∙动作
∙生成的扫描器
∙开始条件
∙文件结尾规则
∙与yacc一起使用
一些简单的例子
首先给出一些简单的例子,来了解一下如何使用flex。
下面的flex输入所定义的扫描器,用来将所有的“
username”字符串替换为用户的登陆名字:
%%usernameprintf("%s",getlogin());
默认情况下,flex扫描器无法匹配的所有文本将被复制到输出,所以该扫描器的实际效果是将输入文件
复制到输出,并对每一个“username”进行展开。
在这个例子中,只有一个规则。
“username”是模式
(pattern),“printf”是动作(action)。
“%%”标志着规则的开始。
这里是另一个简单的例子:
intnum_lines=0,num_chars=0;
%%\n++num_lines;++num_chars;.++num_chars;
%%intmain(void)
{
yylex();
printf("#oflines=%d,#ofchars=%d\n",num_lines,num_chars);
}
该扫描器计算输入的字符个数和行数(除了最后的计数报告,并未产生其它输出)。
第一行声明了两
个全局变量,“num_lines”和“num_chars”,可以在yylex()函数中和第二个“%%”后面声明的main()函数中
使用。
有两个规则,一个是匹配换行符(“\n”)并增加行数和字符数,另一个是匹配所有不是换行符的
其它字符(由正规表达式“.”表示)。
一个稍微复杂点的例子:
/*scannerforatoyPascal-likelanguage*/
%{
/*needthisforthecalltoatof()below*/
#include
%}
DIGIT[0-9]ID[a-z][a-z0-9]*
%%
{DIGIT}+{
printf("Aninteger:
%s(%d)\n",yytext,
atoi(yytext));
}
{DIGIT}+"."{DIGIT}*{
printf("Afloat:
%s(%g)\n",yytext,
atof(yytext));
}
if|then|begin|end|procedure|function{
printf("Akeyword:
%s\n",yytext);
}
{ID}printf("Anidentifier:
%s\n",yytext);
"+"|"-"|"*"|"/"printf("Anoperator:
%s\n",yytext);
"{"[^}\n]*"}"/*eatupone-linecomments*/
[\t\n]+/*eatupwhitespace*/
.printf("Unrecognizedcharacter:
%s\n",yytext);
%%
intmain(intargc,char**argv)
{
++argv,--argc;/*skipoverprogramname*/
if(argc>0)
yyin=fopen(argv[0],"r");
else
yyin=stdin;
yylex();
}
这是一个类似Pascal语言的简单扫描器的初始部分,用来识别不同类型的标志(tokens)并给出报告。
这个例子的详细介绍将在后面的章节中给出。
输入文件的格式
flex输入文件包括三个部分,通过“%%”行来分开:
definitions(定义)%%rules(规则)%%usercode(用户代码)
定义部分,包含一些简单的名字定义(namedefinitions),用来简化扫描器的规范,还有一些开始状态
(startconditions)的声明,将会在后面的章节中说明。
名字定义的形式如下:
namedefinition
“name”由字母或者下划线(“_”)起始,后面跟字母,数字,“_”或者“-”(破折号)组成。
定义由名字
后面的一个非空白(non-white-space)字符开始,直到一行的结束。
可以在后面通过“{name}”来引用定
义,并展开为“(definition)”。
例如,
DIGIT[0-9]ID[a-z][a-z0-9]*
定义了“DIGIT”为一个正规表达式用来匹配单个数字,“ID”为一个正规表达式用来匹配一个字母,后面
跟零个或多个字母和数字。
后面的引用如下,
{DIGIT}+"."{DIGIT}*
等同于
([0-9])+"."([0-9])*
用来匹配一个或多个数字,后面跟一个“.”,然后是零个或者多个数字。
flex输入的规则部分包括一系列的规则,形式如下:
patternaction
模式(pattern)不能有缩进,动作(action)必须在同一行。
参见后面对模式和动作的进一步描述。
最后,用户代码部分将被简单的逐字复制到“lex.yy.c”中,作为随同程序用来调用扫描器或者被扫描器
调用。
该部分是可选的,如果没有,输入文件中第二个“%%”也可以省略掉。
在定义部分和规则部分,任何缩进的文本或者包含在“%{”和“%}”中的文本,都会被逐字的复制到输出中(并去掉“%{}”)。
“%{}”本身不能有缩进。
在规则部分,在第一个规则之前的任何缩进的或者%{}中的文本,可以用来声明扫描程序的局部变量。
其它在规则部分的缩进或者%{}中的文本也会被复制到输出,但是它的含义却不好定义,而且可能会产生编译时错误(这一特点是为了与POSIX相同;参见后面的其它特点)。
在定义部分(但不是在规则部分),一条未缩进的注释(即,由“/*”起始的行)也会被逐字的拷贝到输出,直到下一个“*/”。
模式
输入中的模式,使用的是扩展的正规表达式集。
它们是:
'x'
匹配字符'x'
'.'
除了换行符以外的任意字符(字节)
'[xyz]'
一个字符类别(characterclass);在这个例子中,该模式匹配一个'x',或者一个'y',或者一
个'z''[abj-oZ]'
一个带有范围的字符类别;匹配一个'a',或者一个'b',或者从'j'到'o'的任意字母,或者一个'Z'
'[^A-Z]'
一个反选的字符类别(negatedcharacterclass),即任意不属于这些的类别。
在这个例子中,
表示任意一个非大写字母的字符。
'[^A-Z\n]'
任意一个非大写字母的字符,或者一个换行符
'r*'
零个或者多个r,其中r是任意的正规表达式
'r+'
一个或者多个r
'r?
'
零个或者一个r(也就是说,一个可选的r)
'r{2,5}'
两个到五个r
'r{2,}'
两个或者更多个r
'r{4}'
确切的4个r
'{name}'
“name”定义的展开(参见前面)
'"[xyz]\"foo"'(这里单引号和双引号之间没有空格)
文字串:
'[xyz]"foo'
'\x'
如果x是一个'a','b','f','n','r','t'或者'v',则为ANSI-C所解释的\x。
否则,为一个文字'x'(
用来转义操作符,例如'*')。
'\0'
一个NUL字符(ASCII代码0)
'\123'
八进制值为123的字符
'\x2a'
十六进制值为2a的字符
'(r)'
匹配一个r;括号用来改变优先级(参见后面)
'rs'
正规表达式r,后面跟随正规表达式s;称作“concatenation”
'r|s'
或者r,或者s
'r/s'
一个r,但是后面要跟随一个s。
在文本匹配时,s会被包含进来,以判断该规则是否是最长的匹配,但是在动作执行前会被返回给输入。
因此,动作只会看到匹配r的文本。
这种模式称作trailingcontext。
(有些'r/s'组合,flex会匹配错误;参见后面的不足和缺陷章节中,关于“危险的尾部相关”的注解)
'^r'
一个r,但是只在一行的开始(即,刚开始扫描,或者一个换行符刚被扫描之后)
'r$'
一个r,但是只在一行的结尾(即,正好在换行符之前)。
等同于“r/\n”。
注意,flex中对换行符的概念跟用来编译flex的C编译器中对'\n'的解释是一模一样的。
特别的是,在一些DOS系统上,必须在输入中自己过滤出'\r',或者显示的使用r/\r\n来表示r$。
'r'
一个r,但是只在起始条件(startcondition)s(参见下面关于起始条件的讨论)下匹配。
'<*>r'
一个r,在任意的起始条件下,甚至是互斥的(exclusive)起始条件。
'<
文件结尾
'
文件结尾,当在起始条件s1或s2下匹配。
注意,在字符类别里面,除了转义符(‘\’),字符类别操作符‘-’,‘]’和类别开始处的‘^’,所有其它的
正规表达式操作符不再具有特殊的含义。
上面列出的正规表达式,是按照优先级由高到低排列的。
同一级别的具有相同的优先级。
例如,
foo|bar*
等同于
(foo)|(ba(r*))
因为,‘*’操作符的优先级比串联高,串联的优先级比间隔符高(‘|’),所以,该模式匹配字符串“foo”
或者字符串“ba”后面跟随零个或多个r。
如果要匹配“foo”或者零个或多个“bar”,可以使用:
foo|(bar)*
如果要匹配零个或者多个“foo”,或者零个或多个“bar”:
(foo|bar)*
除了字符和序列字符,字符类别也可以包含字符类别表达式。
这些表达式由‘[:
’和‘:
]’分隔符封装(并且
必须在字符类别的分隔符‘[’和‘]’之中)。
有效的表达式包括:
[:
alnum:
][:
alpha:
][:
blank:
][:
cntrl:
][:
digit:
][:
graph:
][:
lower:
][:
print:
][:
punct:
][:
space:
][:
upper:
][:
xdigit:
]
这些表达式都指定了与标准C中‘isXXX’函数相对应的字符类别。
例如,‘[:
alnum:
]’指定了‘isalnum()’返
回值为真的字符集,即,任意的字母或者数字。
一些系统没有提供‘isblank()’,则flex定义‘[:
bland:
]’为
一个空格符(blank)或者一个制表符(tab)。
例如,下面的字符类别是等同的:
alnum:
[[:
alpha:
][:
digit:
][[:
alpha:
]0-9][a-zA-Z0-9]
如果你的扫描器是大小写无关的(使用‘-i’命令行选项),则‘[:
upper:
]’和‘[:
lower:
]’等同于‘[:
alpha:
]’。
关于模式的一些注意事项:
一个反选的字符类别例如上面的“[^A-Z]”将会匹配一个换行符,除非“\n”(或者等同的转义序
列)在反选字符类别中显示的指出(如“[^A-Z\n]”)。
这一点不像许多其它正规表达式工具,但不幸的
是这种不一致是由历史造成的。
匹配换行符意味着像[^"]*这样的模式能够匹配整个的输出直到遇到另
一个引号。
一条规则中只能最多有一个尾部相关的情况(‘/’操作符或者‘$’操作符)。
起始条件,‘^’和
“<
‘^’如果不出现在
规则的开始处,或者‘$’不出现在规则的结尾,将会失去它的特殊属性,并且被作为普通字符。
下面的
例子是非法的:
foo/bar$
注意,第一个可以写作“foo/bar\n”。
下面的例子中,‘$’和‘^’将会被作为普通字符:
foo|(bar$)foo|^bar
如果想匹配一个“foo”,或者一个“bar”并且后面跟随一个换行符,可以使用下面的方式(特殊动作‘|’,
将在下面介绍):
foo|bar$/*actiongoeshere*/
类似的技巧可以用来匹配一个foo,或者一个bar并且在一行的起始处。
如何匹配输入
当生成的扫描器运行时,它会分析它的输入,来查找匹配任意模式的字符串。
如果找到多个匹配,则采取最长文本的匹配方式(对于尾部相关规则,也包括尾部的长度,虽然尾部还要返回给输入)。
如果找到两个或者更多的相同长度的匹配,则选择列在flex输入文件中的最前面的规则。
一旦匹配确定,则所匹配的文本(称作标识,token)可以通过全局字符指针yytext来访问,文本的长度存放在全局整形yyleng中。
与匹配规则相应的动作(action)将被执行(后面会有关于动作的详细描述),然后剩余的输入再被继续扫描匹配。
如果没有找到匹配,则执行默认的规则:
紧接着的输入字符被认为是匹配的,并且复制到标准输出。
因此,最简单合法的flex输入是:
%%
生成的扫描器只是简单的将它的输入(一次一个字符的)复制到它的输出。
注意,yytext可以通过两种方式来定义:
作为一个字符指针,或者作为一个字符数组。
可以在flex输入的第一部分(定义部分)通过专门的指令‘%pointer’或者‘%array’来控制flex使用哪种定义。
缺省的为‘%pointer’,除非使用‘-l’lex兼容选项,使得yytext为一个数组。
使用‘%pointer’的优点是能够进行足够快的扫描,并且当匹配非常大的标识时不会有缓冲溢出(除非是动态内存耗尽)。
缺点是在动作中对yytext的修改方式将会有所限制(参见下一章节),并且调用‘unput()’函数将会破坏yytext的现有内容,在不同lex版本中移植时,这将是一个非常头痛的事情。
使用‘%array’的优点是,可以按照自己的意愿来修改yytext,并且调用‘unput()’也不会破坏yytext(参见下面)。
而且,已有的lex程序有时可以通过如下的声明方式,在外部访问yytext:
externcharyytext[];
这种定义在使用‘%pointer’时是错误的,但是对于‘%array’却可以。
‘%array’定义了yytext为一个YYLMAX个字符的数组,缺省情况下,YYLMAX是一个相当大的值。
可以在flex输入的第一部分简单的通过#defineYYLMAX来改变大小。
正如上面提到的,‘%pointer’指定的yytext是通过动态增长来适应大的标识的。
这也意味着‘%pointer’的扫描器能够接受非常大的标识(例如匹配整个的注释块),不过要记住,每次扫描器都要重新设定yytext的长度,而且必须从头扫描整个标识,所以匹配这样的标识时速度会很慢。
目前,如果调用‘unput()’并且返回太多的文本,yytext将不会动态增长,而只是产生一个运行时错误。
而且要注意,不能在C++扫描器类(c++选项,参见下面)中使用‘%array’。
动作
规则中的每一个模式都有一个相应的动作,它可以是任意的C语句。
模式结束于第一个非转义的空白字符;该行的剩余部分便是它的动作。
如果动作是空的,则当模式匹配时,输入的标识将被简单的丢弃。
例如,下面所描述的程序,是用来删除输入中的所有“zapme”:
%%"zapme"
(输入中的所有其它字符,会被缺省规则匹配,并被复制到输出。
)
下面的程序用来将多个空格和制表符压缩为单个空格,并且丢弃在一行尾部的所有空格:
%%[\t]+putchar('');[\t]+$/*ignorethistoken*/
如果动作包括‘{’,则动作的范围直到对称的‘}’,并且可以跨过多行。
flex可以识别C字符串和注释,因此字符串和注释中的大括号不会起作用。
但是,也允许动作由‘%{’开始,并且将直到下一个‘%}’之间的动作看作是文本(包括在动作里面出现的普通大括号)。
只包含一个垂直分割线(‘|’)的动作意味着“与下一个规则相同”。
参见下面的例子。
动作能够包含任意的C代码,包括return语句来返回一个值给调用‘yylex()’的程序。
每次调用‘yylex()’,它将持续不断的处理标识,直到文件的结尾或者执行了return。
动作可以自由的修改yytext,除了增加它的长度(在尾端增加字符的,将会覆盖输入流中后面的字符)。
不过这种情形不会发生在使用‘%array’时(参见前面);在那种情况下,yytext可以任意修改。
动作可以自由修改yyleng,除非他们不应该这么做,比如动作中也使用了‘yymore()’(参见下面)。
在动作中可以包含许多特殊指令:
*‘ECHO’将yytext复制到扫描器的输出。
*BEGIN后面跟随起始状态的名字,使扫描器处于相应的起始状态下(参见下面)。
*REJECT指示扫描器继续采用“次优”的规则匹配输入(或者输入的前面一部分)。
规则根据前面在“如何匹配输入”一节中描述的方式来选择,并且yytext和yyleng也被设为适当的值。
它可能是和最初选择的规则匹配相同多的文本,但是在flex输入文件中排在后面的规则,也可能是匹配较少的文本的规则。
例如,下面的将会计算输入中的单词,并且还在“frob”出现时调用程序special():
intword_count=0;
%%
frobspecial();REJECT;
[^\t\n]+++word_count;
如果没有REJECT,输入中任何“frob”都不会被计入单词个数,因为扫描器在通常情况下只是对每一个标识执行一个动作。
可以使用多个REJECT,对于每一个,都将查找当前活动规则的下一个最优选择。
例如,当下面的扫描器扫描标识“abcd”时,它将往输出写入“abcdabcaba”:
%%
a|
ab|
abc|
abcdECHO;REJECT;
.|\n/*eatupanyunmatchedcharacter*/
(前三个规则都执行第四个的动作,因为他们使用了特殊的动作‘|’。
)对于扫描器执行效率来说,REJECT是一种相当昂贵的特征;如果在扫描器的任意动作中使用了它,它将使得扫描器的所有匹配速度降低。
而且,REJECT不能和‘-Cf’或‘-CF’选项一起使用(参见下面)。
还要注意的是,不像其它特有动作,REJECT是一个分支跳转;在动作中紧接其后面的代码将不会被执行。
*‘yymore()’告诉扫描器在下一次匹配规则时,相应的标识应该被追加到现在的yytext的值中,而不是替代它。
例如,假设有输入“mega-kludge”,下面的将会向输出写入“mega-mega-kludge”:
%%
mega-ECHO;yymore();
kludgeECHO;
首先是“mega-”被匹配,并且回显到输出。
然后是“kludge”被匹配,但是先前的“mega-”还保留在yytext的起始处,因此“kludge”规则中的‘ECHO’实际将会写出“mega-kludge”
使用‘yymore()’时,有两点需要注意的。
首先,‘yymore’依赖于yyleng的值能够正确地反映当前标识的长度,所以在使用‘yymore()’时,一定不要修改yyleng。
其次,在扫描器的动作中使用‘yymore()’,会给扫描器的匹配速度稍微有些影响。
*‘yyless(n)’将当前标识的除了前n个字符之外的都回送给输入流,扫描器在查找接下来的匹配时,会重新扫描它们。
yytext和yyleng会相应做适当的调整(例如,yyleng将会等于n)。
例如,在输入“foobar”时,下面的规则将会输出“foobarbar”:
%%
foobarECHO;yyless(3);
[a-z]+ECHO;
如果yyless的参数为0,则会使当前整个输入字符串被重新扫描。
除非已经改变扫描器接下来如何处理它的输入(例如,使用BEGIN),否则将会产生一个无限循环。
注意,yyless是一个宏,并且只能用在flex输入文件中,其它源文件则不可以。
*‘unput(c)’将字符c回放到输入流,并将其作为下一次扫描的字符。
下面的动作将会接受当前的标识,并使它封装在括号中而被从新扫描。
{
inti;
/*Copyyytextbecauseunput()trashesyytext*/
char*yycopy=strdup(yytext);
unput(')');
for(i=yyleng-1;i>=0;--i)
unput(yycopy[i]);
unput('(');
free(yycopy);
}
注意,由于‘unput()’每次都将字符放回到输入流的起始处,因此如果要回放字符串,则必须从后往前操作。
在使用‘unput()’时,一个重要的潜在问题是如果使用‘%pointer’(缺省情况),调用‘unput()’会破坏yytext的内容,将会从最右面的字符开始,每次向左吞并一个。
如果需要保留yytext的值直到调用‘unput()’之后(如上面的例子),必须将其复制到别处,或者使用‘%array’来构建扫描器(参见如何匹配输入)。
最后,注意不能将EOF放回来标识输入流出去文件结尾。
*‘input()’从输入流中读取下一个字符。
例如,下面的方法可以去掉C注释:
%%
"/*"{
registerintc;
for( ; ;)
{
while((c=input()) !
='*'&&
c !
=EOF)
;/*eatuptextofcomment*/
if(c=='*')
{
while((c=input())=='*')
;
if(c=='/')
break;/*foundtheend*/
}
if(c==EOF)
{
error("EOFincomment");
break;
}
}
}
(注意,如果扫描器是用‘C++编译的’,则‘input()’由‘yyinput()’代替,为了避免与‘C++’流input的名字冲突。
)
*YY_FLUSH_BUFFER刷新扫描器内部的缓存,以至于扫描器下次匹配标识时,将会首先使用YY_INPUT重新填充缓存(参见下面的生成的扫描器)。
这个动作是较为常用的函数`yy_flush_buffer()'的特殊情况,将在下面的多输入缓存章节介绍。
*‘yyterminate()’可以在动作中的用来代替r
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 词法 分析器 flex 中文 手册