2014-03-13 45 views
3

我正在使用的DSL允許用戶定義「完整文本替換」變量。在解析代碼時,我們需要查找變量的值並從代碼中再次開始解析。使用ANTLR的DSL代碼替換

替換可以非常簡單(單個常量)或整個語句或代碼塊。 這是一個模擬語法,我希望能夠說明我的觀點。

grammar a; 

entry 
    : (set_variable 
    | print_line)* 
    ; 

set_variable 
    : 'SET' ID '=' STRING_CONSTANT ';' 
    ; 

print_line 
    : 'PRINT' ID ';' 
    ; 

STRING_CONSTANT: '\'' ('\'\'' | ~('\''))* '\'' ; 

ID: [a-z][a-zA-Z0-9_]* ; 

VARIABLE: '&' ID; 

BLANK: [ \t\n\r]+ -> channel(HIDDEN) ; 

然後連續執行的下列語句應該是有效的;

SET foo = 'Hello world!'; 
PRINT foo;    

SET bar = 'foo;' 
PRINT &bar     // should be interpreted as 'PRINT foo;' 

SET baz = 'PRINT foo; PRINT'; // one complete statement and one incomplete statement 
&baz foo;      // should be interpreted as 'PRINT foo; PRINT foo;' 

任何時間&變量令牌被發現後,我們立即切換到解釋變量的值來代替。如上所述,這可能意味着您以無效的方式設置代碼,並且只有當值恰好正確時纔會完成半個語句。變量可以在文本中的任何點重新定義。

嚴格地說當前的語言定義不禁止內部相互嵌套瓦爾&,但目前的分析不處理這一點,如果不允許它,我也不會難過。

目前我正在建立一個使用訪問者的解釋器,但是我堅持使用這個解釋器。

我該如何構建一個詞法分析器/解析器/解釋器來允許我這樣做?謝謝你的幫助!

+0

這是在你的語法中解釋一些討厭的詭計。在單個「條目」中可以出現「VARIABLE」的情況和限制?我的意思是,這是否允許:'SET a ='P'; SET b ='R'; SET c ='I'; SET d ='N'; SET e ='T'; SET f =''; SET g =''''; SET h ='ouch!'; SET i =''''; SET j =';'; &a&b&c&d&e&f&g&h&i&j'最終評估'PRINT'ouch!';'? –

+0

是的,這確實是一個有效的陳述:/我懷疑有人以這種方式使用它,但應用程序已經存在很多年了,所以你永遠無法確定客戶做了什麼。當讀取字符來構成標記時,當前的實現只需切換到從變量值讀取,但我不知道ANTLR是否與ANTLR兼容。 – Trasvi

+0

我不認爲在解析過程中有一種簡單的方法來插入代碼/標記。至少不提供所提供的API類(您當然可以實現自己的'TokenStream'並將其提供給解析器)。 –

回答

0

所以我找到了一個解決方案。我認爲它可能會更好 - 因爲它可能做了很多陣列複製 - 但至少現在它工作。

編輯:我錯了,我的解決方案會消耗它發現的任何&,包括那些在有效的位置,如內部字符串常量。這似乎是一個更好的解決方案:

首先,我擴展了InputStream,以便它能夠在遇到&時重寫輸入蒸汽。這不幸的是涉及複製陣列,我可以在將來解決:

MacroInputStream。的java

package preprocessor; 

    import org.antlr.v4.runtime.ANTLRInputStream; 

    public class MacroInputStream extends ANTLRInputStream { 

     private HashMap<String, String> map; 

     public MacroInputStream(String s, HashMap<String, String> map) { 
     super(s); 
     this.map = map; 
     } 

     public void rewrite(int startIndex, int stopIndex, String replaceText) { 
     int length = stopIndex-startIndex+1; 
     char[] replData = replaceText.toCharArray(); 
     if (replData.length == length) { 
      for (int i = 0; i < length; i++) data[startIndex+i] = replData[i]; 
     } else { 
      char[] newData = new char[data.length+replData.length-length]; 
      System.arraycopy(data, 0, newData, 0, startIndex); 
      System.arraycopy(replData, 0, newData, startIndex, replData.length); 
      System.arraycopy(data, stopIndex+1, newData, startIndex+replData.length, data.length-(stopIndex+1)); 
      data = newData; 
      n = data.length; 
     } 
     } 
    } 

