Classic Computer Magazine Archive COMPUTE! ISSUE 17 / OCTOBER 1981 / PAGE 112

ATARI PROGRAM LIBRARY

Ron and Lynn Marcuse
Freehold, NJ

One of the most difficult aspects of owning a home/personal computer is maintaining an accurate catalog of programs and data files. We first attacked this problem through the use of a home-grown data base which handled the program library in addition to other data. But, alas, it became too time consuming to manually update the data base as new programs were added or changes made. The logical extension to this concept was to automate the cataloging process. To accomplish this, one must compare the index of the disk to the program library file, adding or deleting records on the file as the comparison warrants. The ease of OPENing the ATARI'S DOS directory from BASIC greatly facilitated the programming, but more on this later.

Besides the automatic cataloging function, other required features were:

  1. Listing the directory from the program.
  2. Cataloging Non-DOS disks.
  3. Inquiry, Browse and formatted Print output of catalog records.
  4. Maintaining other data not supplied by the disk directory (source, description, type, and date).
  5. Sorting the file on any field (in machine language, for speed).
  6. An auto-locate function to RUN any program.
  7. Variable search criteria to locate any program.
  8. Creating an internal label to automatically identify a disk. All of these goals were achieved in the program which, incidentally, requires at least 24K RAM, one disk drive (810 or 815) and DOS II.
Table 1. List of variables.
NAME SIZE DESCRIPTION
AP$ 21 "ATARI PROGRAM LIBRARY"
REC$ 62 Program Library Record
VOL$ 4 Disk (or other) Volume Number
DSN$ 12 Data Set Name (Filespec)
DES$ 22 Description
TYP$ 7 Type (Game, Data, etc.)
SRC$ 7 Source
DAT$ 6 Date (Entry, Version, etc.)
SEC$ 3 Number of Sectors
X$ var Input String (Main Prog and Sort)
IN$ 20 Temporary String Storage
PL$, PC$ Filespecs (D : PROGLIB.DB & D : DISK.CAT)
SV$ 20 Search Value
SS, SE Search (and Sort) Start, End Positions
SK Search Key
B Transaction Type
R Return Line Number from TRAPped Error
ST IOCB STATUS Value
I, J, L, N Counting for Loops, Lines, etc.
NP Number of Files in Directory
IO Input/Output Type
SEC, BYT Sector, Byte values for NOTE, POINT
P Output Type (1 = Inqy, 2 = Browse, 3 = Print)
EOF Record Counter
Table 2. Key conversion (Epson MX-80 printer).
SYMBOL ASCII VAL (Dec) KEY SEQUENCE (ATARI) DESCRIPTION
[A] 0 CON; ,(comma) Null; End of Tab Set Seq
[B] 9 CON; I Horizontal Tab
[C] 14 CON; N Print Double Width Characters
[D] 27,68 ESC; ESC; D Set Tab (followed by Tab Positions and NULL Char)
[E] 253 ESC; CON; 2 Console Bell
[F] 125 ESC; CON; CLR Clear Screen
[G] 29 ESC; CON; = Move Cursor Down
[H] 10 CON; J Line Feed
Note: The string of X's in line 3090 are the printer tabs. Type the ASCII characters for these decimal values: 6, 20, 39, 53, 62, 70 (i.e. 6 is CON ;F)

Program 1.

