本章会介绍Forth在终端,硬盘的输入输出处理。
我们将会讨论硬盘访问 输出操作 字符串操作 输入操作 字符串输入等命令
1 输出运算符
通常使用EMIT
从数字栈获取一个ASCII字符编码,然后在终端输出对应的ASCII字符。比如BASE为十进制的情况下
65 EMIT return-key
输出 A ok
66 EMIT return-key
输出 B ok
TYPE
从数字栈中获取地址和字节数,输出对应内存包含的所有字符。
数字栈参数个数为(addr u --)
。
虽然在数字格式化输出中使用TYPE输出内容是并没有使用到地址和字节数参数,因为可以使用#>
提供
通常我们提供TYPE一个保存字符串内容的内存地址参数,正如以前介绍我们将终端输入缓存存储到以TIB为为开始的内存地址,并且使用#TIB保存终端输入内容的字节数,
TIB 保存着终端输入内容的内存地址
#TIB 保存终端输入内容的字节数地址
因此我们可以使用如下语句打印终端输入内容
TIB #TIB @ TYPE
其中TIB提供地址值,#TIB @
提供输入的字节数
让我们看看."
。在定义语句中,通常."
用来将右边的字符串存储到字典存储中,直到下一个"
终结符。另外还会保存字节的数量在字典的开始位置。测试如下
: TEST ." sample " ;
我们可以打印保存到TEST中的字符串如下
TEST >BODY CELL+ 1+ 7 TYPE
其中的TEST >BODY
给出TEST内容的开始位置,然后CELL+ 1+
移动到字符串开头的s字母的地址,
在曾经的鸡蛋计数中我们使用LABEL。其定义使用了嵌套的IF_THEN语句
我们可以使用TYPE重新定义。首先我们把所有标签存储到一个字符串数组中,如下
: "LABEL" ." REJECT SMALL MEDIUM LARGE XTRA LRGERROR
;(需要注意其中的空格按个数存储到字典中因此使用空格保证每个字符串的相同长度)
可以使用下面的结构指向字符串的开始地址
"LABEL" >BODY CELL+ 1+
然后通过适当的偏移量我们可以输出数组的任何标签,比如我们可以输出第2个标签的内容加上面2x8即16个偏移量。
那么我们可以重新定义LABEL如下
: LABEL 8 * ['] "LABEL" BODY CELL+ 1+ + 8 TYPE SPACE ;
有时候这种字符串数组称为超级字符串。按照命名习惯,超级字符串通常使用引号包围在周围("LABEL")。事实上通常不使用这种方式,而是使用已定义的C"
实现存储如下。
: "LABEL" C" REJECT SMALL MEDIUM LARGE XTRA LRGEROR " ;
: LABEL 8 * "LABEL" 1+ + 8 TYPE SPACE ;
需要注意的是在LABEL获得参数不在0到5的时候会输出错误信息。因此需要对LABEL进行判断。重新定义如下
: LABEL 0 MAX 5 MIN LABEL ;
2 输出到硬盘
在前面我们说过BLOCK会拷贝一个块的数据到可用缓存,然后输出这个地址到数字栈,使用这个地址作为开始,然后使用固定长度,我们可以输出硬盘的内容。比如为了打印第一行可以使用下面的语句
CR 1 BLOCK 64 TYPE return-key
为了打印8行可以这样
CR 1 BLOCK 512 + 64 TYPE
这里面包含一个与TYPE相似的操作wordTRAILING
截取字符串的一部分到
3 字符串移动word
在内存中移动字符串或者数组数据非常容易,只需要使用三个参数,源地址 目标地址和字节数 如下
CMOVE (addr1 addr2 u --) 从addr1开始移动n个字节字符串到addr2,移动方式是byte-by-byte
CMOVE> (addr1 addr2 u --) 从addr1开始移动n个字节字符串到addr2,移动方式是c-by-c
MOVE (addr1 addr2 u --)addr2保存addr1开始的u个字节字符串
对于字符串移动运算符我们需要注意的
源地址在目的地址之前,地址在字节数之前
通常的组织是(source destination count --)
对比CMOVE CMOVE> MOVE的效果如下
210 BLOCK PAD 1024 CMOVE (使用一个byte覆盖)
210 BLCOK PAD 1024 CMOVE> (字符串整个移动)
210 BLOCK PAD 1024 MOVE (cell移动)
移动)
4 单字符输入
另外还可以使用KEY
等待终端键盘上一个字符的输入,然后将输入字符的ASICC值存储到数字栈中。
需要注意的是KEY具有等待键盘输入的功能,因此在输入KEY return-key
后,并没有输出ok,而是移动光标等待输入内容,如果输入字母A,那么会将A的ASCII码65存储到数字栈中
也可在定义中使用KEY。这样定义将会停止运行,等待KEY内容的输入,下面的语句将会等待输入内容
: BOLCKS (count --)
SCR @ + SCR @ DO I LIST KEY DROP LOOP ;
我们可以在其中增加如果输入回车键就停止循环。
31 CONSTANT #EOL
: BLOCKS (count -- )
SCR @ +
SCR @ DO I LIST
KEY #EOL = IF LEAVE THEN
LOOP ;
5 底层的输入命令
在字符串输入中还包含如下几个底层的words
ACCEPT (c-addr u1 -- u2 从终端键盘中接受u个字符然后存储到开始地址处,然后返回接受到的字符数量)
WORD (c -- addr) 从字符输入流中获取一个word,使用参数c作为分割符,然后以字符数位开始和字符串内容存储到HERE的位置,返回存储的地址到数字栈 (截取输入流,存储到内存,返回内存地址)
ACCEPT也可以终止运行等待键盘的输入,期待给出数量的输入或者回车键才会进响应,然后将接受到的字符串存储到给出地址的位置返回接受的字符数量
TIB 80 ACCEPT
等待80个字符的输入,然后存储到TIB(Terminal Input Buffer)。
这个语句会在QUIT的定义中使用用来获取INTERPRET的输入内容
可以假设QUIT中包含如下定义
... TIB 80 ACCEPT #TIV ! INTERPRET ...
那么解释器INTERPRET如何从终端输入流中截取word呢?
通常使用如下语句BL WORD
WORD会扫描输入流使用第一个作为分隔符(BL)。然后将获取的word存储到解释器的缓存中,并且使用第一个个位置保存接受的数量。然后返回保存的地址到数字栈,解释器可以知道去内存查找位置
默认的WORD的缓存地址是HERE。
因此WORD会在终端输入流中查询分割符,然后将截取的字符串存储到WORD的缓存区,其中第一位保存字节数
在终端直接输入WORD,将会从TIB中进行扫描。移动过程中会移动缓存指针>IN
。因此每次执行WORD,都会在输入流中查找下个WORD。另外WORD在>IN @的值大于#TIB@的值时将会停止扫描。
>IN
称为相对指针。并不包含真正的地址而是保持一个真正地址的偏移量,通常以TIB作为起始地址计算,比如扫描STAR,过程中将会保持>IN
值为5
WORD会将字符前面的空格忽略掉。
下面我们使用WORD定义一个TEXT
: TEXT ( delimiter -- ) PAD 258 Bl FILL WORD COUNT PAD SWAP MOVE
TEXT也会在输入流中扫描查找分割符,然后将获取的内容存储到PAD中,最为精彩的是TEXT在移动字符串前使用FILL清空了PAD,非常方便使用TYPE,例如
CREATE my-name 40 ALLOT
: I'M BL TEXT PAD my-name 40 MOVE ;
我们首先定义了一个my-name数组。第二个定义了I'M允许我们输入如下语句I'M EDWARD return-key
在这个定义中首先使用BL截取内容,然后将这个移动到pad中
而PAD my-name 40 MOVE又会移动pad的40字节内容到数组中
我们可以定义下面的GREET
: GREET ." HELLo, " my-name 40 -TRAILING TYPE ." , I Speak Forth. " ;
然而使用空格可能将双字符名字的分割,为了获取完整的输入,我们并不需要在换行前获取分隔符,因此可以使用1 TEXT
这样一来会获取整个输入内容,直到允许的大小
通过使用其他分隔符,我们可以获取一系列的字符串和存储到不同的数组中。
我们讲解的输入内容包括如下 数字(存储到数字栈) 字符串(查找字符)。
如果需要我们可以将ACCEPT放置到定义中我们可以获取任意的输入请求,
6 数字转换
在终端中输入数字后,Forth会自动将其转换为二进制的值存储到数字栈,Forth还允许将字符转换为二进制值存储到内存中
>NUMBER (ud1 c-addr1 u1 -- ud2 c-addr2 u2)
举例说明如下
: PLUS 0. BL WORD COUNT >NUMBER 2DROP DROP + ." = " . ;
PLUS提供了中缀允许格式
2 PLUS 13 return-key
输出= 15 ok
数字2存储到数字栈,然而3会作为字符串读取
可以使用>NUMBER创建特定数字转换规则,然后第一个转换字符的地址,可以判断连字符 点灯内容,可以判断转换后的数字
下面介绍一个接受任意字符的版本,其中: , - . /
返回双字长数字其余返回单字长
其中的WITHIN在前面定义过
Wimbledon可以使用PUNCT包含一个标志位判断是否遇到上面的数字类型判断符号
7 深入理解Word
上面只是介绍了使用WORD扫描终端输入内容,也就是TIB包含的ACCEPT从终端接受的输入内容
如果再次使用解释器调用BL WORD那么我们可以扫描终端输入,字符串,硬盘中的内容
为了实现这个功能通常将WORD使用其他的指针标志>IN。也就是标志内存(EVALUATE) 硬盘(LOAD INCLUDED)或者终端输入流
一个比较有意义的word是COUNT。WORD将接受的长度存储到缓存区,然后返回这个字节的地址到数字栈
而COUNT可以将计数值存储到数字栈然后增加地址值,
COUNT (addr -- addr+1 u) 将地址指向的长度存储到数字栈,然后自增字符地址。
8 字符串比较
COMPARE (c-addr1 u1 c-addr2 u2 -- n)