2

我在开发我们的小 DSL 方面取得了一些进展,但是在尝试突出显示我们正在使用的 TextEditorControl 中的注释时遇到了问题。顺便说一下,ICSharpCode 控件非常棒,与 ANTLR 结合使用它为 DSL 提供了一个很好的平台。

我有一个工作语法和词法分析器,并且在文本编辑器中编写了一个突出显示策略,它也很好用。拒绝正确着色的 DSL 的唯一元素是我在隐藏通道上的“评论”。

    Comment
  :  '//' ~('\r' | '\n')* {$channel=Hidden;}  
  |  '/*' .* '*/'  {$channel=Hidden;}        
  ;

令人沮丧的是,如果我将 Comment lexrule 从隐藏通道中移除,我可以使突出显示工作......但是当我这样做时,解析器在评论之后的最后一段文本之后的评估期间停止解析。

举个例子; 当评论被隐藏但在第一个“abc”处停止解析时,这有效

   //Comment

   abc=[7,8,9];

   return abc[2];

我一直在尝试单独访问隐藏通道,以便我可以将默认和隐藏令牌列表组合成一个按开始索引排序的列表,然后从那里突出显示,但我没有运气使用 CommonTokenStream 的 BaseRecognizer.Hidden 参数构造函数。

我当前突出显示 TextEditor 行的尝试如下所示

    private void MarkUsingParalexTokens(IDocument document, LineSegment line)
    {
        var text = document.GetText(line).ToLower();
        var input = new ANTLRStringStream(text);
        _lexer.CharStream = input;
        _tokens = new CommonTokenStream(_lexer, BaseRecognizer.Hidden);
        //_tokens.TokenSource =_lexer;


        var wordStart = 0;
        if (_tokens.Count > 1)
        {
            do
            {
                _tokens.Consume();

            } while (_tokens.LastToken.Type != ParalexLexer.EOF);



            var tokenList = _tokens.GetTokens();

            var tokenEnum = tokenList.GetEnumerator();

            var tokenAvailable = tokenEnum.MoveNext();
            if (tokenAvailable)
            {
                for (var i = 0; i < text.Length; i++)
                {
                    var token = tokenEnum.Current;
                    if (token != null)
                    {
                        var c = text[i];
                        if (c == ' ' || c == '\t')
                        {
                            if (i > wordStart)
                                AddWord(document, line, wordStart, i);
                            line.Words.Add(c == ' ' ? TextWord.Space : TextWord.Tab);
                            wordStart = i + 1;
                        }
                        else
                        {
                            var atStartOfToken = (i == token.StartIndex);

                            if (atStartOfToken)
                            {
                                if (i > wordStart)
                                    AddWord(document, line, wordStart, i);

                                var tokenLength = token.StopIndex - token.StartIndex + 1;

                                AddWord(document, line, i, tokenLength, token);
                                tokenEnum.MoveNext();
                                wordStart = i + tokenLength;
                                i = wordStart - 1;
                            }
                        }
                    }

                }

            }
        }

        if (wordStart < line.Length)
                AddWord(document, line, wordStart, line.Length);
    }

    void AddWord(IDocument document, LineSegment line, int startOffset, int length, IToken token = null)
    {
        if (length==0) return;

        var hasSpecialColor = token != null;
        var color = hasSpecialColor ? GetColor(token) : _highlightColors["Default"];

        line.Words.Add(new TextWord(document, line, startOffset, length, color, !hasSpecialColor));
        if (token != null) Debug.WriteLine("From typing: Text {0}, Type {1}, Color {2}", token.Text, token.Type, color);
    }

    private HighlightColor GetColor(IToken token)
    {
        var name = token.Type;
        var groupName = "Default";

        var punctuation = new[]
            {6, 7, 9, 14, 15, 16, 17, 18, 22, 28, 33, 34, 47, 48, 49, 50, 51, 52, 55, 56, 57, 58, 60, 62, 65, 71};
        var paralexVerbs = new[] { 8, 13, 23, 26, 27, 31, 32, 38, 39, 40, 54, 64, 68, 73, 75, 76 };
        var paralexNouns = new[] {11, 12, 42, 43, 59, 66};
        var paralexNumbers = new[] { 53, 61, 41 };
        var paralexStrings = new[] {70};

        if (Array.IndexOf(punctuation, name) >= 0)
        {
            groupName = "Punctuation";
        }
        else if (Array.IndexOf(paralexVerbs, name) >= 0)
        {
            groupName = "ParalexVerbs";
        }
        else if (Array.IndexOf(paralexNouns, name) >= 0)
        {
            groupName = "ParalexNouns";
        }
        else if (Array.IndexOf(paralexNumbers, name) >= 0)
        {
            groupName = "ParalexNumbers";
        }
        else if (Array.IndexOf(paralexStrings, name) >= 0)
        {
            groupName = "ParalexStrings";
        }
        else if (name == 19)
        {
            groupName = "ParalexComment";
        }

        return _highlightColors[groupName];

    }