10 DIM AP$(21), PC$(10) : AP$ = ""
12 REM ** RON MARCUSE, FREEHOLD NJ **
30 OPEN #2, 4, 0, "K:" : POKE B2, 0 : ? "[E]"
50 DIM REC$(62), VOL$(4), DSN$(12), DES$(22), TYP$(7), SRC$(7), DAT$(6), SEC$(3)
60 DIM X$(500), IN$(20), SV$(20), PL$(12) : PC$ = "D : DISK.CAT" : PL$ = "D : PROGLIB.DB"
100 GRAPHICS 0 : POKE 16, 64 : POKE 53774, 64:? :? , AP$:? ,"[6] CATALOG OPTIONS "
110 ? "[G] 1 AUTO CATALOG        5 UPDATE RECORD"
120 ? " 2 LIST DIRECTORY      6 SORT LIBRARY"
130 ? " 3 ADD DISK (NON DOS)  7 RUN PROGRAM"
140 ? " 4 INQUIRY/LIST        8 END SESSION"
160 GOSUB 6900 : TRAP 160 : B = VAL(CHR$(A)) : TRAP 40000 : IF B < 1 OR B > 8 THEN 160
170 B = B + 1 : IF B > 3 THEN 500
180 ? : IF B = 3 THEN 300
190 ? "" : GOSUB 6910
200 TRAP 240 : OPEN #3, 4, 0, PC$ : TRAP 40000 : INPUT #3, IN$
210 IF LEN(IN$) < 14 OR IN$(1, 10) <> PC$ THEN ? " ERROR- ";PC$ : GOTO 250
220 B = 1 : VOL$ = IN$(11, 14) : GOTO 290
240 CLOSE #3 : R = 200 : STATUS #3, ST : IF ST <> 170 THEN 9000
250 ? "(DNNN) =>" ; : INPUT VOL$ : IF LEN(VOL$) = 0 THEN 100
260 R = 260 : TRAP 9000 : OPEN #3, 8, 0, PC$:? #3; PC$; VOL$
290 CLOSE #3 : XIO 35, #3, 0, 0, PC$
300 R = 300 : TRAP 9000 : OPEN #3, 6, 0, "D: *.*" : L = 0
310 TRAP 400 : INPUT #3, IN$ : TRAP 40000
320 IF LEN(IN$) < 17 THEN 400
330 IF B = 3 THEN ? , IN$ : L = L + 15 : GOTO 310
340 FOR I = 3 TO 10 : IF IN$ (I, I) <> " " THEN NEXT I
350 DSN$(1, 8) = IN$(3, 10) : DSN$(9, 9) = " " : DSN$(I - 1) = IN$(11, 13)
360 IF DSN$(I - 1) <> " " THEN DSN$(I - 2, I - 2) = "."
370 IF DSN$ = "DOS.SYS" OR DSN$ = "DUP.SYS" OR DSN$ = "MEM.SAV" OR DSN$ = "DISK.CAT" THEN 310
380 X$(L + 1, L + 12) = " " : X$(L + 1, L + 12) = DSN$
390 X$(L + 13, L + 15) = IN$(15, 17) : L = L + 15 : GOTO 310
400 CLOSE #3 : NP = L/15
410 ?  :  ? "FILES FOUND =  " ; NP ; ",  FREE SPACE =  " ; IN$(1, 3)
420 IF B < 3 THEN ? "DISK IS # " ; VOL$ : ?""
430 GOSUB 6910 : IF NP = 0 THEN 100
500 ON B GOTO 1000, 1200, 100, 2000, 3000, 4000, 5000, 5500, 900
900 GRAPHICS 0 : END
950 GOSUB 6250 : GOTO 100
960 POP  : GOTO 100
990 FOR I = 1 TO 300 : NEXT I : RETURN
1000 REM ** RE-CATALOG
1010 IO = 12 : GOSUB 6200 : SV$ = VOL$ : SK = 1 : SS = 1 : SE = 4 : EOF = 0 : L = 0
1020 GOSUB 7000 : IF SK = 9 THEN 1190
1030 FOR N = 1 TO NP : IF X$(N * 15 - 14, N * 15 - 3) = DSN$ THEN 1100
1040 NEXT N : REC$ (62) = "D" : ? "" ; DSN$ ; "DELETED ON" ; VOL$ : GOTO 1110
1100 REC$(59, 61) = X$(N * 15 - 2, N * 15) : X$(N * 15, N * 15) = "*" : L = L + 1
1110 POINT #3, SEC, BYT : ? #3 ; REC$ : GOTO 1020
1190 GOSUB 6250 : IF L = NP THEN 100
1200 REM ** AUTO CATALOG
1210 IO = 9 :  GOSUB 6200
1220 FOR N = 1 TO NP : IF B = 1 AND X$(N * 15, N * 15) = "*" THEN 1240
1230 DSN$ = X$(N * 15 - 14, N * 15 - 3) : SEC$ = X$(N * 15 - 2, N * 15) : GOSUB 6800
1240 NEXT N : GOTO 950
2000 REM ** MANUAL ADD
2010 IO = 9 : GOSUB 6200 : GOSUB 6800 : GOTO 950
3000 ? "[F][6]" ; AP$; " - INQY/LIST" : N = 8 : GOSUB 6500 : ?
3020 ? " OUTPUT : ", " 1. INQUIRY"? , " 2. BROWSE" : ? , "3. LISTING"
3040 GOSUB 6900 : TRAP 3040 : P = VAL(CHR$(A)) : TRAP 40000 : IF P < 1 OR P > 3 THEN 3040
3050 L = 0 : IO = 4 : GOSUB 6200 : EOF = 0 : IF P < 3 THEN 3100
3090 R = 3090 : TRAP 9020 : OPEN #4, 8, 0, "P :" : TRAP 40000 : ? #4 ; "[D]XXXXXX[A]"
3100 GOSUB 7000 : L = L + 1 : IF SK > 8 THEN 3300
3110 ON P GOTO 3120, 3160, 3200
3120 GOSUB 7600 : ?
3130 L = 0 : ? " (E = END) OR" ; : GOSUB 6910 : IF CHR$(A) = "E" THEN 950
3140 GOTO 3100
3160 IF L = l THEN ? "[F][G]VOL FILE ID DESCRIP" : ?
3170 ? VOL$; " " ; DSN$ ; DES$ : IF L = 19 THEN 3130
3180 GOTO 3100
3200 IF L > 1 THEN 3220
3205 ? #4 ; "[B][C] ATARI PROGRAM LIBRARY[H]"
3210 ? #4 ; "DISK#[B]PR06/FILE ID[B] DESCRIPTIO[B]TYPE[B]SOURCE[B]DATE SECTORS[H]"
3220 ? #4 ; VOL$ ; "[B]" ; DSN$ ; "[B]" ; DES$ ; [B]" ; TYP$ ; "[B]" ; SRC$ ; "[B]" ; DAT$ ; "[B]" ; SEC$
3230 IF L > 55 THEN L = 0 : ? #4 ; CHR$(12)
3240 GOTO 3100
3300 IF P = 3 THEN CLOSE #4
3310 IF P = 2 THEN GOSUB 6910
3320 GOTO 950
4000 ? "[F][G] " ; AP$ ;" - RECORD UPDATE" : N = 8 : GOSUB 6500
4010 IO = 12 : GOSUB 6200 : EOF = 0
4020 GOSUB 7000 : IF SK > 8 THEN 950
4030 GOSUB 7600 : ?  : ? " TYPE FIELD # TO UPDATE,  D TO DELETE"
4050 GOSUB 6910 : IF CHR$(A) = (D) THEN REC$(62) = "D"
4060 TRAP 4300 : C = VAL(CHR$(A)) : TRAP 40000 : IF C < 1 OR C > 7 THEN 4300
4100 RESTORE 9910 : FOR I = 1 TO C : READ IN$ : NEXT I : ?
4110 ? "ENTER NEN" : GOSUB 6040 + C : GOTO 4030
4300 GOSUB 6100 : POINT #3, SEC, BYT : ? #3 ; REC$ : GOTO 4020
5000 ? "[F][6] " ; AP$ ; " - SORT/COMPRESS" : N = 7 : GOSUB 6500
5010 ? " TYPE Y TO SORT ON FIELD # ";SK : GOSUB 6910
5020 IF CHR$(A)<>"Y" THEN 100
5040 POKE 207, SS-1 : POKE 208, SE-1
5050 ? " LOADING SORT PROGRAM" : RUN "D:PROGSORT"
5500 REM RUN PROG
5510 ? : ? "  = =>"; : INPUT SV$ : I0 = 4 : IF LEN(SV$) = 0 THEN 100
5520 GOSUB 6200 : EOF = 0 : SS = 5 : SE = 4 + LEN(SV$):SK = 2 : GOSUB 7000 : IF SK = 9 THEN 950
5530 ? : ? "  " ; VOL$;" TO RUN ";DSN$
5540 ? : ? " TYPE ‘Y’ TO RUN" : GOSUB 6910 : IF CHR$(A)<>"Y" THEN 950
5550 IN$(3) = DSN$ : IN$(1,2) = "D : "
5560 ? : ? " LOADING ";IN$ : TRAP 5570 : RUN IN$ : TRAP 40000
5570 ? : ? " ";IN$;" NOT ON DISK" : GOSUB 990 : GOTO 950
6000 ? "[F][6] TO ADD ";DSN$;", ENTER : "
6010 RESTORE 9910 : FOR I = 1 TO 7 : READ IN$
6020 IF B<3 AND (I = 1 OR I = 2 OR I = 7) THEN 6040
6030 ? : GOSUB 6040 + I
6040 NEXT I : RETURN
6041 ? "|----| ";IN$ : INPUT VOL$ : RETURN
6042 ? "|------------| ";IN$ : INPUT DSN$ : RETURN
6043 ? "|----------------------| ";IN$ : INPUT DES$ : RETURN
6044 ? "|-------| ";IN$ : INPUT TYP$ : RETURN
6045 ? "|-------| ";IN$ : INPUT SRC$ : RETURN
6046 ? "|------| ";IN$ : INPUT DAT$ : RETURN
6047 ? "|---| ";IN$ : INPUT SEC$ : RETURN
6100 FOR I = 1 TO 61 : REC$(I, I) = " " : NEXT I
6110 REC$(l,4) = VOL$ : REC$(5,16) = DSN$ : REC$(17,38) = DES$ : REC$(39,45) = TYP$
6120 REC$(46,52) = SRC$ : REC$(53,58) = DAT$ : REC$(59,61) = SEC$ : RETURN
6200 R = 6200 : TRAP 9000 : IF 10<>4 THEN XI0 36, #3, 0, 0, PL$
6210 OPEN #3,I0, 0, PL$ : TRAP 40000 : RETURN
6250 R = 6250 : TRAP 9000 : CLOSE #3
6260 IF I0<>4 THEN XI0 35, #3, 0, 0, PL$
6270 TRAP 40000 : RETURN
6500 ? "[G] KEY : " : RESTORE 9910
6510 FOR I = 1 TO N : READ IN$ : ? ,I;" ";IN$ : NEXT I : ? ,"E END" : GOSUB 6900
6540 TRAP 960 : SK = VAL(CHR$(A)) : TRAP 40000 : IF SK<1 OR SK>N THEN 960
6550 IF SK = 8 THEN 6590
6560 RESTORE : FOR I = 1 TO SK : READ SS, SE : NEXT I : IF B = 7 THEN 6590
6570 ? " ENTER VALUE"; : INPUT SV$ : IF LEN(SV$)<1 THEN 960
6580 IF SS + LEN(SV$)-l> SE THEN 6570
6585 SE = SS + LEN(SV$)-1
6590 RETURN
6800 GOSUB 6000 : GOSUB 6100 : REC$(62) = "*" : GOSUB 7600
6810 ? "[G] TYPE ‘Y’ IF OK " : GOSUB 6910 : IF CHRt$(A)<>"Y" THEN 6800
6820 ? #3; REC$ : RETURN
6900 ? : ? "  ==>"; : GET #2, A : ? CHR$(A) : RETURN
6910 ? " PRESS ANY KEY TO CONTINUE"; : GET #2, A : ? CHR$(A) : RETURN
7000 IF B = l OR B = 6 THEN NOTE #3, SEC, BYT
7010 TRAP 7060 : INPUT #3, REC$ : TRAP 40000 : IF REC$(62) = "D" THEN 7000
7020 IF SK = 8 THEN 7040
7030 IF SV$<>REC$(SS, SE) THEN 7000
7040 EOF = EOF + l : VOL$ = REC$(1,4) : DSN$ = REC$(5,16) : DES$ = REC$(17,38) : TYP$ = REC$(39,45)
7050 SRC$ = REC$(46,52) : DAT$ = REC$(53,58) : SEC$ = REC$(59,61) : RETURN
7060 SK = 9 : ? : ? " RECORDS FOUND = ";EOF : GOSUB 990 : RETURN
7600 ? "[F][G]" : RESTORE 9910 : FOR I = 1 TO 7
7610 READ IN$ : ? " ";I;" ";IN$; : GOSUB 7610 + I : NEXT I : RETURN
7611 ? VOL$ : RETURN
7612 ? DSN$ : RETURN
7613 ? DES$ : RETURN
7614 ? TYP$ : RETURN
7615 ? SRC$ : RETURN
7616 ? DAT$ : RETURN
7617 ? SEC$ : RETURN
9000 STATUS #3,ST : CLOSE #3:? " ERROR ";ST:GOSUB 6910:GOTO R
9020 STATUS #4,ST : CLOSE #4: ? " ERROR ";ST:GOSUB 6910 : GOTO R
9900 DATA 1, 4, 5, 16, 17, 38, 39, 45, 46, 52, 53, 58, 59, 61
9910 DATA  ALL RECORDS

