TRS-80 DOS - LDOS 5.3.1 for the Model I - BASIC/OV4 Disassembled
Page Customization
Page Index
BASIC/OV4
Other Navigation
Summary:
LDOS 5.3.1 BASIC/OV4 Disassembly - The LBASIC Active Variable Dump (Model I)
BASIC/OV4 is the last of the four LBASIC overlays shipped with LDOS 5.3.1. It backs the DOS version 5 statement CMD"V [*DO | *PR] [-S] [-A] [=x]", which dumps a list of the variables that are currently active in the BASIC workspace, together with their values, and lists any user defined functions. It can be issued while a program is running, after it has been interrupted with BREAK, or after it has ended, because the overlay reads the interpreter's own variable tables rather than the program text.
The parameters are those documented in the MISOSYS BASIC Reference Manual (M-40-061). *DO sends the dump to the video display and is the default; *PR sends it to the line printer. -S restricts the dump to scalars, -A restricts it to array variables, and =x restricts it to variables whose first letter is x. Both BASIC/CMD and BASIC/OV4 must be present on the disk.
The mechanism is the interesting part. BASIC/OV4 contains no floating point formatter and no string handling of its own. For every variable it finds it BUILDS A FRAGMENT OF BASIC SOURCE TEXT in a work buffer at 557FH - a quoted literal holding the variable's name, followed by the same name again as a live variable reference - and then hands that text to the Level II ROM's PRINT loop, entering it at 2069H so that the device code in Register A is planted in PRTFLG 409CH on the way in. The interpreter evaluates the reference and prints the value. A one dimensional array A$ therefore produces the successive lines A$(0) = "A$(0), A$(1) = "A$(1) and so on, each of which the ROM prints as the subscript expression followed by the element's contents. A user defined function is handled the other way round: its definition text is untokenized back into readable form through the ROM's BUFLIN expander, entered at 2B85H so that the destination buffer is taken from Register Pair BC instead of from BUFPNT 40A7H, and the result is displayed directly with @DSPLY.
The overlay is a standard LDOS load module of 960 bytes: one type 1FH copyright record ("Copyright 1991 MISOSYS, Inc., All rights reserved"), five type 01H load records and a type 02H transfer record. It is the only one of the four BASIC overlays whose image is not contiguous. The image occupies 5200H-5569H and 5574H-557EH, 885 bytes in all, with a ten byte hole at 556AH-5573H that the overlay zeroes itself at run time and uses as five 16-bit subscript counters. The transfer address is 5200H. The disassembly listing was rebuilt from the binary and compared byte for byte: 885 of 885 bytes covered, zero mismatches, no byte loaded twice. The listing carries one byte that is not in the file, at 557FH; the image ends at 557EH and that byte is not shown on this page.
BASIC/OV4 is loaded over LBASIC's own image at 5200H by @RUN at 4433H, exactly as BASIC/OV1 and BASIC/OV2 are. BASIC/CMD's CMD dispatcher at 5454H matches the letter V at 5475H, sets Register B to 34H so that 54A5H builds the filespec "BASIC/OV4" into CFCB$ 4480H, and then does LD HL,4319H / LD DE,4480H / JP 4433H. The overlay is therefore entered at 5200H with Register Pair HL pointing one byte into INBUF$ 4318H, at the character following the V, so that it can read its own parameters. It does not switch stacks; it runs on LBASIC's stack throughout. Because its image stops at 557EH, everything of BASIC/CMD above that address - including the RST 10H replacement at 6579H, the PRINT hooks and the whole 49-slot Disk BASIC vector table's targets - is still in place while the overlay runs, which is what makes the borrowed ROM PRINT path work.
The overlay exits the same way BASIC/OV1 and BASIC/OV2 do. 5217H block moves eleven bytes from 522DH into CFCB$ 4480H, writes an asterisk into INBUF$ 4318H and jumps to @RUN at 4433H, so BASIC/CMD is reloaded and re-entered through its own re-entry path with the program and the variables intact. The byte at 522CH is an ASCII L that is not part of the copy, the same shared source fragment artefact that appears at 55C7H in BASIC/OV1 and at 56A5H in BASIC/OV2.
The sign-on banner "BASIC Active Variable Dump - Version 4.1" is present in the image at 54C0H-54EAH, complete with its home-cursor and clear-to-end-of-frame control bytes, but no instruction in the overlay displays it. The only reference to the string is the LD HL,54C0H at 5201H, and the POP HL at 5204H overwrites Register Pair HL before it can be used. That is stated here as a fact of the image, not as a fault.
BASIC/OV4 issues no RST 28H supervisor request of its own, so the request code table on the main LDOS disassembly page is unchanged by it.
Memory Map
| Address Range | Contents |
|---|---|
| 5200H-522BH 44 bytes | Entry point, dump selection and the exit stub that reloads BASIC/CMD |
| 522CH-5236H 11 bytes | The unused ASCII L artefact plus the reload filespec "BASIC/CMD" and its 03H terminator |
| 5237H-52A9H 115 bytes | The simple (scalar) variable walk, from VARTAB 40F9H to ARYTAB 40FBH |
| 52AAH-53A8H 255 bytes | The array variable walk, from ARYTAB 40FBH to STREND 40FDH, including the subscript odometer |
| 53A9H-53D5H 45 bytes | Build the variable's name and type declaration character into the output buffer |
| 53D6H-53EEH 25 bytes | The two entry string copier, with the quote aware delimiter test |
| 53EFH-5404H 22 bytes | Convert a 16-bit subscript to five ASCII decimal digits |
| 5405H-5418H 20 bytes | Terminate the built line and print it through the Level II ROM PRINT loop |
| 5419H-541FH 7 bytes | Compare Register Pair IX against Register Pair DE |
| 5420H-5438H 25 bytes | Keyboard poll for the pause key and for BREAK |
| 5439H-5449H 17 bytes | Display a string on the selected output device |
| 544AH-54B0H 103 bytes | The command line parameter parser and the two error exits |
| 54B1H-54BFH 15 bytes | Skip blanks, and fetch the next character folded to upper case |
| 54C0H-54EAH 43 bytes | The sign-on banner. Present in the image but never displayed |
| 54EBH-5569H 127 bytes | The two section headings, the five message strings and three work bytes |
| 556AH-5573H 10 bytes | Not loaded. Five 16-bit subscript counters, zeroed by the overlay at 52E5H |
| 5574H-557EH 11 bytes | The restriction letter, the decimal conversion buffer and the output line prefix |
| 557FH upward uninitialised | Not part of the image. The line the overlay builds for each variable |
| 567FH upward uninitialised | Not part of the image. Staging buffer for a user defined function's definition text |
Variable and Work Cell List
| Address | Purpose |
|---|---|
| 520CH 1 byte | Self-Modifying Code The operand of LD A,01H at 520BH, which gates the CALL NZ,5237H at 520EH. It ships as 01H so the scalar dump runs by default, and the -A switch writes 00H there at 5491H to suppress it. |
| 5212H 1 byte | Self-Modifying Code The operand of LD A,01H at 5211H, which gates the CALL NZ,52AAH at 5214H. It ships as 01H so the array dump runs by default, and the -S switch writes 00H there at 5487H to suppress it. |
| 5567H 1 byte | The position of the subscript the odometer is currently advancing, counted from 1 at the leftmost subscript. Zero means every subscript has rolled over and the array is finished. Ships as 00H, which is also the 00H that follows the "Invalid output device!" message. |
| 5568H 1 byte | The number of dimensions of the array being dumped, taken from array header offset 05H at 52CCH. |
| 5569H 1 byte | The output device code. 00H selects the video display and 01H selects the line printer. It is used twice: 5439H tests it to choose between @DSPLY 4467H and @PRINT 446AH, and 5405H passes it to the ROM in Register A so that it becomes PRTFLG 409CH, whose convention is the same 00H video / 01H printer. |
| 556AH-5573H 10 bytes | Not loaded from the file. Five 16-bit subscript counters, one per dimension, addressed through Register Pair IY. The overlay zeroes all ten bytes at 52E5H before it starts an array. |
| 5574H 1 byte | The =x restriction letter, stored at 5499H after case folding. 00H means no restriction. Tested at 5249H for scalars and at 52BCH for arrays. |
| 5575H-5579H 5 bytes | The five digit decimal conversion buffer, filled from the low digit upward by 53EFH. It ships as five blanks. |
| 557AH 1 byte | The 00H that terminates the decimal conversion buffer for the string copier at 53D6H. |
| 557BH 1 byte | The 22H quotation mark that opens the BASIC string literal the overlay builds. 5405H hands this address to the ROM PRINT loop. |
| 557CH-557EH 3 bytes | Three blanks that indent every dumped line. The user defined function path displays from 557CH, so it starts here and skips the quotation mark. |
| 557FH upward uninitialised | Not part of the image. The variable's name and type character are built here by 53A9H and everything else is appended after them. |
| 567FH upward uninitialised | Not part of the image. The tokenized definition text of a user defined function is copied here at 526CH before the ROM expander at 2B85H reads it back. |
Major Routine List
| Address | Name and Purpose |
|---|---|
| 5200H | Entry Point Entry from @RUN with Register Pair HL at 4319H. Parses the parameters, emits a carriage return, then calls the scalar dump and the array dump according to the two self-modified gate bytes. |
| 5217H | Exit Stub Copies "BASIC/CMD" and its 03H terminator into CFCB$ 4480H, writes an asterisk into INBUF$ 4318H and jumps to @RUN 4433H so BASIC/CMD is reloaded and re-entered. |
| 5237H | Simple Variable Dump Prints the "Simple Variables:" heading, then walks the scalar table from VARTAB 40F9H to ARYTAB 40FBH, three header bytes plus a value whose length is the type byte itself. |
| 52AAH | Array Variable Dump Prints the "Array Variables:" heading, then walks the array table from ARYTAB 40FBH to STREND 40FDH, generating a line for every element of every array through an odometer over the subscript counters. |
| 53A9H | Build The Variable Name Writes the variable's two name characters, in the stored order second then first, followed by the type declaration character derived from the type byte: 08H gives #, 04H gives !, 03H gives $ and 02H gives %. |
| 53D6H | String Copier Copies from (DE) to (HL) until a 00H byte. The 53D8H entry takes a second stop character in Register B, honoured only outside a quoted string; Register C toggles on every 22H so a delimiter inside a literal is ignored. |
| 53EFH | Subscript To Decimal Converts Register Pair HL to five ASCII digits at 5575H-5579H using @DIV 44C4H, and returns Register Pair DE pointing at the first digit. |
| 5405H | Print The Built Line Terminates the built line with 00H, then enters the Level II ROM PRINT loop at 2069H with Register Pair HL at the leading quotation mark 557BH and Register A holding the device code, so that the ROM evaluates the fragment as a PRINT argument list. |
| 5419H | Table End Test Subtracts Register Pair DE from Register Pair IX and returns with the Z FLAG set when they are equal, which is how both walks detect the end of their table. |
| 5420H | Keyboard Poll Scans the keyboard through ROM @KBD 002BH. Code 60H holds the listing until another key is pressed; the routine returns with the Z FLAG set when the key was 01H, BREAK. |
| 5439H | Display A String Sends the 0DH terminated string at (HL) to @DSPLY 4467H or to @PRINT 446AH according to the device byte 5569H, preserving Register Pair IX. |
| 544AH | Parameter Parser Reads *DO, *PR, -S, -A and =x from the command tail, writing the device byte, the two gate bytes and the restriction letter. Every failure goes to one of the two error exits. |
| 54A4H | Parameter Error Exit Displays "Parameter error!" through @LOGOT 447BH and leaves through the exit stub. The 54A8H entry inside its LD IX instruction displays "Invalid output device!" instead. |
| 54B1H | Skip Blanks 54B2H returns the character at (HL) with the Z FLAG set at a 0DH; 54B1H advances first. 54BBH advances and returns the next character with bit 5 reset, folding lower case to upper case. |
Cross-Reference Notes
BASIC/OV4 is reached only from BASIC/CMD. The CMD statement handler at 57C6H passes a quoted string of two characters or more to the dispatcher at 5454H, which requires a space as the second character and then matches the first: N selects BASIC/OV1, X selects BASIC/OV2 and V sets Register B to 34H and selects BASIC/OV4. 54A5H builds the eight character stem "BASIC/OV" plus that digit into CFCB$ 4480H, 54E1H has already copied the quoted string into INBUF$ 4318H with a carriage return terminator, and 5484H hands both to @RUN.
Resident LDOS vectors used: @RUN 4433H, @DSPLY 4467H, @PRINT 446AH, @LOGOT 447BH and @DIV 44C4H. Level II ROM entry points used: @KBD 002BH, $KEY 0049H, the LD (409CH),A inside LPRINT at 2069H, and the expander inside BUFLIN at 2B85H. BASIC workspace read: VARTAB 40F9H, ARYTAB 40FBH and STREND 40FDH. LDOS buffers used: INBUF$ 4318H and CFCB$ 4480H. No RST 28H supervisor request is issued.
The variable entry layout this overlay depends on is the same one BASIC/OV3 selector 04H walks: a scalar entry is a type byte whose value is also the length of the value that follows, then the second name character, then the first name character, then the value; and bit 7 of the first name character marks a user defined function, which is the bit BASIC/CMD's function reference parser at 55EBH sets. An array header is that same three byte prefix followed by a 16-bit block length, a dimension count and one 16-bit dimension size per dimension, stored last dimension first.
Disassembly:
5200H - Entry Point, Dump Selection and the Exit Stub
The transfer address of the module. @RUN has just written the overlay over LBASIC's own image at 5200H and entered it here with Register Pair HL pointing at 4319H, one byte into INBUF$ 4318H, which holds the text of the CMD string with a 0DH terminator. The three bytes 4318H, 4319H and onwards therefore read V, then the space the dispatcher insisted on, then the parameters. This block parses those parameters, runs whichever of the two dumps the parameters left enabled, and falls into the stub that reloads BASIC/CMD.
Load Register A with the scalar dump gate byte held at 520CH, the operand of this instruction. It is assembled as 01H so that scalars are dumped by default, and the -A switch writes 00H there at 5491H when the user asks for array variables only.
Load Register A with the array dump gate byte held at 5212H, the operand of this instruction. It is assembled as 01H so that arrays are dumped by default, and the -S switch writes 00H there at 5487H when the user asks for scalars only.
Exit Stub
Every path out of the overlay arrives here: normal completion falls through from 5214H, a BREAK detected by the keyboard poll jumps here from 52A4H, 534EH or 53A3H, and both parameter error messages jump here from 54AEH. The stub reloads BASIC/CMD from disk with a command tail of a single asterisk, which is BASIC/CMD's documented re-entry request, so the interpreter comes back with the program and the variables untouched.
522CH - Reload Filespec For BASIC/CMD
The eleven bytes the exit stub moves into CFCB$ 4480H, preceded by one byte that is not part of them. The same "L" plus "BASIC/CMD" plus 03H layout appears at 55C7H in BASIC/OV1 and at 56A5H in BASIC/OV2, always with the copy starting one byte in, so the file that gets loaded is BASIC/CMD and not LBASIC/CMD.
5237H - The Simple Variable Dump
Called from 520EH when the gate byte at 520CH is non-zero. The scalar variable table runs from VARTAB 40F9H up to ARYTAB 40FBH, and each entry is a type byte whose value is also the length of the value that follows it, then the second name character, then the first name character, then the value itself. This routine walks that table and, for each entry that passes the =x restriction, either displays a user defined function's definition or builds a BASIC PRINT argument list that makes the interpreter print the variable's value.
Loop Start
Each pass handles one scalar entry. Register Pair DE is reloaded from ARYTAB on every pass because the interpreter is not running while the dump proceeds and the table cannot move, but the reload also costs nothing and keeps the test self-contained.
User Defined Function Path
The value field of a DEF FN entry is not a number but a pointer into the program text at which the function's tokenized definition begins, planted there by BASIC/CMD's DEF handler at 56E3H and read by its evaluator at 5603H. The overlay copies that text into a staging buffer, expands the tokens back into readable keywords with the ROM's own untokenizer, and displays the result.
Ordinary Scalar Path
Nothing here formats a value. The routine finishes the BASIC source fragment that was started by the name builder at 53A9H, so that the buffer holds a quoted literal giving the variable's name followed by the same name again as a live variable reference, and lets the interpreter's own PRINT loop evaluate it.
Advance To The Next Scalar Entry
Reached from 5254H when the =x restriction rejected the variable, from 5284H after a user defined function, and by falling through after an ordinary variable has been printed.
Loop End
52AAH - The Array Variable Dump
Called from 5214H when the gate byte at 5212H is non-zero. The array table runs from ARYTAB 40FBH up to STREND 40FDH. An array header is a type byte, the second name character, the first name character, a 16-bit block length, a dimension count, and then one 16-bit dimension size per dimension stored last dimension first. This routine walks that table and, for each array, drives an odometer over the subscript counters at 556AH so that one line is produced for every element in the array, each line being a BASIC PRINT argument list containing the subscripted reference twice - once inside a quoted literal and once as live source text.
Loop Start
Each pass handles one complete array.
Loop Start
Advance Register Pair IX by two bytes per dimension so that offsets 04H and 05H, which addressed the block length and the dimension count on entry, come to address the LAST word of the dimension size table. Because those sizes are stored last dimension first, that last word is the size of the FIRST subscript, which is the one printed first.
Loop End
Loop Start
Zero the ten byte subscript counter block. This is what gives the unloaded gap in the module image its meaning: the overlay initialises it itself rather than carrying ten zero bytes on disk.
Loop End
Loop Start
The odometer. Each pass through 52F9H deals with one subscript position. The stack carries the array header address pushed at 52D4H and, above it, one saved output buffer position for every subscript position currently open, pushed here.
Loop Start
Skip leading zeros in the five digit field at 5575H-5579H by advancing Register Pair DE past them.
Loop End
Complete Subscript List
Reached from 5336H when the rightmost subscript has just been written. The reference is closed and handed to the interpreter, then the odometer resumes at the same subscript position with the counter one higher.
Subscript Rollover
Reached from 5310H when the counter for the current subscript has reached the dimension's element count. The counter is reset, the pointers step back to the subscript on the left, and the buffer position saved for the exhausted subscript is discarded so that the enclosing one is uncovered.
Too Many Subscripts
Reached from 52D6H when the array was declared with six or more dimensions, which the five counters at 556AH cannot enumerate. The array's name is already in the buffer and the message is simply appended to it.
Advance To The Next Array
5391H is reached from 52FEH when the array has been fully enumerated and from 538EH after the too many subscripts message; 5393H is reached from 52C6H when the =x restriction rejected the array, in which case the header address is still in Register Pair IX and no stacked copy has to be recovered.
Loop End
53A9H - Build The Variable Name And Type Character
Called from 5256H for a scalar and from 52C9H for an array. Register Pair IX addresses the entry's type byte. In a Level II BASIC variable entry the two name characters are stored in the order second character then first character, at offsets 01H and 02H, so this routine writes offset 02H first. The type declaration character is derived arithmetically from the type byte rather than from a table. Exit is with Register Pair HL pointing at the first free byte after what was written.
53D6H - The String Copier
Two entries into one loop. Both copy from (DE) to (HL) and stop on a 00H byte. The 53D8H entry additionally takes a stop character in Register B, which is honoured only when the copy is not inside a quoted string; Register C carries the in-string state and toggles between 00H and 22H on every quotation mark encountered. The stopping character is written to the destination before it is tested, but Register Pair HL is not advanced past it, so the caller may overwrite it. Exit is with Register Pair HL pointing at the first free byte.
Loop Start
Loop End
53EFH - Convert A Subscript To Five Decimal Digits
Called from 531CH with the subscript value in Register Pair HL. The digits are produced least significant first by repeated division by ten through the resident @DIV vector, and are written backwards into the five byte field at 5575H-5579H, which the image ships as five blanks. The 00H at 557AH terminates the field for the string copier. Leading zeros are not suppressed here; the caller skips them at 5321H.
Loop Start
Loop End
5405H - Print The Built Line Through The ROM
Called from 5294H for a scalar, from 5365H for an array element and from 538EH for the too many subscripts message. By this point the buffer from 557BH holds a fragment of BASIC source: a quotation mark, three blanks, the variable's name, the characters space equals space, a closing quotation mark, and then the same reference again as live text. Handing that to the interpreter's PRINT loop is what makes the value appear, and is why the overlay needs no formatter of its own.
5419H - Compare The Walking Pointer Against The Table End
Called from 5245H with the value from ARYTAB 40FBH in Register Pair DE and from 52B8H with the value from STREND 40FDH. Register Pair IX cannot be compared directly, so it is moved through the stack into Register Pair HL first. Only the flags matter to the callers.
5420H - Keyboard Poll For Pause And BREAK
Called after every printed line, from 52A1H in the scalar walk and from 534BH and 53A0H in the array walk. It gives the user the two controls a long listing needs: shift and at-sign holds the display, and BREAK abandons the dump. Both index registers are preserved because the ROM keyboard routines make no promises about them.
Loop Start
The pause key was seen. The routine now waits for a key and keeps waiting as long as the pause key is what comes back, so the listing resumes on any other key.
Loop End
5439H - Display A String On The Selected Device
Called from 5208H with an empty string, from 523AH and 52ADH with the two section headings and from 5281H with a user defined function's line. Register Pair HL points at a string terminated by a 0DH, which both service calls display. The routine chooses between them from the device byte at 5569H, holding the flags from that test across the first call.
544AH - The Command Line Parameter Parser
Called from 5205H with Register Pair HL at 4319H, one byte into INBUF$ 4318H, where BASIC/CMD's command string copier at 54E1H left the text of the CMD"V" argument with a 0DH terminator. The parser accepts the four documented forms in the order the manual gives them: an optional *DO or *PR device selection first, then any number of -S, -A and =x parameters. Every failure leaves through one of the two error exits and never returns to the caller.
Parameter Loop Start
Every accepted switch comes back here, so -S, -A and =x may be given in any order and any number of times, separated by blanks.
Store 00H at 5212H, which is the operand of the LD A,01H at 5211H. That instruction feeds the CALL NZ,52AAH at 5214H, so writing zero there suppresses the array dump entirely and leaves only the scalars, which is what -S asks for.
Store 00H at 520CH, which is the operand of the LD A,01H at 520BH. That instruction feeds the CALL NZ,5237H at 520EH, so writing zero there suppresses the scalar dump entirely and leaves only the arrays, which is what -A asks for.
Error Path
Two messages share one exit. The four bytes at 54A7H hold a single DD-prefixed LD IX,5550H when they are entered at 54A7H, and the same bytes from 54A8H onward hold LD HL,5550H. Entered at 54A4H the routine loads Register Pair HL with the address of "Parameter error!" and the LD IX that follows is inert; entered at 54A8H it loads Register Pair HL with the address of "Invalid output device!" instead. One four byte sequence, two messages, and the DD prefix is what makes the first entry step over the second.
54B1H - Character Fetch Helpers
Two small routines the parser leans on. 54B2H returns the character at (HL) and, when that character is a blank, advances through 54B1H until it is not; the Z FLAG marks the 0DH that ends the command tail. 54BBH advances one character unconditionally and clears bit 5 of the result, which folds the letters a to z onto A to Z so that every parameter may be typed in either case.
Loop Start
Loop End
54C0H - The Sign-On Banner
The banner is complete and correctly formed, with the two video control bytes that home the cursor and clear the frame ahead of it and a carriage return behind it, but it is never sent anywhere. The only instruction in the image that names 54C0H is the LD HL,54C0H at 5201H, and the POP HL at 5204H replaces Register Pair HL before any display call is made. BASIC/OV1 and BASIC/OV2 display their equivalents at 55D2H and 579EH.
54EBH - Section Headings, Messages And Work Bytes
Two heading strings and five message strings, followed by three bytes that are work cells rather than text. The two headings are terminated by carriage returns and are sent through 5439H; the four strings that are appended to the output buffer are terminated by 00H for the string copier at 53D6H; and the two error messages are terminated by carriage returns because they go to @LOGOT. The user defined function legend at 552EH is the exception: it is appended by the copier yet ends with a carriage return, so that copy runs past it and only stops at the 00H at 5567H, which is harmless because the display stops at the carriage return.
556AH - The Subscript Counter Block
These ten bytes are the gap in the module image. No load record covers them, so they hold whatever was in memory when the overlay arrived, and the array dump clears all ten at 52E5H before it starts on an array. They are five 16-bit counters, one per subscript position, addressed through Register Pair IY at offsets 00H and 01H, and it is their number that fixes the six dimension limit tested at 52D2H.
5574H - Work Cells And The Output Line Prefix
The last load record of the module covers just these eleven bytes. They are the restriction letter, the five digit decimal conversion field with its terminator, and the four characters that stand immediately in front of the output buffer at 557FH. The image ends at 557EH; everything from 557FH up is uninitialised RAM that the overlay claims for the line it builds.