1571-5.TXT rev 1 96-11-06
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     THIS DOCUMENT IS COPYRIGHT (C) 1988, 1996 BY HERNE DATA
     SYSTEMS LTD.  THE MATERIAL CONTAINED HEREIN MAY BE FREELY
     USED FOR PERSONAL INFORMATION ONLY.  IF YOU REPRODUCE IT,
     THIS COPYRIGHT NOTICE MUST NOT BE REMOVED.  THIS MATERIAL
     MAY NOT BE EXPLOITED FOR COMMERCIAL PURPOSES.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Herne Data Systems Ltd., 
PO Box 250, Tiverton, ON N0G 2T0 CANADA.  
Voice/fax 519-366-2732, 
e-mail herne@herne.com, 
internet: http://www.herne.com

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

 

                     Effective Use of Files 
 
The 1571 disk operating system recognizes a number of different
types of files: sequential, user, program and relative.  Each
file type is denoted in the disk directory listing by the
corresponding 3 character code listed after the filename: SEQ,
USR, PRG, and REL.  Each file type is normally reserved for a
different primary use.  However, this does not mean that a given
file type cannot be used for a different job.  This chapter will
outline how to create and access each of the file types with
BASIC.  Examples will be given of both standard and non-standard
uses for these files.   Other file types, such as VLIR (very
large indexed relative used by the GEOS operating system),
"random files" and CP/M files are not discussed here.  (The
random file is not really a file in the true sense, but a loose
collection of user associated sectors on the disk.)  

 
SEQuential Files: 
Sequential files are primarily used for the storage and retrieval
of data used by other programs.  The data can be numeric or text
strings, or both in any combination.  As its name might suggest,
the data in a SEQ file are read or written in a pre-determined
sequence.  In order to get to an item in the middle of a SEQ
file, you must first correctly process (that is, either read or
write) everything in the file preceding that point.  
 
     Sequential file access is a three step process: 
 
      1. open the file 
      2. read OR write the data 
      3. close the file 
 
After each step, it is a good idea to check the disk drive error
status through either the command channel (BASIC 2.0) or the
reserved disk status variables DS and DS$ (BASIC 3.5 or 7.0). 
This is especially important after write operations and will keep
you informed about potentially disasterous situations such as
writing to the wrong disk, disk full, bad or unformatted disks,
hardware malfunctions, etc. 
 
OPENing the File 
A SEQ file can only be accessed through an appropriate OPEN type
statement.  (You cannot use LOAD or SAVE type commands for a SEQ
file.)  In addition, the file can be opened for either reading OR
writing, but not both simultaneously.  (If you wish to include
simultaneous input and output capability for your data files,
then they should be initially created as relative files.  These
are described later in this document.) 
 
Several options are available for opening a SEQ disk file,
depending on what action is to be taken with the file after it
has been opened.  The following are some typical examples for
opening a file in read mode on device 8, drive 0: 
  
          10  OPEN 8,8,8,"PEACHES,S,R" 
          10  DOPEN#1,"PLUM,S" 
          10  OPEN 1,8,4,"0:ACCOUNTS,S" 
          10  DOPEN#2,"PAYMENT*" 
 
In each of the above examples, the ",S" and ",R" are optional. 
BASIC will default to the read option if "R" is not specified. 
In the last example, since "S" is not specified, BASIC will open
the first file which matches the specified file name pattern,
regardless of its type.  If "S" is explicitly specified, as in
the other examples, and the file listed in the directory under
the name that you requested is not a SEQ file, then a DOS error
code 64, FILE TYPE MISMATCH, error will occur along with a BASIC
"FILE NOT FOUND" error.    
 
The DOPEN command is not available in BASIC 2.0.  Note also that
all of the examples are preceded by a line number.  This is
because normally you can read a file in program mode only.  BASIC
will not let you use INPUT# or GET# in immediate mode. 
 
If you are using BASIC 2.0, the disk command channel should also
be opened at this time to allow reading of the disk error status. 
The typical command used is: 
            20 OPEN 15,8,15 
 
The disk error status is then read using a standard INPUT#
statement: 
            30 INPUT#15,E1$,E2$,E3$,E4$ 
E1$ will represent the disk error code number, E2$ the
description of the error and, E3$ and E4$ the track and sector
where the error occured (if applicable).  In BASIC 3.5 or 7.0,
you can check the error status with DS (disk error code) or DS$
(error code plus description).  The BASIC 2.0 DOS wedge can also
be used for reading the error status without opening the command
channel.  
 
The following examples illustrate opening a SEQ file for writing:

 
          OPEN 8,8,8,"PEACHES,S,W" 
          DOPEN#1,"PLUM,S",W 
          OPEN 1,8,4,"0:ACCOUNTS,S,W" 
          DOPEN#2,"PAYMENT",W 
 
Files can be opened for writing in either program or immediate
mode.  With the BASIC 2.0 version, both "S" and "W" must be
specified to open a sequential file for writing.  If "S" is
omitted from the OPEN or DOPEN command, BASIC will automatically
default to a SEQ file.  Note that wildcards and pattern matching
are not allowed in filenames opened for writing.  In each of the
above examples, a new file bearing the indicated filename will be
created for subsequent writing.  If a file of the same name
already exists in the disk directory (regardless of its type), a
DOS error code 63, FILE EXISTS, error will be generated. 
However, the file number specified in the OPEN statement is still
active to BASIC.  You must first CLOSE this file before
attempting to re-open a new file with the same number under a
different name.  If you attempt to write to the original file,
the computer and/or disk drive may lock-up.  
   
The 1571 also allows SEQ files to be opened for appending data to
the end of the existing file.  The following are examples of this
procedure: 
 
          OPEN 8,8,8,"PEACHES,S,A" 
          APPEND#1,"PLUM,S" 
          OPEN 1,8,4,"0:ACCOU*,S,A" 
 
Note that in the third example a wild card character is used. 
Wild cards are allowed with the append option, but the same
restrictions outlined above for opening a file in read mode
apply. 
 
The fourth mode of the OPEN command, M for MODIFY, is designed
for recovering information from improperly closed or "SPLAT"
files.  These are files which are indicated in the directory with
an asterisk (*) next to the file type.  The MODIFY mode of 1571
SEQ file handling allows you to read and recover the data in such
a file for processing and re-writing to another file.  The
following are examples of such an open statement: 

          10 OPEN 8,8,8,"SPLAT FILE,S,M" 
          10 OPEN 1,8,3,"SPLAT*,S,M" 
 
In each case, the file type ("S") can be replaced by any other
valid file type (U, P, or R).  Once a file has been opened in the
modify mode, it can be read just like any other data file.  Care
should be taken to check the data in the file to ensure that the
bytes transferred still represent desired data.  Because a SPLAT
file will not have a valid end of file marker (EOF), it may
contain some garbage bytes at the end.  These should be discarded
before writing the new file.  More on SPLAT files can be found in
section 7.1.5 of this chapter. 
  
Reading 
In BASIC, a disk file can only be read in program mode.  Two
commands can be used: INPUT# and GET#.  Both commands are
available in all versions of Commodore BASIC, however, the effect
of the INPUT# command may vary slightly depending on the version
of BASIC due to the length of BASIC's input buffer.   
 
On the C-128, the input buffer is 160 characters long.  This is
the same buffer which is used for input from the keyboard.  (On
the C-64 and VIC-20 it is 88 characters long.) Just like keyboard
entry, the INPUT# command is limited to reading a maximum of that
many characters at a time.  If a character string in the disk
file is longer, then you must either read the string with a
series of GET#'s or insert one or more carriage returns into the
string to divide it up into smaller chunks.   
 
The following are examples of INPUT# and GET#: 
 
          10 INPUT#3,A$,B$,C$,D 
          20 GET#3,A$,B$ 
 
The first example will read three strings and one numeric value
from logical file 3.  The second example will read the next two
characters from logical file 3.   
 
Although both INPUT# and GET# work with either numeric or string
arguments, it is often convenient to treat all disk data in
string format, then convert to numeric form with the VAL(string)
function where required.  This is because BASIC will accept
anything from disk as a valid string input but will produce a
BASIC error code 24, FILE DATA, error if an attempt is made to
read string data into a numeric variable.  This is similar to the
TYPE MISMATCH error generated when string data is entered from
the keyboard in response to a prompt for numeric input.  
 