其次,我延長詞法,使得在遇到VARIABLE令牌時,重寫上述方法被稱爲:

MacroGrammarLexer.java

package language; 

import language.DSL_GrammarLexer; 

import org.antlr.v4.runtime.Token; 

import java.util.HashMap; 

public class MacroGrammarLexer extends MacroGrammarLexer{ 

    private HashMap<String, String> map; 

    public DSL_GrammarLexerPre(MacroInputStream input, HashMap<String, String> map) { 
    super(input); 
    this.map = map; 
    // TODO Auto-generated constructor stub 
    } 

    private MacroInputStream getInput() { 
    return (MacroInputStream) _input; 
    } 

    @Override 
    public Token nextToken() { 
    Token t = super.nextToken(); 
    if (t.getType() == VARIABLE) { 
     System.out.println("Encountered token " + t.getText()+" ===> rewriting!!!"); 
     getInput().rewrite(t.getStartIndex(), t.getStopIndex(), 
      map.get(t.getText().substring(1))); 
     getInput().seek(t.getStartIndex()); // reset input stream to previous 
     return super.nextToken(); 
    } 
    return t; 
    } 

} 

最後,我修改生成的解析器以在解析時設置變量:

DSL_GrammarParser.java

... 
    ... 
    HashMap<String, String> map; // same map as before, passed as a new argument. 
    ... 
    ... 

public final SetContext set() throws RecognitionException { 
    SetContext _localctx = new SetContext(_ctx, getState()); 
    enterRule(_localctx, 130, RULE_set); 
    try { 
     enterOuterAlt(_localctx, 1); 
     { 
     String vname = null; String vval = null;    // set up variables 
     setState(1215); match(SET); 
     setState(1216); vname = variable_name().getText(); // set vname 
     setState(1217); match(EQUALS); 
     setState(1218); vval = string_constant().getText(); // set vval 
     System.out.println("Found SET " + vname +" = " + vval+";"); 
      map.put(vname, vval); 
     } 
    } 
    catch (RecognitionException re) { 
     _localctx.exception = re; 
     _errHandler.reportError(this, re); 
     _errHandler.recover(this, re); 
    } 
    finally { 
     exitRule(); 
    } 
    return _localctx; 
} 
    ... 
    ... 

不幸的是這種方法是final所以這會使維護有點難度,但它適用於現在。

-1

處理您的需求的標準模式是實現一個符號表。最簡單的形式是一個關鍵:價值商店。在訪問者中,添加遇到的var聲明,並在遇到var引用時讀出值。

如上所述,您的DSL沒有對所聲明的變量定義範圍要求。如果您確實需要有限範圍的變量,那麼使用一堆key:值存儲庫,推入並彈出範圍入口和出口。

看到這個相關StackOverflow answer.

另外,因爲你的字符串可能包含命令,你可以簡單地解析的內容作爲初始解析的一部分。也就是說,用包含全套有效內容的規則來擴展語法:

set_variable 
    : 'SET' ID '=' stringLiteral ';' 
    ; 

stringLiteral: 
    Quote Quote? ( 
    ( set_variable 
     | print_line 
     | VARIABLE 
     | ID 
    ) 
    | STRING_CONSTANT // redefine without the quotes 
    ) 
    Quote 
    ; 
+1

我不認爲這會起作用。還有另一個變量系統按照您的描述來實現,但是&VAR系統是代碼替換,更像是預處理器宏,它可以包含格式錯誤的語句。所以,'SET foo ='''BAR'; SET baz = &foo';'將是有效的語句,但我不可能通過重新定義stringLiteral來描述它。我基本上需要&foo來標記爲''''+ BAR'。 – Trasvi

+0

要處理格式錯誤的語句,請擴展ANTLRErrorListener和ANTLRErrorStrategy。這將允許您直接在解析操作中智能地處理格式錯誤的語句。如果兩個連續引號定義了一個轉義引號,則將其列爲一個有效的子規則,如編輯後的答案中所示。 – GRosenberg

+0

無論誰低估,請解釋。 – GRosenberg