\       Lesson 7 - Code Words and DOS I/O

\       The Forth Course

\       by Richard E. Haskell

\          Dept. of Computer Science and Engineering

\          Oakland University, Rochester, MI 48309

comment:

 

 

                                Lesson 7

 

                        CODE WORDS AND DOS I/O

 

                7.1  CODE WORDS   ........................................................................  7-2

                7.2  CODE CONDITIONALS ............................................................  7-5

                7.3  LONG MEMORY WORDS  ........................................................  7-6

                7.4  DOS WORDS  .............................................................................  7-7

                7.5  BASIC FILE I/O  .........................................................................  7-9

                7.6  READING NUMBERS AND STRINGS ......................................  7-14

                7.7  WRITING NUMBERS AND STRINGS .......................................  7-20

 

 

 

 


7.1  CODE WORDS

Assembly language instructions can be used to define Forth words when the maximum speed of execution is needed or when direct access to the computers hardware is required.  This is accomplished by using the CODE word to define a Forth word.  The general form of the CODE word is as follows:

        CODE <name>

                <assembly commands>

                <return command>

                END-CODE

 

The word CODE takes the place of the colon in a colon definition and builds a header for the name of the Forth word <name>.  The word END-CODE takes the place of the semi-colon and ends the code word definition.

The <assembly commands> can be written in either POSTFIX or PREFIX notation.  We recommend PREFIX which makes the assembly language look very much like standard 8086/8088 assembly language.  The Forth word PREFIX needs to be executed before the CODE word is compiled.

        The <return command> can be any of the following:

        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 )

 

Debugging CODE words is made easier using the 8088 Tutor monitor that is included with this Forth Course.  A complete description of how to use the Tutor monitor in the process of learning 8088/8086 assembly language is given in the book

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

        Instructions for ordering the book are given when you run the program.

 

 

As an example of using the Tutor monitor to disassemble and single step through a CODE word consider the following F-PC 3.5 definition of the Forth word CMOVE that moves a string of <count> bytes from the address <source> to the address <dest>.  It is assumed that the direction flag in the status register is zero (by having executed CLD) so that the string primitive MOVSB will automatically increment the values of SI and 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

When you FLOAD this lesson the following Forth code will store the hex values 11 22 33 44 55 at the offset address "source.addr" in the code segment.  The actual value of the code segment is given by the Forth word ?CS: and will be printed on the screen when you type the word "show.addrs".

A five byte space is reserved at the offset address "dest.addr".  The offset addresses for "source.addr", "dest.addr", the top of the stack, and the CFA of CMOVE will also be printed on the screen when you type "show.addrs".

comment;

 

        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 ;

 

comment:

        The words [, ] and LITERAL will be discussed in Lesson 9.

 

Assume the values printed when you type "show.addrs" are the following: 

        code segment = 1091

        source addr = 74E0

        dest addr = 74E8

        top of stack = FFE2

        address of CMOVE = 477

 

Your values may be different.  If they are, use your corresponding values in the following exercise.

        Type debug test.

        Type HEX

        Type test.

        Step through the first three word which will put the following

        values on the stack:

                        74E0 74E8 5

        Press F to go to Forth.

        Type SYS TUTOR - This will execute the TUTOR program

        From the TUTOR memory display

        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.

 

Single step the first two instructions by pressing key F1 twice.  Note that the value of SI was moved to BX and the value of DS was moved to AX.  The next instruction is POP CX which is suppose to pop the value of #bytes (5) from the top of the stack into CX.  However, Tutor's stack pointer and stack segment register are not pointing to these values that we saw are really at 1091:FEDC.  You could change the values of SS and SP to these values by typing /RSS1091 to make the stack segment the same as the code segment and then typing /RPSFEDC to set the stack pointer equal to the top of stack (FFE2) minus 6.  You could then execute POP CX by pressing F1.  However, you will then have trouble getting back to F-PC.  When you exit Tutor you may go back to DOS or your computer may hang up.  An alternative is to load CX with the proper value of 5 by hand.  To do this, type /RGC5.  Then skip over the instruction POP CX by just pressing the right cursor arrow. 

Skip over the next instruction POP DI the same way and load the value of "dest addr" by hand by typing /RID74E8.

Skip over the next instruction POP SI the same way and load the value of "source addr" by hand by typing /RIS74E0.

You can execute the next two instructions by pressing F1 twice. 

You should now be at the REP instruction.  Press F1.  Note that the value 11 was copied from address 74E0 in the data  segment to address 74E8 in the extra segment (the same as the data segment) and SI and DI were incremented by 1.  This is what the instruction MOVSB does -- and it just did it once.  Note also that CX was decrement from 5 to 4.

