# The Forth Column

  


## Update on Forth in the Industry

This last year has been a busy one for Forth. I like to believe that the "Forth year" starts and ends with FORML (Forth Modification Laboratory) at Asilomar in Northern California over the Thanksgiving weekend. I'll give you a complete report of this year's FORML (87) in the next column. Meanwhile, here's what's happened since the last FORML (86). I apologize in advance for missing anything. 

The Novix 4000 (renumbered to 4016) continued rising in popularity. Both Novix and Software Composers released Novix boards for the IBM PC. The Novix NB4100 contains the NC4016 with B and X ports wired to board-mounted connectors and 128K of program and data memory. The Software Composer PC4000 has 500K of memory on-board and can run in parallel with other PC4000 boards. Each company offers Forth development systems as well as C-to-Forth translation programs. Novix also announced the NB4300 STE-bus Novix card. (An STE-bus Novix card is available from Forth Systeme, W. Germany.) 

Harris Semiconductor announced FORCE (Forth-optimized RISC computing engine), a modified NC4016 design copied into its macro cell library. It expects to offer a FORCE-based, real-time control processor (RTCP) in the first quarter of 1988. 

Phil Koopman demonstrated his WISC (writable instruction set, stackoriented computer) CPU/32, a modular hardware Forth engine sold by Mountain View Press. The team from Johns Hopkins University demonstrated its Forth engine a 32-bit processor with cached stacks and Gbytes of address space. The team expects it to be generally available sometime in 1988. 

Meanwhile, Forth continued to move into newer and more challenging hardware environments. Dr. C.H. Ting implemented a Novix-based micro-code sequencer for NCR's GAPP computer. Goddard Space Lab (Maryland) implemented Forth on its massively parallel processor, a two-dimensional array of 128 X 128 serial processors. FORTH Inc. announced a polyFORTH for the Texas Instruments TMS32020/C25 digital signal processor (DSP). Forth is currently the only high-level language running directly on this popular DSP chip. 

FORTH Inc. also implemented a nicely optimized Intel 80386 polyFORTH running in native mode. The company reports that Forth benchmarks run faster on this machine than they do on the Motorola 68020. Meanwhile, Laboratory Microsystems demonstrated its new UR/FORTH running on the 80286 and 80386 under Microsoft's OS/2. 

Forth vendors were busy last year applying their tools to a variety of projects. Miller Microcomputer Services put the finishing touches on MMS Forth 2.4, the MS-DOS version of Forth used to develop RapidFile, Ashton-Tate's new query-by-example database. The new book Business Programming with RapidFile was written by our own Leo Brodie, whose improved Starting Forth, second edition, was also published last year. 

Creative Solutions turned its talents inward to develop new hardware technology. The result was a series of boards for Apple's Macintosh II NuBus. Three of these Hurdler boards connect the Mac II to three other buses. the STD bus, the Motorola I/O channel, and the IBM PC bus. CSI also announced the Mac II Toolkit, which adds a 68020 assembler, 6881 support, and color graphics as extensions to its popular MacFORTH. 

FORTH Inc. spent a busy year working with several Fortune 500 companies. The company assisted in developing a package-tracking magic wand for a major mailing company and renovated the American Airlines baggage system, a polyFORTH system that has been in place for the past five years. It expanded Bell Canada's data entry system (70-80 users on one VAX VMS with no loss of response) and demonstrated a large factory heating ventilation and air-conditioning system using the ClusterFORTH distributed intelligence network. 

Advance MicroMotion developed an economy Telex and EasyLink communications package for TTS. Mountain View Press brought out new MVP Forths for the Amiga, the Atari 600/800/1200, and the PDP-11. New Micros announced a hardware and software development system for the Motorola 68HC11, and Inner Access Corp. announced its development system for the Zilog SUPER8 chip. 

Besides RapidFile, two other major Forth products appeared. Electronic Arts' STARFLIGHT game took several programmers years of effort (see Forth Dimensions, July 1987). Frog Peak Music (P.O. Box 9911, Oakland, CA 94613) brought out the Hierarchical Music Specification language, which lets you write your own synthesizer MIDI driver and edit waveforms, durations, and envelopes as you play. 

