最近一直在學習Scala語言,偶然發現其Parser模塊功能強大,乃爲BNF而設計。啥是BNF,讀大學的時候在課本上見過,那時候只以爲這個東西太深奧。沒想到全部的計算機語言都是基於BNF而定義的一套規範。詞法,語法,詞法,語法。。。下面看看解析C++類聲明的一個簡單例子吧。
class CPlusPlusParser extends StandardTokenParsers{ //分隔符,用於repsep,和其它顯示的地方 lexical.delimiters += (":","::","<",">","(",")","&","{","}",";",",","~") //關鍵字集合,全部在解析方法中,以字符串形式出現的單詞,都必須加入保留字集合,保留字大部分屬於關鍵字 lexical.reserved += ("class","public","private","protected","operator","const","mutable","static") // 注意: 詞法分析過程當中,會自動刪除空白,註釋等沒必要要的內容。 /** * 解析類,包含7個部分,解析的時候是按照順序嚴格的匹配。 *1. class 關鍵字 *2. ident 標識符 被解析爲類的名稱 *3. opt(parserBaseClasses) 可選的基類集合 *4. { 類定義開始 *5.opt(parserClassBody) 可選的類內容,若是沒有,就是一個空類了。 *6. } 和 ; 類定於的結束標記. * */ def parserClass : Parser[Any] = { "class"~ident~opt(parserBaseClasses)~"{"~opt(parserClassBody)~"}"~";" } /** * 解析基類集合 * 1. : 分隔符,用於分割類名稱和基類集合,若是沒有改分隔符則代表該類沒有基類。 * 2. repsep(parserOneBaseClass,",") 解析一個或者多個基類,C++支持多繼承,每一個繼承以逗號(,)分割 */ def parserBaseClasses : Parser[Any] ={ ":"~repsep(parserOneBaseClass,",") } /** * 解析單一繼承 *1.opt("public"|"private"|"protected") 沒有包含範圍修飾符時,C++默認爲private繼承 *2.parserType 基類名稱 */ def parserOneBaseClass : Parser[Any] ={ opt("public"|"private"|"protected")~parserType } /** * 解析類型 * 1.rep(parserTypeNamespace) 可選的名稱前綴,例如std::string,std::tr1::shared_ptr,包含了名稱前綴 * 2.ident 類型名稱 * 4.opt("<"~repsep(parserType,",")~">") 模板類型,及其嵌套解析,在此屬於遞歸解析 * * 次類型沒有考慮解析「unsigned」 數據類型 */ def parserType : Parser[Any] ={ rep(parserTypeNamespace)~ident~opt("<"~repsep(parserType,",")~">") } /* * 解析單一名稱空間*/ def parserTypeNamespace : Parser[Any] ={ ident~"::" } /* * 解析類的內容,類的成員,若是沒有public,private,protected等修飾符,則爲默認private * 1.rep(parserFun|parserField) 解析可能包含的默認的private範圍的方法和字段 * 2.rep(parserSection) 後續可能包含其餘public,private,protected修飾的字段。 * * 例如一個類能夠包含public:private: 等多個不一樣的範圍修飾段 * */ def parserClassBody : Parser[Any] = { rep(parserFun|parserField)~rep(parserSection) } /** * 解析每個具體的範圍訪問段。多是public、private或者protected,而且包含一些列的方法和字段 */ def parserSection : Parser[Any] = { ("public"|"private"|"protected")~":" ~rep(parserFun|parserField) } /* * 解析方法的聲明,在此沒有解析方法的定義,比較複雜。 * 1. opt(opt("virtual")~(parserReturnValue|"~")) 方法的返回值,之因此使用opt,是由於構造函數沒有返回值,~用於析構函數的解析 * 2. ident 方法名稱,在此沒有解析操做符重載方法,若是須要,須要另外單獨定義, * 3. "("~repsep(parserFunParam,",")~")" 解析參數列表,不支持 (void) 模式的參數,請使用()替代(void) * 4. opt("const") 可選的const修飾符 * 5.; 函數聲明結束 * * 沒有包含靜態方法(static),很容易根據此模板寫出來 * */ def parserFun : Parser[Any] ={ opt(opt("virtual")~(parserReturnValue|"~"))~ident~"("~repsep(parserFunParam,",")~")"~opt("const")~";" } /** * 解析返回值 * 1.包含可選的const修飾符 * 2.返回值的具體類型 * 3.包含可選的引用 */ def parserReturnValue : Parser[Any] ={ opt("const")~parserType~opt("&") } /** * 解析一個函數參數,數據類型與 parserReturnValue,不過多了參數名稱和可選的默認值 */ def parserFunParam : Parser[Any] = { opt("const")~parserType~opt("&")~ident~opt("="~(numericLit|stringLit|ident)) // 默認參數支持false,true,數字,字符串 } /* * 解析字段定義 * 1.可選的字段修飾符 * 2.字段數據類型,不支持unsigned,相對容易。在此不給出 * 3.ident 字段名稱 * 4.; 字段定義結束*/ def parserField : Parser[Any] ={ opt("const"|"mutable"|"static") ~parserType~ident~";" } def parserAll[T]( p : Parser[T], input :String) = { phrase(p)( new lexical.Scanner(input)) } } object CPlusPlusParser { def main( args : Array[String]) { val c = new CPlusPlusParser val r = c.parserAll(c.parserClass, """ |class MyClass : public A, public N { |int a; |void SetA( int v ); |int GetA()const; |public : |int a; |void SetA( int v ); |int GetA()const; |}; """.stripMargin) println(r) /* 測試輸出 [11.11] parsed: ((((((class~MyClass)~Some((:~List((Some(public)~((List()~A)~None)), (Some(public)~((List()~N)~None))))))~{)~Some((List((((None~((List()~int)~None))~a)~;), ((((((Some((None~((None~((List()~void)~None))~None)))~SetA)~()~List(((((None~((List()~int)~None))~None)~v)~None)))~))~None)~;), ((((((Some((None~((None~((List()~int)~None))~None)))~GetA)~()~List())~))~Some(const))~;))~List(((public~:)~List((((None~((List()~int)~None))~a)~;), ((((((Some((None~((None~((List()~void)~None))~None)))~SetA)~()~List(((((None~((List()~int)~None))~None)~v)~None)))~))~None)~;), ((((((Some((None~((None~((List()~int)~None))~None)))~GetA)~()~List())~))~Some(const))~;)))))))~})~;) Process finished with exit code 0 */ } }
後續目標是分析頭文件,提早全部類和枚舉的定義。自動轉換爲protobuf接口,而且自動生成protobuf消息和類之間進行編解碼的接口。還能夠以類爲藍本生成其它語言的對象及其與protobuf消息之間的編解碼。這樣之後在涉及到客戶機和服務器通訊的時候,大部分的業務數據對象都只要寫一次,其它自動生成,並小改動。php