TRS-80 DOS - NEWDOS/80 v2.0 for the Model I - SYS21/SYS Disassembled
Page Customization
Page Index
SYS21/SYS
Other Navigation
Introduction/Summary
NEWDOS/80 v2.0 SYS21/SYS Disassembly - BASIC CMD"O" Array Sort, ERASE and KEEP (Model I)
SYS21/SYS is a BASIC overlay that loads into the DOS overlay area at 4D00H-51E7H (1,256 bytes in five load records, transfer address 4D00H); 51E2H-51E7H are unused zero bytes. It does two jobs for BASIC/CMD: it sorts arrays for CMD"O", and it deletes variables for CMD"F=ERASE" and CMD"F=KEEP". Several of the routines it calls lie in a patch area of BASIC/CMD (65E0H-6647H) that exists only for this overlay.
CMD"O",n,[*]array(start)[,[-]array(start)...] sorts n elements of one or more arrays, starting at the subscript given for each. The first array is the first sort key and each later array breaks ties left by the ones before it; a minus sign sorts that key in descending order; a string array may be followed by (position,length) to compare only part of each string. All the arrays are moved together, like the columns of one table. With * before the first array (which must then be an integer array) only that array is rearranged: it is filled with the element numbers and sorted by the keys that follow, which stay where they are. The sort is a merge sort that works in place, with a 1,280-byte buffer in the BASIC overlay area at 5200H-56FFH; it keeps equal elements in their original order.
CMD"F=ERASE",name,... deletes the named simple variables and arrays (an array is written with parentheses, A()); CMD"F=KEEP",name,... deletes all the others. User functions (DEF FN) are always kept.
The page shows every byte of the file once, in address order. Calls into BASIC/CMD are linked only where the BASIC page has a row with the right bytes at that address.
Entry Points
| RST 28H code | From BASIC | Statement |
|---|---|---|
| 37H | 57B3H | CMD"O" (sort, 4DBAH). |
| 57H, B = 28H | 57AAH | CMD"F=ERASE" (4D0EH). |
| 57H, B = 20H | 57AEH | CMD"F=KEEP" (4D0EH). |
Any other code returns DOS error 2AH through BASIC 5E16H. ERASE and KEEP are reached from SYS20's CMD"F=" table search, which finds their addresses in BASIC's table at 5952H.
Variables
Locations inside SYS21 whose contents change while it runs (operands written by the code itself):
| Address Range | Purpose |
|---|---|
| 4D8DH 1 byte | Opcode of the keep test in the close-up: 28H (JR Z) for ERASE or 20H (JR NZ, the value in the file) for KEEP. Written at 4D0FH. |
| 4EECH-4EEDH 2 bytes | Start subscript of the first sort key. Written at 4E6EH; read at 4E73H (all keys must start there) and at 4EEBH (first value put into the index array). |
| 4F02H 1 byte | Length in bytes of one table row: the sum of the element lengths of the arrays that are moved. 00H in the file; added up at 4E81H through BASIC 6617H; read at 4F01H. |
| 4F27H-4F28H 2 bytes | n, the number of elements to sort. Written at 4DEBH (and at 4EA4H when n was 0); read at 4E96H, 4EF1H and 4F26H. |
| 4F39H-4F3AH 2 bytes | Run length of the current pass (1, 2, 4 ...). Written at 4F23H; read at 4F38H. |
| 4F40H-4F41H 2 bytes | Elements not yet merged in the current pass. Written at 4F29H and 4F57H; read at 4F3FH. |
| 507EH-507FH 2 bytes | Buffer count: rows that may still be taken before the buffer must be emptied. Written at 4F19H and 508AH; counted down at 507DH-5081H. |
| 5088H-5089H 2 bytes | Rows the 1,280-byte buffer holds (1,280 divided by the row length). Written at 4F09H; read at 4F16H and 5087H. |
| 50FFH-5100H 2 bytes | Address of the routine 50EEH calls for each array (510CH, 511DH, 5153H, 5174H, 5184H or 51D5H). 0000H in the file; written at 50EEH. |
| 5102H 1 byte | Index-sort flag: 00H in the file, 01H when the first array had a *. Written at 4E04H; read at 4E5EH, 4ED6H, 5096H and 5101H. |
| 5124H-5125H 2 bytes | Elements left in run A of the pair being merged. Written at 4F3BH; counted down at 5066H-506AH; read at 5123H. |
| 5145H-5146H 2 bytes | Elements left in run B of the pair being merged. Written at 4F53H; counted down at 5043H-5047H; read at 5144H. |
Locations outside SYS21 that it reads or writes:
| Address Range | Purpose |
|---|---|
| 4101H-411AH 26 bytes | The ROM's DEFINT/DEFSNG/DEFDBL/DEFSTR table, one type byte per letter A-Z; read by BASIC 5BDDH for a name without a type suffix. |
| 40AFH 1 byte | The ROM's type flag of the value or variable just handled: 02H integer, 03H string, 04H single, 08H double precision. Read at 4E15H after PTRGET. |
| 40F9H-40FAH 2 bytes | The ROM's pointer to the start of the simple variable table. Read at 4D4DH and 4D67H. |
| 40FBH-40FCH 2 bytes | The ROM's pointer to the start of the array table (the end of the simple variables). Read at 4D2EH, 4D6DH and 4E26H; written at 4D94H after the close-up. |
| 40FDH-40FEH 2 bytes | The ROM's pointer to the end of the array table (the start of free memory). Read at 4D3BH, 4D99H and 4E29H; written at 4DB3H after the close-up. |
| 4200H-42D0H 209 bytes | Nine 23-byte array blocks for CMD"O" (layout below) and the two zero bytes of a tenth block that end the table. Built at 4DBAH-4DE0H. |
| 4317H 1 byte | SYS0's record of the overlay now in 4D00H (its directory slot). Cleared at 4DBBH so SYS21 is loaded fresh next time. |
| 5200H-56FFH 1,280 bytes | BASIC's overlay area, used by the sort as its buffer. |
| 576EH 1 byte | BASIC/CMD's record (operand of CP at 576DH) of the RST 28H code of the overlay now in 5200H. Cleared at 4DBEH because the sort overwrites that area. |
| 57B8H-57B9H 2 bytes | BASIC/CMD's record of the line BASIC 5D9FH has stepped to, written when an ERASE/KEEP list continues on the next line. |
| 644EH-644FH 2 bytes | Operand of the CALL at BASIC 644DH in BASIC's statement-end clean-up; normally 64BDH (a RET). Set to 4D67H at 4D15H so an error during ERASE/KEEP still closes up the tables; BASIC 64BEH puts back 64BDH. |
The Array Blocks at 4200H
CMD"O" builds one 23-byte block per array at 4200H, 4217H, 422EH ... (nine blocks, then a block number 0 and type 0 at 42CFH-42D0H):
| Offset | Contents |
|---|---|
| +00H | Block number 1-9 (0 after the ninth). |
| +01H | Element type, which is also the element length: 2 integer, 3 string, 4 single, 8 double precision; 0 = block not used. |
| +02H, +03H | String keys: the character position where the compare starts and the number of characters compared (1 and FFH unless given). |
| +04H | Bit 7: descending key (-). Bit 6: the same array as an earlier key, so its data is not moved twice. |
| +05H/+06H | Address of the array's element 0. |
| +07H/+08H | Address of the first element sorted. |
| +09H/+0AH, +0BH/+0CH | Run A: the current element and the end. |
| +0DH/+0EH, +0FH/+10H | Run B: the current element and the end (which is also where the next pair starts). |
| +11H/+12H, +13H/+14H | This array's part of the buffer at 5200H: its start, and the pointer to the next free byte. |
| +15H/+16H | Where the buffer is copied back to; a high byte of 0 means the buffer is not in use. |
Disassembly
4D00H - Entry Point: Select CMD"O", ERASE or KEEP
The SYS0 overlay dispatcher loads SYS21 into 4D00H-51E7H and calls 4D00H with Register A holding the RST 28H code that BASIC/CMD issued. Code 37H (function 1 of directory slot 17H) comes from BASIC 57B3H for CMD"O", the array sort. Code 57H (function 2 of slot 17H) comes from BASIC 57B0H for CMD"F=ERASE" (Register B = 28H, loaded at BASIC 57AAH) and CMD"F=KEEP" (Register B = 20H, loaded at BASIC 57AEH). In every case Register Pair HL holds BASIC's text pointer, pointing just past the CMD string in the program line.
CMD"O". The Z FLAG is set if they match.CMD"O"), JUMP to 4DBAH to parse the array list and sort.CMD"F=ERASE" and CMD"F=KEEP". The Z FLAG is set if they match.4D0EH - CMD"F=ERASE" and CMD"F=KEEP": Mark the Named Variables
CMD"F=ERASE",name,name... deletes the named variables; CMD"F=KEEP",name,name... deletes every variable except the named ones. A name is a simple variable (A, B$, X%) or an array, written with parentheses (A(); anything may stand between them). Each variable found gets bit 4 of its type byte set as a mark; the close-up at 4D67H then copies the kept variables down over the deleted ones. User functions (DEF FN, whose entries have bit 7 set in the name's first character) are never deleted. A comma at the end of a program line continues the list on the next line; the rest of that line may be a REMark, and whole REM lines in between are skipped.
Store Register A (28H for ERASE, 20H for KEEP) at 4D8DH, the opcode byte of the conditional jump in the keep-or-drop test at 4D89H. With JR Z an unmarked variable is kept (ERASE); with JR NZ a marked variable is kept (KEEP).
NAME LOOP
Each pass handles one name of the list. Register Pair HL is BASIC's text pointer throughout; it is saved on the stack while the variable tables are searched.
ARRAY SEARCH LOOP
Register Pair HL walks from array to array; Register Pair DE holds the end of the array table.
SIMPLE VARIABLE SEARCH LOOP
Register Pair HL walks from variable to variable; Register Pair DE holds the end of the simple variable table.
4D67H - Close Up the Variable Tables
Copies every simple variable and every array that is kept down over those that are dropped, clears the marks, and stores the new ends of the tables at 40FBH and 40FDH. Register Pair HL is the source (the entry being looked at) and Register Pair DE the destination (where the next kept entry goes). The test at 4D89H decides for each entry: for ERASE (JR Z at 4D8DH) an unmarked entry is kept, for KEEP (JR NZ) a marked one. User functions (bit 7 of the first name character) are always kept. This routine is also reached from BASIC's statement-end clean-up (CALL at 644DH, set at 4D15H) when an error ends the statement. It ends by jumping to BASIC 64BEH, which puts that CALL back to 64BDH.
SIMPLE VARIABLE CLOSE-UP LOOP
Register Pair HL = the next variable to look at, Register Pair DE = where the next kept variable is copied to.
This opcode is written at 4D0FH: JR Z (28H) for ERASE or JR NZ (20H) for KEEP; 20H is the value in the file. JR Z jumps for an unmarked entry, JR NZ for a marked one; either way the JUMP goes to 4D91H to keep the entry.
ARRAY CLOSE-UP LOOP
Register Pair HL = the next array to look at, Register Pair DE = where the next kept array is copied to.
4DBAH - CMD"O": Build the Array Blocks
CMD"O",n,[*]array(start)[,[-]array(start)...] sorts n elements of the arrays from the start subscript given (n = 0 sorts the rest of the first array). The first array is the first sort key; each array after it breaks ties left by the ones before. A minus sign before an array sorts on it in descending order. All arrays are moved together, as the columns of one table. With * before the first array, that array must be an integer array: it is filled with the element numbers of the keys (start to start+n-1) and only it is rearranged, by the keys that follow (an index sort; the keys stay where they are). A string array may be followed by (position,length) to compare only that part of each string. At most 9 arrays. The routine first builds nine 23-byte blocks at 4200H, one per array; the table "The Array Blocks at 4200H" at the top of the page gives their layout.
BLOCK BUILD LOOP
Register Pair HL = the next byte to write, Register C = the blocks left, Register E = the block number, Register D = 00H.
ARRAY LIST LOOP
One pass per array named. Index Register IX = the block for this array, Register Pair HL = BASIC's text pointer.
Store Register A (01H) at 5102H, the operand of the LD A,00H at 5101H: the index-sort flag, 00H in the file, 01H from here on. The loops at 4E5EH, 4ED6H, 5096H and 5101H read it.
FIND THE ARRAY ENTRY
Register Pair HL walks from array to array; Register Pair DE holds the end of the array table.
SKIP THE DIMENSION WORDS
Register A counts the dimensions; Register Pair HL steps over each 2-byte size and Register Pair DE loses 2 for each.
Load Register A with the byte at 5102H, the index-sort flag: 01H if the first array had a *, else 00H.
Store Register Pair HL (the first key's start subscript) at 4EECH, the operand of the LD DE,0000H at 4EEBH. The later keys are checked against it, and the index sort fills its index array from it.
Load Register Pair DE with the 16-bit value at 4EECH, the first key's start subscript, stored at 4E6EH.
Load Register A with the byte at 4F02H, the length in bytes of one table row counted so far (the operand of the LD A,00H at 4F01H; 00H in the file).
Load Register Pair DE with the 16-bit value at 4F27H, n, the number of elements to sort, stored at 4DEBH (or at 4EA4H below).
n = 0: store Register Pair HL (the elements from the start to the end of the first array) at 4F27H, the operand of 4F26H: that many elements are sorted.
Load Register A with the byte at 5102H, the index-sort flag (01H if the first array had a *).
Load Register Pair DE with the first key's start subscript, stored in this operand (4EECH) at 4E6EH. It is the first element number written into the index array (0000H in the file).
FILL THE INDEX ARRAY
Register Pair HL = the next index element, Register Pair DE = the element number to store, Register Pair BC = the elements left.
4F01H - The Merge Sort
The elements are merged in passes: runs of 1, 2, 4 ... elements are merged pairwise into runs twice as long, until one run holds all n. Two neighbouring runs, A and B, are merged in place. While elements are taken from run A they stay where they are; once an element of run B has been taken, every element taken goes into a buffer at 5200H-56FFH (each moved array has its own part of it). When the buffer is full or a run is used up (5087H), the rest of run A is moved up to meet what is left of run B, and the buffer is copied back in front of it. The routines called through 50EEH do one step for every array that is moved; with the index sort that is only block 1, and 5093H/5096H find the key elements from the index values. For equal keys the element of run A is taken first, so the order of equal elements is kept.
Load Register A with the length in bytes of one table row: the operand 4F02H (00H in the file) was added up at 4E81H through BASIC 6617H, one element length per moved array.
Store Register Pair HL (the rows the buffer holds) at 5088H, the operand of the LD HL,0000H at 5087H, which restarts the buffer count after each emptying.
Load Register Pair HL with the 16-bit value at 5088H, the rows the buffer holds (stored at 4F09H).
Store Register Pair HL at 507EH, the operand of the LD HL,0000H at 507DH: the buffer count, the rows that may still be taken before the buffer must be emptied.
PASS LOOP
Each pass merges runs of the current run length into runs twice as long.
Store Register Pair HL (the run length) at 4F39H, the operand of the LD HL,0000H at 4F38H.
Load Register Pair DE with n, the number of elements to sort; the operand 4F27H was stored at 4DEBH (or 4EA4H when n was 0) (0000H in the file).
Store Register Pair DE (n) at 4F40H, the operand of the LD HL,0000H at 4F3FH: the elements not yet merged in this pass.
PAIR LOOP
One pass of this loop sets up and merges one pair of runs.
Load Register Pair HL with the run length of this pass; the operand 4F39H was stored at 4F23H (0000H in the file).
Store Register Pair HL (the run length) at 5124H, the operand of the LD DE,0000H at 5123H: the count of elements in run A.
Load Register Pair HL with the elements not yet merged in this pass; the operand 4F40H was stored at 4F29H or 4F57H (0000H in the file).
Store Register Pair DE (run B's element count) at 5145H, the operand of the LD DE,0000H at 5144H.
Store Register Pair HL (the elements left after this pair) at 4F40H, the operand of 4F3FH.
COMPARE LOOP
Each pass compares the current elements of run A and run B key by key and takes the smaller one.
KEY LOOP
Index Register IX = the block of the key being compared.
MOVE TO THE START POSITION
Register A counts down the position; Register B and Register C count down the characters left in the first and second string.
CHARACTER COMPARE LOOP
Register Pair DE = the first string's character, Register Pair HL = the second string's; Register B and Register C count down what is left of each.
BYTE COMPARE LOOP
Register Pair DE and Register Pair HL step down from the exponent through the mantissa; Register B counts the bytes.
Store Register Pair HL (the elements left in run B) back at 5145H.
Store Register Pair HL (the elements left in run A) back at 5124H.
Load Register Pair HL with the buffer count, the rows that may still be taken before the buffer must be emptied; the operand 507EH was stored at 4F19H or 508AH (0000H in the file).
Store Register Pair HL (the buffer count) back at 507EH.
Load Register Pair HL with the rows the buffer holds; the operand 5088H was stored at 4F09H (0000H in the file).
Store Register Pair HL at 507EH: the buffer count starts again.
5093H - Index Sort: Find the Key Elements
Used only by the index sort (when the flag at 5102H is 01H). The index array's current element (run A's at +09H or run B's at +0DH in block 1) holds an element number; for every key block from block 2 on, the address of that element of the key array is worked out (element number times element length, plus the address of element 0 in +05H/+06H) and stored in the same field of the key's block. 5093H does it for run A (field +09H); 5096H for the field whose offset is in Register Pair BC. Without the index sort both return at once with Index Register IX unchanged; with it IX returns at 4217H, block 2, so that the compare starts with the first key.
Load Register A with the byte at 5102H, the index-sort flag stored at 4E04H: 01H for an index sort, 00H otherwise.
KEY BLOCK LOOP
Index Register IX = the key block, Register Pair DE = the element number, Register Pair BC = the field offset.
50D7H - Take an Element: Leave It or Copy It to the Buffer
Called with Register Pair HL pointing at an element, Register Pair BC holding its length and Index Register IX on the array's block; returns with HL past the element. 50D7H copies the element to the buffer only when the buffer is in use (byte +16H of the block not 0); otherwise the element stays where it is. 50DFH always copies, except for an array whose data another block moves (bit 6 of +04H), where BASIC 6620H only steps HL past the element.
50EEH - Do One Step for Every Moved Array
Calls the routine whose address is in Register Pair BC once for every array block in use, from block 1 up, with Index Register IX pointing at the block and Register Pair BC holding the element length. With the index sort only block 1 (the index array) is moved, so the loop stops after it. Register Pairs DE and HL are passed through to the routine unchanged.
Store Register Pair BC (the routine's address: 510CH, 511DH, 5153H, 5174H, 5184H or 51D5H) at 50FFH, the operand of the CALL at 50FEH.
BLOCK LOOP
Index Register IX = the block of the array being handled.
GOSUB to the routine whose address was stored in this operand (50FFH) at 50EEH. The file holds 0000H here; the CALL is only executed after 50EEH has written the address.
Load Register A with the index-sort flag, the operand 5102H: 00H in the file, 01H once stored at 4E04H for an index sort.
510CH - Give an Array Its Part of the Buffer
Called through 50EEH at 4F13H, with Register Pair HL = the first free byte of the buffer at 5200H, Register Pair DE = the rows the buffer holds, Register A and Register Pair BC = the element length, and Index Register IX on the array's block.
511DH - Set Up a Pair of Runs
Called through 50EEH at 4F5DH, with Register A and Register Pair BC = the element length and Index Register IX on the array's block. Run A starts at the end of the last pair (+0FH/+10H) and holds the count at 5124H elements; run B follows it and holds the count at 5145H. The block gets run A's pointer (+09H) and end (+0BH), and run B's pointer (+0DH) and end (+0FH), which is also where the next pair starts.
Load Register Pair DE with the count of elements in run A; the operand 5124H was stored at 4F3BH (the run length) and counted down at 506AH (0000H in the file).
Load Register Pair DE with the count of elements in run B; the operand 5145H was stored at 4F53H and counted down at 5047H (0000H in the file).
5153H - Take Run B's Element
Called through 50EEH at 5040H, with Register Pair BC = the element length and Index Register IX on the array's block. Run B's element always goes into the buffer. The first time in a pair, run A's current position is recorded at +15H/+16H as the place the buffer is copied back to.
5174H - Take Run A's Element
Called through 50EEH at 5063H, with Register Pair BC = the element length and Index Register IX on the array's block. Run A's element stays where it is, or goes into the buffer if the buffer is in use, and run A's pointer moves on.
5184H - Empty the Buffer
Called through 50EEH at 5090H, with Register Pair BC = the element length and Index Register IX on the array's block. The elements of run A not yet taken (from +09H to +0BH) are moved up so that they end just before run B's pointer; run B's pointer becomes run A's new end. Then the buffer is copied back to the copy-back address (+15H/+16H), which fills exactly the gap in front of the moved elements, and the buffer is marked not in use.
51D5H - Start a Pass
Called through 50EEH at 4F35H with Index Register IX on the array's block: the first pair of runs of a pass starts at the first element sorted.
51E2H - Unused Bytes
51E2H-51E7H fill the last load record; nothing reads or executes them.