似乎需要 do..while 才能将令牌放入列表中,否则 GetTokens 永远不会提供任何东西。在上面的代码形式中,即使在我的测试装置中输入注释,也不会产生任何标记。

如果我调用 CommonTokenStream 的参数化构造函数并使用基本构造函数,我会得到一个很好的标记流,我可以着色但所有隐藏的标记都是......好吧......我猜是隐藏的。

您对这个小问题的集体想法以及您对如何以编程方式维护类型列表而不是每次更改解析器时都必须重新调整它们的任何想法都将不胜感激。

我曾想过为需要着色的每种类型创建独立的通道,但目前我只是递归地添加到我的问题中!

提前感谢伊恩

编辑:

感谢您的出色回答山姆,非常感谢。它被标记和评分。

我采用了覆盖概念,因为它还解决了按名称跟踪各种 Token 类型的问题,从而在我添加到语法时简化了我的维护。

我创建了一个语法高亮词法分析器和一个单独的评估词法分析器,并使用了我在原始语法中创建的独立通道。

评论现在看起来像这样,虽然我认为 alt 还没有工作,主要工作很好

Comment
:  '//' ~('\r' | '\n')* 
|  '/*' .* '*/'        
;

Lexer 成员添加了这些

        @lexer::members{

    public const int StringChannel = 98;
    public const int NumberChannel = 97;
    public const int NounChannel = 96;
    public const int VerbChannel = 95;
    public const int CommentChannel = 94;

    }

并且高亮词法分析器在 Emit() 上使用此覆盖,您建议的覆盖也已到位并且正在工作

public class HighlightLexer : ParalexLexer
{
    public override IToken Emit()
    {
        switch (state.type)
        {
            case Strng:
                state.channel = StringChannel;
                break;
            case Nmber:
            case Null:
            case Bool:
            case Instrument:
            case Price:
            case PeriodType:
                state.channel = NumberChannel;
                break;
            case BarPeriod:
            case BarValue:
            case InstrumentList:
            case SMA:
            case Identifier:
                state.channel = NounChannel;
                break;
            case Assert:
            case Do:
            case Else:
            case End:
            case Fetch:
            case For:
            case If:
            case In:
            case Return:
            case Size:
            case To:
            case While:
            case T__77:
                state.channel = VerbChannel;
                break;
            case Comment:
                state.channel = CommentChannel;
                break;
            default:
                state.channel = DefaultTokenChannel;
                break;
        }

        return base.Emit();
    }
}

困扰我的一件事是明显无法轻松获取令牌列表。我无法让 CommonTokenStream 在没有延迟和绊倒的情况下交付其令牌。我对“_tokens”使用 BufferedTokenStream 进行了抨击,因为这听起来更像是我所追求的,嘿 presto .. 令牌!我怀疑我的用户错误?

标记方法现在看起来像这样

