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

Forth 语言简明教程

作者 Richard E. Haskell
编译 赵宇 张文翠

第四课 Forth 判断

4.1 分支指令和循环

所有的计算机都必须有某种办法来产生条件分支(IF …… THEN)和实现循环, Forth 使用下面这些“良好定义”的结构:

IF ... ELSE ... THEN

DO ... LOOP

BEGIN ... UNTIL

BEGIN ... WHILE ... REPEAT

BEGIN ... AGAIN

这些语句的工作方式与它们在其它语言所表现的不同。字 IF、UNTIL 和 WHILE 运行时希望堆栈上有 true/false 标志,一个 false 标志的值是 0 ,一个 true 标志的值是 -1.

F-PC 定义两个常数

-1 CONSTANT TRUE

0 CONSTANT FALSE

标志可以通过各种方式产生,但通常的方式都是使用某些条件表达式,它们把标志留在堆栈上。

 

我们先来看看 Forth 的条件字然后给出分支和循环语句的一些例子:

4.2 条件字和true/false 标志

下面这些 Forth 条件字产生 true/false 标志 :

< ( n1 n2 -- f ) ( "less-than" )

如果 n1 小于 n2 则标志 f 为真

> ( n1 n2 -- f ) ( "greater-than" )

如果 n1 大于 n2 则标志 f 为真

= ( n1 n2 -- f ) ( "equals" )

如果 n1 等于 n2 则标志 f 为真

<> ( n1 n2 -- f ) ( "not-equal" )

如果 n1 小等于 n2 则标志 f 为真

<= ( n1 n2 -- f ) ( "less-than or equal" )

如果 n1 小于或者等于 n2 则标志 f 为真

>= ( n1 n2 -- f ) ( "greater-than or equal" )

如果 n1 大于或者等于 n2 则标志 f 为真

0< ( n -- f ) ( "zero-less" )

如果 n 小于 0 (负数)则标志 f 为真

0> ( n -- f ) ( "zero-greater" )

如果 n 大于 0 (正数)则标志 f 为真

0= ( n -- f ) ( "zero-equals" )

如果 n 等于 0 则标志 f 为真

0<> ( n -- f ) ( "zero-not-equal" )

如果 n 小等于 0 则标志 f 为真

0<= ( n -- f ) ( "zero-less-than or equal" )

如果 n 小于或者等于 0 则标志 f 为真

0>= ( n -- f ) ( "zero-greater-than or equal" )

如果 n 大于或者等于 0 则标志 f 为真

以下条件字比较堆栈上的两个无符号数

U< ( u1 u2 -- f ) ( "U-less-than" )

如果 u1 小于 u2 则标志 f 为真。

U> ( u1 u2 -- f ) ( "U-greater-than" )

如果 u1 大于 u2 则标志 f 为真。

U<= ( u1 u2 -- f ) ( "U-less-than or equal" )

如果 u1 小于等于 u2 则标志 f 为真。

U>= ( u1 u2 -- f ) ( "U-greater-than or equal" )

如果 u1 大于等于 u2 则标志 f 为真。

4.3 Forth 逻辑操作

有些 Forth 有一个字 NOT ,它可以反转堆栈上的标志值。在 F-PC 系统中,字 NOT 执行一个堆栈上的字的 1 补码。只要 TRUE 是 -1 ( 16 进制 FFFF ) , 则 NOT TRUE 就是 FALSE 。

你必须小心的是:由于任何的非 0 值都会作为 TRUE 对待,而除 16 进制 FFFF 外的任何值 进行 1 的补码运算之后都不会产生 0 ( FALSE )。你可以使用比较字 0= 来产生标志。

除了逻辑操作符 NOT 外, Forth 也支持下列的双目逻辑操作符:

AND ( n1 n2 -- and )

在堆栈上留下 n1 AND n2 这是一个按位与运算,例如,如果你输入

255 15 AND ( mask lower 4 bits )

在栈顶将留下值 15

OR ( n1 n2 -- or )

在堆栈上留下 n1 OR n2 ,这是按位运算,例如如果你输入:

9 3 OR

将在堆栈上留下值 11

XOR ( n1 n2 -- xor )

在堆栈上留下 n1 XOR n2 ,这是按位运算,例如如果你输入

240 255 XOR ( Hex F0 XOR FF = 0F )

将在栈顶留下值 15