Press F1 again.  Note that the 22 was moved from where SI is pointing in the data segment (74E1) to where DI is pointing in the extra segment (74E9).  The value of CX is also decremented to 3. 

Press F1 three more times and watch the 33 44 and 55 get moved.  Note that when CX gets to zero the REP loop is terminated and the next instruction  MOV  SI, BX  is about to be executed.  

Press F1 two more times to execute the next two instructions. The next instruction is a JMP instruction.  This instruction jumps to >NEXT. 

To exit TUTOR, type /QD.  This should take you back to Forth where you had typed "sys tutor".  Press <Enter> to get back to the debug mode and then press the space bar until you get back to Forth. 

The Forth word CMOVE> ( source dest count -- ) is similar to CMOVE except that the bytes are moved in the opposite direction.  That is, the highest address byte is moved first.  It is necessary to use this word if you are moving a string up in memory where the destination string may overlap the source string.  The use of CMOVE will cause the overlapped portion of the source string to be destroyed before it has a chance to be moved.

 

 

¡@

 

 


7.2  CODE CONDITIONALS

When using the Forth assembler jump instructions are achieved by using the Forth words IF...ELSE...THEN, BEGIN...WHILE...REPEAT, and BEGIN...UNTIL together with the following code conditionals:

 

                Forth           Assembled Code

 

                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

 

As an example, consider the definition of the Forth word ?DUP which duplicates the value on top of the stack only if the value is non-zero.

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

                        MOV     DI, SP

                        MOV     CX, 0 [DI]

                        CX<>0

                        IF

                           PUSH CX

                        THEN

                        NEXT

                        END-CODE

 

Note that when this definition gets assembled into machine code the statement CX<>0 is assembled as JCX0 to the instruction following THEN.

 

 

 

 

 

 

 

 

 


7.3  LONG MEMORY WORDS

The following long memory words are useful for accessing data in segments other than the code segment.

        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

 

        The following are other useful long memory words:

¡@

        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 WORDS

F-PC has a large number of Forth words for handling DOS file I/O.  These words are defined in the source files HANDLES.SEQ and SEQREAD.SEQ.  In this and the next section we will develop a set of file I/O words that you can use and extend to handle a variety of file I/O and other DOS operations.  These words can be used in place of, or in conjunction with, the F-PC DOS and file I/O words.

comment;

 

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 @ - ;

 

comment:

 

{{ . . . }}       Used to keep track of the number of elements

                put on the stack.  For example,

 

                        {{ 5 2 8 }}

 

                will leave the following values

                on the top of the stack:

 

                        5 2 8 3

 

                The 3 on top of the stack is the number of

                items entered between {{ and }}.

comment;

 

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

                DUP C@ SWAP 1+

                TUCK +

                0 SWAP C! ;

 

 

 

 

 

 

 

 

\ DOS 2.0+ disk I/O functions

¡@

comment:  ----------------------------------------------------------

2fdos   calls the DOS INT 21H function with ax=ah:al,

        bx, cx and dx on the stack.  It returns ax, dx

        and an error flag on the stack.  If the error flag

        is TRUE, the error code is in ax (3rd element on the

        stack).  If the error flag is FALSE, then ax and dx

        will have values that depend on the function call.

¡@

fdos    is similar to 2fdos, but does not return an error

        flag.  It should be used for DOS INT 21H calls that

        do not use the carry flag to indicate an error.

*******************************************************************

comment;

 

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

¡@

¡@

¡@

¡@

¡@

comment:

¡@

7.5  BASIC FILE I/O

 

The following words can be used for basic file I/O operations such as opening, creating, closing and deleting files, as well as reading and writing bytes from and to the disk file.

 

                -----------------------------------------------------

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

                Opens a file. Returns handle under a false flag

                or returns error code under a true flag.

                addr points to an asciiz string.

                Access code is set to 2 to open for reading and writing.

comment;

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

¡@

comment:        -----------------------------------------------------

close.file      Closes file whose handle is on the stack.

                Prints error message if unable to close.

comment;

¡@

: close.file    ( handle -- )

                3E00                    \ ah = 3E

                SWAP 0 0                \ bx = handle

                2fdos

                NIP                     \ nip dx

                IF

                   ." Close error number " . ABORT

                THEN

                DROP ;

¡@

comment:        -----------------------------------------------------

create.file     Creates file -- returns values as in open.file

                addr points to an asciiz string

                attr is the file attribute: 0 - normal file

                 01H - read only        02H - hidden

                 04H - system           08H - volume label

                 10H - subdirectory     20H - archive

comment;

¡@

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

                3C00                    \ ah = 3C

                0 2SWAP SWAP            \ 3C00 0 attr addr

                2fdos

                NIP ;                   \ nip dx

¡@

¡@

