lex yacc 入門教程

Augusdi發表於2017-03-20

宣告:原創作品,轉載註明出處http://www.cnblogs.com/vestinfo/

一、簡介

推薦書籍《flex&bison》.

在UNIX下是flex和bison.網上介紹很多,大部分是寫給懂的人看的,初學者一頭霧水。這樣來理解lex和yacc可能容易些:在linux下,有很多系統配置檔案,一些linux下的軟體也有配置檔案,那麼程式是如何讀取配置檔案中的資訊的呢?

首先用到lex詞法分析器,讀取配置檔案中的關鍵詞(後面說到的token標記其實可看做關鍵詞)。然後把關鍵詞

遞交給yacc,yacc對一些關鍵詞進行匹配,看是否符合一定語法邏輯,如果符合就進行相應動作。

上面舉得例子是分析配置檔案內容的,當然可分析其他檔案內容。

 

二、一個簡單的lex檔案例子

1、來看flex&bison這本書開篇給出的例子:輸入幾行字串,輸出行數,單詞數和字元的個數。

關於yylex即lex中相關變數系列3文章介紹。

/* just like Unix wc */
%{
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);
}

2、按照下面過程編譯。

#flex test.l

#gcc lex.yy.c –lfl

#./a.out

 image9

 

3、分析這個簡單的lex檔案:

(1)%%把檔案分為3段,第一段是c和lex的全域性宣告,第二段是規則段,第三段是c程式碼。

(2)第一段的c程式碼要用%{和%}括起來,第三段的c程式碼不用。

(3)第二段規則段,[a-zA-Z]+  \n   . 是正規表示式,{}內的是c編寫的動作。

關於正規表示式系列3文章介紹。

 

4、如果不用-lfl選項,程式碼可以為下面這樣(具體原因見lex的庫和函式分析):

int chars = 0;
int words = 0;
int lines = 0;
int yywrap();
%}
%%
[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;
}
三、修改第一個例子,將正規表示式放在全域性宣告中

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

編譯一同上。

四、The Scanner as Coroutine(協同程式)

即怎樣將掃描到的標記給其他程式使用,下面的例子,希望掃描到+ 或 -時做一個特殊輸出。

當呼叫yylex時,若掃描到return對應的標記時,yylex返回,且值就為return後的值;

若沒掃描到return對應的標記,yylex繼續執行,不返回。

下次呼叫自動從前一次的掃描位置處開始。

%{
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()) {				//yylex的返回值只能是ADD 或 SUB.
		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.");}
	}
}

 

image21

 

五、yacc —— unix下是bison

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語法。

result: components { /*
        action to be taken in C */ }
        ;

(1)components是根據規則放在一起的終端和非終端符號,後面是{}括起來的執行的動作。

3、語法例子。

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

(1)理解 |  的意思,|表示左右兩邊任選一項,如| 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 ;

六、flex和bison相結合。

test.l

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

test.y

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

編譯:

image

 

 

七、檔案資訊分析。

tset.l分析test.txt檔案中的關鍵詞(即test.y中的token標記),遇到token返回給test.y,test.y判斷

是否符合一定語法,符合則進行相應動作。

test.l

%{
#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;
}
test.y
%{
#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);
}
test.txt
ZhangSan=23
LiSi=34
WangWu=43

編譯

image

八、token定義的標記的型別及union的使用。

token定義的標記的型別預設為int 且 預設賦值從258開始。如上面的例子,在生成的標頭檔案

test.tab.h中有如下預編譯,

/* Tokens.  */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
   /* Put the tokens into the symbol table, so that GDB and other debuggers
      know about them.  */
   enum yytokentype {
     NAME = 258,
     EQ = 259,
     AGE = 260
   };
#endif

如果想將token標記定義為其他型別呢?首先將型別定義在聯合中,

%union {
   char *str;
   int  num;
   struct { int num1; int num2; } dnum;
}

然後,如下定義,

%token <str> K_HOST K_ERROR
%token <str> WORD PATH STRING
%token <num> NUM 
%token <dnum> DNUM 

補充 :$$ $1 $2….

Each symbol in a bison rule has a value; the value of the target symbol (the one to the
left of the colon) is called $$ in the action code, and the values on the right are numbered
$1, $2, and so forth, up to the number of symbols in the rule.

$$——表示冒號的左邊符號;$1——冒號右邊第一個;$2——冒號右邊第二個,依此類推。

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

匹配NAME EQ AGE後,$1即NAME所表示的內容,$3即AGE所表示的內容。

lex yacc 入門教程(3)正規表示式和lex變數及函式

 imageimageimage

參考:http://www.ibm.com/developerworks/cn/linux/sdk/lex/#resources