小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

javacc簡(jiǎn)介

 jinzq 2007-09-25
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)

package com.demo.stage1;

import java.io.StringReader;
import java.io.Reader;

public class UQLParser {

/**
A String based constructor for ease of use.
**/
public UQLParser(String s)
{
this((Reader)(new StringReader(s)));

}

public static void main(String args[])
{
try
{
String query = args[0];
UQLParser parser = new UQLParser(query);
parser.parse();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
PARSER_END(UQLParser)

步驟 2. 定義空白

在該語(yǔ)言中,您希望將空格、跳格、回車(chē)和換行作為分隔符處理,而不是將其忽略。這些字符都被稱(chēng)為 空白 。在 JavaCC 中,我們?cè)?SKIP 區(qū)域中定義這些字符,如清單 2 中所示。

清單 2. 在 SKIP 區(qū)域中定義空白
/** Skip these characters, they are considered "white space" **/
SKIP :
{
" "
| "\\t"
| "\\r"
| "\\n"

}


步驟 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 */
{
<AND: "and">
| <OR: "or">
| <TITLE: "title">
| <ACTOR: "actor">
| <DIRECTOR: "director">
| <KEYWORD: "keyword">
| <LPAREN: "(">
| <RPAREN: ")">
| <EQUALS: "=">
| <NOTEQUAL: "<>">
}


對(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 :
{
<STRING : (["A"-"Z", "0"-"9"])+ >
<QUOTED_STRING: "\\"" (~["\\""])+ "\\"" >
}


步驟 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() :
{
}
{ queryTerm()
(
( <AND> | <OR> )
queryTerm() )*

}

當(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() :
{
}
{
(<TITLE> | <ACTOR> |
<DIRECTOR> | <KEYWORD>)
( <EQUALS> | <NOTEQUAL>)
( <STRING> | <QUOTED_STRING> )
|
<LPAREN> expression() <RPAREN>
}

這就是我們所需的所有規(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)
(type "javacc" with no arguments for help)
Reading from file UQLParser.jj . . .
File "TokenMgrError.java" does not exist. Will create one.
File "ParseException.java" does not exist. Will create one.
File "Token.java" does not exist. Will create one.
File "SimpleCharStream.java" does not exist. Will create one.
Parser generated successfully.

除了已提到的四個(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
Call: expression
Call: queryTerm
Consumed token: <"actor">
Consumed token: <"=">
Consumed token: <<QUOTED_STRING>:
""Tom Cruise"">
Return: queryTerm
Return: expression
Consumed token: <<EOF>>
Return: 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
Call: expression
Call: queryTerm
Consumed token: <"(">
Call: expression
Call: queryTerm
Consumed token: <"actor">
Consumed token: <"=">
Consumed token: <<QUOTED_STRING>:
""Tom Cruise"">
Return: queryTerm
Consumed token: <"OR">
Call: queryTerm
Consumed token: <"actor">
Consumed token: <"=">
Consumed token: <<QUOTED_STRING>:
""Kelly McGillis"">
Return: queryTerm
Return: expression
Consumed token: <")">
Return: queryTerm
Consumed token: <"AND">
Call: queryTerm
Consumed token: <"keyword">
Consumed token: <"=">
Consumed token: <<STRING>: "drama">
Return: queryTerm
Return: expression
Consumed token: <<EOF>>
Return: parse

該輸出十分有用,因?yàn)樗菔玖送ㄟ^(guò) queryTerm 和 expression 的遞歸。queryTerm 的第一個(gè)實(shí)例實(shí)際上就是一個(gè)由兩個(gè) queryTerm 組成的表達(dá)式。 圖 1展示了該解析路徑的圖形視圖。

圖 1. 解析用戶查詢的圖形表示
Click to Open in New Window

如果您對(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 {
trace_call("expression");
try {
queryTerm();
label_1:
while (true) {
switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
case AND:
case OR:
;
break;
default:
jj_la1[0] = jj_gen;
break label_1;
}
switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
case AND:
jj_consume_token(AND);
break;
case OR:
jj_consume_token(OR);
break;
default:
jj_la1[1] = jj_gen;
jj_consume_token(-1);
throw new ParseException();
}
queryTerm();
}
} finally {
trace_return("expression");
}
}


它與您最初在其中寫(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.
UQLParserConstants.java */

package com.demo.stage1;

public interface UQLParserConstants {

int EOF = 0;
int AND = 5;
int OR = 6;
int TITLE = 7;
int ACTOR = 8;
int DIRECTOR = 9;
int KEYWORD = 10;
int LPAREN = 11;
int RPAREN = 12;
int EQUALS = 13;
int NOTEQUAL = 14;
int STRING = 15;
int QUOTED_STRING = 16;

int DEFAULT = 0;

String[] tokenImage = {
"<EOF>",
"\\" \\"",
"\\"\\\\t\\"",
"\\"\\\\r\\"",
"\\"\\\\n\\"",
"\\"and\\"",
"\\"or\\"",
"\\"title\\"",
"\\"actor\\"",
"\\"director\\"",
"\\"keyword\\"",
"\\"(\\"",
"\\")\\"",
"\\"=\\"",
"\\"<>\\"",
"<STRING>",
"<QUOTED_STRING>",
};

}


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
FROM MOVIE
where MOVIE_ID IN
(
-- parser will fill in here--
);


解析器填入的內(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 查詢的解析器輸出
Click to Open in New Window

這將確保 SQL 查詢發(fā)出

(actor = "Tom Cruise" or actor = "Kelly McGillis") and keyword=drama
將如清單 13 中所示。

清單 13. 完整的 select 語(yǔ)句
select TITLE, DIRECTOR
FROM MOVIE
where MOVIE_ID IN
(
  (
    select MOVIE_ID
    FROM ACTOR
    where NAME=‘Tom Cruise‘
    union
    select
    MOVIE_ID
    FROM ACTOR
    where NAME=‘kelly McGillis‘
  )
INTERSECT
select MOVIE_ID
  FROM KEYWORD
  where KEYWORD=‘drama‘
);


正如前面提到的,很可能存在更快、更優(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)

package com.demo.stage2;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;

import java.sql.Statement;
import java.lang.StringBuffer;
import java.io.StringReader;
import java.io.Reader;

public class UQLParser {

private static StringBuffer sqlSB;
// internal SQL representation.

public UQLParser(String s)
{
this((Reader)(new StringReader(s)));
sqlSB = new StringBuffer();
}

public String getSQL()
{
return sqlSB.toString();
}

public static void main(String args[])
{
try
{
String query = args[0];
UQLParser parser =
   new UQLParser(query);
parser.parse();
System.out.println("\\nSQL Query: " +
  parser.getSQL());

// Note: This code assumes a
// default connection
// (current userid and password).
System.out.println("\\nResults of Query");

Class.forName(
"COM.ibm.db2.jdbc.app.DB2Driver"
).newInstance();
Connection con =
  DriverManager.getConnection(
    "jdbc:db2:moviedb");
Statement stmt =
  con.createStatement();
ResultSet rs =
  stmt.executeQuery(parser.getSQL());
while(rs.next())
{
System.out.println("Movie Title = " +
  rs.getString("title") +
   " Director = " +
   rs.getString("director"));
}
rs.close();
stmt.close();
con.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
PARSER_END(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()
(
( <AND>
{ sqlSB.append("\\nINTERSECT\\n"); }
| <OR>
{ sqlSB.append("\\nunion\\n"); }
)
queryTerm() )*
}


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() :
{
Token tSearchName, tComparator, tComparand;
String sComparand, table, columnName;
}


第一個(gè)任務(wù)可用清單 17 中的代碼來(lái)完成。設(shè)置與所遇標(biāo)記關(guān)聯(lián)的合適的 DB2 表和列。

清單 17. 將搜索名稱(chēng)映射到 DB2
(
<TITLE> {table = "movie";
columnName = "title"; } |
<DIRECTOR> {table = "movie";
   columnName = "director"; } |
<KEYWORD> {table = "keyword";
columnName = "keyword"; } |
<ACTOR> {table = "actor";
columnName = "name"; }
)


第二個(gè)任務(wù)可用清單 18 中的代碼來(lái)完成。保存標(biāo)記以便可用于 SQL 緩沖區(qū)。

清單 18. 保存比較器
( tComparator=<EQUALS> |
tComparator=<NOTEQUAL> )


第三個(gè)任務(wù)可用清單 19 中的代碼來(lái)完成。相應(yīng)地設(shè)置比較字的值,如果有必要,就從 QUOTED_STRING 標(biāo)記中除去雙引號(hào)。

清單 19. 準(zhǔn)備比較字
tComparand=<STRING> {
sComparand = tComparand.image; }
   |
tComparand=<QUOTED_STRING>
{ // need to get rid of quotes.
sComparand =
   tComparand.image.substring(1,
   tComparand.image.length() - 1);
}


第四個(gè)任務(wù)可用清單 20 中的代碼來(lái)完成。完整的查詢項(xiàng)被追加到了 sql 緩沖區(qū)。

清單 20. 編寫(xiě) SQL 表達(dá)式
{
sqlSB.append("select MOVIE_ID FROM ").append(table);
sqlSB.append("\\nwhere ").append(columnName);
sqlSB.append(" ").append(tComparator.image);
sqlSB.append(" ‘").append(sComparand).append("‘");
}

最后對(duì)于遞歸表達(dá)式的情況,當(dāng)解析器在表達(dá)式遞歸中看到括號(hào)時(shí),就應(yīng)該簡(jiǎn)單地進(jìn)行回送,如清單 21 中所示。

清單 21. 回送括號(hào)
<LPAREN>
{ sqlSB.append("("); }
expression()
<RPAREN>
{ sqlSB.append(")"); }

清單 22 展示了完整的 queryTerm() 產(chǎn)生式。

清單 22. 完整的 queryTerms() 產(chǎn)生式
/**
* Query terms may consist of a parenthetically
* separated expression or may be a query criteria
* of the form queryName = something or
* queryName <> something.
*
*/
void queryTerm() :
{
Token tSearchName, tComparator, tComparand;
String sComparand, table, columnName;
}
{
(
<TITLE> {table = "movie";
  columnName = "title"; } |
<DIRECTOR> {table = "movie";
  columnName = "director"; } |
<KEYWORD> {table = "keyword";
  columnName = "keyword"; } |
<ACTOR> {table = "actor";
  columnName = "name"; }
)

( tComparator=<EQUALS> |
tComparator=<NOTEQUAL> )

(
tComparand=<STRING>
{ sComparand = tComparand.image; } |
tComparand=<QUOTED_STRING>
{ // need to get rid of quotes.
sComparand = tComparand.image.substring(1,
tComparand.image.length() - 1);
}
)

{
sqlSB.append("select MOVIE_ID FROM ").append(table);
sqlSB.append("\\nwhere ").append(columnName);
sqlSB.append(" ").append(tComparator.image);
sqlSB.append(" ‘").append(sComparand).append("‘");
}
|
<LPAREN>
{ sqlSB.append("("); }
expression()
<RPAREN>
{ sqlSB.append(")"); }
}


像前面一樣編譯并運(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() 方法
/**
* An expression is defined to be a queryTerm followed by zero
* or more query terms joined by either an AND or an OR. If two
* query terms are joined with * AND then both conditions must
* be met. If two query terms are joined with an OR, then
* one of the two conditions must be met.
*/
static final public void expression() throws ParseException {
trace_call("expression");
try {
queryTerm();
label_1:
while (true) {
switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
case AND:
case OR:
;
break;
default:
jj_la1[0] = jj_gen;
break label_1;
}
switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
case AND:
jj_consume_token(AND);
sqlSB.append("\\nINTERSECT\\n");
break;
case OR:
jj_consume_token(OR);
sqlSB.append("\\nunion\\n");
break;
default:
jj_la1[1] = jj_gen;
jj_consume_token(-1);
throw new ParseException();
}
queryTerm();
}
} finally {
trace_return("expression");
}
}[/code

按前面一樣運(yùn)行該代碼。您必須在 CLASSPATH 中包含 db2java.zip。這次,當(dāng)您運(yùn)行

java ‘cp bin;c:/sqllib/db2java.zip com.demo.stage2.UQLParser "(actor=\\"Tom Cruise\\" or actor=\\"Kelly McGillis\\") and keyword=drama"
時(shí),它將生成清單 24 中的輸出。

[b]清單 24. 查詢 (actor="Tom Cruise" or actor="Kelly McGillis") and keyword=drama 的 UQL2Parser 輸出 [/b]
[code]Call: parse
Call: expression
Call: queryTerm
Consumed token: <"(">
Call: expression
Call: queryTerm
Consumed token: <"actor">
Consumed token: <"=">
Consumed token: <<QUOTED_STRING>:
""Tom Cruise"">
Return: queryTerm
Consumed token: <"or">
Call: queryTerm
Consumed token: <"actor">
Consumed token: <"=">
Consumed token: <<QUOTED_STRING>:
""Kelly McGillis"">
Return: queryTerm
Return: expression
Consumed token: <")">
Return: queryTerm
Consumed token: <"and">
Call: queryTerm
Consumed token: <"keyword">
Consumed token: <"=">
Consumed token: <<STRING>: "drama">
Return: queryTerm
Return: expression
Consumed token: <<EOF>>
Return: parse

SQL Query: select TITLE,DIRECTOR
FROM MOVIE
where MOVIE_ID IN (
(select MOVIE_ID FROM actor
where name = ‘Tom Cruise‘
union
select MOVIE_ID FROM actor
where name = ‘Kelly McGillis‘)
INTERSECT
select MOVIE_ID FROM keyword
where keyword = ‘drama‘)

Results of Query
Movie Title = Top Gun Director = Tony Scott
Movie Title = Witness Director = Peter Weir
      


嘗試更多使用您的解析器的查詢。試一試使用 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

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多