我可以在运行时添加Antlr令牌吗?

时间:2011-05-24 09:20:56

标签: antlr antlr3

我的情况是,我的语言包含一些在构建时未知但在运行时已知的单词,导致需要不断重建/重新部署程序以考虑新单词。如果在Antlr中有可能从配置文件中生成一些令牌,我就会徘徊?

例如,在一个简化的例子中,如果我有一个规则

rule : WORDS+;

WORDS : 'abc';

我的语言在runntime遇到'bcd',我希望能够修改配置文件以将bcd定义为单词,而不是必须重建然后重新部署。

1 个答案:

答案 0 :(得分:18)

您可以为lexer类添加某种集合。此集合将包含所有运行时单词。然后在规则中添加一些可能与这些运行时单词匹配的自定义代码,并在集合中存在令牌时更改令牌的类型。

演示

假设您要解析输入:

"foo bar baz"

并且在运行时,单词"foo""baz"应该成为特殊的运行时单词。以下语法说明了如何解决这个问题:

grammar RuntimeWords;

tokens {
  RUNTIME_WORD;
}

@lexer::members {

  private java.util.Set<String> runtimeWords;

  public RuntimeWordsLexer(CharStream input, java.util.Set<String> words) {
    super(input);
    runtimeWords = words;
  }
}

parse
  :  (w=. {System.out.printf("\%-15s :: \%s \n", tokenNames[$w.type], $w.text);})+ EOF
  ;

Word
  :  ('a'..'z' | 'A'..'Z')+
     {
       if(runtimeWords.contains(getText())) {
         $type = RUNTIME_WORD;
       }
     }
  ;

Space
  :  ' ' {skip();}
  ;

还有一个小测试班:

import org.antlr.runtime.*;
import java.util.*;

public class Main {
  public static void main(String[] args) throws Exception {
    Set<String> words = new HashSet<String>(Arrays.asList("foo", "baz"));
    ANTLRStringStream in = new ANTLRStringStream("foo bar baz");
    RuntimeWordsLexer lexer = new RuntimeWordsLexer(in, words);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    RuntimeWordsParser parser = new RuntimeWordsParser(tokens);        
    parser.parse();
  }
}

将产生以下输出:

RUNTIME_WORD    :: foo 
Word            :: bar 
RUNTIME_WORD    :: baz

演示II

这是另一个针对您的问题量身定制的演示(我最初过快地浏览了您的问题,但我会留下我的第一个演示,因为它可能会派上用场)。其中没有太多评论,但我的猜测是你不会有问题把握所发生的事情(如果没有,请不要犹豫要求澄清!)。

grammar RuntimeWords;

@lexer::members {

  private java.util.Set<String> runtimeWords;

  public RuntimeWordsLexer(CharStream input, java.util.Set<String> words) {
    super(input);
    runtimeWords = words;
  }

  private boolean runtimeWordAhead() {
    for(String word : runtimeWords) {
      if(ahead(word)) {
        return true;
      }
    }
    return false;
  }

  private boolean ahead(String word) {
    for(int i = 0; i < word.length(); i++) {
      if(input.LA(i+1) != word.charAt(i)) {
        return false;
      }
    } 
    return true; 
  }
}

parse
  :  (w=. {System.out.printf("\%-15s :: \%s \n", tokenNames[$w.type], $w.text);})+ EOF
  ;

Word
  :  {runtimeWordAhead()}?=> ('a'..'z' | 'A'..'Z')+
  |  'abc'
  ;

Space
  :  ' ' {skip();}
  ;

和班级:

import org.antlr.runtime.*;
import java.util.*;

public class Main {
  public static void main(String[] args) throws Exception {
    Set<String> words = new HashSet<String>(Arrays.asList("BBB", "CDEFG"));
    ANTLRStringStream in = new ANTLRStringStream("BBB abc CDEFG");
    RuntimeWordsLexer lexer = new RuntimeWordsLexer(in, words);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    RuntimeWordsParser parser = new RuntimeWordsParser(tokens);        
    parser.parse();
  }
}

将产生:

Word            :: BBB 
Word            :: abc 
Word            :: CDEFG 

如果某些运行时单词以另一个运行时单词开头,请小心。例如,如果您的运行时单词包含"stack""stacker",则需要先检查较长的单词!根据字符串的长度对集合进行排序应该是有序的。

最后一句提醒:如果只有"stack"在运行时单词列表中并且词法分析器遇到"stacker",那么您可能不想创建"stack" - 令牌,离开"er"悬空。在这种情况下,您需要检查word中最后一个字符后面的字符是否一个字母:

private boolean ahead(String word) {
  for(int i = 0; i < word.length(); i++) {
    if(input.LA(i+1) != word.charAt(i)) {
      return false;
    }
  }
  // charAfterWord = input.LA(word.length())
  // assert charAfterWord != letter
  // note that charAfterWord could also be EOF
  return ... ; 
}