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 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.
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.
The string package was built on the Standard prelude (see DDJ, October 1987) and the DDJ Controlled Reference Word Set:
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:
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:
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:
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:
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:
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 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:
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:
[LISTING ONE]Forth in AI
Text and Data 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.
2* D2* HEX C, BL ERASE BLANK .R 2>R 2R> AGAIN DLITERAL S>D WITHIN TRUE
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.
2. UPDATE forces a partial block to be extended to a full block. The file length must be adjusted accordingly.
: MORE ( n )...;
: EXTEND (d)
\ extends file d bytes.
1024 UM/MOD SWAP
IF 1 + THEN (round up)
MORE;
: EXTEND ( d ) COMPILE 2DROP ;
\ null definition.
IMMEDIATE
: 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.
: 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.
: TYPE-FILE
\ reads and prints a file.
BEGIN PAD 80 GETLINE
WHILE CR TYPE
REPEAT 2DROP;
: EVAL-FILE
\ reads and interprets a file.
BEGIN PAD 80 GETLINE
WHILE EVAL
REPEAT 2DROP;
<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) ;