4.4 IF 语句

Forth 的 IF 语句与其它语言的不同。你所熟悉的一个典型 IF ... THEN ... ELSE 语句大概是这样的:

IF <cond> THEN

<true statements>

ELSE

<false statements>

而在 Forth 中, IF 语句是这样的:

<cond> IF <true statements>

ELSE <false statements>

THEN

注意,在 IF 字执行的时候, true/false 标志必须在栈顶。如果栈顶上是一个真标志,则 <true statements> 被执行,如果栈顶上是一个假标志,则 <false statements> 被执行。在 <true statements> 或者 <false statements> 被执行之后,字 THEN 后面的语句被执行。 ELSE 子句是可选的

IF 字必须在冒号定义内使用,作为一个例子,定义下列字:

: iftest ( f -- )

IF CR ." true statements"

THEN CR ." next statements" ;

 

: if.else.test ( f -- )

IF CR ." true statements"

ELSE CR ." false statements"

THEN CR ." next statements" ;

 

然后你输入:

TRUE iftest

FALSE iftest

TRUE if.else.test

FALSE if.else.test

4.5 DO 循环

Forth 的 DO 循环必须在冒号定义中使用,为了说明它是如何工作的,定义下列字:

: dotest ( limit ix -- )

DO

I .

LOOP ;

然后你输入:

5 0 dotest

值 0 1 2 3 4 将打印到屏幕上,试一下。

DO 循环是这样工作的: 字 DO 从参数栈顶上取两个值并把它们放到返回栈上。这时这两个值已经不在参数栈上了。字 LOOP 将索引值加 1 并把结果与限值进行比较。如果增量之后的索引值小于限值,则分支到 DO 下面的字。如果增量之后的索引值等于限值,则分支到 LOOP 之后的字。我们将在第九课中仔细研究 DO 循环是如何实现的。

Forth 字 I 把索引值从返回栈复制到参数栈顶。因此上面的例子可以解释如下:

5 \ 5

0 \ 5 0

DO

I \ ix ( ix = 0,1,2,3,4)

.

LOOP

注意限值必须比你希望的最大索引值还要大 1 ,例如:

11 1 DO

I .

LOOP

将打印出值 1 2 3 4 5 6 7 8 9 10

字 +LOOP

Forth 的 DO 循环索引值可以是 1 以外的其它值,这时需要用字 +LOOP 来替代 LOOP 。 可以通过下列的例子来看工作情况:

: looptest ( limit ix -- )

DO

I .

2 +LOOP ;

然后你输入

5 0 looptest

值 0 2 4 将打印出来。

字 +LOOP 从参数栈顶取得值并把它加到返回栈的索引值中,之后的动作与 LOOP 一样,只要是增量后的索引值小于限值,它就分支到 DO 后面的语句(如果增量值为正)。如果增量的值为负,则当增量的索引值小于限值时就退出循环。例如你可以输入

: neglooptest ( limit ix -- )

DO

I .

-1 +LOOP ;

然后输入

0 10 neglooptest

值 10 9 8 7 6 5 4 3 2 1 0 将打印在屏幕上。

嵌套循环 – 字 J

Forth 的循环可以嵌套。这时就要有两对索引/限值被移到返回栈上。字 I 把内层循环的索引值从返回栈复制到参数栈上,字 J 把外层循环的索引值从返回栈复制到参数栈上。

作为一个嵌套循环的例子,定义下面的字:

: 1.to.9 ( -- )

8 1 DO

CR

3 0 DO

J I + .

LOOP

3 +LOOP ;

如果你执行这个字,下面的内容将打印到屏幕上:

1 2 3

4 5 6

7 8 9

你明白这是为什么吗?

嵌套的循环在 Forth 中比在其它高级语言中用得少。更好的办法是定义一个小的字,它只包含一个 DO 循环,然后在另外的循环中调用这个字。

字 LEAVE

Forth 字 LEAVE 可以用在 DO 循环中以退出循环。它通常是用在 DO 循环的 IF 语句中。字 LEAVE 可以立即通出 DO 循环( LOOP 之后那个字的地址作为第三个字保存在返回栈上)。还有一个相关的字 ?LEAVE (flag --) 在栈顶为真时退出 DO 循环,这就不用使用 IF 语句了。

