lex一个词法分析器的生成器 翻译版.docx
《lex一个词法分析器的生成器 翻译版.docx》由会员分享,可在线阅读,更多相关《lex一个词法分析器的生成器 翻译版.docx(23页珍藏版)》请在冰豆网上搜索。
lex一个词法分析器的生成器翻译版
Lex-词法分析器生成器(M.E.Lesk,E.Schmidt著,中文翻译)
Lex-词法分析器生成器
M.E.Lesk与E.Schmidt
BellLaboratories
MurrayHill,NewJersey07974
翻译:
寒蝉退士
译者声明:
译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。
原文:
http:
//cm.bell-
摘要
Lex帮助书写其控制流由在输入流中的正则表达式的实例来导向的程序。
它适合于编辑器脚本类型的变换,和为解析例程做准备工作而分解输入。
Lex源码是正则表达式和相应的程序片段的表格。
Lex把这个表格变换成读取输入流、复制它到输出流、并把输入划分到匹配给定表达式的字符串中的一个程序。
随着每个这种字符串被识别出来,相应的程序片段就被执行。
表达式通过用Lex生成的确定有限自动机来识别。
用户书写的程序片段按照对应的正则表达式在输入流中出现的次序来执行。
用Lex写成的词法分析程序接受有歧义的规定,并在每个输入点上选择最长的匹配可能。
如果需要,在输入上进行实质的超前查看(lookahead),但输入流会被回退(backup)到当前划分的结束处,所以用户有操纵它的普遍自由。
Lex可以生成用C语言或Ratfor语言写的分析器,Ratfor可以自动的转换成可移植的Fortran。
它可以在PDP-11UNIX、HoneywellGCOS和IBMOS系统上得到。
本手册只讨论在UNIX系统上的生成C语言的分析器,这是在UNIX第7版中唯一支持的Lex形式。
设计Lex时简化了与编译器的编译系统Yacc的交接。
July21,1975
目录
∙1. 介绍
∙2. Lex源码
∙3. Lex正则表达式
∙4. Lex动作
∙5. 歧义源规则
∙6. Lex源定义
∙7. 用法
∙8. Lex与Yacc
∙9. 例子
∙10.左上下文敏感
∙11.字符集
∙12.源格式总结
∙13.告诫和缺陷
∙14.致谢
∙15.引用
1.介绍
Lex是设计用于字符输入流的词法处理的一个程序生成器。
它接受高级的、面向问题的对字符串匹配的规定,并生成识别正则表达式的通用语言写的一个程序。
正则表达式在用户给Lex的源规定中指定。
Lex写出的代码识别在输入流中的这些表达式,并把输入流划分到匹配这些表达式的字符串中。
在字符串间的分界上执行用户提供的程序片段。
Lex源文件对正则表达式关联上程序片段。
随着每个表达式出现在给Lex写出的程序的输入中,相应的程序片段就被执行。
用户提供超出表达式所需要的额外代码来完成他的任务,可能包括用其他生成器写出的代码。
生成的识别表达式的程序采用用户的程序片段所采用的通用编程语言。
因此,提供了高级表达式语言来写要被匹配的字符串表达式,而用户写动作的自由不受侵犯。
这避免了强制希望使用字符串操纵语言做输入分析的用户、去使用同样的并且经常不适合字符串处理的语言来书写处理程序。
Lex不是完整的语言,而是体现了可增加到叫做“宿主语言”的不同编程语言中的新语言特征的一个生成器。
正如同通用语言可以生成在不同计算机硬件上运行的代码,Lex可以写出不同宿主语言的代码。
宿主语言被用于Lex生成的输出代码和用户增加的程序片段。
还为不同的宿主语言提供兼容的运行时间库。
这使Lex适应不同的环境和不同的用户。
每个应用都可以被定向到适合这个任务的硬件和宿主语言、用户背景和本地实现性质的各种组合上。
目前唯一支持的宿主语言是C,尽管过去曾经支持过Fortran(Ratfor[2]形式)。
Lex自身存在于UNIX、GCOS和OS/370;但Lex生成的代码可以采用于存在适当的编译器的任何地方。
Lex把用户的表达式和动作(在本文中叫做源码)转换成宿主通用语言;生成的程序叫做yylex。
yylex程序将识别在流(在本文中叫做输入)中的表达式,并在检测到的时候执行给每个表达式的动作。
参见图1。
+-------+
源码->|Lex|->yylex
+-------+
+-------+
输入->|yylex|->输出
+-------+
Lex概述
图1
作为一个平凡的例子,考虑从输入中删除所有行结束处的空白或tab的程序。
%%
[\t]+$;
就是所需要的。
这个程序包含标记规则开始的一个%%分界符和一个规则。
这个规则包含匹配在行结束处之前的空白或tab(按照C语言约定写为\t)字符的一个或多个实例的一个正则表达式。
方括号指示由空白和tab构成的字符类;同于QED,+指示“一或多个”;而$指示“行结束”。
没有指定动作,所以Lex生成的程序(yylex)将忽略这些字符。
所有其他东西都被复制。
要把任何余下的空白或tab的字符串改变为一个单一的空白,可增加另一个规则:
%%
[\t]+$;
[\t]+printf("");
为这个源码生成的有限自动机将立刻扫描这两个规则,在空白或tab的字符串的终止处观察是否有一个换行字符,并执行想要的规则动作。
第一个规则匹配在行结束处的所有空白或tab的字符串,而第二个规则匹配所有余下的空白或tab的字符串。
Lex可以单独的用做简单变换,或在词法层次上聚集(gather)出的分析和统计。
Lex还可以与解析器一起使用而进行词法分析阶段;交接Lex与Yacc[3]是特别容易的。
Lex程序只识别正则表达式;Yacc写出接受一大类上下文无关文法的解析器,但是需要底层的分析器来识别输入记号(token)。
所以,Lex和Yacc的组合经常是适当的。
在用做后面的解析器生成器的预处理器的时候,Lex用来划分输入流,而解析器生成器指派文法结构到结果的词法块(piece)。
这种情况下的(编译器的前半部分)控制流在图2中展示。
其他生成器或手工写的额外的程序可以轻易的增加到Lex写出的程序。
词法规则文法规则
||
vv
+---------++---------+
|Lex||Yacc|
+---------++---------+
||
vv
+---------++---------+
输入->|yylex|->|yyparse|->解析后的输入
+---------++---------+
Lex与Yacc一起
图2
Yacc用户会发现yylex的名字就是Yacc期望词法分析器叫的名字,所以Lex采用这个名字简化了交接。
Lex从源码中的正则表达式生成一个确定有限自动机[4]。
为了节省空间,这个自动机是解释的而不是编译的。
结果仍是一个快速的分析器。
特别是,Lex程序识别和划分输入流所花费的时间正比于输入的长度。
Lex规则的数目或规则的复杂性在决定速度上都不重要,除了包含前向(forward)上下文的规则需要大量的重新扫描之外。
与规则的数目和复杂性一起增加的是自动机的大小,因此也是Lex生成的程序的大小。
在Lex写出的程序中,用户的片段(表示在找到每个正则表达式时要进行的动作)被收集一起成为switch语句中各个case。
自动机的解释器导向控制流。
提供给用户在包含动作的例程中插入声明或增加语句,或者在这些动作例程之外增加例程的机会。
Lex不受限制于可在提前查看一个字符基础上得到解释的源码。
例如,如果有两个规则一个查找ab而另一个查找abcdefg,则在输入流是abcdefh的时候,Lex将识别ab并留下输入指针正好在cd之前。
这种回退比起简单语言的处理要更加高代价。
2.Lex源码
Lex源码的一般格式为:
{定义}
%%
{规则}
%%
{用户子例程}
这里的定义和用户子例程部分经常被省略。
第二个%%是可选的,但是需要第一个来标记规则的开始。
绝对极小的Lex程序是
%%
(没有定义,没有规则)它被变换成无改变的复制输入到输出的一个程序。
在上述Lex程序的轮廓中,规则代表用户的控制决定;它们是个表格,在其中左列包含正则表达式(参见章节3)而右列包含动作,在识别了表达式的时候要执行的程序片段。
所以如下一个单独规则
integerprintf("foundkeywordINT");
在输入流中查找integer字符串并在它出现的时候打印消息“foundkeywordINT”。
在这个例子中宿主过程语言是C并使用C库函数printf打印这个字符串。
表达式的结束通过第一个空白或tab字符来指示。
如果动作只是一个单一的C语言表达式,它可以给出在这行的右侧;如果它是复合的或多余一行,则应当包围在花括号中。
作为稍微有用一些的例子,假设想要把一些美式拼写改为英式拼写。
Lex规则如下
colourprintf("color");
mechaniseprintf("mechanize");
petrolprintf("gas");
将是一个好的开始。
这些规则非常不够,因为单词petroleum将变成gaseum;后面会讨论处理它的方式。
3.Lex正则表达式
正则表达式的定义非常类似于QED[5]中的定义。
正则表达式指定要被匹配的一组字符串。
它包含文本字符(它匹配被比较的字符串中的相应的字符)和操作符字符(它指定重复、选择和其他特征)。
字母表字母和数字总是文本字符;所以正则表达式
integer
匹配字符串integer只要它出现,而表达式
a57D
查找字符串a57D。
操作符
操作符字符有
"\[]^-?
.*+|()$/{}%<>
如果它们被用做文本字符,应当使用转义(escape)。
引号操作符(")指示在一对引号之间包含的任何东西都被当作文本字符。
所以
xyz"++"
匹配字符串xyz++,在它出现的时候。
注意可以引用字符串的一部分。
引用普通字符是无害和没有必要的;表达式
"xyz++"
同于上面的表达式。
所以通过引用用做文本字符的所有非字母数字字符,用户可以避免记住上面的操作字符列表,并在Lex进一步扩展这个列表的时候是安全。
还可以通过前导一个\来把一个操作符字符转义为一个文本字符,比如
xyz\+\+
是上述表达式的另一个更少可读性的等价者。
引用机制的另一个用处是把空白介入到表达式中;如上所述正常情况下空白或tab结束一个规则。
不包含在[](见后)内的任何空白字符必须被引用。
还识别使用\的一些常见的C转义:
\n是换行、\t是tab而\b是退格(backspace)。
要录入\自身需使用\\。
因为换行在一个表达式中是非法的,必须使用\n;不需要转义tab和退格。
除了空白、tab、换行和上述列表中字符之外的所有字符总是文本字符。
字符类
字符类可以使用运算符对[]来指定。
构造[abc]匹配一个单一字符,可以是a、b或c。
在方括号内,忽略多数字符的意义。
只有三个字符特殊:
它们是\-和^。
-字符指示范围。
例如
[a-z0-9<>_]
指示包含所有小写字母、数字、尖括号和下划线的字符类。
可以按任意次序给出范围。
使用不都是大写字母、不都是小写字母、