提交 9142d633 编写于 作者: W wizardforcel

2023-03-30 16:48:47

上级 00d5ff65
......@@ -8,7 +8,7 @@
尽管计算机很强大,但它是一个苛刻的监工。它的程序必须是正确的,我们想说的话必须在每个细节上都准确无误。就像在每一个其他的象征性活动中一样,我们通过争论确信程序的真实性。Lisp 本身可以被赋予一个语义(顺便说一下,另一个模型),如果一个程序的功能可以被指定,比方说,在谓词演算中,逻辑的证明方法可以被用来进行一个可接受的正确性论证。不幸的是,随着程序变得越来越大和复杂,就像他们经常做的那样,规范本身的充分性、一致性和正确性变得值得怀疑,因此完整的正确性的形式论证很少伴随着大型程序。因为大程序是从小程序发展而来的,所以我们开发一个标准程序结构的武器库是至关重要的,我们已经确定了这些标准程序结构的正确性——我们称之为习惯用法——并学会使用有价值的组织技术将它们组合成更大的结构。这些技术将在本书中详细讨论,理解它们对于参与名为编程的创新事业是必不可少的。最重要的是,强大的组织技术的发现和掌握加速了我们创建大型重要项目的能力。相反,由于编写大型程序非常费力,我们被刺激去发明新的方法来减少大量的功能和细节以适应大型程序。
与程序不同,计算机必须遵守物理定律。如果它们希望快速运行——每个状态变化几纳秒——它们必须只将电子传输很短的距离(最多![f3-fig-5001.jpg](../images/f3-fig-5001.jpg)英尺)。空间中如此集中的大量设备所产生的热量必须被移除。一门精致的工程艺术在功能多样性和设备密度之间取得了平衡。无论如何,硬件总是在比我们关心的编程水平更原始的水平上运行。将我们的 Lisp 程序转换成“机器”程序的过程本身就是我们编程的抽象模型。他们的研究和创造给了与编程任意模型相关的组织程序大量的洞察力。当然,计算机本身也可以这样建模。想想看:最小的物理开关元件的行为是通过微分方程描述的量子力学来模拟的,微分方程的详细行为是通过在计算机上执行的计算机程序中表示的数值近似来捕捉的,计算机程序由以下组成。。。!
与程序不同,计算机必须遵守物理定律。如果它们希望快速运行——每个状态变化几纳秒——它们必须只将电子传输很短的距离(最多 1.5 英尺)。空间中如此集中的大量设备所产生的热量必须被移除。一门精致的工程艺术在功能多样性和设备密度之间取得了平衡。无论如何,硬件总是在比我们关心的编程水平更原始的水平上运行。将我们的 Lisp 程序转换成“机器”程序的过程本身就是我们编程的抽象模型。他们的研究和创造给了与编程任意模型相关的组织程序大量的洞察力。当然,计算机本身也可以这样建模。想想看:最小的物理开关元件的行为是通过微分方程描述的量子力学来模拟的,微分方程的详细行为是通过在计算机上执行的计算机程序中表示的数值近似来捕捉的,计算机程序由以下组成。。。!
分开确定这三个焦点不仅仅是战术上的方便。尽管,正如他们所说的,这都是头脑中的,这种逻辑上的分离导致了这些焦点之间符号交通的加速,这些焦点的丰富性、活力和潜力在人类经验中只有生命本身的进化才能超越。在最好的情况下,焦点之间的关系是亚稳态的。计算机总是不够大或不够快。硬件技术的每一次突破都导致更大规模的编程企业、新的组织原则和抽象模型的丰富。每个读者都应该定期问自己“向什么目标前进,向什么目标前进?”—但是不要问得太频繁,以免你因为苦乐参半的哲学的便秘而错过编程的乐趣。
......
......@@ -6,7 +6,7 @@
> 有没有可能软件不像其他任何东西,它注定要被抛弃:它的全部意义在于总是把它看成一个肥皂泡?
>
> —艾伦·J·佩利
> —艾伦·J·佩利
自 1980 年以来,本书中的材料一直是麻省理工学院入门级计算机科学学科的基础。当第一版出版时,我们已经教了四年这种材料,直到第二版的出现,又过了十二年。我们感到高兴的是,我们的工作已被广泛采纳并纳入其他案文。我们已经看到我们的学生接受了这本书里的思想和程序,并把它们作为新的计算机系统和语言的核心。从字面上理解一句古老的犹太法典双关语,我们的学生已经成为我们的建设者。我们很幸运有这样有能力的学生和这样有成就的建设者。
......@@ -24,7 +24,7 @@
> 电脑就像小提琴。你可以想象一个新手先试留声机,再试小提琴。他说,后者听起来很可怕。这是我们从人文主义者和大多数计算机科学家那里听到的观点。他们说,对于特定的目的来说,计算机程序是好的,但是它们不灵活。小提琴和打字机都不是,除非你学会如何使用它们。
>
> —马文·明斯基,“为什么编程是表达理解不佳、表述不严谨的想法的好媒介”
> —马文·明斯基,“为什么编程是表达理解不佳、表述不严谨的想法的好媒介”
“计算机程序的结构和解释”是麻省理工学院计算机科学的入门课程。这是麻省理工学院所有电子工程或计算机科学专业学生的必修课,占“公共核心课程”的四分之一,其中还包括两个关于电路和线性系统的科目以及一个关于数字系统设计的科目。自 1978 年以来,我们一直参与这一学科的发展,自 1980 年秋季以来,我们每年向 600 至 700 名学生教授这一材料的现有形式。这些学生中的大部分以前很少或没有接受过正式的计算培训,尽管许多人玩过一点计算机,少数人有丰富的编程或硬件设计经验。
......@@ -40,4 +40,4 @@
Scheme 是我们使用的 Lisp 方言,它试图将 Lisp 和 Algol 的强大和优雅结合在一起。从 Lisp 中,我们获得了源自简单语法的元语言能力,程序作为数据对象的统一表示,以及垃圾收集堆分配的数据。从 Algol 中,我们得到了词法范围和块结构,这是 Algol 委员会中编程语言设计先驱们的礼物。我们希望引用约翰·雷诺兹和彼得·兰丁对 Church 的 lambda 演算与编程语言结构之间关系的见解。我们也认识到我们对数学家的亏欠,他们在计算机出现的几十年前就探索了这个领域。这些先驱包括阿隆佐·邱奇、巴克利·罗瑟、斯蒂芬·克莱尼和哈斯克尔·库里。
—哈罗德·艾贝尔森和杰拉德·让伊·萨斯曼
\ No newline at end of file
——哈罗德·艾贝尔森和杰拉德·让伊·萨斯曼
\ No newline at end of file
......@@ -4,7 +4,7 @@
《计算机程序的结构和解释 (SICP JS)的 JavaScript 改编是在新加坡国立大学(NUS)为课程 CS1101S 开发的。该课程由 Low Kok Lim 共同教授了六年,他良好的教学判断对该课程和该项目的成功至关重要。CS1101S 教学团队包括许多新加坡国立大学的同事和 300 多名本科生助教。他们在过去九年里不断的反馈让我们解决了无数特定于 JavaScript 的问题,消除了不必要的复杂性,同时保留了 SICP 和 JavaScript 的基本特性。
SICP JS 是一个软件项目,也是一个图书项目。我们在 2008 年从原作者处获得了![f6-fig-5001.jpg](../images/f6-fig-5001.jpg)图书来源。早期的 SICP JS 工具链是由刘航开发,冯飘飘完善的。陈·何岸开发子印刷版本的第一批工具,乔林·谭在此基础上开发子第一版电子书的工具,心悦和王千将这些工具重新用于当前的比较版。Samuel Fang 设计并开发了 SICP JS 的网络版。
SICP JS 是一个软件项目,也是一个图书项目。我们在 2008 年从原作者处获得了![f6-fig-5001.jpg](img/f6-fig-5001.jpg)图书来源。早期的 SICP JS 工具链是由刘航开发,冯飘飘完善的。陈·何岸开发子印刷版本的第一批工具,乔林·谭在此基础上开发子第一版电子书的工具,心悦和王千将这些工具重新用于当前的比较版。Samuel Fang 设计并开发了 SICP JS 的网络版。
SICP JS 和 CS1101S 的网络版很大程度上依赖于一个叫做源码学院的软件系统,它支持的 JavaScript 子语言叫做源码。在 SICP JS 的准备过程中,许多学生对源学院做出了贡献,系统将他们突出地列为“贡献者”自 2020 年以来,新加坡国立大学课程 CS4215“编程语言实现”的学生贡献了几个在 SICP JS 中使用的编程语言实现:3.4 节中使用的源代码的并发版本由郑群·库和乔纳森·陈开发;第 4.2 节中使用的惰性实现是由杰卢利·艾哈迈德、伊恩·肯德尔·邓肯、克鲁兹·乔马里·埃万格利斯塔和奥尔登·谭开发的;第 4.3 节中使用的不确定性实现是由阿尔萨兰·吉玛和阿努巴夫开发的;Daryl Tan 帮助将这些实现整合到学院中。
......@@ -44,7 +44,7 @@ Al Moyé安排我们将这些材料教授给惠普公司的工程师,并制作
丹·弗里德曼(Dan Friedman)是该计划社区的长期领导者。该社区更广泛的工作超越了语言设计问题,包括重大的教育创新,如基于 Schemer's Inc .的 EdScheme 的高中课程,以及 Mike Eisenberg、Brian Harvey 和 Matthew Wright 的精彩书籍。
我们感谢那些为使这本书成为一本真正的书做出贡献的人,特别是特里·艾林、拉里·科恩和麻省理工学院出版社的保罗·贝奇。艾拉·马泽尔找到了精彩的封面图片。对于第二版,我们特别感谢伯纳德和艾拉·马泽尔对书籍设计的帮助,以及非凡的巫师大卫·琼斯![f6-fig-5002.jpg](../images/f6-fig-5002.jpg)。我们也非常感谢那些对新草稿发表深刻评论的读者:雅各布·卡泽尼尔森、哈迪·梅尔、吉姆·米勒,特别是布莱恩·哈维,他对这本书的贡献就像朱莉对他的书《简单计划》的贡献一样。
我们感谢那些为使这本书成为一本真正的书做出贡献的人,特别是特里·艾林、拉里·科恩和麻省理工学院出版社的保罗·贝奇。艾拉·马泽尔找到了精彩的封面图片。对于第二版,我们特别感谢伯纳德和艾拉·马泽尔对书籍设计的帮助,以及非凡的巫师大卫·琼斯![f6-fig-5002.jpg](img/f6-fig-5002.jpg)。我们也非常感谢那些对新草稿发表深刻评论的读者:雅各布·卡泽尼尔森、哈迪·梅尔、吉姆·米勒,特别是布莱恩·哈维,他对这本书的贡献就像朱莉对他的书《简单计划》的贡献一样。
最后,我们要感谢多年来鼓励这项工作的各组织的支持,包括由 Ira Goldstein 和 Joel Birnbaum 促成的惠普公司的支持,以及由鲍勃·卡恩促成的 DARPA 的支持。
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -40,7 +40,7 @@
这两个规则描述了评估过程的本质,一个基本循环,在这个循环中,要在环境中评估的语句和表达式被简化为要应用于参数的函数,这些函数又被简化为要在新环境中评估的新语句和表达式,以此类推,直到我们深入到名称(其值在环境中被查找)以及直接应用的运算符和原始函数(参见[图 4.1](#c4-fig-0001) )。 [](#c4-fn-0004) 这个评估周期将通过评估器中两个关键函数`evaluate``apply`的相互作用来体现,这在 4.1.1 节中有描述(参见[图 4.1](#c4-fig-0001) )。
![c4-fig-0001.jpg](../images/c4-fig-0001.jpg)
![c4-fig-0001.jpg](img/c4-fig-0001.jpg)
[图 4.1](#c4-fig-0001a)`evaluate``apply`循环暴露了一种计算机语言的本质。
......@@ -282,7 +282,7 @@ list("sequence",
[图 4.2](#c4-fig-0002) 描绘了由语法谓词和选择器形成的抽象屏障,它们将评估器与程序的标记列表表示连接起来,而标记列表表示又通过`parse`与字符串表示分离。下面我们描述程序组件的解析,并列出相应的语法谓词和选择器,以及构造函数(如果需要的话)。
![c4-fig-0002.jpg](../images/c4-fig-0002.jpg)
![c4-fig-0002.jpg](img/c4-fig-0002.jpg)
[图 4.2](#c4-fig-0002a) 评估器中的语法抽象。
......@@ -1108,13 +1108,13 @@ function factorial(n) {
我们可以把这个程序看作是对一台机器的描述,这台机器包含递减、相乘和相等测试的部件,还有一个两位开关和另一台阶乘机器。(阶乘机器是无限的,因为它包含另一个阶乘机器。)[图 4.3](#c4-fig-0003) 是阶乘机器的流程图,显示了各部分是如何连接在一起的。
![c4-fig-0003.jpg](../images/c4-fig-0003.jpg)
![c4-fig-0003.jpg](img/c4-fig-0003.jpg)
[图 4.3](#c4-fig-0003a) 阶乘程序,被视为抽象机器。
同样,我们可以把评价者看作一台非常特殊的机器,它接受对机器的描述作为输入。给定该输入,评估器对自身进行配置以仿真所描述的机器。例如,如果我们将`factorial`的定义提供给评估者,如图[图 4.4](#c4-fig-0004) 所示,评估者将能够计算阶乘。
![c4-fig-0004.jpg](../images/c4-fig-0004.jpg)
![c4-fig-0004.jpg](img/c4-fig-0004.jpg)
[图 4.4](#c4-fig-0004a) 仿真阶乘机的评估器。
......@@ -3704,7 +3704,7 @@ job($x, list("computer", "programmer"))
在我们的系统中,一个查询获取一个输入帧流,并对该流中的每一帧执行上述匹配操作,如图[图 4.5](#c4-fig-0005) 所示。也就是说,对于输入流中的每个帧,查询通过匹配数据库中的断言来生成一个新的流,该流由该帧的所有扩展组成。所有这些流然后被组合成一个巨大的流,它包含输入流中每个帧的所有可能的扩展。这个流是查询的输出。
![c4-fig-0005.jpg](../images/c4-fig-0005.jpg)
![c4-fig-0005.jpg](img/c4-fig-0005.jpg)
[图 4.5](#c4-fig-0005a) 一个查询处理一个帧流。
......@@ -3733,13 +3733,13 @@ job($person, $x)
以与给定的`$x`绑定一致的方式。每个这样的匹配将产生一个包含`$x``$person`绑定的帧。两个查询的`and`可以看作是两个分量查询的串联组合,如图[图 4.6](#c4-fig-0006) 所示。通过第一个查询过滤器的帧被第二个查询过滤并进一步扩展。
![c4-fig-0006.jpg](../images/c4-fig-0006.jpg)
![c4-fig-0006.jpg](img/c4-fig-0006.jpg)
[图 4.6](#c4-fig-0006a) 两个查询的`and`组合是通过对连续的帧流进行操作产生的。
[图 4.7](#c4-fig-0007) 显示了计算两个查询的`or`的类似方法,作为两个组件查询的并行组合。每个查询分别扩展输入的帧流。这两个结果流然后被合并以产生最终的输出流。
![c4-fig-0007.jpg](../images/c4-fig-0007.jpg)
![c4-fig-0007.jpg](img/c4-fig-0007.jpg)
[图 4.7](#c4-fig-0007a) 两个查询的`or`组合是通过对帧流并行操作并合并结果产生的。
......@@ -4518,7 +4518,7 @@ list("job ",list("name "," $x "),list("computer "," wizard"))
查询系统函数,比如 4.4.4.5 部分的`add_rule_or_assertion`和 4.4.4.2 部分的`evaluate_query`,使用选择器和谓词,比如下面声明的`type``contents``is_rule``first_conjunct`,对特定于查询语言的表示进行操作。[图 4.8](#c4-fig-0008) 描述了查询系统使用的三个抽象障碍,以及转换函数`parse``unparse``convert_to_query_syntax`如何桥接它们。
![c4-fig-0008.jpg](../images/c4-fig-0008.jpg)
![c4-fig-0008.jpg](img/c4-fig-0008.jpg)
[图 4.8](#c4-fig-0008a) 查询系统中的语法抽象。
......
......@@ -29,7 +29,7 @@ function gcd(a, b) {
我们可以使用[图 5.1](#c5-fig-0001) 中所示的数据路径图来说明该机器所需的寄存器和操作。在该图中,寄存器(`a``b``t`)由矩形表示。向寄存器赋值的每种方式都由一个箭头表示,箭头后面有一个按钮(绘制为),从数据源指向寄存器。按下按钮时,该按钮允许源端的值“流入”指定的寄存器。每个按钮旁边的标签是我们用来指代该按钮的名称。这些名称是任意的,可以选择具有助记值的名称(例如,`a<-b`表示按下将寄存器`b`的内容分配给寄存器`a`的按钮)。一个寄存器的数据来源可以是另一个寄存器(如在`a<-b`赋值中),一个运算结果(如在`t<-r`赋值中),或一个常数(一个不能改变的内置值,在数据路径图中用包含常数的三角形表示)。
![c5-fig-0001.jpg](../images/c5-fig-0001.jpg)
![c5-fig-0001.jpg](img/c5-fig-0001.jpg)
[图 5.1](#c5-fig-0001a)GCD 机床的数据路径。
......@@ -37,7 +37,7 @@ function gcd(a, b) {
为了让数据路径实际计算 gcd,必须按正确的顺序按下按钮。我们将根据控制器图来描述这个序列,如图[图 5.2](#c5-fig-0002) 所示。控制器图的元素指示数据路径组件应该如何操作。控制器图中的矩形框标识要按下的数据路径按钮,箭头描述从一个步骤到下一个步骤的顺序。图中的菱形代表一个决定。根据菱形中标识的数据路径测试值,将遵循两个排序箭头中的一个。我们可以从物理类比的角度来解释控制器:把这个图想象成一个弹子在其中滚动的迷宫。当弹球滚进一个盒子时,它会按下由盒子命名的数据路径按钮。当弹球滚入一个决策节点时(比如对`b` = 0 的测试),它会离开由指示的测试结果所确定的路径上的节点。
![c5-fig-0002.jpg](../images/c5-fig-0002.jpg)
![c5-fig-0002.jpg](img/c5-fig-0002.jpg)
[图 5.2](#c5-fig-0002a)GCD 机的控制器。
......@@ -76,7 +76,7 @@ function factorial(n) {
[图 5.3](#c5-fig-0003) 显示了这样描述的 GCD 机器。这个例子仅仅暗示了这些描述的一般性,因为 GCD 机器是一个非常简单的例子:每个寄存器只有一个按钮,每个按钮和测试在控制器中只使用一次。
![c5-fig-0003.jpg](../images/c5-fig-0003.jpg)
![c5-fig-0003.jpg](img/c5-fig-0003.jpg)
[图 5.3](#c5-fig-0003a) 一台规格的 GCD 机。
......@@ -124,7 +124,7 @@ perform(list(op("display"), reg("a")))
[图 5.4](#c5-fig-0004) 显示了新 GCD 机器的数据路径和控制器。我们没有让机器在打印出答案后停下来,而是让它重新开始,这样它可以重复读取一对数字,计算它们的 GCD,并打印出结果。这个结构就像我们在第四章的解释器中使用的驱动循环。
![c5-fig-0004.jpg](../images/c5-fig-0004.jpg)
![c5-fig-0004.jpg](img/c5-fig-0004.jpg)
[图 5.4](#c5-fig-0004a) 读取输入并打印结果的 GCD 机器。
......@@ -150,11 +150,11 @@ assign("t", list(op("rem"), reg("a"), reg("b")))
在 GCD 中,控制器定义被包含一个循环的指令序列所取代,如图[图 5.6](#c5-fig-0006) 所示。
![c5-fig-0005.jpg](../images/c5-fig-0005.jpg)
![c5-fig-0005.jpg](img/c5-fig-0005.jpg)
[图 5.5](#c5-fig-0005a) 精心设计的 GCD 机床的数据路径和控制器。
![c5-fig-0006.jpg](../images/c5-fig-0006.jpg)
![c5-fig-0006.jpg](img/c5-fig-0006.jpg)
[图 5.6](#c5-fig-0006a) 图 5.5 中 GCD 机控制器指令序列。
......@@ -185,19 +185,19 @@ function sqrt(x) {
当设计一台执行计算的机器时,我们通常更喜欢安排组件由计算的不同部分共享,而不是复制组件。考虑一个包含两个 GCD 计算的机器——一个计算寄存器`a``b`内容的 GCD,另一个计算寄存器`c``d`内容的 GCD。我们可以从假设我们有一个原始的`gcd`操作开始,然后根据更多的原始操作扩展`gcd`的两个实例。[图 5.7](#c5-fig-0007) 只显示了最终机器数据路径的 GCD 部分,没有显示它们如何连接到机器的其余部分。该图还显示了机器控制器序列的相应部分。
![c5-fig-0007.jpg](../images/c5-fig-0007.jpg)
![c5-fig-0007.jpg](img/c5-fig-0007.jpg)
[图 5.7](#c5-fig-0007a) 带有两个 GCD 计算的机器的数据路径和控制器序列部分。
这台机器有两个余数操作箱和两个相等测试箱。如果复制的部件很复杂,就像余料箱一样,这将不是制造机器的经济方法。我们可以通过对两个 GCD 计算使用相同的组件来避免复制数据路径组件,只要这样做不会影响更大机器的其余计算。如果控制器到达`gcd_2`时不需要寄存器`a``b`中的值(或者如果这些值可以移动到其他寄存器进行保管),我们可以改变机器,使其在计算第二个和第一个 GCD 时使用寄存器`a``b`,而不是寄存器`c``d`。如果我们这样做,我们将获得如图 5.8 所示的控制器序列。
![c5-fig-0008.jpg](../images/c5-fig-0008.jpg)
![c5-fig-0008.jpg](img/c5-fig-0008.jpg)
[图 5.8](#c5-fig-0008a) 使用相同数据路径组件进行两种不同 GCD 计算的机器的控制器序列部分。
我们已经移除了重复的数据路径组件(因此数据路径再次如图[图 5.1](#c5-fig-0001) 所示),但是控制器现在有两个 GCD 序列,它们的不同之处仅在于入口点标签。最好是将这两个序列替换成一个单独的序列——一个`gcd` 子程序——在这个序列的末尾,我们分支回到主指令序列中的正确位置。我们可以这样完成:在转移到`gcd`之前,我们将一个区别值(比如 0 或 1)放入一个特殊的寄存器`continue`。在`gcd`子程序结束时,我们返回到`after_gcd_1``after_gcd_2`,这取决于`continue`寄存器的值。[图 5.9](#c5-fig-0009) 显示了生成的控制器序列的相关部分,其中仅包含一份`gcd`指令。
![c5-fig-0009.jpg](../images/c5-fig-0009.jpg)
![c5-fig-0009.jpg](img/c5-fig-0009.jpg)
[图 5.9](#c5-fig-0009a) 使用`continue`寄存器避免[图 5.8](#c5-fig-0008) 中的重复控制器序列。
......@@ -205,7 +205,7 @@ function sqrt(x) {
为了反映这种能力,我们将扩展 registermachine 语言的`assign`指令,以允许一个寄存器被指定为来自控制器序列的值 a 标签(作为一种特殊的常量)。我们还将扩展`go_to`指令,以允许在寄存器内容描述的入口点继续执行,而不仅仅是在常量标签描述的入口点继续执行。使用这些新的构造,我们可以用一个到存储在`continue`寄存器中的位置的分支来终止`gcd`子程序。这导致图 5.10 中[所示的控制器序列。](#c5-fig-0010)
![c5-fig-0010.jpg](../images/c5-fig-0010.jpg)
![c5-fig-0010.jpg](img/c5-fig-0010.jpg)
[图 5.10](#c5-fig-0010a)`continue`寄存器分配标签简化并概括了[图 5.9](#c5-fig-0009) 所示的策略。
......@@ -249,7 +249,7 @@ function gcd(a, b) {
[图 5.11](#c5-fig-0011) 显示了实现递归`factorial`功能的机器的数据路径和控制器。机器有一个堆栈和三个寄存器,称为`n``val``continue`。为了简化数据路径图,我们没有命名寄存器分配按钮,只命名了堆栈操作按钮(`sc``sn`保存寄存器,`rc``rn`恢复寄存器)。为了操作机器,我们将希望计算其阶乘的数字放入寄存器`n`并启动机器。当机器到达`fact_done`时,计算结束,答案将在`val`寄存器中找到。在控制器序列中,`n``continue`在每次递归调用之前保存,并在调用返回时恢复。通过分支到存储在`continue`中的位置来完成呼叫返回。当机器启动时,寄存器`continue`被初始化,以便最后一次返回将到达`fact_done`。保存阶乘计算结果的`val`寄存器在递归调用之前没有保存,因为在子例程返回之后`val`的旧内容不再有用。只需要新值,即子计算产生的值。
![c5-fig-0011.jpg](../images/c5-fig-0011.jpg)
![c5-fig-0011.jpg](img/c5-fig-0011.jpg)
[图 5.11](#c5-fig-0011a) 递归阶乘机。
......@@ -271,7 +271,7 @@ function fib(n) {
就像阶乘一样,我们可以用寄存器`n``val``continue`来实现递归斐波那契计算。该机器比用于阶乘的机器更复杂,因为在控制器序列中有两个地方我们需要执行递归调用——一次是计算 Fib(n–1),一次是计算 Fib(n–2)。为了设置这些调用,我们保存稍后需要其值的寄存器,将`n`寄存器设置为我们需要递归计算其 Fib 的数字(n–1 或 n–2),并将主序列中要返回的入口点分配给`continue`(分别为`afterfib_n_1``afterfib_n_2`)。我们接着去`fib_loop`。当我们从递归调用返回时,答案在`val`中。图 5.12 显示了该机器的控制器顺序。
![c5-fig-0012.jpg](../images/c5-fig-0012.jpg)
![c5-fig-0012.jpg](img/c5-fig-0012.jpg)
[图 5.12](#c5-fig-0012a) 用于计算斐波那契数列的机器控制器。
......@@ -502,7 +502,7 @@ function push(stack, value) {
`make_new_machine`函数,如图[图 5.13](#c5-fig-0013) 所示,构造了一个对象,其本地状态由一个堆栈、一个最初为空的指令序列、一个最初包含一个初始化堆栈操作的操作列表以及一个最初包含两个名为`flag``pc`(代表“程序计数器”)的注册表。内部函数`allocate_register`向注册表添加新条目,内部函数`lookup_register`在表中查找寄存器。
![c5-fig-0013.jpg](../images/c5-fig-0013.jpg)
![c5-fig-0013.jpg](img/c5-fig-0013.jpg)
[图 5.13](#c5-fig-0013a)`make_new_machine`功能实现了基本机型。
......@@ -1124,7 +1124,7 @@ cancel_all_breakpoints(machine)
我们可以使用向量来实现列表结构存储器所需的基本对结构。我们假设计算机内存分为两个向量:`the_heads``the_tails`。我们将如下表示列表结构:指向一对的指针是两个向量的索引。该对的`head`是指定索引的`the_heads`中的条目,该对的尾部是指定索引的`the_tails`中的条目。我们还需要一种对象而不是对的表示(比如数字和字符串),以及一种区分不同类型数据的方法。有许多方法可以实现这一点,但它们都归结为使用类型化指针,也就是说,扩展“指针”的概念,以包含关于数据类型的信息。 [](#c5-fn-0009) 数据类型使系统能够区分指向一对(由“对”数据类型和内存向量索引组成)的指针和指向其他类型数据(由其他数据类型和用于表示该类型数据的任何内容组成)的指针。如果两个数据对象的指针相同,则认为它们是相同的(`===`)。[图 5.14](#c5-fig-0014) 说明了使用这种方法来表示`list(list(1, 2), 3, 4)`,其盒指针图也已显示。我们使用字母前缀来表示数据类型信息。因此,指向具有索引 5 的对的指针被表示为`p5`,空列表由指针`e0`表示,而指向数字 4 的指针被表示为`n4`。在盒指针图中,我们在每一对的左下方指示了向量索引,该索引指定了该对的`head``tail`的存储位置。`the_heads``the_tails`中的空白位置可能包含其他列表结构的部分(这里不感兴趣)。
![c5-fig-0014.jpg](../images/c5-fig-0014.jpg)
![c5-fig-0014.jpg](img/c5-fig-0014.jpg)
[图 5.14](#c5-fig-0014a) 列表的盒指针和内存向量表示法`list(list(1, 2), 3, 4)`
......@@ -1269,7 +1269,7 @@ accumulate((x, y) => x + y,
当我们耗尽当前工作内存中的空闲单元时,也就是说,当一个`pair`操作试图将`free`指针递增到内存向量的末尾之外时,垃圾收集被触发。当垃圾收集过程完成时,`root`指针将指向新内存,所有可从`root`访问的对象将被移动到新内存,并且`free`指针将指示新内存中可分配新对的下一个位置。此外,工作内存和新内存的角色将互换——新的内存对将在新内存中构建,从`free`指示的位置开始,并且(先前的)工作内存将可用作下一次垃圾收集的新内存。[图 5.15](#c5-fig-0015) 显示了垃圾收集前后的内存安排。
![c5-fig-0015.jpg](../images/c5-fig-0015.jpg)
![c5-fig-0015.jpg](img/c5-fig-0015.jpg)
[图 5.15](#c5-fig-0015a) 垃圾收集进程对内存的重新配置。
......@@ -1374,7 +1374,7 @@ accumulate((x, y) => x + y,
在 5.1 节中,我们看到了如何将简单的 JavaScript 程序转换成注册机器的描述。我们现在将在一个更复杂的程序上执行这种转换,即 4 . 1 . 1–4 . 1 . 4 节的元循环求值器,它展示了如何用函数`evaluate``apply`来描述 JavaScript 解释器的行为。我们在本节开发的显式控制评估器展示了评估过程中使用的底层函数调用和参数传递机制是如何根据寄存器和堆栈上的操作来描述的。此外,显式控制评估器可以作为 JavaScript 解释器的实现,用与传统计算机的本机语言非常相似的语言编写。评估器可以由 5.2 节的寄存器机器模拟器执行。或者,它可以作为构建 JavaScript 评估器的机器语言实现的起点,甚至可以作为评估 JavaScript 程序的专用机器的起点。[图 5.16](#c5-fig-0016) 显示了这样一个硬件实现:一个充当 Scheme 评估器的硅片,Scheme 是本书最初版本中代替 JavaScript 使用的语言。芯片设计者从与本节描述的评估器相似的寄存器机器的数据路径和控制器规格开始,并使用设计自动化程序来构建集成电路布局。 [^(20)](#c5-fn-0020)
![c5-fig-0016.jpg](../images/c5-fig-0016.jpg)
![c5-fig-0016.jpg](img/c5-fig-0016.jpg)
[图 5.16](#c5-fig-0016a) 一种方案评估器的硅片实现。
......@@ -2468,7 +2468,7 @@ lambda 主体的这个简单转换是确保不显式返回的函数具有返回
`append_return_undefined`目前的设计有点粗糙:它总是在 lambda 主体后面附加一个`**return** undefined;`,即使在主体的每个执行路径中已经有一个 return 语句。重写`append_return_undefined`,以便它只在那些不包含 return 语句的路径末尾插入`**return** undefined;`。在下面的函数上测试您的解决方案,用任何表达式替换 e1 和 e2,用任何(非返回)语句替换 s1 和 s2。在`t`中,应在两个`(*)`处或仅在`(**)`处添加 return 语句。在`w``h`中,应在其中一个`(*)`处添加返回语句。在`m`中,不应添加返回语句。
![c5-fig-5001.jpg](../images/c5-fig-5001.jpg)
![c5-fig-5001.jpg](img/c5-fig-5001.jpg)
### 5.5.3 编制申请书和申报表
......@@ -2976,8 +2976,8 @@ return n === 1
错误分支的代码是另一个函数调用,其中函数是符号`"*"`的值,参数是`n`和另一个函数调用的结果(对`factorial`的调用)。这些调用中的每一个都建立了`fun``argl`以及它自己的原始和复合分支。[图 5.17](#c5-fig-0018) 显示了`factorial`函数声明的完整编译。注意,如上所示,谓词周围的`continue``env`的可能的`save``restore`实际上是生成的,因为这些寄存器被谓词中的函数调用修改,并且需要用于分支中的函数调用和`"return"`链接。
![c5-fig-0017a.jpg](../images/c5-fig-0017a.jpg)
![c5-fig-0017b.jpg](../images/c5-fig-0017b.jpg)
![c5-fig-0017a.jpg](img/c5-fig-0017a.jpg)
![c5-fig-0017b.jpg](img/c5-fig-0017b.jpg)
[图 5.17](#c5-fig-0018a) 编译声明的`factorial`功能。
......@@ -3016,8 +3016,8 @@ function factorial(n) {
编译了什么程序来产生如图[图 5.18](#c5-fig-0021) 所示的代码?
![c5-fig-0018a.jpg](../images/c5-fig-0018a.jpg)
![c5-fig-0018b.jpg](../images/c5-fig-0018b.jpg)
![c5-fig-0018a.jpg](img/c5-fig-0018a.jpg)
![c5-fig-0018b.jpg](img/c5-fig-0018b.jpg)
[图 5.18](#c5-fig-0021a) 编译器输出的一个例子。见练习 5.38。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册