三、解析QueryParser.jj
3.1、聲明QueryParser類
在QueryParser.jj文件中,PARSER_BEGIN(QueryParser)和PARSER_END(QueryParser)之間,定義了QueryParser類。
其中最重要的一個函數是public Query parse(String query)函數,也即我們解析Lucene查詢語法的時候調用的函數。
這是一個純Java代碼定義的函數,會直接拷貝到QueryParser.java文件中。
parse函數中,最重要的一行代碼是調用Query res = TopLevelQuery(field),而TopLevelQuery函數是QueryParser.jj中定義的語法分析器被JavaCC編譯后會生成的函數。
3.2、聲明詞法分析器
在解析詞法分析器之前,首先介紹一下JavaCC的詞法狀態的概念(lexical state)。
有可能存在如下的情況,在不同的情況下,要求的詞法詞法規則不同,比如我們要解析一個java文件(即滿足java語法的表達式),在默認的狀態 DEFAULT下,是要求解析的對象(即表達式)滿足java語言的詞法規則,然而當出現"/**"的時候,其后面的表達式則不需要滿足java語言的語 法規則,而是應該滿足java注釋的語法規則(要識別@param變量等),于是我們做如下定義:
//默認處于DEFAULT狀態,當遇到/**的時候,轉換為IN_JAVADOC_COMMENT狀態 <DEFAULT> TOKEN : {<STARTDOC : “/**” > : IN_JAVADOC_COMMENT } //在IN_JAVADOC_COMMENT狀態下,需要識別@param變量 <IN_JAVADOC_COMMENT> TOKEN : {<PARAM : "@param" >} //在IN_JAVADOC_COMMENT狀態下,遇到*/的時候,裝換為DEFAULT狀態 <IN_JAVADOC_COMMENT> TOKEN : {<ENDDOC: "*/">: DEFAULT } |
<*> 表示應用于任何狀態。
(1) 應用于所有狀態的變量
<*> TOKEN : { <#_NUM_CHAR: ["0"-"9"] > //數字 | <#_ESCAPED_CHAR: "\\" ~[] > //"\"后的任何一個字符都是被轉義的 | <#_TERM_START_CHAR: ( ~[ " ", "\t", "\n", "\r", "\u3000", "+", "-", "!", "(", ")", ":", "^", "[", "]", "\"", "{", "}", "~", "*", "?", "\\" ] | <_ESCAPED_CHAR> ) > //表達式中任何一個term,都不能以[]括起來的列表中的lucene查詢語法關鍵字開頭,當然被轉義的除外。 | <#_TERM_CHAR: ( <_TERM_START_CHAR> | <_ESCAPED_CHAR> | "-" | "+" ) > //表達式中的term非起始字符,可以包含任何非語法關鍵字字符,轉義過的字符,也可以包含+, -(但包含+,-的符合詞法,不合語法)。 | <#_WHITESPACE: ( " " | "\t" | "\n" | "\r" | "\u3000") > //被認為是空格的字符 | <#_QUOTED_CHAR: ( ~[ "\"", "\\" ] | <_ESCAPED_CHAR> ) > //被引號括起來的字符不應再包括"和\,當然轉義過的除外。 } |
(2) 默認狀態的Token
<DEFAULT> TOKEN : { <AND: ("AND" | "&&") > | <OR: ("OR" | "||") > | <NOT: ("NOT" | "!") > | <PLUS: "+" > | <MINUS: "-" > | <LPAREN: "(" > | <RPAREN: ")" > | <COLON: ":" > | <STAR: "*" > | <CARAT: "^" > : Boost //當遇到^的時候,后面跟隨的是boost表達式,進入Boost狀態 | <QUOTED: "\"" (<_QUOTED_CHAR>)* "\""> | <TERM: <_TERM_START_CHAR> (<_TERM_CHAR>)* > | <FUZZY_SLOP: "~" ( (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? )? > //Fuzzy查詢,~后面跟小數。 | <PREFIXTERM: ("*") | ( <_TERM_START_CHAR> (<_TERM_CHAR>)* "*" ) > //使用*進行Prefix查詢,可以盡包含*,或者末尾包含*,然而只包含*符合詞法,不合語法。 | <WILDTERM: (<_TERM_START_CHAR> | [ "*", "?" ]) (<_TERM_CHAR> | ( [ "*", "?" ] ))* > //使用*和?進行wildcard查詢 | <RANGEIN_START: "[" > : RangeIn //遇到[]的時候,是包含邊界的Range查詢 | <RANGEEX_START: "{" > : RangeEx //遇到{}的時候,是不包含邊界的Range查詢 } |
<Boost> TOKEN : { <NUMBER: (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? > : DEFAULT //boost是一個小數 } |
//包含邊界的Range查詢是[A TO B]的形式。 <RangeIn> TOKEN : { <RANGEIN_TO: "TO"> | <RANGEIN_END: "]"> : DEFAULT | <RANGEIN_QUOTED: "\"" (~["\""] | "\\\"")+ "\""> | <RANGEIN_GOOP: (~[ " ", "]" ])+ > } |
//不包含邊界的Range查詢是{A TO B}的形式 <RangeEx> TOKEN : { <RANGEEX_TO: "TO"> | <RANGEEX_END: "}"> : DEFAULT | <RANGEEX_QUOTED: "\"" (~["\""] | "\\\"")+ "\""> | <RANGEEX_GOOP: (~[ " ", "}" ])+ > } |
3.3、聲明語法分析器
Lucene的語法規則如下:
Query ::= ( Clause )* Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")" ) |
(1) 從Query到Clause
一個Query查詢語句,是由多個clause組成的,每個clause有修飾符Modifier,或為+, 或為-,clause之間的有連接符,或為AND,或為OR,或為NOT。
在Lucene的語法解析中NOT被算作Modifier,和-起相同作用。
此過程表達式如下:
Query TopLevelQuery(String field) : { Query q; } { q=Query(field) <EOF> { return q; } } |
Query Query(String field) : { List<BooleanClause> clauses = new ArrayList<BooleanClause>(); Query q, firstQuery=null; int conj, mods; } { //查詢語句開頭是一個Modifier,可以為空 //Modifier后面便是子語句clause,可以生成子查詢語句q mods=Modifiers() q=Clause(field) { //如果第一個語句的Modifier是空,則將子查詢q付給firstQuery,從后面我們可以看到,當只有一個查詢 語句的時候,如果其Modifier為空,則不返回BooleanQuery,而是返回子查詢對象firstQuery。從這里我們可以看出,如果查詢語 句為"A",則生成TermQuery,其term為"A",如果查詢語句為"+A",則生成BooleanQuery,其子查詢只有一個,就是 TermQuery,其term為"A"。 addClause(clauses, CONJ_NONE, mods, q); if (mods == MOD_NONE) firstQuery=q; } ( //除了第一個語句外,其他的前面可以有連接符,或為AND,或為OR。 //如果在第一個語句之前出現連接符,則報錯,如"OR a",會報Encountered " <OR> "OR "" at line 1, column 0. //除了連接符,也會有Modifier,后面是子語句clause,生成子查詢q,并加入BooleanQuery中。 conj=Conjunction() mods=Modifiers() q=Clause(field) { addClause(clauses, conj, mods, q); } )* { //如果只有一個查詢語句,且其modifier為空,則返回firstQuery,否則由所有的子語句clause,生成BooleanQuery。 if (clauses.size() == 1 && firstQuery != null) return firstQuery; else { return getBooleanQuery(clauses); } } } |
int Modifiers() : { //默認modifier為空,如果遇到+,就是required,如果遇到-或者NOT,就是prohibited。 int ret = MOD_NONE; } { [ <PLUS> { ret = MOD_REQ; } | <MINUS> { ret = MOD_NOT; } | <NOT> { ret = MOD_NOT; } ] { return ret; } } |
//連接符 int Conjunction() : { int ret = CONJ_NONE; } { [ <AND> { ret = CONJ_AND; } | <OR> { ret = CONJ_OR; } ] { return ret; } } |
(2) 一個子語句clause
由上面的分析我們可以知道,JavaCC使用的是編譯原理里面的自上而下分析法,基本采用的是LL(1)的方法:
- 第一個L :從左到右掃描輸入串
- 第二個L :生成的是最左推導
- (1):向前看一個輸入符號(lookahead)
JavaCC還提供LOOKAHEAD(n),也即當僅讀入下一個符號時,不足以判斷接下來的如何解析,會出現Choice Conflict,則需要多讀入幾個符號,來進一步判斷。
Query Clause(String field) : { Query q; Token fieldToken=null, boost=null; } { //此處之所以向前看兩個符號,就是當看到<TERM>的時候,不知道它是一個field,還是一個term,當<TERM><COLON>在一起的時候,說明<TERM>代表一個field, 否則代表一個term [ LOOKAHEAD(2) ( fieldToken=<TERM> <COLON> {field=discardEscapeChar(fieldToken.image);} | <STAR> <COLON> {field="*";} ) ] ( //或者是一個term,則由此term生成一個查詢對象 //或者是一個由括號括起來的子查詢 //()?表示可能存在一個boost,格式為^加一個數字 q=Term(field) | <LPAREN> q=Query(field) <RPAREN> (<CARAT> boost=<NUMBER>)? ) { //如果存在boost,則設定查詢對象的boost if (boost != null) { float f = (float)1.0; try { f = Float.valueOf(boost.image).floatValue(); q.setBoost(f); } catch (Exception ignored) { } } return q; } } |
Query Term(String field) : { Token term, boost=null, fuzzySlop=null, goop1, goop2; boolean prefix = false; boolean wildcard = false; boolean fuzzy = false; Query q; } { ( ( //如果term僅結尾包含*則是prefix查詢。 //如果以*開頭,或者中間包含*,或者結尾包含*(如果僅結尾包含,則prefix優先)則為wildcard查詢。 term=<TERM> | term=<STAR> { wildcard=true; } | term=<PREFIXTERM> { prefix=true; } | term=<WILDTERM> { wildcard=true; } | term=<NUMBER> ) //如果term后面是~,則是fuzzy查詢 [ fuzzySlop=<FUZZY_SLOP> { fuzzy=true; } ] [ <CARAT> boost=<NUMBER> [ fuzzySlop=<FUZZY_SLOP> { fuzzy=true; } ] ] { //如果是wildcard查詢,則調用getWildcardQuery, // *:*得到MatchAllDocsQuery,將返回所有的文檔 // 目前不支持最前面帶通配符的查詢(雖然詞法分析和語法分析都能通過),否則報ParseException // 最后生成WildcardQuery //如果是prefix查詢,則調用getPrefixQuery,生成PrefixQuery //如果是fuzzy查詢,則調用getFuzzyQuery,生成FuzzyQuery //如果是普通查詢,則調用getFieldQuery String termImage=discardEscapeChar(term.image); if (wildcard) { q = getWildcardQuery(field, termImage); } else if (prefix) { q = getPrefixQuery(field, discardEscapeChar(term.image.substring(0, term.image.length()-1))); } else if (fuzzy) { float fms = fuzzyMinSim; try { fms = Float.valueOf(fuzzySlop.image.substring(1)).floatValue(); } catch (Exception ignored) { } if(fms < 0.0f || fms > 1.0f){ throw new ParseException("Minimum similarity for a FuzzyQuery has to be between 0.0f and 1.0f !"); } q = getFuzzyQuery(field, termImage,fms); } else { q = getFieldQuery(field, termImage); } } //包含邊界的range查詢,取得[goop1 TO goop2],調用getRangeQuery,生成TermRangeQuery | ( <RANGEIN_START> ( goop1=<RANGEIN_GOOP>|goop1=<RANGEIN_QUOTED> ) [ <RANGEIN_TO> ] ( goop2=<RANGEIN_GOOP>|goop2=<RANGEIN_QUOTED> ) <RANGEIN_END> ) [ <CARAT> boost=<NUMBER> ] { if (goop1.kind == RANGEIN_QUOTED) { goop1.image = goop1.image.substring(1, goop1.image.length()-1); } if (goop2.kind == RANGEIN_QUOTED) { goop2.image = goop2.image.substring(1, goop2.image.length()-1); } q = getRangeQuery(field, discardEscapeChar(goop1.image), discardEscapeChar(goop2.image), true); } //不包含邊界的range查詢,取得{goop1 TO goop2},調用getRangeQuery,生成TermRangeQuery | ( <RANGEEX_START> ( goop1=<RANGEEX_GOOP>|goop1=<RANGEEX_QUOTED> ) [ <RANGEEX_TO> ] ( goop2=<RANGEEX_GOOP>|goop2=<RANGEEX_QUOTED> ) <RANGEEX_END> ) [ <CARAT> boost=<NUMBER> ] { if (goop1.kind == RANGEEX_QUOTED) { goop1.image = goop1.image.substring(1, goop1.image.length()-1); } if (goop2.kind == RANGEEX_QUOTED) { goop2.image = goop2.image.substring(1, goop2.image.length()-1); } q = getRangeQuery(field, discardEscapeChar(goop1.image), discardEscapeChar(goop2.image), false); } //被""括起來的term,得到phrase查詢,調用getFieldQuery | term=<QUOTED> [ fuzzySlop=<FUZZY_SLOP> ] [ <CARAT> boost=<NUMBER> ] { int s = phraseSlop; if (fuzzySlop != null) { try { s = Float.valueOf(fuzzySlop.image.substring(1)).intValue(); } catch (Exception ignored) { } } q = getFieldQuery(field, discardEscapeChar(term.image.substring(1, term.image.length()-1)), s); } ) { if (boost != null) { float f = (float) 1.0; try { f = Float.valueOf(boost.image).floatValue(); } catch (Exception ignored) { } // avoid boosting null queries, such as those caused by stop words if (q != null) { q.setBoost(f); } } return q; } } |
此處需要詳細解析的是getFieldQuery:
protected Query getFieldQuery(String field, String queryText) throws ParseException { //需要用analyzer對文本進行分詞 TokenStream source; try { source = analyzer.reusableTokenStream(field, new StringReader(queryText)); source.reset(); } catch (IOException e) { source = analyzer.tokenStream(field, new StringReader(queryText)); } CachingTokenFilter buffer = new CachingTokenFilter(source); TermAttribute termAtt = null; PositionIncrementAttribute posIncrAtt = null; int numTokens = 0; boolean success = false; try { buffer.reset(); success = true; } catch (IOException e) { } //得到TermAttribute和PositionIncrementAttribute,此兩項將決定到底產生什么樣的Query對象 if (success) { if (buffer.hasAttribute(TermAttribute.class)) { termAtt = buffer.getAttribute(TermAttribute.class); } if (buffer.hasAttribute(PositionIncrementAttribute.class)) { posIncrAtt = buffer.getAttribute(PositionIncrementAttribute.class); } } int positionCount = 0; boolean severalTokensAtSamePosition = false; boolean hasMoreTokens = false; if (termAtt != null) { try { //遍歷分詞后的所有Token,統計Tokens的個數numTokens,以及positionIncrement的總數,即positionCount。 //當有一次positionIncrement為0的時候,severalTokensAtSamePosition設為true,表示有多個Token處在同一個位置。 hasMoreTokens = buffer.incrementToken(); while (hasMoreTokens) { numTokens++; int positionIncrement = (posIncrAtt != null) ? posIncrAtt.getPositionIncrement() : 1; if (positionIncrement != 0) { positionCount += positionIncrement; } else { severalTokensAtSamePosition = true; } hasMoreTokens = buffer.incrementToken(); } } catch (IOException e) { } } try { //重設buffer,以便生成phrase查詢的時候,term和position可以重新遍歷。 buffer.reset(); source.close(); } catch (IOException e) { } if (numTokens == 0) return null; else if (numTokens == 1) { //如果分詞后只有一個Token,則生成TermQuery String term = null; try { boolean hasNext = buffer.incrementToken(); term = termAtt.term(); } catch (IOException e) { } return newTermQuery(new Term(field, term)); } else { //如果分詞后不只有一個Token if (severalTokensAtSamePosition) { //如果有多個Token處于同一個位置 if (positionCount == 1) { //并且處于同一位置的Token還全部處于第一個位置,則生成BooleanQuery,處于同一位置的Token之間是OR的關系 BooleanQuery q = newBooleanQuery(true); for (int i = 0; i < numTokens; i++) { String term = null; try { boolean hasNext = buffer.incrementToken(); term = termAtt.term(); } catch (IOException e) { } Query currentQuery = newTermQuery(new Term(field, term)); q.add(currentQuery, BooleanClause.Occur.SHOULD); } return q; } else { //如果有多個Token處于同一位置,但不是第一個位置,則生成MultiPhraseQuery。 //所謂MultiPhraseQuery即其可以包含多個phrase,其又一個ArrayList<Term[]> termArrays,每一項都是一個Term的數組,屬于同一個數組的Term表示在同一個位置。它有函數void add(Term[] terms)一次添加一個數組的Term。比如我們要搜索"microsoft app*",其表示多個phrase,"microsoft apple","microsoft application"都算。此時用QueryParser.parse("\"microsoft app*\"")從而生成PhraseQuery是搜不出microsoft apple和microsoft application的,也不能搜出microsoft app,因為*一旦被引號所引,就不算通配符了。所以必須生成MultiPhraseQuery,首先用add(new Term[]{new Term("field", "microsoft")})將microsoft作為一個Term數組添加進去,然后用add(new Term[]{new Term("field", "app"), new Term("field", "apple"), new Term("field", "application")})作為一個Term數組添加進去(算作同一個位置的),則三者都能搜的出來。 MultiPhraseQuery mpq = newMultiPhraseQuery(); mpq.setSlop(phraseSlop); List<Term> multiTerms = new ArrayList<Term>(); int position = -1; for (int i = 0; i < numTokens; i++) { String term = null; int positionIncrement = 1; try { boolean hasNext = buffer.incrementToken(); assert hasNext == true; term = termAtt.term(); if (posIncrAtt != null) { positionIncrement = posIncrAtt.getPositionIncrement(); } } catch (IOException e) { } if (positionIncrement > 0 && multiTerms.size() > 0) { //如果positionIncrement大于零,說明此Term和前一個Term已經不是同一個位置 了,所以原來收集在multiTerms中的Term都算作同一個位置,添加到MultiPhraseQuery中作為一項。并清除 multiTerms,以便重新收集相同位置的Term。 if (enablePositionIncrements) { mpq.add(multiTerms.toArray(new Term[0]),position); } else { mpq.add(multiTerms.toArray(new Term[0])); } multiTerms.clear(); } //將此Term收集到multiTerms中。 position += positionIncrement; multiTerms.add(new Term(field, term)); } //當遍歷完所有的Token,同處于最后一個位置的Term已經收集到multiTerms中了,把他們加到MultiPhraseQuery中作為一項。 if (enablePositionIncrements) { mpq.add(multiTerms.toArray(new Term[0]),position); } else { mpq.add(multiTerms.toArray(new Term[0])); } return mpq; } } else { //如果不存在多個Token處于同一個位置的情況,則直接生成PhraseQuery PhraseQuery pq = newPhraseQuery(); pq.setSlop(phraseSlop); int position = -1; for (int i = 0; i < numTokens; i++) { String term = null; int positionIncrement = 1; try { boolean hasNext = buffer.incrementToken(); assert hasNext == true; term = termAtt.term(); if (posIncrAtt != null) { positionIncrement = posIncrAtt.getPositionIncrement(); } } catch (IOException e) { } if (enablePositionIncrements) { position += positionIncrement; pq.add(new Term(field, term),position); } else { pq.add(new Term(field, term)); } } return pq; } } } |
----------------------------------------------------------------------------------------------------------
相關文章:
http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623594.html
http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623596.html
http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623597.html
http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623599.html
http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661436.html
http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661439.html
http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661440.html
http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661441.html
http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661442.html
Lucene學習總結之五:Lucene段合并(merge)過程分析
http://www.cnblogs.com/forfuture1978/archive/2010/03/06/1679501.html
http://www.cnblogs.com/forfuture1978/archive/2010/03/07/1680007.html
http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704242.html
http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704245.html
http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704250.html
http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704254.html
http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704258.html
http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704263.html