【问题标题】:Code substitution for DSL using ANTLR使用 ANTLR 的 DSL 代码替换
【发布时间】:2014-03-13 06:40:28
【问题描述】:

我正在开发的 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;'

每当发现 & 变量标记时,我们立即切换到解释该变量的值。如上所述,这可能意味着您以一种无效的方式设置代码,其中充满了仅在值恰到好处时才完成的半语句。可以在文本中的任何位置重新定义变量。

严格来说,当前的语言定义并没有不允许在彼此内部嵌套 &var,但当前的解析无法处理这个问题,如果不允许,我也不会感到不安。

目前我正在使用访问者构建一个解释器,但我坚持使用这个。

我怎样才能构建一个词法分析器/解析器/解释器来允许我这样做?感谢您的帮助!

【问题讨论】:

  • 在你的语法中,这是一些令人讨厌的诡计。在单个entry 中可以出现VARIABLEs 的位置和数量是否有任何限制?我的意思是,这是否允许: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!';
  • 是的,这确实是一个有效的声明:/ 我怀疑有人曾经这样使用它,但该应用程序已经存在多年,因此您永远无法确定客户做了什么。读取字符以组成标记时的当前实现只是切换到从变量值读取,但我不知道这是否/如何与 ANTLR 兼容。
  • 我认为在解析过程中没有一种简单的方法可以插入代码/令牌。至少不使用提供的 API 类(您当然可以实现自己的 TokenStream 并将其提供给解析器)。
  • 如果你这样做SET baz = '&baz'; &baz;会发生什么?
  • 3/ 您能否添加限制以禁止愚蠢的行为,例如不包含整个令牌的 &-vars 或 set bob = set; &bob xyzzy = plugh; 之类的东西 :-)

标签: parsing antlr interpreter antlr4 lexer


【解决方案1】:

所以我找到了解决这个问题的方法。我认为它可能会更好——因为它可能会进行大量的数组复制——但至少它现在可以工作。

编辑:我之前错了,我的解决方案会消耗它找到的任何 & ,包括那些在有效位置的,比如在字符串常量内部。这似乎是一个更好的解决方案:

首先,我扩展了 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;
        }
      }
    }

其次,我扩展了 Lexer,以便在遇到 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,所以这会使维护变得更加困难,但它现在有效。

【讨论】:

    【解决方案2】:

    处理您的需求的标准模式是实现一个符号表。最简单的形式是作为键:值存储。在您的访问者中,添加遇到的 var 声明,并在遇到 var 引用时读出值。

    如上所述,您的 DSL 没有对声明的变量定义范围要求。如果您确实需要作用域变量,则使用一组键:值存储,在作用域进入和退出时推送和弹出。

    看到这个相关的StackOverflow answer.

    另外,由于您的字符串可能包含命令,您可以简单地将内容作为初始解析的一部分进行解析。也就是说,使用包含完整有效内容集的规则扩展您的语法:

    set_variable
       : 'SET' ID '=' stringLiteral ';'
       ;
    
    stringLiteral: 
       Quote Quote? ( 
         (    set_variable
            | print_line
            | VARIABLE
            | ID
         )
         | STRING_CONSTANT  // redefine without the quotes
       )
       Quote
       ;
    

    【讨论】:

    • 我认为这行不通。正如您所描述的那样,还有另一个变量系统,但是 &VAR 系统是一个代码替换,更像是一个预处理器宏,它可以包含格式错误的语句。所以,SET foo = '''BAR'; SET baz = &amp;foo'; 将是有效的陈述,但我不可能通过重新定义你描述的 stringLiteral 来解释它。我本质上需要 &foo 来标记为 ' + BAR
    • 要处理格式错误的语句,请扩展 ANTLRErrorListener 和 ANTLRErrorStrategy。这将允许您直接在解析操作中智能地处理格式错误的语句。如果两个连续的引号定义了一个转义引号,则将其列为有效的子规则,如编辑后的答案中所示。
    猜你喜欢
    • 2016-03-29
    • 1970-01-01
    • 2012-04-10
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 1970-01-01
    • 1970-01-01
    • 2015-12-26
    相关资源
    最近更新 更多