這個星期,項目中要使用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。