|
JavaCC 簡(jiǎn)介
許 多基于 Web 的項(xiàng)目都包含即席(ad-hoc)查詢系統(tǒng)以允許終端用戶搜索信息。因此,終端用戶會(huì)需要某種語(yǔ)言來(lái)表達(dá)他們所希望搜索的內(nèi)容。以前,用戶查詢語(yǔ)言的定義 極其簡(jiǎn)單。如果終端用戶滿足于使用與最典型的 Google 搜索一般簡(jiǎn)單的語(yǔ)言,那么 Java 的 StringTokenizer 對(duì)于解析任務(wù)就綽綽有余了。然而,如果用戶希望有一種更健壯的語(yǔ)言,比如要添加括號(hào)和"AND"/"OR"邏輯,那么我們很快就會(huì)發(fā)現(xiàn)我們需要更強(qiáng)大的工 具。我們需要一種方法,用以首先定義用戶將要使用的語(yǔ)言,然后用該定義解析相應(yīng)的條目并且對(duì)各種后端數(shù)據(jù)庫(kù)制定正確的查詢。 這就是工具 JavaCC 出現(xiàn)的原因。JavaCC 代表"Java® Compiler Compiler",是對(duì) YACC("Yet Another Compiler Compiler")的繼承(YACC 是 AT&T 為了構(gòu)建 C 和其他高級(jí)語(yǔ)言解析器而開(kāi)發(fā)的一個(gè)基于 C 的工具)。YACC 和其伙伴詞法記號(hào)賦予器(tokenizer)——"Lex"——接收由常用的巴科斯-諾爾范式(Backus-Nauer form,又稱(chēng) Bacchus Normal Form,BNF)形式的語(yǔ)言定義的輸入,并生成一個(gè)"C"程序,用以解析該語(yǔ)言的輸入以及執(zhí)行其中的功能。JavaCC 與 YACC 一樣,是為加快語(yǔ)言解析器邏輯的開(kāi)發(fā)過(guò)程而設(shè)計(jì)的。但是,YACC 生成 C 代碼,而 JavaCC 呢,正如您想像的那樣,JavaCC 生成的是 Java 代碼。 JavaCC 的歷史極具傳奇色彩。它起源于 Sun 公司的"Jack"。Jack 后來(lái)輾轉(zhuǎn)了幾家擁有者,比如著名的 Metamata 和 WebGain,最后變成了 JavaCC,然后又回到了 Sun。Sun 公司最后在 BSD 的許可下將它作為開(kāi)放源代碼的代碼發(fā)布。JavaCC 目前的 Web 主頁(yè)是 http://javacc.net.。 JavaCC 的長(zhǎng)處在于它的簡(jiǎn)單性和可擴(kuò)展性。要編譯由 JavaCC 生成的 Java 代碼,無(wú)需任何外部 JAR 文件或目錄。僅僅用基本的 Java 1.2 版編譯器就可以進(jìn)行編譯。而該語(yǔ)言的布局也使得它易于添加產(chǎn)生式規(guī)則和行為。該 Web 站點(diǎn)甚至描述了如何編制異常以便給出用戶合適的語(yǔ)法提示。 問(wèn)題定義 讓 我們假設(shè)您有一位客戶在一個(gè)出租視頻節(jié)目的商店里,該商店擁有一個(gè)簡(jiǎn)單的電影數(shù)據(jù)庫(kù)。該數(shù)據(jù)庫(kù)包含表 MOVIES、ACTORS 和 KEYWORDS。MOVIES 表列舉他商店中每部電影的相關(guān)數(shù)據(jù),即如每部電影的名稱(chēng)和導(dǎo)演等內(nèi)容。ACTORS 表列舉每部電影中的演員姓名。而 KEYWORDS 表則列舉描述電影的詞語(yǔ),例如"action"、"drama"、"adventure"等等。 客戶希望能夠?qū)υ摂?shù)據(jù)庫(kù)發(fā)出稍微復(fù)雜的查詢。例如,他想輸入以下形式的查詢 actor = "Christopher Reeve" and keyword=action and keyword=adventure 并且希望返回由 Christopher Reeve 主演的 Superman 系列電影。他還希望像下面這樣用括號(hào)來(lái)說(shuō)明求值次序以區(qū)分查詢 (actor = "Christopher Reeve" and keyword=action) or keyword=romance 這樣可能返回不是由 Christopher Reeve 主演的電影 actor = "Christopher Reeve" and (keyword=action or keyword=romance) 這樣則總會(huì)返回 Christopher Reeve 主演的電影。 解決方案 對(duì)于該任務(wù),您將分兩個(gè)階段來(lái)定義解決方案。在第 1 階段中,您將用 JavaCC 定義語(yǔ)言,要確保能夠正確解析終端用戶的查詢。在第 2 階段中,您將向 JavaCC 代碼添加行為以產(chǎn)生 DB2® SQL 代碼,從而確保返回正確的電影來(lái)響應(yīng)終端用戶的查詢。 階段 1 - 定義用戶的查詢語(yǔ)言 將在名為 UQLParser.jj 的文件里定義該語(yǔ)言。該文件將被 JavaCC 工具編譯成為一組 .java 類(lèi)型的 Java 類(lèi)文件。要在 JJ 文件中定義語(yǔ)言,您需要做以下 5 件事: 定義解析環(huán)境 定義"空白" 定義"標(biāo)記(token)" 按照標(biāo)記定義語(yǔ)言本身的語(yǔ)法 定義每個(gè)解析階段中將發(fā)生的行為 您 可以通過(guò)所展示的代碼段定義自己的 UQLParser.jj 文件,也可以通過(guò)本文的相關(guān)代碼進(jìn)行效仿。對(duì)于步驟 1 到 4,在 JavaCCPaper/stage1/src 中使用 UQLParser.jj 的副本。而步驟 5 則在 JavaCCPaper/stage2/src 中進(jìn)行。樣本數(shù)據(jù)庫(kù)的 DDL 可以在 JavaCCPaper/moviedb.sql 中找到。如果使用相同的用戶標(biāo)識(shí)創(chuàng)建數(shù)據(jù)庫(kù)和運(yùn)行解析器,該實(shí)例將運(yùn)行得最好。Ant 文件(build.xml)可用于加快編譯過(guò)程。 步驟 1. 定義解析環(huán)境 JavaCC .jj 文件通過(guò)執(zhí)行 JavaCC 將被轉(zhuǎn)換為 .java 文件。JavaCC 將獲取 .jj 文件里 PARSER_BEGIN 與 PARSER_END 的中間部分并將之復(fù)制到 Java 結(jié)果文件中。作為解析器設(shè)計(jì)者,您可以將解析前后所有與解析器有關(guān)的動(dòng)作置于該文件中。您還可以在其中將 Java 代碼鏈接到步驟 4 和 5 中將會(huì)定義的解析器動(dòng)作上。 在以下所示的實(shí)例中,解析器相對(duì)比較簡(jiǎn)單。構(gòu)造函數(shù) UQLParser 接收一個(gè)字符串輸入,通過(guò) Java 的 java.io.StringReader 類(lèi)將其讀入,然后調(diào)用另一個(gè)不可見(jiàn)的構(gòu)造函數(shù)將 StringReader 強(qiáng)制轉(zhuǎn)換為 Reader。這里定義的惟一其他方法就是 static main 方法,該方法將在調(diào)用構(gòu)造函數(shù)之后再調(diào)用迄今還未定義的名為 parse() 的方法。 正如您可能已猜到的,JavaCC 已經(jīng)提供了一個(gè) Java Reader 類(lèi)的構(gòu)造函數(shù)。而我們添加了基于字符串的構(gòu)造函數(shù),以便易于使用和測(cè)試。 清單 1. 解析器的 Java 環(huán)境 PARSER_BEGIN(UQLParser) 步驟 2. 定義空白 在該語(yǔ)言中,您希望將空格、跳格、回車(chē)和換行作為分隔符處理,而不是將其忽略。這些字符都被稱(chēng)為 空白 。在 JavaCC 中,我們?cè)?SKIP 區(qū)域中定義這些字符,如清單 2 中所示。 清單 2. 在 SKIP 區(qū)域中定義空白 /** Skip these characters, they are considered "white space" **/ 步驟 3. 定義標(biāo)記 接下來(lái),您將定義該語(yǔ)言所識(shí)別的標(biāo)記。 標(biāo)記 是將對(duì)解析程序有意義的解析字符串的最小單位。掃描輸入字符串以及判斷是何標(biāo)記的過(guò)程稱(chēng)作 記號(hào)賦予器(tokenizer) 。在以下查詢中, actor = "Christopher Reeve" 其標(biāo)記為 actor = "Christopher Reeve" 在 您的語(yǔ)言中,您要將 actor 和等號(hào)(=)作為該語(yǔ)言中的保留標(biāo)記,盡管字 if 和 instanceof 在 Java 語(yǔ)言中都是帶有特殊意義的保留標(biāo)記。通過(guò)保留字和其他特殊標(biāo)記,程序員希望解析器會(huì)逐字地識(shí)別這些字并為其指派特定的意義。如果您正在保留這些字,請(qǐng)繼續(xù) 進(jìn)行下去并且保留不等號(hào)(<>)和左右括號(hào)。還要保留名稱(chēng)、導(dǎo)演和關(guān)鍵字以表示用于用戶搜索的特定字段。 要定義所有這些 內(nèi)容,請(qǐng)使用 JavaCC 的 TOKEN 指令。每個(gè)標(biāo)記的定義都用尖括號(hào)(< 和 >)括起來(lái)。在冒號(hào)(:)的左邊給出標(biāo)記的名稱(chēng),并在右邊給出正則表達(dá)式。正則表達(dá)式是定義將要匹配的文本部分的方式。在其最簡(jiǎn)單的形式中,正則表 達(dá)式可以匹配精確的字符序列。使用下列代碼來(lái)定義六個(gè)匹配精確字的標(biāo)記和四個(gè)匹配符號(hào)的標(biāo)記。當(dāng)分析器看到任何一個(gè)字時(shí),將會(huì)用符號(hào) AND、OR、TITLE、ACTOR、DIRECTOR 或 KEYWORD 來(lái)加以匹配。在匹配符號(hào)之后,解析器將相應(yīng)地返回 LPAREN、RPAREN、EQUALS 或 NOTEQUAL。清單 3 展示了 JavaCC 保留標(biāo)記的定義。 清單 3. 定義保留標(biāo)記 TOKEN: /*RESERVED TOKENS FOR UQL */ 對(duì)于像"Christopher Reeve"一樣的字符串,您或許無(wú)法在我們的語(yǔ)言中將所有的演員姓名存儲(chǔ)為保留字。但是,您可以通過(guò)使用正則表達(dá)式定義的字符模式識(shí)別 STRING 或 QUOTED_STRING 類(lèi)型的標(biāo)記。 正則表達(dá)式 是定義匹配模式的字符串。定義匹配所有字符串或引用字符串的正則表達(dá)式要比定義精確的字匹配更具技巧性。 您將定義一個(gè)由一個(gè)或更多字符系列構(gòu)成的字符串(STRING),其中的有效字符為大小寫(xiě)的 A 到 Z 以及數(shù)字 0 到 9。為了簡(jiǎn)單起見(jiàn),不考慮定影明星或電影名稱(chēng)的重音字符或其他不規(guī)則體。您可以按下列方式將該模式寫(xiě)為一個(gè)正則表達(dá)式。 <STRING : (["A"-"Z", "a"-"z", "0"-"9"])+ > 加號(hào)表示圍在括號(hào)中的模式(從 A 到 Z、a 到 z 或 0 到 9 中的任何字符)可依次出現(xiàn)一次或多次。在 JavaCC 中,您還可以用星號(hào)(*)來(lái)表示模式的零次或多次出現(xiàn)以及用問(wèn)號(hào)(?)來(lái)表示 0 或 1 此重復(fù)。 QUOTED_STRING 就更具技巧性了。如果您定義一個(gè)以引號(hào)開(kāi)頭,以引號(hào)結(jié)尾并在其中包含任何其他字符的字符串,那么該字符串就是一個(gè) QUOTED_STRING。其正則表達(dá)式為 "\\"" (~["\\""])+ "\\"" ,這肯定有些眼花繚亂的。簡(jiǎn)單一點(diǎn)理解就是,由于引用字符本身對(duì)于 JavaCC 是有意義的,因此我們需要將對(duì)它的引用轉(zhuǎn)換為對(duì)我們的語(yǔ)言而非 JavaCC 是有意義的。為了轉(zhuǎn)換該引用,我們?cè)谒笆褂昧艘粋€(gè)反斜杠。字符顎化符號(hào)(~)意味著并非是針對(duì) JavaCC 記號(hào)賦予器的。 (~["\\""])+ 是對(duì)于一個(gè)或更多非引用字符的速寫(xiě)。合在一起, "\\"" (~["\\""])+ "\\"" 就意味著"一個(gè)引用加上一個(gè)或更多非引用再加上一個(gè)引用"。 您必須在保留字規(guī)則之后添加 STRING 和 QUOTED_STRING 的記號(hào)賦予器規(guī)則。保持該次序是極其重要的,因?yàn)橛浱?hào)賦予器規(guī)則在文件中出現(xiàn)的次序就是應(yīng)用標(biāo)記規(guī)則的次序。您需要確定"title"是被視作保留字而非 字符串的。清單 4 中顯示了 STRING 和 QUOTED_STRING 標(biāo)記的完整定義。 清單 4. 定義 STRING 和 QUOTED_STRING TOKEN : 步驟 4. 按照標(biāo)記定義語(yǔ)言 既然已經(jīng)定義了標(biāo)記,那么現(xiàn)在是時(shí)候按照標(biāo)記來(lái)定義解析規(guī)則了。用戶輸入表達(dá)式形式的查詢。一個(gè) 表達(dá)式 就是一系列由 布爾運(yùn)算符 and 或 or 連接的一個(gè)或更多查詢項(xiàng)。 為了表達(dá)這一點(diǎn),我們需要編寫(xiě)一個(gè)解析規(guī)則,也稱(chēng)作 產(chǎn)生式 。將清單 5 中的產(chǎn)生式寫(xiě)入 JavaCC UQLParser.JJ 文件。 清單 5. expression() 產(chǎn)生式 void expression() : 當(dāng)對(duì) .jj 文件運(yùn)行 Javacc 時(shí),產(chǎn)生式將被轉(zhuǎn)換為方法。所有的 JavaCC 產(chǎn)生式方法的返回都必須為空。第一組花括號(hào)包含產(chǎn)生式方法所需的所有聲明。這里暫時(shí)為空。第二組花括號(hào)包含以 JavaCC 理解的方式所寫(xiě)的產(chǎn)生式規(guī)則。請(qǐng)注意先前所定義的 AND 和 OR 標(biāo)記的用法。還請(qǐng)注意,queryTerm() 是作為方法調(diào)用而寫(xiě)的。實(shí)際上,queryTerm() 是另一個(gè)產(chǎn)生式方法。 現(xiàn)在,就讓我們定義 queryTerm() 產(chǎn)生式。queryTerm() 要么是一個(gè)單獨(dú)的判別式(例如 title="The Matrix"),要么是一個(gè)由括號(hào)括起來(lái)的表達(dá)式。JavaCC 中通過(guò) expression() 遞歸地定義了 queryTerm(),這使得您可以通過(guò)清單 6 中所示的代碼簡(jiǎn)明地總結(jié)該語(yǔ)言。 清單 6. JavaCC 中的 queryTerm() 產(chǎn)生式方法(UQLParser.jj) void queryTerm() : 這就是我們所需的所有規(guī)則。兩個(gè)產(chǎn)生式中總結(jié)了全部的語(yǔ)言解析器。 將 JavaCC 當(dāng)作測(cè)試驅(qū)動(dòng)器 在這個(gè)時(shí)候,您應(yīng)該已經(jīng)有了一個(gè)有效的 JavaCC 文件。在進(jìn)行到步驟 5 之前,您可以編譯并"運(yùn)行"該程序以查看您的解析器運(yùn)作是否正確。 隨 本文一起提供的 ZIP 文件應(yīng)包含了階段 1 的 JavaCC 示例文件 UQLParser.jj。將整個(gè) ZIP 文件解壓到一個(gè)空目錄下。要編譯 stage1/UQLParser.jj,您首先需要下載 JavaCC 并根據(jù) JavaCC Web 頁(yè) 上的指導(dǎo)進(jìn)行安裝。為了簡(jiǎn)單起見(jiàn),請(qǐng)務(wù)必將 Javacc.bat 的執(zhí)行路徑填入 PATH 環(huán)境變量中。編譯十分容易,將目錄更改為卸載 UQLParser.jj 的位置并輸入下列命令。 javacc "debug_parser " output_directory=.\\com\\demo\\stage1 UQLParser.jj 如果您愿意,也可以使用附帶的 Ant 文件 build.xml。您必須將上方的屬性文件調(diào)整為指向 JavaCC 安裝。在您第一次運(yùn)行它時(shí),JavaCC 將生成如清單 7 中所示的消息。 清單 7. UQLParser.jj 的編譯輸出 Java Compiler Compiler Version 3.2 (Parser Generator) 除了已提到的四個(gè)文件,JavaCC 還將產(chǎn)生 UQLParser.java、UQLParserConstants.java 和 UQLParserTokenManager.java。所有這些文件都被寫(xiě)入了 com\\demo\\stage1 目錄。此時(shí)起,您就能夠編譯這些文件且無(wú)需向默認(rèn)的運(yùn)行時(shí)類(lèi)路徑做任何添加了。如果 JavaCC 步驟運(yùn)行成功,Ant 文件的默認(rèn)目標(biāo)將自動(dòng)執(zhí)行 Java 編譯。如果沒(méi)有成功,您可以用以下命令編譯頂層目錄(JavaCCPaper/stage1)的文件: javac "d bin src\\com\\demo\\stage1\\*.java Java 類(lèi)文件一旦就位,您就可以通過(guò)向您所定義的 "main" java 方法輸入下列用戶示例查詢來(lái)測(cè)試新的解析器了。如果您正使用同一代碼,請(qǐng)從 JavaCCPaper/stage1 目錄開(kāi)始并在命令行中輸入下列命令。 java "cp bin com.demo.stage1.UQLParser "actor = \\"Tom Cruise\\"" 我們?cè)?JavaCC 步驟中所使用的 -debug_parser 選項(xiàng)確保將輸出下列有用的跟蹤消息,以顯示用戶查詢是如何被解析的。其輸出應(yīng)該如清單 8 中所示。 清單 8. 查詢 actor="Tom Cruise" 的 UQLParser 輸出 Call: parse 要測(cè)試帶括號(hào)表達(dá)式的遞歸路徑,請(qǐng)嘗試以下測(cè)試。 java "cp bin com.demo.stage1.UQLParser "(actor=\\"Tom Cruise\\" or actor=\\"Kelly McGillis\\") and keyword=drama" 這將產(chǎn)生清單 9 中的輸出。 清單 9. 查詢 (actor="Tom Cruise" or actor="Kelly McGillis") and keyword=drama 的 UQL1Parser 輸出 Call: parse 該輸出十分有用,因?yàn)樗菔玖送ㄟ^(guò) queryTerm 和 expression 的遞歸。queryTerm 的第一個(gè)實(shí)例實(shí)際上就是一個(gè)由兩個(gè) queryTerm 組成的表達(dá)式。 圖 1展示了該解析路徑的圖形視圖。 圖 1. 解析用戶查詢的圖形表示 ![]() 如果您對(duì)于 JavaCC 生成怎樣的 Java 代碼感到好奇,就想盡方法看一看(但不要試圖進(jìn)行任何更改?。D鷮⒄业揭韵聝?nèi)容。 UQLParser.java —— 在這一文件中,您將找到您在 UQLParser.jj 文件里的 PARSER_BEGIN 和 PARSER_END 之間所放置的代碼。您還會(huì)發(fā)現(xiàn) JJ 產(chǎn)生式方法已經(jīng)被改變?yōu)?Java 方法了。 例如,expression() 規(guī)則已將被擴(kuò)展為清單 10 中的代碼了。 清單 10. UQLParser.java static final public void expression() throws ParseException { 它與您最初在其中寫(xiě)入 queryTerm()、AND 和 OR 所呈現(xiàn)的樣子有些相像,但其余的就是 JavaCC 所添加的解析細(xì)節(jié)。 UQLParserConstants.java —— 該文件易于得到。您所定義的所有標(biāo)記都在這里。JavaCC 只不過(guò)將它們記錄在數(shù)組中并提供整型常數(shù)來(lái)引用該數(shù)組。清單 11 展示了 UQLParserConstants.java 的內(nèi)容。 清單 11. UQLParserConstants.java /* Generated By:JavaCC: Do not edit this line. UQLParserTokenManager.java —— 這是一個(gè)嵌接文件。JavaCC 將該類(lèi)用作記號(hào)賦予器。這是一段確定標(biāo)記為什么的代碼。這里讓人感興趣的首要例程是 GetNextToken。解析器產(chǎn)生式方法將用該例程來(lái)判斷采用哪條路經(jīng)。 SimpleCharStream.java —— UQLParserTokenManager 用該文件來(lái)表示將被解析的字符的 ASCII 流。 Token.java —— 其中提供了 Token 類(lèi)來(lái)表示標(biāo)記本身。本文的下一部分將演示 Token 類(lèi)的用途。 TokenMgrError.java and ParseException—— 這些類(lèi)分別表示記號(hào)賦予器和分析器中的異常狀況。 階段 2 - 給 JavaCC 代碼添加行為 注 意:關(guān)于教程的這一部分,請(qǐng)查閱代碼的 stage2 子目錄。從這里開(kāi)始所展示的 JJ 文件就是 JavaCCPaper/stage2/UQLParser.jj。為了運(yùn)行示例 SQL 查詢,您還應(yīng)該通過(guò)附帶的 moviedb.sql 文件創(chuàng)建 MOVIEDB 數(shù)據(jù)庫(kù)。請(qǐng)通過(guò) db2 -tf moviedb.sql 執(zhí)行 DDL。 既然已經(jīng)進(jìn)行了解析,我們就需要對(duì)單個(gè)表達(dá)式采取行動(dòng)了。這一階段的目標(biāo)是生成可運(yùn)行的 DB2 SQL 查詢并將返回用戶期望的結(jié)果。 該過(guò)程應(yīng)該首先從一個(gè)包含空白處的模板 select,解析器將填寫(xiě)此空白處。 清單 12中顯示了 select 模板。解析器所生成的查詢或許不像人類(lèi) DBA 所寫(xiě)的那樣為最佳的,但是它將返回終端用戶所期望的正確結(jié)果。 清單 12. select 語(yǔ)句 select TITLE, DIRECTOR 解析器填入的內(nèi)容取決于它通過(guò)記號(hào)賦予器所采用的路徑。例如,如果用戶從上面輸入查詢: (actor="Tom Cruise" or actor="Kelly McGillis") and keyword=drama" 那么解析器將根據(jù) 圖 2 在 SQL 查詢的遺漏部分中發(fā)出文本。它將回送括號(hào),輸入終端 queryTerm 的子查詢并且用 INTERSECT 代替 AND 以及 union 代替 OR。 圖 2. SQL 查詢的解析器輸出 ![]() 這將確保 SQL 查詢發(fā)出 (actor = "Tom Cruise" or actor = "Kelly McGillis") and keyword=drama 將如清單 13 中所示。 清單 13. 完整的 select 語(yǔ)句 select TITLE, DIRECTOR 正如前面提到的,很可能存在更快、更優(yōu)的方法來(lái)編寫(xiě)這個(gè)特定的查詢,但是此 SQL 將生成正確的結(jié)果。DB2 優(yōu)化器通??梢越鉀Q性能方面的不足。 因 此,需要向 JavaCC 源代碼添加什么來(lái)生成該查詢呢?您必須添加動(dòng)作和其他支持所定義語(yǔ)法的代碼。 動(dòng)作 是指為響應(yīng)特定產(chǎn)生式而執(zhí)行的 Java 代碼。在添加動(dòng)作之前,首先要添加將向調(diào)用程序返回完整 SQL 的方法。為此,要在 JavaCC 文件的最上部分添加一個(gè)名為 getSQL() 的方法。您還應(yīng)該給解析器的內(nèi)部成員添加 private StringBuffer sqlSB。該變量將表示任何解析階段的當(dāng)前 SQL 字符串。 清單 14 展示了 UQLParser.jj 的 PARSER_BEGIN/PARSER_END 部分。最后,在 main() 測(cè)試方法中添加一些代碼,用以輸出和執(zhí)行所生成的 SQL 查詢。 清單 14. PARSER_BEGIN/PARSER_END 部分 PARSER_BEGIN(UQLParser) 現(xiàn)在,填入由解析器來(lái)完成的動(dòng)作。我們將先從一個(gè)容易的開(kāi)始。在解析一個(gè)表達(dá)式的時(shí)候,解析器每當(dāng)解析 "AND"時(shí)就發(fā)出字"INTERSECT",而解析"OR"時(shí)就發(fā)出"union"。為此,要在 expression 產(chǎn)生式中的 <AND> 和 <OR> 標(biāo)記之后插入自包含的 Java 代碼塊。該代碼應(yīng)向 sqlSB StringBuffer 追加 INTERSECT 或 union。清單 15 中顯示了這些代碼。 清單 15. expression 所執(zhí)行的動(dòng)作 void expression() : queryTerm() 產(chǎn)生式內(nèi)需要執(zhí)行多個(gè)動(dòng)作。這些任務(wù)如下: 1.將搜索名稱(chēng)映射到它們各自的 DB2 表和列上 2.保存比較器(comparator)標(biāo)記 3.將比較字(comparand)轉(zhuǎn)換為 DB2 可以理解的形式,例如,除去 QUOTED_STRING 標(biāo)記的雙引號(hào) 4.向 sqlSB 發(fā)送合適的子查詢 5.對(duì)于遞歸表達(dá)式的情況,隨之發(fā)出括號(hào)。 對(duì)于所有這些任務(wù),您將需要一些局部變量。如清單 16 中所示,這些變量是在產(chǎn)生式中第一對(duì)花括號(hào)之間定義的。 清單 16. queryTerm() 的局部變量 void queryTerm() : 第一個(gè)任務(wù)可用清單 17 中的代碼來(lái)完成。設(shè)置與所遇標(biāo)記關(guān)聯(lián)的合適的 DB2 表和列。 清單 17. 將搜索名稱(chēng)映射到 DB2 ( 第二個(gè)任務(wù)可用清單 18 中的代碼來(lái)完成。保存標(biāo)記以便可用于 SQL 緩沖區(qū)。 清單 18. 保存比較器 ( tComparator=<EQUALS> | 第三個(gè)任務(wù)可用清單 19 中的代碼來(lái)完成。相應(yīng)地設(shè)置比較字的值,如果有必要,就從 QUOTED_STRING 標(biāo)記中除去雙引號(hào)。 清單 19. 準(zhǔn)備比較字 tComparand=<STRING> { 第四個(gè)任務(wù)可用清單 20 中的代碼來(lái)完成。完整的查詢項(xiàng)被追加到了 sql 緩沖區(qū)。 清單 20. 編寫(xiě) SQL 表達(dá)式 { 最后對(duì)于遞歸表達(dá)式的情況,當(dāng)解析器在表達(dá)式遞歸中看到括號(hào)時(shí),就應(yīng)該簡(jiǎn)單地進(jìn)行回送,如清單 21 中所示。 清單 21. 回送括號(hào) <LPAREN> 清單 22 展示了完整的 queryTerm() 產(chǎn)生式。 清單 22. 完整的 queryTerms() 產(chǎn)生式 /** 像前面一樣編譯并運(yùn)行 UQLParser.jj。訪問(wèn) UQLParser.java 并注意產(chǎn)生式規(guī)則是如何被整齊地插入生成代碼中的。清單 23 中展示了一個(gè) expression() 方法的擴(kuò)展實(shí)例。請(qǐng)注意 jj_consume_token 調(diào)用之后的代碼。 清單 23. UQLParser.java 中的 expression() 方法 /** 嘗試更多使用您的解析器的查詢。試一試使用 NOTEQUAL 標(biāo)記的查詢,比如 actor<>"Harrison Ford"。嘗試一些像"title="一樣的非法查詢,看看將發(fā)生什么情況。通過(guò)非常少的幾行 JavaCC 代碼,您就生成了非常有效的終端用戶查詢語(yǔ)言。 最后要考慮的問(wèn)題 JavaCC 除了提供解析器生成器之外,還提供 JJDOC 工具,用以編制巴科斯-諾爾范式(Bacchus-Nauer Form)表示的語(yǔ)法。JJDOC 可以使您易于向終端用戶提供他們所使用語(yǔ)言的描述。例如,在附帶代碼中提供的 ant 文件有一個(gè)"bnfdoc"目標(biāo)。 JavaCC 還提供名為 JJTree 的工具。該工具提供樹(shù)和節(jié)點(diǎn)類(lèi),使您易于將代碼分成單離的解析和動(dòng)作類(lèi)。繼續(xù)該實(shí)例,您可以考慮為查詢編寫(xiě)一個(gè)簡(jiǎn)單優(yōu)化器,以消除不必要的 INTERSECT 和 union。您可以通過(guò)訪問(wèn)解析樹(shù)的節(jié)點(diǎn)以及合并相似的相鄰節(jié)點(diǎn)(例如,actor="Tom Cruise" 和 actor="Kelly McGillis")來(lái)完成該工作。 JavaCC 擁有一個(gè)豐富的語(yǔ)法庫(kù)。您在自己編寫(xiě)解析器之前,一定要查看 JavaCC 的 examples 目錄,以便可能獲取已構(gòu)建好的解決方案。 請(qǐng)務(wù)必閱讀 JavaCC Web 頁(yè)上的 Frequently Asked Questions 并訪問(wèn) comp.compilers.tools.javacc 上的 javacc 新聞組以便更好地理解 JavaCC 的所有功能和特性。 結(jié)束語(yǔ) JavaCC 是一個(gè)健壯的工具,可用于定義語(yǔ)法并且易于在 Java 商業(yè)應(yīng)用程序中包含該語(yǔ)法的解析和異常處理。通過(guò)本文,我們說(shuō)明了 JavaCC 可用于為數(shù)據(jù)庫(kù)系統(tǒng)的終端用戶提供一種功能強(qiáng)大卻很簡(jiǎn)單的查詢語(yǔ)言。 參考資料 您可以參閱本文在 developerWorks 全球站點(diǎn)上的英文原文. 可在JAVACC網(wǎng)站上找到 JavaCC 程序包。 在 Johnson,Stephen C(AT&T Bell Laboratories,Murray Hill,New Jersey 07974)所寫(xiě)論文 YACC: Yet Another Compiler Compiler中第一次描述了這個(gè)可廣泛獲得的"編譯器的編譯器"。 一篇由 Oliver Ensileng 撰寫(xiě)的 JavaWorld 的好文章 Build your own Languages with JavaCC。 Jocelyn Paine 的 Introduction to JJTree。 可在 Wikipedia 中找到對(duì)于上下文無(wú)關(guān)語(yǔ)法的很好解釋。 關(guān)于作者JoAnn Brereton 是 IBM 的 Software Group,F(xiàn)ederal Software Services 的一位高級(jí)軟件工程師。她已為 IBM 編程近 20 年了。她最近的項(xiàng)目包括為 CBS、Warner Brothers 和 CNN 電視網(wǎng)構(gòu)建視檔案搜索引擎。 [ Edited by flylyke at 2005-05-07 0:32:09 AM ] http://www./blogview.asp?logID=17 |
|
|
來(lái)自: jinzq > 《我的圖書(shū)館》