comment:        ------------------------------------------------------

open/create     Opens a file if it exists,

                otherwise creates a new normal file.

                "addr" points to an asciiz string.

                Returns a handle for the opened file.

                Prints error messages if unable to open.

comment;

¡@

: 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 -- )    \ erase file with counted string at $addr

                $>asciiz

                delete.file

                IF

                   CR ." Delete file error no. " .

                ELSE

                   DROP

                THEN ;

¡@

comment:        -----------------------------------------------------

read.file       Reads '#bytes' bytes from file with 'handle'

                into buffer at 'buff.addr'.  Returns #bytes

                actually read.  If this value is 0 then the

                end of file was read.  Prints error message

                if unsuccessful.

comment;

¡@

: 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 ;

¡@

comment:        -----------------------------------------------------

write.file      Writes '#bytes' bytes from buffer at 'buff.addr'

                to file with 'handle'.  Prints error message

                if unsuccessful.

comment;

¡@

: 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 ;

¡@

comment:        -------------------------------------------------------

mov.ptr         Moves the file pointer of the file with 'handle'.

                doffset is a double number (32-bit) offset

                code is the method code:

                0 - move pointer to start of file + offset

                1 - increase pointer by offset

                2 - move pointer to end of file + offset

comment;

¡@

: 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 ;

¡@

comment:        -------------------------------------------------------

rewind.file     Moves the pointer of file with 'handle'

                to the start of file.

comment;

¡@

: rewind.file   ( handle -- )

                0 0 0 mov.ptr 2DROP ;

¡@

comment:        -------------------------------------------------------

get.length      Returns the 32-bit length of the file with

                'handle'.

comment;

¡@

: get.length    ( handle -- dlength )

                0 0 2 mov.ptr ;

¡@

 

 

 

 

comment:        -------------------------------------------------------

read.file.L     Reads the next "#bytes" bytes from the opened file

                with handle "handle" and stores these bytes in

                extended memory at seg:offset.

comment;

¡@

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

¡@

comment:        -------------------------------------------------------

write.file.L    Writes "#bytes" bytes from extended memory at

                seg:offset to the opened file with handle "handle".

comment;

¡@

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

¡@

¡@

¡@

¡@

¡@

comment:        -------------------------------------------------------

findfirst.dir   Search the directory for the first match of the

                file specified by the asciiz string at "addr".

comment;

¡@

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

¡@

comment:        -------------------------------------------------------

findnext.dir    Search the directory for the next match of the

                file specified by the asciiz string at "addr".

comment;

¡@

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

comment:        -------------------------------------------------------

set-dta.dir     Set the disk transfer area address.

comment;

¡@

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

¡@

comment:

¡@

7.6  READING NUMBERS AND STRINGS

¡@

                The following words can be used to read bytes, numbers

                and strings from a disk file.

¡@

                ------------------------------------------------------

get.fn          enter a filename from the keyboard and

                store it as an asciiz string in fname.

comment;

¡@

: 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 ;

¡@

comment:        ------------------------------------------------------

open.filename   Enter a filename, open it, and store its

                handle in the variable 'handl'.

comment;

¡@

: open.filename         ( -- )

                get.fn

                fname open/create

                handl ! ;

¡@

comment:        ------------------------------------------------------

eof?            If an end-of-file was read (eof = true)

                then exit word containing eof?.

comment;

¡@

: eof?          ( -- )

                eof @

                IF

                   2R> 2DROP EXIT

                THEN ;

¡@

¡@

 

 

 

 

 

comment:        -------------------------------------------------------

get.next.byte   Get the next byte from the disk file

                whose handle is in 'handl'.

                Sets eof variable to true if eof.

comment;

¡@

: get.next.byte         ( -- byte )

                handl @ 1 PAD read.file

                IF

                   FALSE eof ! PAD C@

                ELSE

                   TRUE eof !

                THEN ;

¡@

comment:        -------------------------------------------------------

get.next.val    Read the next 16-bit value (2 bytes) from the

                disk file whose handle is in 'handl'.

                Sets eof variable to true if eof.

                Useful if actual numbers, rather than ASCII data,

                is stored on the disk file.

comment;

¡@

: get.next.val          ( -- n )

                handl @ 2 PAD read.file

                IF

                   FALSE eof ! PAD @

                ELSE

                   TRUE eof !

                THEN ;

¡@

comment:        -------------------------------------------------------

get.next.dval   Read the next 32-bit value (4 bytes) from the

                disk file whose handle is in 'handl'.

                Sets eof variable to true if eof.

                Useful if actual numbers, rather than ASCII data,

                is stored on the disk file.

comment;

¡@

: get.next.dval          ( -- d )

                handl @ 4 PAD read.file

                IF

                   FALSE eof ! PAD 2@

                ELSE

                   TRUE eof !

                THEN ;