You did notice that there is a "II" after "DOS." ATARI has finally released the new version and, to say the least, it is a vast improvement. Not that we were unhappy with its predecessor, but it did tend to hide whenever one walked by with a can of "RAID" or "BLACK FLAG." Yes, there are bugs in DOS I, one of which leads the NOTE and POINT-commands (needed to update any record) somewhere into the twilight zone. This program can be modified to work under DOS I, but the explanation would probably take up the rest of the magazine. If you plan to do any serious file processing, it would be advisable to pick up a copy of DOS II. There are other advantages as well, such as less RAM used through the auto-swap feature (the program and DOS share the same area).

The three listings represent the main program, the sort program and the machine language sort routine. The sort program is executed by a RUN statement, allowing the DIMensioning of the rather large string necessary to sort the file in. It loads the file into X$ and calls the machine language sort through the USR function in line 70. You may POKE the routine into storage using the third BASIC program. You must do a BINARY SAVE (DOS II function "K") with AUTORUN.SYS as the file name, 0600 and 066D (hex) as the starting and ending addresses. A possible alternative to this would be to key the FOR/NEXT loop and DATA statements into the BASIC sort program, with the loop as line 14.

The main program begins with the DIMensioning of strings and OPENing of the keyboard in lines 30-60. The strings and other variables are detailed in the accompanying table. Graphics mode 0 is set and the <BREAK> key is disabled (POKE 16,64:POKE 53774,64) in line 100. This begins the start of the primary option menu as well. An option (from 1 to 8) is selected and held in variable B. Note that B is increased by 1 in line 170, allowing "Autocatalog" to split into two functions depending on whether or not the disk has already been cataloged. A "D:DISK.CAT" file, containing the volume number, is written on each as an internal label. If this file is found on any disk, the volume number is retrieved and a "re-catalog" function is performed. If not found, a new disk is assumed and the volume number is requested in line 250. One important note: there is a liberal use of subroutines in the program and, for expediency, we will get into them later.

