本节书摘来自华章出版社《编译与反编译技术实战 》一书中的第1章,第1.2节,庞建民 主编 ,刘晓楠 陶红伟 岳 峰 戴超 编著,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1.2 词法分析生成器LEX
词法分析是编译过程的第一个阶段,其任务就是将输入的各种符号转化成相应的标识符号,转化后的标识符很容易被后续阶段处理。
LEX是LEXical compiler的缩写,是UNIX环境下非常著名的工具,主要功能是生成一个词法分析器的C源码,描述规则采用正则表达式。描述词法分析器的文件*.l经过LEX编译后生成一个lex.yy.c的文件,然后由C编译器编译生成一个词法分析器。
LEX接收用户输入的正则表达式,识别这些表达式并且将输入流转化为匹配这些表达式的字符串。在这些字符串的分界处,用户提供的程序片段被执行。LEX代码文件将正则表达式和程序片段关联,将每一条输入对应到由LEX生成的程序的表达式,且执行相应的代码片段。
为了完成任务,除了需要提供匹配的表达式以外,用户还需要提供其他代码,甚至是由其他生成器产生的代码。用户提供一般程序设计语言的代码片段完成程序识别表达式后的工作。因此,用户自由编写动作代码时,并不影响其编写高级语言代码来匹配字符串表达式。这就避免迫使用户使用字符串语言来进行输入分析时,也必须使用同样的方法来编写字符处理程序,而这样做有时是不合适的。LEX不是完整的语言,它是一个新语言的生成器,可以插入各种不同的被叫作“宿主语言”的程序设计语言中。就像大多数高级语言可以生成在不同计算机硬件上运行的代码一样,LEX可以生成不同的宿主语言。宿主语言用于LEX生成输出代码,也用于用户插入程序片段,这使得LEX适用于不同的环境和不同的使用者。每一个应用程序可以是硬件、适用于该任务的宿主语言、用户背景和局部接口属性的直接结合。现在,LEX唯一支持的宿主语言是C,过去也支持过其他语言。Fortran LEX自身一般运行于UNIX或Linux系统之上,但是LEX生成的代码可以在任何适当的编译器上使用。
LEX将用户输入的表达式和动作代码转换为宿主语言,生成的函数一般名为yylex。yylex识别字符流中的表达式,并且当每一个表达式被检测出来后,输出相应的动作。
LEX的文件结构简单,分为三个部分:
declarations
%%
translation rules
%%
auxiliary procedures
分别是声明段、规则段和辅助部分。
1)声明段包括变量、符号常量和正则表达式的声明。希望出现在目标C源码中的代码,用“%{…%}”括起来。比如:
%{
#include <stdio.h>
#include "y.tab.h"
typedef char * YYSTYPE;
char * yylval;
%}
2)规则段是由正则表达式和相应的动作组成的。如
[0-9]+ printf("Int : %s/n",yytext);
[0-9]*/.[0-9]+ printf("Float : %s/n",yytext);
“[0-9]+”是描述整数的正则表达式,“[0-9]*/.[0-9]+”是描述浮点的正则表达式,后面的printf即为它们对应的动作。
值得注意的是,LEX依次尝试每一个规则,尽可能地匹配最长的输入流,即规则部分具有优先级的概念。比如下面的规则部分
%%
A {printf("run");}
AA {printf("like ");}
AAAA {printf("I ");}
%%
对于内容“AAAAAAA”,LEX程序会输出“I like run”,首先匹配最长的4个“A”,之后在剩下的三个“A”中匹配两个“A”,直到最后的一个“A”。可以看出LEX的确按照最长的规则匹配。
3)辅助部分放一些扫描器的其他模块,可以包含用C语言编写的子程序,而这些子程序可以用在前面的动作中,这样就可以达到简化编程的目的。辅助部分也可以在另一个程序文件中编写,最后再链接到一起。生成C代码后,需用C的编译器编译,链接时需要指定链接库。
本书的第3章将更加详细地介绍LEX及其用法。需要说明的是,对于GNU/Linux用户,与UNIX环境中LEX对应的工具是Flex,其具体用法和LEX相似,这里不再赘述。