我们为什么不存储语法树而不是源代码?


111

我们有很多编程语言。在将每种语言转换为代码之前,都要对每种语言进行解析和语法检查,以便构建抽象语法树(AST)。

我们有这个抽象的语法树,为什么不存储这个语法树而不是源代码(或在源代码旁边)呢?

通过使用AST而不是源代码。团队中的每个程序员都可以将此树序列化为他们想要的任何语言(具有适当的上下文无关语法),并在完成后解析回AST。因此,这将消除有关编码样式问题(在何处放置{和},在何处放置空格,缩进等)的争论。

这种方法的优缺点是什么?


37
Lisp通常写为抽象语法树。它没有赶上更多类似Algol的语言。
David Thornley

2
我不敢相信大卫是唯一提及LISP程序是抽象语法树的人。
WuHoUnited 2011年

3
除了其他几点:AST甚至不是最终的决定。用代码创建AST也不需要很长时间。当我在较小的VS2010项目上运行StyleCop时,它以非常快的速度(有时是一两秒钟)在数千行代码上运行了数十种基于AST的不同规则。扩展StyleCop和编写自定义规则也相当容易。我怀疑将源代码解析为AST是一个很好理解的问题,并且是一个相对容易的问题。它首先提出了良好的语言,并进行了优化和所有困难的而不是解析的库。
工作

1
已经被解析的代码,它是不那么容易产生另一种语言的代码。(如何将Prolog的隐式统一转换为C?)。通常,您拥有的是原始程序的AST。
艾拉·巴克斯特

3
解析问题在技术上已广为人知,但是解析C或C ++并不是一件容易的事,因为它们是杂乱的讨厌的语言。许多编译器将C或C ++解析为AST:Clang,GCC等……它们并不是用于程序存储的,GCC迫切希望成为编译器,而不是程序分析工具。我们的DMS软件再造工具包可解析C和C ++的许多方言,生成AST,符号表和各种流分析工件。这种方法的最大优点是能够构建自动化变更工具。logicaldesigns.com/Products/DMS/DMSToolkit.html
艾拉·巴克斯特

Answers:


72

空格和注释

通常,AST不包括空格,行终止符和注释。

有意义的格式

您是正确的,在大多数情况下,这是肯定的(消除格式化圣战),在许多情况下,原始代码的格式传达了某些含义,例如在多行字符串文字和“代码段”中(空行的语句)。

无法编译的代码

尽管许多解析器可以很好地抵御缺少的语法,但是带有错误的代码通常会导致语法树非常怪异,在用户重新加载文件之前,这种语法树是很好且花哨的。是否曾经在IDE中犯了一个错误,然后突然整个文件都出现了“弯曲”?想象一下如何用另一种语言重新加载。

也许用户不会提交无法解析的代码,但是他们确实确实需要保存在本地。

没有两种语言是完美的搭配

正如其他人指出的那样,几乎没有两种语言具有完美的功能奇偶性。我能想到的最接近的是VB和C#,或者JavaScript和CoffeeScript,但即使到那时,VB仍具有XML文字等功能,这些功能在C#中并不完全等效,并且从JavaScript到CoffeeScript的转换可能会导致很多JavaScript文字。

个人经验:

在我编写的软件应用程序中,实际上我们需要这样做,因为期望用户输入“纯英语”表达式,这些表达式会在后台转换为JS。我们考虑只存储JS版本,但发现几乎没有可接受的方法可以可靠地加载和卸载,因此我们最终总是存储用户文本和JS版本,以及一个标志,指示“ ”版本是否完美解析。


9
有解析器捕获AST中的注释和布局。我们的DMS软件Rengineering工具包可以很好地做到这一点。使用非法代码确实很难。它具有精确的语言解析器。
伊拉·巴克斯特

2
实际上,有一个将Javascript转换为CoffeeScript的工具,因此我认为JavaScript和CoffeScript在没有Javascript文字的情况下可以相互翻译。
彼得·奥尔森

有趣的工具,彼得,我没有意识到。
凯文·麦考密克,

+1提供有意义的格式和有趣的个人体验。-空白对于问题并不重要,可以保留评论。带有错误的代码无论如何都将更易于修复,当然问题的“一种语言可以全部统治”这一部分是无法实现的。
cregox