Last year also saw the birth of a new Forth computer, sort of. The Canon Cat (Byte, October 1987) is Jef Raskin's first new machine since he left Apple, where he headed the original Macintosh team. The Cat has a 9-inch mono display, 3.5-inch drive, and a Motorola 68000 CPU. According to Ezra Shapiro, "If the highlighted text [on the screen] is a computer program written in either Forth or 68000 assembly language, the Cat executes it." 

And of course, the ANSI CBEMA X3J14 Forth standardization effort began last year. I'll report on the second meeting of this committee in the next column. Also, there are two new Forth electronic bulletin boards: the FIG-sponsored GENie board (see DDJ, December 1987) and the new North Coast Forth Board (Minnesota [612] 483-6711). Now there are Forth boards north, east, and west. South Coast Forth Board anyone? 

### Forth in AI

Forth continued to make inroads into artificial intelligence. Henry Harris (JPL) presented papers on conceptual dependency; William Dress (Oak Ridge National Lab) presented several on neural nets. Two implementations of OPS5 and two of "fuzzy" rule production systems were developed last year. Jack Park's popular Expert 2 system evolved to Expert 5. For a general summary of Forth's recent history in AI, see "The Forth Wave in AI" by Robert Trelease in Al Expert (October 1987). 

Part of Forth's popularity in AI is the ease in which it can be implemented on experimental computer architectures. This makes Forth particularly attractive for simulating, controlling, and testing inference engines and neural networks. Once Forth is ported to a new hardware environment, it is possible to implement additional languages, such as BASIC, LISP, and PROLOG, rapidly by writing them in Forth. Panasonic, for example, implemented BASIC this way on the original HHC (handheld computer). Charles Duff's object-oriented NEON and Actor were both written as extensions to Forth, too. 

By the way, a tiny Modula-2 has just been written in Forth by S. Lohr. You can download it from the East Coast Forth Board ([703] 442-8695) or from the new GENie Forth Board as the file TM2ARC. You will find an interesting discussion of language bootstrapping techniques in "Embeddings of Languages in Forth" by R.D. Dixon in the latest Journal of Forth Application and Research, vol. 4, no. 4, 1987. 

This same issue contains two important articles on implementing PROLOG in Forth: "A Full PROLOG Interpreter Embedded in Forth" by Odette and Paloski and "Compiling PROLOG to Forth" by Odette. The latter article contains careful and expert notes on implementing the compiler, with complete source code. Compiling PROLOG to run on Forth hardware gives you an inference engine with truly awesome speed. Lou Odette reports that a "naive reverse benchmark" runs at 6,000 LIPS (logical inferences per second) on an NC4016 with a (conservative) 4-KHz clock. When the next generation of Forth processors appears later this year, they should run the same benchmark at the speed of compiled (Quintus) PROLOG on a VAX 11/780. 

Flash! At this year's annual Forth Convention (San Jose, Calif., November 1987) Silicon Composers unveiled an IBM PC AT board containing the FORCE core set from Harris Semiconductor. FORCE is implemented on the board as five separate chips: the Forth-based CPU, a hardware multiplier, an interrupt controller, a data stack, and a return stack. All pertinent signals are bused to a prototyping area on the board. It has 32K of high-speed RAM, expandable to 128K (as soon as highdensity 35-nsec RAM chips become available). 

The development system includes an optimizing compiler that reads standard ASCII text files. You could use this system to prototype and test hardware macros before integrating them into a custom VLSI Forth-based RISC machine. The AT/FORCE board is available from Silicon Composers ([415] 322-8763) starting mid-December 1987 and retails for $4,500. 

###  Text and Data files

In my last column (December 1987), I looked at a minimal but useful set of string operators, which are summarized in  Example 1, page 110. The word MATCH replaces the word -MATCH in the last column. Its source code is in this month's listing (see Listing One, page 86). -MATCH is the name of a polyFORTH word that compares strings cell by cell and is used primarily for searching index files. 

