编译与反编译技术实战
上QQ阅读APP看书,第一时间看更新

3.3.1 LEX源文件结构

LEX的输入是用LEX源语言编写的程序,它是扩展名为.l的文件。LEX源程序经LEX系统处理后输出一个C程序文件,此文件含有两部分内容:一个是依据正规式所构建的状态转移表;另一个是用来驱动该状态转移表的总控程序yylex ()。该文件再经过C编译器的编译就产生一个实际可以运行的词法分析程序,其使用方法如图3-4所示。

图3-4 用LEX构建词法分析器

一般而言,一个LEX源程序由“% %”分隔的三个部分组成,其书写格式为:

    定义部分
    % %
    识别规则部分
    % %
    辅助函数部分

其中,定义部分和辅助函数部分是任选的,识别规则部分则是必备的。如果辅助函数部分缺省,则第二个分隔号“% %”可以省略;但由于第一个分隔号% %用来指示识别规则部分的开始,故即使没有定义部分,也不能将其省略。下面将对这三部分的内容及其书写格式作一概括性介绍。

1.定义部分

定义部分对规则部分要引用的文件和变量进行说明,通常可包含头文件表、常数定义、全局变量定义、外部变量定义以及正规式定义等。正规式定义用来定义在规则部分引用的正规式,类似于C语言中的宏定义,所以也称为宏定义。每一个宏定义由分隔符(适当个数的空格或制表字符)连接的宏名字和宏内容组成:

    D1  r1
    D2  r2
    …  …
    Dn  rn

其中,Di是要定义的一组互不相同的宏名字,它是以字母或者下划线“_”开始,由字母、数字和下划线“_”组成的字符串,并且大小写敏感;每个ri是以后用来替换宏名字Di的宏内容,其都是Σ∪{D1, D2, ..., Di-1}上的正规式;Σ是相应程序设计语言的基本字符集。设置宏定义的目的在于给一些较为复杂的正规式命名,以便以后在需要出现这些正规式的地方只需写上相应的宏名字。例如:

    digit [0-9]
    letter [a-zA-Z]

其中,digit是匹配单个数字的正规式;letter是匹配单个字母的正规式。需要注意的是,在以后的定义部分和规则部分,凡对已定义宏名字的引用都需用花括号将它们括起来。例如:

    {letter}({letter}|{digit})*

LEX扫描源文件时,将{letter}替换为letter所定义的正规式并加上括号,将{digit}替换为digit所定义的正规式并加上括号,所以,上式等价于

    ([a-zA-Z])(([a-zA-Z])|([0-9]))*

它将匹配以字母开始并由字母和数字组成的字符串。

除宏定义外,定义部分的其余代码需用符号“%{”和“%}”括起来。LEX将“%{”和“% }”之间的内容直接复制到生成的C文件lexyy. c中。在LEX源程序中,起标识作用的符号“% %”“%{”以及“%}”都必须处于所在行的最左字符位置。另外,在其中也可随意添加C语言形式的注释。如

    %{
    #include〈math. h〉
    #include〈string. h〉
    int num_chars = 0, num_lines = 0;
    %}

2.识别规则部分

识别规则部分是具有如下形式的语句序列:

    P1 { A1 }
    P2 { A2 }
        ...
    Pn { An }

其中,每个Pi都是定义在Σ∪{D1, D2, … , Dn}上的正规式(Di是定义部分所定义的宏名字),用来描述一种单词模式;Ai是一段C语言源代码,用来指出当从输入字符串中识别出词型为Pi的单词时词法分析器应执行的操作。每个Pi都必须顶行书写,并用分隔符(若干个空格或tab字符)与其后的代码段Ai分开。每个代码段Ai可引用已定义的符号常量、全局变量和外部变量,并能调用辅助函数部分所定义的函数,必要时也可在Ai中定义自己的局部变量。Ai一般不必用花括号括起来,但若Ai多于一行或者需要在其中定义局部变量,则应使用花括号并且左括号“{”一定要与相应的Pi在同一行,以便确定这些局部变量的作用域。

3.辅助函数部分

在识别规则部分中所调用的函数若不是库函数,则需要给出这些函数的定义。这些函数在辅助函数部分给出,由用户用C语言编写,它们由LEX系统直接复制到输出的C程序文件之中。

表3-2中列出了LEX中常用的一些变量和函数,在与正规式匹配的动作或辅助过程中都可以使用。

表3-2 LEX中常用的一些变量和函数

例3.1下面给出了一个LEX源文件,其功能是统计文本文件中的字符数和行数。该程序首先定义了num_chars和num_lines两个计数器,分别记录文本文件的字符数和行数。在LEX源文件中定义两个正规式“\n”和“. ”,分别用来匹配换行符和任意字符,并且在识别这两个正规式后其相应的计数器累加1,从而完成对文件的字符数和行数的统计。

        %{
        /* 该LEX程序的功能是统计文本文件中的字符数和行数,并输出结果*/
        #include <stdio. h>
        int num_chars = 0, num_lines= 0; /* C语言全局变量,定义两个计数器并置初值为0*/
        %}
        %%
        \n   {++num_chars; ++num_lines; } /* " \n" 匹配换行符 */
        .     {++num_chars; } /* "."匹配除换行符以外的任意字符 */
        %%
        main () /* 主函数 */
        {
        yylex ();
        printf ("本文件的行数为:%d,字符数为:%d\n", num_lines, num_chars);
        return 0;
        }
        int yywrap () /* 文件结束处理函数,yylex在读到文件结束标记EOF时,调用该函数,用户必须提供该
    函数,否则在编译链接时会出错 */
        {
        return 1; /* 返回1表示文件扫描结束,不必再扫描别的文件 */
        }