第 2章 词法分析要点
利用 Lex自动生成扫描程序
2.6 利用 Lex自动生成扫描程序
Lex 代表 Lexical Analyzar
使用 LEX工具可根据描述语言的词法构成规则的正规式来自动生成扫描程序。
LEX在 unix平台上使用,在 windows平台上的一种常用实现是 FLEX,它是 GNU compiler
package的一部分。
Lex 简介词法分析器的自动产生 --LEX
Lex
compiler
Lex
source
program
lex.l
lex.yy.c
C
compilerlex.yy.c a.out
a.outInputstream
sequence
of
tokens
正则表达式由符号组成,符号一般是字符和数字,但
Lex 中还有一些特殊标记。
2.6.1 正则表达式的 Lex约定 (1)
字符 含义
A-Z,0-9,a-z 构成了部分模式的字符和数字
,匹配除 \n外的任意字符
- 用来指定范围例如,A-Z 指从 A 到 Z 之间的所有字符。
字符 含义
[ ] 一个字符集合。匹配括号内的任意字符。
如果第一个字符是 ^ 那么它表示否定模式。
例,[abC] 匹配 a,b,和 C中的任何一个。
* 匹配 0个或者多个上述的模式。
+ 匹配 1个或者多个上述模式。
匹配 0个或 1个上述模式。
\n 匹配行尾
2.6.1 正则表达式的 Lex约定 (2)
字符 含义
$ 作为模式的最后一个字符匹配一行的结尾。
{ } 表示重复(如果它们括起来的是数字)或定义的表达式 (如果它们括起来的是一个名字 ) 。
例,A{1,3} 表示 A 可能出现 1次或 3次。
{digit} 名字为 digit的 正则表达式
\ 用来转义元字符。
用来覆盖字符在此表中定义的特殊意义,表示字符本意。
^ 否定。
| 表达式间的逻辑或。
2.6.1 正则表达式的 Lex约定 (3)
字符 含义
,<一些符号 >” 字符的字面含义。用于元字符。
/ 向前匹配。
如果在匹配的模版中的,/”后跟有后续表达式,
只匹配模版中,/”前面的部分。
例:输入 A01,那么在模版 A0/1 中的 A0 是匹配的。
( a) a本身。
% 专用于 Lex源程序的段间分割符
2.6.1 正则表达式的 Lex约定 (4)
正则表达式 含义
joke[rs] 匹配 jokes 或 joker。
A{1,2}shis? 匹配 AAshis,Ashis,AAshi,Ashi。
(A[b-e])+ 匹配以 A开头、奇数位为 A、偶数位为 b~ e之间的任意字符且长度为大于 0的偶数的串。
正则表达式举例标记 相关表达式 含义数字 (nat) ([0-9])+ 1个或多个数字带符号数( signedNat) (“+”|”-”)? Nat 带符号的整数字符 (chars) [A-Za-z] 1个任意字符空格 (blank) " " 一个空格字 (word) (chars)+ 1个或多个 chars
标识符 (id) [A-Za-z][A-Za-z0-9]* 以字母开头后跟任意字母或者数字程序设计语言正则表达式举例
Lex 中的标记声明类似 C 中的变量名。每个标记都有一个相关的表达式。
2.6.2 LEX输入文件的格式定义集
%%
规则部分
%%
辅助程序部分
LEX源程序由 3个部分构成,
定义部分出现在第 1个双百分号之前。
它包括两种成份:
( 1) 函数外部的任意 C代码必须插入到应在这一部分中分隔符,% {”和
,% }”之间。
( 2) 正则表达式的名字定义正则表达式的名字写在一行的第 1列,空格后跟所表示的正则表达式 。
2.6.2 LEX输入文件的格式
—— 定义部分
<规则部分 >包含着一些规则。
一条规则由带有一段 C代码的正则表达式组成,书写为:
正则表达式 { c代码 }
当匹配相对应的正则表达式时,对应的 C代码就会被执行。
<辅助程序部分 >是一些 C代码,供第二部分调用。
还可定义 main函数,以使生成的扫描程序能独立运行,
2.6.2 LEX输入文件的格式
—— 规则部分、辅助程序部分
Lex 动作当上述的正规式被匹配的时候,Lex将运行相应的动作,
注:有一个将输入串原样照抄到输出串的默认动作,
所有不匹配的串都将执行这个动作,
因此如果 Lex用户希望接受整个输入串,而不产生任何输出,就必须提供与所有可能词形相匹配的规则,
2.6.2 LEX输入文件的格式
Lex 动作
——滤掉某些输入使用一个 C的空语句 ;
例,滤掉三种空格的字符 (空格,tab和回车换行 )
[ \t\n] ;
省略动作的另一个简单的方法是使用动作字符 |,
表示这条规则的动作与下一条规则相同,
上例也可写为:,,|
"\t"|
"\n" ;
2.6.2 LEX输入文件的格式
Lex动作
——输出正规式真正匹配的内容例:正规式 [a-z]+
[a-z]+ printf("% s",yytext);
[a-z]+ ECHO;
或注,Lex 将匹配的字符串存在外部字符数组 yytext中
2.6.2 LEX输入文件的格式匹配的串的最后一个字符可用以下方式访问
yytext[yyleng-1]
Lex中使用计数器 yyleng来表示被匹配的字符个数,
例:对输入串中的单词的个数和字符的个数进行计数,
用语句 [a-zA-Z]+ {words++; chars+=yyleng;}
2.6.2 LEX输入文件的格式
Lex动作
—— 计数例 给文本添加行号的扫描程序
% {
/* 在源代码中加行号的 LEX程序 * /
#include <stdio.h>
int lineno = 1;
% }
line,*\n
%%
{line} { printf ( "%5d %s",lineno++,yytext ); }
%%
main( )
{ yylex(); return 0; }
2.6.2 LEX输入文件的格式
Lex应用举例(一)
例,只输出以 a开头和 a结尾的所有行的 Lex 输入文件:
% {
#include <stdio.h>
% }
ends_with_a,*a\n
begins_with_a a.*\n
% %
{ends_with_a} ECHO;
{begins_with_a} ECHO;
.*\n ;
% %
main( )
{ yylex( ); return 0; }
2.6.2 LEX输入文件的格式
Lex应用举例(二)
注:已匹配的串将不再与后面的正则表达式进行匹配。
例 将除 c-风格注释外所有的大写字母转变成小写字母的程序:
% {
#include <stdio.h>
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
% }
% %
2.6.2 LEX输入文件的格式
Lex应用举例(三)
[A-Z] {putchar(tolower(yytext[0]));
/* yytext[0]为找到的单大写字母 */ }
"/*" { char c ;
int done = FALSE;
ECHO ;
do
{ while ((c=input())!='*') putchar( c ) ;
putchar( c ) ;
while( ( c = input( ) ) = = ‘ * ’) putchar( c ) ;
putchar( c ) ;
if (c == '/') done = TRUE;
} while (!done);
}
% %
void main(void ){ yylex();}
2.6.2 LEX输入文件的格式
Lex应用举例(三)
(1) 二义性的解决
Lex输出总是首先将可能的最长子串与规则相匹配。
如果某个子串可与两个或更多的规则匹配,则 L e x的输出将找出列在行为部分中的第 1个规则。
如果没有规则可与任何非空子串相匹配,则缺省行为将下一个字符复制到输出中并继续下去。
Lex约定
2.6.2 LEX输入文件的格式
(2) C代码的插入
任何写在定义部分 % {和 % }之间的文本将被直接复制到外置于任意过程的输出程序之中。
辅助过程中的任何文本都将被直接复制到 Lex 代码末尾的输出程序中。
将任何跟在行为部分(在第 1个 % %之后)的正则表达式之后(中间至少有一个空格)的代码插入到识别过程
yylex的恰当位置,并在与对应的正则表达式匹配时执行它。代表一个行为的 C代码可以既是一个 C语句,也可以是一个由任何说明及由位于花括号中的语句组成的复杂的 C语句。
Lex约定
2.6.2 LEX输入文件的格式
(3) 内部名字列出了在本章中所提到过的 Lex内部名字。
表 2-3 一些 L e x内部名字
Lex 内部名字 含义 /使用
lex.yy.c或 lexyy.c Lex 输出文件名
yylex Lex 扫描例程
yytext 当前行为匹配的串
yyin Lex 输入文件(缺省,stdin)
yyout Lex 输出文件(缺省,stdout)
input Lex 缓冲的输入例程
ECHO Lex 缺省行为(将 yytext打印到 yyout
2.6.2 LEX输入文件的格式