返回入门教程首页 主页 | 开发工具 | 应用芯片 | 核心模块  
 
   
 

Forth 初学者指导 (使用Win32Forth)

原著 J.V. Noble

编译 Forth 中文网, ForthChina.com

原文 Win32Forth 系统联机在线文档

堆栈和逆波兰表示法 (RPN)

我们现在讨论堆栈和基于堆栈运算的“逆波兰”或者称为“后缀”运算。

虚拟地看,所有的现代 CPU 都是围绕堆栈着而设计的。 Forth 通过在语言中反映底层的体系结构而高效率地使用它的 CPU 。

但是什么是堆栈呢?就像这个名字所说明那样,一个堆栈就像是一堆叠放起来的卡片,每个卡片上都有一个数字。数字总是加入到这一堆卡片的上面,并最先从上面取走卡片。 Forth 的输入行是:

2 5 73 -16 <cr> ok

堆栈的状态是

cell # contents

0 -16 (TOS)

1 73 (NOS)

2 5

3 2

这里 TOS 代表“栈顶元素”, NOS 代表“次栈顶元素”。

在 Forth 数据结构(比如堆栈、数组、表等)中,我们总是使用基于 0 的相对数,所以 TOS 相对表示为 #0, NOS 表示为 #1 等等。

假设我们对上面的输入行使用

+ - * . <cr> xxx ok

xxx 将会是什么?操作将产生连续的堆栈状态

cell# initial + - * .

0 -16 57 -52 -104

1 73 5 2

2 5 2

3 2

empty stack

操作 " . " (TOS-> 显示 ) 显示 -104 到屏幕上,保留堆栈为空。那就是 xxx 是 -104 。

处理参数栈

Forth 系统(最少)由两个堆栈组成:参数栈和返回栈。

基于堆栈的系统必须提供把数据放到堆栈上、移去它们、重新安排它们顺序的手段。 Forth 包含有实现这些目的的标准字。

把数放到堆栈上非常简单:简单地打入数(或者把它们放在 Forth 字的定义里)。

DROP 把数从 TOS 中移去,并把后面的数上移(因为在存储器中,堆栈总是向下增长的, DROP 仅仅需要增量指向 TOS 的指针 1 个单元)。

SWAP 交换栈顶的两个数。

DUP 复制 TOS 到 NOS 。

ROT 旋转栈顶的 3 个数。

这些行为在下面显示(我们显示每个字做堆栈初始化)。

cell | initial | DROP SWAP ROT DUP

0 | -16 | 73 73 5 -16

1 | 73 | 5 -16 -16 -16

2 | 5 | 2 5 73 73

3 | 2 | 2 2 5

4 | | 2

Forth 包含有字 OVER 、 TUCK 、 PICK ROLL ,它们的行为如下(注意 PICK ROLL 之前必须有一个整数,它说明一个元素在堆栈中的位置)

cell | initial | OVER TUCK 3 PICK 3 ROLL

0 | -16 | 73 -16 2 2

1 | 73 | -16 73 -16 -16

2 | 5 | 73 -16 73 73

3 | 2 | 5 5 5 5

4 | | 2 2 2

很明显, 0 PICK DUP 相同1 PICK OVER 的代名词, 1 ROLL 就是 SWAP 2 ROLL 意味着 ROT

返回栈和它的使用

我们已经说明:编译器建立了从被访问的已经定义的字到调用字的链接。链接机制 — 在执行的时候 — 使用返回栈 (rstack) :下一个将要访问的地址被放到返回栈上,这样在当前的字被执行的时候,系统知道下一个跳转的字在什么地方。(这只是大多数情况,并不是所有的 Forth 实现都是这样的。但是不论它们是不是使用堆栈作为链接子程序,它们都有返回堆栈)。

此外,返回堆栈也作为递归的返回地址(由于它可以被嵌套,返回地址需要一个堆栈。返回栈也可用于 DO...LOOP 结构的限制值)。

用户也可以在返回栈上存放和读取东西。这是一个使用非标准组件的例子,并不鼓励初学者使用这种方法,因为它增加了程序设计的风险。参看下面的“注意”。

为了向返回栈存储数据,我们可以说 >R , 为了读回我们使用 R> 。字 R@ 复制返回栈栈顶到 TOS 。

既然已经有了那么好的参数栈,我们还为什么还需要使用返回栈呢?有时是因为在参数堆栈上执行操作会使代码变得很难读,返回栈可以减少复杂性。

另外, VARIABLE – 命名的位置 — 提供了一个在堆栈之外存储数的位置 – 运算中的临时结果,也可以减少复杂性。试试:

\ YOU DO THIS \ EXPLANATION

VARIABLE X <cr> ok \ create a named storage location X;

\ X executes by leaving its address

3 X ! <cr> ok \ ! ("store") expects a number and

\ an address, and stores the number to

\ that address

X @ . <cr> 3 ok \ @ ("fetch") expects an address, and

\ places its contents in TOS.

不过,Forth 鼓励尽可能少地使用命名变量。原因是:由于是典型的全局变量 — 任何子程序都可以访问它们 — 在一个大型程序中,会产生不希望的交连。

尽管在 Forth 中可以使用局部于子程序的本地变量, 但返回栈通常能够替代本地变量:

•  返回栈已经存在,你不需要再重新申请一个本地变量 ;

•  当它上面的数被移走之后,返回栈自动收缩,能够释放一些存储器空间;

注意:因为返回栈对于执行是至关重要的,我们不能把它弄乱。如果我们使用返回栈作临时存储器,必须把它们恢复到初始状态。一个把数放到返回栈上的字必须在退出这个字的时候移开它 — 使用 R> 或者 RDROP (如果它被定义) 因为 DO...LOOP 也使用返回栈,对于每个 DO 之后的 >R 必须在 LOOP 之前有一个 R> 或者 RDROP 忽略这些过程可能导致系统崩溃。

RDROP 不是一个 ANS Forth 标准字,我们不能假设在每个系统上都有定义。因为它不标准,有些系统称之为 R>DROP ( 它也不是标准的 ) 。如果需要,这里是它的定义:

: RDROP ( or R>DROP) ( r: n -- ) R> DROP ;

 

 

   

(C) ForthChina.com 版权所有 2004-2010
Email:forthchina@163.com