攻克编译器技术(1)
接上一篇文章
1.2.1 代码
本书是关于制作解释器的,所以这本书包含真实的代码。所需的每一行代码都包括在内,每个代码片段都会告诉您需要将不断增长的实现代码插入哪个位置。
许多其他语言书籍和语言实现使用诸如 Lex 和 Yacc 之类的工具,即所谓的编译器-编译器,它们会根据某些更高级别的(语法)描述自动生成一些源文件。这样的工具各有利弊,而且各方都有强烈的主张——有些人可能会说是信仰。
我们避免在这里使用这些工具。我想确保没有魔法和困惑藏在黑暗角落,所以我们将手写所有代码。正如您将看到的,它并不像听起来那么糟糕,这意味着您将真正理解每一行代码以及两个解释器的工作方式。
一本书与“现实世界”有不同的限制,因此这里的编码风格可能并不是可维护生产软件的最佳方式。我在省略私有变量或声明全局变量方面显得有点无所谓,请理解我这样做是为了让代码更容易阅读。书籍页面没有你的 IDE 那么宽,每个字符的空间都是很珍贵的。
此外,代码没有太多注释。那是因为每一部分代码前都用一些简介的文字进行解释,当你写一本书来配合你的程序时,你也可以省略注释。否则,您可能应该比我多使用更多注释符号(//)。
虽然这本书包含每一行代码实现,并说明每行代码的含义,但它没有描述编译和运行解释器所需的机制。我假设您可以在您选择的 IDE 中将一个 makefile 或一个项目放在一起,以便让代码运行。这些说明很快就会过时,我希望这本书能像 XO 白兰地一样历久弥新,而不是自家酿的酒一样容易过期。
1.2.2 代码片段
这本书实际上包含了实现所需的每一行代码,因此这些代码片段非常准确。 另外,即使缺少主要功能,我也会尝试让程序保持在可运行状态,因此有时我们会添加临时代码,这些代码会在以后的代码片段中被替换。
一个完整的代码片段如下所示:
您可以在其中添加新代码。 它可能在上方或下方有一些淡出的行,用来显示它在相关代码中的位置。如果该简介说“replace x lines”,则在浅色的行之间存在一些现有代码需要删除并替换为新的代码段。
1.2.3 旁白
旁白包含传记简介、历史背景、对相关主题的引用以及其他领域的探索建议。您不需要了解就可以理解本书后面的部分,所以如果你愿意,你可以跳过它们。 我不加评判,但可能会有点难过。
1.2.4 挑战
每章结尾都有一些练习题。 与教科书习题集不同,这些习题集往往会复习您已经学习的材料,这些习题集旨在帮助您学习比本章更加丰富的内容。 他们引导您离开文章引导路径并自行探索。 它们会让你研究其他语言,弄清楚如何实现功能,或者让你走出舒适区。
克服挑战,您将获得更广泛的认知,可能还会遇到一些挫折。 如果您想留在旅游巴士舒适区内,也可以跳过它们。 这是你的书。
1.2.5 设计说明
大多数“编程语言”书籍都是严格意义上的编程语言实现书籍。他们很少讨论如何设计正在实现的语言。实现之所以有趣,是因为它的定义如此精确。程序员似乎更喜欢黑与白、1 和 0 这样的内容。
就个人而言,我认为世界只需要一些 FORTRAN 77 的实现。在某些时候,您会发现自己正在设计一种新语言,一旦开始这样做,表达式中更舒适、更人性化的一面就变得至关重要。诸如哪些功能易于学习,创新和熟悉程度需要取得平衡,哪种语法更易读以及面向哪些用户。
所有这些都对新语言的成功有深远的影响。我希望你的语言能够取得成功,所以在某些章节中,我以“设计笔记”结尾,这是一篇关于编程语言人文某些方面的讨论。我不是这方面的专家,我不知道是否有人真的精通这些内容。所以把这些思考的食材和调料一起烹饪,这应该使它们变得更美味,值得深思,这是我的主要目标。
1.3 第一个解释器
我们将用 Java 编写我们的第一个解释器 jlox。关注的重点是概念。我们将编写最简单、最干净的代码来正确实现语言的语义。这将使我们熟悉基本技术,并磨练我们正确理解语言应表现形式。
Java 是一种很好的语言。它足够高级,我们不会被繁琐的实现细节所淹没,代码仍然非常明确。与脚本语言不同,隐藏在引擎盖下的机器往往不太复杂,并且您可以使用静态类型来查看正在使用的数据结构。
我特别选择了 Java,因为它是一种面向对象的语言。这种编程范式在 90 年代席卷了编程世界,现在是数百万程序员的主要思维方式。很有可能你已经习惯于将代码组织成类和方法,所以我们会让你保持在这个舒适区。
虽然学术性语言专家有时看不起面向对象的语言,但现实中它们广泛用于语言工作中。 GCC 和 LLVM 是用 C++ 编写的,大多数 JavaScript 虚拟机也是如此。面向对象的语言无处不在,一种语言的工具和编译器通常是用同一种语言编写的。
最后,Java 非常受欢迎。这意味着你很有可能已经知道它,所以你要学习的东西就更少了。如果您对 Java 不太熟悉,也不用担心。我尽量只使用它的一个最小子集。我使用 Java 7 中的菱形运算符使代码变得更简洁,但就“高级”功能而言,仅此而已。如果您了解另一种面向对象的语言,例如 C# 或 C++,您可以应对自如。
在第二部分结束时,我们将有一个简单易读的计时器实现。它的运行速度不是很快,但它是正确运行的,因为只能利用在 Java 虚拟机自身运行时基础上构建的实现。我们想了解 Java 本身是如何实现这些东西的。
1.4 第二个解释器
所以在下一部分,我们从头开始,但这次是使用 C 语言。C 是理解实现如何真正工作的完美语言,它深入到内存中的字节和流经 CPU 的代码。
我们使用 C 的一个重要原因是我可以向你展示 C 特别擅长的东西,但这确实意味着你需要对它非常熟悉。你不必是丹尼斯·里奇的转世,但你也不应该被指针吓到。
如果您还没有非常熟悉 C 语言,请找一本关于 C 的介绍性书籍并仔细阅读,然后在完成后返回这里。作为回报,从本书中您将成为一个优秀的 C 程序员。可以想象有多少语言实现是用 C 编写的,例如:Lua、CPython 和 Ruby 的 MRI 等等。
在我们的 C 解释器 clox 中,我们不得不自己实现 Java 免费提供给我们的东西。我们将编写自己的动态数组和哈希表。我们将决定对象在内存中的表示方式,并构建一个垃圾收集器来回收它们。
我们的 Java 实现专注于正确性。既然我们已经做到了,我们将转向提高运行速度。我们的 C 解释器将包含一个编译器,该编译器将 Lox 转换为有效的字节码表示(别担心,我很快就会讲解明白这意味着什么),然后它会执行。这与 Lua、Python、Ruby、PHP 和许多其他成功语言的实现所使用的技术相同。
我们甚至会尝试进行基准测试和优化。 到最后,我们将拥有一个强大、准确、快速的语言解释器,能够跟其他专业水准的实现媲美。 对于一本书和几千行代码来说还不错。
挑战
1. 在我编写和出版这本书中的系统中至少使用了六种特定领域的语言。 是哪些?
2.用 Java 编写和运行一个“hello world!”的程序。 设置您需要的 makefile 或 IDE 项目以使其正常工作。 如果您有调试器,请熟悉它并在程序运行时对代码进行单步调试。
3. 对 C 做同样的操作。为了练习指针,定义一个在堆中分配字符串的双向链表。 编写函数在其中插入、查找和删除项目。 测试编写的函数。
下一章会进入大家喜欢的代码环节了
版权声明: 本文为 InfoQ 作者【刘旭东】的原创文章。
原文链接:【http://xie.infoq.cn/article/75e65db0df77da2605ad9c4f2】。文章转载请联系作者。
评论