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

Forth 语言简明教程

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

第七课 CODE 字和 DOS I/O

7.1 CODE 字

当我们需要最大的执行速度或者需要直接访问计算机硬件时,可以用汇编语言来定义 Forth 字。这需要使用 Forth 字 CODE 来完成。 CODE 的一般格式是:

CODE <name>

<assembly commands>

<return command>

END-CODE

字 CODE 代替冒号定义的冒号,并建立一个 Forth 字的头,END-CODE 代替分号结束 CODE 字的定义。

<assembly commands> 可以用 POSTFIX (后缀)也可以用 PREFIX 前缀格式来编写。我们建议使用 PREFIX 前缀格式,这样汇编语言就和标准的 8086/8088 汇编语言相似了,于是在 CODE 编译之前,需要给出字 PREFIX 。

<return command> 可以是下面这些指令中的任何一个:

NEXT JMP >NEXT ( jumps to the inner interpreter >NEXT )

1PUSH PUSH AX

JMP >NEXT ( pushes ax on the stack and jumps to >NEXT )

2PUSH PUSH DX

PUSH AX ( pushes dx and ax on the stack

JMP >NEXT and then jumps to >NEXT )

调试 CODE 字时可以使用 8088 Tutor monitor, 它包含在本教程中。 Tutor monitor 使用 8086 汇编语言,学习 8088/8086 汇编语言的书可以参看

"IBM PC - 8088 Assembly Language Programming" by Richard E. Haskell.

作为一个使用 Tutor monitor 反汇编和单步执行 CODE 字的例子,可以用 F-PC 3.5 提供的字 CMOVE ,它从地址 <source> 移动 <count> 字节到地址 <dest>,并假设状态寄存器的方向标志是 0 (通过执行 CLD 指令) 所以字符串原语 MOVSB 将自动增量 SI 和 DI.

CODE CMOVE ( source dest count -- )

MOV BX, SI \ save SI (IP)

MOV AX, DS \ copy DS for setting ES

POP CX \ cx = count

POP DI \ di = destination address

POP SI \ si = source address

MOV DX, ES \ save es in dx

MOV ES, AX \ point es to code segment

REPNZ \ repeat until count is zero

MOVSB \ copy DS:SI to ES:DI

MOV SI, BX \ restore si

MOV ES, DX \ restore es

NEXT \ done, jmp to >NEXT

END-CODE

当你装入这个代码后, 16 进制值 11 22 33 44 55 在偏移地址在source.addr的代码段,代码段的实际值由 Forth 字 ?CS: 给出,使用字 show.addrs 可以打印到屏幕上。

在偏移量dest.addr" 地址处保留 5 个字节的空间,当你打入字 show.addrs 后,偏移地址 source.addr, dest.addr, 栈顶元素, CMOCE 的 CFA 也被印到屏幕上。

HEX

CREATE source.addr 11 C, 22 C, 33 C, 44 C, 55 C,

CREATE dest.addr 5 ALLOT

5 CONSTANT #bytes

: test ( -- )

source.addr dest.addr #bytes CMOVE ;

: show.addrs ( -- )

HEX

CR ." code segment = " ?cs: u.

CR ." source addr = " source.addr u.

CR ." dest addr = " dest.addr u.

CR ." top of stack = " SP0 @ U.

