lex使用

概念

Lex代表词法分析器(Lexical Analyzar)。

Lex读取输入源将匹配的字符串转换为标志(token),匹配的方式是使用正则表达式。每个匹配的模式(pattern)都对应一个动作(action),一般情况下,动作是返回一个标志,代表匹配的字符串。

Lex的限制。Lex不能识别嵌套的结构(如括号),因为处理嵌套结构需要一个栈,而lex没有。对此的处理使用yacc来实现。

正则表达式概述

关键词 说明
. 匹配任何字符(除了新行符)
\n 新行符(newline)
  • | 匹配0个或多个前面模式的拷贝
  • | 匹配一个或多个前面模式的拷贝
    ? | 匹配0个或1个前面模式的拷贝
    ^ | 匹配行开头
    $ | 匹配行尾
    a|b | a或b
    (ab)+ | 匹配一个或多个ab的拷贝
    “a+b” |匹配a+b
    [] | 匹配集合里的任意字符

字符的特殊含义:字符”-“在字符集里两个字符之间时代表一个范围。字符”^”在字符集里作为首字符,表示”反”含义。

匹配规则

如果有多个模式匹配同一个串,则使用匹配最长的模式;若匹配长度也相同,则使用位于前面的模式。

Lex文件格式

Lex输入文件分为三个部分,使用%%来分隔每个部分。格式如下:

1
2
3
4
5
... definitions ...
%%
... rules ...
%%
... subroutines ...

第一个%%是必须的,在它后面是规则部分。如果不定义任何规则,缺省的动作是匹配任何文本并拷贝到输出。缺省的输入和输出是stdin和stdout。

第一部分 定义

这部分的C代码被拷贝到生成的C文件的头部,C代码必须用"%{""%}"包括起来。该部分还可定义简单的匹配模式,这些定义可以在后面的规则部分使用。如:

1
2
3
4
5
6
7
8
9
digit [0-9]
letter [A-Za-z]
%{
int count;
%}
%%
/* match identifier */
{letter}({letter}|{digit})* count++;
%%

在规则部分使用定义的模式必须用{}括起来,以示区别。

第二部分 规则

规则中的每个模式必须开始于一行的第一列。之后是空白符(空格、制表符、新行符)和对应的动作。动作可以是一个简单的C语句或使用{}包括的多个语句。

文件中第一列没有内容的行都原样的拷贝到生成的C文件中。这个特性可以在lex文件中插入注释。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%%
/* match everything except newline */
. ECHO;
/* match newline */
\n ECHO;
%%
int yywrap(void)
{
return 1;
}
int main(void)
{
yylex();
return 0;
}

定义了两个模式:”.”和”\n”。

lex本身定义了一些宏和变量。ECHO是一个宏,该宏的典型实现如下:

1
#define ECHO fwrite(yytext, yyleng, 1, yyout)

第三部分 定义函数

定义main函数和其他处理函数。

lex的常用变量和函数(宏)

变量 说明
int yylex( void ) Lex的入口,返回标志(token)
char *yytext 指向匹配的串(null结尾)
yyleng 匹配串的长度
yylval 对应标志(token)的值
int yywrap( void ) 输入结束时调用,返回1表示结束,0表示还有数据处理
FILE *yyout 输出文件,缺省是stdout
FILE *yyin 输入文件,缺省是stdin
INITIAL 开始状态
BEGIN condition 切换开始状态
ECHO 输出匹配串

一些lex的实现提供变量yylineno。

处理方法

处理引号

以双引号包围的字符串在程序中很常见,如何匹配它们呢?

简单的方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
%{
char *yylval;
#include <string.h>
%}
%%
\"[^"\n]*["\n] {
yylval = strdup(yytext+1);
if (yylval[yyleng-2] != '"')
warning("improperly terminated string");
else
yylval[yyleng-2] = 0;
printf("found '%s'\n", yylval);
}

上述方法不能处理串中的转义字符如 \n 和 \ 等。要加上这些功能,如下处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%{
char buf[100];
char *s;
%}
%x STRING
%%
\" { BEGIN STRING; s = buf; }
<STRING>\\n { *s++ = '\n'; }
<STRING>\\t { *s++ = '\t'; }
<STRING>\\\" { *s++ = '\"'; }
<STRING>\" { *s = 0;
BEGIN 0;
printf("found '%s'\n", buf);
}
<STRING>\n { printf("invalid string"); exit(1); }
<STRING>. { *s++ = *yytext; }

在定义部分定义了一个独占的开始符STRING,当执行了BEGIN 开始符后,lex进入该独占符模式,lex只匹配以该独占符开头的模式,直到另一个BEGIN开始执行为止。

处理保留字

如果程序中有大量的保留字要处理,比较好的方法是写一个函数来查看一个字符串是一个变量还是保留字。而不是定义一堆标志。如:

1
2
3
4
5
6
7
{letter}({letter}|{digit})*  {
int i;
if ((i = resWord(yytext)) != 0)
return (i);
yylval.id = symLookup(yytext);
return (IDENTIFIER);
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!