使用抽象语法树生成波浪线

3

情境:

我已经为一个很少用的编程语言制作了一个扫描器、解析器和各种AST类,这是我的一个业余项目。解析器在扫描器的帮助下构建了一个异构AST,我对其进行一些操作。过去,我曾为一些IDE创建了语法高亮插件/附加组件以及其他一些元素。

问题在于错误:解析器会生成一些错误,并且可以访问构成语句的标记。然而有些错误只会在后来出现,比如无法解析标识符。我想在这样的标识符或其他有问题的标记下显示波浪线。不仅如此,我还希望能够在不失去原始文档中所有注释、空格等内容的情况下操纵我的AST节点。

在我的AST中创建一个新的Statement时,我可以轻松地将组成该Statement的标记添加为子项。但是……

问题:

如果合理可行,我希望包括支持显示波浪线。这需要语句知道构成该语句的标记的位置。不幸的是,语句有一些变化,有时包含更多的标记,有时少些标记。如果我正在创建一个只读的AST,那么这将不是问题。但是,出于重构目的,我希望我的AST可以进行读写操作!这意味着在AST中更改语句基本上意味着添加标记(也就是Statement的子项),因此Statement类应该能够重新解析自己。

这会污染AST,并且不再维护关注点分离!

技术细节:

一个替代方案是将AssignmentStatement变成一个工厂,获取一组标记,生成一个语句实例,并始终让它知道自己的标记。

在我的情况下,赋值的AST节点基本上是这样的:

一个分层示例AST可能如下所示:

AssemblyDeclaration
    .. Statement ..
    .. Statement ..
    ClassDeclaration
       .. Token .. // one or more that make up the entire class statement
       .. Token .. // one or more that make up the entire class statement
       Statement(s)
         .. Token .. // Which have their own tokens that make up the statement .. and possibly have sub-nodes of their own such as Expressions which have -their- own Tokens that comprise it.

一个基本的ast节点的概念,它是语法树中所有内容的父节点,无论是类声明、语句还是标记。
public abstract class BaseAstNode : IList<BaseAstNode>
{
    ... implementation of IList<BaseAstNode>
    ... implementation of Visitor Pattern
    ... implementation of Clonable
}    

public sealed class AssignmentStatement : BaseAstNode 
{
public Expression Expression { get; set; }; // Setting this will alter the Tokens (children!) of this node, possibly even ADD Tokens!
public TypeReference Target { get; set; }
}

public sealed class PrimitiveNumberExpression : Expression // is a BaseAstNode
{
public int Value { get; set; } // Setting this will alter the Tokens (children!) of this node!
}

public abstract class Token : BaseAstNode
{
    public Layers Layer { get; set; }
    public TokenType TokenType { get; set; }
    public int Column { get; set; }
    public int Line { get; set; }
    public int Position { get; set; }
    public abstract int Length { get; set; }
    public virtual string Value { get; set; }
    public override string ToString(){}
}

别人是怎么解决这个问题的?这是正确的方式吗?


你的当前代码如何构建AssignmentStatement对象? - BlueMonkMN
var stmt = new AssignmentStatement { Expression = Expression.Null, Target = TypeReference.Null }; .. 只有在解析器生成的表达式和引用之后才能进行。 - Jaapjan
我想我的问题是成员如何填充数据,为什么类也不能填充起始值和长度值? - BlueMonkMN
语句是语法树的一部分,由解析器填充。每个语句都可以轻松获得开始和结束标记。但是,除非我开始跟踪语句由哪些令牌组成以及它们的位置,否则我仍然无法在我的标识符下放置花括号。 - Jaapjan
1个回答

0

如果您可以获取发生错误的语句的代码,那么您应该能够重新解析单个语句,此时跟踪令牌的位置并注意无效的令牌(因为您第一次没有保存令牌的位置)。至少您不必重新解析整个文档,只需处理一行...也许可以重用您已经拥有的一些解析代码。

或者,确实存储每个令牌的位置(在文档中的起始位置和长度),以便在需要引用其源时轻松访问该令牌的位置。为什么不这样做呢?

