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

Forth 语言简明教程

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

第一课 Forth 语言简介

1.1 介绍 Forth

下面列出的几条文字非常简单,但它能够完整地描述 Forth 程序设计语言:

•  Forth 中的每一个事物都是一个字( word );

•  Forth 字必须用空格分开;

•  Forth 字存储在字典中;

•  Forth 字可以被解释,也可以被编译;

•  在解释模式下,一个 Forth 字被执行;

•  在编译模式下,一个 Forth 字被存储到字典中;

•  你可以通过把一串 Forth 字组合在一起而形成 Forth 短语;

•  如果你打入一个 Forth 字并且后随 <Enter>, 它将被执行(解释模式);

•  如果你打入一个数(比如 6)并按下 <Enter>, 这个数将作为一个 16 位的无符号数存储到堆栈上;

•  Forth 广泛使用堆栈在字间传递参数。这就意味着 Forth 应用程序对变量的使用将显著减少;

•  你可以在 Forth 中定义一个新的字(使用早先已经定义的 Forth 字),它会成为 Forth 字典的一部分,可以像其它的 Forth 字一样使用。

1.2 Forth 算术

Forth 使用堆栈和后缀表示法进行算术运算。

包括 F-PC 在内的许多 Forth 系统,其堆栈都存储 16 位的值。 32 位的 Forth 系统比如 MacForth 在堆栈上存储 32 位的值。在 16 位的 Forth 系统中,堆栈上的值将占2个字节的位置,在 32 位的 Forth 系统中,堆栈上的值将占 4 个字节的位置。

当你打入一个数的时候,它就被放到堆栈上。你可以用任何的基数来输入数,我们后面将看到如何改变数基。默认的数基是十进制。因此,如果你打入 35 , 16 进制的 23H (后缀 H 表示是一个 16 进制数)将按以下的格式存储在堆栈上:

如果你输入 2 个数,中间以空格分开,它们都将存储到堆栈上。

例如,你输入:

127 (空格) 256

两个 16 进制数 7Fh 和 100h 将按以下方式存储在堆栈上:

打入 .S ( 或者 .s ,对于大多数 Forth 系统来说,大小写没有关系 ) 将非破坏性地显示堆栈内容,顺序是先打印栈底部的内容,再打印栈顶的内容。

127 256 .s 127 256 ok

这里, ok 是 Forth 的提示符,数值按 2 的补码方式存储在堆栈上。

16 位 Forth 系统在堆栈上可以存储值的范围是 -32,768 到 +32,767 ,32 位系统可以在堆栈上存储值的范围是 -2,147,483,648 到 +2,147,483,647.

1.3 Forth 算术操作

Forth 字 . ( 读作点或者 dot) 可以打印栈顶元素的值

7 9 . . 将打印出 9 和 7

回车通常被 Forth 忽略或者作为一个空格对待,它可以使程序更易读。采用“垂直”风格编写程序,我们可以很容易地用一个反斜杠 \ 来说明“堆栈的组织结构”,反斜杠之后直到这一行尾的任何内容都作为注释而被忽略。

例如,为了解释上面例子的每一步的堆栈情况,我们可以这样写:

7 \ 7

9 \ 7 9

. \ 7

. \

注意,点从堆栈移去一个值。

Forth 字 + ( 加 ) 把栈顶的两个值相加并把结果放到堆栈上,例如:

7 9 + . 将打印 16

7 \ 7

9 \ 7 9

+ \ 16

. \

Forth 字 - ( 减 ) 将用栈顶元素减去次栈顶元素并把结果差放到栈顶。

8 5 - . 将打印 3

8 \ 8

5 \ 8 5

- \ 3

. \

Forth 字 * ( 乘法 ) 把栈顶的两个值相乘,积留在堆栈上。

4 7 * . 将打印 28

4 \ 4

7 \ 4 7

* \ 28

. \

Forth 字 / (除法)实现除法,栈顶元素为除数,次栈顶元素为被除数,商留在堆栈上。

8 3 / . 将打印 2

8 \ 8

3 \ 8 3

/ \ 2

