用antlr解析簡單的語法很簡單 。 您要做的就是使用正則表達式描述您的語言,并讓antlr生成詞法分析器和解析器。 解析大型或復雜的語言有時會需要更多,因為僅使用正則表達式描述它們是困難的,甚至是不可能的。
語義謂詞是在語法內部編寫的Java(或C ++,JavaScript或…)條件。 Antlr使用它們在多個替代方案之間進行選擇,或者作為要檢查的其他聲明。 它們可以放在lexer和解析器中,但是本文僅著重于它們在解析器中的用法。 它們為antlr增加了很多功能。
這篇文章假定您對什么是antlr以及生成的解析器如何工作有一般的了解。 如果您不這樣做,請先閱讀鏈接的文章。 它們包含所需的一切。
第一章包含兩個動機用例。 第二章介紹語法,術語,并顯示簡單的失敗的語義謂詞。 然后,該帖子解釋了語義謂詞如何影響預測和生成的代碼。 它還顯示了如何編寫有用的條件以及如何解決初始用例。 最后一章將其總結為簡短的結論。
這篇文章中使用的所有示例和語法都可以在Github上找到 。
目錄
- 用例
- 關鍵字–第n個
- 基本
- 句法
- 吊裝和預測
- 這是什么
- 回溯
- 寫作條件
- 輸入令牌流
- 解決初始用例
- 關鍵字–第n個
- 包起來
- 驗證語義謂詞
- 資源資源
用例
當我們花一些時間解析類css的語言時 ,兩個用例都描述了在編寫該解析器的css部分時必須解決的問題。 第一個是在處理偽類時遇到的問題,第二個是在選擇器中處理棘手的空格。
如果您從未使用過CSS或對用例不感興趣,請跳過本章。
關鍵字–第n個
一些css 偽 類需要參數,該參數可以是數字,標識符,選擇器或稱為nth表達式的參數。 僅在某些偽類內允許第N個表達式,并且這些偽類的名稱不是CSS中的保留關鍵字。
第N個表達式是形式為an+b
的表達式,其中a
和b
是可選整數。 第n個有效表達式的示例: 5n+2
, -5n-2
, -5n
, -2
, -n
, n
。
我們希望語法接受第n個表達式,但僅將其作為實際允許使用的偽類的參數。 我們希望它拒絕第n個表達式作為所有其余偽類的參數。
所有名稱通常對應于IDENT
令牌。 創建與第n個偽類名稱相對應的特殊標記是不切實際的,因為它們不是保留關鍵字。 例如,它們也是完全有效的類名稱或元素名稱。 擁有特殊令牌將迫使我們用IDENT | NTH
替換幾乎所有IDENT
事件IDENT | NTH
IDENT | NTH
。
因此,我們剩下的通用標識符IDENT
可以是普通或第n個偽類名稱。 標準句法正則表達式無法區分它們,但是語義謂詞可以。
鏈接到解決方案。
重要空白
CSS具有半重要的空格。 半重要性意味著它們中的大多數僅表示令牌的結尾,也就是它們的作用結束的地方。 例如,聲明中的空格是不相關的。 以下聲明是相等的:
padding : 2;
padding:2;
大多數CSS語法的行為均與上述相同,因此強烈傾向于將所有空格都丟棄。 但是,如果這樣做,那么接下來的兩個選擇器將最終成為相同的IDENT COLON IDENT LPAREN COLON IDENT RPAREN COLON IDENT LPAREN COLON IDENT RPAREN LBRACE
令牌流:
div :not(:enabled) :not(:disabled) {}
div:not(:enabled):not(:disabled) {}
選擇器中的空格很重要。 第一個選擇器等效于div *:not(:enabled) *:not(:disabled)
而第二個選擇器則不是。
注意:
可從antlr網站獲得的CSS 2.1語法忽略了此問題。 如果要使用它,則必須先對其進行修復。
一種解決方案是停止隱藏空格。 這將迫使我們在所有解析器規則的所有可能位置中添加顯式空白WS*
。 這將需要大量工作,并且所產生的語法將難以理解。
也有可能放棄antlr解析器中的選擇器樹構建,并為其編寫定制的手工樹構建器。 這是我們最初執行此操作的方式,我們可以放心地說它可以工作,但是比基于最終語義謂詞的解決方案需要更多的時間和調試時間。
鏈接到解決方案。
基本
我們從語義謂詞語法和一些需要的術語開始。 本章還概述了斷言失敗時的基本情況。 我們不會詳細介紹,這些將在下一章中進行介紹。
句法
語義謂詞總是括在花括號內,后跟問號或問號和雙箭頭:
-
{ condition }?
-
{ condition }?=>
第一個示例使用簡單
1+2==3
和2+3==5
條件。 語法存儲在Basics.g文件中:
LETTER : 'a'..'z' | 'A'..'Z';
word: LETTER {1+2==3}? LETTER;NUMERAL: '0'..'9';
number: {2+3==5}?=> NUMERAL NUMERAL;
術語
根據所使用的語法以及語法的位置,語義謂詞可以通過以下三種不同的名稱之一進行調用:
- 歧義語義謂詞,
- 驗證語義謂詞,
- 門控語義謂詞。
歧義語義謂詞
消除歧義的謂詞使用較短的{...}?
句法。 但是,僅當謂詞位于規則的開頭或替代項的開頭時,才稱為歧義消除。
歧義語義謂詞:
LETTER : 'a'..'z' | 'A'..'Z';
// beginning of a rule
rule: {1+2==3}? LETTER LETTER;// beginning of an alternative
alternatives: LETTER ({2+3==5}? LETTER*| {2+3==5}? LETTER+
);
驗證語義謂詞
驗證謂詞還使用較短的{...}?
句法。 消除歧義謂詞的區別僅在于位置。 驗證謂詞位于規則的中間或替代的中間:
LETTER : 'a'..'z' | 'A'..'Z';
word: LETTER {1+2==3}? LETTER;
門控語義謂詞
門控語義謂詞使用更長的{...}?=>
語法。 該條件可以放置在任何地方。
門控語義謂詞:
NUMERAL: '0'..'9';
number: {2+3==5}?=> NUMERAL NUMERAL;
謂詞失敗
正如我們在表達式語言教程文章中所解釋的那樣,解析器開始知道哪個規則應該對應于輸入,然后嘗試將其與輸入匹配。 匹配總是從規則的最左邊的元素開始,一直到右邊。
如果匹配遇到語義謂詞,則它將測試條件是否滿足。 如果不滿意,則拋出FailedPredicateException
異常。
請考慮本章開頭顯示的Basics.g語法:
LETTER : 'a'..'z' | 'A'..'Z';
word: LETTER {1+2==3}? LETTER;NUMERAL: '0'..'9';
number: {2+3==5}?=> NUMERAL NUMERAL;
如果打開生成的BasicsParser
類,您會發現每個規則都有相同名稱的對應方法。 它們都包含謂詞的副本,并且如果不滿足條件,它們都將引發異常:
// inside the generated word() method
if ( !((1+2==3)) ) {throw new FailedPredicateException(input, 'word', '1+2==3');
}
// inside the generated number() method
if ( !((2+3==5)) ) {throw new FailedPredicateException(input, 'number', '2+3==5');
}
預測,例如,如果解析器同時遇到帶有多個備選和謂詞的規則,會發生什么情況
start: word | number
start: word | number
規則,將在下一章中介紹。
吊裝和預測
根據使用語義謂詞的位置和方式,解析器可能會嘗試避免失敗的謂詞異常。 使用的策略稱為“提升”,這使謂詞變得有用。
本章說明了什么是起重以及其產生的后果。 然后我們將說明何時使用和不使用它。
這是什么
遇到具有多個備選方案的規則的解析器必須決定應使用這些備選方案中的哪個。 如果其中一些以謂詞開頭,則解析器可以使用該謂詞來幫助決策。
考慮存儲在DisambiguatingHoistingNeeded.g文件中的語法:
LETTER : 'a'..'z' | 'A'..'Z';
word: {1+2==3}? LETTER LETTER;
sameWord: {1+2!=3}? LETTER LETTER;start: word | sameWord;
生成的解析器的word()
和sameWord()
方法都包含通常的失敗謂詞檢查。
DisambiguatingHoistingNeededParser
類摘錄:
//inside the word() method
if ( !((1+2==3)) ) {throw new FailedPredicateException(input, 'word', '1+2==3');
}
//inside the sameWord() method
if ( !((1+2!=3)) ) {throw new FailedPredicateException(input, 'sameWord', '1+2!=3');
}
另外,與start
規則相對應的代碼包含word
和sameWord
語義謂詞的副本。 選擇下一步使用哪個規則的部分包含以下代碼(注釋是我的):
int LA1_2 = input.LA(3);
//predicate copied from the word rule
if ( ((1+2==3)) ) {alt1=1;
} //predicate copied from the sameWord rule
else if ( ((1+2!=3)) ) {alt1=2;
}
else {NoViableAltException nvae = new NoViableAltException('', 1, 2, input);throw nvae;
}
將謂詞復制到生成的解析器的預測部分中的操作稱為提升。
后果
如果沒有提升,則謂詞僅充當斷言。 我們可以使用它們來驗證某些條件,僅此而已。 上面的語法是非法的–如果您忽略謂詞,它有兩種語法上等效的替代方法。
由于提升的語法在整個語法中都占主導地位,因此對它們也有一些局限性。 您可以放心地忽略不只是在后臺發生的事情:
- 每個謂詞可以運行多次,
- 謂詞運行的順序可能很難預測,
- 本地變量或參數可能在懸掛式副本中不可用。
條件必須無副作用,可重復且評估順序無關緊要。 如果將它們提升為其他規則,則它們將無法引用局部變量或參數。
只有放在規則開頭的謂詞才會被提升到其他規則中。 在其他情況下吊裝僅在規則之內。 因此,如果謂詞未放在規則的開頭,則可以破壞第三條規則。
使用時
僅當解析器必須在多個規則或替代方法之間做出決定并且其中一些規則以謂詞開頭時,才使用提升。 如果它是門控謂詞,例如{...}?=>
語法中的條件,則無論如何都將提升謂詞。
如果它是消除歧義的謂詞,例如{...}?
內的條件{...}?
語法,則僅在確實需要謂詞時才會掛起謂詞。 術語“實際需要”是指多個備選方案可以匹配相同的輸入。 否則,僅當某些輸入的多個替代項不明確時才使用它。
置于規則中間或替代中間的謂詞永遠不會被提升。
如果需要吊裝-消除謂詞歧義
考慮規則
start
DisambiguatingHoistingNotNeeded.g語法start
:
LETTER : 'a'..'z' | 'A'..'Z';
NUMBER : '0'..'9';
word: {1+2==3}? LETTER LETTER;
differentWord: {1+2!=3}? LETTER NUMBER;start: word | differentWord;
規則
start
必須在word
規則和differentWord
規則之間進行選擇。 它們都以謂詞開頭,但是不需要謂詞來區分它們。 word
的第二個標記是LETTER
而differentWord
的第二個標記是NUMBER
。
不會使用吊裝。 相反,語法將研究即將到來的2個標記,以區分這些規則。 為了進行驗證,請在我們的示例項目中打開生成的DisambiguatingHoistingNotNeededParser
類的start()
方法:既沒有復制1+2==3
也沒有復制1+2!=3
條件。
int alt1=2;
switch ( input.LA(1) ) {
case LETTER: {switch ( input.LA(2) ) {case LETTER: {alt1=1;}break;case NUMBER: {alt1=2;}break;default:NoViableAltException nvae =new NoViableAltException('', 1, 1, input);throw nvae;
}
另一方面,請考慮從DisambiguatingHoistingNeeded.g語法中start
的規則:
LETTER : 'a'..'z' | 'A'..'Z';
word: {1+2==3}? LETTER LETTER;
sameWord: {1+2!=3}? LETTER LETTER;start: word | sameWord;
規則start
必須在word
規則和sameWord
規則之間進行選擇。 這兩個規則完全匹配相同的標記序列,并且僅謂詞不同。
將使用吊裝。 要進行驗證,請在我們的示例項目中打開生成的DisambiguatingHoistingNeededParser
類的start()
方法。 它包含1+2==3
和1+2!=3
條件的副本。
int alt1=2;
switch ( input.LA(1) ) {
case LETTER: {switch ( input.LA(2) ) {case LETTER: {int LA1_2 = input.LA(3);if ( ((1+2==3)) ) {alt1=1;} else if ( ((1+2!=3)) ) {alt1=2;} else { /* ... */ }}break;default: // ...}
}
break;
default: // ...
}
謂詞中的歧義歧義正在發生完全相同的事情。
這將不會被吊起(DisambiguatingHoistingNotNeeded.g語法):
LETTER : 'a'..'z' | 'A'..'Z';
alternatives: LETTER ({2+3==5}? LETTER| {2+3==5}? NUMBER
);
這將被懸掛(DeambiguatingHoistingNeeded.g語法):
LETTER : 'a'..'z' | 'A'..'Z';
alternatives: LETTER ({2+3==5}? LETTER| {2+3==5}? LETTER
);
始終吊起–門控規則
查看GatedHoisting.g語法中的start
規則:
LETTER : 'a'..'z' | 'A'..'Z';
NUMBER: '0'..'9';word: {1+2==3}?=> LETTER LETTER;
differentWord: {1+2!=3}?=> LETTER NUMBER;start: word | differentWord;
規則start
必須在word
和differentWord
單詞之間進行選擇。 它們都以謂詞開頭,不需要謂詞來區分它們。
但是,將使用卷揚,因為我們使用了門控語義謂詞。 要進行驗證,請在我們的示例項目中打開生成的GatedHoisting
類的start()
方法。 它包含1+2==3
和1+2!=3
條件的副本。
int LA1_0 = input.LA(1);if ( (LA1_0==LETTER) && (((1+2==3)||(1+2!=3)))) {int LA1_1 = input.LA(2);if ( (LA1_1==LETTER) && ((1+2==3))) {alt1=1;}else if ( (LA1_1==NUMBER) && ((1+2!=3))) {alt1=2;}else {NoViableAltException nvae =new NoViableAltException('', 1, 1, input);throw nvae;}
}
else {NoViableAltException nvae =new NoViableAltException('', 1, 0, input);throw nvae;
}
在替代方案中,門控謂詞發生了完全相同的事情。 這將被提升(GatedHoisting.g語法):
LETTER : 'a'..'z' | 'A'..'Z';
NUMBER: '0'..'9';alternatives: LETTER ({2+3==5}?=> LETTER| {2+3==5}?=>NUMBER
);
永不吊裝-一條規矩
如果謂詞位于規則或替代規則的中間,則永遠不要使用提升。 使用哪種謂詞類型都沒有關系。 因此,如果您的規則僅因謂詞而不同,則該謂詞必須放在規則或替代規則的開頭。
非提升的門控謂詞(GatedNoHoisting.g):
LETTER: 'a'..'z' | 'A'..'Z';
NUMBER: '0'..'9';//gated predicate in the middle of a rule
word: LETTER {1+2==3}?=> LETTER;
differentWord: LETTER {1+2!=3}?=> NUMBER;start: word | differentWord;
另一個非懸掛式門控謂詞(GatedNoHoisting.g):
LETTER: 'a'..'z' | 'A'..'Z';
NUMBER: '0'..'9';//gated predicate in the middle of an alternative
alternatives: LETTER (LETTER {2+3==5}?=> LETTER| LETTER {2+3==5}?=> NUMBER
);
生成的解析器在GatedNoHoistingParser
類中。
最重要的一點是,如果您的規則僅因謂詞而不同,并且該謂詞位于規則的中間,則antlr將拒絕生成相應的解析器。 下一個可擴展框包含語法錯誤的幾個語法示例,以及它們引起的反錯誤。
不正確的語法(SyntacticallyIncorrect.g):
LETTER : 'a'..'z' | 'A'..'Z';
word: LETTER {1+2==3}? LETTER;
sameWord: LETTER {1+2!=3}? LETTER;start: word | sameWord;
控制臺錯誤:
warning(200): org\meri\antlr\predicates\SyntacticallyIncorrect.g:28:6: Decision can match input such as 'LETTER LETTER' using multiple alternatives: 1, 2
As a result, alternative(s) 2 were disabled for that input
error(201): org\meri\antlr\predicates\SyntacticallyIncorrect.g:28:6: The following alternatives can never be matched: 2
另一個不正確的語法(SyntacticallyIncorrect.g):
alternativesStart: LETTER (LETTER {1+2==3}?| LETTER {1+2!=3}?
);
控制臺錯誤:
warning(200): org\meri\antlr\predicates\SyntacticallyIncorrect.g:31:27: Decision can match input such as 'LETTER' using multiple alternatives: 1, 2
As a result, alternative(s) 2 were disabled for that input
error(201): org\meri\antlr\predicates\SyntacticallyIncorrect.g:31:27: The following alternatives can never be matched: 2
另一個不正確的語法(SyntacticallyIncorrect.g):
LETTER : 'a'..'z' | 'A'..'Z';
gatedWord: LETTER {1+2==3}?=> LETTER;
gatedSameWord: LETTER {1+2!=3}?=> LETTER;gatedStart: gatedWord | gatedSameWord;
控制臺錯誤:
warning(200): org\meri\antlr\predicates\SyntacticallyIncorrect.g:40:11: Decision can match input such as 'LETTER LETTER' using multiple alternatives: 1, 2
As a result, alternative(s) 2 were disabled for that input
error(201): org\meri\antlr\predicates\SyntacticallyIncorrect.g:40:11: The following alternatives can never be matched: 2
上次不正確的語法(SyntacticallyIncorrect.g):
LETTER : 'a'..'z' | 'A'..'Z';
gatedAlternativesStart: LETTER (LETTER {1+2==3}?=> LETTER| LETTER {1+2!=3}?=> LETTER
);
控制臺錯誤:
warning(200): org\meri\antlr\predicates\SyntacticallyIncorrect.g:43:32: Decision can match input such as 'LETTER LETTER' using multiple alternatives: 1, 2
As a result, alternative(s) 2 were disabled for that input
error(201): org\meri\antlr\predicates\SyntacticallyIncorrect.g:43:32: The following alternatives can never be matched: 2
細微差別
上一章“何時使用”子章節顯示了謂詞在明顯懸掛和明顯不懸掛的情況下的行為。 我們選擇了一些示例,以顯示盡可能簡單明了的情況。
本子章節包含不同的示例集。 我們選擇了我們所知道的最有可能引起混淆的地方。 所有使用的示例都位于Nuances.g文件中。
消除歧義謂詞–高級
僅當多個替代方案對于當前輸入不明確時,才使用提升歧義的謂詞。 否則,僅當實際輸入可以由多個替代方案解析時,才運行謂詞的提升后的副本。
示例:以下規則中的替代項在語法上不等效,因為它們與同一組輸入不匹配。 第一個備選方案完全匹配兩個字母,第二個備選方案匹配任意數量的字母:
advancedDisambiguating: LETTER ({1+2==3}? LETTER LETTER| {1+2!=3}? LETTER*
);
如果輸入正好以一個LETTER
開頭,則第一個選擇不能解析它。 因為只有第二個替代匹配,所以不會使用謂詞。 解析器將使用第二個替代方法,如果1+2!=3
條件恰好為false
,則解析器將引發失敗的謂詞異常。
但是,如果輸入以兩個字母開頭,則兩個選項都可以匹配它,并且將使用謂詞。 這是生成的代碼的樣子:
int alt4=2;
switch ( input.LA(1) ) {
case LETTER: {switch ( input.LA(2) ) {case LETTER: {int LA4_3 = input.LA(3);//predicate is used only if first two tokens are LETTERif ( ((1+2==3)) ) {alt4=1;}else if ( ((1+2!=3)) ) {alt4=2;}else {// ... irrelevant code ... }}break;//if the second token is not LETTER, predicate is not usedcase EOF: { alt4=2; } break;default: // ... }}break;
//if the first token is not LETTER, predicate is not used
case EOF: // ...
default: // ...
}
將其與非常相似的門控規則進行比較:
compareGated: LETTER ({1+2==3}?=> LETTER LETTER| {1+2!=3}?=> LETTER*
);
解析器將不使用謂詞。 永遠不會輸入第二個選擇,因為永遠不會滿足謂詞1+2!=3
:
int alt6=2;
int LA6_0 = input.LA(1);if ( (LA6_0==LETTER) && (((1+2==3)||(1+2!=3)))) {int LA6_1 = input.LA(2);if ( (LA6_1==LETTER) && (((1+2==3)||(1+2!=3)))) {int LA6_3 = input.LA(3);
在這種情況下,門控謂詞會使antlr引發不同類型的異常。 正如我們將在本文后面所展示的,門控謂詞和歧義謂詞的不同提升對于更復雜的謂詞而言可以帶來更大的不同。 即,它可以在接受和拒絕輸入之間有所不同。
循環盡管乍看起來并不像循環,但是循環也是替代方法。 他們使用預測來猜測是否應該再進行一輪還是應該結束。
僅在謂詞返回true時才使用謂詞:
LETTER : 'a'..'z' | 'A'..'Z';
loop: ( {somePredicate()}?=> LETTER )*;
loop
規則將匹配字母,直到函數somePredicate()
返回false
或直到規則用完LETTER
令牌為止。
loop1:
do {int alt1=2;int LA1_0 = input.LA(1);// predicate is used during the predictionif ( (LA1_0==LETTER) && ((somePredicate()))) {alt1=1;}//matching: either jump out or match another LETTERswitch (alt1) {case 1: {if ( !((somePredicate())) ) {throw new FailedPredicateException(...);}// ... match LETTER ...}break;default:break loop1;}
} while (true);
消除歧義的謂詞不能用于此目的。 下一個謂詞將不會用于決定解析器是否應留在循環中:
LETTER : 'a'..'z' | 'A'..'Z';
loopDisambiguating: ( {somePredicate()}? LETTER )*;
從技術上講,循環由LETTER
和<nothing>
替代方案決定。 這些在語法上是不同的,并且只有在必須在語法上模棱兩可的替代方案之間做出決定時,預測才使用歧義謂詞。
loopDisambiguating
規則將匹配字母,直到用完LETTER
令牌為止。 如果函數somePredicate()
在此期間返回false
,則該規則將引發FailedPredicateException
異常。
生成的代碼與前一個代碼非常相似,只是預測部分發生了變化。 謂詞不使用:
loop2:
do {int alt2=2;//prediction ignores the predicateswitch ( input.LA(1) ) {case LETTER: {alt2=1;}break;}//matching: either jump out or match another LETTERswitch (alt2) {case 1: {if ( !((somePredicate())) ) {throw new FailedPredicateException(...);}// ... match LETTER ...}break;default:break loop2;}
} while (true);
未發現的替代品
保留一些替代方案是完全可以的。 帶有謂詞的替代方案將按預期工作。 只有在存在多個模棱兩可的替代方案時,才會始終懸掛門控謂詞,并且會消除歧義謂詞。
門控謂詞總是被懸掛:
uncoveredGated: LETTER ({3+4==7}?=> LETTER| NUMBER
);
懸掛歧義謂詞:
uncoveredDisambiguatingHoisted: LETTER ({2+5==7}? LETTER*| LETTER+
);
非懸掛式歧義謂詞:
uncoveredDisambiguatingNotHoisted: LETTER ({2+4==6}? LETTER| NUMBER
);
門控謂詞和歧義消除謂詞的組合如果一個備選方案被門控,而另一種歧義消除了歧義,則始終會提升門控謂詞,只有在實際需要時才解除歧義消除謂詞。
提升了門控謂詞,而沒有消除歧義謂詞:
combinationDisambiguatingNotHoisted: LETTER ({1+4==5}?=> LETTER| {1+4!=5}? NUMBER
);
兩個謂詞都被懸掛:
combinationDisambiguatingHoisted: LETTER ({1+5==6}?=> LETTER*| {1+5!=6}? LETTER+
);
附加括號
如果在括號中關閉歧義謂詞,則該謂詞仍將被視為歧義謂詞。
編寫歧義謂詞的另一種方法:
stillDisambiguating: ({2+2==4}?) LETTER;
testStillDisambiguating: stillDisambiguating | LETTER;
如果在門控謂詞周圍附加括號,則該謂詞將被忽略:
ignored: ({3+3==6}?)=>LETTER;
回溯
即使解析器正在回溯,謂詞也會運行,例如,如果它在語法謂詞內部。 如果解析器正在回溯并且謂詞失敗,那么回溯也會失敗。 僅當解析器未回溯時,才會引發失敗的謂詞異常。
backtrack
規則將啟動回溯(Backtracking.g):
LETTER : 'a'..'z' | 'A'..'Z';
word: LETTER {1+2==3}? LETTER;
number: LETTER {2+3!=5}? LETTER;backtrack: (number)=> number | word;
由于可以進行回溯,因此生成的謂詞檢查是不同的:
if ( !((2+3!=5)) ) {if (state.backtracking>0) {state.failed=true; return retval;}throw new FailedPredicateException(input, 'number', '2+3!=5');
}
回溯是條件不得產生副作用,必須可重復且評估順序不重要的另一個原因。
寫作條件
本章說明如何為語義謂詞編寫高級條件。 首先,我們將展示如何訪問和使用輸入令牌流。 我們還將說明如何引用標記的令牌。 最后一部分是關于非懸掛條件下的局部變量。
除非另有說明,否則所有使用的示例都位于Environnement.g文件中。
輸入令牌流
每個生成的解析器都有公共TokenStream input
字段。 該字段提供對整個輸入令牌流以及該令牌流中當前位置的訪問。 它最重要的方法是Token LT(int k)
方法。 參數k
包含您感興趣的令牌的相對位置。
數字1表示“先看一個令牌”,數字2表示“先看第二令牌”,依此類推。 負數表示傳遞的令牌:-1將返回前一個令牌,-2將返回前一個令牌,依此類推。 不要使用0。其含義是undefined,并且默認解析器返回null
。
注意:即使語法處于回溯狀態,相對引用也可以正常工作。 -1始終是前一個標記,1始終是下一個標記。
例子
消除歧義:如果
word
以字母開頭
a
,那么它必須至少包含兩個字母:
word: LETTER ({ input.LT(-1).getText().equals('a')}? LETTER+| { !input.LT(-1).getText().equals('a')}? LETTER*);
門控:如果所述的第二個數字number
是9
,那么它必須具有完全相同3標記:
number: NUMERAL ({input.LT(1).getText().equals('9')}?=> NUMERAL NUMERAL| {!input.LT(1).getText().equals('9')}?=> NUMERAL*
);
注:謂語的選擇略有事情在這種情況下。 如果輸入不符合規則,它將影響將引發何種類型的錯誤。
標簽和清單
謂詞可以像操作一樣引用和使用任何先前定義的標簽或標簽列表。
標簽示例
如果單詞的第一個字母是a
,那么這個詞必須至少有兩個字母:
labeledWord: a=LETTER ({ $a.getText().equals('a')}? LETTER+| { !$a.getText().equals('a')}? LETTER*);
標簽列表示例如果單詞以少于3個字母開頭,則它必須以數字結尾:
labeledListWord: a+=LETTER+ ({ $a.size() < 3 }?=> NUMERAL| { $a.size() >= 3}?=> );
注意:在這種情況下,謂詞的選擇確實很重要。 上面的示例只有在使用門控{...}?=>
謂詞而不是消除{...}?
歧義的情況下才能正常工作{...}?
一。 NUMERAL
和<nothing>
在語法上是不同的。 消除歧義的謂詞將不會用于預測,例如不會被提升。 解析器將僅根據下一個標記(是NUMERAL
嗎?)做出決定。
該條件將用作事后聲明,以檢查字母數是否正確。 這樣的語法會在abcd9
輸入上引發異常,而我們的輸入會接受它。
未定義標簽
謂詞不能引用尚未定義的標簽。 解析器已生成,但首次嘗試使用該規則會在運行時引發空指針異常:
//this would cause null pointer exception
nullPointerAtPredicate: LETTER { $a.getText().equals('a') }? a=LETTER;
標簽和吊裝其他規則
由于必須在使用謂詞之前先定義標簽,并且僅當謂詞位于規則的開頭時才將其復制到其他規則中,因此您不必擔心提升到其他規則中。
訪問局部變量
Antlr允許您定義自定義局部變量并與一個規則一起使用。 如果您確定謂詞不會被復制到其他規則中,則可以使用它們。 當然,如果可以將謂詞復制到其他規則中,則使用局部變量將導致錯誤的解析器。
創建局部變量并在謂詞中使用它們。 如果單詞以少于10個字母開頭,則必須以數字結尾
localVariables
@init {int num=0;} //define local variable num: (LETTER { num++; })* //raise the num variable by 1 for each letter ( // what should follow depends on the variable value{ num < 10 }?=> NUMERAL| { num >= 10}?=> );
注意:適用與之前相同的警告。 我們必須使用門控謂詞。
您必須特別小心,不要在潛在的提升謂詞中使用局部變量。 例如,Antlr參考書建議遵循以下規則以僅匹配由少于四個數字組成的數字(ANTLRReference3Error.g):
localVariablesWarning
@init {int n=1;} // n becomes a local variable: ( {n<=4}?=> NUMERAL {n++;} )+ // enter loop only if n<=4;
上面的規則可以很好地隔離工作,即在其他規則中不使用時。 不幸的是,如果將其包含在其他規則中,則該謂詞可能會被提升到該其他規則中(ANTLRReference3Error.g):
// syntax error in generated parser
syntaxError: localVariablesWarning | LETTER;
n<=4
謂詞將被復制到syntaxError
規則中。 在該規則內無法訪問變量n
,并且生成的解析器在語法上將是錯誤的。
解決初始用例
最后,我們將解決激勵性章節中描述的兩個用例。
關鍵字–第n個
鏈接到原始用例。
我們創建了isInsideNth
函數,該函數僅在先前的令牌與某個第n個偽類的名稱匹配時才返回true
。 該函數用作門控謂詞內部的條件。 且僅當輸入在第n個偽類內時,生成的解析器才會假定輸入包含第n個表達式。
UseCasesNth.g文件:
@parser::members {private static Set<String> NTH_PSEUDOCLASSES = new HashSet<String>();static {NTH_PSEUDOCLASSES.add('nth-child');NTH_PSEUDOCLASSES.add('nth-last-child');NTH_PSEUDOCLASSES.add('nth-of-type');NTH_PSEUDOCLASSES.add('nth-last-of-type');}public boolean isInsideNth() {return isNthPseudoClass(input.LT(-1));}private boolean isNthPseudoClass(Token a) {if (a == null)return false;String text = a.getText();if (text == null)return false;return NTH_PSEUDOCLASSES.contains(text.toLowerCase());}}LPAREN: '(';
RPAREN: ')';
COLON: ':';
COMMA: ',';
IDENT : ('a'..'z' | 'A'..'Z')+;//pseudoparameters and nth with dummy syntax
pseudoparameters: IDENT (COMMA IDENT)*;
nth: IDENT; //real nth syntax ommited for simplicity sake// pseudoclass
pseudo: COLON COLON? IDENT (({ isInsideNth()}?=> LPAREN nth RPAREN| LPAREN pseudoparameters RPAREN)?);
具有標簽和重寫規則的替代解決方案:
//different solution - note that we need to use rewrite rules in this case
pseudoDifferentSolution: COLON COLON? name=IDENT (({ isNthPseudoClass($name)}?=> LPAREN nthParameters=nth RPAREN| LPAREN parameters=pseudoparameters RPAREN)?)-> $name $nthParameters? $parameters? ;
重要空白
鏈接到原始用例。
CSS選擇器可以由用組合符>
, +
, ~
和<space>
分隔的多個部分組成。 每個稱為簡單選擇器的部分都以一個可選的元素名稱開頭,然后可以跟隨多個偽類,屬性和類似結構。
忽略空格作為組合器的問題,簡化的簡單選擇器語法如下所示:
COLON: ':';
STAR: '*';
NUMBER: ('0'..'9')+;
IDENT : ('a'..'z' | 'A'..'Z')+;//some options have been removed from following rules for simplicity sake
elementName: IDENT | STAR | NUMBER;
pseudoClass: COLON COLON? IDENT;
elementSubsequent: pseudoClass; simpleSelectorWrong: (elementName elementSubsequent*)| elementSubsequent+
;
上面的simpleSelectorWrong
規則匹配有效的簡單選擇器: h1
, h1:first-child:hover
, :first-child:hover
和:hover
。
不幸的是,隨著空白的消失,上述規則的匹配程度更高。 例如,它也將匹配h1:first-child :hover
,這應該與h1:first-child *:hover
選擇器完全一樣地解釋,例如,兩個簡單的選擇器由<space>
。
我們創建了僅在上一個和下一個標記之間沒有隱藏標記的情況下才返回true的方法。 除非另有配置,否則所有令牌都是CommonToken
類的實例。 由于通用令牌知道其起始索引和終止索引,因此我們可以對其進行轉換和比較,以查看它們之間是否存在某些事物。
新的解析器方法(UseCasesSelectors.g):
@parser::members {public boolean onEmptyCombinator(TokenStream input) {return !directlyFollows(input.LT(-1), input.LT(1));}private boolean directlyFollows(Token first, Token second) {CommonToken firstT = (CommonToken) first;CommonToken secondT = (CommonToken) second;if (firstT.getStopIndex() + 1 != secondT.getStartIndex())return false;return true;}
}
固定的簡單選擇器使用gated謂詞來檢查是否應該繼續添加后續元素(UseCasesSelectors.g):
simpleSelector: (elementName ({!onEmptyCombinator(input)}?=>elementSubsequent)*) | (elementSubsequent ({!onEmptyCombinator(input)}?=>elementSubsequent)*);
在這種情況下,我們必須使用門控謂詞。 如果使用歧義謂詞,則生成的解析器將不會使用謂詞來決定是否保留在循環內。 這是因為循環在技術上決定了elementSubsequent
和<nothing>
替代方案,而這些替代方案在語法上是不同的。 {...}?
謂詞不會在預測期間使用,只會偶爾拋出異常。
包起來
語義謂詞是在語法內部編寫的Java條件。 它們將被原樣復制到生成的解析器中,而沒有任何更改。 如果令牌匹配算法到達語義謂詞并且該謂詞失敗,則拋出FailedPredicateException
異常。
如果規則或替代規則以語義謂詞開頭,則可以在預測階段使用該語義謂詞。 預測階段中失敗的謂詞永遠不會引發異常,但是它們可能會禁用某些替代項。 這稱為吊裝。
條件必須無副作用,可重復且評估順序無關緊要。 如果將它們提升為其他規則,則它們將無法引用局部變量或參數。
語義謂詞以三種不同的方式使用:作為驗證語義謂詞,作為歧義語義謂詞和作為門控語義謂詞。
驗證語義謂詞
驗證語義謂詞僅充當斷言。 結果,永遠不會懸掛驗證語義謂詞。
條件在花括號內關閉,后跟問號{ condition }?
。 必須將其放置在規則的中間或替代的中間:
LETTER : 'a'..'z' | 'A'..'Z';
word: LETTER {1+2==3}? LETTER;
歧義語義謂詞
歧義性語義謂詞有助于在語法上等效的替代方案之間進行選擇。 結果,僅當解析器必須在多個模棱兩可的備選方案之間進行選擇時,才會消除歧義的語義謂詞。
歧義語義謂詞使用與驗證謂詞完全相同的語法。 條件在花括號內關閉,后跟問號{ condition }?
。 但是,必須將它們放在規則的開頭或替代的開頭:
LETTER : 'a'..'z' | 'A'..'Z';
// beginning of an alternative
alternatives: LETTER ({2+3==5}? LETTER*| {2+3==5}? LETTER+
);
門控語義謂詞
門控語義謂語用于動態打開和關閉語法部分。 結果,提升了放置在規則或替代規則開頭的所有門控謂詞。 置于規則或替代規則中間的門控謂詞永遠不會被懸掛。
條件在花括號內關閉,后跟問號和雙箭頭{ condition }?=>
:
NUMERAL: '0'..'9';
number: {2+3==5}?=> NUMERAL NUMERAL;
- Wincent維基
- 堆棧溢出問題
- 權威的ANTLR參考
參考: ANTLR –我們的JCG合作伙伴 Maria Jurcovicova的“ 語義謂詞”在“ This is Stuff”博客上。
翻譯自: https://www.javacodegeeks.com/2012/12/antlr-semantic-predicates.html