#### **Example 1:** String operator
    
    
    /STRING ( a n n2 -a + n2  n-n2)
    \ truncates leftmost n chars of string.
    \n may be negative
    
    EVAL ( a n )
    \ evaluates ("text interprets") a string.
    
    ASCII ( - c )
    \ returns a value of following char.
    
    CTO"" ( c - a 1)
    \converts character to a string.
    
    SKIP ( a I c - a2 12)
    \ returns shorter string from
    \ first position unequal to byte.
    
    SCAN ( a 1 byter - a2 12)
    \ returns shorter string from
    \ first position equal to byte.
    
    " ( - a)
    \ STATE-smart string literal.
    
    VAL? ( a n - d 2, n2 1 , 0)
    \ string to number conversion. \ True if d is valid.
    \ Returns d if number contains ",-./:"
    \ and sets DPL = 0
    \ Returns n if no punctuation present
    \ and sets DPL = 0<
    
    VAL ( a n - d f)
    \ converts string to double number. \ True if number is valid.
    
    -TEXT ( a n a2 - -1 , 0 , 1)
    \ returns -1 if string a n < a2 n ,
    \ 0 if equal, and 1 if >.
    
    COMPARE ( a n a2 n2 - -1 , 0 , 1)
    \ returns -1 if a n < a2 n2, \ 0 if equal, and 1 if >.
    
    MATCH ( a n a2 n2 - ???? 0 , offset -1)
    \ returns the position of
    \ string a2 n2 in (a n).
    \ Offset is zero if ( a n )
    \ is found in first char position.
    \ FAlse with valid offset
    \ if ( a n ) isn't in a2 n2.
    
    

The string package was built on the Standard prelude (see DDJ, October 1987) and the DDJ Controlled Reference Word Set: 
    
    
    2* D2* HEX C, BL ERASE BLANK .R 2>R 2R> AGAIN DLITERAL S>D WITHIN TRUE

Definitions for these words are also in the listing. 

This month's topic is accessing files from Forth. You may have heard that Forth is both a language and an operating system. It's true. Because Forth is often the first development system to work in a new environment, it needs to manage memory, handle interrupts, and access mass storage. The essential primitives for reading and writing mass storage are BLOCK and UPDATE. Eventually when other operating systems become available, Forth may be extended to coexist peacefully with them. 

When Forth is itself the operating system, it divides mass storage into consecutively numbered (1,024-byte) blocks. Both source code and data are kept in these blocks. There are no "text" or "data" files in the normal sense. Any source or data read by Forth is previously written by Forth. 

When Forth runs under an operating system, it takes advantage of operating system calls. Under CP/M, for example, any logical sector in a file can be read or written directly. Sectors can be grouped into blocks, and so any file can be accessed as a sequence of blocks. 

But what if the file is not an integer number of blocks long? The file will only fill part of the last block. This leads to the first rule of standard file access from Forth: 

1\. BLOCK must be able to read the entire file. If the last block is partial, the contents of BLOCK past the end of the file are undefined. Reading a partial block is not an error condition.


What about writing to a partial block? The UPDATE command tells Forth only that some part of a block has been written to but not which part. This leads to a second rule: 

2\. UPDATE forces a partial block to be extended to a full block. The file length must be adjusted accordingly.


If you use Forth only to read Forth files, you will see no partial blocks and neither rule will apply. Otherwise, two operating system calls are required: 

1\. to determine the length of a file in some unit easily convertible to a double number of bytes 

2\. to extend the length of a file by a multiple of the same unit 

The easiest way to keep track of the length of a file is to call the system when the file is first opened and to maintain its double-number length in a 2VARIABLE (or equivalent)--for example, CAPACITY. Furthermore, assume that you can constmct the word EXTEND, which extends the file by at least a given double number of bytes. Suppose, for example, that Forth provides the word MORE, which extends a file by a given number of blocks: 
    
    
    : MORE ( n )...;
    : EXTEND (d)
    \ extends file d bytes.
           1024 UM/MOD SWAP
           IF 1 + THEN (round up)
           MORE;

In the interests of keeping primitives primitive, assume that whoever calls EXTEND also maintains CAPACITY. Some operating systems extend files automatically, so you can use a null definition for EXTEND: 
    
    
    : EXTEND ( d  ) COMPILE 2DROP ;
    \ null definition.
          IMMEDIATE

BLOCK and UPDATE give you direct access to a file. Data stream and text files, however, use sequential access, reading and writing the next group of characters or bytes. This implies that you should maintain a current (double-number) byte position for the file, which can be kept in a 2VARIABLE (or equivalent)--call it POSITION. POSITION is initialized to 0 when the file is first opened. The words GETDATA and PUTDATA can now be defined to read or write the next n bytes of a file to or from a memory location: 
    
    
    : GETDATA ( a n - n2) ...;
         \ reads n bytes of data from file
         \ into address, n < 64K. Returns
         \ n2 bytes not read, ie beyond
         \end of file. PUTDATA (a n) ...;
         \ writes n bytes of data to file
         \ from address, n < 64K.

