跟我一起写编译器(一)——lex&yacc【转载】

来源:互联网 发布:linux 重启后不能远程 编辑:程序博客网 时间:2024/05/17 21:46

跟我一起写编译器(一)——lex&yacc【转载】

出处http://www.cnblogs.com/lucasysfeng/p/4842310.html  

 第一节、工欲善其事,必先利其器。

  笔者不会过多地陈述理论,而是希望通过实践还原一个C编译器的完成过程。

  先来看一个简单的源文件main.c:

1
2
3
4
5
6
7
#include <stdio.h>
 
int main()
{
     printf(“Hello World\n”);
     return 0;
}

  我们会用gcc main.c生成可执行文件a.out, 那么问题来了,gcc是如何识别include int main printf return这些词汇的呢,又是如何根据这些词汇做出相应操作的呢?答案是使用lex和yacc.

  lex 代表 lexical analyzar(词法分析器),yacc 代表 yet another compiler compiler(编译器代码生成器)。lex和yacc在UNIX下分别叫flex和bison. 简单地理解下lex&yacc, lex词法分析器,读取文件中的关键词(后面说到的token标记其实可看做关键词);然后把关键词递交给yacc,yacc对一些关键词进行匹配,看它们是否符合一定的语法逻辑,如果符合就进行相应动作

  我们使用lex&yacc写编译器,所以先来学习下lex&yacc吧。

 

第二节、一个简单的lex程序。

  跟着笔者将下面的程序编译运行一遍,相信你会有所收获。

1. 程序代码。

  文件名:test.l
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 第一段 */
%{
    int chars = 0;
    int words = 0;
    int lines = 0;
%}
 
/* 第二段 */ 
%%
[a-zA-Z]+  { words++; chars += strlen(yytext); }
\n         { chars++; lines++; }
.          { chars++; }
%%
 
/* 第三段 */ 
main(int argc, char **argv)
{
    yylex();
    printf("%8d%8d%8d\n", lines, words, chars);
}

分析:

  这段lex程序的作用是:根据输入的字符串,输出其行数、单词数和字符的个数。

(1) %%把文件分为3段,第一段是c和lex的全局声明,第二段是规则段,第三段是c代码。

(2) 第一段的c代码要用%{和%}括起来,第三段的c代码不用。

(3) 第二段是规则段,[a-zA-Z]+  \n   . 是正则表达式,表示匹配的内容,{}内的是c编写的动作。

  上面程序中yytext是lex变量,匹配模式的文本存储在这一变量中。yylex()这一函数开始分析,它由lex自动生成。关于lex变量和函数后续介绍,这里只是通过简单的lex程序来认识lex. 

2、按照下面过程编译运行。

#flex test.l

#gcc lex.yy.c –lfl

#./a.out

然后输入一段文字,按ctrl+d结束输入,则会输出行数,单词数和字符的个数。

编译过程和运行结果等见下图:

image

 

第三节、lex进阶。

  修改第二节程序,将正则表达式放在全局声明中,使逻辑更清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%{
int chars = 0;
int words = 0;
int lines = 0;
%}
 
mywords [a-zA-Z]+
mylines \n
mychars . 
 
%%
{mywords}  { words++; chars += strlen(yytext); }
{mylines}  { chars++; lines++; }
{mychars}  { chars++; }
%%
 
main(int argc, char **argv)
{
  yylex();
  printf("%8d%8d%8d\n", lines, words, chars);
}

  编译运行同第二节。

 

第四节、lex再进阶—循环扫描。

  下面给出一个lex程序,这个程序在扫描到 +  或 - 时做一个特殊输出。当调用yylex()函数时,若扫描到return对应的标记时,yylex返回,且值就为return后的值;若没扫描到return对应的标记,yylex继续执行,不返回。下次调用自动从前一次的扫描位置处开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
%{
enum yytokentype
{
    ADD = 259,
    SUB = 260,
};
%}
  
myadd   "+"
mysub   "-"
myother .
  
%%
{myadd}    { return ADD; }
{mysub}    { return SUB; }
{myother}  { printf("Mystery character\n"); }
%%
  
main(int argc, char **argv)
{
    int tok;
  
    while (tok = yylex())
    {                          
        if (tok == ADD || tok == SUB)
        {
            printf("meet + or -\n");
        }
        else
        {
            printf("this else statement will not be printed, \
                   because if yylex return,the retrun value must be ADD or SUB.");
        }
    }
}

 编译同上,运行结果见下图:

image

   

  到现在,仅仅介绍了lex,后续会介绍yacc.


0 0
原创粉丝点击