The following short example will read a specified SEQ file and
print its contents to the screen: 
 
          10 INPUT"FILENAME TO READ";F$ 
          20 OPEN 8,8,8,F$ 
          30 GET#8,A$:IF ST THEN 50 
          40 PRINT A$;:GOTO 30 
          50 CLOSE 8:END 
 
The above example will work with all versions of Commodore BASIC. 
It will also work with all file types, although generally only
SEQ and REL files will contain data that will make any sense when
read and printed in this manner.  (Remember: what you GET# is
what you see!) 
 
In order for INPUT# to work correctly, the data in the file must
be properly separated or "delimited".  Failure to do so may
result in an attempt to read more characters than the input
buffer can handle, thus causing a BASIC error code 23, STRING TOO
LONG error, or an attempt to read too many data items resulting
in the equivalent to the keyboard input warning message EXTRA
IGNORED and the loss of these data items.  Even if these error
conditions do not occur, the data from the file may be corrupted. 
Both numeric and string data will combine into longer non-usable
entries if not properly delimited.  Proper data delimitation is
discussed in the next subsection on writing files.  Delimitation
is not required for reading a file with GET# commands since this
method returns one character at a time, regardless of its value. 
 
The variable list included with the INPUT# statements must be
properly formulated to match how the data are stored in the disk
file.  If sufficient data are not available in the file to fill
all of the requested variables, INPUT# will essentially wait
forever expecting to read more data from a file which has no more
to give.  GET# will return a null character for extra requests,
but will not cause lock up. 
 
In addition, multiple data items which have been delimited in the
disk file by commas must be read with a single INPUT# statement. 

 
Writing  
In BASIC, writing to SEQ files is done using the PRINT# (or
PRINT#, USING;) command.  A simple example would be: 
 
          PRINT#1,A$ 
 
More frequently, a number of items will be written to the same
data file.  In this case, the data must be properly separated or
"delimited" to allow the data to be read again correctly. 
Commodore BASIC and DOS recognize a number of delimiters.  The
simplest, and perhaps most versatile, is the carriage return
(ASCII character code 13).  A carriage return can be placed in a
file by two methods: explicitly or automatically.  The explicit
method has the following format: 
 
          PRINT#1,A$;CHR$(13);B$;CHR$(13);C$; ... etc 
 
The carriage return can also be asssigned to a string variable: 
 
          CR$=CHR$(13) 
          PRINT#1,A$;CR$;B$;CR$;C$; ... etc  
 
The second method of inserting carriage returns into a file takes
advantage of the automatic carriage return added by BASIC to the
end of a PRINT or PRINT# statement with no continuation character
(, or ;) specified at the end of the variable list.  For example:

 
          PRINT#1,A$ 
          PRINT#1,B$ 
          PRINT#1,C$ 
will automatically place a carriage return after each item. 
 
The second delimiter recognized by Commodore BASIC and DOS is the
comma ",".  This must be added to the file explicitly in a form
such as: 
 
          CO$="," 
          PRINT#1,A$;CO$;B$;CO$; ... etc 
 
The third delimiter is the colon ":".  This acts in its own
peculiar manner: everything after a colon, up to a carriage
return, will be skipped over.  Data formatted in this manner can
only be read with a series of GET#'s. 
 
Although all three of the above delimiters can be used to
separate data, there is a significant difference in the way that
the carriage return acts compared to the comma.  Similar to the
INPUT statement for keyboard data input, commas are used to
handle multiple data items which will always be read by a single
INPUT# statement, while carriage returns will work for both
single and multiple INPUT#'s in any combination.   
 
The fourth type of delimitation involves the use of quotation
marks around strings.  The quotes can be used to delimit strings
which contain commas or colons.  If, for example, you wanted to
write the following to a disk file as a single string: 
          LAST NAME, FIRST NAME : GOLF HANDICAP 
it must be enclosed in quotes or else it will be interpretted as
three different strings.  Quotes must be added explicitly to a
data file: 
 
     PRINT#1,CHR$(34)+"LAST NAME, FIRST NAME : GOLF HANDICAP"
             +CHR$(34) 
 
In order to insert quotes, you must use the expression CHR$(34)
or assign it to a string variable (such as Q$=CHR$(34)).  You
cannot insert a quote by simply printing it PRINT#1," because
BASIC uses the quote mark to delimit string constants for
printing.  Alternatively, the trailing quote can be replaced by
either an explicit or automatic carriage return.  Unfortunately,
there is no way to use INPUT# to read a string which contains a
quote mark from a disk file.   In this case, you must use a GET#.
 
The PRINT# USING command can be used to write preformatted output
to a disk file.  This may seem to be of less use than sending
such formatted output to the screen or printer, unless the file
is intended to be input for a word processor document or another
program which expects data in a rigid format.  However, PRINT#
USING can be used to delimit data.  For example, the following
statement will output properly delimited numerical data to
logical disk file 1: 
 
          PRINT#1,USING "####.##,";A,B,C 
 
Note the comma at the end of the definition string.  Each item in
the list will be printed separated by a comma.  Alternatively, a
carriage return can also be used in the following manner: 
 
          B$="####.##"+CHR$(13):PRINT#1,USING (B$);A,B,C 
 
The use of "=" or ">" in the format definition string will allow
text to be centered or right justified in the character field. 
 
Disk files can also be written with the aid of the CMD command
which re-directs the normal screen output to an OPEN file.  For
example, the most common way to create a disk file listing of a
BASIC program or assembly language source code (perhaps to
include it in a document or to upload it to an electronic
bulletin board system  (BBS)) is: 
 
          OPEN 8,8,8,"LISTING,S,W" 
          CMD 8 
          LIST 
          PRINT#8:CLOSE 8 
 