private void MarkUsingParalexTokens(IDocument document, LineSegment line)
    {
        var text = document.GetText(line).ToLower();
        var input = new ANTLRStringStream(text);
        _lexer.CharStream = input;
        _tokens.TokenSource = _lexer;

        var wordStart = 0;
        var tokenCounter = 1;

        for (var i = 0; i < text.Length; i++)
        {
            var token = _tokens.LT(tokenCounter);
            if (token != null)
            {
                var c = text[i];
                if (c == ' ' || c == '\t')
                {
                    if (i > wordStart)
                        AddWord(document, line, wordStart, i);
                    line.Words.Add(c == ' ' ? TextWord.Space : TextWord.Tab);
                    wordStart = i + 1;
                }
                else
                {
                    var atStartOfToken = (i == token.StartIndex);

                    if (atStartOfToken)
                    {
                        if (i > wordStart)
                            AddWord(document, line, wordStart, i);

                        var tokenLength = token.StopIndex - token.StartIndex + 1;

                        AddWord(document, line, i, tokenLength, token);
                        tokenCounter++;
                        wordStart = i + tokenLength;
                        i = wordStart - 1;
                    }
                }
            }

        }

        if (wordStart < line.Length)
                AddWord(document, line, wordStart, line.Length);
    }

    void AddWord(IDocument document, LineSegment line, int startOffset, int length, IToken token = null)
    {
        if (length==0) return;

        var hasSpecialColor = token != null;
        var color = hasSpecialColor ? GetColor(token) : _highlightColors["Default"];

        line.Words.Add(new TextWord(document, line, startOffset, length, color, !hasSpecialColor));
        if (token != null) Debug.WriteLine("From typing: Text {0}, Type {1}, Color {2}", token.Text, token.Type, color);
    }

    private HighlightColor GetColor(IToken token)
    {
        var name = token.Channel;
        var groupName = "Default";

        if (name==0)
        {
            groupName = "Punctuation";
        }
        else if (name==95)
        {
            groupName = "ParalexVerbs";
        }
        else if (name==96)
        {
            groupName = "ParalexNouns";
        }
        else if (name==97)
        {
            groupName = "ParalexNumbers";
        }
        else if (name==98)
        {
            groupName = "ParalexStrings";
        }
        else if (name == 94)
        {
            groupName = "ParalexComment";
        }

        return _highlightColors[groupName];

    }

再次感谢你的帮助。我要去看看错误识别和标记...问候伊恩

4

2 回答 2

3

我总是使用与其他解析任务不同的词法分析器来突出显示语法。用于语法高亮的词法分析器始终满足以下条件:

  • NEWLINE除了包含\ror\n字符外,没有标记。相反,多个词法分析器模式用于块注释和任何其他跨越多行的构造(这甚至适用于 ANTLR 3 词法分析器,但如果 ANTLR 3 本身不支持词法分析器模式,它会很快变得复杂)。

  • NEWLINE定义如下:

    // ANTLR 3-based syntax highlighter:
    NEWLINE : ('\r' '\n'? | '\n') {skip();};
    
    // ANTLR 4-based syntax highlighter:
    NEWLINE : ('\r' '\n'? | '\n') -> skip;
    
  • 隐藏通道上没有令牌。

如果你不想走这条路,你可以{$channel=Hidden;}从你的Comment规则中删除这些动作,而是从你的基础词法分析器派生一个新类。在派生类中,覆盖Emit(). 使用基本实现来突出显示语法,使用派生实现来传递给解析器。在某些情况下这更容易,但对于具有多行字符串或注释的语言,我们发现我们的任何产品都无法接受这种严重的性能限制。

public override IToken Emit()
{
    if (state.type == Comment)
        state.channel = Hidden;

    return base.Emit();
}
于 2013-03-19T15:06:58.680 回答
0

我使用 C++(QT/SCintilla 库),但无论如何我建议使用不同的 Lexer 进行语法高亮显示。我的高亮 Lexer 与解析器不同:

  • 不需要上下文相关的词法分析(“X”是一个关键字,如果有的话,只有当它后面跟着“Y”时,否则它是一个标识符

  • 高亮词法分析器绝不能失败

  • 我希望突出显示内置函数(解析不需要)

Gui Lexer 语法包含附加规则(在末尾)。

QUOTED_STRING_FRAGMENT
    :    '"' (~('"') | '\"')+ EOF 
    ;

// Last resort rule matches any character. This lexer should never fail.
TOKEN_FAILURE : . ;

规则 TOKEN_FAILURE 将匹配用户输入中的任何“无效”字符,并将其显示为红色背景。否则,该字符将被跳过并且突出显示将被转移。

QUOTED_STRING_FRAGMENT 处理用户输入第一个引号并且字符串尚未完成时的情况。

于 2013-03-20T11:12:18.840 回答