【问题标题】:Is it advisable to use tokens for the purpose of syntax highlighting?是否建议使用标记来突出显示语法?
【发布时间】:2017-11-13 01:39:13
【问题描述】:

我正在尝试使用 Xamarin 在 Android 上的 C# 中实现语法突出显示。我正在使用 C# 的 ANTLR v4 library 来实现这一点。我的代码目前是使用this grammar 突出显示Java 的语法,它不会尝试构建解析树并使用访问者模式。相反,我只是将输入转换为令牌列表:

private static IList<IToken> Tokenize(string text)
{
    var inputStream = new AntlrInputStream(text);
    var lexer = new JavaLexer(inputStream);
    var tokenStream = new CommonTokenStream(lexer);
    tokenStream.Fill();
    return tokenStream.GetTokens();
}

然后我遍历荧光笔中的所有标记,并根据它们的种类为它们分配颜色。

public void HighlightAll(IList<IToken> tokens)
{
    int tokenCount = tokens.Count;

    for (int i = 0; i < tokenCount; i++)
    {
        var token = tokens[i];
        var kind = GetSyntaxKind(token);
        HighlightNext(token, kind);

        if (kind == SyntaxKind.Annotation)
        {
            var nextToken = tokens[++i];
            Debug.Assert(token.Text == "@" && nextToken.Type == Identifier);
            HighlightNext(nextToken, SyntaxKind.Annotation);
        }
    }
}

public void HighlightNext(IToken token, SyntaxKind tokenKind)
{
    int count = token.Text.Length;

    if (token.Type != -1)
    {
        _text.SetSpan(_styler.GetSpan(tokenKind), _index, _index + count, SpanTypes.InclusiveExclusive);
        _index += count;
    }
}

最初,我认为这是明智的,因为语法突出显示在很大程度上与上下文无关。但是,我已经发现自己需要在 @ 前面添加特殊情况标识符,因为我希望这些标识符像在 GitHub (example) 上一样作为注释突出显示。 GitHub 提供了在某些情况下为标识符着色的更多示例:hereListArrayList 是着色的,而 mItems 不是。在这些场景中,我可能需要添加更多代码来突出显示标识符。

我的问题是,在这里检查标记而不是分析树是个好主意吗?一方面,我担心当一个令牌的邻居改变它应该如何突出显示时,我最终可能不得不做很多特殊情况。另一方面,解析将为内存受限的移动设备增加额外的开销,并且当用户在代码编辑器中编辑文本时,实现高效的语法突出显示(例如,不重新标记/解析所有内容)变得更加复杂。我还发现处理所有标记类型而不是解析器规则类型要简单得多,因为您只需在token.Type 上使用switch,而不是覆盖一堆Visit* 方法。

语法高亮的完整代码可供参考,here.

【问题讨论】:

  • 你总是需要解析一棵树。复杂的表达式有优先级,顺序很重要。我们都知道 1 + 5 * 6 = 31。然后是 5 + 6 / 4 + 2 = 8.5。

标签: c# .net parsing antlr antlr4


【解决方案1】:

这取决于你的语法高亮。

如果你使用一个简单的解析器,那么文本中的任何语法错误都会导致高亮失败。这使得它成为一个非常脆弱的解决方案,因为很多你可能想要语法高亮的文本都不能保证是正确的(特别是用户输入,在完全输入之前它最多不会是正确的)。由于语法高亮有助于使语法错误可见并且经常用于此目的,因此完全失败语法错误会适得其反。

有错误的文本不容易适应语法树。但它确实比令牌流具有更多的结构。最准确的表示可能是子树片段的森林,但与树相比,这是一种更难处理的数据结构。

无论您选择哪种解决方案,您最终都会在相互冲突的目标之间进行协商:复杂性、准确性、速度和可用性。解析器可能是解决方案的一部分,但即席模式匹配也可能是解决方案的一部分。

【讨论】:

    【解决方案2】:

    您的方法非常好,几乎每个人都在使用。通过环顾四周来微调类型匹配是完全正常的(而且因为令牌类型被缓存,所以它很便宜)。因此,如果您需要调整实际使用的SyntaxKind,您可以随时在令牌流中向后或向前看。不要开始解析您的输入。它对你没有帮助。

    【讨论】:

      【解决方案3】:

      我最终选择使用解析器,因为临时规则太多。例如,虽然我想将常规标识符着色为白色,但我希望类型声明中的类型(例如 class C 中的 C)为绿色。最终总共有大约 20 条这样的特殊规则。此外,与我的应用程序中的其他瓶颈相比,解析的额外开销被证明是微不足道的。

      有兴趣的可以在这里查看我的代码:https://github.com/jamesqo/Repository/blob/e5d5653093861bc35f4c0ac71ad6e27265e656f3/Repository.EditorServices/Internal/Java/Highlighting/JavaSyntaxHighlighter.VisitMethods.cs#L19-L76。我已经突出显示了我必须制定的所有约 20 条特殊规则。

      【讨论】:

      • 这真的有效吗?您知道只有在输入语法正确时才能获得好的解析树。否则,大多数情况下都会出现未正确突出显示的元素。根据输入可能会发生的大部分文档没有突出显示,因为没有您可以访问的解析树。
      • @MikeLischke ANTLR 确实有 API 来处理与语法不匹配的畸形代码,即 IErrorNode,但我承认我还没有尝试过。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多