如果您可以从令牌对象重新生成令牌文本,则可能有一些可能的优化,例如不存储令牌长度,或仅在行内搜索该文本,假设您要对该行上该令牌的每个出现执行相同的操作(或所有行)。

编辑:现在您已将令牌与它们解析出的原始源代码链接起来,您需要将语句与它们解析出的令牌链接起来。您可以通过以下两种方式完成此操作:

  1. 在BaseAstNode中添加一个List<Token>成员变量。或者
  2. 在每个派生语句类中添加特定的Token成员变量,以便您确切地知道哪个标记链接到语句的哪个部分。

现在,您从语句到源代码都有了一条链,因此您可以确定与每个语句的每个部分相关联的源代码。

编辑2:如果您正在尝试弄清楚实际解析如何将标记处理为BaseAstNode派生对象,则我尚未考虑该问题(这不是非常清楚的问题)。由于我以前没有做过这件事,我的建议可能不是第一次尝试时最好的。但希望它可以根据需要进行改进。我的第一个想法是使用System.Xml命名空间,它也实现了解析器。也许您的一些设计可以模仿XML解析器和相关类。XmlNode可能类似于您的BaesAstNode类。XmlElement可能类似于您的Token类。XmlDocument是XmlNode的一个专业化,它通过一个LoadXml函数处理所有解析,并填充对象的子元素。

所以,按照Xml命名空间的示例,您可以将ParseCode作为ClassDeclaration的成员,用它来填充对象的所有适当子对象。它可以接受字符串或有序的令牌集合作为输入,并将BaseAstNode的派生实例添加到存储在类中的BaseAstNode对象的有序集合中。但并不是所有的解析都需要在一个函数中完成。当您解析文本或令牌时,我假设您会从ParseCode函数开始,将解析的令牌推送到堆栈或队列中,直到您解析了足够的令牌以唯一地识别您正在解析的语句类型,然后将这些排队的令牌和解析器的当前状态传递给语句类中的适当派生解析器之一。当派生类完成定义语句的令牌解析时,这些令牌将被添加到语句中,然后调用代码将解析的语句添加到父语句集合中。

我已经重写了我的问题,包括您要求的细节和一些额外的编辑以提高清晰度。如果我所描述的是您提出的建议,我有一些关于谁负责将标记解析和跟踪到ast节点的额外问题。请查看新信息! - Jaapjan
看起来跟我想的差不多。我可能不会单独存储位置和列;如果我需要的话,我可能会从另一个和原始文本计算出一个。说实话,我以前没有做过这个,但是我通常能够很好地解决新问题 :)。那么仍然存在哪些问题...谁负责在之前解析令牌,为什么必须改变呢? - BlueMonkMN
完全不是这样——我确实想要跟踪所有内容。空格、注释、语言元素,这些都是标记。一个语句将由空格、注释、行尾和语言标记组成。标记(ast)节点具有长度,因此确定了行和列。至于解析——如果一个语句节点应该能够理解自己的元素,它必须将构成自身的标记堆解析为有效的语句,并牢记可能存在或不存在的可选关键字。 - Jaapjan
我想我很难理解AssignmentStatement和Token这样的对象之间的关系。 AssignmentStatement是不是比token高一级,因此您首先将所有内容解析为标记,然后将它们解析为更高级别的类,但是它们都源于BaseAstNode?而且这些级别之间没有关系吗?您是否考虑过在更高级别之间添加关系,就像刚刚在Tokens和原始源之间添加关系一样? - BlueMonkMN
如果您将语句与令牌链接起来,那么定位语句来源的代码就不会有任何问题。但基于您上述的代码,似乎没有这样的链接。而且根据您的问题,似乎缺少某种识别语句所建立的令牌的方式。您说AST树不包含语句级别之外的任何细节。我的观点是,您需要将其链接到令牌,以便AST树可以包含更多细节。我错过了什么吗?您需要帮助以一种保持链接的方式从令牌生成语句吗? - BlueMonkMN
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接