2016-03-01 54 views
2

介紹

在文檔來看,ANTLR 2配合使用,有一些所謂predicated lexing,結合實例像這樣的(由帕斯卡啓發):句法斷言規則

RANGE_OR_INT 
    : (INT "..") => INT { $setType(INT); } 
    | (INT '.') => REAL { $setType(REAL); } 
    | INT     { $setType(INT); } 
    ;  

我看到的樣子它在本規則開始時基本上是一個積極的預見性斷言:如果前瞻性匹配INT ".."那麼第一個規則將應用(並匹配該輸入的INT部分),依此類推。

我還沒有在ANTLR 4中找到過這樣的東西。該2 to 3 migration guide似乎並沒有提到這一點,而3 to 4 changes document狀態:

ANTLR 3和4之間的最大區別在於,ANTLR 4需要你給它,除非語法有間接左遞歸語法的任何。這意味着我們不需要語法謂詞或回溯,因此ANTLR 4不支持該語法;你會得到一個使用它的警告。

這是與錯誤信息,我得到行,如果我離開這個基本的是:

(...)=> syntactic predicates are not supported in ANTLR 4 

雖然我能理解一個更智能解析器實施將如何解決這些模糊之處,我失敗看看這將如何工作詞法分析器

再現例如

可以肯定,我們嘗試了這一點:

grammar Demo; 
prog: atom (',' atom)* ; 
atom: INT { System.out.println("INT: " + $INT.getText()); } 
    | REAL { System.out.println("REAL: " + $REAL.getText()); } 
    | a=INT RANGE b=INT { System.out.println("RANGE: " + 
           $a.getText() + " .. " + $b.getText()); } 
    ; 
WS : (' ' | '\t' | '\n' | '\r')+ -> skip ; 
INT : ('0'..'9')+ ; 
REAL: INT '.' INT? | '.' INT ; 
RANGE: '..' ; 

保存這Demo.g,然後編譯並運行:

$ wget -nc http://www.antlr.org/download/antlr-4.5.2-complete.jar 
$ java -jar antlr-4.5.2-complete.jar Demo.g 
$ javac -cp antlr-4.5.2-complete.jar Demo*.java 
$ java -cp .:antlr-4.5.2-complete.jar org.antlr.v4.gui.TestRig \ 
    Demo prog <<< '1,2.,3.4,5 ..6,7..8' 
INT: 1 
REAL: 2. 
REAL: 3.4 
RANGE: 5 .. 6 
REAL: 7. 
line 1:17 extraneous input '.8' expecting {<EOF>, ','} 

這樣看來我是正確的:雖然刪除語法預定可能適用於解析器,但詞法分析器不會突然猜出正確的標記類型。

核心問題

那麼一個會如何轉換這個具體的例子來ANTLR 4?有沒有辦法表達先行條件?或者也許有一個像INT '..'這樣的單個規則發出兩個不同的標記的方法?

參考和可能的解決方案

望着ANTLR 4 Pascal grammar,我注意到,它不允許實數在.沒有結束之後的數字,所以學習的解決方案從那裏不會出現是一個選擇。我看過Semantic predicates in ANTLR4?syntactic predicates - Upgrading from Antlr 3 to Antlr 4。在解析器規則中都討論句法謂詞。後者也有詞法規則的例子,但前瞻與後面的規則是一致的,這意味着規則可以被移除而沒有不利影響。在我上面的例子中,情況並非如此。

答案check previous/left token in lexer提詞法分析器的emit方法,帶有註釋的ANTLR 3維基引用How can I emit more than a single token per lexer rule? FAQ頁面,所以我想這是一種方法。如果沒有人打我的話,我會把它變成一個答案,如果我能在我的例子中得到它的工作。

ANTLR4 negative lookahead in lexer的回答利用_input.LA(int)方法來檢查前瞻。 ANTLR 4 lexical analysis faq提到_input.LA沒有進入細節。這也適用於上面的例子,但對於不止一個字符的前瞻性考慮的場景來說很難。

回答

1