This is generally done in immediate mode, although the same
technique can be used in program mode to re-direct a program
output from the screen to a disk file or printer.  In this latter
case, regular PRINT statements will send output to the open file. 
The CHAR statement used in BASIC 7.0 for placing text at certain
locations on a text or graphics screen will always send output to
the screen.  CHAR cannot be used for writing to a disk file.  
 
 
Closing the File 
A SEQ file is closed using a standard CLOSE {file#} or DCLOSE
type of statement.  It is especially important to properly close
a file which has been written to.  Failure to do so will result
in a SPLAT file which may lead to lost or corrupted data, not
only in the guilty file but in other files on the disk as well. 
A simple PRINT# statement with not variable list is often used
before closing the file.  This ensures that the last of the disk
buffer is written to the disk before the file is closed.  An
example would be: 
 
          PRINT#1 
          CLOSE 1 
 
The PRINT#1 statement in the above example is not applicable if
the file was opened in read or modify mode. 
 
 
Recovering Data From SPLAT Files 
"SPLAT" files are created when a file is not properly closed
after being written to.  This is most frequently caused by sloppy
file management practices or improper error recovery.  The SPLAT
file can be easily identified in the disk directory by an
asterisk (*) next to the file type and 0 blocks showing for the
file length.  Even though 0 blocks are indicated in the directory
entry, the file does contain data and has been allocated disk
space.  Usually this can be seen in a reduced value for the
"blocks free" parameter in the directory listing.  

The correct method for recovering data from, then deleting, a
SPLAT file is outlined below: 
 
  1.  OPEN the SPLAT file in M for modify mode. 
  2.  Read the data in the file with GET#'s and store it          
     somewhere. 
  3.  OPEN a new write file.  
  4.  Write data to new file. 
  5.  CLOSE the new file. 
  6.  Validate (COLLECT) the disk to erase the SPLAT file. 
 
A SPLAT file can also be opened as a standard read file. 
However, a DOS error code 60, WRITE FILE OPEN, error will occur
and the recovered data may not be as reliable as that read in the
M mode.   
 
The following short program can be used for SPLAT file data
recovery: 
 
     10 INPUT "SPLAT FILENAME";OF$
     20 INPUT "FILE TYPE (S, P, OR U)";FT$
     30 INPUT "NEW FILENAME";NF$
     40 OD=8 : ND=8 : REM DEVICE NUMBERS
     50 OPEN 1,OD,5,OF$+","+OT$+",M" : REM OPEN IN MODIFY MODE
     60 OPEN 2,ND,6,NF$+","+OT$+",W"
     70 GET#1,A$ : PRINT A$; : PRINT#2,A$;
     80 GET B$ : IF B$ THEN 100
     90 GOTO 70
     100 PRINT#2 : CLOSE 2 : CLOSE 1

This program will prompt you to enter the splat filename and type
(S, P, or U), and the new filename.  The new file need not be on
the same device as the old (you can change the device numbers in
line 40 if you wish).  The splat file will be read character by
character and printed to both the screen and the new file.  When
you see something on the screen that looks like it should not be
part of the file (i.e. random garbage from the end of the splat
file) you have come to the end of the usable data. (This method
works best with files that contain alphanumeric text only.) 
Press any key to abort the file reading and the new file will be
properly closed.   At this point, the disk with the splat file
should be VALIDATEd to remove the splat file.
 

USeR Files: 
 
User files have no pre-defined structure: they can be totally
defined by the user to suit virtually any purpose.  They are
sometimes used by commercial programs for data files because they
can be easily distinguished in the disk directory by checking the
file type.  In most respects, they can be handled identically to
SEQ files by merely changing the file type designator from S to
U.   

USR files are also used by GEOS as the basis for VLIR files for
data and application program storage.
 

DOS Utility "&" Files 
USR files can also be used for some very special purposes such as
automatic programming of the disk drive.  In Commodore DOS these
are known as ampersand or "&" files, due to the command used to
execute them.

The USR program is "loaded" into the drive's RAM and then
executed with a command such as:
          OPEN 15,8,15,"&0:{filename}"
Note that with the 1571, the "&0:" is not actually part of the filename. (This is different from earlier
drives, such as the 1541, which required the "&0:" to be part of
the filename).  

To create the file, a standard OPEN type statement is used,
followed by PRINT#'ing the data to it, just like a SEQ file.

For example the following forms can be used in BASIC 2.0 and
3.5/7.0 respectively.
          OPEN 8,8,8,"0:DOSPRG,U,W"
          DOPEN#1,"DOSPRG,U",W 
Note that the file type specifier "U" must be explicitly stated,
otherwise a SEQ file would result. 

The format of a DOS "&0:" file must be strictly adhered to as
outlined below.  The file consists of a header, which tells DOS
where to put the file in the drive's memory; the machine language
program, in standard 6502 machine code; followed by a checksum
byte.  The specific byte usage is summarized in Table 7-1.


Table 7-1: DOS "&" File Structure 
.............................................................
Byte                     Usage                     
..............................................................
0,1                      Track and sector link (for long files)
                         (normally handled by DOS automatically) 

2,3                      Load address (lo byte/hi byte).  This is
                         also the start of execution address.  To
                         avoid crashing, this should be one of
                         the disk buffers.

4                        Number of bytes to follow (except for
                         checksum).

5-end                    Machine code bytes.

end+1                    Checksum = sum of lo and high bytes of
                         bytes 2 to end added together.
................................................................

The length refers to the length of the machine language and can
be up to 250 bytes long.  If your program is longer, it must be
broken up into 250 byte segments, each with its own length, load
address and checksum bytes.  This leads to the interesting
observation that a DOS "&" file does not need to occupy
contiguous areas of RAM.  For example, the address bytes for the
first sector might specify $400 as the starting point, while a
second sector in the file may jump to $700.

Since the 1571 is controlled by a 6502 processor, the code can be
developed on any standard C-64/VIC-20/C-128 etc. type of
assembler or monitor.  Control is returned to DOS from your
program by a simple RTS at the end.  The file can be written as a
byte string or you can use a sector editor.  If using a sector
editor, you must properly select the track and sector link bytes
yourself.  

The check sum consists of the sum of bytes 2 through to the end
of the machine language.  (The address bytes, and length byte are
included in the sum.)  The low and high bytes of this sum are
added together to form the checksum byte which is included at the
end of the file.

It should be noted that if you wish your program to remain active
in the drive after it stops executing, you will have to allocate
the buffer into which it was placed by opening it as a direct
access buffer.  If you do not do this, DOS will use the buffer
for its own purposes and overwrite your program possibly causing
a spectacular crash.
     
The following simple example will turn off the re-try after disk
errors to prevent head chatter on heavily copy protected disks:

          10 OPEN 1,8,1,"0:KNOCK-IT-OFF,U,W"
          20 FOR Z=0 TO 8:READ X:PRINT#1,CHR$(X);:NEXT:CLOSE 1
          30 DATA 0, 3, 5, 169, 197, 133, 106, 96, 199

The machine code represented by the data bytes 169 to 96 is:
          169 197  LDA #$c5
          133 106  STA $6a
          96       RTS

The file will load and execute at $300.  It is not necessary to
protect this buffer, because the program is not required to be
memory resident.

The second example will turn off the "verify after write" of the
1571 for job buffer #0.  This will greatly speed up writes to the
disk from this buffer, but it should be used with extreme caution
because it changes some very important vectors in the drive's
operating system and may cause a crash if not used properly.  It
is shown here as an example only.  Be aware of what you are doing
and the possible consequences before trying to use it yourself.

     1 REM CREATE & FILE
     10 OPEN 8,8,8,"V-OFF,U,W"
     20 FOR I=0 TO 32 : READ X : PRINT#8,CHR$(X); : NEXT
     30 PRINT#8 : CLOSE 8
     40 DATA   0,   6,  28 : REM FILE HEADER
     50 DATA 120, 169,  13, 141, 169,   2, 169,   6 : REM CODE
     60 DATA 141, 170,   2,  88,  96,  72, 165,   0
     70 DATA 201, 160, 208,   4,  69,   0, 133,   0
     80 DATA 104,  76, 222, 157
     90 DATA  86,   0,   0 : REM CHECKSUM AND FILLER

     1 REM TO USE THE FILE
     10 OPEN 9,8,9,"#3" :         REM RESERVE BUFFER #3
     20 OPEN 15,8,15,"&0:V-OFF" : REM ACTIVATE CODE
     30 (. . . the rest of your program)
     .
     .
     .
     60000 PRINT#15,"UJ" : REM RESET DRIVE BEFORE GOING
     60001 CLOSE 9 : CLOSE 15

Note that you should not close either the command channel or the
buffer channel until just before exiting from your program.  The
above routine should be used in program mode only, for special
cases, such as a disk copying program, which operate under well
defined conditions.  The program can cause considerable damage to
a disk if not used properly.

The source code for the machine language portion is as follows:

.ORG $300           ; NOT RELOCATABLE
     SEI            ; DISABLE INTERUPTS
     LDA #<VEROFF   ; LOW BYTE OF NEW INTERUPT VECTOR
     STA $02A9      ; SAVE IT
     LDA #>VEROFF   ; REPEAT FOR HIGH BYTE
     STA $02AA
     CLI            ; RESTORE INTERUPTS
     RTS            ; AND EXIT
VEROFF =*           ; NEW INTERUPT HANDLER
     PHA            ; SAVE .A
     LDA $00        ; GET JOB CODE FOR BUFFER #0
                    ; CAN USE OTHER BUFFERS ALSO
     CMP #$A0       ; CHECK FOR VERIFY
     BNE CONTINUE   ; NO? THEN JUMP TO OLD INTERUPT
     EOR $00        ; YES? THEN CANCEL IT
     STA $00        ; AND STORE IT BACK
CONTINUE =*         ; EXIT TO OLD INTERUPT ROUTINE
     PLA            ; GET BACK .A
     JMP $9DDE      ; AND EXIT



PRoGram Files:

Program or PRG files generally contain an image or exact copy of
a given area of a computer's RAM.  The first two bytes of the
file, known as the load address, contain the starting address (in
standard low byte/high byte format) of the area of memory
contained in the file.  The potential length of a PRG file is
limited only by the amount of RAM available in the computer. 
Since the maximum addressable RAM in the 65xx/75xx/85xx series of
microprocessers used in most of the Commodore 8 bit computers is
64 K bytes (65535 bytes), a file of this type is, therefore,
limited in length to somewhat less than 64 K bytes.  (Actually,
it is equal to 65535 - load address). 
 
Although a PRG file will normally contain a program (either
machine language, tokenized basic or some other tokenized
language), it can contain anything stored in the computer's
memory.  This may be a high resolution graphics screen, sprite
definitions, color maps, text files from a word processor,
assembly language source code, etc.  It can even be used to store
data from the variables definition area of the BASIC workspace.  

Creating a PRG File 
A PRG file is normally created with the SAVE type commands (SAVE,
DSAVE, BSAVE, MONITOR-S, or their KERNAL equivalents).  With a
disk drive, a PRG file can also be used for storing data,
identical to a conventional SEQ (sequential) file.  (The DOS
makes no distinction internally as to how data are stored in a
SEQ and PRG file.  The only difference to DOS is the value of the
file type flag in the directory entry.)  The procedure for
accessing a PRG file as a data file is the same as for a SEQ file
except that the {file type} specifier in the OPEN statement must
be "P".  PRG files can be either read or written this way.  SEQ
files are discussed in more detail in the first section of this
chapter. 
 
The following example can be used to save a segment of memory to
a disk file using BASIC 2.0.  (In BASIC 3.5, this can be done
with the MONITOR-S command, while in BASIC 7.0 it can be done
with either MONITOR-S or BSAVE.) 

          10 INPUT"FILENAME";F$ 
          20 INPUT"STARTING ADDRESS";LO 
          30 INPUT"END ADDRESS";HI 
          40 OPEN 8,8,8,"0:"+F$+",P,W"
          50 L2=INT(LO/256):L1=LO-L2*256 
          60 PRINT#8,CHR$(L1);CHR$(L2); 
          70 FOR I=LO TO HI:PRINT#8,CHR$(PEEK(I));:NEXT 
          80 PRINT#8:CLOSE 8 
 
Although the example is given as a program, the same technique
can be used in immediate mode if LO and HI are explicitly defined
instead of INPUT from the keyboard. 
 
Reading a PRG File 
     A PRG file is generally recovered from disk using the LOAD
commands (LOAD, DLOAD, BLOAD, BOOT, RUN, MONITOR-L, or their
KERNAL equivalents).  A PRG file is the only type of file which
can be recovered by the LOAD commands.  It can, however, be
opened and read with the SEQ file handling commands GET#, and
even INPUT# if the data are in a suitable format. 
 
The following is a short BASIC 3.5/7.0 demonstration program
which will open a PRG file as a sequential file, read the first
two bytes and determine the load address.  This is handy if, for
example, you have forgotten the SYS address to start a machine
language program, or you want to examine it with a disassembler
and don't know where it resides in memory. 
 
          10 TRAP 70 
          20 INPUT"ENTER FILENAME";F$ 
          30 DOPEN#1,F$+",P" 
          40 GET#1,A$,B$:DCLOSE#1 
          50 AD=ASC(A$)+ASC(B$)*256 
          60 PRINT"PROGRAM "F$" START ADDRESS =";AD:END 
          70 PRINT"ERROR "ERR$(ER):RESUME 20 
   
The TRAP statement in line 10 is used to divert the program to
line 70 if an error occurs.  The specific error that may be
encountered is a "FILE NOT FOUND".  This error will be detected
by BASIC before it is picked up on the disk error channel.  

The equivalent program in BASIC 2.0 would be: 
 
          10 INPUT"ENTER FILENAME";F$ 
          20 OPEN 1,8,1,"0:"+F$+",P,R" 
          30 GET#1,A$,B$:CLOSE 1 
          40 AD=ASC(A$)+ASC(B$)*256 
          50 PRINT"PROGRAM "F$" START ADDRESS =";AD:END 
 
It should be noted that the BASIC 2.0 version will not trap the
FILE NOT FOUND error.  If the specified filename is not on the
disk, the program will simply stop. 
 
Combining BASIC Programs 
Many people tend to write BASIC programs in a "modular fashion". 
You can create a library of standard subroutines, or program
modules, to perform such tasks as selectively reading a disk
directory, bubble sorting, disk file handling, screen formatting
and graphics, etc.  When creating the first draft of a new
program, much of it can be produced by combining some of these
standard subroutines.  Customization and optimization are
relatively minor tasks compared to retyping a number of twenty to
thirty line, or more, subroutines every time you want to use
them.  

The whole concept of combining several subroutines or program
files into a single program usually depends on the existence of a
BASIC command called MERGE, which performs this task
automatically.  Although Commodore DOS supports the combining of
two or more data files (using APPEND or CONCAT), a MERGE command
for BASIC programs is not available as an intrinsic command in
any version of Commodore BASIC, not even the newest version 7.0
on the C-128.  MERGE is available, however, with several BASIC
extenders such as the Programmers' Aid Cartridges for the VIC-20
and C-64.    
 
In order to understand how appending BASIC programs works, we
must first take a look at the structure of a Commodore BASIC
program (PRG) file.  A BASIC program line starts with two link
bytes followed by two bytes representing the line number.  This
four byte sequence is followed by the text of the BASIC line with
keywords in tokenized form (i.e. BASIC keywords, such as INPUT,
PRINT, INT(, etc.  are stored as single or double byte "tokens"
rather than spelled out).  The end of the line is designated by a
zero byte (not the character zero "0", but ASCII character code
zero CHR$(0)).  

The key to creating a program file with many lines is to
establish the line links.  These links are absolute addresses in
low byte high byte format which tell the BASIC interpreter where
to find the next BASIC line link.  A program file ends with three
zero bytes (an end of line zero byte plus two zero bytes to
indicate that there are no more line links).  Normally, when you
add more BASIC lines from the keyboard or edit an existing line,
the link bytes are automatically adjusted to reflect the new
situation.  In addition, in most Commodore computers, BASIC
programs are relocatable when they are LOADed, that is, programs
will be LOADed back into memory at the start of BASIC pointer,
regardless of where they were originally SAVEd from.  This allows
you to temporarily move the BASIC workspace around in memory (to
reserve memory for graphics screens, for example) and to LOAD
programs written on one computer with a different one.  In this
process, the link bytes are automatically adjusted during program
execution or LISTing to reflect the new RAM address locations.   
 
It is very simple to combine any number of program modules into a
single BASIC program.  Depending on the computer used and the
length of the modules, one of several methods can be used. 
 
Although it will work with program modules of any length, the
first method is generally only used when at least one of the
program modules is relatively short (less than about 20 lines). 
This method, which incidentally can be used on any brand or model
of computer which features a full screen BASIC editor, consists
of only a few simple steps and does not involve any additional
programming.  The procedure is as follows: 
 
1.   LOAD the shorter program module into memory.   
2.   LIST on the screen the section of the module to be
     combined with the other program.  If you are using a
     C-128, you should switch to the 80-column display if
     possible because you can fit more on the screen.  You
     can even use both the 40 and 80 screens, if you have
     two monitors or a switchable one.  On all Commodore
     computers you must leave at least five blank lines at
     the bottom of the screen.  If you can't LIST everything
     on the screen at once that's OK, you can repeat steps 1
     to 5 as many times as you wish.   
3.   Without erasing any of the LISTing of the first program
     on the screen, cursor up as far as you can go (it is OK
     to erase the "READY" message) and type in the command
     to LOAD the second program into memory.  Normally when
     you do this, the program originally in memory will be
     erased and replaced by the newly LOADed program. 
     Fortunately, some of the old program can be "protected"
     in the form of a screen listing.   
4.   With the cursor control keys, cursor up to the lines of
     the first program that you wish to add to the second
     and then press the <RETURN> key.  Before pressing
     <RETURN>, you can change the line number or perform any
     other editing on the line that you wish.  Do this for
     all the lines displayed on the screen that you wish to
     add to the second program.  The lines from the first
     program will be added to the second program just as if
     you had re-typed them on the keyboard.   
5.   SAVE the combined program under a new or temporary
     filename.   
6.   Repeat steps 1 to 5 until all desired lines have been
     added to the new program. 
 
This technique takes advantage of the fact that the BASIC input
editor can read data and program lines directly from the screen. 
LISTing a program, or a portion of a program, on the screen will
"protect" the listed portion from being erased when you LOAD in a
new program.  The protection lasts only as long as the lines are
displayed on the screen.  When you clear the screen, the
protected program will be erased also.  Obviously, the longer the
programs being merged with this technique are, the more times you
will have to repeat the above procedure to complete the merger. 
The process is, however, extremely flexible in that it works on
virtually any computer and you can select and edit the individual
lines from one program to be merged with the other.  The main
disadvantage of the procedure is that you are limited in the
number of lines which can be displayed on the screen and thus
merged at a given time.   
 
The second method works on entire program files rather than just
a segment of one.  It involves LOADing one program into memory,
setting the start of BASIC pointer to the end of the first
program and LOADing the second program.  The start of BASIC
pointer is then re-set back to its original value and the
combined program is SAVEd.  This can be done in either immediate
or program mode.  On a C-64, C-16, Plus/4 or VIC-20, the
immediate mode procedure is as follows: 

1.   LOAD"first program",8
     This puts the first program into memory normally.  This
     step may be skipped if the desired program is in memory
     already.

2.   PRINT PEEK(43),PEEK(44)
     Note down these two numbers for future reference in
     step 7 (referred to as "{first#}" and "{second#}",
     respectively).

3.   HI=PEEK(45)+PEEK(46)*256-2 <RETURN>
     This is the end of file address for the first file. 
     Remember to include the "-2".  This will compensate for
     the two zero bytes used as the end of file marker.

4.   POKE 44,HI/256:POKE 43,HI-PEEK(44)*256 <RETURN>
     This sets the start of BASIC pointer to the end of the
     first file.

5.   NEW <RETURN>
     This resets all of the other BASIC pointers to match
     the new start address.

6.   LOAD"program 2",8 <RETURN>
     The second program is now loaded in at the end of the
     first.

7.   POKE 43,{first#}:POKE 44,{second#} <RETURN>
     This resets the start of BASIC pointer to its normal
     location.  The combined program is now ready to be
     SAVEd or used.

The following short program can be used on the C-64 to perform
the same task in program mode: 
 
10 REM AUTO PROGRAM COMBINER   
20 POKE 56,PEEK(44)+5:CLR:DE=8:L0=PEEK(56)*256+PEEK(55)
   :POKE L0-1,0   
30 INPUT"  ROOT FILE NAME   >>";RF$
   :INPUT" APPEND FILE NAME >>";AF$ 
40 INPUT" MERGED FILE NAME >>";MF$
   :F$=RF$:L=L0:GOSUB 60:L=MH-2:F$=AF$:GOSUB 60   
50 SA=1:F$=MF$:L=L0:GOSUB 60:PRINT"        ***DONE***":END   
60 PRINT"      *INSERT "F$" DISK*":PRINT"THEN PRESS A  KEY" 
   :POKE 198,0:WAIT 198,1   
70 FOR I=1 TO LEN(F$):POKE 827+I,ASC(MID$(F$,I,1)):NEXT
   :POKE 780,LEN(F$): POKE 781,60   
80 POKE 782,3:SYS 65469:POKE 780,1:POKE 781,DE:POKE 782,SA
   :SYS 65466:IF SA THEN 100    
90 POKE 780,0:POKE 782,L/256:POKE 781,L AND 255
   :SYS 65493:GOTO 120   
100 POKE 252,L/256:POKE 251,L AND 255:POKE 780,251   
110 POKE 782,MH/256:POKE 781,MH AND 255:SYS 65496   
120 OPEN 15,8,15:INPUT#15,DS,DS$:CLOSE 15
    :IF DS THEN PRINT"   "DS;DS$:END   
130 MH=PEEK(781)+256*PEEK(782):RETURN    
 
When you run the program, you will be prompted to enter three
things:  
 
       the ROOT file name;  
       the APPEND file name; and   
       the MERGED file name. 
The ROOT file is the program or module which contains the lowest
line numbers as outlined below.  The APPEND file is the program
or module which contains the highest line numbers.  The relative
lengths of the ROOT and APPEND files are not important.  The
MERGED file name is the name under which you wish to store the
new combined program file.  All three file names should be
different unless you want to use the infamous "SAVE with replace"
DOS option for saving the new file.    
 
The example will also work on the VIC-20 and on the C-128 when it
is in C-64 emulation mode.  In addition, because of its
simplicity of operation it is easily adaptable to any other
Commodore computer which is capable of relocating BASIC programs
during a normal LOAD.   
 
The program will prompt you to insert the disk with each file on
it then press a key to continue.  When the ROOT and APPEND files
have been loaded, the program will prompt you to insert the disk
to save the combined file on and then save it. The three files
need not be on the same disk.     
 
There is one relatively minor restriction on the line numbering
of the programs being appended either in immediate or program
modes.  Since these methods work by appending all of the lines in
one program module to the end of the another program module, the
line numbers of the second program module must all be greater
than the line numbers of the first module.  If you do not adhere
to this restriction, some odd things may happen to the combined
program.  For example, when you list the program you may find
that line 50 comes after line 100 or before line 20.  This can be
avoided by ensuring that the line numbers are in correct sequence
before merging the programs.   In BASIC 3.5 or 7.0, the combined
programs can be renumbered with a RENUMBER command.  If duplicate
line numbers are included, as above, they will be correctly
renumbered to reflect their sequence in the combined program but,
references to the duplicate lines such as GOTO's and GOSUB's will
not be correctly renumbered. 
 
The third way to combine program files on the C-128 is to use the
programmable function keys to store the command sequence for the
immediate mode method.  The following is a short BASIC program
which redefines function key <F2> and SAVEs the new key
definitions in a disk file called "MERGE".  Enter and run this
program first to create the MERGE file.   
 
10 REM FUNCTION KEY MERGE SETUP  
20 A$=CHR$(13)+"POKE 250,PEEK(45):POKE 251,PEEK(46):  
   X=PEEK(4624)+PEEK(4625)*256-2:POKE 45,X AND 255: 
   POKE 46,X/256"+CHR$(13) 
30 B$="DLOAD(ME$)"+CHR$(13)+"POKE 45,PEEK(250):  
   POKE 46,PEEK(251)"+CHR$(13): KEY 2,(A$+B$)
40 BSAVE"MERGE",B0,P4096 TO P4352 
 
To use this MERGE file, follow these three easy steps:  
 
1.   Before you start an editing session, type in:
     BLOAD"MERGE",B0    <RETURN>
     to retrieve the special function key defintions.   
2.   LOAD the first program (the ROOT program as described
     above) or enter it into memory from the keyboard.   
3.   When you want to add a previously SAVEd program module 
     (equivalent to the APPEND file described above), type
     in ME$="filename2", then press function key <F2>
     instead of the <RETURN> key.  Step three can be
     repeated as often as you wish without having to reload
     the MERGE definition file each time. 
 
A series of BASIC commands will be printed on the screen and
executed while the disk drive comes on for a moment.  The
function key method of appending requires the same care with line
numbers as the other program appending methods outlined above. 
It should also be noted that no error checking is done by the
function key, so make sure that the file you specify as ME$
actually exists on your current disk or strange things may
happen.  Not to worry though, a simple <RUN/STOP>-<RESTORE> key
sequence will abort the function key commands if necessary.  
 
The final method takes advantage of a peculiarity in the KERNAL
ROM of the C-128 and involves MERGEing a SEQ file listing with a
BASIC program using the CHKIN in immediate mode technique
described in Chapter 6 as a sequential disk command file (SDC). 
The input routine in the C-128 KERNAL will accept line numbers in
SDC's.  These line numbered files will "execute" exactly as if
the line, complete with line number, had been entered from the
keyboard.  That is, it will be added to any program currently in
memory.  This little trick is a simple yet powerful way to MERGE
two or more program files on the C-128.  Unlike other psuedo
merge routines which merely append one program to the end of
another, this technique allows full intermeshing of line numbers. 
Only a few steps are required.  First, LOAD one of the programs
into memory in the normal manner or enter it from the keyboard. 
Next convert it into a SEQ disk file listing with a series of
commands such as: 
 
      OPEN 8,8,8,"0:filename,S,W":CMD8:LIST 
      PRINT#8:CLOSE8 
 
LOAD in the second file or enter it from the keyboard.  With the
second file now in memory, read in the SEQ listing of the first
file with:  
      DOPEN#8,"filename" 
      SYS 65478,0,8 
 
After a brief period, the programs will be fully merged. The
merge will terminate with a mysterious "OUT OF DATA" error
message.  This is a good sign:  the process worked.  The error
message is caused by the last line of the listing in the disk
file "READY.". (Commodore BASIC listings always include this.) 
The computer, not being able to recognize its own writing, thinks
that someone typed in "READ Y".  Since there are no accompanying
DATA statements, the out of data error occurs and control is
restored to the keyboard.  If the READY. message did not appear
at the end of the listing (for example if you edited it out with
a word processor to give it a neater appearance), keyboard
control would only be restored with a <RUN-STOP>/<RESTORE>.   The
programs will, however, be merged correctly.  This technique can
also be used for "loading" program listings produced on machines
with incompatible keyword tokens and programs transferred into
SEQ files via a modem download. 
 
This method is perhaps the easiest way to implement a MERGE
command on the C-128.  In additon, it is the only one of the
methods presented which gives you the fully MERGEd program in
RAM, ready to run when it is completed.       
  
 
RELative Files: 
 
Relative (REL) files are a convenient method for storing and
retrieving a series of similar or otherwise related data that may
be edited, added to, searched, sorted, etc.  REL files are the
only type in Commodore DOS that allow the simultaneous input and
output of data to the same open file.  In other operating
systems, such as CP/M or MS-DOS, this concept is known as a
"random" file.  (Unfortunately, Commodore uses the term "random
file" to signify access to specific tracks and sectors on a
disk.)   
 
Whatever you choose to call it, a relative file handles data in
packets of uniform size called "records".  Each record may be
further subdivided into logical or physical "fields", each of
which contains a specific item.  For example, a record may be an
page in an address file.  Each record in this example would
typically have a field for name, address, city, zip code, phone
number, etc.  The total length of the record is the sum of the
lengths of each of the fields.  This physical record length is
fixed when the file is first created.  

The length of each logical field within the record is usually
fixed and the fields are always entered in the same order. 
Variable length (floating) physical fields can also be used, but
the total length of a record still remains fixed.  The choice of
fixed or floating fields must be made when the datafile is first
created because it will affect how data are read from and written
to the file.  Once the choice has been made, you must stick with
it. 
 
In Commodore DOS, a relative file consists of two parts: "data
sectors" and "side sectors".  The data sectors contain the file
data in a format identical to other file types.  The side sectors
contain the sector linking information for all data sectors in
the file.  This is used by the operating system to access each
record selectively without having to trace through the sector
link chain.  
 
Creating and maintaining REL files has never been easier than
with the C-128.  The procedure can be broken down into four easy
steps: 
 
     1.  OPEN the file 
     2.  find the record you want 
     3.  read or write the desired data 
     4.  CLOSE the file. 
 
Each step will be discussed in turn below. 
 
Opening a Relative File 
To create a REL file, or to read or edit an existing file, the
DOPEN statement is used in BASIC 3.5 or 7.0.  This takes the form
of:  

          DOPEN#{file#},"{filename}",L{record length}  

In BASIC 2.0, the following procedure is used: 
 
     OPEN {file#},{device#},{channel#},"{filename},L,"
                                        +CHR$({record length}) 
 
In both cases, {file#} is the logical file number associated with
the file for future input and/or output operations;  "{filename}"
is the name under which the file is stored in the disk directory 
(Relative files are denoted by the identifier REL listed after
the filename in the disk directory.); and  {record length} is the
length in bytes to be used for each record in the file.  {record
length} need only be specified when a file is first created.  The
record length for a previously created file is stored as part of
the file pointers in the directory entry and the side sectors. 
You should not attempt to change the length once a file has been
created.  If you incorrectly specify the record length for an
already existing file, a DOS error code 50, RECORD NOT PRESENT,
error will occur.   
 
If "{filename}" does not exist, then a new file will be opened
automatically, otherwise the existing file is opened for reading
and/or additions.  The {channel#} parameter in the BASIC 2.0
version is used in conjunction with the record positioning
procedure used by BASIC 2.0.  Because the {channel#} is not
specified explicitly with the BASIC 3.5/7.0 version, you should
not mix the record specifying methods.  That is, if you OPEN the
file using BASIC 2.0, you should use the BASIC 2.0 record
positioning method.  Likewise, if you DOPEN the file from BASIC
3.5 or 7.0 you should use the BASIC 3.5/7.0 record positioning
method.  
 
When setting up a relative file with fixed length fields, it is
necessary to add up all of the character spaces required for each
field, including punctuation, carriage return characters, and
"hidden" items such as leading spaces associated with numeric
values, to determine the required record length.  It may be wise
to add a few extra spaces for contingency.  The other approach is
to define a series of "floating" fields.  The length of each
individual field can vary from record to record, however, the
total length of the record remains fixed and can be estimated
using the procedure outlined above for fixed fields.  
 
In either case, the maximum record size allowed by Commodore DOS
is 254 characters and the minimum is 2.  The optimum record sizes
based on speed and ease of access at the internal DOS level is
either 127 bytes or 254 bytes.  This corresponds to a half or
whole data sector.  It should be noted that Commodore DOS, as
used on the 1571 drive, only permits one relative file to be OPEN
at a time.  This is due to a limitation on buffer space in the
disk drive.  

When a REL file is first set up, it is initially assigned 1 data
sector and 1 side sector.  Unused record space is filled with a
CHR$(255) in the first position of a record followed by a series
of CHR$(0)'s in the remainder of a record.  As the file grows,
more data sectors are assigned, one at a time, and side sectors
are added as required.  The number of side sectors used depends
on the number of records. A maximum of 6 side sectors can be
associated with a single relative file.  This will map out a
total of 720 data sectors, although only 612 are actually used. 
Each additional data sector is automatically filled with a
CHR$(255) and CHR$(0)'s as it is allocated.   
 
The following BASIC 7.0 technique can be used to determine the
record length of an unknown file.  The method relies on detecting
the DOS error code 51, OVERFLOW IN RECORD, error. 
 
     10 DOPEN#1,"filename" 
     20 FOR I=2 TO 254:RECORD#1,1,I:IF DS<>51 THEN NEXT 
     30 PRINT "RECORD LENGTH IS"I-1" BYTES":DCLOSE 
 
 
The equivalent in BASIC 2.0 is: 
 
     10 OPEN 1,8,1,"filename":OPEN 15,8,15 
     20 FOR I=2 TO 254 
     30 PRINT#15,"P"+CHR$(97)+CHR$(1)+CHR$(0)+CHR$(I) 
     40 INPUT#15,DS:IF DS<>51 THEN NEXT 
     50 PRINT"RECORD LENGTH IS"I-1" BYTES":CLOSE 1:CLOSE 15 
 
Finding a Record 
Once the REL file has been opened, it is ready to accept or
provide data to the host program.  Since the file is actually a
collection of records, you must first determine where in the file
you wish to read or write the data.  Unlike conventional SEQ data
files, where you must read or write each byte in sequence before
you can read or write the next, you can read from, or write to,
any record (in any order) with REL files.  The same file can also
be used for both reading and writing simultaneously.  In BASIC
3.5 and 7.0, the RECORD command is used to position the relative
file pointers to the correct location for subsequent input and/or
output.  The syntax of the command is:  
 
       RECORD#{file#},{record#}[,{byte#}] 
 
This allows data to be read from or written to {record#} of
logical {file#}, optionally beginning at {byte#}.  The {byte#}
parameter is generally only used for fixed field length record
formats.  A {byte#} value of 1 can be included for floating
fields to ensure that the byte pointer is always positioned at
the first character in the record.  If {byte#} is greater than
the record length, a DOS error code 51, OVERFLOW IN RECORD, error
will result. 
 
In BASIC 2.0, record pointers are set through the disk drive
command channel in the following manner (assuming file #15 has
been previously opened as the disk command channel): 
 
 
  PRINT#15,"P"+CHR$(96+{channel#})+CHR$({rec#lo})+CHR$({rec#hi}) 
                [+CHR$({byte#})] 
 
where {channel#} is the channel (secondary address) used in the
OPEN statement for the file, and {rec#lo} and {rec#hi} are the
low and high bytes of the desired record number, respectively. 
{byte#} has the same meaning as in the BASIC 7.0 RECORD command. 
Although apparently not technically necessary for the 1571 drive,
a 96 is added to the {channel#} parameter for consistent
operation with other Commodore disk drives. 
 
The 1571 Disk Drive Users Guide recommends that the RECORD
command (or its BASIC 2.0 equivalent) be issued once before and
again after any attempt is made to read or write the file.  While
this precaution may seem unnecessary to some, it is wise to use
it to prevent the corruption of data in your file if a major
error occurs during attempted file access and, more importantly, 
to allow adequate time for DOS to locate and read in the desired
record.  This is especially important for "odd" sized records. 
(That is, anything but a half sector (127 bytes) or full sector
(254 bytes) record size.)  Another method frequently used to wait
until the record pointers have been correctly set is to read the
disk error status immediately after issuing the record
positioning pointers.  This will "tie-up" any further
communication between the computer and the disk drive until after
the record has been located and read into the buffer.  This
latter method is often used with machine language and KERNAL
calls.   It is also necessary to reposition the pointers for a
direct read-after-write or write-after-read, to ensure correct
placement of the data. 
 
If you attempt to read or write to a record which is outside of
the range currently defined for the file, a DOS error code 50,
RECORD NOT PRESENT, error may occur.  If trying to read a record,
this error indicates that you have gone past the last data sector
used in the file.  (Depending on the size of your record, you may
be able to go one or two records past the current last record
before you get this error.  This is because the assigned space
for the record is already included in the sectors previously read
into the disk buffer.)  An unused record can always be identified
by a CHR$(255) as the first character in the record.  If trying
to write a record, this is not an error message but only an
indication that you are writing a new record.   
 
The maximum record number allowed is 65535.  The minimum is 1. 
The maximum file size (# of records x record length) that is
currently supported on the 1571 drive is 167,132 bytes (163 K
bytes).  This is the same for both single and double sided disks. 
(I.e. relative files must all fit on one side of the disk.) 
 
Relative files have a habit of growing uncontrollably.  You
cannot "delete" records from the file.  The most you can do is
copy good records from a file into a new file, then scratch the
old file.  If the file grows to take over your whole disk, a DOS
error code 52, FILE TOO LARGE, error will result.  It is then
time to split the file into several smaller ones, or too purge
out all the unused records. 
 
Input and Output 
With the relative file pointers correctly positioned, you can
proceed with input and output using the normal disk file commands
such as INPUT#, GET#, PRINT#, and PRINT#,USING.  In this respect,
relative files are no different than any other type of disk file. 
However, because each record length is fixed, you should always
check the length of anything being written to disk to make sure
that it will fit.  If you try to overrun a record boundary (i.e.
there is not enough space in the record to contain all of the
data), a DOS error code 51, OVERFLOW IN RECORD, error will occur. 
When this happens the extra characters will be dropped or
truncated from the end of the record to make it fit.  It will
not, however, affect the rest of the file.  It should be noted
that not all input and output operations will appear to
activatethe disk drive, especially if the record length is short. 
This is because the disk drive will read two sectors into the
buffer at once.  If the record you try to access is already
contained entirely in the sectors in the buffer, no visible disk
activity will take place.  (When two sectors are in the disk
buffers, at least one record of 254 bytes or less will always be
in memory, no matter how it is physically contained on the disk.)

Input 
On the C-128, the input buffer is 160 characters long. 
Therefore, the INPUT# command can only read 160 characters at a
time.  If your record length is less than 160 characters, then
there is no problem: the entire record can be read at once.  If
it is greater than 160, then you must either read the record with
a series of GET#'s or insert one or more carriage returns into
the record to divide it up into smaller chunks (or physical
fields).  Breaking up a record into physical fields (as opposed
to logical fields used in fixed field length format records) is
the basis of the floating field method of relative file
construction.  Each field is separated by a carriage return and
therefore, can be read directly with a separate INPUT# statement. 
Of course, you can use floating fields with a record length of
less than 160, but remember: in both cases each carriage return
character counts as one character in the record length.  In many
respects, floating fields are much easier to use in BASIC simply
because you do not have to keep track of the lengths of
individual fields.  The disadvantage of using floating fields is
that each record acts like a sequential file, that is each field
must be read or written in the proper order.  It is generally
more difficult (although by no means impossible) to read and
write floating fields in the selective manner of fixed fields. 
 
Assuming a file with a record length of 80 characters and four
data fields, the following is an example of how to read a
floating field record format (neglecting the error channel for a
moment): 
 
10 DOPEN#1,"DATAFILE",L80  
20 INPUT "RECORD # TO READ";NU 
30 RECORD#1,(RE),1 
40 INPUT#1, F1$,F2$,F3$,F4$ 
50 RECORD#1,(RE),1 
      . 
      . 
      .   
  (the rest of your program) 
 
In the above example, each field can be any length, as long as
all four totaled do not exceed 80 characters.  The field lengths
are quite often different from record to record.  Note that the
",1" is included at the end of each RECORD statement.  This is to
ensure that the byte pointer is correctly positioned to the first
character in each record. 
 
Assuming each field was 20 characters long, and lines 10, 20 and
30 are identical to those above, a similar program for reading
fixed field records would appear as : 
 
     . 
     . 
     . 
40 INPUT#1,A$ 
50 RECORD#1,(NU),1 
60 F1$=LEFT$(A$,20):F2$=MID$(A$,20,20) 
70 F3$=MID$(A$,40,20):F4$=RIGHT$(A$,20) 
     . 
     . 
     . 
 
 
When using the fixed field model, no field can exceed 20
characters (in this example) even if some of the other fields
contain less than the maximum number of characters.  If the
record length is to be longer than 160 characters, then either a
carriage return would be required to break the record up into
less than 160 character chunks, or the record should be read
using a series of GET#'s.  In the first case, line 40 in the
example would read: 
 
     40 INPUT#1,A1$,A2$ 
Each of A1$ and A2$ would then be divided into its logical
fields, using a method similar to line 60 in the above example. 
In the second case, line 40 might read: 
 
40 A$="":FOR I=1 TO {record length}:GET#1,A1$:A$=A$+A1$:NEXT 
 
A$ would then be divided into its constituent logical fields as
in the previous example. 
 
 
Output 
Output to relative files is similar to other disk file types: it
is done using the PRINT# (or PRINT#, USING;) command.  Of course,
the data must be output to the file in the correct order and, if
using floating fields, each item must be separated by a carriage
return.   
 
The following example will write the data represented by F1$,
F2$, F3$, and F4$ to the file created for the input demonstration
outlined above using floating field format. 
    
90 CR$=CHR$(13) 
100 INPUT "RECORD #";NU 
110 INPUT "FIELD #1 DATA";F1$ 
120 INPUT "FIELD #2 DATA";F2$  
130 INPUT "FIELD #3 DATA";F3$ 
140 INPUT "FIELD #4 DATA";F4$ 
150 R$=F1$+CR$+F2$+CR$+F3$+CR$+F4$+CR$ 
160 IF LEN(R$)>80 THEN PRINT"RECORD TOO LONG":GOTO 100 
170 RECORD#1,(NU),1:PRINT#1,R$:RECORD#1,(NU),1:GOTO 100 
 
Note the carriage return character specifically placed between
each field.   
 
If you are absolutely certain that the total record length will
not exceed the specified record size, the following can be
substituted for lines 150 to 170: 
 
 
150 RECORD#1,(NU),1 
160 PRINT#1,F1$:PRINT#1,F2$:PRINT#1,F3$:PRINT#1,F4$ 
170 RECORD#1,(NU),1:GOTO 100 
 
 
The above example using fixed field records may look like: 
 
90 SP$="{20 SPACES}" 
       . 
       . 
       . 
150 F1$=LEFT$(F1$+SP$,20):F2$=LEFT$(F2$+SP$,20) 
160 F3$=LEFT$(F3$+SP$,20):F4$=LEFT$(F4$+SP$,20) 
170 R$=F1$+F2$+F3$+F4$:RECORD#1,(NU),1 
180 PRINT#1,R$;:RECORD#1,(NU),1:GOTO 100 
 
Alternatively, for fixed field lengths, the following may be
substituted in the above example beginning at line 170: 
 
170 RECORD#1,(NU),1:PRINT#1,F1$:RECORD#1,(NU),1 
180 RECORD#1,(NU),21:PRINT#1,F2$:RECORD#1,(NU),21 
190 RECORD#1,(NU),41:PRINT#1,F3$:RECORD#1,(NU),41 
200 RECORD#1,(NU),61:PRINT#1,F4$:RECORD#1,(NU),61 
 
This last method has the advantage that each field can be written
separately.  It should be noted, however, that with any output
method, DOS will always fill to the end of the record with
CHR$(0)'s, regardless of whether or not the record contains any
more valid data.  Therefore, the selective field writing should
only be done if all subsequent data in the record is going to be
updated.  Otherwise, you must write the entire record at once. 
 
Closing the File 
A relative file is closed using a standard CLOSE {file#} or
DCLOSE type of statement.  In this respect, it is identical to
all other types of disk files.  However, you should not use a
dummy PRINT# statement before closing the file as is customary
for other types of write files.
 
 
Relative File Demo 
The following program is a short demonstration of the principles
of relative file creation and input/output.  It can be used to
store addresses and phone numbers.  Each record has a length of
80 bytes, consisting of 7 floating fields representing the
following data: 
 
................................... 
Field name         Estimated length 
...................................  
NAME                    20  
STREET ADDRESS          20  
CITY                    15  
STATE                   5  
ZIP                     5 
AREA CODE               5 
PHONE #                 10 
                       ===== 
total # bytes           80           
................................... 
 
 
10 DOPEN#1,"DATAFILE",L80:IF DS THEN GOSUB 410:DCLOSE:END 
20 PRINT"{CLR}RELATIVE FILE DEMO":CR$=CHR$(13)  
30 PRINT"1: READ":PRINT"2: WRITE":PRINT"3: QUIT" 
40 INPUT"ENTER SELECTION";S:IF S<1 OR S>3 THEN 20 
50 ON S GOSUB 90,240,430:GOTO 20 
60 : 
70 REM READ RECORDS 
80 : 
90 WR=0:PRINT"{CLR}READ RECORDS":INPUT"RECORD NUMBER";NU: 
   IF NU=0 THEN RETURN 
100 RECORD#1,(NU),1:IF DS THEN GOSUB 410:GOTO 90 
110 INPUT#1,N$,S$,C$,SA$,ZI$,AC$,PH$ 
120 RECORD#1,(NU),1   
130 IF N$=CHR$(255) THEN PRINT"RECORD NOT PRESENT":
    GOSUB 420:GOTO 90 
140 PRINT"     NAME >> "N$ 
150 PRINT"  ADDRESS >> "S$ 
160 PRINT"     CITY >> "C$ 
170 PRINT"    STATE >> "SA$ 
180 PRINT"      ZIP >> "ZI$ 
190 PRINT"AREA CODE >> "AC$ 
200 PRINT"    PHONE >> "PH$:IF WR THEN RETURN:ELSE GOSUB 420:
    GOTO 90 
210 : 
220 REM WRITE RECORDS 
230 : 
240 PRINT"{CLR}WRITE RECORDS":INPUT"RECORD NUMBER";NU: 
    IF NU=0 TNEN RETURN 
250 RECORD#1,(NU),1:WR=1 
260 IF DS=50 THEN PRINT"NEW RECORD":ELSE IF DS THEN GOSUB 410:
    GOTO 240 
270 IF DS<>50 THEN GOSUB 100 
280 INPUT"     NAME >> ";N$:N$=N$+CR$ 
290 INPUT"  ADDRESS >> ";S$:S$=S$+CR$ 
300 INPUT"     CITY >> ";C$:C$=C$+CR$ 
310 INPUT"    STATE >> ";SA$:SA$=SA$+CR$ 
320 INPUT"      ZIP >> ";ZI$:ZI$=ZI$+CR$ 
330 INPUT"AREA CODE >> ";AC$:AC$=AC$+CR$ 
340 INPUT"    PHONE >> ";PH$:PH$=PH$+CR$ 
350 R$=N$+S$+C$+SA$+ZI$+AC$+PH$ 
360 IF LEN(R$)>80 THEN PRINT"RECORD TOO LONG":GOSUB 420:GOTO 280 
370 RECORD#1,(NU),1:PRINT#1,R$:RECORD#1,(NU),1:GOTO 240 
380 : 
390 REM ERROR AND EXIT ROUTINES 
400 : 
410 PRINT"DISK ERROR >> ";DS$:GOSUB 420:RETURN 
420 PRINT"PRESS A KEY TO CONTINUE":GETKEY Z$:RETURN 
430 DCLOSE:END 
 
 
Index Files 
Sequential data files are often used as index or "key" files for
relative files.  Commodore DOS will allow one SEQ and one REL
file to be OPEN at the same time with a 1571 disk drive.  An
index can be read from a SEQ file and stored in the computer's
memory in array form.  Of course this method is limited by the
size of the array, and hence, the computer's memory, but it does
allow for a quick and easy way to search and sort data in
relative files.  (The index arrays are searched or sorted
entirely in memory, then only the selected records are actually
read from the relative file.) 

Index files can be used in a variety of ways.  Often it is
quicker to search an index for a specific record than searching
the entire relative file.  The only disadvantage is that index
files require a bit of time to create and must be updated each
time you change the relative file.  However, if you have a large
database that is fairly static (i.e. you are not constantly
changing it) the advantages far outweigh the disadvantages.  For
example, supposing you had a relative file, similar to the one
used in the example in the previous section, which contained 1000
records.  You wish to find the address of a given name.  There
are two ways that this can be done.  One is to read each record
in sequence to until you find a match with the name field. This
can be very time consuming for a large database.  The second
method is to build an index and scan it instead.  Once the index
has been built, it can be used to speed up subsequent  searches.

The following example demonstrates the creation and use of an
index file.  (Assume that the database file has been created
similar to the one used in the example above.)

     0 REM CREATE INDEX
     1 REM RELATIVE FILE = DATAFILE
     2 REM INDEX FILE = INDEX
     10 DIM N$(1000)               : REM SET UP INDEX ARRAY
     20 OPEN 1,8,4,"INDEX,S,W"     : REM OPEN INDEX FILE
     30 OPEN 8,8,8,"DATAFILE"      : REM OPEN REL FILE
     40 RE=1                       : REM FIRST RECORD
     50 RECORD#8,(RE),1            : REM GET RECORD
     60 IF DS THEN 100             : REM CHECK FOR END OF FILE
     70 INPUT#8,NA$                : REM READ NAME
     80 N$(RE)=NA$                 : REM STORE IN ARRAY
     90 RE=RE+1 : GOTO 50          : REM NEXT RECORD
     100 RE=RE-1
     110 PRINT#1,RE                : REM SAVE # OF RECORDS
     120 FOR I=1 TO RE 
     130 PRINT#1,N$(I) : NEXT      : REM PRINT INDEX
     140 PRINT#1 : CLOSE 1 : CLOSE 8


     0 REM USE INDEX
     10 DIM N$(1000)
     20 OPEN 1,8,4,"INDEX"
     30 INPUT#1,NU                 : REM NUMBER OF RECORDS
     40 FOR I=1 TO NU
     50 INPUT#1,N$(I) : NEXT       : REM RECOVER INDEX
     60 CLOSE 1
     70 OPEN 8,8,8,"DATAFILE"      : REM OPEN REL FILE
     80 INPUT "NAME TO SEARCH FOR";NS$
     90 RE=0 : FOR I=1 TO NU             : REM SEARCH INDEX
     100 IF NS$=N$(I) THEN RE=I : I=NU+1 : REM RECORD FOUND
     110 NEXT : IF RE=0 THEN 160         : REM CONTINUE SEARCH
     120 RECORD#8,(RE),1           : REM GET RECORD
     130 INPUT#8,N$,S$,C$,SA$,ZI$,AC$,PH$
     140 PRINT N$,S$,C$,SA$,ZI$,AC$,PH$ : REM DISPLAY RECORD
     150 GOTO 80                   : REM TRY AGAIN
     160 PRINT "NAME NOT FOUND" : GOTO 80

Another type of index file might contain the sorted order of the
records by various fields.  For example, in the above
demonstration, we wish to print out the entire list arranged
alphabetically by name or by area code.  A single index file can
be used to contain the sorted parameters.  Such a file may look
like:
   57, 102, 1, 17, 27, etc.
where these numbers represent the sequence of records according
to the specified sorting.  In this case, the records are not
actually moved in the file but you access them in a given
sequence.  Multi dimensional arrays can be used to contain a
master cross reference of all the records according to all
possible sorting mechanisms.  For example, an index file might
contain:
  57, 1, 32, 4
  19, 57, 22, 1
  9, 7,  1,  2
  etc.
Each line of this example represents four distinct sorting
creiteria. The records in sequence 57, 19, 9 might be the
arrangement of the records by name, while 1, 57, 7, might be the
arrangement by area code, 32, 22, 1 by city and 4, 1, 2 by zip
code, etc.  

This shows that index files have a great deal of flexiblity and
can be used for many purposes.  The key factor is that it is
faster to search a table in memory for a given entry number them
get that entry directly rather than search each entry on disk
looking for a match.