The options that do not require a DOS directory (B>3) are shunted to line 500. Those that do are sent through the routine (lines 300-430) that OPENs the disk directory (IO = 6) and stores it as substrings of X$. The file name is translated into the more familiar format FILENAME.EXT in lines 340-360. File names equal to DOS.SYS, DUP.SYS, MEM.SAV or DISK.CAT are dropped at this point (Why catalog them?). Line 500 (ON B GOTO) then transfers program control to the routine that will process the requested option.

The "Re-catalog" function (B = 1, lines 1000-1110), after OPENing the library file for both input and output operation (IO = 12), extracts all records having that volume number. These are then compared to the directory string (X$). Changes and deletions are posted to the file using the NOTE and POINT commands. An asterisk (*) is moved to each directory member that was successfully matched to the library. At the conclusion of this procedure, control is passed to "Auto-catalog."

The "Auto-catalog" function (lines 1200-1240) OPENs the file for output (append, IO = 9) operation. All new entries, as per the X$ directory, are shuffled through the proper subroutines and then added to the file. Option 1 (B = 1 or 2) travels this route, with (1) being "Re-cataloged" first. The significance of the asterisk on matched directory members becomes apparent in the bypass on line 1220. The file is then CLOSEd and we return to the option menu.

Option 2 (B = 3) consists of displaying the DOS directory and then returning to the menu. The remaining options (B>3), not requiring any help from the directory, go straight into their respective procedures. Option 3 (B = 4) handles the manual addition of library records to the file. This could be utilized for Non-DOS disks or even cassette tapes. Line 2010, helped by several subroutines, does all of the processing.

