c# - 使用抽象语法树生成曲线

标签 c# .net parsing abstract-syntax-tree

情况:

我为一种很少使用的编程语言制作了一个扫描器、解析器和各种 AST 类,这是我的一个爱好项目。解析器在扫描器的帮助下构建了一个异构 AST,我对其进行了一些操作。过去,我为某些 IDE 创建了一个插件/加载项,用于语法高亮显示和其他一些元素。

问题出在错误上:解析器生成一些并可以访问构成语句的标记。然而,有些错误只会在以后出现,例如无法解析标识符。我想在此类标识符或其他错误标记下显示波浪线。不仅如此,我还喜欢在不丢失原始文档中的所有注释、空格等的情况下操纵 AST 节点的能力。

在我的 AST 中创建新语句时,我可以轻松地将构成此语句的标记添加为子语句。但是……

问题:

如果合理可行,我希望包括对显示波浪线的支持。这需要一个语句来了解码成该语句的标记的位置。不幸的是,语句有变体,有时包含更多 token ,有时包含更少 token 。如果我正在制作只读 AST,这将不是问题。但是,为了重构目的,我希望我的 AST 是可读写的!这意味着更改 AST 中的语句本质上意味着添加标记(语句的子项),因此语句类应该能够重新解析自身。

这会用解析代码污染 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(){}
}

其他人如何解决这个问题?这是正确的方法吗?

最佳答案

如果您可以获得发生错误的语句的代码,您应该能够重新解析单个语句,这次跟踪标记的位置并注意那些无效的(因为您没有第一次保存的 token 的位置)。至少您不必将整个文档都解析一遍,只需一行...也许可以重新使用您已有的一些解析代码。

或者,存储每个标记的位置(文档中的开始和长度),这样当您需要引用其来源时,您可以轻松访问该标记的位置。为什么不这样做呢?

一些可能的优化,如果你可以从 token 对象重新生成 token 文本,将不存储 token 长度,或者只是在行内搜索该文本,假设你想对每个做同样的事情该标记出现在该行(或所有行)上。

编辑:既然您已将标记链接到从中解析它们的原始源代码,您需要将语句链接到从中解析它们的标记。有两种方法可以做到这一点:

  1. 将 List 成员添加到 BaseAstNode。或者
  2. 将特定的 token 成员添加到每个派生语句类,以便您准确知道哪个 token 链接到语句的哪个部分。

现在您拥有一条从语句到源代码的链,因此您可以识别与每条语句的每个部分关联的源代码。

编辑 2: 如果您试图弄清楚实际解析是如何将标记处理为 BaseAstNode 派生对象的,我还没有考虑过这个问题(不是很清楚这是问题的一部分)。由于我以前没有这样做过,所以我的建议在第一次尝试时可能不是最好的。但希望它可以根据需要进行改进。我首先想到的是 System.Xml 命名空间,它也实现了一个解析器。也许您的某些设计可以仿效 XML 解析器和相关类。 XmlNode 可能类似于您的 BaesAstNode 类。 XmlElement 可能类似于您的 Token 类。 XmlDocument 是 XmlNode 的一种特殊化,它在一个 LoadXml 函数中处理所有解析,该函数用子对象填充对象。

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

关于c# - 使用抽象语法树生成曲线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5908726/

相关文章:

c# - 如何在 C# 中获取正确的时间戳

c# - System.dll 中缺少“System.InvalidOperationException”,类别名称丢失

c# - 桌面应用程序的应用程序内购买

asp.net - Thread.CurrentPrincipal 未在使用 WebGet 调用的 WCF 服务中设置

parsing - 递归下降解析: high precedence unary operators

c# - TableAdapter 向导没有来自选择列表中设置的连接字符串

c# - C#接收和发送数据

C# - 是否可以使用新接口(interface)扩展现有的内置类

parsing - 编写用于 syslog-ng 的自定义模板/解析器/过滤器

php - 用php替换文件中的字符串