javacc-LOOKAHEAD MiniTutorial 翻譯
本文翻譯自\javacc-5.0\doc\lookahead.html章節。
上文:http://blog.csdn.net/chaofanwei/article/details/25541065
1、LOOKAHEAD是什麼
lookahead就是當語法分析器從詞法分析器裡取token時,需要取多少個才能讓分析器正確的走下去。
例一
void Input() :
{}
{
"a" BC() "c"
}
void BC() :
{}
{
"b" [ "c" ]
}
在這個語法中,只能匹配“abc”和“abcc”。
假設我們現在把“abc”當成輸入字元,來看javacc是如何工作的。
1、第一個字元是‘a’,這裡沒什麼疑問,匹配“a”
2、讀取下一個字元‘b’,毫無疑問進入BC(),剛好匹配‘b’
3、讀取下一個自稱‘c’,在這一步,我們看到了一個選擇點,即是匹配['c']呢,還是跳出BC(),匹配最後一個‘c’。這裡假定我們選擇了[...],那麼繼續往下走。
4、因為現在我們已經跳出了BC(),但是Input說現在還需要一個‘c’,但我們已經沒有字元了,因此宣告失敗。
5、在遇到這種問題時,就說明我們在前面的選擇點的地方可能選擇了一個錯誤的決定,因此需要回溯到[...]
6、這個時候我們就應該選擇Input裡面的‘c’,這時候才能正確執行。
2、javacc裡面的選擇點
可以將javacc中choice point歸結為以下幾類:
l . 由選擇運算元 | 引入的衝突,
2. 由可省略運算元 [] 或 ?引入的衝突
3. 由重複運算元 * 引入的衝突
4. 由重複運算元 + 引入的衝突
3、預設的選擇點演算法
看語法:
TOKEN [IGNORE_CASE] :
{
<ID: (["a"-"z"])+>
}
void basic_expr() :
{}
{
<ID> "(" expr() ")" // Choice 1
|
"(" expr() ")" // Choice 2
|
"new" <ID> // Choice 3
}
if (next token is "(") {
choose Choice 2
} else if (next token is "new") {
choose Choice 3
} else if (next token is <ID>) {
choose Choice 1
} else {
produce an error message
}
上面語法是沒有什麼衝突的,假如改成如下語法:
void basic_expr() :
{}
{
<ID> "(" expr() ")" // Choice 1
|
"(" expr() ")" // Choice 2
|
"new" <ID> // Choice 3
|
<ID> "." <ID> // Choice 4
}
就會報如下衝突資訊:
arning: Choice conflict involving two expansions at
line 25, column 3 and line 31, column 3 respectively.
A common prefix is: <ID>
Consider using a lookahead of 2 for earlier expansion.
意思就是說在預設的選擇演算法在這種情況下不能正確執行,因為1和4都是以ID開頭的,這就是我們上面說的左因子。
4、選擇點衝突解決演算法
1.修改語法,使之成為LL(1)語法。
2.只要將LOOKAHEAD=k寫到Options塊中即可,javacc產生的分析器就可以分析LL(K)語法生成的語言
採用第一種方法的好處是效率非常高,易於維護。採用第二種方法的好處是語法更加直觀,但是卻不易維護。有時候採用第一種方法是無法解決衝突的,第二種方法是唯一的選擇。
5、設定全域性LOOKAHEAD
全域性的lookahead是在jj檔案中的options中指定的,可以指定為非負整數,javacc自動生成LL(K)演算法。這種方法是不提倡的,因為這會在每個選擇點都進行LL(K)演算法,即多向前看k個token,但大部分選擇點都是一個(預設)就可以了。
假定這時把lookahead設定成2,那麼在上面的3中的第二個文法就會變成:
當下來的兩個token是<ID> 和"("時,那麼選擇點1,
如果下來的兩個token是<ID> and "."時,那麼就選擇點4。
這樣就能讓上面的語法正常執行。
6、設定區域性LOOKAHEAD
可以通過設定區域性的lookahea方法,使語法分析器只在需要的時候向前看K個字元,別的情況下只用看一個就可以了,這種情況下,效率自然比通過全域性設定好。
可以把上面語法改下:
void basic_expr() :
{}
{
LOOKAHEAD(2)
<ID> "(" expr() ")" // Choice 1
|
"(" expr() ")" // Choice 2
|
"new" <ID> // Choice 3
|
<ID> "." <ID> // Choice 4
}
通過以上設定,只使第一個選擇點使用LOOKAHEAD(2)。這種情況下工作邏輯如下:
if (next 2 tokens are <ID> and "(" ) {
choose Choice 1
} else if (next token is "(") {
choose Choice 2
} else if (next token is "new") {
choose Choice 3
} else if (next token is <ID>) {
choose Choice 4
} else {
produce an error message
}
7、語法上的LOOKAHEAD
看語法:
void TypeDeclaration() :
{}
{
ClassDeclaration()
|
InterfaceDeclaration()
}
這裡假定ClassDeclaration定義為在class的前面可以出現無數多次的public,final,而InterfaceDeclaration的定義也是前面可以出現出現無數多次的public,final。那麼問題就出現了,因為當分析器在工作時,並不知道到底有多少個public或者fianl,也就不知道到底需要向前看多不個token,才能確定到底是選擇ClassDeclaration還是InterfaceDeclaration。
顯然簡單的方法就是向前看無數多個,如下:
void TypeDeclaration() :
{}
{
LOOKAHEAD(2147483647)
ClassDeclaration()
|
InterfaceDeclaration()
}
但這樣顯示是不合理的,合理的做法應該是下面的方法:
void TypeDeclaration() :
{}
{
LOOKAHEAD(ClassDeclaration())
ClassDeclaration()
|
InterfaceDeclaration()
}
意思就是說,還是一直向前看,如果ClassDeclaration()能夠匹配成功,則就用ClassDeclaration(),否則的話進入InterfaceDeclaration()。即:
if (the tokens from the input stream match ClassDeclaration) {
choose ClassDeclaration()
} else if (next token matches InterfaceDeclaration) {
choose InterfaceDeclaration()
} else {
produce an error message
}
當然還有一種優化的方法,見下:
void TypeDeclaration() :
{}
{
LOOKAHEAD( ( "abstract" | "final" | "public" )* "class" )
ClassDeclaration()
|
InterfaceDeclaration()
}
工作過程就是:
if (the nest set of tokens from the input stream are a sequence of
"abstract"s, "final"s, and "public"s followed by a "class") {
choose ClassDeclaration()
} else if (next token matches InterfaceDeclaration) {
choose InterfaceDeclaration()
} else {
produce an error message
}
即如果下面的一些列token匹配( "abstract" | "final" | "public" )* "class",那麼就選擇ClassDeclaration(),否則選擇InterfaceDeclaration()。
當然還有一種更加優化的方法,見下:
void TypeDeclaration() :
{}
{
LOOKAHEAD(10, ( "abstract" | "final" | "public" )* "class" )
ClassDeclaration()
|
InterfaceDeclaration()
}
在這種情況下,具體的工作過程就是,這時lookahead最多向前看10個token,如果超過10token後,還匹配( "abstract" | "final" | "public" )* "class"的話,那麼就進入選擇點ClassDeclaration()。
其實當不設定10的時候,預設的值就是最大值,即2147483647。
8、語義上的LOOKAHEAD
看語法:
void Input() :
{}
{
"a" BC() "c"
}
void BC() :
{}
{
"b" [ "c" ]
}
為了解決上面說到的回溯問題,我們可以使用如下的語法:
void BC() :
{}
{
"b"
[ LOOKAHEAD( { getToken(1).kind == C && getToken(2).kind != C } )
<C:"c">
]
}
意思就是:
if (next token is "c" and following token is not "c") {
choose the nested expansion (i.e., go into the [...] construct)
} else {
go beyond the [...] construct without entering it.
}
首先把‘c’封裝成一個label C,便於我們在lookahead裡面引用它,即如果下來的第一個token是C和第二個不是C,那麼選擇[..],否則跳出BC。
其實上面的改寫還可以使用下面的形式:
void BC() :
{}
{
"b"
[ LOOKAHEAD( "c", { getToken(2).kind != C } )
<C:"c">
]
}
即同時使用語法和語義上的lookahead。第一個c為語法上的lookahead,第二個為語義上面的lookahead。
9、LOOKAHEAD結構總結
通用的格式為:
LOOKAHEAD( amount,
expansion,
{ boolean_expression }
)
amount:即設定向前看的token個數;
expansion:即指定的語法上的lookahead,
boolean_expression:即指定的語義上的lookahead。
格式中的3個部分,至少指定一個部分,如果同時出現多個部分,則使用逗號分隔。
相關文章
- 翻譯
- Yurii談翻譯(五)怎樣翻譯更地道:so…that…的翻譯
- 如何完成中文翻譯日文線上翻譯
- Yurii談翻譯(四)怎樣翻譯更地道:翻譯如鋪路
- Yurii談翻譯(九)怎樣翻譯更地道:冠詞a的翻譯
- Yurii談翻譯(十)怎樣翻譯更地道:最高階的翻譯
- 翻譯的未來:翻譯機器和譯後編譯編譯
- Ubuntu安裝劃詞翻譯軟體Goldendict 單詞翻譯 句子翻譯UbuntuGo
- Yurii談翻譯(六)怎樣翻譯更地道:“as somebody said…”的翻譯AI
- Yurii談翻譯(十三)怎樣翻譯更地道:It is…that…句型諺語的翻譯
- Yurii談翻譯(十四)怎樣翻譯更地道:否定句的翻譯
- 蝴蝶書-task2: 文字推理、摘要、糾錯 transformers實現翻譯 OpenAI翻譯 PyDeepLX翻譯 DeepLpro翻譯ORMOpenAI
- Nginx翻譯Nginx
- [翻譯] TransitionKit
- 翻譯篇
- OllDbg翻譯LLDB
- OpenCV翻譯專案總結二——Mat翻譯OpenCV
- 文件翻譯器怎麼用?如何翻譯Word文件?
- Laravel 谷歌翻譯 /Bing 翻譯擴充套件包Laravel谷歌套件
- 使用google翻譯 api 翻譯中文成其他語言GoAPI
- 有道雲詞典--翻譯/螢幕取詞翻譯
- TailWind文件翻譯說明以及每日翻譯進度AI
- 翻譯軟體
- 翻譯介面整理
- JavaPoet 文件翻譯Java
- 有趣的翻譯
- 術語翻譯
- 痛苦的翻譯
- 翻譯二三事
- [翻譯] 深入SaltStack
- bulma中文翻譯
- 歌詞翻譯
- socket中文翻譯
- 詞典翻譯 英譯漢
- Python 使用白嫖網易翻譯 API 進行翻譯PythonAPI
- Google 谷歌翻譯 Mac 客戶端(Mac翻譯軟體)Go谷歌Mac客戶端
- QT TS檔案翻譯,部分不能正確被翻譯QT
- Yurii談翻譯(十一)怎樣翻譯更地道:and不是“和”