作为一个例子,假设你想定义一个字 find.n ,它查找一个指定值在字表中的索引值(也就是这个值在表中的位置),如果找到则返回真,否则在栈顶返回假。首先用 Forth 语句构造表:

CREATE table 50 , 75 , 110 , 135 , 150 , 300 , 600 ,

将在代码段中创建表

表中值的数目是 imax ( 在我们的情况下是 7). 要查找的值是 n. 在被执行时这些值必须在堆栈是,下面是 find.n 的定义:

: find.n ( imax n -- ff | index tf )

0 SWAP ROT \ 0 n imax

0 DO \ 0 n

DUP I table \ 0 n n ix pfa

SWAP 2* + \ 0 n n pfa+2*ix

@ = \ 0 n f

IF \ 0 n

DROP I TRUE \ 0 ix tf

ROT LEAVE \ ix tf 0

THEN

LOOP \ 0 n

DROP ; \ 0 | ix tf

研究这个定义一直到你明白它是如何工作的时候为止。通常情况下,在使用 DO 循环时的堆栈情况在执行完 DO 时和执行室外 LOOP 时是一样的,你常常需要使用 DUP 在 DO 循环中复制值并在离开循环时用 DROP 去除一些值。特别注意 ROT 在 LEAVE 之前使用以建立堆栈以使得真标志留在堆栈顶。

4.6 UNTIL 循环

Forth 的 UNTIL 循环必须用于冒号定义中, UNTIL 循环的格式是:

BEGIN <Forth statements> <flag> UNTIL

如果 <flag> 是假,程序分支到 BEGIN 之后的字。如果 <flag> 是真 , 程序执行 UNTIL 之后的字。

下面的两个 Forth 字能够检测和读出键盘的输入

KEY? ( -- flag )

如果键盘有键按下,返回真标志。

KEY ( -- char )

等待键盘按下并将 ASCII 码返回到栈顶。

F-PC 字 EMIT ( char -- )

将在屏幕上打印栈顶 ASCII 码对应的字符。

定义下面的字

: dowrite ( -- )

BEGIN

KEY \ char

DUP EMIT \ print on screen

13 = \ if equal to CR

UNTIL ; \ quit

执行这个字将在屏幕上打印出所有你输入的字符,直到你打入了 <Enter> 键 (ASCII 码 = 13). 注意 UNTIL 从堆栈上移去标志。

4.7 WHILE 循环

Forth 的 WHILE 循环必须在冒号定义中使用, WHILE 循环的格式是

BEGIN <words> <flag> WHILE <words> REPEAT

如果 <flag> 是真,在字 WHILE 和 REPEAT 之间的字被执行,然后再分支到 BEGIN 后面的字。如果 <flag> 是假,程序分支到 REPEAT 之后的字。

作为一个例子,考虑下面求 n 阶乘的算法:

x = 1

i = 2

DO WHILE i <= n

x = x * i

i = i + 1

ENDDO

factorial = x

下面的 Forth 字计算阶乘

: factorial ( n -- n! )

1 2 ROT \ x i n

BEGIN \ x i n

2DUP <= \ x i n f

WHILE \ x i n

-ROT TUCK \ n i x i

* SWAP \ n x i

1+ ROT \ x i n

REPEAT \ x i n

2DROP ; \ x

注意,为了使 WHILE 循环能够正常工作,在 BEGIN 和 REPEAT 之间的堆栈安排必须相同。还要注意的是,尽管上面的算法使用了 3 个变量 x、i 和 n , 但 Forth 实现却不使用任何变量!这是 Forth 的特点。你可以发现在 Forth 中使用变量比在其它语言中使用变量要少得多。

可以输入以下内容测试阶乘的定义

3 factorial .

4 factorial .

0 factorial .

4.8 练习

练习 4.1 Fibonacci 序列是一个数值序列,其中的每个数(从第三个开始)都是它紧邻的前两个数之和。于是开始几个数看起来像是这样:

1 1 2 3 5 8 13 21 34

定义一个 Forth 字

fib ( n -- )

它将打印所有值小于 n 的 fibonacci 序列,通过下面方法来测试你的字:

1000 fib

练习 4.2 创建一个表称为 weights ,它包含下列值

75 135 175 115 220 235 180 167

定义一个 Forth 字称为

heaviest ( pfa -- max.value )

它将按照栈顶的值从表中打印最大值,如果你输入

weights heaviest .

值 235 将要打印出来

 

 

   

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