High-level definitions of these words are in Listing One. If possible, you should implement them as system calls instead. 

Given POSITION, CAPACITY, GETDATA, and PUTDATA, you can immediately implement GETLINE and PUTLINE to access text files. A text file is simply a sequence of text lines, which are in turn a sequence of characters terminated by an end-of-line condition or an end-of-file condition. These conditions are typically implemented as special characters. Each line may be terminated by an optional line-feed character: 
    
    
    : GETLINE ( a n - a n2 f) ....;
    \ reads n bytes of text from file
    \ into address, n < 64K. n2 bytes
    \ are actually read. True if end-
    \ of-line terminates line.
    : PUTLINE (a.)...;
    \ writes n bytes of data to file
    \ from address, n < 64K.

GETLINE is based on GETTEXT, which is based on GETDATA. The end-of-line flag is needed because either end-of-line or end-of file can return a zero-length string. 

Note that all the words I've just described can ultimately be built from BLOCK and UPDATE, provided that the given rules and assumptions are valid. Nothing has been said about file selection, which changes greatly from Forth to Forth. Selecting the proper file before accessing it is up to you. Most operating-system-oriented Forths allow you to select one of at least two files, typically one for input only and one for output or modiiy. When you change files, you must update or switch CAPACITY and POSITION accordingly. 

Now that you can access text files, you can do some truly wonderful things: 
    
    
    : TYPE-FILE
    \ reads and prints a file.
         BEGIN PAD 80 GETLINE
         WHILE CR TYPE
         REPEAT 2DROP;

