ANTLR –語義謂詞

用antlr解析簡單的語法很簡單 。 您要做的就是使用正則表達式描述您的語言,并讓antlr生成詞法分析器和解析器。 解析大型或復雜的語言有時會需要更多,因為僅使用正則表達式描述它們是困難的,甚至是不可能的。

語義謂詞是在語法內部編寫的Java(或C ++,JavaScript或…)條件。 Antlr使用它們在多個替代方案之間進行選擇,或者作為要檢查的其他聲明。 它們可以放在lexer和解析器中,但是本文僅著重于它們在解析器中的用法。 它們為antlr增加了很多功能。

這篇文章假定您對什么是antlr以及生成的解析器如何工作有一般的了解。 如果您不這樣做,請先閱讀鏈接的文章。 它們包含所需的一切。

第一章包含兩個動機用例。 第二章介紹語法,術語,并顯示簡單的失敗的語義謂詞。 然后,該帖子解釋了語義謂詞如何影響預測和生成的代碼。 它還顯示了如何編寫有用的條件以及如何解決初始用例。 最后一章將其總結為簡短的結論。

這篇文章中使用的所有示例和語法都可以在Github上找到 。

目錄

  • 用例
    • 關鍵字–第n個
  • 基本
    • 句法
  • 吊裝和預測
    • 這是什么
  • 回溯
  • 寫作條件
    • 輸入令牌流
  • 解決初始用例
    • 關鍵字–第n個
  • 包起來
    • 驗證語義謂詞
  • 資源資源

用例

當我們花一些時間解析類css的語言時 ,兩個用例都描述了在編寫該解析器的css部分時必須解決的問題。 第一個是在處理偽類時遇到的問題,第二個是在選擇器中處理棘手的空格。

如果您從未使用過CSS或對用例不感興趣,請跳過本章。

關鍵字–第n個

一些css 偽 類需要參數,該參數可以是數字,標識符,選擇器或稱為nth表達式的參數。 僅在某些偽類內允許第N個表達式,并且這些偽類的名稱不是CSS中的保留關鍵字。

第N個表達式是形式為an+b的表達式,其中ab是可選整數。 第n個有效表達式的示例: 5n+2-5n-2-5n-2-nn

我們希望語法接受第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==32+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規則相對應的代碼包含wordsameWord語義謂詞的副本。 選擇下一步使用哪個規則的部分包含以下代碼(注釋是我的):

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的第二個標記是LETTERdifferentWord的第二個標記是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==31+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必須在worddifferentWord單詞之間進行選擇。 它們都以謂詞開頭,不需要謂詞來區分它們。

但是,將使用卷揚,因為我們使用了門控語義謂詞。 要進行驗證,請在我們的示例項目中打開生成的GatedHoisting類的start()方法。 它包含1+2==31+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*);

門控:如果所述的第二個數字number9 ,那么它必須具有完全相同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規則匹配有效的簡單選擇器: h1h1: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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/369975.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/369975.shtml
英文地址,請注明出處:http://en.pswp.cn/news/369975.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python輸入一個數組輸出24進制式的時間_4.4 用于數組的文件輸入輸出 線性代數...

Numpy能夠讀寫磁盤上的文本數據或二進制數據。這一小節只討論Numpy的內置二進制格式&#xff0c;因為更多的用戶會使用pandas或其它工具加載文本或表格數據(見第6章)。np.save和np.load是讀寫磁盤數組數據的兩個主要函數。默認情況下&#xff0c;數組是以未壓縮的原始二進制格式…

DBMS-數據庫設計與E-R模型:E-R模型、約束、E-R圖、E-R擴展特性、E-R圖轉換為關系模式、UML建模...

設計過程概覽 1. 設計階段 最初階段&#xff1a;刻畫未來數據庫用戶的數據需求&#xff0c;產品為用戶需求規格說明&#xff1b; 概念設計階段&#xff08;conceptual-design phase&#xff09;&#xff1a;&#xff08;關注描述抽象數據及其聯系&#xff0c;通常使用實體-聯系…

tooltip.css-2.0文檔

tooltip.css 純CSS鼠標提示工具。 v. 2.0.0 更新日期&#xff1a;2018.4.12 預覽DEMO。 安裝&#xff1a; 只需在頁面中引入"tooltip.css"或“tooltip.min.css”文件即可。 如&#xff1a; <link rel"stylesheet" href"css/tooltip.css"…

Java虛擬機:如何判定哪些對象可回收?

版權聲明&#xff1a;本文為博主原創文章&#xff0c;轉載請注明出處&#xff0c;歡迎交流學習&#xff01; 在堆內存中存放著Java程序中幾乎所有的對象實例&#xff0c;堆內存的容量是有限的&#xff0c;Java虛擬機會對堆內存進行管理&#xff0c;回收已經“死去”的對象&…

html標簽object和embed,html標簽object和embed的區別

object和embed的區別The code in bold above is the actual code that you need to place in your page to embed a FusionCharts chart.In the above code, weveused and tags to embed the 3D Column Chart (Column3D.swf) within the HTML page.used &dataUrlData.xml u…

Apache Apollo REST API

Apache Apollo是新一代&#xff0c;高性能&#xff0c;多協議的消息傳遞代理&#xff0c;它是從頭開始構建的&#xff0c;可以替代ActiveMQ5.x。 我過去曾在博客上發表過文章 &#xff08;第一部分已經與第二部分一起發布了&#xff09;。 Apollo的非阻塞異步體系結構使其速度…

bzoj1588 [HNOI2002]營業額統計