43

我们为什么不存储此语法树而不是源代码?团队中的每个程序员都可以将此树序列化为他们想要的任何语言,并在完成后解析回AST。

确实,这是一个合理的想法。微软在1990年代一个研究项目几乎可以做到这一点

我想到了几种情况。

首先是微不足道的;如您所说,您可以根据不同程序员对间距等事物的偏好,将AST渲染为不同的视图。但是,在这种情况下,存储AST会显得过大。只是给自己写一个漂亮的打印机。将文件加载到编辑器中时,请运行漂亮打印机将其设置为首选格式,然后在保存时恢复为原始格式。

第二个更有趣。如果您可以存储抽象语法树,则对代码进行差异化更改就不会变成文本,而是语法。重构代码的位置变得更加容易理解。不利的一面当然是,编写树差异算法并不完全是琐碎的事,通常必须根据每种语言来完成。文本差异适用于几乎所有语言。

第三个更像西蒙尼(Simonyi)为有意编程所设想的那样:编程语言共有的基本概念是序列化的,然后您对以不同语言呈现的那些概念有不同的看法。尽管这是一个美丽的主意,但丑陋的事实是语言的细节差异很大,以至于最低分母方法实际上是行不通的。

简而言之,这是一个不错的主意,但这是大量的额外工作,但收益却相对较小。这就是为什么几乎没有人这样做。


3
实际上,您可以以独立于语言的方式进行tree-diff。您确实需要特定于语言的解析器来构建树。请参阅我们的Smart Differencer工具系列,该工具可以比较多种语言的AST。它们都使用相同的基础差异引擎。 logicaldesigns.com/Products/SmartDifferencer
Ira Baxter,

1
我希望有一天能在Visual Studio中看到我的风格漂亮的加载时保存团队风格的漂亮打印...希望已经很多年了...还没有运气...
罗马斯塔科夫2012年

19

您可能会争辩说这正是.NET中的字节码。Infact redgate的反射器程序确实将字节代码转换回一系列.NET编程语言。

但是,有问题。语法是特定于语言的,因为您可以用一种语言表示的事物可以用其他语言不表示。这是在.NET中发生的,其中C ++是唯一可以访问所有7个访问级别的.NET语言。

在.NET环境之外,它变得更加棘手。每种语言都将开始拥有自己的一组关联库。在C和Java中都不可能反映出通用的语法,因为它们以完全不同的方式解决模拟问题时,它们反映了相同的指令执行。


5
是否曾尝试反编译F#生成的MSIL?
SK-logic

12

我有点喜欢您的一些想法,但是您大大高估了将语言翻译成语言的难易程度。如果那样简单,您甚至不需要存储AST,因为您总是可以将语言X解析为AST,然后从AST转换为语言Y。

但是,我希望编译器规范对通过某种API公开一些AST进行更多的思考。诸如面向方面的编程,重构和静态程序分析之类的事情可以通过这样的API来实现,而那些功能的实现者不必重做编译器编写者已经完成的大量工作。

奇怪的是,程序员表示一个程序的数据结构经常是一堆包含字符串的文件。


5
您是否一直在关注Microsoft的“ Roslyn ”项目的开发,以将VBc和C#编译器作为API开放?有一个预览版本。
Carson63000

11

我认为最突出的要点是:

  • 没有好处。您说过,这意味着每个人都可以使用他们的宠物语言。但是,事实并非如此 -使用语法树表示只会消除语法差异,而不会消除语义差异。它在某种程度上适用于非常相似的语言,例如VB和C#或Java和Scala。但是,甚至没有完全。

  • 这是有问题的。您已经获得了语言自由,但是却失去了工具自由。您不再可以在文本编辑器甚至任何IDE中读取和编辑代码,而只能依靠能说AST表示的特定工具来读取和编辑代码。这里什么都没有得到。

    为了说明最后一点,请看一下RealBasic,它是功能强大的BASIC方言的专有实现。有一时间,它似乎看起来像是一种语言,但它完全取决于供应商,以至于您只能在他们的IDE中查看代码,因为它是以专有的非文本格式保存的。