It really is that easy. In the listing, I have included examples for copying text files `and for converting blocks to text and back again. One final example shows you how to interpret text files, which you can create using Borland's SideKick or some other handy text editor: 
    
    
    : EVAL-FILE
    \ reads and interprets a file.
           BEGIN PAD 80 GETLINE
           WHILE EVAL
           REPEAT 2DROP;

**[LISTING ONE]**
    
    
    <a name="0070_0008">
    
    
    
    
    
    
    ( Stream data and text read and write )
    These utilities read and write streams of data and text from
    standard or BLOCKed files.
    
    Text lines are read into the user buffer until either the buffer
    is full, or the file is empty, or an #EOF or #EOL is read.
    The terminating #EOF or #EOL , if present, is not read into the
    buffer.  #LF ( linefeeds) are read but ignored.
    
    Output files are assumed to be extensible.
    
    For your convenience, the Standard Prelude and DDJ Controlled
    Reference Word Set are duplicated in this file.
    
    
    
    ( LOAD screen for DDJ Standard Prelude and String Extension)
    ( MJT  Nov 22 1987 for DDJ February 1987)
    
    (     2 LOAD ( Standard prelude)
          3 LOAD ( Augmented interpretation)
       4  5 THRU ( Controlled words)
       6  9 THRU ( Strings)
      10 13 THRU ( General file support)
    \    14 LOAD ( Read and write data files)
      15 16 THRU ( Read and write BLOCKed data files)
         17 LOAD ( Read text file, no   #EOL)
    \    18 LOAD ( Read text file, with #EOL)
         19 LOAD ( Write text file)
      20 22 THRU ( Some examples)
    
    
    ( FORTH-83 functions-- typical definitions)
    ( Adjust these words for your Forth.    See DDJ Oct 1987.)
    ( Note: functions already provided need not be redefined.)
    : RECURSE   [COMPILE] MYSELF ;  IMMEDIATE
    : INTERPRET   INTERPRET ;
    
    : I> ( - 'data)   COMPILE R> ;  IMMEDIATE
    : >I ( - 'data)   COMPILE >R ;  IMMEDIATE
    
    ( Used for alignment: )
    : ALIGN    ( HERE 1 AND ALLOT) ;
    : REALIGN  ( a - a' )  ( DUP 1
    AND  ***********************************************************************
     ***************************************************************************
     ***************************************************************************
     ***************************************************************************
     *******al interpretation or compilation.
    
    : NEED ( - f)   32 ( ie blank) WORD FIND  SWAP DROP  0= ;
    \ true if the following word is in the search order.
    \ FORTH-83 Controlled Words
    
    NEED  2* \IF  :  2*    DUP  + ;
    NEED D2* \IF  : D2*   2DUP D+ ;
    
    NEED HEX \IF  : HEX   16 BASE ! ;
    NEED  C, \IF  : C, ( n )   HERE 1 ALLOT C! ;
    
    NEED BL \IF  32 CONSTANT BL
    
    NEED ERASE \IF  : ERASE ( a n)   00 FILL ;
    NEED BLANK \IF  : BLANK ( a n)   BL FILL ;
    
    NEED .R \IF  : .R ( n width)   >R DUP 0< R> D.R ;
    
    
    \ DDJ Forth Column Controlled Words
    NEED 2>R
    \IF : 2>R   COMPILE SWAP COMPILE >R COMPILE >R ;  IMMEDIATE
    NEED 2R>
    \IF : 2R>   COMPILE R> COMPILE R> COMPILE SWAP ;  IMMEDIATE
    NEED @EXECUTE \IF  : @EXECUTE   @ EXECUTE ;
    
    NEED AGAIN
    \IF  : AGAIN   0 [COMPILE] LITERAL [COMPILE] UNTIL ;  IMMEDIATE
    NEED DLITERAL
    DUP \IF  : DLITERAL  SWAP [COMPILE] LITERAL [COMPILE] LITERAL ;
        \IF   IMMEDIATE
    
    NEED S>D  \IF  : S>D  ( n - d)    DUP 0< ;
    NEED WITHIN   \IF  : WITHIN ( n n2 n3 - f)  OVER - >R - R> U< ;
    NEED TRUE \IF  -1 CONSTANT TRUE
    \ String operators    See  DDJ  December 1987
    \ Only  /STRING  and  EVAL  are used in this application.
    
    : /STRING ( a n n2 - a+n2 n-n2)   ROT OVER +  ROT ROT - ;
    \ truncates leftmost n chars of string.  n may be negative.
    
    : EVAL ( a n )
    \ evaluates ("text interprets") a string.
       DUP >R  TIB SWAP CMOVE  R@ #TIB !
       0 >IN ! 0 BLK !  INTERPRET  R> >IN ! ;
    
    
    
    
    
    
    \\ String operators from  STRINGS.ARC  are summarized here:
    
    ASCII ( - c)       \ returns value of following character.
    CTO"" ( c - a 1)   \ converts character to string.
    
    SKIP ( a l c - a2 l2)
    \ returns shorter string from first position unequal to byte.
    SCAN ( a l byte - a2 l2)
    \ returns shorter string from first position equal to byte.
    
    " ( - a n)     \ STATE-smart string literal.
    
    
    
    
    
    \\ String operators from  STRINGS.ARC  continue here:
    VAL? ( a n - d 2 , n2 1 , 0)
    \ string to number conversion primitive.  True if d is valid.
    \ Returns d if number contains ",-./:"  and sets DPL = 0
    \ Returns n if no punctuation present   and sets DPL = 0<
    
    VAL ( a n - d f)
    \ converts string to double number.  True if number is valid.
    \ If number contains ",-./:" then sets DPL = 0
    \ If no punctuation present  then sets DPL = 0<
    
    -TEXT ( a n a2 - -1 , 0 , 1)
    \ returns -1 if string a n < a2 n , 0 if equal, and 1 if >.
    
    COMPARE  ( a n a2 n2 - -1 , 0 , 1)
    \ returns -1 if a n < a2 n2 , 0 if equal, and 1 if >.
    \ The corrected version of  MATCH
    
    : MATCH ( a n a2 n2 - ???? 0 , offset -1)
    \ returns the position of string a2 n2 in (a n).
    \ Offset is zero if ( a n ) is found in first char position.
    \ Returns false with invalid offset if ( a n ) isn't in a2 n2.
       DUP 0= IF  2DROP 2DROP   0 TRUE EXIT  THEN
       2SWAP  2 PICK OVER SWAP -
       DUP 0< IF  2DROP 2DROP   0 EXIT  THEN
       0   ( index ) SWAP 1+ 0
       DO  ( index ) >R
         2OVER 2OVER DROP  -TEXT 0=  ( equal? )
         IF  2DROP 2DROP  R> TRUE  UNDO EXIT  THEN
         1 /STRING   R> 1+
       LOOP  2DROP 2DROP  0 ;
    
    \ Data stream general support
    1024 CONSTANT 1K
    
    : UMIN ( u u2 - u3)   2DUP U< 0= IF  SWAP  THEN  DROP ;
    
    \ Adjust these constants for your system:
    10 CONSTANT #LF    \ linefeed character.
    13 CONSTANT #EOL   \ end-of-line character.
    26 CONSTANT #EOF   \ end of file character (control-Z).
    
    \ Adjust end-of-line and end-of-file sequence for your system:
    CREATE ENDLINE   2 ( count) C,  #EOL C, #LF C,
    CREATE ENDFILE   1 ( count) C,  #EOF C,
    
    
    
    \ File size and position
    \ Example of some of the structure of a file control block:
    
    \ VARIABLE FCB   HERE FCB !  5 CELLS ALLOT  ( Containing: )
    \    1 cell   current file handle-- ie selects current file.
    \    2 cells  current file size in bytes (double number).
    \    2 cells  current file position      (double number).
    
    \\ You can implement CAPACITY and POSITION as 2VARIABLES.
    \\ You must initialize CAPACITY to the size of your file.
    
    2VARIABLE POSITION
    2VARIABLE CAPACITY  ( eg  DSIZE CAPACITY 2! )
    
    
    
    \ Set and reset file position
    \ Given POSITION you can control the position of file access:
    
    : MARKDATA ( - d)   POSITION 2@ ;
    \ determines the position of the current file.
    
    : SEEKDATA ( d )    POSITION 2! ;
    \ changes the position of the current file.
    
    
    
    
    
    
    
    
    \ Extend the file
    \ If your Forth or operating system requires explicit extension,
    \ supply an appropriate definition for  EXTEND .
    \ Otherwise, use  : EXTEND ( d ) COMPILE 2DROP ;  IMMEDIATE
    
    : EXTEND ( d ) COMPILE 2DROP ;  IMMEDIATE
    
    \\
    : EXTEND ( d )
    \ properly extends current file by  d  bytes.
    \ This example converts  d  to blocks and calls a MORE function.
       1K UM/MOD  SWAP IF  1+  THEN ( # of blocks to extend ) MORE ;
    
    
    
    
    \ Read and write data files directly
    
    : GETDATA ( a n - n2)   ;
    \ reads n bytes of data from input file into address, n < 64K
    \ Returns n2 bytes not read ( ie beyond end of file ).
    \ Implement as a system call using CAPACITY and POSITION
    
    : PUTDATA ( a n)   ;
    \ writes n bytes of data to output file from address, n < 64K
    \ Implement as a system call using CAPACITY POSITION and EXTEND
    
    
    
    
    
    
    \ Read BLOCKed file as data file
    
    : GETDATA ( a n - n2)
    \ reads n bytes of data from input file into address, n < 64K
    \ Returns n2 bytes not read ( ie beyond end of file ).
       ( calculate # of bytes to move < 64K : )  POSITION 2@
       BEGIN  2 PICK ( n ) DUP
         IF ( n ) >R  2DUP 1K UM/MOD  SWAP DROP  1+  1K UM*
            CAPACITY 2@ DMIN  2OVER D- 0= NOT OR  R> UMIN
         THEN  ?DUP
       WHILE  >R  2DUP 1K UM/MOD  BLOCK +  4 PICK R@ CMOVE
         R@ 0 D+  2SWAP  R> /STRING  2SWAP
       REPEAT  POSITION 2!  SWAP DROP ;
    
    
    
    \ Write BLOCKed file as data file
    
    : PUTDATA ( a n)
    \ writes n bytes of data to output file from address, n < 64K
       ( extend the file as needed : )
       DUP 0  POSITION 2@ D+  CAPACITY 2@  2SWAP D-  DUP 0<
       IF  2DUP EXTEND  2OVER CAPACITY 2!  THEN  2DROP
       ( calculate # of bytes to move < 64K : )  POSITION 2@
       BEGIN  2 PICK ( n ) DUP
         IF ( n ) >R  2DUP 1K UM/MOD  SWAP DROP  1+  1K UM*
            CAPACITY 2@ DMIN  2OVER D- 0= NOT OR  R> UMIN
         THEN  ?DUP
       WHILE  >R  2DUP 1K UM/MOD  BLOCK +  4 PICK SWAP R@ CMOVE
         R@ 0 D+  2SWAP  R> /STRING  2SWAP   UPDATE
       REPEAT  POSITION 2!  2DROP ;
    
    \ Read text file with #EOF
    
    : GETTEXT ( a n - n2 f)
    \ reads n bytes of text from input file into address, n < 64K
    \ Returns n2 bytes not read ( ie end-of-line or beyond file)
    \ Returns true if #EOL terminates line; false otherwise.
       POSITION 2@  CAPACITY 2@  2OVER D- 0= NOT OR ( limit to 64K)
       3 PICK UMIN  ?DUP 0= IF  2DROP SWAP DROP  0 EXIT  THEN  0
       DO  2DUP 1 0 D+  2SWAP 1K UM/MOD  BLOCK + C@ ( read a char )
          DUP #EOL = OVER #EOF = OR
          IF  >R  POSITION 2!  SWAP DROP  R> #EOL = UNDO EXIT  THEN
          DUP #LF -  ( a n dpos ch f )
          IF  >R  2SWAP  R@ 2 PICK C!  1 /STRING  2SWAP  R> THEN
          DROP
       LOOP  POSITION 2!  SWAP DROP  0 ;
    
    \ Read text file without #EOF
    
    : GETTEXT ( a n - n2 f)
    \ reads n bytes of text from input file into address, n < 64K
    \ Returns n2 bytes not read ( ie end-of-line or beyond file)
    \ Returns true if #EOL terminates line; false otherwise.
       POSITION 2@  CAPACITY 2@  2OVER D- 0= NOT OR ( limit to 64K)
       3 PICK UMIN  ?DUP 0= IF  2DROP SWAP DROP  0 EXIT  THEN  0
       DO  2DUP 1 0 D+  2SWAP 1K UM/MOD  BLOCK + C@ ( read a char )
          DUP #EOL =
          IF  >R  POSITION 2!  SWAP DROP  R> #EOL = UNDO EXIT  THEN
          DUP #LF -  ( a n dpos ch f )
          IF  >R  2SWAP  R@ 2 PICK C!  1 /STRING  2SWAP  R> THEN
          DROP
       LOOP  POSITION 2!  SWAP DROP  0 ;
    
    \ Read and write lines of text
    
    : GETLINE ( a n - a n2 f)
    \ reads n bytes of text from input file into address, n < 64K
    \ n2 bytes are actually read; this is the opposite of GETTEXT
    \ Returns true if #EOL terminates line; false otherwise.
       2DUP GETTEXT >R  -  DUP 0= 0=  R> OR ;
    
    : PUTLINE ( a n )   PUTDATA  ENDLINE COUNT PUTDATA ;
    \ writes n bytes of data to output file from address, n < 64K
    
    
    
    
    \ Text stream examples
    
    : TYPE-FILE   \ reads and prints the input text file.
    \ Assumes zero-length string TYPEs nothing.
       SWITCH ( to input file saving currently active file)
       BEGIN  PAD 80  GETLINE ( n2 f)
       WHILE  CR TYPE  REPEAT  2DROP
       SWITCH ( back to current file) ;
    
    : COPY-FILE
    \ copies the input text file to the output text file.
    \ Save and restore current file as needed.
       BEGIN  SWITCH ( to input  file)  PAD 80 GETLINE
              SWITCH ( to output file)
       WHILE  PUTLINE  REPEAT  2DROP  ENDFILE COUNT PUTDATA ;
    
    \ Text stream examples
    : BLOCK-TO-TEXT
    \ copies the input BLOCK file to the output text file.
    \ Save and restore current file as needed.
       BEGIN  SWITCH ( to input  file)  PAD 64 GETLINE
              SWITCH ( to output file)
       WHILE  -TRAILING  PUTLINE
       REPEAT  2DROP  ENDFILE COUNT PUTDATA ;
    
    : TEXT-TO-BLOCK   0 ( previous line length )
    \ copies the input text file to the output BLOCK file.
       BEGIN  SWITCH ( to input  file)
              PAD  64  2DUP BLANK  GETLINE  ROT ( a ) DROP
              SWITCH ( to output file)
       WHILE  DUP 0= ROT 64 = AND NOT IF  PAD 64 PUTDATA  THEN
       REPEAT  2DROP ;
    \ Text stream examples
    
    : EVAL-FILE   \ reads and interprets the input text file.
    \ Assumes zero-length interpreted string does nothing.
       SWITCH ( to input file saving currently active file)
       BEGIN  PAD 80  GETLINE ( n2 f)
       WHILE  EVAL  REPEAT  2DROP
       SWITCH ( back to current file) ;