lex與yacc快速入門


第一節、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變量和和函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM