TRS-80 DOS - NEWDOS/80 v2.0 for the Model III - SYS21/SYS Disassembled
Page Customization
Page Index
SYS21/SYS
Other Navigation
Introduction/Summary
NEWDOS/80 v2.0 SYS21/SYS Disassembly - ERASE, KEEP and the Array Sort CMD"O" (Model III)
SYS21/SYS carries out three Disk BASIC commands. It loads 4D00H-51E7H in five load records (transfer address 4D00H) into the overlay area 4D00H-51FFH; the code ends at 51E1H and 51E2H-51E7H are 6 bytes of 00H. BASIC/CMD runs it with RST 28H, not through its loader at 576CH: code 57H from 57B0H for CMD"F=ERASE" and CMD"F=KEEP" (57AAH and 57AEH, found by SYS20/SYS in BASIC/CMD's table at 592BH, with Register B = 28H or 20H), and code 37H from 57B3H for CMD"O" (from the letter commands at 57DDH). 4D00H drops the dispatcher's return address 4BC5H, so the commands return straight to the ROM's statement loop. The page shows every byte of the file once, in address order.
- CMD"F=ERASE",name,name... deletes the named variables; CMD"F=KEEP",name,name... deletes all others. A name is a simple variable or an array written with parentheses. Each one found is marked with bit 4 of its type byte (4D5BH); 4D67H then copies the kept entries down and stores the new 40FBH and 40FDH. User functions are always kept. A comma at the end of a line continues the list on the next line (BASIC 5D78H); empty lines and REM lines in between are passed over. BASIC's statement-end CALL at 6426H points at 4D67H while the list is read, so an error still closes up the tables.
- CMD"O",n,[*]array(start)[,[-]array(start)...] sorts n elements (n = 0: the rest of the first array) of up to nine arrays, from the same start subscript, as the columns of one table. The first array is the first key, each later one breaks the ties left; a minus sign makes a key descending; a string array may be followed by (position,length). With an asterisk the first array must be an integer array: it is filled with the element numbers start to start + n - 1 and only it is rearranged, by the keys after it (an index sort). The sort is a merge sort in place, with the buffer 5200H-56FFH (BASIC/CMD's overlay area, so 576EH is cleared) and the blocks at 4300H; 4288H is cleared so SYS21/SYS is loaded again next time, because the sort changes operands in its own code.
The Array Blocks at 4300H
4DBAH builds nine blocks of 17H (23) bytes at 4300H-43CEH in SYS0/SYS's sector buffer, numbered 1-9; 43CFH-43D0H (block number 0, type 0) end the table. With an index sort block 1 is the index array and the keys start at block 2 (4317H).
| Bytes | Contents |
|---|---|
| +00H | The block number, 1-9. |
| +01H | The element type, which is also its length (2, 3, 4 or 8); 0 = block not used. |
| +02H, +03H | For a string key: the first character compared (1) and the number of characters (255). |
| +04H | Bit 7 a descending key (BASIC 65B4H); bit 6 the same array as an earlier block, whose data is moved only once (65B9H). |
| +05H-+06H | The address of the array's element 0. |
| +07H-+08H | The address of the first element sorted. |
| +09H-+0AH, +0BH-+0CH | Run A's current element and its end. |
| +0DH-+0EH, +0FH-+10H | Run B's current element and its end, which is where the next pair starts. |
| +11H-+12H, +13H-+14H | The array's part of the buffer: its start and the next free byte. |
| +15H-+16H | Where the buffer is copied back to (run A's position when run B's first element was taken); a high byte of 00H means the buffer is not in use. |
Variables
ROM Addresses
| Address Size | Contents |
|---|---|
| 40AFH 1 byte | Value Type The type PTRGET found, also the element length (4E15H). |
| 40F9H-40FAH 2 bytes | VARTAB The start of the simple variables (4D4DH, 4D67H). |
| 40FBH-40FCH 2 bytes | ARYTAB The start of the arrays (4D2EH, 4D6DH, 4D94H, 4E26H). |
| 40FDH-40FEH 2 bytes | STREND The end of the arrays (4D3BH, 4D99H, 4DB3H, 4E29H). |
| 4101H-411AH 26 bytes | DEF Types Read by BASIC 5BB4H for a name without a suffix. |
DOS Addresses
| Address Size | Contents |
|---|---|
| 4288H 1 byte | Overlay in 4D00H The directory slot of the overlay in 4D00H-51FFH; cleared at 4DBBH. |
| 4300H-43D0H 209 bytes | Array Blocks Nine 17H-byte blocks and the end mark, in the sector buffer 4300H-43FFH (4DC2H). |
BASIC/CMD Addresses
| Address Size | Contents |
|---|---|
| 576EH 1 byte | Overlay in 5200H Cleared at 4DBEH: the buffer 5200H-56FFH overwrites BASIC's overlay. |
| 57B8H-57B9H 2 bytes | Next Line Number Stored by BASIC 5D78H (4D21H). |
| 5BFEH, 5C03H, 5C09H 3 bytes | Name Compare Operands Stored by BASIC 5BB4H, compared by 5BFAH. |
| 5200H-56FFH 1280 bytes | Sort Buffer BASIC/CMD's overlay area (4F03H, 4F0DH). |
| 6427H-6428H 2 bytes | Statement-End CALL 4D67H while ERASE or KEEP runs (4D15H); 6496H again from 6497H. |
Operands Changed at Run Time
| Address Size | Contents |
|---|---|
| 4D8DH 1 byte | ERASE or KEEP The opcode at 4D8DH: 28H (JR Z) or 20H (JR NZ), stored at 4D0FH. |
| 4EECH-4EEDH 2 bytes | First Key Start Operand of 4EEBH, stored at 4E6EH, read at 4E73H. |
| 4F02H 1 byte | Row Length Operand of 4F01H, added up at 4E81H through BASIC 65EBH. |
| 4F27H-4F28H 2 bytes | n Operand of 4F26H, stored at 4DEBH and 4EA4H, read at 4E96H and 4EF1H. |
| 4F39H-4F3AH 2 bytes | Run Length Operand of 4F38H, stored at 4F23H. |
| 4F40H-4F41H 2 bytes | Elements Left Operand of 4F3FH, stored at 4F29H and 4F57H. |
| 507EH-507FH 2 bytes | Buffer Count Operand of 507DH, stored at 4F19H, 5081H and 508AH. |
| 5088H-5089H 2 bytes | Buffer Rows Operand of 5087H, stored at 4F09H, read at 4F16H. |
| 50FFH-5100H 2 bytes | Step Routine Operand of the CALL at 50FEH, stored at 50EEH. |
| 5102H 1 byte | Index Sort Operand of 5101H: 01H for an index sort (4E04H; read at 4E5EH, 4ED6H, 5096H and by BASIC 65BCH). |
| 5124H-5125H 2 bytes | Run A Count Operand of 5123H, stored at 4F3BH and 506AH. |
| 5145H-5146H 2 bytes | Run B Count Operand of 5144H, stored at 4F53H and 5047H. |
Outside Routines
| Where | Addresses |
|---|---|
| ROM | 1997H (SNERR), 1E46H (INTID2), 1E4AH (FCERR), 260DH (PTRGET), 2B1CH (GETBYT); RST 08H, RST 10H, RST 18H. |
| BASIC/CMD | 586EH (POP HL / RET), 5BB4H and 5BB6H (read a name), 5BFAH (compare), 5C0DH and 5C18H (next entry), 5C21H (pass a subscript), 5D56H (divide HL by A), 5D78H (next line), 5DEFH (DOS error), 6497H, 65B4H, 65B9H, 65EBH, 65F4H, 65FFH, 6608H. |
Disassembly
4D00H - Entry Point: Select CMD"O", ERASE or KEEP
The SYS0/SYS overlay dispatcher loads SYS21/SYS 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.
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 6426H, set at 4D15H) when an error ends the statement. It ends by jumping to BASIC 6497H, which puts that CALL back to 6496H.
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,[asterisk]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 an asterisk 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 4300H, one per array; the table "The Array Blocks at 4300H" 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.
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 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 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 65EBH, 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.
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 4317H, block 2, so that the compare starts with the first key.
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 65F4H 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.