4
潜在的好处是,它可以结束无休止的争论,例如“制表符与空格”,“ unix与Windows括号/缩进”,“ m_前缀是否在成员前面”,因为它们可以变成简单的IDE选项。
nikie 2011年

1
@nikie True,但是您已经可以使用重新格式化工具(例如astyle或UnniversalIndent)来执行此操作。不需要神秘的二进制格式。
康拉德·鲁道夫

2
真正的好处是拥有差异/补丁工具的潜力,这些工具可以使您更好地了解真正的变化。但这似乎意味着需要一个全新的工具链来进行版本控制,这是一个严重的限制。
彼得·泰勒

1
如果您认为“没有好处”,那么您就没有看到Intentional Software的Domain Workbench。
Craig Stuntz 2011年

1
简而言之,可以将相同的逻辑投影到不同的表示形式中,而不是全部基于文本,从而使非程序员可以访问规则。例如,像精算师这样的领域专家可以编写保险申请书中的精算部分。类似于DSL,但不限于该表示形式。但是,这与问题非常相关。有一个很好的演示
Craig Stuntz

6

我认为,如果同时存储文本和AST,那么您实际上并没有添加任何有用的东西,因为文本已经以一种语言存在,并且AST可以从文本中快速重建。

另一方面,如果仅存储AST,则会丢失无法恢复的注释之类的内容。


6
并且是否使注释成为语法树的一部分(注释节点可以是任何子项)?
棘轮怪胎

我们的工具正是这样做的。在此主题中查看我的其他评论。
伊拉·巴克斯特

4

我认为这个想法在理论上很有趣,但不是很实用,因为不同的编程语言支持不同的构造,其中一些在其他语言中没有等效的构造。

例如,X ++有一个'while select'语句,如果没有很多额外的代码(额外的类,额外的逻辑等),就无法用C#编写该语句。http://msdn.microsoft.com/en-us/library/aa558063.aspx

我在这里要说的是,许多语言都有语法糖,它们可以翻译成相同语言的大块代码,甚至翻译成其他语言根本不存在的元素。这是为什么AST方法不起作用的示例:

语言X的关键字K在AST中用以下4种语句翻译:S1,S2,S3和S4。AST现在被翻译成语言Y,并且程序员更改了S2。现在,转换回X会发生什么?该代码被翻译为4条语句,而不是单个关键字。

反对AST方法的最后一个争论是平台功能:将功能嵌入平台后会发生什么?就像.NET的Environment.GetEnvironmentVariable一样。您如何翻译?


4

有一个围绕这个想法构建的系统:JetBrains MPS。编辑器有点奇怪,或者只是有所不同,但是总的来说,这不是一个大问题。最大的问题是,好了,这是不是一个文本的多,所以你不能使用任何正常的基于文本的工具-其他编辑,grepsed,合并和差异工具,等等。


2
...但是您确实可以使用许多编辑器功能。考虑稍微扩展这个答案,这是一项非常有趣的技术,值得更多地详细说明不将源代码存储为文本的优点。例如,当我在制表符与空格上回答此问题时
史蒂文·杰里斯

AST可以以人类可读的格式保存,而不是以二进制格式保存。您现在可以使用linux工具替换例如以参数可序列化对象作为参数的代码中的每个方法吗?很难写,但是AST使它非常容易。
IAdapter

1
人们不断犯这个错误。与仅使用原始文本相比,AST使其更容易。但是对于任何有趣的事情,您需要大量的附加信息:控制和数据流,符号表,范围分析等。AST可以帮助您,但仅是真正需要的一小部分。
艾拉·巴克斯特

@Ira Baxter,当然,使用AST更容易。但是,将其集成到现有 基础架构中要困难得多。
SK-logic

4

实际上,有几种产品(通常称为“语言工作台”)存储AST,并在其编辑器中将AST的“投影”呈现回特定的语言。正如@ sk-logic所说,JetBrains的MPS就是这样一种系统。另一个是故意软件的故意工作台。

语言工作台的潜力似乎很高,尤其是在特定领域的语言领域,因为您可以创建特定领域的投影。例如,Intental演示了与电力相关的DSL,它以电路图的形式展示-与基于文本的编程语言描述的电路相比,对于领域专家来说,讨论和批评它更容易,更准确。

