我试图基于this ANTLR v4 grammar file在C#中为Java编写语法高亮显示器。为此,我目前正在尝试跟踪访问者中每次传递节点时添加的_index
字段。这是代码:
private class Visitor : JavaBaseVisitor<object>
{
private readonly string _rawText;
private readonly SpannableString _text;
private readonly ISyntaxStyler _styler;
private int _index;
internal Visitor(string text, ISyntaxStyler styler)
{
_rawText = text;
_text = new SpannableString(text);
_styler = styler;
}
public override object VisitAnnotation([NotNull] JavaParser.AnnotationContext context)
{
Advance("@", SyntaxKind.Annotation);
VisitAnnotationName(context.annotationName());
this.VisitChildren(context, 2);
return null;
}
public override object VisitPackageDeclaration([NotNull] JavaParser.PackageDeclarationContext context)
{
int index = 0;
var child = context.GetChild(0);
while (child is JavaParser.AnnotationContext)
{
Visit(child);
child = context.GetChild(++index);
}
Advance("package", SyntaxKind.Keyword);
this.VisitChildren(context, index + 1);
return null;
}
public override object VisitTerminal(ITerminalNode node)
{
Advance(node.Symbol.Text, SyntaxKind.Identifier);
return null;
}
internal SpannableString HighlightText()
{
Visit(CreateTree(_rawText));
return _text;
}
private void Advance(int count, SyntaxKind kind)
{
var span = _styler.GetSpan(kind);
_text.SetSpan(span, _index, _index + count, SpanTypes.InclusiveExclusive);
_index += count;
}
private void Advance(string toSkip, SyntaxKind kind)
{
int count = toSkip.Length;
Debug.Assert(string.Compare(_rawText, _index, toSkip, 0, count) == 0);
Advance(count, kind);
}
private static JavaParser.CompilationUnitContext CreateTree(string text)
{
var inputStream = new AntlrInputStream(text);
var lexer = new JavaLexer(inputStream);
var tokenStream = new CommonTokenStream(lexer);
var parser = new JavaParser(tokenStream);
return parser.compilationUnit();
}
}
要注意的方法是Advance
和VisitTerminal
。本质上,我依赖于每个节点最终分解成终端的事实,因此如果一个节点没有被另一个覆盖处理(例如@
中的VisitAnnotation
),则每个节点都会它的终端将在VisitTerminal
。每次调用VisitTerminal
都会添加一个索引字段,我假设该字段与AST和原始文本同步。也就是说,_index
应表示原始文本中的索引和当前标记的索引。
我最近遇到了空白问题。语法规则似乎eat up whitespace silently而没有在任何解析器规则中提及它。这会导致空格标记不通过VisitTerminal
,导致所有内容都不对齐。例如,请考虑以下Java代码段:
package a.b.c.d ;
直到处理package
之后,当前令牌的索引与原始文本中的索引相同。但是,当处理a
时,_index
没有为空白增加,因此它落后于它应该是的1。处理;
时,它将落后于它应该是2。然后将获得
a
的着色,a
将获得下一个.
的着色,d
将获得;
的着色等等。
有没有办法在访问者中获取空白令牌,这样索引就不会搞砸了?感谢。
答案 0 :(得分:0)
你是对的,语法吃了空白。但是词法分析员没有。我处理语法突出显示 - 因此显式令牌处理 - 包括如下所示的空格。主要是覆盖词法分析器的Emit
方法。这是我在这里概述的过程的一部分。
一个允许我在令牌源中记录类型,开始和停止长度的小类。
public class TokenExtent
{
public string Name { get; set; }
public int Start { get; set; }
public int Length { get; set; }
public TokenExtent(string name, int start, int stop)
{
Name = name;
Start = start;
Length = stop - start + 1;
}
}
我在每次解析时填写的TokenExtents列表。
public static List<TokenExtent> TokenExtents = new List<TokenExtent>();
我在其中覆盖Emit()
方法以对代币进行分类的自定义词法分析器,包括WHITESPACE
public class BailLexer : LISBASICLexer
{
public BailLexer(ICharStream input) : base(input) { }
public override IToken Emit()
{
IToken token = base.Emit();
switch (token.Type)
{
case ID :
{
TokenExtent extent = new TokenExtent("ID", token.StartIndex, token.StopIndex);
BasicEnvironment.TokenExtents.Add(extent);
break;
}
case COMMENT :
{
TokenExtent extent = new TokenExtent("COMMENT", token.StartIndex, token.StopIndex);
BasicEnvironment.TokenExtents.Add(extent);
break;
}
case WS:
{
TokenExtent extent = new TokenExtent("WS", token.StartIndex, token.StopIndex);
BasicEnvironment.TokenExtents.Add(extent);
break;
}
我的表单中的Higlight()
方法,我应用了样式:
private void Highlight()
{
foreach (TokenExtent ext in BasicEnvironment.TokenExtents)
{
switch (ext.Name)
{
case "ID" :
{
etCode2.Select(ext.Start, ext.Length);
etCode2.SelectionColor = Color.Blue;
break;
}
case "COMMENT" :
{
etCode2.Select(ext.Start, ext.Length);
etCode2.SelectionColor = Color.DimGray;
break;
}
case "WS":
{
etCode2.Select(ext.Start, ext.Length);
etCode2.SelectionBackColor = Color.BurlyWood;
break;
}
}
}
}
正如您所看到的,如果您使用词法分析器进入,那么您可以将这些空格标记视为与词法分析器规则命名的任何其他标记相同。