Roslyn SyntaxNodes是否可以重用?


124

我一直在研究Roslyn CTP,尽管它解决了与Expression Tree API类似的问题,但两者都是不可变的,但Roslyn却以完全不同的方式做到了:

  • Expression节点没有对父节点的引用,而是使用进行了修改ExpressionVisitor,因此可以重复使用大部分部件。

  • SyntaxNode另一方面,罗斯林(Roslyn's )对其父节点进行了引用,因此所有节点实际上都成为了无法重用的块。类似的方法UpdateReplaceNode等等,提供了进行修改。

这在哪里结束?DocumentProjectISolution?API促进了树的逐步更改(而不是向上按钮),但是是否每个步骤都进行了完整复制?

他们为什么做出这样的选择?我缺少一些有趣的把戏吗?

Answers:


181

更新:这个问题是我在2012年6月8日发布的博客的主题。感谢您提出的好问题!


好问题。我们对您提出的问题进行了很长时间的辩论。

我们希望有一个具有以下特征的数据结构:

  • 一成不变。
  • 一棵树的形式。
  • 从子节点便宜地访问父节点。
  • 可以从树中的节点映射到文本中的字符偏移量。
  • 坚持不懈

通过持续我的意思是能力再使用大多数在树中的现有节点时,编辑是对文本的缓冲区的。由于节点是不可变的,因此重用它们没有障碍。我们需要这样做来提高性能;每次您按下键时,我们都无法重新解析文件中的大量杂物。我们只需要重新编制和重新分析仅受编辑影响的树的部分。

现在,当您尝试将所有这五种内容放到一个数据结构中时,立即会遇到问题:

  • 首先如何构建节点?父母和孩子都互相指称,并且是不可变的,那么哪个首先被构建?
  • 假设您设法解决了这个问题:如何使其持久化?您不能在其他父节点中重复使用子节点,因为这将告诉子节点其具有新的父节点。但是孩子是一成不变的。
  • 假设您设法解决了这个问题:当在编辑缓冲区中插入新字符时,映射到该点之后某个位置每个节点的绝对位置都会发生变化。这使得很难建立持久的数据结构,因为任何编辑都可以更改大多数节点的跨度!

但是在罗斯林团队中,我们通常会做不可能的事情。实际上,我们通过保留两棵解析树来完成不可能的事情。“绿色”树是不可变的,持久的,没有父级引用,是“自下而上”构建的,每个节点都跟踪其宽度,但不跟踪其绝对位置。进行编辑时,我们仅重建受该编辑影响的绿树部分,通常大约是树中所有解析节点的O(log n)。

“红色”树是围绕绿色树构建的不可变外观。它是按需 “自上而下”构建的,每次编辑都被丢弃。当您从顶部向下穿过树时,它通过按需制造父引用来计算它们。当您下降时,它通过从宽度计算绝对位置来制造绝对位置。

用户(您)只能看到红色的树;绿树是一个实现细节。如果您查看解析节点的内部状态,您实际上会发现其中存在对另一个解析节点的不同类型的引用。那是绿树节点。

顺便提一下,这些被称为“红色/绿色树”,因为它们是我们在设计会议中用来绘制数据结构的白板笔颜色。颜色没有其他含义。

这种策略的好处是我们获得了所有这些伟大的东西:不变性,持久性,父引用等等。代价是该系统很复杂,如果“红色”外观变大,则会消耗大量内存。目前,我们正在做实验,看看是否可以减少一些成本而不损失收益。


3
为了解决您有关IProjects和IDocuments的问题,我们在服务层使用了类似的模型。在内部,在道德上等效于语法树的绿色节点的“ DocumentState”和“ ProjectState”类型。您获得的IProject / IDocument对象是这些对象的红色节点外观。如果您在反编译器中查看Roslyn.Services.Project的实现,您将看到几乎所有调用都转发给内部状态对象。
杰森·马林诺夫斯基

@Eric对此表示抱歉,但您自相矛盾。The expense and difficulty of building a complex persistent data structure doesn't pay for itself.ref:stackoverflow.com/questions/6742923/…如果您有高性能的目标,为什么首先要使其不可变?除了显而易见的原因之外,还有其他原因吗?例如,容易使线程安全的,道理约等
卢卡斯玛顿

2
@lukas您在上下文中引用了该报价。前面的句子是“因为当您查看通常在.NET程序中对字符串进行的操作时,简单地创建一个全新的字符串在所有相关方面都几乎不会变坏。” OTOH,当您查看通常在表达式树上执行的操作(例如,在源文件中键入几个字符)时,构建全新的表达式树会变得更加糟糕。因此,它们只构建一半。
Timbo 2012年

1
@lukas我的猜测:鉴于Roslyn应该在后台线程上运行,不变性允许多个线程同时分析同一源代码,而不必担心用户按下键时它将被更改。响应用户输入,可以更新不可变的树,而无需停止正在运行的分析任务。因此,我认为不变性的主要目标是使Roslyn易于编写(对于客户来说可能更容易使用)。
Qwertie 2012年

3
@lukas当数据结构通常比对它的更改大得多时,持久数据结构比复制更有效。你的观点,如果有的话,就落在我身上。
Qwertie 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.