跟我一起写编译器(一)——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吧。
跟着笔者将下面的程序编译运行一遍,相信你会有所收获。
1. 程序代码。
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结束输入,则会输出行数,单词数和字符的个数。
编译过程和运行结果等见下图:
修改第二节程序,将正则表达式放在全局声明中,使逻辑更清晰。
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程序,这个程序在扫描到 + 或 - 时做一个特殊输出。当调用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.");
}
}
}
|
编译同上,运行结果见下图:
到现在,仅仅介绍了lex,后续会介绍yacc.