CR ." address of CMOVE = " [ ' CMOVE ] LITERAL U.

CR DECIMAL ;

字 [, ] 和 LITERAL 将在第九课讨论。

假设被 "show.addrs" 打印的值如下

code segment = 1091

source addr = 74E0

dest addr = 74E8

top of stack = FFE2

address of CMOVE = 477

你的值可能不同,如果不同,则在下面的练习中你应该使用对应的实际值。

Type debug test.

Type HEX

Type test.

单步通过前三个字,它们将打印下面的堆栈值:

74E0 74E8 5

Press F to go to Forth.

Type SYS TUTOR – 将执行 TUTOR 程序

通过 TUTOR 存储器显示

Type >S1091 to display the code segment.

Type /GS1091 to display the data segment = code segment.

Type /GOFEDC to display the stack starting at the top of the

stack (FEE2) minus 6 in the data segment region. The

value 5 (05 00) should be on top of the stack, followed

by the "source addr" 74E0 (E0 74) and the "dest addr"

74E8 (E8 74).

Type /GO74E0 to display the "source addr" in the data segment.

Note that 11 22 33 44 55 is displayed.

Type >O477 to go to the start of the CMOVE code.

再次按 F1 单步执行前两个指令。注意 SI 的值被移到 BX 而 DS 的值被移到 AX 。下一个指令是 POP CX ,它假设从栈顶弹出了 #bytes (5) 值到 CX 。然而, Tutor 的堆栈指针和堆栈段寄存器并没有指向这些值,我们实际看它们在 1091:FEDC. 你可以改变 SS 和 SP 的值,通过打入 /RSS1091 使堆栈段和代码段相同,打入 /RPSFEDC 使堆栈指针等于栈顶 (FFE2) 减 6 。

接着按 F1 执行 POP CX,不过,你又会遇到一个问题,这就是就如何回到 F-PC 。当你退出 Tutor 时你也许退到了 DOS ,或者也可能计算机挂起了。一个变通的办法是用手工方法装入适当的值 5 到 CX 。输入 /RGC5 ,然后按右光标键跳过指令 POP CX 。

使用同样方法跳过指令 POP DI,通过手工输入 /RID74E8 装入dest addr 。

使用同样方法跳过指令 POP SI,通过手工输入 /RIS74E0 装入source addr。

你可以按两次 F1 执行下面两个指令。

你现在位于 REP 指令,按 F1 。注意到值 11 从数据段地址 74E0 复制到扩展段(它实际上与数据段一致)地址 74E8 ,并且 SI 和 DI 都增加了1. 这是指令 MOVSB 的工作 – 它也只做这些。同时 CX 从 5 减量到 4 。

再按 F1 。注意 22 从 SI 所指示的数据段位置( (74E1) 移动到 DI 所指示的扩展段位置 (74E9) , CX 的值 减量到 3

按 F1 三次则值 33 44 和 55 被移动,注意当 CX 为 0 时, REP 循环终止,下一条指令准备执行。

按 F1 两次,执行下面两条指令。下面的指令是一个 JMP 指令,它跳转到 >NEXT.

要退出 TUTOR, 批入 /QD. 这会返回到你在 Forth 中你打入 sys tutor 命令的地方。打入 <Enter> 返回到调试模式,打一个空格键你就可以回到 Forth 。

Forth 字 CMOVE> ( source dest count -- ) 与 CMOVE 类似,差异是字节按相反的方向移动。也就是说,最高地址的字节先移动。在向上移动字符串时这个功能很有用,因为字符串可能重叠,如果这时使用CMOVE则可能导致源串还没有移动之前就已经被破坏了。

7.2 CODE 条件

当我们使用 Forth 汇编的跳转指令时, Forth 字 IF ... ELSE ... THEN 、 BEGIN ... WHILE ... REPEAT 和 BEGIN ... UNTIL 可以有下列的代码条件

0= JNE/JNZ

0<> JE/JZ

0< JNS

0>= JS

< JNL/JGE

>= JL/JNGE

<= JNLE/JG

> JLE/JNG

U< JNB/JAE/JNC

U>= JB/JNAE/JC

U<= JNBE/JA

U> JBE/JNA

OV JNO

CX<>0 JCX0

作为一个例子,考虑 Forth 字 ?DUP 的定义,它只在堆栈上的值为非 0 时才复制栈顶:

CODE ?DUP ( n -- n n | 0 )

MOV DI, SP

MOV CX, 0 [DI]

CX<>0

IF

PUSH CX

THEN

NEXT

END-CODE

注意当这个定义汇编后,语句 CX<>0 被汇编成 JCX0 放在 THEN.

7.3 长存储器地址字

下面这些长存储器地址字对于访问不在代码段的数据非常有用:

CODE @L ( seg off -- n ) \ Fetch 16-bit value from seg:off

POP BX \ BX = offset address

POP DS \ DS = segment address

MOV AX, 0 [BX] \ AX = data at DS:BX

MOV BX, CS \ Restore DS to CS value

MOV DS, BX

1PUSH \ push value on stack

END-CODE

CODE !L ( n seg off -- ) \ Store 16-bit value at seg:off

POP BX \ BX = offset address

POP DS \ DS = segment address

POP AX \ AX = n

MOV 0 [BX],AX \ Store n at DS:BX

MOV BX, CS \ Restore DS to CS value

MOV DS, BX

NEXT

END-CODE

下面是一些有用的长存储器字:

C@L ( seg off -- byte ) \ Fetch 8-bit byte from seg:off

C!L ( byte seg off -- ) \ Store 8-bit byte at seg:off

CMOVEL ( sseg soff dseg doff count )

\ move a block of count bytes from sseg:soff to dseg:doff

CMOVEL> ( sseg soff dseg doff count )

\ move a block of count bytes from sseg:soff to dseg:doff

\ moves last byte first to avoid overwriting moved data

7.4 DOS 字

F-PC 拥有大量的 Forth 字用于处理 DOS 文件 I/O ,这些字都在源文件 HANDLES.SEQ 和 SEQREAD.SEQ 中定义。本节和下面一节将开发一系列的文件 I/O 字,它们可以让你使用并扩展处理各种文件 I/O 、进行其它 DOS 操作。这些字可以替代或者与 F-PC DOS 和文件 I/O 字联合使用。

VARIABLE ITEMS \ used to record stack depth

VARIABLE handl \ file handle

VARIABLE eof \ TRUE if end-of-file was read

CREATE fname 80 ALLOT \ 80 byte buffer containing ASCII filename

: {{ ( -- )

DEPTH ITEMS ! ;

: }} ( -- c )

DEPTH ITEMS @ - ;

{{ . . . }} 使用追踪放置到堆栈上的元素的数目,例如:

{{ 5 2 8 }}

将把下列值留在堆栈上

5 2 8 3

堆栈上的3是在 {{ 和 }} 之间的元素的数目。

: $>asciiz ( addr1 -- addr2 ) \ change counted string to ASCIIZ string

DUP C@ SWAP 1+

TUCK +

0 SWAP C! ;

DOS 2.0+ 磁盘 I/O 功能

2fdos 调用 DOS INT 21H 功能,并使用堆栈上的 ax =ah:al, bx, cx 和 dx 。它在堆栈上返回 ax, dx 和一个错误标志。如果错误标志为真,则错误代码在 ax 中(堆栈上的第三个元素)。如果错误标志为假,则 ax 和 dx 的值依赖于所调用的功能。

fdos 与 2fdos 相似,但是不返回错误标志,它被用于不使用进位标志来指示错误的功能调用。

PREFIX

HEX

CODE 2fdos ( ax bx cx dx -- ax dx f )

POP DX

POP CX

POP BX

POP AX

INT 21 \ DOS function call

U>=

IF \ if carry = 0

MOV BX, # FALSE \ set error flag to false

ELSE \ else

MOV BX, # TRUE \ set error flag to true

THEN

PUSH AX

PUSH DX

PUSH BX

NEXT

END-CODE

CODE fdos ( ax bx cx dx -- ax dx )

POP DX

POP CX

POP BX

POP AX

INT 21 \ DOS function call

PUSH AX

PUSH DX

NEXT

END-CODE

DECIMAL

7.5 基本的文件 I/O

下面这些字可以用于基本的文件 I/O 操作,比如打开、创建、关闭和删除文件,以及从磁盘文件中读写字节。

open.file ( addr -- handle ff | error.code tf )

打开一个文件。在栈顶的假标志下返回一个句柄,在真标志下返回一个错误代码。 addr 指向一个 asciiz 串,访问码设为 2 用于读写方式读写。

HEX

: open.file ( addr -- handle ff | error.code tf )

3D02 \ ah = 3D; al = access.code=2

0 ROT 0 SWAP \ 3D02 0 0 addr

2fdos \ DOS function call

NIP ; \ nip dx

close.file 关闭一个文件,文件句柄在栈顶,如果不能关闭则打印错误信息。

: close.file ( handle -- )

3E00 \ ah = 3E

SWAP 0 0 \ bx = handle

2fdos

NIP \ nip dx

IF

." Close error number " . ABORT

THEN

DROP ;

create.file 创建文件 – 返回值与 open.file 一样

addr 指向一个 asciiz 串

attr 是文件属性

0 - normal file

01H - read only

02H - hidden

04H - system

08H - volume label

10H - subdirectory

20H – archive

: create.file ( addr attr -- handle ff | error.code tf )

3C00 \ ah = 3C

0 2SWAP SWAP \ 3C00 0 attr addr

2fdos

NIP ; \ nip dx

open/create 在文件存在就打开它,不存在时则创建一个新的一般文件

addr 指向一个 asciiz 串,返回一个打开文件的句柄,如果不能打开则打印一个错误信息。

: open/create ( addr -- handle )

DUP open.file

IF

DUP 2 =

IF

DROP 0 create.file

IF ." Create error no. " . ABORT

THEN

ELSE

." Open error no. " . DROP ABORT

THEN

ELSE

NIP

THEN ;

: delete.file ( addr -- ax ff | error.code tf )

4100

0 ROT 0 SWAP

2fdos

NIP ;

: erase.file ( $addr -- )

$>asciiz

delete.file

IF

CR ." Delete file error no. " .

ELSE

DROP

THEN ;

read.file 从文件 handle 中读出 #bytes 个字节到 buff.addr 缓冲区,返回读入的字节数 #bytes ,如果返回 0 ,则读到了文件尾,如果不成功则打印错误信息。

: read.file ( handle #bytes buff.addr -- #bytes )

>R 3F00 \ handle #bytes 3F00

-ROT R> \ 3F00 handle #bytes addr

2fdos

NIP \ nip dx

IF

." Read error no. " . ABORT

THEN ;

write.file 将 buff.addr' 绘缓冲区的 '#bytes' 个字节写入文件 'handle'. 如果不成功则打印一个错误信息。

: write.file ( handle #bytes buff.addr -- )

>R 4000 \ handle #bytes 4000

-ROT R> \ 4000 handle #bytes addr

2fdos

NIP \ nip dx

IF

." Write error no. " . ABORT

ELSE

DROP

THEN ;

mov.ptr 移动文件 handle 的文件读写指针,doffset 是一个双精度 32 位偏移量,code 是方式代码,其意义如下:

0 – 移动文件指针到文件开始 + offset 处

1 – 用 offset 增量指针

2 - 移动文件指针到文件尾 + offset 处

: mov.ptr ( handle doffset code -- dptr )

42 FLIP + \ hndl offL offH 42cd

ROT >R \ hndl offH 42cd

-ROT R> \ 42cd hndl offH offL

2fdos

IF

DROP ." Move pointer error no. " . ABORT

THEN ;

rewind.file 移动文件 handle 的读写指针到文件开始处

: rewind.file ( handle -- )

0 0 0 mov.ptr 2DROP ;

get.length 返回文件 handle 的 32 位字节长度

: get.length ( handle -- dlength )

0 0 2 mov.ptr ;

read.file.L 从已经打开的文件 handle 中读出 #bytes 字节到扩展存储器 seg:offset 处

CODE read.file.L ( handle #bytes seg offset -- ax f )

POP DX

POP DS

POP CX

POP BX

MOV AH, # 3F

INT 21

U>=

IF

MOV BX, # FALSE

ELSE

MOV BX, # TRUE

THEN

MOV CX, CS \ restore DS

MOV DS, CX

PUSH AX

PUSH BX

NEXT

END-CODE

write.file.L 写 #bytes 个字节到一个打开的文件 handle 中,要写入的数据在扩展存储器 seg:offset 处。

CODE write.file.L ( handle #bytes seg offset -- ax f )

POP DX

POP DS

POP CX

POP BX

MOV AH, # 40

INT 21

U>=

IF

MOV BX, # FALSE

ELSE

MOV BX, # TRUE

THEN

MOV CX, CS \ restore DS

MOV DS, CX

PUSH AX

PUSH BX

NEXT

END-CODE

findfirst.dir 查找文件目录的第一个匹配,文件指示符位于 addr 的 asciiz 串。

CODE findfirst.dir ( addr -- f ) \ search directory for first match

POP DX \ dx = addr of asciiz string

PUSH DS \ save ds

MOV AX, CS

MOV DS, AX \ ds = cs

MOV CX, # 10 \ attr includes subdirectories

MOV AX, # 4E00 \ ah = 4E

INT 21 \ DOS function call

JC 1 $ \ if no error

MOV AX, # FF \ flag = TRUE

JMP 2 $ \ else

1 $: MOV AX, # 0 \ flag = FALSE

2 $: POP DS \ restore ds

PUSH AX \ push flag on stack

NEXT

END-CODE

findnext.dir 查找文件目录的下一个匹配,文件描述在 addr 处

CODE findnext.dir ( -- f ) \ search directory for next match

PUSH DS \ save ds

MOV AX, CS

MOV DS, AX \ ds = cs

MOV AX, # 4F00 \ ah = 4F

INT 21 \ DOS function call

JC 1 $ \ if no error

MOV AX, # FF \ flag = TRUE

JMP 2 $ \ else

1 $: MOV AX, # 0 \ flag = FALSE

2 $: POP DS \ restore ds

PUSH AX \ push flag on stack

NEXT

END-CODE

set-dta.dir 设置磁盘传输区 DTA 地址

CODE set-dta.dir ( addr -- ) \ set disk transfer area address

POP DX \ dx = dta address

PUSH DS \ save ds

MOV AX, CS

MOV DS, AX \ ds = cs

MOV AX, # 1A00 \ ah = 1A

INT 21 \ DOS function call

POP DS \ restore ds

NEXT

END-CODE

DECIMAL

7.6 读入数和字符串

下面的字可以用于从磁盘文件中读入字节、数和串。

get.fn 从键盘输入一个文件名并作为一个 asciiz 串存放 fname 中。

: get.fn ( -- )

QUERY BL WORD \ addr

DUP C@ 1+ \ addr cnt+1

2DUP + \ addr len addr.end

0 SWAP C! \ make asciiz string

SWAP 1+ SWAP \ addr+1 len

fname SWAP \ from to len

CMOVE ;

open.filename 输入一个文件名,打开这个文件,将文件句柄存入变量 handl 中。

: open.filename ( -- )

get.fn

fname open/create

handl ! ;

eof? 如果读到了一个文件结束符(eof = true),则退出包含 eof? 的这个字。

: eof? ( -- )

eof @

IF

2R> 2DROP EXIT

THEN ;

get.next.byte 从磁盘文件中得下一个字节,文件句柄在 handl 中,如果是 eof 则设置变量 eof 为真。

: get.next.byte ( -- byte )

handl @ 1 PAD read.file

IF

FALSE eof ! PAD C@

ELSE

TRUE eof !

THEN ;

get.next.val 从文件中读出下一个字的值(2 字节),文件句柄在 handl 中,如果到达文件尾则设置变量 eof 为真,如果文件中存储的不是 ASCII 码而是实际的数则这个字就非常有用。

: get.next.val ( -- n )

handl @ 2 PAD read.file

IF

FALSE eof ! PAD @

ELSE

TRUE eof !

THEN ;

get.next.dval 从磁盘文件中读入 32 位的值(4 字节),文件句柄在 handl 中。如果文件结束则则设置 eof 变量为真,如果文件中存储的不是 ASCII 码而是实际的数则这个字就非常有用。

: get.next.dval ( -- d )

handl @ 4 PAD read.file

IF

FALSE eof ! PAD 2@

ELSE

TRUE eof !

THEN ;

parenchk 如果栈上是一个 '(' 则读文件直到字符 ')' 被读入。如果 eof 则退出。

: parenchk ( byte -- byte )

DUP ASCII ( =

IF

DROP

BEGIN

get.next.byte eof?

ASCII ) =

UNTIL

get.next.byte eof?

THEN ;

quotechk 如果堆栈上的字节是引号 (") ,读入文件直到字节 " 被读入。如果读到 eof 则退出。

: quotechk ( byte -- byte )

DUP ASCII " =

IF

DROP

BEGIN

get.next.byte eof?

ASCII " =

UNTIL

get.next.byte eof?

THEN ;

?digit 检查堆栈上的字节是不是一个对应当前数基的 ASCII 码。

: ?digit ( byte -- byte f )

DUP BASE @ DIGIT NIP ;

get.next.digit 从磁盘文件中得到一个合法的 ASCII 数字,如果读到 eof 则退出。

: get.next.digit ( -- digit )

BEGIN

get.next.byte eof?

parenchk eof?

quotechk eof?

?digit NOT

WHILE

DROP

REPEAT ;

get.digit/minus 从磁盘文件中得到一个合法的 ASCII 数字或者一个减号,如果读到 eof 则退出。

: get.digit/minus ( -- digit or - )

BEGIN

get.next.byte eof?

parenchk eof?

quotechk eof?

DUP ASCII - =

SWAP ?digit ROT OR NOT

WHILE

DROP

REPEAT ;

get.next.number 从磁盘文件中读入一个以 ASCII 串存储的有符号数,并把它转换成一个有符号的 16 位整数,如果读到 eof 则退出。

: get.next.number ( -- n )

{{ get.digit/minus eof? \ uses {{ }} to store

BEGIN \ consecutive digits

get.next.byte eof? \ on the stack.

parenchk eof? \ ignore (...)

quotechk eof? \ and "..."

?digit NOT

UNTIL

DROP }}

DUP PAD C!

DUP PAD + BL OVER 1+ C!

SWAP 0 DO \ move digits on stack

SWAP OVER C! 1- \ to counted string as PAD

LOOP

NUMBER DROP ; \ convert to number

?period 测试一个字节是不是一个小数点。注意标志作为为次栈顶元素。

: ?period ( byte -- f byte )

DUP ASCII . = SWAP ;

get.next.dnumber 从磁盘文件中读入一个以 ASCII 串存储的有符号实数,并把它转换成一个有符号双精度数放到堆栈上,小数点之后的数字数目放到变量 DPL 中,如果读到 eof 则退出。

: get.next.dnumber ( -- dn )

{{ get.digit/minus eof?

BEGIN

get.next.byte eof?

parenchk eof? \ similar to

quotechk eof? \ get.next.number

?period \ but include period

?digit ROT OR NOT \ in number string

UNTIL

DROP }}

DUP PAD C!

DUP PAD + BL OVER 1+ C!

SWAP 0 DO

SWAP OVER C! 1-

LOOP

NUMBER ; \ convert to double number

get.next.string 从磁盘文件中读入包含在引号中的字符串,并把它存储成位于 addr 地址处的一个计数串。

: get.next.string ( -- addr ) \ counted string

BEGIN

get.next.byte eof?

ASCII " =

UNTIL

0 PAD 1+

BEGIN \ cnt addr

get.next.byte eof?

DUP ASCII " <>

WHILE

OVER C!

SWAP 1+ SWAP

1+

REPEAT

2DROP PAD C! PAD ;

7.7 数字和串

send.byte 输入一个字节到打开的文件中,文件的句柄在 handl 中。

: send.byte ( byte -- )

PAD C!

handl @

1 PAD write.file ;

send.number 把一个有符号的 16 位数字作为一个 ASCII 串写入打开的文件中,文件的句柄在 handl 中。

: send.number ( n -- )

(.) 0

DO

DUP C@ send.byte

1+

LOOP

DROP ;

send.number.r 把一个有符号 16 位数作为一个 ASCII 串写入一个打开的文件中,这个数字将被右对齐到一个宽度为 len 的字段中,并用 ASCII 空格填充。

: send.number.r ( n l -- )

>R (.) R>

OVER -

0 DO

BL send.byte

LOOP

0 DO

DUP C@ send.byte 1+

LOOP

DROP ;

send.dnumber 把一个有符号的 32 位数作为一个 ASCII 串写入打开的文件中,文件的句柄在 handl,小数点的位置由 DPL 的内容决定。

: send.dnumber ( d -- ) \ DPL = #digits after dec. point

TUCK DABS <# DPL @ ?DUP

IF

0 DO # LOOP

ASCII . HOLD

THEN

#S ROT SIGN #>

0 DO

DUP C@ send.byte 1+

LOOP DROP ;

 

: send.val ( n -- ) \ send 16-bit value

PAD ! handl @

2 PAD write.file ;

: send.dval ( d -- ) \ send 32-bit value

PAD 2! handl @

4 PAD write.file ;

: send.string ( addr -- ) \ addr of counted string

DUP C@

SWAP 1+ SWAP

0 DO

DUP I + C@

send.byte

LOOP

DROP ;

: send.crlf ( -- )

13 send.byte

10 send.byte ;

: send.lf ( -- )

10 send.byte ;

: send.cr ( -- )

13 send.byte ;

: send.tab ( -- )

9 send.byte ;

: send.( ( -- )

ASCII ( send.byte ;

: send.) ( -- )

ASCII ) send.byte ;

: send., ( -- )

ASCII , send.byte ;

: send." ( -- )

ASCII " send.byte ;

: send."string" ( addr -- )

send."

send.string

send." ;

 

 

   

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