在实践中,语言工作台一直很慢,因为除DSL工作外,开发人员可能更喜欢使用熟悉的通用编程语言工作。当与文本编辑器或编程IDE进行面对面的比较时,语言工作台会产生大量的开销,并且它们的优势还不太明显。我所见过的语言工作台都没有引导到可以轻松扩展自己的IDE的地步,也就是说,如果语言工作台对提高生产力非常有用,为什么语言工作台工具却没有变得更好越来越好?


“语言工作台”不必一定基于存储原始AST。它们也可以是面向纯文本语法的,例如,参见meta-alternative.net/pfront.pdf(这实际上扩展了Visual Studio和Emacs编辑器,并在其之上实现了任何eDSL)。
SK-logic

那是一篇有趣的论文。它使我想起了(雄心勃勃,根本没有实现)一个名为SugarJ的工具,该工具是在几周前在SPLASH / OOPSLA上展示的:uni-marburg.de/fb12/ps/research/sugarj
Larry OBrien

有趣的是,我也会尝试一下。
SK-logic

3

你一直在读我的想法。

几年前,当我参加编译器课程时,我发现如果您使用前缀表示法而不是通常的中缀表示法对AST进行序列化并使用括号定界整个语句,则会得到Lisp。虽然我在本科学习中了解了Scheme(一种Lisp的方言),但我从未真正获得过赞赏。通过该课程,我肯定对Lisp及其方言表示赞赏。

您提出的问题:

  1. 在图形环境中组成AST很难/很慢。毕竟,我们大多数人的打字速度都比移动鼠标快。然而,一个新出现的问题是“如何用平板电脑编写程序代码?” 与带有硬件键盘的键盘/笔记本电脑相比,在平板电脑上打字速度慢/麻烦。如果您可以通过将组件从调色板拖放到大型画布上来创建AST,那么在平板电脑上进行触摸屏设备编程就可以成为现实。

  2. 我们现有的工具中很少/没有支持此功能的。在开发日益复杂的IDE和日益智能的编辑器方面,我们数十年的发展历程不断。我们拥有所有用于重新格式化文本,比较文本,搜索文本的工具。可以在树上进行与正则表达式等效的工具在哪里?还是两棵树的区别?所有这些事情都可以通过文本轻松完成。但是他们只能比较单词。更改变量名,以使单词不同但语义相同,并且这些diff工具会遇到麻烦。开发用于在AST而不是文本上运行的此类工具,将使您更接近比较语义。那将是一件好事。

  3. 虽然将程序源代码转换为AST相对容易理解(我们有编译器和解释器,对吗?),但对AST转换为程序代码的理解却不那么了解。将两个质数相乘得到一个大的复合数相对简单,但是将一个大的复合数分解为质数则要困难得多。那就是我们解析与反编译AST的地方。这就是语言之间的差异成为问题的地方。即使在特定语言中,也有多种方法可以反编译AST。例如,遍历对象的集合并获得某种结果。使用for循环,遍历数组?那将是紧凑且快速的,但是存在局限性。使用某种迭代器 在集合上操作?该Collection可以是可变大小的,从而增加了灵活性(但有可能牺牲速度)。映射/缩小?更复杂,但隐式可并行化。这仅适用于Java,具体取决于您的偏好。

随着时间的流逝,开发工作将花费更多,我们将使用触摸屏和AST进行开发。键入将变得不必要。我认为这是从现在到现在的逻辑发展,看看我们如何使用计算机,这将解决第一。

我们已经在与树木合作。Lisp仅仅是序列化的AST。XML(和扩展的HTML)只是一个序列化的树。为了进行搜索,我们已经有几个原型:XPath和CSS(分别用于XML和HTML)。当创建允许我们创建CSS样式的选择器和修饰符的图形工具时,我们将解决部分#2。当这些选择器可以扩展为支持正则表达式时,我们将更加接近。仍在寻找比较两个XML或HTML文档的良好图形差异工具。随着人们开发这些工具,第二个问题将得以解决。人们已经在做这类事情了;他们只是不在那里。

