您的位置:首页 > 其它

lex与yacc快速入门

2014-03-12 22:12 197 查看

lex与yacc快速入门 【原创】

声明:原创文章,转载注明出处http://www.cnblogs.com/lucasysfeng/

联系作者:lucasysfeng@gmail.com

第一节、lex和yacc是什么?

  lex 代表 lexical analyzar(词法分析器),yacc 代表 yet another compiler compiler(编译器代码生成器)。lex和yacc在UNIX下分别叫flex和bison. 可以搜索到很多介绍flex&bison的文章,但这类文章对初学者来说不太容易看懂。

  我们举个简单的例子来理解lex和yacc:在linux下,有很多系统配置文件,一些linux下的软件也有配置文件,那么程序是如何读取配置文件中的信息的呢?先用到lex词法分析器,读取配置文件中的关键词(后面说到的token标记其实可看做关键词);然后把关键词递交给yacc,yacc对一些关键词进行匹配,看是否符合一定的语法逻辑,如果符合就进行相应动作。

  上面举的例子是分析配置文件内容的,当然可分析其他文件内容,或者制作编译器等。

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

1、程序代码。

来看一个简单的lex程序,代码见下面,这段lex程序的目的是:输入几行字符串,输出行数,单词数和字符的个数。

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
/*******************************************
* Name        : test.l
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : 一个简单的lex例子,输入几行字符串,
*               输出行数,单词数和字符的个数。
*******************************************/

/* 第一段 */
%{
int chars = 0;
int words = 0;
int lines = 0;
%}

/* 第二段 */

%%
[a-zA-Z]+ { words++; chars += strlen(yytext); }
\n { chars++; lines++; }
. { chars++; }
%%

[code]/* 第三段 */

main(int argc, char **argv)
{
yylex();
printf("%8d%8d%8d\n", lines, words, chars);
}
[/code]
程序中yytext是lex变量,匹配模式的文本存储在这一变量中。yylex()这一函数开始分析,它由lex自动生成。关于lex变量和函数后续再介绍,这里只是通过简单的lex程序来认识lex.

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

#flex test.l

#gcc lex.yy.c –lfl

#./a.out

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

见下图:



3、分析上面的lex程序。

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

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

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

4、编译时不加-lfl选项。

  上面编译时用gcc lex.yy.c –lfl,那么如何直接用gcc lex.yy.c进行编译呢?答案是加上yywrap函数(具体原因见lex的库和函数分析),代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2526
27
28
29
30
/*******************************************
* Name        : test.l
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : 一个简单的lex例子,输入几行字符串,
*               输出行数,单词数和字符的个数。
*               加yywrap函数。
*******************************************/

%{
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);
}

int yywrap()
{
return 1;
}

第三节、lex进阶。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2526
27
28
/*******************************************
* Name        : test.l
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : 一个简单的lex例子,输入几行字符串,
*               输出行数,单词数和字符的个数。
*               正则表达式放在全局声明中。
*******************************************/

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
2526
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*******************************************
* Name        : test.l
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : lex进阶,循环扫描。
*******************************************/

%{
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.");
}
}
}
编译和运行见下图:





第五节、yacc语法。

1、yacc语法规则部分和BNF类同,先来看BNF巴克斯范式。

(1)<> 内包含的内容为必选项;

(2)[] 内的包含的内容为可选项;

(3){ } 内包含的为可重复0至无数次的项;

(4) | 表示在其左右两边任选一项,相当于"OR"的意思;

(5)::= 是“被定义为”的意思;

(6)双引号“”内的内容代表这些字符本身;而double _quote用来表示双引号。

(7)BNF范式举例,下面的例子用来定义java中的for语句:

FOR_STATEMENT ::=

  "for" "(" ( variable_declaration |

  ( expression ";" ) | ";" )

  [ expression ] ";"

  [ expression ]

  ")" statement

2、yacc语法。

注:components是根据规则放在一起的终端和非终端符号,后面是{}括起来的执行的动作。

3、语法例子。

1
2
3
4
5
param : NAME EQ NAME {
printf("\tName:%s\tValue(name):%s\n", $1,$3); }
| NAME EQ VALUE {
printf("\tName:%s\tValue(value):%s\n",$1,$3);}
;
yacc文件第一段中定义的token,lex文件对目标进行扫描并返回这些token。yacc文件对规则冒号右边componets进行匹配,如果符合一定语法规则就执行相应动作。