.

1.4 堆栈管理字

堆栈的说明通常使用这样的格式 ( before -- after ) ,其中

before = 这个字被执行之前的栈顶元素;

after  = 这个字被执行之后的栈顶元素

DUP ( n -- n n )

复制栈顶元素,如 5 DUP . . 将打印 5 5

5 \ 5

DUP \ 5 5

. \ 5

. \

SWAP ( n1 n2 -- n2 n1 )

交换堆栈上的两个元素,如 3 7 SWAP . . 将打印 3 7

3 \ 3

7 \ 3 7

SWAP \ 7 3

. \ 7

. \

DROP ( n -- )

移去栈顶元素,如 6 2 DROP . 将打印 6

6 \ 6

2 \ 6 2

DROP \ 6

.

OVER ( n1 n2 -- n1 n2 n1 )

复制次栈顶元素,如 6 1 OVER . . . 将打印 6 1 6

6 \ 6

1 \ 6 1

OVER \ 6 1 6

. \ 6 1

. \ 6

.

TUCK ( n1 n2 -- n2 n1 n2 )

复制栈顶元素到次栈顶元素之下,这个操作等效于 SWAP OVER

如 6 1 TUCK . . . 将打印 1 6 1

6 \ 6

1 \ 6 1

TUCK \ 1 6 1

. \ 1 6

. \ 1

.

ROT ( n1 n2 n3 -- n2 n3 n1 )

旋转堆栈上的三个元素,原来的第三个元素变成了第一个元素

如 3 5 7 ROT . . . 将打印 3 7 5

3 \ 3

5 \ 3 5

7 \ 3 5 7

ROT \ 5 7 3

. \ 5 7

. \ 5

.

-ROT ( n1 n2 n3 -- n3 n1 n2 )

反向旋转堆栈顶部的三个元素,栈顶元素被旋转到了第二位置

如 3 5 7 -ROT . . . 将打印 5 3 7

3 \ 3

5 \ 3 5

7 \ 3 5 7

-ROT \ 7 3 5

. \ 7 3

. \ 7

.

NIP ( n1 n2 -- n2 )

从堆栈上移去第二个元素,这个操作等效于 SWAP DROP ,如 6 2 NIP . 将打印 2

6 \ 6

2 \ 6 2

NIP \ 2

.

2DUP ( n1 n2 -- n1 n2 n1 n2 )

复制栈顶两个元素,如 2 4 2 DUP .S 将打印 2 4 2 4

2SWAP ( n1 n2 n3 n4 -- n3 n4 n1 n2 )

把栈顶的两个元素与第三个和第四个元素交换,如 2 4 6 8 2SWAP .S 将打印 6 8 2 4

2DROP ( n1 n2 -- )

从堆栈上移去栈顶两个元素

PICK ( n1 -- n2 )

从栈顶计算 n1 位置(不包含 n1 ),把这个位置的值复制到栈顶,栈顶与 n1 位置对应的是 0.

0 PICK 等效于 DUP

1 PICK 等效于 OVER

2 4 6 8 2 PICK .S 将打印 2 4 6 8 4

ROLL ( n -- )

旋转位置 n ( 不包含 n) 到栈顶, n 必须大于 0.

1 ROLL 等效于 SWAP

2 ROLL 等效于 ROT

2 4 6 8 3 ROLL .S 将打印 4 6 8 2

1.5 更多的 Forth 字

MOD ( n1 n2 -- n3 )

n1 除以 n2 并把余数 n3 留在堆栈上。

8 3 MOD . 将打印 2

/MOD ( n1 n2 -- n3 n4 )

n1 除以 n2 并把商 n4 放到栈顶、余数 n3 作为次栈顶。

10 3 /MOD .S 将打印 1 3

MIN ( n1 n2 -- n3 )

把 n1 和 n2 之中最小的一个放到栈顶。

8 3 MIN . 将打印 3

MAX ( n1 n2 -- n3 )

把 n1 和 n2 之中最大的放到栈顶。

8 3 MAX . 将打印 8

NEGATE ( n1 -- n2 )