我认为能够将这些AST反编译为编程语言文本的唯一方法是寻求目标。如果我要修改现有代码,则可以通过一种算法来实现该目标,该算法可使修改后的代码尽可能类似于起始代码(最小文本差异)。如果我是从头开始编写代码,则目标可能是最小,最紧密的代码(可能是for循环)。或者它可能是尽可能高效并行化的代码(可能是映射/归约或涉及CSP的某种东西)。因此,基于目标的设置方式,即使使用相同的语言,相同的AST也会导致代码差异很大。开发这样的系统将解决#3。这会导致计算复杂,这意味着我们可能需要某种客户端-服务器的安排,


1

如果您的目的是消除关于格式样式的争论,那么您可能想要的是一个编辑器,该编辑器读取源文件,将其格式化为您个人喜好的显示和编辑方式,但是保存时,将格式重新设置为团队选择的样式用途。

如果使用像Emacs这样的编辑器,这非常容易。更改整个文件的格式样式是一项三命令工作。

您还应该能够构建挂钩,以在加载时自动将文件转换为您自己的样式,并在保存时将其转换为团队样式。


1
然后,您仍然需要语义差异和合并(即,再次是AST级)。
SK-logic

不,编辑器会重新格式化为团队样式以存储源-因此,您需要将一种源类型与同一类型进行比较。
古斯塔夫·贝特拉姆

好的一点是,单一的规范化表示形式可以解决所有问题
SK-logic

1
不,它仅解决了区分两个文件以进行标识的问题。如果要查看文件之间的差异,则理想情况下需要了解结构的内容。我爱我的emacs,但它不了解结构。
艾拉·巴克斯特

Emacs很棒,但是我从不使用它来区分。为了在签入之前比较我的源代码树,我总是使用meld。它实际上了解SVN和git。在Windows上,我可能将WinMerge与乌龟结合使用。
Gustav Bertram

1

很难读取和修改AST,而不是源代码。

但是,某些与编译器相关的工具的确允许使用AST。Java字节码和.NET中间代码的工作方式与AST类似。


1
用机械工具可靠地修改AST比用文本可靠地修改。您可以通过模式定向的更改来执行此操作。参见语义设计
.com /产品/DMS/ProgramTransformation.html

2
现在告诉LISPers ...
hugomg

@艾拉·巴克斯特 我知道,Im实际上是在直接与AST一起使用的自定义视觉工具上工作,但是,有时开发人员必须使用文本而不是视觉。某些AST也以较短的编程语言表示为文本。
umlcat 2011年

@umlcat,您能告诉我更多有关AST视觉工具的工作吗?
Daniel Albuschat 2013年

@Daniel Albuschat我正在研究一个宠物编程语言项目。解析器难以实现,因此暂时跳过它,并制作了一个工具,用于显示AST(带有树视图控件的表单)并直接添加表达式。并且可以做相反的事情,从AST生成代码。
umlcat

0

这是一个好主意;但是每种语言的AST彼此都不相同。

我知道的唯一例外是VB.NET和C#,Microsoft认为它们是“使用不同语法的完全相同的语言”。甚至其他.NET语言(IronPython,F#等)在AST级别上也有所不同。

与JVM语言相同,它们都以相同的字节码为目标,但是语言结构不同,从而使其具有不同的语言和不同的AST。

甚至像CoffeScript和Xtend这样的“薄层”语言也共享了很多基础语言的理论(分别是JavaScript和Java)。但要介绍(或应该)保留在AST级别的更高级别的概念。

如果Xtend可以从Java AST重构,我认为它将被定义为Java到Xtend的“反编译器”,可以从现有Java代码神奇地创建更高级别的抽象,您是不是认为呢?


1
作为熟悉C#和VB编译器的人,我可以告诉您,它们当然很相似,但是有很多重要的细节完全不同,以至于将它们视为“具有不同语法的相同语言”是不切实际的。我们考虑过为罗斯林项目这样做;建立一个可以用相同的功能编译两种语言的编译器-经过大量辩论后,决定使用两种编译器来处理两种语言。
埃里克·利珀特

@EricLippert:太可惜了。并不是说我曾经计划学习任何一种语言,但这听起来确实是个不错的例外。我认为htat留下了像lisp一样的Dylan和algol一样的Dylan作为唯一的“具有不同语法的相同语言”的示例。
哈维尔
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.