ANTLR:如何避免在用户修改文本时重新解析整个文件

ANTLR: How to avoid re-parsing entire file when user modifies text

编辑: 对于那些 interested/who 想要确切了解我在做什么的人,可以找到我的应用程序的源代码 here


我正在使用 C# 构建一个代码编辑器应用程序,它提供语法高亮显示。我目前正在使用 ANTLR for C# 来解析代码以突出显示它。到目前为止,我的应用程序可以在用户最初打开文件时非常快速地突出显示代码。但是,我还没有编写任何代码来在用户开始编辑时重新突出显示文本。

我希望编辑器能够很好地处理大文件,所以我不想在用户每次键入一个字符时都重新解析整个文件。我做了一些研究,看起来我正在寻找的是一个增量解析器。不幸的是,它看起来像 ANTLR v4 can't do incremental parsing,所以我不确定该怎么做。

我的问题是:有没有我可以采用的另一种方法,即使用 ANTLR,以便在用户键入时不冻结应用程序?我真的很犹豫要不要放弃 ANTLR,因为有 a bunch of free grammars 可用,所以添加对新语言的支持并不是什么大事。我研究了 TextMate 语法,VSCode 使用了很多语法,但我不理解它们,也没有可用于操作它们的 C# 库。

感谢您的帮助!

我不会在每次击键后进行解析,但会解析整个文件。这对于我创建的特定于域的语言的中等大小的文件非常有用。我没有尝试只解析文件的一部分,而是使用了一种混合方法,在三个条件中的第一个存在时进行解析:

  1. 用户类型 n 个字符
  2. 计时器表示 m 毫秒内没有变化。
  3. 对于某些语法,用户键入行 terminator/separator 字符;

最重要的是,您可能会惊讶于人们在输入任何强加语法的内容时会花多少时间停下来思考。可以利用这些暂停在用户思考时做有用的工作,即使是 400 毫秒。由于它们的语法,我在为工作创建的 DSL 中使用#1 和#2。

"no change" 时钟在每次击键事件后重置,n 字符计数器当然在 n[=29 之后发生解析时设置=] 字符。我发现像这样的组合方法在 IDE 类型的环境中效果很好。

要记住的一件事是,如果你这样做,不要在发现语法错误时弄乱文本控件的插入点,因为错误在键入时是不可避免的。我只是在标签中显示一条消息:

    public override void Recover(Parser recognizer, RecognitionException e)
    {
        IToken token = recognizer.CurrentToken;
        string message = string.Format("parse error at line {0}, position {1} right before {2} ", token.Line, token.Column, GetTokenErrorDisplay(token));
        BasicEnvironment.SyntaxError = message;

在我的使用环境中,定时器通常控制何时关闭;使用 800 毫秒和 10 个字符的值我得到了很好的结果,计时器通常控制解析何时开始。

@JLH 基本上也说明了我正在使用的方法,但我想添加一些您可能需要注意的事项:
首先,我会在与编辑器的 UI 线程不同的线程中进行解析,以防止解析器在用户决定继续编码的同时启动的情况。如果您使用不同的线程,用户可以继续编码,甚至不会注意到解析器在后台 运行。当然,您需要使用某种机制在这种情况下取​​消解析,或者至少不要在编辑器中使用生成的解析树做任何事情,因为它已经过时了。

接下来我发现 ANTLR 在处理中等大小的文件(~100 行)时会变得非常慢。真的很慢,我的意思是解析最多可能需要 20 秒!这可以通过将 ANTLR 的默认解析算法切换为 SLL 并使用 BailoutStrategy 来防止,这样它就不会尝试从语法错误中恢复。如果发生这种情况,您必须使用正常的 LL 模式再次解析整个内容,以检查它是否真的是语法错误或者只是 SLL 无法处理的东西。
通过这种方法,我将一些文件的解析时间从大约 20 秒减少到大约 1 秒。

你可以看看我的 source code 中的一个有效的(当然不是完美的)实现(虽然它在 java 中,但原理应该是相同的)。