1588: [HNOI2002]營業額統計 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 17931 Solved: 7391[Submit][Status][Discuss]Description 營業額統計 Tiger最近被公司升任為營業部經理&#xff0c;他上任后接受公司交給的第一項任務便是統計并分析公司成立以來的營業情況。 T…

python管道通信_Python進程通信之匿名管道實例講解

匿名管道管道是一個單向通道,有點類似共享內存緩存.管道有兩端,包括輸入端和輸出端.對于一個進程的而言,它只能看到管道一端,即要么是輸入端要么是輸出端.os.pipe()返回2個文件描述符(r, w),表示可讀的和可寫的.示例代碼如下:復制代碼 代碼如下:#!/usr/bin/pythonimport timeim…

css3中的box-sizing屬性的使用

box-sizing屬性用來定義元素的width和height所表示的區域,該屬性一般有三種值&#xff1a;content-box、border-box、inherit。 其中inherit表示box-sizing的值應該從父元素繼承。 content-box和border-box的主要區別就是元素的width和height的值包不包括border、padding這兩…

ES6擴展運算符...進行的數組刪除

今天寫了按照React小書寫了Reducer&#xff0c;發現基礎真是太重要了&#xff0c;所有關于上層建筑的細節都需要回到下層細節中去尋找&#xff0c;而且現在的基礎也由ES3變成了ES6了。 const ADD_USER "ADD_USER" const DELETE_USER "DELETE_USER" const…

中南大學在線考試答案計算機基礎,中南大學《計算機基礎》在線考試題庫(267題)(有答案).doc...

中南大學《計算機基礎》在線考試題庫(267題)(有答案).doc 計算機基礎01 總共89題共100分 一. 單選題 (共35題,共35分) 1. 域名服務器DNS的主要功能是( )。 (1分) A.通過請求及回答獲取主機和網絡相關的信息 B.查詢主機的MAC地址 C.為主機自動命名 D.合理分配IP地址 ★標準答案&…

自動化的OSGi測試運行器

在我的團隊成員中&#xff0c;我以忘記維護&#xff08;JUnit&#xff09;測試套件而聞名。 我只是無法為此付出額外的手動為套件添加測試的步驟。 幸運的是&#xff0c;有連續的集成服務器通過命名模式收集測試。 如果我介紹的一項孤立測試失敗了&#xff0c;那么它會脫穎而出…

php post請求后端拿不到值_PHP Post獲取不到非表單數據的問題解決辦法

問題描述在使用vue-axios向后端post數據時&#xff0c;PHP端獲取不到post的數據。問題解決修改php.ini配置找到php.ini配置文件&#xff0c;查找enable_post_data_reading變量&#xff0c;修改為打開狀態&#xff0c;注釋掉句前分好; Whether PHP will read the POST data.; Th…

CSS制作簡單loading動畫

曾經以為&#xff0c;loading的制作需要一些比較高深的web動畫技術&#xff0c;后來發現大多數loading都可以用“障眼法”做出來。比如一個旋轉的圓圈&#xff0c;并不都是將gif圖放進去&#xff0c;有些就是畫個靜止圖像&#xff0c;然后讓它旋轉就完了。gif圖也可以&#xff…

機器學習:多變量線性回歸

************************************** 注&#xff1a;本系列博客是博主學習Stanford大學 Andrew Ng 教授的《機器學習》課程筆記。博主深感學過課程后&#xff0c;不進行總結非常easy遺忘&#xff0c;依據課程加上自己對不明確問題的補充遂有此系列博客。本系列博客包含線性…

Java對象復活

總覽 收集覆蓋了finalize&#xff08;&#xff09;的對象之后&#xff0c;將其添加到終結處理隊列中&#xff0c;以在調用每個對象的finalize&#xff08;&#xff09;方法之后進行清理。 如果您復活該物體&#xff0c;會發生什么&#xff1f; 何時定稿&#xff1f; finalize方…

經過路由無法找到計算機,電腦無法啟動服務提示系統找不到指定的路徑(圖)

原標題&#xff1a;"電腦無法啟動服務提示系統找不到指定的路徑"相關電腦問題教程分享。 - 來源:191路由網。眾所周知&#xff0c;使用電腦的時候需要啟動一些服務才能使用相關的功能&#xff0c;但是如果出現無法啟動服務項&#xff0c;并且提示“錯誤3&#xff1a;…

有關域索引錯誤產生的原因及解決辦法

1說明 數據庫錯誤ORA-29861:域索引標記為LOADING/FAILED/UNUSABLE&#xff0c;其錯誤原因及解決辦法&#xff0c;根據ORACLE官方文檔的說法如下&#xff1a; // *Cause: An attempt has been made to access a domain index that is// being built or is marked faile…

詳細解讀css中的浮動以及清除浮動的方法

對于前端初學者來說&#xff0c;css浮動部分的知識是一塊比較難以理解的部分&#xff0c;下面我將把我學習過程中的心得分享給大家。 導讀&#xff1a; 1.css塊級元素講解 2.css中浮動是如何產生的 3.出現浮動后&#xff0c;如何清除浮動&#xff08;本文將涉及到多種清除浮動…

微信多開txt_電腦版微信怎么雙開、多開

新建一個txt文本文件&#xff0c;在文件中寫入如下代碼&#xff1a;echo offstart /d "C:\Program Files (x86)\Tencent\WeChat\" WeChat.exestart /d "C:\Program Files (x86)\Tencent\WeChat\" WeChat.exeexit保存文本文件。這里需要注意的是&#xff1a…