改变 n1 的符号。

8 NEGATE . 将打印 -8

ABS ( n1 -- n2 )

把 n1 的绝对值放到栈顶 .

-8 ABS . 将打印 8

2* ( n1 -- n2 )

通过执行算术移位把 n1 乘 2

8 2* . 将打印 16

这个操作等效于 8 2 * 但是执行得更快。

2/ ( n1 -- n2 )

通过执行算术右移把 n1 除以 2 .

8 2/ . 将打印 4

这个操作等效于 8 2 / 但是更快。

U2/ ( n1 -- n2 )

执行 16 位的逻辑右移。 .

40000 U2/ . 将打印 20000

但是 40000 2/ . 将打印 -12768

8* ( n1 -- n2 )

通过执行 3 位算术移位实现 n1 乘 8.

7 8* . 将打印 56

这等效于 7 8 * 但是更快

1+ ( n1 -- n2 )

把栈顶元素增 1

1- ( n1 -- n2 )

把栈顶元素减 1

2+ ( n1 -- n2 )

把栈顶元素加 2 .

2- ( n1 -- n2 )

把栈顶元素减 2 。

U/16 ( u -- u/16 )

u 是一个无符号的 16 位整数,通过执行一个 4 位的右移而实现 u 除以 16.

1.6 冒号定义

你可以引用其它的 Forth 字来定义自己的 Forth 字,方法是使用 Forth 字 :( 冒号 ) ,就像下面这样:

: <name> --- --- --- --- ;

其中冒号 : 开始一个定义, <name> 是你自己要定义 Forth 字的名字, --- --- 是组成这个定义字的具体内容,而分号 ; 结束这个定义

下面是一些定义的例子:

如果你不喜欢用点来打印栈顶的值,你可以把它定义成 = = 两个等号,因为一个等号已经是一个 Forth 字了。

注意:上面的左括号'( '是一个 Forth 字,它把从它开始直到右括号之间的内容作为一个注释。因此,它们必须被空格分开,在'(' 之后必须有一个空格。

: = = ( n -- ) \ 打印栈顶值

. ;

打入这个冒号定义,试着再打入 5 7 + = =

: squared ( n – n * * 2 ) \ 计算 n 的平方,方法是一个自身相乘

DUP * ;

可以试着打入 5 squared = =

3 squared = =

7 squared ==

: cubed ( n -- n**3 ) \ 计算 n 的立方

DUP \ n n

squared \ n n**2

* ; \ n**3

以下是两个有用的 Forth 字

CR ( -- ) ( 读作回车 )

在屏幕上产生回车和换行

." ( -- ) ( 读作点引号 )

打印字符串,直到闭括号 "

我们还可以定义下列字

: bar ( -- ) \ 打印一个杠

CR ." *****" ;

: post ( -- ) \ 打印一个位置

CR ." *"

CR ." *" ;

: C ( -- ) \ 打印一个 C

bar post post bar ;

: F ( -- ) \ 打印一个 E

bar post bar post ;

我们看到,新的 Forth 字是用以前的 Forth 字定义而成的。这就是 Forth 的方式。新的、更强大的字被不断地定义,当你完成全部的程序时,程序还是一个字。

你定义的字和预定义的 Forth 字一样被存储在 Forth 字典中。 Forth 解释器不知道你定义的 Forth 字和语言预定义的字两者之间的差异。这就意味着每个 Forth 应用程序实际上都将是一种特殊的语言,这种语言被设计得用来解决你自己的、特殊的问题。

1.7 练习

一个正方形可以用它的左上角 (t l) 和右下角坐标来定义。令 X 坐标从左向右增加, Y 坐标从上向下增加。定义三个 Forth 字: AREA 、 CIRCUM 和 CENTER ,它们将根据给定的顶、左、底、右计算出面积、周长和中心。

AREA ( t l b r -- area )

CIRCUM ( t l b r -- circum )

CENTER ( t l b r -- xc yc )

用下面的给定值来测试你定义的字:

顶 : 31 10

左 : 16 27

底 : 94 215

右 : 69 230

 

 

   

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