*LOAD <filename> (<Load address>)
If the file happens to be a BASIC program, and the load address defaults to, or is given as, the current setting of PAGE, then it will be loaded and can be run just as if it had been loaded with the BASIC command LOAD "<filename>" .
Similarly, you can use the command *SAVE to save a BASIC program, if you know the right addresses to use.
What really creates an effective distinction between different types of file is the way that the information in the file is structured. This structuring will be carried out by the commands used to create the information or to store the information in the file. We can in this sense distinguish at least five separate file types
BASIC program
machine code program
binary file
data file
ASCII file
BASIC programs and machine code programs must have the information structured as the appropriate type of program before saving to a file.
Data files and ASCII files have their information structured (differently) by the commands which handle the respective types of file.
Binary files can contain any type of information, including the other four file types and also otherwise incomprehensible information such as graphics dumps.
We can deal fairly rapidly with the types of command associated with three of the file types.
*SAVE GRDUMP 3000 8000
The picture could be loaded back simply by
*LOAD GRDUMP
The final two filetypes require special commands, and these are dealt with in the following sections.
10 PRINT "*SAVE test" 20 PRINT "program compteted"
Save it to disc using the command
*SAVE TESTPG 1900 19FF |
*LOAD TESTPG
LIST and RUN the program.
MODE 1 MOVE 1,1 DRAW 1000,1 DRAW 1000,1000 DRAW 1,1000 DRAW 1,1 DRAW 1000,1000 MOVE 1000,1 DRAW 1,1000
Use the commands given above to save the file with *SAVE, clear the screen with CLS, and then restore the display with *LOAD. (Note, however, that an interesting effect occurs if the screen display is scrolled between *SAVEing and *LOADing.)
<channel variable>=OPENOUT(<filename>)
For example, if some data is to be written to a file called DFILE then the line
CH%=OPENOUT("DFILE")
can be used to get the system to allocate a buffer. (Note that as with most functions, brackets are conventionally included, but are actually unnecessary.)
The channel number to be associated with the file DFILE is allocated to the variable CH%, and CH% should be used with all subsequent file handling operations to inform the system that DFILE is the file to be written to.
A point to note is that with the disc filing system, the command OPEN OUT erases any existing version of DFILE, if it exists. If it does not exist, then a file named DFILE, 64 sectors long, is created. The filename given as the argument to OPENOUT may be either a string, such as "DFILE", or a string variable.
Strictly speaking a number could be used for the channel instead of a variable, provided you were quite certain that you knew in advance what the correct number was for a particular channel, but it would be pointless as well as foolhardy since variables are actually more efficient than numbers within a program. Again it is possible, in principle, to open a file within one program, or even in direct mode, and use it within a subsequent program, but it is most unlikely that you will ever have reason to do this in practice.
PRINT#<channel>,variable,variable,...
For example
PRINT#CH%,B%,C$,"DATA"
Any variable, expression or value may be sent as output, just as with the normal PRINT command, but in other respects PRINT# differs. Items must only be separated by commas, and some commands such as SPC() and TAB() cannot be used.
CLOSE#<channel>
For example
CLOSE#CH%
If instead of the channel variable the value 0 is used for the channel in the CLOSE command, then all open files will be closed. This is particularly valuable in an ON ERROR routine, to ensure that all files are closed if a program fails during execution. If you have not included such an error trap, the CLOSE command can also be used in direct mode for the same purpose, either specifying the actual file by its channel variable, or closing all files with a zero. It must be issued before the program is run again, or an error will occur when the computer attempts to reopen the still open file.
500 DEF PROC_writedata(F$) 510 CH%=0PENOUT(F$) 520 FOR I=1 TO 20 530 PRINT#CH%,A(I) 540 NEXT I 550 CLOSE#CH% 560 ENDPROC
A simple program to run this subroutine would be
10 DIM A(100) 20 FOR I=1 TO 20 30 A(I)=I*I: REM DATA IS FIRST 20 SQUARES 40 NEXT I 50 PROC_writedata("DFILE") 60 END
The procedure could equally well handle strings or integers if A(I) were replaced by A$(I) or A%(I).
CH%=OPENIN("DFILE")
Note that if DFILE does not exist, it will not be created. CH% will be set to zero and all will appear to be well, but when a command tries to use the channel number, it will fail with the error message
Channel at line...
This is particularly confusing because it is not really that line that is in error, but the line where the OPENIN was issued. It is useful in a situation like this to PRINT the value of CH% in immediate mode. If the value is zero it will wnfirm that a non-existent file is the source of error.
The fact that CH% is assigned a value of zero if the file does not exist could be used as a safety check, when a new file is to be created, to ensure that a file of the same name does not exist already before issuing an OPENOUT that would destroy an existing file.
INPUT#CH%,A,B%,C$,D$
(Obviously, input has to be directly stored into variables, whereas values and expressions can be output to a file. Also, INPUT# is not a function, so it could not be used in an expression such as X=Y+INPUT#.)
500 DEF PROC_readdata(F$) 510 CH%=OPENIN(F$) 520 FOR I=1 TO 20 530 INPUT#CH%,A(I) 540 NEXT I 550 CLOSE#CH% 560 ENDPROC
It could be used with a program of the form
10 DIM A(100) 20 PRINT "The first 20 squares are" 30 PRINT '" Number square"' 40 PROC_readdata("DFILE") 50 FOR 1=1 TO 20 60 PRINT I,A(I) 70 NEXT I 80 END
EOF#<channel>
and returns the value TRUE(-1) if the end of the file has been reached, and FALSE (0) otherwise. This is ideally suited to REPEAT...UNTIL loops, where it can be used as the UNTIL test.
20 PROC_readdata("DFILE") 25 I=I-1 30 PRINT "The first ";I;" squares are" 40 PRINT '" Number square"' 50 FOR J=1 TO I 60 PRINT J,A(J) 70 NEXT 515 I=1 520 REPEAT 530 INPUT#CH%,A(I): I=I+1 540 UNTIL EOF#CH%
CH%=OPENUP("DFILE")
A serious complication occurs at this point between the two versions of BASIC that have been released by Acorn. These two versions are generally referred to as BASIC I (or just BASIC) and BASIC II respectively. To find out which version of BASIC your computer has, if you do not already know, press <BREAK> and then type
REPORT
The computer will respond either
(C)1981 Acorn
or
(C)1982 Acorn
The former message is issued by BASIC I, and the latter by BASIC II, which links the two dates very conveniently to the two versions of BASIC.
The above description of OPENOUT, OPENIN and OPENUP refers to BASIC II. Only OPENOUT is the same for BASIC I.
The other two commands are replaced by a single command, OPENIN, but worse is to follow. OPENIN in BASIC I has the same effect as OPENUP in BASIC II. There is no equivalent to the effect produced by BASIC Il's OPENIN. Furthermore, a program containing OPENIN written with BASIC I will list the command as OPENUP on BASIC II. A program with OPENIN written with BASIC II will list nothing on BASIC I, and the command will cause an error.
If this seems totally confusing, Table 9.1 may help.
Table 9.1 Summary of commands to open files in BASIC I and BASIC II.
BASIC I | BASIC II | Token | Action |
OPENOUT | OPENOUT | &AE | Delete existing file and open file for writing only |
N/A | OPENIN | &8E | Open file for reading only |
OPENIN | OPENUP | &AD | Open file for reading, writing or updating |
100 CH%=OPENUP("DFILE"): REM USE OPENIN WITH BASIC I 110 PTR#CH%=EXT#CH% 120 FOR I=21 TO 30 130 PRINT#CH%,I*I 140 NEXT I 150 CLOSE#CH%
You can test the success of this program by reading back DFILE again with the program from Example 9.3.
*SAVE DFILE 0 10
that will create a tiny file. It will contain rubbish, but this is immaterial since it is to be overwritten. (You could equally well create a small BASIC program with SAVE.)
The second problem is more serious, and stems from the inefficient way in which the DFS stores its files in strict sequential order on the sectors of a disc. If, as is likely, a new file is saved to a disc following a data file, it will butt up against it on the disc, as shown in Figure 9.1.
The problem now arises that if you wish to append to the data file, there is no room to do so and the program will fail with
Can't extend
This is an infuriating message when you know that there is plenty of room left on the disc, and there is no foolproof way of avoiding it, except by making the data file the only file on the disc.
Figure 9.1 The problem with extending a data file.
A reasonably safe, but wasteful, way of ensuring that there is enough room is initially to create a file sufficiently large for all possibilities, by the same *SAVE method a described before. A file of 100 sectors could be created by
*SAVE LARGEFL 0 6400
(&64=100 decimal)
after which it should only be accessed by OPENUP or OPENIN.
However, you can no longer append to the file by the simple technique described above, since EXT# will point to the end of the 100 sectors, not the end of the genuine data.
Until Acorn change the DFS away from the strict sequential storage method there will be no really satisfactory solution to this 'Can't extend' problem.
PTR#CH%=40*(N-1)
which immediately skips over the previous (N-) records.
There is a wide range of situations where data can be conveniently subdivided into records - phone lists, names and addresses, journal references, library catalogues, club membership lists, subscription accounts, stock lists, patient records and many similar record-keeping requirements. Quite clearly, anything that can be stored in a card index is ideally suited for a random access textfile.
In most cases, including those listed above, each record will contain more than one item, or field. These fields will be accessed sequentially, and the record acts in many respects like a very small sequential data file. In principle, each record could contain a different number of fields, though handling the file would be very difficult unless the records had some systematic structure, such as alternate records with the same numbers of fields.
10 CH%=OPENOUT("INVENT") 20 RECNO=0 30 REPEAT 40 INPUT "Item name: "ITNM$ 50 INPUT "Type: "TPE$ 60 INPUT "Quantity: "QU% 70 PTR#CH%=RECNO*32 80 PRINT#CH%,ITNM$,TPE$,QU% 90 RECNO=RECNO+1 100 UNTIL ITNM$="" 110 CLOSE#CH%
The program will loop until <RETURN> is pressed with no other input in response to the item name. (It will also be necessary to press <RETURN> in response to the type and quantity, and this last empty record will be written into the file.)
The key lines are line 70, where PTR# is set to the start of the next record, and line 90 where the record number is incremented.
9.4.2 Reading a random access file
To read records back we need a program which is effectively the mirror image of that in Example 9.5, but it is necessary to know when to stop. As with Example 9.3, the EOF# function can be used for this purpose.
10 CH%=OPENIN("INVENT") 20 RECNO=0 30 REPEAT 40 PTR#CH%=RECNO*32 50 INPUT#CH%,ITNM$,TPE$,QU% 60 PRINT "Item name: ";ITNM$ 70 PRINT "Type: ";TPE$ 80 PRINT "Quantity: ";QU% 90 PRINT 100 RECNO=RECNO+1 110 UNTIL EOF#CH% 120 CLOSE#CH%
However, the whole point of random access files is to be able to read an individual record without first searching through the whole file and, even more importantly, to be able to modify or edit a record. For instance, with an inventory it must be possible to change the quantity, as items are drawn from stock or reordered. All that is necessary is to set PTR# to the desired record, as explained earlier.
10 PRINT "Do you want to read or wite (R/W)? "; 20 INPUT " "RW$ 30 INPUT "Type the record number that you want: "RECNO 40 IF RW$="R" THEN PROC_read(RECNO-1): REM SINCE RECORDS START AT 0 50 IF RW$="W" THEN PROC_write(RECNO--1) 60 END 1000 DEF PROC_read(RECNO) 1010 CH%=OPENIN("INVENT") 1020 PTR#CH%=RECNO*32 1030 INPUT#CH%,ITNM$, TPE$,QU% 1040 PRINT "Item name: ";ITNM$ 1050 PRINT "Type: ";TPE$ 1060 PRINT "Quantity: ";QU% 1070 PRINT 1080 CL0SE#CH% 1090 ENDPROC 2000 DEF PROC_wite(RECNO) 2010 CH%=OPENUP("INVENT"): REM USE OPENIN WITH BASIC I 2020 INPUT "Item name: "ITNM$ 2030 INPUT "Type: "TPE$ 2040 INPUT "Quantity: "QU% 2050 PTR#CH%=RECNO*32 2060 PRINT#CH% ,ITNM$,TPE$,QU% 2080 CLOSE#CH% 2090 ENDPROC
BPUT#<channel> ,<number/variable>
For example
BPUT#CH%,10
or
BPUT#CH%,A%
Note that if any value exceeds 255, then only the least significant byte will be written. BGET# on the other hand returns a value, and must be PRINTed or assigned to a variable
BGET# on the other hand returns a value, and must be PRINTed or assigned to a variable
PRINT BGET#CH%
or
BT%=BGET#CH%
In the case of both BPUT# and BGET#, the pseudo-variable PTR# is incremented after the operation.
10 INPUT "NAME",NAME$ 20 L%=LEN(NAME$) 30 IF L%>10 THEN PRINT "Not more than 10 characters": GOTO 10 40 NAME$=NAME$+STRING$(10-L%," ")
10 ON ERROR GOTO 5000 20 CLS 30 PRINT "Do you want to WRITE or READ a data file?" 40 INPUT "Please answer R or W: "A$ 50 PRINT 60 IF A$<>"R" AND A$<>"W" THEN GOTO 40 70 INPUT "Please type filename: "F$ 80 IFA$="R" THEN GOTO 500 90 REM 100 REM ******** WRITE DATA FILE ******** 110 REM 120 INPUT '"Do you want to append to the file? "YN$ 130 YN$=LEFT$(YN$,1): IF YN$<>"Y" AND YN$<>"N" THEN VDU 7: GOTO 120 140 CLS 150 PRINT"Do you want to write:" 160 PRINT''"1) All real numbers" 170 PRINT'"2) All integer numbers" 180 PRINT'"3) All strings" 190 PRINT'"4) A mixture of data types" 200 INPUT''"Type the number for your choice: "DTYPE% 210 IF DTYPE%<1 OR DTYPE%>4 THEN VDU 7:GOTO 200 220 MODE 7 230 PRINT CHR$(136);"TYPE <ESC> TO TERMINATE INPUT" 240 PRINT' 250 *FX229,1 260 REM CANCEL ESCAPE 270 IF YN$="Y" THEN CH%=OPENUP(F$): PTR#CH%=EXT#CH% ELSE CH%=OPENOUT(F$) 275 REM *** USE OPENIN FOR BASIC I *** 280 REM APPEND IF YN$="Y" 290 DONE=0 300 REPEAT 310 INP$="" 320 IF DTYPE%=1 OR DTYPE%=2 THEN PROC_number(DTYPE%) 330 IF DTYPE%=3 THEN PROC_string 340 IF DTYPE%=4 THEN PROC_mixture 350 UNTIL DONE 360 CLOSE#CH% 370 *FX 229,0 380 REM RESTORE ESCAPE 390 CLS 400 PRINT "Writing completed" 410 END 470 REM 480 REM ******** READ DATAFILE ******** 490 REM 500 PRINT'"Do you want to pause after each" 510 INPUT "field? (Y/N):"YN$ 520 YN$=LEFT$(YN$,1) 530 IF YN$<>"Y" AND YN$<>"N" THEN VDU 7:GOTO 500 540 CLS 550 CH%=OPENIN(F$) 560 REPEAT 570 DTYPE%=BGET#CH% 580 PTR#CH%=PTR#CH%-1: REM BACK TO START OF DATA ITEM 590 IF DTYPE%=0 THEN INPUT #CH%,INP$:PRINT INP$ 600 IF DTYPE%=64 THEN INPUT#CH%,INP%:PRINT INP% 610 IF DTYPE%=255 THEN INPUT#CH%,INP:PRINT INP 620 IF YN$="Y" THEN A$=GET$:PRINT 630 UNTIL EOF#CH% 640 PRINT''"THAT IS THE END OF THE FILE" 650 CLOSE#CH% 660 END 1000 DEFPROC_number(DTYPE%) 1010 PROC_input 1020 IF DONE THEN ENDPROC 1030 NUM=VAL(INP$) 1040 IF DTYPE%<>2 THEN ELSE NUM%=INT(NUM):IF STR$(NUM%)<>INP$ THEN INP$="":VDU 7:GOTO 1010 1050 IF DTYPE%=1 THEN PRINT#CH%,NUM ELSE PRINT#CH%,NUM% 1060 ENDPROC 1500 DEFPROC_string 1510 PROC_input 1520 IF DONE THEN ENDPROC 1530 PRINT#CH%,INP$ 1540 ENDPROC 2000 DEF PROC_mixture 2010 PRINT "Which data type? (I-R-S): "; 2020 DTYPE$=GET$: PRINT DTYPE$; 2030 IF DTYPE$=CHR$(27) THEN DONE=1: ENDPROC 2040 REPEAT 2050 UNTIL GET$=CHR$(13) 2060 PRINT 2070 IF DTYPE$="I" THEN PROC_number(2): ENDPROC 2080 IF DTYPE$="R" THEN PROC_number(1):ENDPROC 2090 IFDTYPE$="S"THENPROC_string:ENDPROC 2100 VDU7:GOTO 2010 2500 DEF PROC_input 2510 CH$=GET$ 2520 IF CH$=CHR$(13) THEN PRINT: ENDPROC 2530 IF CH$=CHR$(127) THEN PROC_cancel: ENDPROC 2540 IF CH$=CHR$(27) THEN PROCescape: ENDPROC 2550 PRINT CH$; 2560 INP$=INP$+CH$ 2570 PROC_input 2580 ENDPROC 3000 DEF PROC_cancel 3010 IF LEN(INP$)=0 THEN VDU 7: PROC_input: ENDPROC 3020 PRINT CH$;: INP$=LEFT$(INP$,LEN(INP$)-1) 3030 PROC_input 3040 ENDPROC 3500 DEF PROCescape 3510 IF LEN(INP$)>0 THEN VDU7:PROC_input: ENDPROC 3520 DONE=1:ENDPROC 4970 REM 4980 REM ******** ERROR ROUTINE ******** 4990 REM 5000 *FX 229,0 5010 REM RESTORE ESCAPE 5020 CLOSE#0 5030 PRINT "UNEXPECTED ERROR, NUMBER ";ERR;"AT LINE ";ERL 5040 END
This program consists of two main parts: the WRITE section starting at line 120, and the READ section starting at line 500. When writing, four data input options are offered: real numbers only; integers only; strings only; or a mixture of data types. Procedures PROC number and PROC string handle the actual input of a data item, and for the mixed option, PROC_mixture determines which data type is wanted next and then calls either PROC_number or PROC_string. Typing <ESCAPE> at the start of a data item (but not in the middle) terminates input to the file. There is also an option to append to an existing file, which opens the file with OPENUP (or OPENIN for BASIC I) instead of OPENOUT, and uses EXT# to move the file pointer to the end of an existing file (line 270).
For reading files, a single option is offered, to pause after each field, so that a long file can be read (an alternative would be to use <CTRL-N>). The first character of each data item is read with BGET# at line 570 to determine the data type. The file pointer is then decremented back to the statt of the item at line 580, and the item read into the appropriate type of variable on lines 590, 600 or 610.
*BUILD <filename>
Items are entered one 'line' at a time, where line here means, as with a BASIC program line, the entry up to the point the <RETURN> key is pressed, not a screen line. The computer prompts with a number for each line, but these are not stored in the file. Entry to the file is terminated, and the file closed, by pressing <ESCAPE>.
We shall see shortly that ASCII files are most commonly used as *EXEC files, but in principle *BUILD can be used to produce an ASCII file for any purpose.
An ASCII file can be read quite simply using one of three commands: *TYPE <filename> and *LIST <filename> are almost equivalent. They list back the ASCII file line by line, just as it was created by *BUILD, the only difference being that *LIST adds line numbers, in the same way that *BUILD does, whereas *TYPE does not. The third command is
*DUMP <filename>
Its effect is quite different. It produces a hexadecimal dump of the file contents, 8 bytes per line, together with the ASCII representation of each byte. *DUMP can be used with any type of file, not just ASCII files. For instance, it could be used with a data file and would quite conveniently display the format used to store integers, real variables and strings. A specimen *DUMP listing of a data file is shown in Figure 9.2.
0000 00 09 53 52 4F 54 53 49 ..SROTSI 0008 53 45 52 00 04 4B 59 2E SER..K9. 0010 33 40 00 00 00 7D 00 00 3@...}.. 0018 00 00 00 00 00 00 00 00 ........ 0020 00 0A 53 52 4F 54 49 43 ..SROTIC 0028 41 50 41 43 00 05 46 6E APAC..Fn 0030 30 30 32 40 00 00 00 4B 002@...K 0038 00 00 00 00 00 00 00 00 ........ 0040 00 0A 53 52 4F 54 49 43 ..SROTIC 0048 41 50 41 43 00 03 46 75 APAC..Fu 0050 31 40 00 00 00 19 00 00 1@)..... 0058 00 00 00 00 00 00 00 00 ........ 0060 00 00 00 00 40 00 00 00 ....@... 0068 00 ** ** ** ** ** ** ** .*..*.....
Figure 9.2 A specimen *DUMP listing.
*EXEC <filename>
The action of *EXEC is to issue the contents of the specified ASCII file to the computer exactly as if the contents had been typed at the keyboard.
There are three common uses for *EXEC.
Another example where it can be useful to issue not only commands but program input as well, is to run an interactive program automatically. This could be useful to set up a file to control a program that takes a very long time to run, or to keep testing a program during debugging.
CHAIN "EX4.12"
then enter the marks one per line.
*OPT 4,3
and then, when <SHIFT-BREAK> is pressed, the file !BOOT is automatically *EXECed.
!BOOT can as usual be created by *BUILD. Quite commonly it will only be one or two lines long, say to CHAIN a BASIC program (unfortunately there is no direct boot option to CHAIN a program).
*SPOOL <filename>
creates or opens the specified file, and thereafter any output to the screen from a BASIC command is also written into the file.
*SPOOL
with no filename closes the file or files. (The same effect can also be obtained with CLOSE#0.) Thus to capture into an ASCII file a program in memory (or part thereof), you would issue the commands
*SPOOL TXTPROG LIST
(or
LIST 100,200
)
*SPOOL
If you subsequently issue *EXEC TXTPROG, all the listed lines will be reissued. Note that the ASCII file contains everything that appeared on the screen after *SPOOL TXTPROG, including the commands and BASIC prompts (>), so as well as the BASIC lines you will see the extra lines
>LIST
and
>*SPOOL
When *EXECed these will generate 'Syntax error' messages just as they would if you typed them (complete with leading >, that is), but this will not affect the program lines that you want.
1000 FOR J=0 TO 19 1010 READ X 1020 ?(BASEADD+J)=X 1030 NEXT J 1040 DATA 1,2,3,4,5,... 19,20
The numbers in the DATA statement(s) need to be worked out from the machine code, and if the code is already in memory, this could be done by a program, and the resulting values could be generated as a set of BASIC lines of DATA. *SPOOL can be issued from within a program to create an EXEC file. The following program shows how to do this.
10 INPUT "Start address of machine code: "SA$ 20 INPUT "Number of bytes: "NB$ 30 SA=EVAL(SA$): REM SA CAN BE IN DECIMAL OR HEX 40 NB=EVAL(NB$) 50 INPUT "Line number for start of DATA lines: "LI% 60 DIM BT%(NB-1) 70 FOR J=0 TO (NB-1) 80 BT%(J)=?(SA+J) 90 NEXT J 100 *SPOOL DATALNS 110 FOR K=0 TO (NB-1)/10 120 PRINT LI%+10*K;" DATA "; 130 J=0 140 REPEAT 150 PRINT ;BT%(J+10*K); 160 IF J<9 AND J+10*K<(NB-1) THEN PRINT ","; 170 J=J+1 180 UNTIL J>9 OR J+10*K=NB 190 PRINT 200 NEXT K 210 *SPOOL
Note that in contrast to BASIC commands, the name of the file following *SPOOL cannot be a string variable. This is because *SPOOL, like any command starting with a star, is an operating system command which is sent directly to the Command Line Interpreter. The CLI cannot interpret BASIC variables, since it operates at a lower level quite independently of whether BASIC is even fitted to the computer. There is a way round this problem, however, as we shall see in Section 10.7.1.
10 ON ERROR GOTO 900 20 CLS 30 PRINT "Do you want to WRITE or READ an ASCII" 40 INPUT "file? Please anwer W or R: "A$ 50 PRINT 60 IF A$<>"R" AND A$<>"W" THEN GOTO 40 70 INPUT "Please type filename: "F$ 80 IF A$="R" THEN GOTO 500 90 REM 100 REM ******** WRITE ASCII FILE ******* 110 REM 120 INPUT '"Do you want to append to the file? "YN$ 130 YN$= LEFT$(YN$,1): IF YN$<> "Y" AND YN$<>"N" THEN VDU 7: GOTO 120 140 MODE 7 150 PRINT CHR$(136);"TYPE <ESC> TO TERMINATE INPUT" 160 PRINT' 170 *FX 229,1 180 REM CANCEL ESCAPE 190 IF YN$="Y" THEN CH%=OPENUP(F$): PTR#CH%=EXT#CH% ELSE CH%=OPENOUT(F$): REM USE OPENIN WITH BASIC I 200 REM APPEND IF YN$="Y" 210 REPEAT 220 CH=GET 230 IF CH<>27 AND CH<>127 THEN PRINT CHR$(CH);: BPUT#CH%,CH 240 IF CH<>127 THEN ELSE IF PTR#CH%>0 THEN PRINT CHR$(CH);: PTR#CH%=PTR#CH%-1 ELSE VDU 7 250 IF CH=13 THEN PRINT CHR$(10);: REM CR NEEDS LF ALSO 260 UNTIL CH=27 270 CLOSE#CH% 280 *FX 229,0 290 REM RESTORE ESCAPE 300 CLS 310 PRINT "Writing compteted" 320 END 500 REM 510 REM ******** READ ASCII FILE ******** 520 REM 530 CLS 540 PRINT TAB(0,4);"Option List:" 550 PRINT ''"1) Suppress high bit of each byte" 560 PRINT '"2) Suppress CONTROL codes" 570 PRINT '"3) Send output to printer" 580 PRINT '"4) Pause after each paragraph" 590 PRINT '"5) Start listing" 600 OP%=0 610 REPEAT 620 INPUT ''"Type the number for your choice: "OP 630 IF OP=1 THEN OP%=(OP% OR 1): PRINT '"HIGH BITS WILL BE SUPPRESSED" 640 IF OP=2 THEN OP%=(OP% OR 2): PRINT '"CONTROL CODES WILL BE SUPPRESSED" 650 IF OP=3 THEN OP%=(OP% OR 4): PRINT '"PRINTER ENABLED" 660 IF OP=4 THEN OP%=(OP% OR 8): PRINT '"PRESS A KEY AFTER EACH PARAGRAPH" 670 IF OP<>INT(OP) OR 0P>5 THEN VDU 7 680 UNTIL OP=5 690 CLS 700 IF (OP% AND 4)=4 THEN VDU 2 710 CH%=OPENIN(F$) 720 REPEAT 730 CH$=CHR$(BGET#CH%) 740 IF (OP% AND 1)=1 AND ASC(CH$)>128 THEN CH$=CHR$(ASC(CH$)-128) 750 IF (OP% AND 2)=2 AND ASC(CH$)<32 AND CH$<>CHR$(13) THEN CH$="." 760 PRINT CH$; 770 IF CH$=CHR$(13) THEN PRINT CHR$(10);: IF (OP% AND 8) = 8 THEN A$=GET$ 780 UNTIL EOF#CH% 790 VDU 3 800 PRINT ''"THAT IS THE END OF THE FILE" 810 CLOSE#CH% 820 END 900 REM 910 REM ******** ERROR ROUTINE ******** 920 REM 925 REPORT:PRINT " AT ";ERL:END 930 *FX 229,0 940 REM RESTORE ESCAPE 950 CLOSE#0 960 PRINT "UNEXPECTED ERROR, NUMBER ";ERR;" AT LINE ";ERL: VDU 3 970 END
When reading a file the program offers a number of options. The first is to suppress the high bit of any byte. Some files may contain characters in a modified ASCII form, having 128 added to the normal code, and this option will strip off the 128.
The second option allows you to suppress any control characters (except the <RETURN> character), printing a full stop instead. The third and fourth options are self-explanatory, allowing you to list to a printer or pause after each paragraph (or more accurately, after each <RETURN>) until any key is pressed. Note that if your printer requires setting up by commands such as *FX 6,0 these will have to be issued before running the program.
One particular use for this program is to read word processor files if you have access to them but do not yourself possess a word processor. Word processor files often contain control codes as well as simple text, and you can use the option to suppress these in order to read the text. The popular word processor Wordwise in particular can create files containing CTRL-B codes that will hang the computer unless a printer is connected.