The inquiry and print option (B = 5) is handled in lines 3000-3320. The search strategy (GOSUB 6500) and output mode are selected, the file is OPENed for input (IO = 4) and, if applicable, the print tabs are set. The file is actually read in the subroutine at line 7000, with only the records matching the search key being passed back. An inquiry goes to line 3120, the browse (19 records per screen) to line 3160 and print (55 records per page) to line 3200. For the multiple record options, the variable L is the line counter. When end of file is reached at line 7010, the search key (SK) is set to "9" indicating completion, the file and printer are closed, and we are back at the menu.

The Update option (B = 6, lines 4000-4300) would be used for changing or deleting library records. The search key is selected, file OPENed (IO = 12) and records read like the inquiry, but here only the full record is displayed. At this point (line 4030), typing the field number will cause updating of that field and "D" the deletion of the record. Any other character will write (using POINT) the record.

The sort (B = 7, lines 5000-5050), as stated earlier, is executed through a RUN "D:PROGSORT statement. Before this is done, the sort key is selected and its offset (beginning and ending positions) is POKEd into locations 207 and 208 (decimal) for use by the machine language program. These addresses, as well as those on page 6 (1536-1791) containing the sort program, are safe from the ravaging effects of RUN, LOAD and NEW. The RUN PROGRAM option (B = 8, lines 5500-5570) adds a little touch of class to the library. By inserting the program name when requested, it will tell you which disk to load and will then RUN it. Obviously, it will only function with BASIC programs that have been SAVEd on disk. The final option (B = 9) terminates the system. The BREAK key, disabled in line 100, could have disastrous effects on the file if used to end the program at the wrong time.

