将状态数字放入语法中

Put state numbers into grammar

我能否以某种方式将 ATN 状态编号插入出现它们的语法中?

我正在尝试制作一个工具,自动将所有不可避免的文字值添加到文档中。例如给定以下规则:

statement
    :   block
    |   'assert' expression (':' expression)? ';'
    |   'if' '(' expression ')' statement ('else' statement)?
    ;

如果用户写 assert 我会添加 ; 或者如果用户输入 if 我想添加括号 ( ).

我在想,如果我有状态编号,那么我可以解析语法以找到文字值,然后将它们与适当的状态编号一起存储,这样当用户 "enters" 特定状态时,解析器可以检查是否有任何可以为用户自动插入的文本。

给定的语法不可能做到这一点,因为语法总是描述有效输入。
因此,当您尝试解析用户尚未完成语句的输入时(例如,他刚刚键入 assert),您将遇到错误。当然,您随后可以尝试依靠 ANTLR 的错误恢复系统来为您处理该错误,但我认为这是一个很好的 "dirty" 解决方案。

你的选择(在我看来)是

  1. 你写了一个语法来匹配各自不完整的语句,并根据那个解析器决定是否插入一个特定的字符
  2. 您完全独立地处理插入过程(我会推荐),因为它与解析无关。如果您希望在更改语法时自动更新完成,我会说您需要编写一个程序,将语法中的相应信息写入一个文件,然后您可以使用该文件输入 插入器

好吧,我试了一下 API 并不太难。下面是将所有状态编号插入到语法文件副本中的代码,该副本位于进入状态时已识别的语法区域之前或之后。老实说,我不确定间隔为空时的含义。大约三分之一的州似乎属于这种情况。

用于插入文件的代码是从 xor_eq's answer 中逐字提取的。

此代码的结果如下所示:

private static String GRAMMAR_FILE_NAME = "JavaSimple.g4";
private static String EDITED_GRAMMAR_FILE_NAME = "JavaSimple_edited.g4";

private static void insertStateNumbersIntoGrammar() throws IOException, RecognitionException {
    copyGrammarFile();

    // Load tokens
    ANTLRInputStream input = new ANTLRFileStream(GRAMMAR_FILE_NAME);
    ANTLRv4Lexer lexer = new ANTLRv4Lexer(input);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    tokens.fill();

    // Load Grammar
    String contents = new String(Files.readAllBytes(Paths.get(GRAMMAR_FILE_NAME)));
    Grammar g = new Grammar(contents);

    List<Insert> inserts = new ArrayList<Insert>();
    boolean before = false;
    for (ATNState state : g.atn.states) {
        int stateNr = state.stateNumber;
        Interval interval = g.getStateToGrammarRegion(stateNr);
        if (interval != null) {
            Token token = before ? tokens.get(interval.a) : tokens.get(interval.b);
            int i = before ? token.getStartIndex() : token.getStopIndex() + 1;

            String stateStr = "[" + stateNr + "]";
            long insertSize = calcInsertLengthBefore(inserts, i);
            insert(EDITED_GRAMMAR_FILE_NAME, i + insertSize, stateStr.getBytes());
            inserts.add(new Insert(i, stateStr));
        }
    }
}

private static int calcInsertLengthBefore(List<Insert> inserts, int index) {
    return inserts.stream()
            .filter(insert -> insert.index < index)
            .flatMapToInt(insert -> IntStream.of(insert.state.length()))
            .sum();
}

private static void insert(String filename, long offset, byte[] content) throws IOException {
    RandomAccessFile r = new RandomAccessFile(new File(filename), "rw");
    RandomAccessFile rtemp = new RandomAccessFile(new File(filename + "~"), "rw");
    long fileSize = r.length();
    FileChannel sourceChannel = r.getChannel();
    FileChannel targetChannel = rtemp.getChannel();
    sourceChannel.transferTo(offset, (fileSize - offset), targetChannel);
    sourceChannel.truncate(offset);
    r.seek(offset);
    r.write(content);
    long newOffset = r.getFilePointer();
    targetChannel.position(0L);
    sourceChannel.transferFrom(targetChannel, newOffset, (fileSize - offset));
    sourceChannel.close();
    targetChannel.close();
}

private static void copyGrammarFile() {
    File source = new File(GRAMMAR_FILE_NAME);
    File target = new File(EDITED_GRAMMAR_FILE_NAME);
    try {
        Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static class Insert {
    final Integer index;
    final String state;

    Insert(int index, String state) {
        this.index = index;
        this.state = state;
    }
}