Lex(Lexical Analyzar) 是一种词法分析程序生成器,它可以根据词法规则说明书的要求来生成单词识别程序,由该程序识别输入文本中的各个单词。其文件可为:
(1)定义
(2)规则
(3)用户子程序
其中规则部分是必须的,定义和用户子程序部分是任选的。
1、定义
(1)%{ 符号定义:
定义部分起始于 %{ 符号,终止符 %} 符号,可以是:文件包含,宏定义,常数定义,全局变量及外部变量定义,函数声明等。
%{
#include <Foundation/Foundation.h>
#include <stdio.h>
int linenum;
#define smallerFunction(a, b) ((a) < (b) ? (a) : (b))
%}
注意:特殊括号%{和 %}都必须顶着行首写。
(2)正规定义
示例:
letter [A-Za-z]
digit [0-9]
id {letter}({letter}|{digit})*
注意:上面正规定义中出现的小括号表示分组,而不是被匹配的字符。而大括号括起的部分表示正规定义名。
(3)状态定义
词法分析器在匹配正规式时,可以在不同状态(或环境)下进行。我们可以规定在不同的状态下有不同的匹配方式。每个词法分析器都至少有一个状态,这个状态叫做初始状态,可以用INITIAL或0来表示,如果还需要使用其他状态,可以在定义段用%s 来定义。
注意: %s也要顶行首写。
示例1:定义了一个名为COMMENT的状态和一个名为STRING_LETTER_STATE的状态,状态名之间用空白分隔;
%s COMMENT STRING_LETTER_STATE
示例2:使用状态时,可以用如下方式写词法规则:
<state1, state2> p0 {action0;}
<state1> p1 {action1;}
这两行词法规则表示:在状态state1和state2下,匹配正规式p0后执行动作action0,而只有在状态state1下,才可以匹配正规式p1后执行动作action1。如果不指明状态,默认情况下处于初始状态INITIAL。
要想进入某个特定状态,可以在动作中写上这样一句: BEGINstate; 执行这个动作后,就进入状态state。
下面是一段处理C语言注释的例子,里面用到了状态的转换,在这个例子里,使用不同的状态,可以让词法分析器在处于注释中和处于注释外时使用不同的匹配规则:
%s c_comment
%%
<INITIAL>“/*” {BEGIN c_comment;}
<c_comment>“*/” {BEGIN 0;}
<c_comment>. {;}
2、规则
规则,起始于%%符号,终止于%%符号,其规则是词法规则。词法规则由模板公式和动作两部分组成。
模板公式部分可以由任意的正则表达式组成,动作部分是由开发者想用的开发语言(C语言、OC、JavaScript 等等)的语句组成,这些语句用来对所匹配的模式进行相应的处理。
需要注意的是,lex将识别出来的单词存放在 yytext[]字符数组中,因此该数组的内容就代表了所识别出来的单词的内容。
(1)Lex源程序中词法规则(即正规式)的相关规定:
(a)正文字符:除元字符以外的其他字符,这些字符在正规式中可以被匹配。
若单个正文字符c作为正规式,则可与字符c匹配,元字符无法被匹配,如果元字符想要被匹配,则需要通过“转义”的方式,即用” ”包括住元字符,或在元字符前加 \ 。例如 ”+” 和 + 都表示加号。
C语言中的一些转义字符也可以出现在正规式中,例如 \t \n \b 等。
(b)元字符:元字符是lex语言中作特殊用途的一些字符,包括:* + ? | { } [ ] ( ). ^ $ “ \ - / < >。
部分元字符在lex语言中的特殊含义:
(c) : 表示补集:[^…]表示补集,即匹配除 之后所列字符以外的任何字符。如 [^0-9] 表示匹配除数字字符 0-9 以外的任意字符。
除 ^ - \ 以外,任何元字符在方括号内失去其特殊含义。
如果要在方括号内表示负号 - ,则要将其至于方括号内的第一个字符位置或者最后一个字符位置,例如+0-9]0-9都匹配数字及+ - 号。
(d) . ^$ /:
点运算符 . 匹配除换行之外的任何字符,一般可作为最后一条翻译规则。
(e) 匹配行首字符。如:begin匹配出现在行首的begin
(f) $ 匹配行末字符。如:end$ 匹配出现在行末的end
(g)R1/R2(R1和R2是正规式)表示超前搜索:若要匹配R1,则必须先看紧跟其后的超前搜索部分是否与R2匹配。
如:DO/=,表示如果想匹配DO,则必须先在DO后面找到形式为*=*的串,才能确定匹配DO。
·········
(2)词法规则段列出的是词法分析器需要匹配的正规式,以及匹配该正规式后需要进行的相关动作。
示例:
while {return (WHILE);}
do {return (DO);}
{id} {yylval = installID (); return (ID);}
每行都是一条规则,该规则的前一部分是正规式,需要顶行首写,后一部分是匹配该正规式后需要进行的动作,这个动作是用C语法来写的,被包裹在 {}之内,被Lex翻译器翻译后会被直接拷贝进lex.yy.c。正规式和语义动作之间要有空白隔开。其中用{}扩住的正规式表示正规定义的名字。
也可以若干个正规式匹配同一条语义动作,此时正规式之间要用| 分隔。
3、辅助函数段
辅助函数段用C语言语法来写,辅助函数一般是在词法规则段中用到的函数。这一部分一般会被直接拷贝到lex.yy.c中。
4、Lex源程序中常用到的变量及函数
yyin 和yyout :这是Lex中本身已定义的输入和输出文件指针。这两个变量指明了lex生成的词法分析器从哪里获得输入和输出到哪里。默认:键盘输入,屏幕输出。
yytext 和yyleng :这也是lex中已定义的变量,直接用就可以了。
yytext :指向当前识别的词法单元(词文)的指针
yyleng :当前词法单元的长度。
ECHO :Lex中预定义的宏,可以出现在动作中,相当于fprintf(yyout, “%s”,yytext),即输出当前匹配的词法单元。
yylex() :词法分析器驱动程序,用Lex翻译器生成的lex.yy.c 内必然含有这个函数。
yywrap() :词法分析器遇到文件结尾时会调用yywrap() 来决定下一步怎么做;若yywrap() 返回0 ,则继续扫描;若返回1 ,则返回报告文件结尾的0标记。
备注 : 由于词法分析器总会调用yywrap,因此辅助函数中最好提供yywrap,如果不提供,则在用C编译器编译lex.yy.c时,需要链接相应的库,库中会给出标准的yywrap函数(标准函数返回1)。