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
|