A few notes on the subroutines that do most of the work in the program. The routine starting at line 6000 is used for the input of data for both adding and updating records. The labels for the individual fields, being DATA statements, are READ during the FOR/NEXT loop in line 6010. The library record (REC$) is built from the individual fields at line 6100. Lines 6200 and 6250 OPENs and CLOSEs the file. The search and sort keys are built at line 6500, using the same DATA statements as above for the headings. The positions of the fields are contained in another DATA statement. Lines 6900 and 6910 are prompts. The library record is READ and moved into its elements in line 7000-7060. The variables SV$, SS, SE and SK are used in the search process. The full screen display of the record is taken care of by 7600-7617, once again using the heading DATA statements. Finally, the error routines for the disk drive and printer are found at lines 9000 and 9020. The variable R is the return line number.

Program 2.

10 REM ** ATARI PROGRAM LIBRARY SORT **
11 REM ** R MARCUSE
15 A = FRE(0)-800
20 DIM X$(A),REC$(62),AP$(12):AP$ = "D:PROGL IB.DB":? "LOADING FILE"
30 TRAP 130 : OPEN #3, 4, 0, AP$:N = 0
40 TRAP 60 : INPUT #3,REC$ : TRAP 40000: IF REC$(62) = "D" THEN 40
50 N = N + 1: X$(N$62-61,N$62) = REC$: GOTO 40
60 CLOSE #3:? "RECORDS LOADED = ";N;", BEGIN SORT"
70 IF N>1 THEN A = USR(1536,ADR(X$),N)
80 ? "[E] "
90 XIO 36, #3, 0, 0, AP$ : OPEN #3, 8, 0, AP$
100 FOR 1 = 1 TO N: REC$ = X$(I*62-61,I*62):? #3;REC$: NEXT I
110 CLOSE #3 : XIO 35, #3, 0, 0, AP$
120 ? "[E] LOADING PROGLIB": RUN "D: PROGLIB"
130 STATUS #3,ST: CLOSE #3:? " , ERROR ";ST
140 ? " PRESS RETURN TO CONTINUE": INPUT REC$ : GOTO 30

Program 3.

10 REM ** PROGLIB MACHINE LANG SORT **
11 REM ** R MARCUSE **
12 REM BINARY SAVE WITH FILESPEC = AUTORUN.SYS
20 FOR I = 0 TO 109: READ A: POKE 1536 + I, A : NEXT I
100 DATA 104, 104, 133, 216, 104, 133, 215, 104, 133, 213, 104, 133, 212, 169, 0, 133, 209, 133
110 DATA 214, 162, 1, 165, 215, 133, 205, 165, 216, 133, 206, 24, 165, 205, 133, 203, 105, 62
120 DATA 133, 205, 165, 206, 133, 204, 105, 0, 133, 206, 164, 207, 177, 205, 209, 203, 144, 11
130 DATA 240, 2, 176, 28, 196, 208, 176, 24, 200, 144, 239, 169, 1, 133, 209, 160, 62, 136, 177
140 DATA 205, 72, 177, 203, 145, 205, 104, 145, 203, 192, 0, 208, 241, 232, 224, 0, 208, 2, 230
150 DATA 214, 228, 212, 208, 188, 165, 213, 197, 214, 208, 182, 165, 209, 201, 0, 208, 160, 96

As there are several unprintable characters in the programs, we have substituted others in their place in the BASIC listings. Take a peek at the conversion table before typing. For a better visual effect, the characters enclosed by a box should be typed in reverse video. The two programs call each other by name, so please save them by the names "D:PROGLIB" and "D:PROGSORT". Some last thoughts: The machine language sort routine will not be in storage unless you put it there by either powering up (AUTORUN.SYS will boot in) or doing a DOS binary load. Create the PROGLIB file by typing (in direct mode):

OPEN #3, 8, 0, "D:PROGLIB.DB": CLOSE #3 : XIO 35,#3, 0, 0, "D:PROGLIB.DB".

Good luck.