sources of the current (as of this writing) Lexer implementation包含多個關於多個標記發射的文檔字符串條目。這些當然也代表the Lexer API JavaDoc。根據這些,就必須做到以下幾點:

  1. 覆蓋emit(Token)

    默認情況下不支持每個nextToken調用 多次發出出於效率的考慮。子類並覆蓋此方法,nextToken, 和getToken(將令牌插入列表並從該列表中取出 而不是像本實現那樣使用單個變量)。

  2. 覆蓋nextToken()

  3. 覆蓋getToken()

    覆蓋如果發光多個令牌。

  4. 務必將_token到非null

    如果你繼承允許多個令牌 排放,然後將其設置爲最後一個令牌匹配或 非空的東西,這樣的自動令牌發射機制不會 發出另一個令牌。

不過,我不明白爲什麼壓倒一切的getToken將是重要的,因爲我在運行時庫看到沒有調用該方法的任何地方。如果你設置了_token,那麼這也將是getToken的輸出。

所以我做了什麼,從一個單一的規則發出兩個令牌是這樣的:

@lexer::members { 

    private Token _queued; 

    @Override public Token nextToken() { 
     if (_queued != null) { 
      emit(_queued); 
      _queued = null; 
      return getToken(); 
     } 
     return super.nextToken(); 
    } 

    @Override public Token emit() { 
     if (_type != INT_RANGE) 
      return super.emit(); 
     Token t = _factory.create(
      _tokenFactorySourcePair, INT, null, _channel, 
      _tokenStartCharIndex, getCharIndex()-3, 
      _tokenStartLine, _tokenStartCharPositionInLine); 
     _queued = _factory.create(
      _tokenFactorySourcePair, RANGE, null, _channel, 
      getCharIndex()-2, getCharIndex()-1, _tokenStartLine, 
      _tokenStartCharPositionInLine + getCharIndex()-2 - 
      _tokenStartCharIndex); 
     emit(t); 
     return t; 
    } 
} 

INT_RANGE: INT '..' ; 

所有位置計算感到很乏味,但是,並給了我另一個(至少對於這個應用程序好得多)我會在一個特別的答案中發佈的想法。

1

這裏是一個很短的解決方案:

@lexer::members { private int _pos; } 
INT_RANGE: INT { _pos=_input.index(); setType(INT); emit(); } 
      '..' { _input.seek(_pos); }; 

這符合整個INT '..'表達,但隨後倒回輸入到我們發出的令牌和保存位置剛過INT。然後在規則末尾使用該位置以更持久的方式倒回輸入。

但是,有一個問題:由於_input.seek將不會影響getCharPositionInLine返回的結果標記將有不正確的位置信息。在這種情況下,人們可以在規則的結尾做

setCharPositionInLine(getCharPositionInLine() - 2) 

,但如果不是..一個正在處理可變長度的輸入這種做法是行不通的。我本來希望能夠在第一個動作中保存getCharPositionInLine()的結果,但不幸的是,這已經反映了整個表達的結束。

看着LexerATNSimulator.evaluatePredicate我看到這種方法努力恢復給定的位置狀態。因此,我們可以通過濫用語義斷言它的副作用在正確的狀態得到:

@lexer::members { 
    private int _savedIndex, _savedLine, _savedColumn; 
    private boolean remember() { 
     _savedIndex = _input.index(); 
     _savedLine = getLine(); 
     _savedColumn = getCharPositionInLine(); 
     return true; 
    } 
    private void recall(int type) { 
     _input.seek(_savedIndex); 
     setLine(_savedLine); 
     setCharPositionInLine(_savedColumn); 
     setType(type); 
    } 
} 
INT_RANGE: INT { remember() }? '..' { recall(INT); } ; 

記住,語義謂詞會在某個時間點的地方,尚不能保證整個得到執行表達式實際上會匹配。所以如果你在幾個地方使用這個技巧,你必須小心,你不會從覆蓋狀態的不同規則中調用remember()。如果有疑問,你可以使用多個這樣的函數,或者一個索引到數組中,以使每個匹配都是明確的。