5. 字典
Forth 程序存储在字典中。字典占据了系统存储器的很大部分,它由一个串线链接的可变长度的项目组成,每个项目定义了一个字。每个定义的内容根据字的类型(数据项、常数、操作序列等)而有所不同,字典是可扩展的。
字是由“定义字”加入字典的,最常用的定义字是:(冒号)。当冒号执行的时候,它为后面的字建立一个字典项,然后进入“编译”模式。有许多不同的编译方法,最常用的是“串线编码”,这种方法把定义编译成一系列以前定义字的地址引用。定义由;(分号)结束。下面就是一个定义:
: NETWORK ( -- ) OPEN LINK TRXT. ECHO CLOSE LINK ;
图 1 编译的字典项
当一个名字项被编译到字典中的时候(称为定义的首部),它包含一个指向字典中前一个首部的指针。新字的名字加入字典(这里就是 NETWORK ),接着一个指向名字为“( : )”子程序调用的指针编译到字典中作为定义的第一部分,这个指针指向一段在解释定义体时需要执行的代码。当然,这里所说的不是唯一的编译技术,但它的应用最为普遍,这种技术称为间接串线编码,因为定义中的第一个项目是一段代码的引用,这段代码知道如何解释定义的其它部分。
定义的其它部分称为这个定义的体。在编译模式下,系统将依次寻找每个字的首部。每个首部地址依次放到定义体中,这样就产生了一个地址列表。最后在到达;时,一个称为“EXIT”的子程序地址被编译进定义。 EXIT 子程序用来将控制返回到调用字,就像一个子程序返回一样。
6. 堆栈
Forth 维护两个下推式堆栈,下推式堆栈也称为后进先出列表,它们提供了 Forth 字之间通信以及逻辑控制的有效机制。
尽管两个堆栈的结构是相同的,但它们的用途却大不相同。与用户/程序员关系最为密切的是数据栈,它保存有调用字之间传递的参数,以替代传统语言的参数列表,同时也是一个实现定义重用的有效内部机制。第二个堆栈是返回栈,用于保存嵌套定义的返回地址,偶尔也用于临时保存其它的数据。
数据栈的使用导致了一种操作数位于操作符后面的“后缀”表示方法,这种后缀表示方法常常被称为 RPN 或者“逆波兰表示法”,用以纪念 Lukasiewicz 教授,上个世纪 20 年代时, Lukasiewicz 教授在华沙大学工作,为 Sentential Calculus 开发了这种方法。
作为一个例子,我们来看一下字 BLANK 。这个字希望堆栈上有一个地址和一个数,字 BLANK 将从指定地址开始填充指定数目的 ASCII 空格,于是:
PAD 25 BLANK
将向 Forth 系统的便笺区填入25个空格,这里地址是由字 PAD 放到堆栈上的。应用字通常按相似的方法定义,比如:
100 SAMPLES
将定义一个用于保存 100 个测量值的数据数组。算术操作也从堆栈上取值并将结果保存到堆栈上。例如, + 操作把栈顶的两个数相加,并用它们的和替代栈顶。由于操作的结果仍然留在栈顶,所以连续的操作就可以组合在一起而不需要临时存储变量。例如,表达式
tempn + ((reading % interval) / interval) * (tempn1 - tempn)
变成:
reading interval % interval / tempn1 tempn - * tempn +
7. 解释器
Forth 本质上是一个解释系统,在这样的系统中,程序的执行是由数据项而不是由机器代码控制的。解释系统通常很慢,但是 Forth 却通过两级解释而保持了实时应用所需要的高速度。当然,我们这样说是假设系统使用普通的串线编码方法,如果使用编译技术来产生本地代码,就根本不会有任何速度上的损失了。
• 第一个解释器是文本解释器,它扫描从终端(或者从海量存储器)得到的字符串,并在字典中查找每一个字。如果找到了这个字,就通过第二级地址解释器执行。
• 第二级解释器是“地址解释器”。尽管不是所有的系统都用这种方法实现,但这种方法却是最早的和最基本的方法,地址解释器通过执行每个定义点的程序来处理地址(或者标记)串,这些定义点是由:(冒号)创建并编译到定义中的。
7.1 文本解释器
上电之后。 Forth 启动一个称为 QUIT 的无限循环。这就是 Forth 文本解释器,也称为键盘解释器。
: QUIT ( -- ) BEGIN RESET QUERY INTERPRET AGAIN ;
RESET 清除堆栈, QUERY 等待用户从键盘(或者从海量存储设备)输入一个命令, INTERPRET 寻找字典匹配,然后执行。 BEGIN 和 AGAIN 是程序控制字,用于构造无限循环。
QUIT 循环提供了语言的“交互式”特点,如果输入命令,它就可以立即被执行,而执行的结果可以使用相同的文本解释器来观察。与其它的编辑 - 编译 - 链接 - 测试程序开发过程相比,一个新的定义可以在极短的时间里被反复地测试和排错。
7.2 地址解释器
定义项首部的最后一个字段是指向功能执行的代码指针,这个字段所在的地址被称为“代码域地址”,简称 CFA 。对于所有的 Forth 高级定义字(使用 : 冒号定义的字),这个地址就是地址解释器的地址,它指向字 (:) 。
地址解释器有一个寄存器 I ,其中包含被执行列表的下一个入口的地址。这个入口就是某个字的 CFA 地址,这个字被当前正在执行的高级定义字所调用。正是 CFA 决定了一个字的属性(或者类型)。
在下面的例子中,字 A 调用字 B ,字 B 调用字 X 等等。 CFA 保存在中间寄存器 W 中。采用这种两级间接寻址和字典列表结构的 Forth 编码方式也被称为“间接串线编码”。
图 2 间接串线编码
地址解释器读入 I 并使用列表中的下一个地址装入 W ,然后从 W 读出并执行由它指示的代码。 I 自动增量指向列表中的下一个入口,因为第一个入口是 CFA ,所以 I 现在指向了定义体。这对于查找表一类的数据结构非常有用,因为定义体就是表元素的存储区。在高级定义的情况下,代码域地址指向子程序 (:) ,它把当前的 I 保存到返回栈上,然后从 W 装入 I 并重复这一过程。在高级定义的结尾,字 EXIT 被执行,恢复原来的 I 值,程序像以前一样继续执行。这三个动作“读 I 指针,保存 I 指针,恢复 I 指针”是地址解释器得以工作的基本机制。
地址解释器有三个重要的特点:
• 特别快。尽管实际的速度依赖于特定的实现,但专业化的实现是高度优化的,每个地址通常要求 1-2 个机器字。这相比纯的汇编代码只增加 0%-50% 的开销。在许多基准程序测试中,好的 Forth 实现远远胜过诸如 BASIC 或者 LISP 一类的解释语言,可以与其它的编译高级语言相比;
• 它使得 Forth 定义极其紧缩,每一个引用仅仅需要一个单元(可以把单元理解成机器字,但是“字”在 Forth 中是指一个定义项)。作为比较,我们可以考虑一下许多高级语言产生的子程序调用结构:一个 CALL 或者 JSR 指令,后随地址,在这之前和之后,还有参数序列的访问指令。
• 使用返回栈保存地址的方法对于程序员是透明的,字也可以按需要嵌套许多层,这样的高级定义字序列可以被自动处理而不需要特殊的代码。
Forth 系统中的许多字都是用 : 定义并通过地址解释器进行解释的,而许多 Forth 系统本身也是用这种方法定义的。
正文
Forth 语言概要(2)2005-08-05 14:09:00
【评论】 【打印】 【字体:大 中 小】 本文链接:http://blog.pfan.cn/forth/3454.html
阅读(2709) | 评论(0)
版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!
评论