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++; }
%%

/* 第三段 */  
main(int argc, char **argv)
{
    yylex();
    printf("%8d%8d%8d\n", lines, words, chars);
}

程序中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
25
26
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
25
26
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
25
26
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
5
6
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
5
6
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
25
26
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
25
26
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
25
26
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变量和和函数。

 
 
分类: 编译原理

lex与yacc快速入门的更多相关文章

  1. Yacc 与 Lex 快速入门

    Yacc 与 Lex 快速入门 Lex 与 Yacc 介绍 Lex 和 Yacc 是 UNIX 两个非常重要的.功能强大的工具.事实上,如果你熟练掌握 Lex 和 Yacc 的话,它们的强大功能使创建 ...

  2. Yacc 与 Lex 快速入门(词法分析和语法分析)

    我们知道,高级语言,一般的如c,Java等是不能直接运行的,它们需要经过编译成机器认识的语言.即编译器的工作. 编译器工作流程:词法分析.语法分析.语义分析.IR(中间代码,intermediate ...

  3. Lex和Yacc入门

     Lex和Yacc入门 标签: lexyacc 2013-07-21 23:02 584人阅读 评论(0) 收藏 举报  分类: Linux(132)  原文地址:http://coanor.blog ...

  4. Go 快速入门

    入门 Go 语言需要多久?答案是 -- 读完这篇文章的时间!不妨找一个周末的下午,踏上 Go 之旅吧! 更新记录: 2016.12.12: 完成重制 2016.11.02: 增加重点理解和参考链接 2 ...

  5. Web Api 入门实战 (快速入门+工具使用+不依赖IIS)

    平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...

  6. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  7. 前端开发小白必学技能—非关系数据库又像关系数据库的MongoDB快速入门命令(2)

    今天给大家道个歉,没有及时更新MongoDB快速入门的下篇,最近有点小忙,在此向博友们致歉.下面我将简单地说一下mongdb的一些基本命令以及我们日常开发过程中的一些问题.mongodb可以为我们提供 ...

  8. 【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  9. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

随机推荐

  1. OR1200数据Cache运用情景分析

    以下摘录<步骤吓得核心--软-core处理器的室内设计与分析>一本书 13.7DCache使用情景之中的一个--存储指令运行阶段DCache失靶 存储指令运行阶段DCache失靶这样的情景 ...

  2. open-flash-chart2各种效果

    <pre class="html" name="code"><pre class="html" name="co ...

  3. boadWorld Mark!

    2014-11-21 xiazaiba.com>jiaocheng>972.html

  4. 绑定枚举到dropdownlist

    pageTools.BindEnumToDropdownList(typeof(enumDealerArea), ddlBmwArea, new ListItem("--请选择--" ...

  5. css3简单几步画一个乾坤图

    原文:[原创]css3简单几步画一个乾坤图 效果如上,鼠标移上去会有动画. 代码如下非常简单: <html> <head> <style> .outer{heigh ...

  6. 从WebBrowser中取得Cookie 和 WebClient设置cookie!

    原文:从WebBrowser中取得Cookie 和 WebClient设置cookie! 从WebBrowser中取得Cookie 的代码 CookieContainer myCookieContai ...

  7. 左右lcm,gcd一些性质

    两个整数a,b  他们的最大公约数为n  最小公倍数为m  则有 a,b都能分解为有限个素数的积               12 = 2^2 * 3^1 * 5^0 , 30 = 2^1 * 3^1 ...

  8. cocos2d-x 3.0 rapidJson 解析操作应该注意的细节

    Size visibleSize = Director::getInstance()->getVisibleSize(); Point origin = Director::getInstanc ...

  9. hibernate之使用Annotation注解搭建项目

    之前开发都是使用xml配置来开发项目,开发起来特别繁琐 大家会发现通过注解大大简化了我们开发流程,使我们从繁琐的XML配置中解放出来. 第一步:新建一个javaweb项目.并将hibernate需要的 ...

  10. orleans开篇之hello world

    orleans开篇之hello world 什么是orleans Orleans是一个建立在.NET之上的,设计的目标是为了方便程序员开发需要大规模扩展的云服务.Orleans项目基本上被认为是并行计 ...