iQuery是一個開源的自動化測試框架項目,有興趣的朋友能夠在這裏下載:https://github.com/vowei/iQuery/downloadscss
源碼位置:https://github.com/vowei/iQueryhtml
在上一篇文章中,簡單介紹了iQuery解析器的詞法分析部分,本文接着將語法分析部分解釋完畢,閱讀完本文後,應該能夠將iQuery擴展到其餘編程語言上。git
下面是iQuery的完整語法(其實能夠把它看成一個廣義的正則表達式對待):
https://github.com/vowei/iQuery/blob/master/iQuery.g github
antlr支持EBNF語法,也就是說它支持可選和重複的元素,如:
正則表達式
1: selectors
2: : multi_selectors multi_selectors*
3: ;
上面的語法表示selectors由一到多個multi_selectors組成,由於在EBNF語法裏,沒有相似正則表達式的「+」號操做符,因此使用「multi_selectors multi_selectors*」這種形式描述。express
而:編程
1: selector
2: : selector_expression
3: (
4: ('+' selector_expression)
5: |
6: ('~' selector_expression)
7: )?
8: ;
在antlr裏,語法定義的規則通常是,小寫字母組成的單詞是語法的組成部份,而標記(Token)都是由大寫字母組成,如:框架
1: selector_expression
2: : atom
3: | ':' indexop '(' INTEGER ')'
4: | ':' NOT '(' selectors ')'
5: | ':' HAS '(' selectors ')'
6: | ':' CONTAINS '(' QUOTED_STRING ')'
7: ….
8: ;
上面的語法裏,大寫的字母例如「INTEGER」、「NOT」、「HAS」、「CONTAINS」和「QUOTED_STRING」都是標記(Token),這些標記都是從詞法分析器解析完輸入字符串以後產生的標記流中獲得。而小寫字母組成的單詞例如「selector_expression」、「atom」、「indexop」和「selectors」都是語法的組成部份,這些元素的定義均可以在語法定義源文件中找到。編程語言
Antlr生成的語法解析器是一個LL(*)的編譯器,LL類型的編譯器是一個自頂向下進行語法分析的編譯器,這種編譯方式跟手寫編譯器進行語法解析的方式很像,所以相對於yacc等LR類型(自底向上)的編譯器更容易理解。函數
好比說,以「#ABC」這個iQuery查詢語句爲例,antlr生成的解析器,也就是iQuery解釋器碰到這個字符串之後,它依照一個自頂向下的順序進行語法匹配:
1. 首先從「query」這個語法元素開始,因爲「query」元素的兩個子元素只有第一個「selectors NEWLINE* EOF」成功匹配(這是由於第二個元素的開頭就是換行符,跟「#ABC」沒法匹配),因此解析器再接着進入「selectors」的定義嘗試匹配。
2. 「selectors」的定義裏「multi_selectors」也僅僅是一個語法元素,裏面沒有任何詞法標記(Token),所以解析器遞歸向下匹配語法元素,匹配順序以下:
query -> selectors -> multi_selectors -> selector -> selector_expression
3. 當匹配到selector_expression時,由於其可選的語法子句裏有詞法標記,並且「# ELEMENT」能夠徹底匹配「#ABC」這個輸入字符串,這樣在selector_expression處就成功匹配了輸入字符串,而且「selector_expression」沒有更多的語法規則須要輸入字符串匹配,所以解析器退出「selector_expression」這個語法規則,發現上一層規則「selector」也沒有更多的語法規則須要匹配,遞歸上溯,直到「query」規則。
4. 在「query」規則裏,匹配完「selector」子規則後,後一個是可選的換行符 - 「NEWLINE*」,因爲輸入字符串中沒有換行符,因此跳過這個規則,碰到最後一個規則「EOF」-表示字符串或者文件的結尾。由於在「selector_expression」裏已經由「’#’ ELEMENT」匹配完整個字符串了,沒有更多的字符留下。到這裏,語法解析器就認爲執行了一次成功的匹配,而輸入字符串「#ABC」是一個合法的輸入。
好比說,針對「> :first [‘value’]」這個查詢,iQuery解析器依照自頂向下的方式進行匹配:
1. 首先匹配字符「>」,匹配順序是「query」-> 「selectors」 -> 「multi_selectors」-> 「'>’ selector」。
2. 匹配到「'>’ selector」這個規則時,由於「'>’」後面必須跟一個知足「selector」規則的字符串。
3. 解析器繼續用「:first [‘value’]」試圖匹配「selector」這個規則,這時的匹配順序是「selector」 -> 「selector_expression」-> 「’:’ FIRST」。
4. 「’:’ FIRST」這個規則消化掉「:first」字符串,因爲輸入的iQuery字符串還剩下「[‘value’]」,而「selector_expression」規則已經沒有多餘的子規則了,解析器上溯,上溯的順序是:「selector_expression」 -> 「'>' selector」-> 「multi_selectors」-> 「selectors」。
5. 在「selectors」這個語法規則裏,前面的匹配步驟只消化掉「multi_selectors multi_selectors*」裏的第一個規則「multi_selectors」,還剩下第二個規則「multi_selectors*」沒有匹配,所以解析器使用輸入字符串剩下的字符匹配第二個規則。
6. 匹配的順序是:「multi_selectors」 -> 「selector」 -> 「multi_attributes」 -> 「'['」。
7. 在此次匹配過程當中,因爲剩下的字符串是「[‘value’]」(注意value周邊的單引號),沒有任何一個「multi_attributes」的子規則匹配這段字符串,並且也沒法回溯,所以一個語法錯誤發生了,解析器會拋出一個語法錯誤的異常信息,這個異常信息有點晦澀,須要作二次處理才能讓iQuery使用者明白語法錯誤緣由 – 語法錯誤的處理在後文會講到。
從上面關於語法匹配的描述能夠看出,這個過程跟一個函數遞歸調用很是相似,實際上,對於語法定義文件中的每個語法規則(例如「query」、「multi_selectors」等規則),antlr都會爲其生成一個函數調用(例如函數query()、multi_selectors()等,並且antlr還提供了給生成的函數傳入參數,設置和獲取函數返回值的手段,參數的聲明語法跟指定語言的語法是一致的。
好了,語法方面的解釋就暫時寫到這裏,下文講解Java版和JavaScript版的iQuery解析器的具體實現。