Могу ли я добавить токены Antlr во время выполнения?

У меня есть ситуация, когда мой язык содержит некоторые слова, которые неизвестны во время сборки, но будут известны во время выполнения, что потребует постоянной перестройки/повторной развертывания программы, чтобы учесть новые слова. Я блуждал, если в Antlr было возможно генерировать некоторые из токенов из файла конфигурации?

Например, в упрощенном примере, если у меня есть правило

rule : WORDS+;

WORDS : 'abc';

И мой язык встречает "bcd" во время выполнения, я хотел бы иметь возможность модифицировать конфигурационный файл, чтобы определить bcd как слово, а не перестраивать, а затем передислоцировать.

Ответ 1

Вы можете добавить какую-то коллекцию в свой класс lexer. Эта коллекция будет содержать все слова во время выполнения. Затем вы добавляете какой-либо пользовательский код внутри правила, который может соответствовать этим словам времени выполнения и изменять тип токена, если он присутствует в коллекции.

Demo

Предположим, вы хотите проанализировать ввод:

"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", а встреча lexer "stacker", то вы, вероятно, не хотите создавать "stack" -token и оставлять "er" зависанием. В этом случае вам нужно будет проверить, не является ли символ после последнего char в 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 ... ; 
}