1
2
3
4
56
7
8
9
10
11
simple_sentence: subject verb object
|     subject verb object prep_phrase ;
subject:    NOUN
|     PRONOUN
|     ADJECTIVE subject ;
verb:       VERB
|     ADVERB VERB
|     verb VERB ;
object:     NOUN
|     ADJECTIVE object ;
prep_phrase:     PREPOSITION NOUN ;
分析:|表示左右两边任选一项,如| subject verb object prep_phrase ;中|的左边为空,所以该句表示匹配空或者subject verb object prep_phrase ;而上面还有一句subject verb object ,所以

simple_sentence: subject verb object

| subject verb object prep_phrase ;

的意思是匹配subject verb object 或 subject verb object prep_phrase ;

第六节、lex和yacc结合使用。

1、lex程序。

当匹配a b c not时分别返回相应的token.

1
2
3
4
56
7
8
9
10
11
12
13
14
15
16
17
18
19
/*******************************************
* Name        : test.l
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc结合使用。
*******************************************/

%{
#include "test.tab.h"
#include <stdio.h>
#include <stdlib.h>
%}

%%
a   { return A_STATE; }
b   { return B_STATE; }
c   { return C_STATE; }
not { return NOT; }
%%
2、yacc程序。

当扫描到A_STATE B_STATE时打印1,当扫描到
A_STATE B_STATE
c_state_not_token时打印2,当扫描到NOT时打印3.


其中,
A_STATE B_STATE NOT是token,c_state_not_token
是非终端符号,此处定义为


c_state_not_token : C_STATE {}.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2526
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*******************************************
* Name        : test.y
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc结合使用。
*******************************************/
%{
#include <stdio.h>
#include <stdlib.h>
%}

%token  A_STATE B_STATE C_STATE NOT

%%
program :
A_STATE B_STATE
{
printf("1");
}
c_state_not_token
{
printf("2");
}
|    NOT
{
printf("3");
}
c_state_not_token : C_STATE {}
%%

yyerror(const char *s)
{
fprintf(stderr, "error: %s\n", s);
}

int main()
{
yyparse();
return 0;
}
3、编译和运行。

lex和yacc在UNIX下分别叫flex和bison.





第七节、lex和yacc结合使用进阶。

1、我们希望用lex和yacc结合完成下面文件解析。

我们对文本test.txt进行分析,test.txt中的内容如下:

ZhangSan=23
LiSi=34
WangWu=43

扫描test.txt文本后,我们希望输出:

ZhangSan is 23 years old!!!

LiSi is 34 years old!!!

WangWu is 43 years old!!!

2、利用lex扫描test.txt文本,返回token.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2526
27
28
29
/*******************************************
* Name        : test.l
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc结合使用进阶。
*******************************************/

%{
#include "test.tab.h"
#include <stdio.h>
#include <string.h>
%}

char [A-Za-z]
num [0-9]
eq [=]
name {char}+
age {num}+

%%
{name}         { yylval = strdup(yytext); return NAME; }
{eq}          { return EQ; }
{age}          { yylval = strdup(yytext); return AGE; }
%%

int yywrap()
{
return 1;
}
3、yacc根据lex返回的token,判断这些token是否符合一定的语法,符合则进行相应动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2526
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*******************************************
* Name        : test.y
* Date        : Mar. 11, 2014
* Blog        : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc结合使用进阶。
*******************************************/

%{
#include <stdio.h>
#include <stdlib.h>
typedef char* string;
#define YYSTYPE string
%}
%token NAME EQ AGE

%%
file : record file
| record
;
record : NAME EQ AGE {
printf("%s is %s years old!!!\n", $1, $3); }
;
%%

int main()
{
extern FILE* yyin;
if (!(yyin = fopen("test.txt", "r")))
{
perror("cannot open parsefile:");
return -1;
}

yyparse();
fclose(yyin);
return 0;
}
int yyerror(char *msg)
{
printf("Error encountered: %s \n", msg);
}
4、编译运行。





补充:lex变量和和函数。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: