如何用flex+bison寫語法分析器

背景

這個星期,項目中要使用C++或C語言解析JSON格式的數據,把解析的結果放到一個通用的數據結構。這個通用的數據結構,其實是做爲web服務層(這一層你們能夠認爲是相似於PHP服務器或webpy的服務器容器)到web頁面層(這一層是語法相似PHP腳本或者tornardo模板)的數據傳輸的協議。 之因此要這樣處理, 主要是由於這個web類的項目(通常的web類項目也是如此)需求變化較快,而web的服務層使用是採用C++進行開發的,爲了使當web服務層的數據格式變化不影響web頁面層,因此雙方使用統一的通用的數據結構。而之因此交代這麼多的背景是, 爲了讓你們瞭解爲何咱們不使用相似rapidjson或jsoncpp來實現json的解析而須要手寫解析器。 由於使用相似rapidJson或者是jsoncpp之類的Json解析器,至關於咱們要作: java

 JSON文檔 -> json DOM -> 通用數據結構。  ios

而若是手寫解析器,只須要作: web

JSON文檔 -> 通用數據結構。 正則表達式

少一層轉換能換來不少效率的提高。 json

說了這麼多,下面開始進入正題。   之前學編譯原理的時候,老師推薦過LEX /YACC來寫編譯器,其實這是古老的UNIX軟件。 LINUX上有他們的GNU版本 FLEX、BISON。 這兩個東西一個是詞法分析器,一個是語法分析器。詞法分析器的做用是把字符解析成單詞。通常的把單詞稱爲token, 而語法分析器則是把單詞解析成語法樹。 api

詞法分析

首先來看flex的使用:簡單來講分爲兩步: 1 先定義一個flex的輸入文件,描述詞法。2 用flex程序處理這個文件,生成對應的C語言源代碼文件。 服務器

 通常flex的輸入文件以.l文件結尾, 好比這個文件json.l。  數據結構


%{

#define YYSTYPE _EasyTData*
#include <iostream>
#include "stdio.h"
#include "easytdata.h"
#include "json.y.hpp"


%}

int [+-]*[0-9]+ 
num [+-]*([0-9]|\.)* 
string \"(\\.|[^\\"])*\"
ignore_char [ \t\r\n]
identifier [a-zA-Z_][a-zA-Z0-9_]*

%%
{identifier} {
        if(strcmp(yytext,"true")==0)
        {
            json2tdata_lval=ed_factory_bool(true);
            return TRUE;
        }
        else if(strcmp(yytext,"false")==0)
        {
            json2tdata_lval=ed_factory_bool(false);
            return FALSE;
        }
        else if(strcmp(yytext,"null")==0)
        {
            json2tdata_lval=ed_factory_none();
            return NIL;
        }
        else
        {
            json2tdata_lval=ed_factory_string(yytext);
            return IDENTIFIER;
        }
}

{num}  {
        json2tdata_lval=ed_factory_int(atoi(yytext));
        return NUM;
    }/*要區分浮點數*/


{string}  {
    /*去掉先後引號的處理,存到TData裏面不須要引號*/
    /**/
    json2tdata_pre_process_string(yytext);
    json2tdata_lval=ed_factory_string(yytext);
    return STRING;
}
"{" {return L_BRACE;}
"}" {return R_BRACE;}
"[" {return L_BRACKET;}
"]" {return R_BRACKET;}
":" {return COLON;}
";" {return SEMICOLON;}
"," {return COMMA;}
{ignore_char}
%%


文件分紅三個部分。第一部分是從%{到 }%標記的部分。 這個部分會原封不動的複製到flex的生成代碼中。文件開頭定義了一個YYSTYPE宏。每一個TOKEN能夠有一個lval值屬性,YYSTYPE定義類型就是token的lval的類型。_EasyTData是咱們的web服務層和web頁面層公用的通用數據結構。而後就是一些要include的頭文件,第一部分就完了。 ide

flex的輸入文件的第二部分,是從%}到%%之間的部分,這部分用正則表達式定義了一些數據類型。 好比int num string ignore_char identifier等。 int型的定義就是(+-號)後面跟着一些重複的數字。注意這裏使用的正則表達式的形式是ERE而不是BRE。 ERE與BRE比較明顯的區別就是,ERE使用+表示字符重複一次以上,*表示字符重複0次以上。BRE使用{1,}這種方式表示字符重複1次以上。 函數

flex的輸入文件的第三部分,是%%到%%的部分。這裏定義了詞法分析器在解析的處理動做。yytext是一個flex內部的標識符,表示匹配到的字符串。上文介紹了,lval也是一個內部標識符,表示TOKEN的值。json2tdata_是標識符的前綴, 在執行flex的時候,用-P指定。

flex輸入文件寫完以後,使用下面這條命令,就能夠把flex的輸入文件轉換爲C語言的源代碼了。
flex -P"json2tdata_" -o json.l.cpp json.l

語法分析

語法分析是使用bison工具。使用bison工具也是分爲兩步,第一步寫bison的輸入文件,第二步用bison程序生成C語言源碼。

bison的輸入文件通常用.y做爲後綴名,好比下面這個json.y, 看下bison的輸入文件長什麼樣子。


%{
  	
  	#include "stdio.h"
  	#include "easytdata.h"
//	#define YYDEBUG 1
	#define YYSTYPE _EasyTData*
	  	
	extern int json2tdata_lex();
	void json2tdata_error(const char*msg);
	
	_EasyTdata *g_oJsonData; //結果存放點  %}
%token INT NUM STRING IGNORE_CHAR L_BRACE R_BRACE L_BRACKET R_BRACKET COLON SEMICOLON COMMA IDENTIFIER TRUE FALSE NIL
%%

Json	: Value {g_oJsonData=$1;/*printf("=========\nResult ToJson():%s",g_oJsonData.ToJson().c_str());*/}

Object	: L_BRACE Pairs R_BRACE {$$=$2;}
		| L_BRACE R_BRACE {$$=ed_factory_map();}

Array	: L_BRACKET Elements R_BRACKET {$$=$2;}
		| L_BRACKET R_BRACKET {$$=ed_factory_vector();}

ID		: NUM {$$=$1;}
		| STRING {$$=$1;}
		| IDENTIFIER{$$=$1;}

Pair	: ID COLON Value 
     {
        
        $$ = ed_factory_pair($1, $3);  
    }

Pairs	: Pairs COMMA Pair {
            ed_map_add_pair($1,$3);
            $$ = $1;
        }
		| Pair {
                  
            $$=ed_factory_map();
            ed_map_add_pair($$, $1);
        }


Value	: NUM {
            $$=$1;
        }
		| STRING {
            $$=$1;

        }
		| Object {$$=$1;}
		| Array {$$=$1;}
		| FALSE {
            $$=$1;
            }
		| TRUE {
            $$=$1;
            }
		| NIL {
            $$=$1;
       
        }

Elements	: Elements COMMA Value {
            ed_vector_add($1,$3);
            $$ = $1;

        }
		| Value {
            $$ = ed_factory_vector();
            ed_vector_add($$,$1);
        }

%%


和flex的詞法分析輸入文件相似,bison的輸入文件也是分紅3部分。第一部分%{和%}之間,是原封不動拷貝到輸出的C語言源文件中的。 json2tdata_lex這個函數是flex生成的。 json2tdata_error是用來處理錯誤信息的函數。經過定義和實現這個函數你能夠把錯誤信息寫到任何地方。與flex相似,json2tdata也是自定義的前綴。

第二部分是%token INT NUM STRING IGNORE_CHAR L_BRACE R_BRACE L_BRACKET R_BRACKET COLON SEMICOLON COMMA IDENTIFIER TRUE FALSE NIL這一行, 這一行的做用就是聲明在flex中定義的那些TOKEN。

第三部分是%% %%包圍的部分。這部分就是語法的推導過程。  能夠比較輕鬆的看出,這部分主要就是採用BNF對語法進行描述。好比Array, 它有兩種形式。 第一種是 L_BRACKET ELEMENTS R_BRACKET, 第二種則是L_BRACKET R_BRACKET, 這表示一個空的Array。Bison可以徹底支持LR(1)文法。 這種文法的特色是隻要多向前看一個TOKEN,就可以決定如何解析。 所以若是bison告訴你語法ambiguous的時候,能夠想想如何把本身的文法改爲LR(1)型文法。另外,每一條規則的後面能夠用{}來定義解析的動做。bison用$$表示規則左邊的對象,用$1 $2 $3 等依次表示規則右邊的對象。

好比:


Elements	: Elements COMMA Value {
            
            ed_vector_add($1,$3);
            $$ = $1;

        }
在執行這條規則的時候,就會用ed_vector_add函數將 Value加入到  Elements中去,而後把$1賦值給$$。


bison的輸入文件能夠用下面這樣的命令轉換成C語言的源文件:


bison -d -o json.y.cpp json.l -p"json2tdata_"


-p "json2tdata_"是給語法分析器加一個前綴。 有這個選項,就會生成json2tdata_parse等以json2tdata開頭的函數。

總結

1 用flex+bison能夠本身寫語法分析器。對於程序效率要求高的地方,能夠考慮這麼作。

2 用java的同窗若是也要寫語法分析,能夠考慮用javacc。

相關文章
相關標籤/搜索