¡@

¡@

¡@

¡@

¡@

¡@

¡@

¡@

comment:        -------------------------------------------------------

parenchk        If the byte on the stack is a '('

                read the file until the byte following

                the next ')' is read.

                Exits if eof is read.

comment;

¡@

: parenchk      ( byte -- byte )

                DUP ASCII ( =

                IF

                   DROP

                   BEGIN

                      get.next.byte eof?

                      ASCII ) =

                   UNTIL

                   get.next.byte eof?

                THEN ;

¡@

comment:        -------------------------------------------------------

quotechk        If the byte on the stack is a quote (")

                read the file until the byte following

                the next quote (") is read.

                Exits if eof is read.

comment;

¡@

: quotechk      ( byte -- byte )

                DUP ASCII " =

                IF

                   DROP

                   BEGIN

                      get.next.byte eof?

                      ASCII " =

                   UNTIL

                   get.next.byte eof?

                THEN ;

¡@

comment:        ------------------------------------------------

?digit          Checks to see if the byte on the stack

                is the ASCII code of a valid digit in

                the current base.

comment;

¡@

: ?digit        ( byte -- byte f )

                DUP BASE @ DIGIT NIP ;

¡@

¡@

¡@

¡@

¡@

¡@

¡@

¡@

comment:        ------------------------------------------------

get.next.digit  Gets the next valid ASCII digit

                from the disk file.

                Exits if eof is read.

comment;

¡@

: get.next.digit        ( -- digit )

                BEGIN

                   get.next.byte eof?

                   parenchk eof?

                   quotechk eof?

                   ?digit NOT

                WHILE

                   DROP

                REPEAT ;

¡@

comment:        ------------------------------------------------

get.digit/minus   Gets the next valid ASCII digit

                  or a minus sign from the disk file.

                  Exits if eof is read.

comment;

¡@

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

                BEGIN

                   get.next.byte eof?

                   parenchk eof?

                   quotechk eof?

                   DUP ASCII - =

                   SWAP ?digit ROT OR NOT

                WHILE

                   DROP

                REPEAT ;

¡@

comment:        ---------------------------------------------------

get.next.number   gets the next signed integer stored

                  as an ASCII string on the disk and

                  converts it to a signed 16-bit integer.

                  exits if eof is read.

comment;

¡@

: 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

¡@

comment:        ----------------------------------------------------

?period         Checks to see if a byte is a period.

                Note that the flag is left as the

                second element on the stack.

comment;

¡@

: ?period       ( byte -- f byte )

                DUP ASCII . = SWAP ;

¡@

comment:        ----------------------------------------------------

get.next.dnumber        Gets the next signed real number stored

                        as an ASCII string on the disk and

                        converts it to a signed double

                        number on the stack.

                        The number of digits after the decimal

                        point is stored in the variable DPL.

                        Exits if eof is read.

comment;

¡@

: 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

¡@

¡@

¡@

¡@

¡@

¡@

¡@

¡@

comment:        ----------------------------------------------------

get.next.string         Reads the next string enclosed between

                        double quotes "....." in the disk file

                        and stores it as a counted string at "addr".

comment;

¡@

: 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 ;

¡@

¡@

¡@

¡@

 

 

 

 

 

 

 

 

 

comment:

 

7.7  WRITING NUMBERS AND STRINGS

 

                ---------------------------------------------------

send.byte       Sends a byte to the opened disk file

                whose handle is in 'handl'.

comment;

¡@

: send.byte     ( byte -- )

                PAD C!

                handl @

                1 PAD write.file ;

¡@

comment:        ---------------------------------------------------

send.number     Sends a signed 16-bit number as an

                ASCII string to the opened disk file

                whose handle is in 'handl'.

comment;

¡@

: send.number   ( n -- )

                (.) 0

                DO

                   DUP C@ send.byte

                   1+

                LOOP

                DROP ;

¡@

comment:        ---------------------------------------------------

send.number.r   Sends a signed 16-bit number as an

                ASCII string to the opened disk file

                whose handle is in 'handl'.

                The number will be right-justified in a

                field of width "len", padded with leading

                ascii blanks.

comment;

¡@

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

                >R (.) R>

                OVER -

                0 DO

                   BL send.byte

                LOOP

                0 DO

                   DUP C@ send.byte 1+

                LOOP

                DROP ;

¡@

¡@

¡@

¡@

¡@

¡@

comment:        ---------------------------------------------------

send.dnumber    Sends a signed 32-bit number as an

                ASCII string to the opened disk file

                whose handle is in 'handl'.

                The decimal point is positioned according

                to the contents of DPL.

comment;

¡@

: 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." ;