TRS-80 DOS - LDOS 5.3.1 for the Model I - BASIC/CMD Disassembled

Page Customization

Summary:

LDOS 5.3.1 BASIC/CMD Disassembly - LBASIC, the Disk BASIC Interpreter (Model I)

BASIC/CMD is LBASIC, the Disk BASIC supplied with LDOS 5.3.1 for the TRS-80 Model I. It is not a complete BASIC interpreter. The Model I Level II BASIC ROM at 0000H-2FFFH already contains the expression evaluator, the tokenizer, the statement executor and the whole floating-point library, and it was written from the start with a set of RAM hooks reserved for a disk extension that did not exist when the machine shipped. LBASIC is that extension. It supplies the code behind every disk keyword the ROM knows the name of but has no body for, and it re-points a handful of the ROM's inner loops at itself so it can add keywords the ROM has never heard of.

Physically the file is a single LDOS /CMD load module preceded by a type-1FH copyright record ("Copyright 1991 MISOSYS, Inc., All rights reserved"). It loads into two disjoint regions. The first, 4E00H-4E9EH, is 159 bytes that are never executed where they land: they are two blocks of JP instructions that the initialization code copies into the Level II BASIC RAM hook areas and then abandons. The second, 5200H-65FBH, is the interpreter extension proper, 5116 contiguous bytes, and the load module's transfer address is 52CCH.

The 4E00H block is loaded into what is normally LDOS overlay space. That is deliberate and it is safe: a /CMD program is brought in by @LOAD, and no numbered SYS overlay is resident at 4E00H at the moment the load records are being placed. The very first thing LBASIC does after taking control is to copy 4E00H-4E92H to 4152H and 4E93H-4E9EH to 4000H, after which the 4E00H area is free again. LBASIC then re-uses it as the load address for BASIC/OV3, the error-message and Sort overlay.

The Two RAM Hook Blocks

The 147 bytes at 4E00H-4E92H are forty-nine 3-byte JP instructions. They are copied verbatim to 4152H-41E4H, which is the Level II BASIC ROM's Disk BASIC entry-point table. Every slot in that table is a fixed address the ROM CALLs or JPs to when it meets a disk keyword or reaches one of its documented exit points; in a cassette-only machine each slot holds a RET or an error jump. Installing this block is what turns Level II BASIC into Disk BASIC. The slot order is fixed by the ROM and is confirmed here by the code each entry lands on: 4152H is CVI, 4158H is CVS and 415EH is CVD (the three routines at 5D2CH, 5D2FH and 5D32H differ only in the value they load into Register A, 01H, 03H and 07H, and share the tail at 5D34H), while 416AH, 416DH and 4170H are MKI$, MKS$ and MKD$ (the routines at 5D13H, 5D16H and 5D19H, which load 02H, 04H and 08H and share the tail at 5D1BH).

The 12 bytes at 4E93H-4E9EH are four more JP instructions, copied to 4000H-400BH. Those four addresses are the RAM hooks behind RST 08H, RST 10H, RST 18H and RST 20H. Three of them are pointed straight back at the ROM routines they normally reach - 1C96H for the RST 08H syntax check, 1C90H for the RST 18H unsigned DE:HL compare and 25D9H for the RST 20H value-type test. The fourth is the important one: the RST 10H "get the next character from the BASIC text stream" hook at 4003H is pointed at LBASIC's own routine at 6579H instead of at the ROM's CHRGTR at 1D78H. RST 10H is executed by the interpreter thousands of times per second, so owning it is what lets LBASIC recognise statement keywords the ROM's token table does not contain.

Start-Up Sequence

Execution begins at 52CCH with HL pointing at the command tail that LDOS left behind the program name. The initialization stores the first character of that tail and the tail pointer into self-modifying operand cells, saves the entry values of HIGH$ (4049H), TXTTAB (40A4H), 408EH and STKTOP (40A0H) into four more operand cells so that a later re-entry can undo everything, copies the ROM's 39-byte relocatable data table from 18F7H down to 4080H (the block that contains the self-modifying operands of the floating-point divide), installs the two hook blocks, sizes memory, prints the sign-on banner and finally builds the file control blocks. Control then passes to the ROM's BASIC command loop through the standard entry at 1A81H or, when a command tail was supplied, by re-executing that tail as a BASIC command line.

Before any of that, 5344H reads OSVER$ at 403EH and refuses to run under anything earlier than 5.3, displaying "Use LDOS 5.3 or later!" and returning to DOS. This is the same OSVER$ byte the rest of LDOS uses; 51H is the value the gate compares against.

Command Line Parameters

LBASIC accepts four keyword parameters, parsed by the resident @PARAM service at 4476H against the 8-byte-per-entry table at 528BH. MEM and M set the highest address BASIC may use, FILES and F set the number of disk file buffers to build, BLK and B switch blocked file mode on or off, and EXT and E switch off the default /BAS extension that LOAD, RUN, MERGE and SAVE otherwise apply. The published ranges are 1 to 15 files with 3 assumed, blocked mode ON by default, and EXT ON by default; the code tests the file count against 10H at 53C8H and takes the blocked-mode default from the assembled 0FFFFH at 53B9H, so both agree. A parameter that fails validation prints "Parameter error" through @LOGOT at 447BH and aborts; a memory request that cannot be satisfied prints "Out of memory". A command tail beginning with an asterisk is treated as a re-entry rather than a fresh start, which is how BASIC is resumed after a CMD shell-out.

File Buffers and the USR Vector Table

The FILES parameter decides how much workspace is built above the loaded image. 53CFH walks a chain of file buffers starting at 6632H, each 0122H bytes long (0222H when blocked file mode is on), zeroing the first byte of each and writing its address into a pointer table that starts at 65FAH. Before that, 5397H fills the ten words at 661CH-662FH with 642FH: that is the USR0 to USR9 vector table, and 642FH is the head of the error-code table, so a USR number that has never been given an address raises an error instead of jumping into nothing. Everything from 65FAH upward is workspace that exists only at run time; the loaded image itself ends at 65FBH.

The CMD String Shell

CMD"string" is the mechanism by which a BASIC program runs an LDOS command. The handler at 54ECH is the most intricate piece of code in the file. It copies the command text into INBUF$ at 4318H, checks that there is room, saves the six bytes of @EXIT and @ABORT (402DH-4032H) and the four bytes of @ERROR (4409H-440CH) into 540AH-5413H, then block-moves the whole of BASIC's low memory - 5200H up to STREND (40FDH), plus 407DH-41E4H - into the free space immediately below the stack, lowers HIGH$ to protect it, rewrites @EXIT, @ABORT and @ERROR to point at relocated stubs, and hands the line to @CMNDI at 4405H. When the DOS command finishes it exits through the patched @EXIT, which lands in the relocated copy of the restore routine at 559EH; that copies everything back, restores HIGH$, SP and the three DOS vectors from the saved bytes, and returns to BASIC. The saved-vector area is genuinely clever: 402DH, 4030H and 4409H each begin with a C3H JP opcode, so the saved copy is itself executable and the error stub simply jumps to it to chain to the real DOS handler.

The save area at 540AH-5413H physically overlays the instructions at 540AH-5413H, which belong to the MEM parameter range check. That is safe because the MEM check runs only during initialization and is never re-entered once BASIC is running.

Overlay Loading

CMD"N", CMD"X" and CMD"V" are dispatched at 5454H. The character following the quote selects the overlay digit in Register B: 31H for N (renumber, BASIC/OV1), 32H for X (cross-reference, BASIC/OV2) and 34H for V (variable dump, BASIC/OV4). Anything else is treated as a DOS command string and goes to 54ECH. 54A5H builds the filespec by copying the eight characters "BASIC/OV" from 548DH into CFCB$ at 4480H, appending the digit and a zero byte, and the overlay is then started with @RUN at 4433H with HL pointing at INBUF$ 4318H so the overlay can read the rest of the command line.

BASIC/OV3 is handled differently at 54B6H because it is a subroutine, not a program: it holds the long error-message text and the Sort routine and must return to LBASIC. It loads at 4E00H, and LBASIC uses OVRLY$ at 430EH as its "OV3 is already resident" marker - if 430EH holds 01H the overlay is simply entered at 4E00H without a disk access. Otherwise the code reproduces the resident @RUN preamble inline (set bit 2 of SFLAG$ at 430FH, then CALL @LOAD at 4430H, then EX (SP),HL and RET) so that the loaded overlay is entered with LBASIC's return address underneath it. A failed load prints "Can't load OV3".

Memory Map

Address RangeContents
4E00H-4E92H
147 bytes
Image of the 49-entry Disk BASIC vector table. Copied to 4152H-41E4H at 5331H and then abandoned.
4E93H-4E9EH
12 bytes
Image of the four RST hook vectors. Copied to 4000H-400BH at 5306H and then abandoned.
4E00H-5281H
1154 bytes
Run-time load area for BASIC/OV3 (error messages and Sort). Occupies the space the two hook images were loaded into.
5200H-524FH
80 bytes
Sign-on banner text, CR-framed and 00H-terminated.
5250H-528AH
59 bytes
Message block: version refusal, "Parameter error", "Out of memory".
528BH-52CBH
65 bytes
@PARAM keyword table, eight 8-byte entries plus a 00H terminator.
52CCH-5453H
392 bytes
Initialization, version gate, parameter processing, file control block construction and entry to BASIC.
5454H-548CH
57 bytes
CMD string dispatcher and the DOS command tail builder.
548DH-54A4H
24 bytes
"BASIC/OV" filespec stem and the "Can't load OV3" message.
54A5H-55F6H
338 bytes
Overlay filespec builder, BASIC/OV3 loader and the CMD string memory-swap shell.
55F7H-65FBH
4101 bytes
The disk keyword bodies, the file I/O engine, the RST 10H token hook and the shared support routines.
65FAH upward
run time
Workspace built at start-up: file buffer pointer table from 65FAH, the ten-word vector table 661CH-662FH, and the file buffers themselves from 6632H, each 0122H or 0222H bytes.

Self-Modifying Operand Cells and Run-Time Variables

Address RangePurpose
5301H-5302H
2 bytes
Operand of the LD HL,0000H at 5300H. Loaded at 52ECH with the entry value of 408EH, the maximum-files byte, so a re-entry can restore it.
5354H
1 byte
Operand of the LD A,00H at 5353H. Loaded at 52D4H with the first character of the LDOS command tail. Compared against 2AH at 5355H and again at 53F4H to decide between a cold start and a re-entry.
5360H-5361H
2 bytes
Operand of the LD HL,0000H at 535FH. Loaded at 52F2H with the entry value of STKTOP (40A0H).
5366H-5367H
2 bytes
Operand of the LD HL,0000H at 5365H. Loaded at 52E6H with the entry value of TXTTAB (40A4H).
53ACH-53ADH
2 bytes
Operand of the LD HL,0000H at 53ABH. Loaded at 52D7H with the command tail pointer and reloaded at 53B6H with whatever @PARAM left in HL.
53FEH-53FFH
2 bytes
Operand of the LD DE,0000H at 53FDH. Loaded at 52E0H with the entry value of HIGH$ (4049H); it is the ceiling the MEM parameter is checked against.
540AH-5413H
10 bytes
Save area for the DOS vectors across a CMD shell-out: 402DH-4032H (@EXIT and @ABORT) go to 540AH-540FH and 4409H-440CH (@ERROR) to 5410H-5413H. Both copies retain their C3H JP opcodes and are jumped to directly. The area overlays the MEM range-check instructions, which do not run again.
556BH-556CH
2 bytes
Operand of the LD DE,0000H at 556AH. The destination of the outward LDDR, set at 5537H to SP minus one.
5572H-5573H
2 bytes
Operand of the LD BC,0000H at 5571H. The byte count of the block to be saved, computed at 5519H as STREND minus 5200H.
5592H-5593H
2 bytes
Operand cell loaded at 5529H with HIGH$ so the relocated copy can report it.
5598H-5599H
2 bytes
Operand of the JP M,0000H at 5597H. Set at 5567H to the relocated address of the saved @ERROR vector bytes, so a return-to-caller error chains to the real DOS handler.
55BAH-55BBH
2 bytes
Operand of the LD BC,0000H at 55B9H. The residual byte count for the second half of the restore LDIR.
55BFH-55C0H
2 bytes
Operand of the LD HL,0000H at 55BEH. The HIGH$ value to restore after a CMD shell-out.
55C5H-55C6H
2 bytes
Operand of the LD SP,0000H at 55C4H. The stack pointer to restore after a CMD shell-out.
65FAH upward
run time
File buffer pointer table, two bytes per file, built at 53E5H. The image ships 65FAH-65FBH as 0000H.
661AH-661BH
run time
Address of the first file buffer, stored at 53E2H.
661CH-662FH
run time
The USR0 to USR9 vector table, ten words, each initialized at 539FH to 642FH so that an undefined USR raises an error. DEF USR writes a slot at 5737H and USR reads one at 5704H.
6630H-6631H
run time
Cleared to 0000H at 53A8H.

Major Routines

AddressName and Purpose
52CCHInitialization Entry
The load module's transfer address. Entered with HL pointing at the LDOS command tail. Saves the entry environment into self-modifying cells, installs both RAM hook blocks, sizes memory and starts BASIC.
5344HVersion Gate
Reads OSVER$ at 403EH and refuses to run below 51H, displaying "Use LDOS 5.3 or later!" through STROUT at 28A7H.
5397HUSR Vector Table Initialization
Fills the USR0 to USR9 vector table at 661CH-662FH with ten copies of the error entry 642FH and clears 6630H, then calls @PARAM at 4476H against the table at 528BH.
53CFHFile Buffer Chain Builder
Builds one 0122H-byte (or 0222H-byte) buffer per file from 6632H upward and records each address in the pointer table from 65FAH.
5400HMEM Parameter Range Check
Validates the requested memory ceiling against HIGH$ and against TXTTAB plus 012CH, sets MEMSIZ (40B1H) and STKTOP (40A0H), then calls the ROM NEW routine at 1B4DH.
5454HCMD String Dispatcher
Entered from the CMD vector at 4173H. Selects BASIC/OV1, BASIC/OV2 or BASIC/OV4 on N, X or V, and sends anything else to the DOS command shell at 54ECH.
54A5HOverlay Filespec Builder
Copies "BASIC/OV" from 548DH into CFCB$ at 4480H, appends the digit in Register A and a 00H byte.
54B6HBASIC/OV3 Loader
Uses OVRLY$ at 430EH as the resident marker; loads the overlay to 4E00H with @LOAD at 4430H using the resident @RUN preamble, and enters it by RET.
54E1HCommand Tail Copier
Copies a 00H-terminated string to INBUF$ at 4318H and terminates it with a carriage return.
54ECHCMD String Shell
Saves BASIC's whole low-memory state above the stack, patches @EXIT, @ABORT and @ERROR, and passes the line to @CMNDI at 4405H.
559EHCMD String Shell Return
The relocated restore routine that the patched @EXIT, @ABORT and @ERROR vectors reach. Copies everything back, restores HIGH$, SP and the DOS vectors and returns to BASIC.
55EBHVariable Reference Parser
Checks for the FN token, sets SUBFLG (40DCH) to 80H and enters the ROM's PTRGET at 2612H.
55F7HFN
User-defined function evaluation. Reached from vector slot 4155H.
56D8HDEF
DEF FN and DEF USR processing. Reached from vector slot 415BH.
56FBHUSR
USRn function call. Reached from vector slot 41A9H.
573CHPRINT Device Check
The ROM's print processing exit at 41CAH; recognises PRINT# and diverts to the file writer.
57C6HCMD
The CMD statement. Reached from vector slot 4173H.
587BHPRINT#
Reached from vector slot 41BEH.
5882HINPUT#
Reached from vector slot 41D6H.
5912HLINE INPUT
Reached from vector slot 41A3H.
5A2CHLIST and INPUT Assignment Exit
Reached from vector slot 41DFH.
5A42HLong Error Message Handler
Reached from vector slot 41A6H. Expands a BASIC error code into full text using BASIC/OV3.
5A61HTIME$
Reached from vector slot 4176H.
5A77HINSTR
Reached from vector slot 419DH.
5AFFH& (Hexadecimal and Octal Constants)
Reached from vector slot 4194H.
5B50HMID$ on the Left of an Assignment
Reached from vector slot 41D9H, the only ROM exit that is a JP rather than a CALL.
5BD1HKeyboard Line Input Exit
Reached from vector slot 41AFH.
5C00HByte Output To Any Device
Reached from vector slot 41C1H, the ROM's character output exit at 032CH.
5C0EHKeyboard Scan Exit
Reached from vector slot 41C4H, the ROM's INKEY$ and BREAK check at 0358H.
5C9EHComma Field and TAB Exit
Reached from vector slot 41D3H.
5CB6HLine Table Update Exit
Reached from vector slot 41B5H.
5CC3HLine Table Update With CLEAR Exit
Reached from vector slot 41B8H.
5CCFHPRINT Carriage Return Exit
Reached from vector slot 41D0H.
5CD0HNumber Conversion Exit
Reached from vector slot 41CDH.
5D13H / 5D16H / 5D19HMKI$ / MKS$ / MKD$
Three entries into one routine, separated by a 01H byte so that a fall-through reads the following LD A,nn as the operand of a LD BC. Reached from vector slots 416AH, 416DH and 4170H.
5D2CH / 5D2FH / 5D32HCVI / CVS / CVD
The same three-entry construction, reached from vector slots 4152H, 4158H and 415EH.
5D49HREAD and INPUT Variable Assignment Exit
Reached from vector slot 41DCH.
5E42HMERGE
Reached from vector slot 418BH.
5E70HRUN With A Filename
Reached from vector slot 41C7H.
5E78HLOAD
Reached from vector slot 4188H.
5FC4HREADY Prompt Exit
Reached from vector slot 41ACH.
5FD8HPost-Tokenization Exit
Reached from vector slot 41B2H.
5FECHSAVE
Reached from vector slot 41A0H.
6043HCLOSE
Reached from vector slot 4185H.
6074HNEW and END File Closure Exit
Reached from vector slot 41BBH.
607EHFIELD
Reached from vector slot 417CH.
60D8H / 60D9HRSET / LSET
Reached from vector slots 419AH and 4197H.
617BHEOF
Reached from vector slot 4161H.
61B2H / 61B7HLOC / LOF
Reached from vector slots 4164H and 4167H.
6204H / 6205HPUT / GET
Reached from vector slots 4182H and 417FH.
62DBHOPEN
Reached from vector slot 4179H.
6385HEXT Parameter Cell
The result cell the EXT and E keywords in the @PARAM table at 52BBH and 52C3H write, controlling whether LOAD, RUN, MERGE and SAVE supply the default /BAS extension.
6412HKILL
Reached from vector slot 4191H.
642FHError Code Table
Ten entry points separated by 01H skip bytes, each loading Register E with a BASIC error number and converging on the ROM error handler at 19A2H through 644CH. 642FH itself supplies 2EH and is the default contents of all ten USR vector slots at 661CH-662FH.
6579HRST 10H Token Hook
Installed at 4003H in place of the ROM's CHRGTR. Fetches the next character of the BASIC text stream and recognises the keywords LBASIC adds.

Cross-Reference Notes

BASIC/CMD is started by the LDOS command interpreter (SYS1) like any other /CMD program and issues no RST 28H supervisor request of its own. Everything it asks of the operating system goes through a resident SYS0 vector or a Level II BASIC ROM entry point.

Resident SYS0 vectors used: @EXIT 402DH, @ABORT 4030H, @CMNDI 4405H, @ERROR 4409H (and its operand 440AH), @DEBUG 440DH, @FSPEC 441CH, @INIT 4420H, @OPEN 4424H, @CLOSE 4428H, @KILL 442CH, @LOAD 4430H, @RUN 4433H, @READ 4436H, @WRITE 4439H, @REW 443FH, @POSN 4442H, @BKSP 4445H, @PEOF 4448H, @CKEOF 444BH, @LOC 445AH, @LOF 445DH, @TIME 446DH, @DATE 4470H, @FEXT 4473H, @PARAM 4476H, @LOGOT 447BH, CFCB$ 4480H and the disk primitive slot at 4750H. Resident state read or written: @KITSK 4300H, OVRLY$ 430EH, SFLAG$ 430FH, the DEBUG re-entry trap slot 4315H, INBUF$ 4318H and 4319H, KFLAG$ 4423H, and the patch byte 4BF0H, which is the NOP at the head of the RST 28H dispatcher's sub-80H request path at 4BEFH. LBASIC writes 0C8H (RET Z) there to make that path return immediately, and 00H (NOP) to restore it, in step with setting and clearing bit 4 of SFLAG$ 430FH.

Level II BASIC ROM entry points are used throughout; the file references 132 distinct ROM addresses in 0000H-2FFFH. The four RST hooks it installs at 4000H-400BH point at 1C96H (SYNCHR), 6579H (LBASIC's own CHRGTR replacement), 1C90H (DCOMPR) and 25D9H (GETYPR).

Overlays: BASIC/OV1 (CMD"N", renumber) and BASIC/OV2 (CMD"X", cross-reference) and BASIC/OV4 (CMD"V", variable dump) all load at 5200H and are started with @RUN, replacing LBASIC's own image; BASIC/OV3 (long error messages and Sort) loads at 4E00H, is a subroutine, and is tracked by OVRLY$ at 430EH.

Disassembly:

4E00H - Disk BASIC Vector Table Image (Data)

Forty-nine 3-byte JP instructions that are never executed at this address. The initialization routine at 5331H block-moves all 147 bytes to 4152H-41E4H, the Level II BASIC ROM's Disk BASIC entry-point table, and the area is then re-used as the load address for BASIC/OV3. Every slot below is identified by the fixed ROM address it lands on once the copy has been made; the ROM either CALLs or JPs to that address when it meets the corresponding keyword or reaches the corresponding exit point.

4E00
Slot 0, installed at 4152H. This is the CVI function, which converts the first two bytes of a string to an integer. The routine at 5D2CH loads Register A with 01H, the length less one, and falls into the shared tail at 5D34H.
4E03
Slot 1, installed at 4155H. This is the FN function reference, the evaluation of a user-defined function. The routine at 55F7H parses the function name and its argument list.
4E06
Slot 2, installed at 4158H. This is the CVS function, which converts the first four bytes of a string to a single-precision number. The routine at 5D2FH loads Register A with 03H and falls into the shared tail at 5D34H.
4E09
Slot 3, installed at 415BH. This is the DEF statement, covering both DEF FN and DEF USR. The routine at 56D8H tests the following token to tell the two apart.
4E0C
Slot 4, installed at 415EH. This is the CVD function, which converts the first eight bytes of a string to a double-precision number. The routine at 5D32H loads Register A with 07H and falls into the shared tail at 5D34H.
4E0F
Slot 5, installed at 4161H. This is the EOF function, which reports whether the file whose buffer number follows has reached end of file.
4E12
Slot 6, installed at 4164H. This is the LOC function, which returns the current record number of an open file.
4E15
Slot 7, installed at 4167H. This is the LOF function, which returns the number of the last record in an open file.
4E18
Slot 8, installed at 416AH. This is the MKI$ function, which packs an integer into a 2-character string. The routine at 5D13H loads Register A with 02H, the value-type code for integer, and falls into the shared tail at 5D1BH.
4E1B
Slot 9, installed at 416DH. This is the MKS$ function, which packs a single-precision number into a 4-character string. The routine at 5D16H loads Register A with 04H, the value-type code for single precision.
4E1E
Slot 10, installed at 4170H. This is the MKD$ function, which packs a double-precision number into an 8-character string. The routine at 5D19H loads Register A with 08H, the value-type code for double precision.
4E21
Slot 11, installed at 4173H. This is the CMD statement, the gateway to the DOS command shell and to the BASIC/OV1, BASIC/OV2 and BASIC/OV4 overlays.
4E24
Slot 12, installed at 4176H. This is the TIME$ function, which reads the resident clock workspace through the SYS0 vectors @TIME 446DH and @DATE 4470H.
4E27
Slot 13, installed at 4179H. This is the OPEN statement, which builds a file control block and issues @OPEN 4424H or @INIT 4420H.
4E2A
Slot 14, installed at 417CH. This is the FIELD statement, which maps string variables onto positions inside a random file buffer.
4E2D
Slot 15, installed at 417FH. This is the GET statement, which reads one record into a file buffer.
4E30
Slot 16, installed at 4182H. This is the PUT statement. Its entry at 6204H is one byte below the GET entry at 6205H, so PUT executes one extra opcode and then joins the same code.
4E33
Slot 17, installed at 4185H. This is the CLOSE statement, which flushes and closes one file or every open file.
4E36
Slot 18, installed at 4188H. This is the LOAD statement, which reads a BASIC program file from disk into the program text area.
4E39
Slot 19, installed at 418BH. This is the MERGE statement, which reads an ASCII program file and merges its lines into the program already in memory.
4E3C
Slot 20, installed at 418EH. This is the NAME statement slot, and LBASIC does not implement it: the slot is pointed straight at the ROM's ILLEGAL FUNCTION CALL error processor at 1E4AH, so a NAME statement raises ?FC. Renaming a file under LDOS is done with the RENAME library command through CMD"RENAME".
4E3F
Slot 21, installed at 4191H. This is the KILL statement, which deletes a disk file through @KILL 442CH.
4E42
Slot 22, installed at 4194H. This is the & prefix, which introduces a hexadecimal (&H) or octal (&O) constant.
4E45
Slot 23, installed at 4197H. This is the LSET statement, which stores a string left-justified into a fielded buffer position.
4E48
Slot 24, installed at 419AH. This is the RSET statement. Its entry at 60D8H is one byte below the LSET entry at 60D9H, which is how the two share a single body while remembering which justification was requested.
4E4B
Slot 25, installed at 419DH. This is the INSTR function, which searches one string for an occurrence of another.
4E4E
Slot 26, installed at 41A0H. This is the SAVE statement, which writes the program text area to a disk file.
4E51
Slot 27, installed at 41A3H. This is the LINE statement, which under Disk BASIC means LINE INPUT and LINE INPUT#.
4E54
Slot 28, installed at 41A6H. This is the first of the twenty-one hookable stubs the ROM leaves as bare RETs in a cassette-only machine. LBASIC uses it for the long error message handler, which expands a two-letter ROM error abbreviation into full text using BASIC/OV3.
4E57
Slot 29, installed at 41A9H. This is the USRn link, CALLed by the ROM's USR function processor at 27FEH so that Disk BASIC can support ten numbered USR entry points instead of the single ROM one.
4E5A
Slot 30, installed at 41ACH. This is the READY exit, CALLed by the ROM at 1A1CH each time BASIC returns to command level.
4E5D
Slot 31, installed at 41AFH. This is the keyboard line input exit, taken by the ROM at 0368H, which is what lets LBASIC intercept a whole typed line before the ROM tokenizes it.
4E60
Slot 32, installed at 41B2H. This is the exit the ROM takes at 1AA1H immediately after a BASIC line has been tokenized.
4E63
Slot 33, installed at 41B5H. This is the exit the ROM takes at 1AECH after the program line link table has been rebuilt.
4E66
Slot 34, installed at 41B8H. This is the exit the ROM takes at 1AF2H after rebuilding the line table and performing a CLEAR.
4E69
Slot 35, installed at 41BBH. This is the exit the ROM takes at 1B8CH (NEW) and 1DB0H (END), which is where Disk BASIC closes every open file.
4E6C
Slot 36, installed at 41BEH. This is the PRINT# hook, CALLed by the ROM at 2174H.
4E6F
Slot 37, installed at 41C1H. This is the byte output to any device hook, CALLed by the ROM's character despatcher at 032CH, so every character BASIC prints passes through LBASIC.
4E72
Slot 38, installed at 41C4H. This is the keyboard scan hook, CALLed by the ROM at 0358H, which serves both INKEY$ and the BREAK-key test.
4E75
Slot 39, installed at 41C7H. This is the exit the ROM takes at 1EA6H, which is how RUN"filename" and RUN line-number are told apart.
4E78
Slot 40, installed at 41CAH. This is the exit the ROM takes at 206FH while starting a PRINT statement, where the # that turns PRINT into PRINT# is detected.
4E7B
Slot 41, installed at 41CDH. This is the exit the ROM takes at 20C6H, after a number has been converted to ASCII but before it is printed.
4E7E
Slot 42, installed at 41D0H. This is the exit the ROM takes at 2103H after PRINT has sent a carriage return. Its entry at 5CCFH is one byte below slot 41's entry at 5CD0H, so the two share one body.
4E81
Slot 43, installed at 41D3H. This is the exit the ROM takes at 2108H and 2141H, which handles PRINT comma fields and the TAB function.
4E84
Slot 44, installed at 41D6H. This is the INPUT# hook, CALLed by the ROM at 219EH.
4E87
Slot 45, installed at 41D9H. This is the MID$ on the left of an assignment hook. It is the only one of the ROM's Disk BASIC exits reached by a JP rather than a CALL, from 2AECH, so the routine at 5B50H must not expect a return address for it.
4E8A
Slot 46, installed at 41DCH. This is the exit the ROM takes at 222DH while assigning a value to a variable during READ or INPUT.
4E8D
Slot 47, installed at 41DFH. This is the exit the ROM takes at 2278H, after an INPUT assignment, and at 2B44H, during LIST.
4E90
Slot 48, installed at 41E2H, the last slot in the table. This is the SYSTEM command hook, taken by the ROM at 02B2H before it prints its *? prompt. LBASIC points it back into the middle of the ROM's own SYSTEM routine at 02B8H, which skips the prompt and goes straight to the cassette load, so a SYSTEM command typed at the LBASIC prompt behaves exactly as it does in Level II.

4E93H - RST Hook Image (Data)

Four more 3-byte JP instructions, again never executed where they are loaded. The initialization routine at 5306H copies these 12 bytes to 4000H-400BH, the RAM hook block behind RST 08H, RST 10H, RST 18H and RST 20H. Three of the four are pointed back at the ROM routines they normally reach, which is what makes the copy safe to perform unconditionally; only the RST 10H hook is diverted into LBASIC.

4E93
Installed at 4000H, the RST 08H hook. 1C96H is the ROM's SYNCHR routine, which compares the character at (HL) against the literal byte that follows the RST 08H instruction and raises a syntax error if they differ. LBASIC leaves this one alone.
4E96
Installed at 4003H, the RST 10H hook. This is the one LBASIC takes over. The ROM would normally place its own CHRGTR routine at 1D78H here; instead every "fetch the next character of the BASIC text stream" reaches LBASIC's replacement at 6579H, which is how keywords the ROM's token table does not contain are recognised. The exit path at 57BAH puts 1D78H back into the operand at 4004H before returning to DOS.
4E99
Installed at 4006H, the RST 18H hook. 1C90H is the ROM's DCOMPR routine, an unsigned comparison of Register Pair DE against Register Pair HL. LBASIC leaves this one alone.
4E9C
Installed at 4009H, the RST 20H hook. 25D9H is the ROM's GETYPR routine, which reads VALTYP at 40AFH and returns the flags describing whether the value in the floating-point accumulator is an integer, a string, single precision or double precision. LBASIC leaves this one alone.

5200H - Sign-On Banner (Data)

The banner LBASIC displays once memory has been sized. It is sent by STROUT at 28A7H from 5421H, which prints characters until it meets the 00H at 5250H. The two carriage returns at the end leave a blank line before the READY prompt.

5200
DEFB 0DH 0D
Leading carriage return, so the banner starts on a fresh line whatever the cursor column was when LDOS handed over.
5201-5221
DEFM 'LBASIC - Version 5.3.1 - 08/01/91' 4C 42 41 53 49 43 20 2D 20 56 65 72 73 69 6F 6E 20 35 2E 33 2E 31 20 2D 20 30 38 2F 30 31 2F 39 31
Product name, version and build date. The date matches the LDOS 5.3.1 release the rest of the disk was built from.
5222
DEFB 0DH 0D
Carriage return, ending the version line.
5223-524D
DEFM '(C) 1991 MISOSYS, Inc., All rights reserved' 28 43 29 20 31 39 39 31 20 4D 49 53 4F 53 59 53 2C 20 49 6E 63 2E 2C 20 41 6C 6C 20 72 69 67 68 74 73 20 72 65 73 65 72 76 65 64
Copyright line. This is the on-screen copyright and is distinct from the type-1FH load-record copyright that precedes the image in the file.
524E-524F
DEFB 0DH, 0DH 0D 0D
Two carriage returns, leaving one blank line under the banner.
5250
DEFB 00H 00
String terminator for STROUT at 28A7H.

5251H - Message Block (Data)

Three short messages, each with its own terminator convention because each is displayed by a different routine. The refusal at 5254H goes through STROUT at 28A7H and needs a 00H; the two error messages go through @LOGOT at 447BH and need a carriage return.

5251-5253
DEFM 'V12' 56 31 32
A three-character build stamp sitting between the banner terminator at 5250H and the first message at 5254H. No instruction in the image reads it.
5254-5269
DEFM 'Use LDOS 5.3 or later!' 55 73 65 20 4C 44 4F 53 20 35 2E 33 20 6F 72 20 6C 61 74 65 72 21
The version refusal. Displayed from 534BH when the OSVER$ byte at 403EH is below 51H.
526A-526B
DEFB 0DH, 00H 0D 00
Carriage return and the 00H terminator STROUT at 28A7H stops on.
526C-527A
DEFM 'Parameter error' 50 61 72 61 6D 65 74 65 72 20 65 72 72 6F 72
Displayed from 5392H whenever @PARAM at 4476H rejects the command tail or a parameter value fails its range test.
527B
DEFB 0DH 0D
Carriage return terminator, the form @LOGOT at 447BH expects.
527C-5288
DEFM 'Out of memory' 4F 75 74 20 6F 66 20 6D 65 6D 6F 72 79
Displayed from 5388H when the requested memory ceiling leaves no room for the program text area.
5289
DEFB 0DH 0D
Carriage return terminator for @LOGOT at 447BH.
528A
DEFB 00H 00
A single zero byte immediately below the @PARAM table. 533FH writes 00H here through Register Pair HL loaded at 533CH.

528BH - @PARAM Keyword Table (Data)

The keyword table the resident @PARAM service at 4476H is pointed at from 53AEH. The format is the standard LDOS one used throughout SYS6 and SYS7: eight 8-byte entries, each a six-character blank-padded keyword followed by a two-byte little-endian address of the cell that is to receive the parsed value, ended by a single 00H byte. Every keyword is present twice, once spelled out and once abbreviated to its initial letter, and both spellings point at the same result cell.

528B-5292
DEFM 'MEM ' / DEFW 53FEH 4D 45 4D 20 20 20 FE 53
MEM, the highest address BASIC may use. The result cell 53FEH is the operand of the LD DE,0000H at 53FDH, which is pre-loaded at 52E0H with the entry value of HIGH$ 4049H so that an omitted MEM leaves the ceiling where LDOS put it.
5293-529A
DEFM 'M ' / DEFW 53FEH 4D 20 20 20 20 20 FE 53
The one-letter abbreviation of MEM, writing the same result cell 53FEH.
529B-52A2
DEFM 'FILES ' / DEFW 53C1H 46 49 4C 45 53 20 C1 53
FILES, the number of disk file buffers to build, documented as 1 to 15. The result cell 53C1H is the operand of the LD DE,0003H at 53C0H, so three buffers are built when the parameter is omitted. The value is range-checked at 53C3H-53CBH and must be below 10H.
52A3-52AA
DEFM 'F ' / DEFW 53C1H 46 20 20 20 20 20 C1 53
The one-letter abbreviation of FILES, writing the same result cell 53C1H.
52AB-52B2
DEFM 'BLK ' / DEFW 53BAH 42 4C 4B 20 20 20 BA 53
BLK, blocked file mode, which allows a random file to have a logical record length below 256 and lets DOS block and deblock records across sector boundaries. The result cell 53BAH is the operand of the LD DE,0FFFFH at 53B9H, so blocked mode is ON unless BLK=OFF is given; its high byte is copied to VARREC at 408FH by 53BDH, and a non-zero VARREC adds 0100H to the size of every file buffer built at 53E0H.
52B3-52BA
DEFM 'B ' / DEFW 53BAH 42 20 20 20 20 20 BA 53
The one-letter abbreviation of BLK, writing the same result cell 53BAH.
52BB-52C2
DEFM 'EXT ' / DEFW 6385H 45 58 54 20 20 20 85 63
EXT, the default-extension switch. With EXT on, which is the default, LOAD, RUN, MERGE and SAVE supply /BAS for any filespec that does not carry an extension. Unlike the other three parameters its result cell 6385H is out in the body of the interpreter rather than beside the initialization code, because that is where the filespec is built.
52C3-52CA
DEFM 'E ' / DEFW 6385H 45 20 20 20 20 20 85 63
The one-letter abbreviation of EXT, writing the same result cell 6385H.
52CB
DEFB 00H 00
End-of-table marker. @PARAM at 4476H stops scanning here.

52CCH - Initialization Entry

The load module's transfer address, entered by the LDOS command interpreter through @RUN. On entry Register Pair HL points at the first character of the command tail, the text LDOS left behind the program name, and the machine is still in the state SYS1 handed over: Level II BASIC's RAM workspace holds whatever the last BASIC session or the boot left there, and none of the disk keywords work yet. This routine saves the entry environment into self-modifying operand cells so a later re-entry can undo it, installs both RAM hook blocks, sizes memory and sets up the keyboard line buffer.

52CC
XOR A AF
Set Register A to ZERO and clear all flags. The zero is about to be written into two resident LDOS cells.
52CD
LD (4315H),A 32 15 43
Store zero (Register A) into 4315H, the resident DEBUG re-entry trap slot. A C3H there means a "JP 400FH" is armed and the RST 38H timer interrupt will drop into DEBUG on the next tick; writing 00H disarms it, so a breakpoint left over from a DEBUG session cannot fire inside BASIC.
52D0
LD (430EH),A 32 0E 43
Store zero (Register A) into OVRLY$ 430EH, the resident cell that records which SYS overlay is currently in memory at 4E00H. BASIC/CMD takes this cell over for its own use: from here on 00H means BASIC/OV3 is not loaded and 01H means it is, which is what the loader at 54B6H tests.
52D3
LD A,(HL) 7E
Fetch the first character of the LDOS command tail into Register A. Register Pair HL was set by the command interpreter to point at whatever followed the program name on the command line.
52D4
LD (5354H),A 32 54 53
Self-Modifying Code
Store the first command-tail character (Register A) into 5354H, which is the operand of the LD A,00H at 5353H. The listing shows the initial value 00H; at run time it holds the character just fetched, and 5355H compares it against 2AH to decide between a cold start and a re-entry.
52D7
LD (53ACH),HL 22 AC 53
Self-Modifying Code
Store the command-tail pointer (Register Pair HL) into 53ACH, the operand of the LD HL,0000H at 53ABH. That instruction later reloads the pointer so @PARAM can be pointed at the tail.
52DA
GOSUB to 5C7FH to establish LBASIC's device and interrupt state. The same routine is called again at 55D9H when a CMD shell-out returns, which is what makes it the single place that state is set up.
52DD
LD HL,(4049H) 2A 49 40
Fetch HIGH$ from 4049H into Register Pair HL. HIGH$ is the resident LDOS cell holding the highest address a program may use; anything above it is reserved for resident modules such as a double-density driver or the spooler.
52E0
LD (53FEH),HL 22 FE 53
Self-Modifying Code
Store the entry value of HIGH$ (Register Pair HL) into 53FEH, the operand of the LD DE,0000H at 53FDH. This is the ceiling the MEM parameter is measured against at 5400H, and it is also the value that stands when MEM is omitted.
52E3
LD HL,(40A4H) 2A A4 40
Fetch TXTTAB from 40A4H into Register Pair HL. TXTTAB is Level II BASIC's pointer to the byte before the first line of the program text area.
52E6
LD (5366H),HL 22 66 53
Self-Modifying Code
Store the entry value of TXTTAB (Register Pair HL) into 5366H, the operand of the LD HL,0000H at 5365H. The re-entry path at 5359H uses it to put the program text area back exactly where it was, which is how a BASIC program survives a CMD shell-out.
52E9
LD HL,(408EH) 2A 8E 40
Fetch the word at 408EH into Register Pair HL. Under LDOS the low byte 408EH is MAXFIL, the number of disk file buffers currently built, and 408FH is VARREC, the variable-length record flag; in cassette Level II the same word is the USR entry address.
52EC
LD (5301H),HL 22 01 53
Self-Modifying Code
Store the entry value of the MAXFIL and VARREC word (Register Pair HL) into 5301H, the operand of the LD HL,0000H at 5300H.
52EF
LD HL,(40A0H) 2A A0 40
Fetch STKTOP from 40A0H into Register Pair HL. STKTOP is Level II BASIC's top of string space, the highest address strings may occupy and the lower boundary of the BASIC return stack.
52F2
LD (5360H),HL 22 60 53
Self-Modifying Code
Store the entry value of STKTOP (Register Pair HL) into 5360H, the operand of the LD HL,0000H at 535FH, and it is also read back as a word by the LD DE,(5360H) at 5313H when the memory ceiling is resolved.
52F5
LD DE,4080H 11 80 40
Point Register Pair DE at 4080H, RAMLOW, the destination of the ROM's relocatable data block.
52F8
LD HL,18F7H 21 F7 18
Point Register Pair HL at 18F7H in the Level II ROM, the source of that block. These 39 bytes are not data in the ordinary sense: they are the parts of the floating-point divide routine that modify their own operands, which is why they cannot stay in ROM.
52FB
LD BC,0027H 01 27 00
Load Register Pair BC with 0027H, 39 decimal, the length of the relocatable block.
52FE
LDIR ED B0
Block move: source (Register Pair HL) 18F7H in ROM, destination (Register Pair DE) 4080H in RAM, count (Register Pair BC) 39 bytes, incrementing. This restores the self-modifying divide operands FDIVC+1 at 4081H, FDIVB+1 at 4085H, FDIVA+1 at 4089H and FDIVG+1 at 408CH to their power-up values. On completion Register Pair BC is 0000H and Register Pair DE is 40A7H, which matters two instructions later.
5300
LD HL,0000H 21 00 00
Self-Modifying Code
Load Register Pair HL with the operand held at 5301H, which 52ECH set to the entry value of the MAXFIL and VARREC word. The listing shows the assembled 0000H.
5303
LD (408EH),HL 22 8E 40
Store that word back into 408EH, restoring MAXFIL and VARREC to the values they held on entry. Nothing has changed them yet, so the effect at a cold start is simply to leave them alone; the point of the round trip is that the same cell can be restored on re-entry.
5306
LD HL,4E93H 21 93 4E
Point Register Pair HL at 4E93H, the start of the 12-byte RST hook image. These three bytes are overwritten at 5323H once the instruction has executed, and the area they occupy becomes the guard bytes below the keyboard line buffer.
5309
LD DE,4000H 11 00 40
Point Register Pair DE at 4000H, the RAM hook block behind RST 08H, RST 10H, RST 18H and RST 20H. 5309H is also the address that becomes the keyboard line buffer once 532EH sets BUFPNT to it.
530C
LD C,0CH 0E 0C
Load Register C with 0CH, 12 decimal, the length of the hook image. Only Register C is loaded because the LDIR at 52FEH already left Register B at 00H, so Register Pair BC becomes 000CH.
530E
LDIR ED B0
Block move: source (Register Pair HL) 4E93H, destination (Register Pair DE) 4000H, count (Register Pair BC) 12 bytes, incrementing. This installs the four RST vectors. Three point back into the ROM at 1C96H, 1C90H and 25D9H; the RST 10H vector at 4003H now points at 6579H, so from this instruction onward every character the interpreter fetches passes through LBASIC.
5310
LD HL,(4049H) 2A 49 40
Fetch HIGH$ from 4049H into Register Pair HL again, as the candidate memory ceiling.
5313
LD DE,(5360H) ED 5B 60 53
Fetch the entry value of STKTOP into Register Pair DE from the cell 52F2H filled.
5317
CP 2AH FE 2A
Compare Register A against 2AH (ASCII: *). Register A still holds the first character of the command tail, fetched at 52D3H. If Register A equals 2AH the Z FLAG is set, meaning this is a re-entry into a BASIC session that is still in memory; otherwise the NZ FLAG is set.
5319
If the NZ FLAG has been set, meaning this is a cold start rather than a re-entry, JUMP to 531FH to adopt HIGH$ as the ceiling without further comparison.
531B
GOSUB through the RAM hook at 4006H to the ROM's DCOMPR routine at 1C90H, an unsigned comparison of Register Pair DE (the saved STKTOP) against Register Pair HL (HIGH$). The CARRY FLAG is set when Register Pair HL is the smaller of the two.
531C
If the CARRY FLAG has been set, meaning HIGH$ is below the saved STKTOP, JUMP to 531FH and keep HIGH$ in Register Pair HL as the ceiling.
531E
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL now holds the saved STKTOP. On a re-entry where the previous session's string space reached higher than HIGH$, the previous ceiling is the one that is kept.
531F
LD (40A0H),HL 22 A0 40
Store the resolved ceiling (Register Pair HL) into STKTOP 40A0H, fixing the top of string space.
5322
LD SP,HL F9
Load the Stack Pointer with the resolved ceiling (Register Pair HL). BASIC's return stack now grows downward from the top of string space, which is the standard Level II arrangement.
5323
LD HL,5306H 21 06 53
Point Register Pair HL at 5306H, the LD HL,4E93H instruction that ran at 5306H a moment ago. Those three bytes are about to be replaced with constants; the instruction is never reached again, so the space is free.
5326
LD (HL),3AH 36 3A
Store 3AH (ASCII: :) at 5306H. The colon is BASIC's statement separator, and it sits two bytes below the keyboard line buffer that is about to be established.
5328
INC HL 23
INCrement Register Pair HL by 1, advancing the write pointer to 5307H.
5329
LD (HL),B 70
Store Register B at 5307H. Register B holds 00H: it is the high half of Register Pair BC, which the LDIR at 52FEH counted down to 0000H and which the LD C,0CH at 530CH left untouched. So a zero byte separates the colon from the comma.
532A
INC HL 23
INCrement Register Pair HL by 1, advancing the write pointer to 5308H.
532B
LD (HL),2CH 36 2C
Store 2CH (ASCII: ,) at 5308H. This comma is the byte immediately below the keyboard line buffer, and the routines at 5892H, 5BFBH and 5D94H reach it by loading BUFPNT and DECrementing, so that a ROM routine scanning forward meets a separator before it meets the buffer text.
532D
INC HL 23
INCrement Register Pair HL by 1, leaving 5309H in Register Pair HL.
532E
LD (40A7H),HL 22 A7 40
Store 5309H (Register Pair HL) into BUFPNT 40A7H, Level II BASIC's keyboard line buffer pointer. From here on, every line typed at the BASIC prompt is stored at 5309H onward. 5BE0H accepts up to 0F0H characters there and 6499H hands the same pointer with a 240-byte capacity to the ROM's line-collection loop at 05E3H, so the buffer occupies 5309H-53F8H. That range is BASIC/CMD's own initialization code, which is deliberate: none of 52CCH-5453H is entered again once BASIC is running, so the space is reclaimed rather than wasted.
5331
LD DE,4152H 11 52 41
Point Register Pair DE at 4152H, the first slot of the Level II ROM's Disk BASIC entry-point table.
5334
LD HL,4E00H 21 00 4E
Point Register Pair HL at 4E00H, the start of the 49-entry vector table image loaded with the program.
5337
LD BC,0093H 01 93 00
Load Register Pair BC with 0093H, 147 decimal, which is forty-nine 3-byte JP instructions. 4152H plus 147 is 41E5H, exactly where the ROM's device control block area begins, so the table is copied edge to edge.
533A
LDIR ED B0
Block move: source (Register Pair HL) 4E00H, destination (Register Pair DE) 4152H, count (Register Pair BC) 147 bytes, incrementing. This is the instruction that turns Level II BASIC into Disk BASIC. Every disk keyword and every documented ROM exit point now reaches code inside LBASIC. 4E00H-4E92H is free from this moment and becomes the load address for BASIC/OV3.
533C
LD HL,528AH 21 8A 52
Point Register Pair HL at 528AH, the single byte between the "Out of memory" message and the first entry of the @PARAM keyword table at 528BH.
533F
LD (HL),00H 36 00
Store 00H at 528AH, so the byte below the @PARAM table is known to be zero whatever a previous session left there.
5341
GOSUB to the ROM routine STKINI at 1B8FH, which initializes the BASIC stack, clears the string descriptor workspace and resets the interpreter's pointers so the newly installed vector table is used from a clean state.

5344H - Version Gate and Re-Entry Test

LBASIC 5.3.1 depends on services and on a directory date format that earlier LDOS releases do not have, so it refuses to run under them. OSVER$ at 403EH is the resident byte every LDOS component uses to identify the release. Once the gate is passed, the saved first character of the command tail decides whether this is a fresh start or a re-entry into a session that is still resident.

5344
LD A,(403EH) 3A 3E 40
Fetch OSVER$ from 403EH into Register A. This is the resident LDOS version byte, set by SYS0 during boot initialization.
5347
CP 51H FE 51
Compare Register A, the LDOS version byte, against 51H. If Register A is below 51H the CARRY FLAG is set, meaning the running system is older than 5.3; if Register A is 51H or above the NO CARRY FLAG is set.
5349
If the NO CARRY FLAG has been set, meaning the system is 5.3 or later, JUMP to 5353H to carry on with initialization.
534B
LD HL,5254H 21 54 52
Error Path
Point Register Pair HL at the message "Use LDOS 5.3 or later!" at 5254H, which is carriage-return then 00H terminated.
534E
GOSUB to the ROM routine STROUT at 28A7H, which sends the string at Register Pair HL to the current output device and stops on the 00H at 526BH.
5351
JUMP to 538EH, the common exit that restores the ROM's RST 10H vector and returns to DOS.
5353
LD A,00H 3E 00
Self-Modifying Code
Load Register A with the operand held at 5354H, which 52D4H set to the first character of the LDOS command tail. The listing shows the assembled 00H.
5355
CP 2AH FE 2A
Compare Register A, the first command-tail character, against 2AH (ASCII: *). If Register A equals 2AH the Z FLAG is set, meaning this is the documented BASIC * form, which re-enters BASIC with the program and the variables intact after an exit to DOS Ready; otherwise the NZ FLAG is set and this is a cold start.
5357
If the NZ FLAG has been set, meaning a cold start, JUMP to 5397H to initialize the file vector table and parse the command line parameters.
5359
LD A,(408EH) 3A 8E 40
Re-Entry Path
Fetch MAXFIL from 408EH into Register A, the number of file buffers the previous session built. The same number is rebuilt so the file control blocks land in the same places.
535C
GOSUB to 53CFH to rebuild the chain of file buffers and their pointer table from the count in Register A.
535F
LD HL,0000H 21 00 00
Self-Modifying Code
Load Register Pair HL with the operand held at 5360H, which 52F2H set to the entry value of STKTOP. The listing shows the assembled 0000H.
5362
LD (40A0H),HL 22 A0 40
Store the saved STKTOP (Register Pair HL) into 40A0H, putting the top of string space back where the previous session had it.
5365
LD HL,0000H 21 00 00
Self-Modifying Code
Load Register Pair HL with the operand held at 5366H, which 52E6H set to the entry value of TXTTAB. The listing shows the assembled 0000H.
5368
LD (40A4H),HL 22 A4 40
Store the saved TXTTAB (Register Pair HL) into 40A4H, so the program text area is once more where the previous session left it and the BASIC program is intact.
536B
LD DE,(40B1H) ED 5B B1 40
Fetch MEMSIZ from 40B1H into Register Pair DE, the top of usable RAM as the previous session understood it.
536F
LD HL,(4049H) 2A 49 40
Fetch the current HIGH$ from 4049H into Register Pair HL. A resident module installed while BASIC was away, or a CMD shell-out that has not fully unwound, can have moved it.
5372
GOSUB through the RAM hook at 4006H to the ROM's DCOMPR routine at 1C90H, comparing Register Pair DE (the old MEMSIZ) against Register Pair HL (the current HIGH$). The CARRY FLAG is set when Register Pair HL is the smaller.
5373
If the NO CARRY FLAG has been set, meaning HIGH$ is at or above the old MEMSIZ so nothing has been taken away, JUMP to 5382H and rejoin BASIC without disturbing the program.
5375
LD (40B1H),HL 22 B1 40
Memory has been taken away above BASIC. Store the lower HIGH$ (Register Pair HL) into MEMSIZ 40B1H so BASIC does not use the space that has been claimed.
5378
LD DE,0FFCEH 11 CE FF
Load Register Pair DE with 0FFCEH, which is minus 50 decimal, the 50-byte gap Level II BASIC leaves between the top of string space and the top of memory for its own return stack.
537B
ADD HL,DE 19
ADD minus 50 (Register Pair DE) to the new memory top (Register Pair HL), giving the new top of string space.
537C
LD (40A0H),HL 22 A0 40
Store the recomputed top of string space (Register Pair HL) into STKTOP 40A0H.
537F
GOSUB to the ROM routine RUNC at 1B5DH, which performs a CLEAR: it discards all variables and re-lays the variable and string areas within the reduced memory. The program text itself survives.
5382
LD BC,1A18H 01 18 1A
Load Register Pair BC with 1A18H, the address of the ROM's READY entry. It is passed as a return address rather than jumped to, which is the convention the ROM routine at 19AEH expects.
5385
JUMP to the ROM's command-mode error handling entry at 19AEH with Register Pair BC pointing at READY. This is the standard way back into BASIC's command loop and does not return.
5388
LD HL,527CH 21 7C 52
Error Path
Point Register Pair HL at the message "Out of memory" at 527CH, which is carriage-return terminated. Reached from 5404H and 5411H when the MEM parameter leaves no room for a program.
538B
GOSUB to the resident @LOGOT vector at 447BH, which sends the carriage-return-terminated string at Register Pair HL to the video display and to the system log device.
538E
SCF 37
Set the Carry flag. The exit routine at 57BAH tests it: CARRY means leave through @ABORT 4030H rather than @EXIT 402DH, so LDOS treats the failure as an aborted program and a JCL job running BASIC stops.
538F
JUMP to 57BAH, the single exit to DOS. It restores the ROM's CHRGTR address into the RST 10H hook operand at 4004H before leaving, which every path out of LBASIC must do.
5392
LD HL,526CH 21 6C 52
Error Path
Point Register Pair HL at the message "Parameter error" at 526CH. Reached from 53B4H when @PARAM rejects the command tail, and from 53C5H, 53CAH when a parameter value is out of range.
5395
JUMP to 538BH to display the message through @LOGOT and abort.

5397H - USR Vector Table Initialization and Parameter Parsing

The cold-start path. The ten USR vectors above the loaded image are seeded with the address of the error-code table so that calling a USR number that was never defined raises an error rather than jumping into whatever happens to be there, and then the command tail is handed to the resident @PARAM service. The three parameters that have local result cells are validated here; MEM is validated later at 5400H because it depends on where the file buffers ended up.

5397
LD DE,642FH 11 2F 64
Load Register Pair DE with 642FH, the first entry of the error-code table at the top of the file, which loads Register E with 2EH and raises the corresponding BASIC error. It is the safe default for every slot in the USR vector table about to be filled.
539A
LD HL,661CH 21 1C 66
Point Register Pair HL at 661CH, the base of the USR0 to USR9 vector table. This is run-time workspace above the loaded image, which ends at 65FBH.
539D
LD B,0AH 06 0A
Load Register B with 0AH, 10 decimal, the number of words to fill.
539F
LD (HL),E 73
Loop Start
Store the low byte of 642FH (Register E) into the current USR vector slot at the address in Register Pair HL.
53A0
INC HL 23
INCrement Register Pair HL by 1, advancing to the high byte of the current table entry.
53A1
LD (HL),D 72
Store the high byte of 642FH (Register D) at the address in Register Pair HL.
53A2
INC HL 23
INCrement Register Pair HL by 1, advancing to the next table entry.
53A3
DECrement Register B and LOOP BACK to 539FH if not zero, to fill the next of the ten words. Loop End Register Pair HL is 6630H on exit.
53A5
LD HL,0000H 21 00 00
Load Register Pair HL with 0000H, the value to be planted immediately above the table.
53A8
LD (6630H),HL 22 30 66
Store 0000H (Register Pair HL) at 6630H, the word immediately above the USR vector table, clearing it.
53AB
LD HL,0000H 21 00 00
Self-Modifying Code
Load Register Pair HL with the operand held at 53ACH, which 52D7H set to the pointer to the LDOS command tail. The listing shows the assembled 0000H.
53AE
LD DE,528BH 11 8B 52
Point Register Pair DE at 528BH, the @PARAM keyword table: eight 8-byte entries of a six-character blank-padded keyword plus a two-byte result cell address, ended by 00H.
53B1
GOSUB to the resident @PARAM vector at 4476H. It scans the command tail at Register Pair HL against the keyword table at Register Pair DE and writes each supplied value into the result cell named by that keyword's entry. It returns with the Z FLAG set when every keyword on the line was recognised, and with Register Pair HL pointing past the parameters.
53B4
If the NZ FLAG has been set, meaning @PARAM met a keyword that is not in the table, JUMP to 5392H to report "Parameter error" and abort.
53B6
LD (53ACH),HL 22 AC 53
Self-Modifying Code
Store the pointer @PARAM returned (Register Pair HL) back into 53ACH, the operand of the LD HL,0000H at 53ABH. 542BH reloads it so that anything left on the command line after the parameters can be executed as a BASIC command.
53B9
LD DE,0FFFFH 11 FF FF
Self-Modifying Code
Load Register Pair DE with the operand held at 53BAH, the result cell of the BLK and B keywords. The listing shows the assembled 0FFFFH, which is what stands when BLK is omitted, so blocked file mode is on by default and BLK=OFF is needed to turn it off.
53BC
LD A,D 7A
Copy the high byte of the BLK value (Register D) into Register A.
53BD
LD (408FH),A 32 8F 40
Store it into VARREC at 408FH, the blocked file mode flag. Blocked mode lets a random file carry a logical record length below 256, with DOS blocking and deblocking records across sector boundaries. A non-zero VARREC makes 53E0H add 0100H to the size of every file buffer, giving 0222H bytes instead of 0122H, because deblocking needs a second 256-byte area.
53C0
LD DE,0003H 11 03 00
Self-Modifying Code
Load Register Pair DE with the operand held at 53C1H, the result cell of the FILES and F keywords. The listing shows the assembled 0003H, so three file buffers are built when FILES is omitted.
53C3
LD A,D 7A
Copy the high byte of the FILES value (Register D) into Register A, to check that the number given was small enough to fit in one byte.
53C4
AND A A7
Test Register A, the high byte of the FILES value, against itself. If it is zero the Z FLAG is set; any non-zero high byte means a value of 256 or more was supplied.
53C5
If the NZ FLAG has been set, meaning the FILES value will not fit in a byte, JUMP to 5392H to report "Parameter error" and abort.
53C7
LD A,E 7B
Copy the low byte of the FILES value (Register E) into Register A, the file count itself.
53C8
CP 10H FE 10
Compare Register A, the requested file count, against 10H, 16 decimal. If Register A is below 16 the CARRY FLAG is set; if it is 16 or more the NO CARRY FLAG is set. LBASIC allows 0 to 15 files.
53CA
If the NO CARRY FLAG has been set, meaning 16 or more files were asked for, JUMP to 5392H to report "Parameter error" and abort.
53CC
LD (408EH),A 32 8E 40
Store the validated file count (Register A) into MAXFIL at 408EH, the cell every later file operation consults to check that a buffer number is in range.

53CFH - File Buffer Chain Builder

Entered with Register A holding the number of files, either freshly validated at 53CCH or recovered from MAXFIL on a re-entry at 5359H. It lays out one buffer per file above the loaded image and records the address of each in a pointer table, then falls through into the program text area setup. The buffers are 0122H bytes each, or 0222H when the BLK parameter has set VARREC.

53CF
INC A 3C
INCrement Register A by 1. One more buffer than the file count is built, so that buffer number zero exists as well as buffers 1 to MAXFIL.
53D0
LD HL,6632H 21 32 66
Point Register Pair HL at 6632H, the address of the first file buffer. It sits immediately above the ten-word vector table that ends at 6631H.
53D3
LD DE,65FAH 11 FA 65
Point Register Pair DE at 65FAH, the start of the buffer pointer table. 65FAH and 65FBH are the last two bytes of the loaded image and ship as 0000H, so the table starts inside the file and continues into memory beyond it.
53D6
LD BC,0122H 01 22 01
Load Register Pair BC with 0122H, 290 decimal, the size of one file buffer: a 256-byte sector buffer plus 34 bytes of control information.
53D9
PUSH AF F5
Save Register A, the buffer count, onto the stack so that Register A can be used to test the VARREC flag.
53DA
LD A,(408FH) 3A 8F 40
Fetch VARREC from 408FH into Register A, the blocked file mode flag that 53BDH set from the BLK parameter.
53DD
AND A A7
Test Register A, the VARREC flag, against itself. Zero sets the Z FLAG and means fixed-length records; non-zero sets the NZ FLAG and means variable-length records.
53DE
If the Z FLAG has been set, meaning blocked file mode is off and every record is a full 256-byte sector, JUMP to 53E1H leaving the buffer size at 0122H.
53E0
INC B 04
INCrement Register B by 1, which adds 0100H to Register Pair BC and makes the buffer size 0222H. Blocked file mode needs a second 256-byte area in each buffer so DOS can deblock records that span a sector boundary.
53E1
POP AF F1
Restore Register A, the buffer count, from the stack.
53E2
LD (661AH),HL 22 1A 66
Store the address of the first buffer, 6632H (Register Pair HL), at 661AH. This is the word immediately below the ten-entry vector table and gives later code a direct handle on the start of the buffer chain.
53E5
LD (HL),00H 36 00
Loop Start
Store 00H into the first byte of the buffer at the address in Register Pair HL. That byte is the buffer's "in use" flag, and clearing it marks the file as closed.
53E7
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL now addresses the next free slot of the pointer table and Register Pair DE holds the buffer address that is to be stored there.
53E8
LD (HL),E 73
Store the low byte of the buffer address (Register E) into the pointer table slot addressed by Register Pair HL.
53E9
INC HL 23
INCrement Register Pair HL by 1, advancing to the high half of the pointer table slot.
53EA
LD (HL),D 72
Store the high byte of the buffer address (Register D) into the pointer table.
53EB
INC HL 23
INCrement Register Pair HL by 1, leaving it on the next free pointer table slot.
53EC
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL back again, so Register Pair HL holds the current buffer address and Register Pair DE the next pointer table slot.
53ED
ADD HL,BC 09
ADD the buffer size (Register Pair BC, 0122H or 0222H) to the current buffer address (Register Pair HL), giving the address of the next buffer.
53EE
DEC A 3D
DECrement Register A by 1, counting off one buffer.
53EF
If the NZ FLAG has been set, meaning buffers remain to be built, LOOP BACK to 53E5H. Loop End On exit Register Pair HL points just above the last buffer, which is the first byte available to the BASIC program text.
53F1
LD A,(5354H) 3A 54 53
Fetch the saved first character of the LDOS command tail from 5354H into Register A, the cell 52D4H filled.
53F4
CP 2AH FE 2A
Compare Register A against 2AH (ASCII: *). If Register A equals 2AH the Z FLAG is set, meaning this is a re-entry and the caller at 535CH must get control back so it can restore the saved pointers.
53F6
RET Z C8
If the Z FLAG has been set, RETurn to the re-entry path at 535FH. The program text area is not touched, which is exactly what preserves the BASIC program across a CMD shell-out.
53F7
LD (HL),00H 36 00
Cold Start Path
Store 00H at the address in Register Pair HL, the byte just above the last file buffer. Level II BASIC requires a zero byte immediately below the program text as the terminator the line-link walker stops on.
53F9
INC HL 23
INCrement Register Pair HL by 1, leaving it on the first byte of the program text area proper.
53FA
LD (40A4H),HL 22 A4 40
Store that address (Register Pair HL) into TXTTAB 40A4H, fixing the start of the BASIC program text immediately above the file buffers. The more files were requested, the higher the program starts.

53FDH - MEM Parameter Range Check

The requested memory ceiling is checked twice: it must not be above the HIGH$ that LDOS handed over, and it must leave at least 012CH bytes above the start of the program text. This code runs exactly once, which is what makes it safe for 540AH-5413H to be reused as the DOS vector save area by the CMD shell at 5504H, and for 5309H-53F8H to be reused as the keyboard line buffer.

53FD
LD DE,0000H 11 00 00
Self-Modifying Code
Load Register Pair DE with the operand held at 53FEH. 52E0H pre-loaded it with the entry value of HIGH$, and the MEM or M keyword overwrites it with the user's request through the @PARAM table entry at 528BH. The listing shows the assembled 0000H.
5400
LD HL,(4049H) 2A 49 40
Fetch the current HIGH$ from 4049H into Register Pair HL, the highest address LDOS permits.
5403
GOSUB through the RAM hook at 4006H to the ROM's DCOMPR routine at 1C90H, comparing Register Pair DE (the requested ceiling) against Register Pair HL (HIGH$). The CARRY FLAG is set when Register Pair HL is below Register Pair DE, that is when the request is above what LDOS allows.
5404
If the CARRY FLAG has been set, meaning the requested ceiling is above HIGH$, JUMP to 5388H to report "Out of memory" and abort.
5407
DEC DE 1B
DECrement Register Pair DE by 1, so the requested ceiling becomes the last address BASIC may actually touch.
5408
LD HL,(40A4H) 2A A4 40
Fetch TXTTAB from 40A4H into Register Pair HL, the start of the program text area that 53FAH just fixed above the file buffers. The three bytes of this instruction, and the ten that follow it, are overwritten at 5504H when a CMD statement saves the DOS vectors, and 5428H writes 00H over the byte at 5409H.
540B
LD BC,012CH 01 2C 01
Load Register Pair BC with 012CH, 300 decimal, the minimum workspace BASIC needs above the program text for variables, strings and the stack.
540E
ADD HL,BC 09
ADD the 300-byte minimum (Register Pair BC) to the start of the program text (Register Pair HL), giving the lowest ceiling that could possibly work.
540F
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL holds the requested ceiling and Register Pair DE the minimum acceptable one, which is the order the comparison needs.
5410
GOSUB through the RAM hook at 4006H to the ROM's DCOMPR routine at 1C90H, comparing Register Pair DE (the minimum) against Register Pair HL (the request). The CARRY FLAG is set when the request is below the minimum.
5411
If the CARRY FLAG has been set, meaning the ceiling leaves fewer than 300 bytes above the program text, JUMP to 5388H to report "Out of memory" and abort.
5414
LD DE,0FFCEH 11 CE FF
Load Register Pair DE with 0FFCEH, minus 50 decimal, the gap Level II BASIC reserves above the top of string space for its return stack.
5417
LD (40B1H),HL 22 B1 40
Store the accepted ceiling (Register Pair HL) into MEMSIZ 40B1H, the top of usable RAM as BASIC understands it.
541A
ADD HL,DE 19
ADD minus 50 (Register Pair DE) to the ceiling (Register Pair HL), giving the top of string space.
541B
LD (40A0H),HL 22 A0 40
Store the top of string space (Register Pair HL) into STKTOP 40A0H.
541E
GOSUB to the ROM routine SCRATH at 1B4DH, the body of the NEW command. It empties the program text area, clears all variables and lays out the variable, array and string regions between TXTTAB and STKTOP.

5421H - Sign-On and Entry to BASIC

Everything is in place, so the banner is displayed and control passes to Level II BASIC. If anything followed the parameters on the LDOS command line it is treated as a BASIC command and executed immediately, which is how a JCL job can start BASIC and run a program in one line; if nothing followed, BASIC simply comes up at the READY prompt.

5421
LD HL,5200H 21 00 52
Point Register Pair HL at the sign-on banner at 5200H, which runs to the 00H terminator at 5250H.
5424
GOSUB to the ROM routine STROUT at 28A7H to display the banner at Register Pair HL.
5427
XOR A AF
Set Register A to ZERO and clear all flags.
5428
LD (5409H),A 32 09 54
Self-Modifying Code
Store zero (Register A) into 5409H, the middle byte of the LD HL,(40A4H) at 5408H. That instruction belongs to the MEM range check, which has already run and is not reached again, and 540AH onward is about to be claimed as the DOS vector save area. No instruction anywhere in the image reads 5409H.
542B
LD HL,(53ACH) 2A AC 53
Self-Modifying Code
Fetch the command-line pointer from 53ACH into Register Pair HL. 53B6H stored the position @PARAM had reached, so Register Pair HL now points at whatever followed the parameters.
542E
DEC HL 2B
DECrement Register Pair HL by 1. RST 10H increments before it fetches, so backing up one byte makes the next fetch return the first character of the remaining text.
542F
GOSUB through the RAM hook at 4003H to LBASIC's own character fetch at 6579H, which INCrements Register Pair HL, skips blanks and returns the next significant character in Register A with the CARRY FLAG clear if it is not a digit.
5430
CP 0DH FE 0D
Compare Register A, the first character after the parameters, against 0DH, a carriage return. If Register A equals 0DH the Z FLAG is set, meaning the command line ended with the parameters and there is nothing to execute.
5432
If the Z FLAG has been set, meaning nothing follows the parameters, JUMP to the ROM's READY entry at 1A19H. BASIC prints READY and waits for the operator; this is the normal end of initialization and does not return.
5435
PUSH HL E5
Save the pointer to the leftover command text (Register Pair HL) onto the stack, because the scan that follows advances it.
5436
LD A,(HL) 7E
Loop Start
Fetch the character at the address in Register Pair HL into Register A, scanning forward for the end of the text.
5437
CP 20H FE 20
Compare Register A against 20H, the ASCII space. If Register A is below 20H the CARRY FLAG is set, meaning a control character such as the terminating carriage return has been reached; a printable character sets the NO CARRY FLAG.
5439
If the CARRY FLAG has been set, meaning the end of the text has been found, JUMP to 543EH to terminate the string there.
543B
INC HL 23
INCrement Register Pair HL by 1, advancing to the next character of the leftover command text.
543C
LOOP BACK to 5436H to test the next character. Loop End
543E
LD (HL),00H 36 00
Store 00H over the terminating control character, giving the leftover text the zero terminator BASIC's tokenizer expects.
5440
GOSUB to the ROM printer routine FINLPT at 038BH, which flushes any partly built printer line so that the command about to be executed starts from a known printer state.
5443
CALL 41ACH CD AC 41
GOSUB to the READY slot of the vector table at 41ACH, which the LDIR at 533AH pointed at LBASIC's routine at 5FC4H. This performs the housekeeping BASIC normally does on returning to command level, without printing the READY message.
5446
GOSUB to the ROM routine ISTTY at 20F9H, which selects the video display as the current output device and clears the print device flag PRTFLG at 409CH.
5449
LD HL,0FFFFH 21 FF FF
Load Register Pair HL with 0FFFFH, the value that marks immediate mode.
544C
LD (40A2H),HL 22 A2 40
Store 0FFFFH (Register Pair HL) into CURLIN 40A2H, the currently executing line number. 0FFFFH tells the interpreter and the error handler that what follows was typed rather than run from a program line.
544F
POP HL E1
Restore the pointer to the start of the leftover command text (Register Pair HL) from the stack.
5450
DEC HL 2B
DECrement Register Pair HL by 1, so that the first RST 10H performed by the interpreter returns the first character of the text.
5451
JUMP to the ROM routine NTAUTO at 1A81H, which tokenizes and executes the text at Register Pair HL as a directly entered BASIC statement. A command line such as BASIC RUN"PROG" therefore starts the program without the operator typing anything. This does not return.

5454H - CMD String Dispatcher

Reached from the CMD statement at 57CBH when the quoted string is two characters or longer, with Register A holding the string length and Register Pair DE pointing at the string itself. There is a second entry at 5468H, taken from 5803H-5808H when CMD was given a single letter that the statement's own dispatcher did not recognise; that path arrives with Register Pair HL already loaded with 1E4AH so that an unrecognised letter raises ?FC. The test that separates an overlay call from a DOS command is not the letter but the character after it: only a space makes 5465H look at the letter at all, which is exactly the published syntax CMD"N !line,newline,inc,last", CMD"X devspec..." and CMD"V ...".

5454
LD C,A 4F
Copy the length of the quoted string (Register A) into Register C, where it becomes the byte count for the copy into INBUF$ at 54E1H.
5455
CP 40H FE 40
Compare Register A, the string length, against 40H, 64 decimal. If Register A is below 64 the CARRY FLAG is set; a string of 64 characters or more sets the NO CARRY FLAG. INBUF$ cannot usefully hold a longer command line.
5457
If the NO CARRY FLAG has been set, meaning the string is 64 characters or longer, JUMP to the ROM's ILLEGAL FUNCTION CALL error processor at 1E4AH to raise ?FC.
545A
PUSH HL E5
Save Register Pair HL, the interpreter's BASIC text pointer, onto the stack. It is recovered by the CMD shell as part of the saved stack, or discarded when an overlay replaces LBASIC entirely.
545B
INC DE 13
INCrement Register Pair DE by 1, so it addresses the second character of the quoted string.
545C
LD A,(DE) 1A
Fetch the second character of the string into Register A.
545D
DEC DE 1B
DECrement Register Pair DE by 1, putting it back on the first character of the string.
545E
CP 20H FE 20
Compare the second character (Register A) against 20H, the ASCII space. If it is a space the Z FLAG is set, meaning this may be an overlay call of the form CMD"N ..."; anything else sets the NZ FLAG and the whole string is a DOS command.
5460
LD HL,54ECH 21 EC 54
Point Register Pair HL at 54ECH, the DOS command shell, as the fall-through destination of the JP (HL) at 5479H. The second entry at 5468H arrives with 1E4AH here instead, so the same JP (HL) raises ?FC on that path.
5463
If the NZ FLAG has been set, meaning no space follows the first character, JUMP to 5479H to hand the whole string to the DOS command shell.
5465
LD A,(DE) 1A
Fetch the first character of the quoted string into Register A, the letter that names the overlay.
5466
RES 5,A CB AF
Reset bit 5 of Register A. In ASCII bit 5 is the case bit, so this folds a lower-case letter to upper case and lets CMD"n ..." work as well as CMD"N ...".
5468
LD B,31H 06 31
Load Register B with 31H (ASCII: 1), the overlay digit for BASIC/OV1. This is also the second entry point, reached by the JP 5468H at 5808H with Register A already folded to upper case and Register Pair HL already holding 1E4AH.
546A
CP 4EH FE 4E
Compare the letter (Register A) against 4EH (ASCII: N). If it matches the Z FLAG is set, meaning the renumber overlay BASIC/OV1 is wanted.
546C
If the Z FLAG has been set, JUMP to 547AH to load BASIC/OV1 with the digit already in Register B.
546E
INC B 04
INCrement Register B by 1, making it 32H (ASCII: 2), the overlay digit for BASIC/OV2.
546F
CP 58H FE 58
Compare the letter (Register A) against 58H (ASCII: X). If it matches the Z FLAG is set, meaning the cross-reference overlay BASIC/OV2 is wanted.
5471
If the Z FLAG has been set, JUMP to 547AH to load BASIC/OV2.
5473
LD B,34H 06 34
Load Register B with 34H (ASCII: 4), the overlay digit for BASIC/OV4. Register B is loaded outright rather than INCremented because digit 3 is skipped: BASIC/OV3 is a subroutine overlay and is loaded by the different route at 54B6H.
5475
CP 56H FE 56
Compare the letter (Register A) against 56H (ASCII: V). If it matches the Z FLAG is set, meaning the active-variable dump overlay BASIC/OV4 is wanted.
5477
If the Z FLAG has been set, JUMP to 547AH to load BASIC/OV4.
5479
JP (HL) E9
JUMP to the address in Register Pair HL. From the main entry that is 54ECH, the DOS command shell; from the 5468H entry it is 1E4AH, the ROM's ILLEGAL FUNCTION CALL processor.
547A
PUSH DE D5
Save the string address (Register Pair DE) onto the stack. It is recovered into Register Pair HL two instructions after the CALL, which is why Register Pair BC is pushed on top of it.
547B
PUSH BC C5
Save Register Pair BC, whose Register C holds the string length, onto the stack across the filespec build.
547C
GOSUB to 54A5H to build the filespec "BASIC/OVn" into CFCB$ at 4480H, taking the digit n from Register B.
547F
POP BC C1
Restore Register Pair BC from the stack, putting the string length back into Register C.
5480
POP HL E1
Restore the string address from the stack into Register Pair HL. The value was pushed from Register Pair DE at 547AH, so this is a deliberate swap of register pairs through the stack.
5481
GOSUB to 54E1H to copy Register C characters from the string at Register Pair HL into INBUF$ at 4318H and terminate them with a carriage return, so the overlay can read its own parameters from the system input buffer.
5484
LD HL,4319H 21 19 43
Point Register Pair HL at 4319H, one byte into INBUF$. The first character of the copied string is the overlay letter itself, which the overlay does not need, so it is stepped over.
5487
LD DE,4480H 11 80 44
Point Register Pair DE at CFCB$ 4480H, the command file control block that 54A5H has just filled with "BASIC/OVn".
548A
JUMP to the resident @RUN vector at 4433H, which loads and enters the program named by the file control block at Register Pair DE. The overlay loads at 5200H and therefore replaces LBASIC's own image; it does not return here, and it is responsible for getting back into BASIC itself.

548DH - Overlay Filespec Stem and Message (Data)

Twenty-four bytes of data sitting between the dispatcher and the filespec builder. The first eight are the constant part of the overlay filespec, copied wholesale into the command file control block; the rest is the message shown when BASIC/OV3 cannot be loaded.

548D-5494
DEFM 'BASIC/OV' 42 41 53 49 43 2F 4F 56
The eight-character stem of the overlay filespec. 54A5H LDIRs exactly these eight bytes into CFCB$ at 4480H and then appends the digit, giving "BASIC/OV1", "BASIC/OV2", "BASIC/OV3" or "BASIC/OV4".
5495-54A2
DEFM 'Can''t load OV3' 43 61 6E 27 74 20 6C 6F 61 64 20 4F 56 33
Displayed from 54D5H when @LOAD reports a failure while fetching BASIC/OV3. That overlay holds the long error message text and the CMD"O" array sort, so LBASIC cannot expand an error or sort an array without it.
54A3-54A4
DEFB 0DH, 00H 0D 00
Carriage return and the 00H terminator STROUT at 28A7H stops on.

54A5H - Overlay Filespec Builder

Builds "BASIC/OVn" into CFCB$ at 4480H, the command file control block that @LOAD and @RUN both take in Register Pair DE. Entered with the overlay digit in Register B. Note the terminator: after @FSPEC has parsed a filespec the control block begins with the specification as plain ASCII text, and a 00H byte ends it.

54A5
LD A,B 78
Copy the overlay digit (Register B) into Register A, because the LDIR that follows destroys Register Pair BC.
54A6
LD HL,548DH 21 8D 54
Point Register Pair HL at 548DH, the eight-character stem "BASIC/OV".
54A9
LD DE,4480H 11 80 44
Point Register Pair DE at CFCB$ 4480H, the resident command file control block buffer.
54AC
LD BC,0008H 01 08 00
Load Register Pair BC with 8, the length of the stem.
54AF
LDIR ED B0
Block move: source (Register Pair HL) 548DH, destination (Register Pair DE) 4480H, count (Register Pair BC) 8 bytes, incrementing. Register Pair DE is left on 4488H, the ninth byte of the control block.
54B1
LD (DE),A 12
Store the overlay digit (Register A) at 4488H, completing the nine-character filespec.
54B2
INC DE 13
INCrement Register Pair DE by 1, advancing to the byte after the filespec.
54B3
XOR A AF
Set Register A to ZERO and clear all flags, ready to plant the terminator.
54B4
LD (DE),A 12
Store 00H at 4489H, ending the filespec text in the control block.
54B5
RET C9
RETurn to the caller with CFCB$ 4480H holding "BASIC/OVn" followed by 00H.

54B6H - BASIC/OV3 Loader

BASIC/OV3 is the odd one out. It holds the long error message text and the CMD"O" array sort, so it must be callable as a subroutine and must return; the other three overlays are programs that replace LBASIC. It therefore loads at 4E00H, in the space the two RAM hook images occupied before 5306H and 5331H copied them away, and it is entered with @LOAD rather than @RUN. Five places call here: 5837H (CMD"O" sort), 5A52H (long error text), 5EB0H, 6453H and 6564H. OVRLY$ at 430EH is used as the "already resident" marker, which is a second, entirely legitimate use of a resident LDOS cell by a transient program.

54B6
Fetch OVRLY$ from 430EH into Register A. LDOS normally uses this cell to record which numbered SYS overlay is resident at 4E00H; LBASIC cleared it at 52D0H and uses it to record whether BASIC/OV3 is loaded.
54B9
LD B,33H 06 33
Load Register B with 33H (ASCII: 3), the overlay digit that 54A5H needs to build "BASIC/OV3".
54BB
DEC A 3D
DECrement Register A by 1. If OVRLY$ held 01H the result is zero and the Z FLAG is set, meaning BASIC/OV3 is already sitting at 4E00H.
54BC
If the Z FLAG has been set, meaning the overlay is already resident, JUMP straight to its entry point at 4E00H with no disk access at all. The overlay RETurns to whoever called 54B6H.
54BF
PUSH HL E5
Save Register Pair HL, the caller's working pointer, onto the stack. The EX (SP),HL at 54CEH turns this saved value into the entry mechanism for the overlay.
54C0
GOSUB to 54A5H to build "BASIC/OV3" into CFCB$ at 4480H from the digit in Register B.
54C3
LD DE,4480H 11 80 44
Point Register Pair DE at CFCB$ 4480H, the control block @LOAD expects.
54C6
Point Register Pair HL at SFLAG$ 430FH, the resident LDOS system flag byte.
54C9
SET 2,(HL) CB D6
Set bit 2 of SFLAG$ 430FH, which marks that a program load initiated by @RUN is in progress. This is not guesswork about the bit: the resident @RUN routine at 4C5EH performs exactly PUSH HL / LD HL,430FH / SET 2,(HL) / CALL 4430H / EX (SP),HL, and 54BFH-54CFH is that sequence inlined so LBASIC can keep control of the return address.
54CB
GOSUB to the resident @LOAD vector at 4430H, which reads the load module named by the control block at Register Pair DE into memory and returns its transfer address in Register Pair HL, with the Z FLAG set on success and Register A holding an error code on failure.
54CE
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack. The overlay's transfer address 4E00H replaces the caller's pointer on the stack, and Register Pair HL gets the caller's pointer back, so a RET now enters the overlay with the caller's registers intact.
54CF
RET Z C8
If the Z FLAG has been set, meaning @LOAD succeeded, RETurn - which transfers to 4E00H, the entry point of BASIC/OV3.
54D0
OR 0C0H F6 C0
Error Path
Set bits 7 and 6 of the error code in Register A. Bit 6 asks @ERROR for the message text only, without the "Error code =NN" framing, and bit 7 asks it to return to the caller instead of aborting to @ABORT.
54D2
GOSUB to the resident @ERROR vector at 4409H to display the DOS error that stopped the load.
54D5
LD HL,5495H 21 95 54
Point Register Pair HL at the message "Can't load OV3" at 5495H.
54D8
GOSUB to the ROM routine STROUT at 28A7H to display it.
54DB
LD BC,1A18H 01 18 1A
Load Register Pair BC with 1A18H, the ROM's READY entry, as the return address the routine at 19AEH expects.
54DE
JUMP to the ROM's command-mode error handling at 19AEH, which unwinds the interpreter and returns BASIC to command level. This does not return, so the failed operation is abandoned.

54E1H - Command String Copier

Copies a counted string into INBUF$ at 4318H, the resident LDOS input buffer that @CMNDI reads, and gives it the carriage-return terminator LDOS expects. Entered with Register Pair HL pointing at the string and Register C holding its length; Register B is cleared here so the caller need only set the length byte.

54E1
LD DE,4318H 11 18 43
Point Register Pair DE at INBUF$ 4318H, the resident command input buffer. SYS0 declares this area but does not load it, so it is scratch at this moment.
54E4
LD B,00H 06 00
Load Register B with zero, so that Register Pair BC becomes the string length already held in Register C. Every caller passes the length in Register C alone.
54E6
LDIR ED B0
Block move: source (Register Pair HL) the quoted string, destination (Register Pair DE) INBUF$ 4318H, count (Register Pair BC) the string length, incrementing. Register Pair DE is left on the byte after the copied text.
54E8
LD A,0DH 3E 0D
Load Register A with 0DH, a carriage return, the terminator every LDOS command line carries.
54EA
LD (DE),A 12
Store the carriage return after the copied text, completing the command line in INBUF$.
54EB
RET C9
RETurn to the caller.

54ECH - CMD String Shell, Outward Half

This is how a BASIC program runs an LDOS library command. The problem it solves is that a DOS command loads at 5200H, exactly where LBASIC lives, so BASIC's whole low memory must be moved out of the way and brought back afterwards. The block moved is 5200H up to STREND, plus the Level II workspace 407DH-41E4H, and it is parked immediately below the stack with HIGH$ lowered to protect it. Three DOS vectors are saved and rewritten so that however the command finishes - normally, by aborting, or by raising an error - control comes back to the relocated restore routine.

54EC
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL points at the quoted command string. Register C already holds its length, set at 5454H.
54ED
GOSUB to 54E1H to copy the command string into INBUF$ at 4318H and terminate it with a carriage return.
54F0
GOSUB to 4750H in the resident disk primitive table. Those three bytes are LD A,(4308H) followed by LD C,A, which loads LDRV$ 4308H, the number of the drive last used, into Register C and falls into the SELECT primitive at 4754H. The effect is to select that drive and start its motor, so the drive is settled before interrupts are disabled for the memory move.
54F3
LD HL,0F098H 21 98 F0
Load Register Pair HL with 0F098H, which is minus 0F68H, 3944 decimal. This is the headroom a DOS command needs below the stack.
54F6
ADD HL,SP 39
ADD the Stack Pointer to Register Pair HL, giving the Stack Pointer less 3944, the lowest address the command could reach.
54F7
LD DE,(40FDH) ED 5B FD 40
Fetch STREND from 40FDH into Register Pair DE, the first free byte above all of BASIC's variables and arrays, and therefore the top of the block that has to be saved.
54FB
XOR A AF
Set Register A to ZERO and clear all flags, which clears the CARRY FLAG so the subtraction that follows is a plain 16-bit subtract.
54FC
SBC HL,DE ED 52
SUBtract STREND (Register Pair DE) from the headroom limit (Register Pair HL). The CARRY FLAG is set when STREND is the higher of the two, meaning BASIC's data already reaches into the space the command would need.
54FE
If the CARRY FLAG has been set, meaning there is not enough room below the stack, JUMP to the ROM's OUT OF MEMORY error processor at 197AH.
5501
LD HL,402DH 21 2D 40
Point Register Pair HL at 402DH, the resident @EXIT vector. @EXIT and @ABORT at 4030H are six consecutive bytes, two JP instructions.
5504
LD DE,540AH 11 0A 54
Point Register Pair DE at 540AH, the save area. Those bytes are the middle of the MEM parameter range check, which ran once during initialization and is never entered again, so the space is reclaimed rather than reserved.
5507
LD BC,0006H 01 06 00
Load Register Pair BC with 6, the combined length of the @EXIT and @ABORT vectors.
550A
LDIR ED B0
Block move: source (Register Pair HL) 402DH, destination (Register Pair DE) 540AH, count 6 bytes, incrementing. The saved copy is itself executable: each vector begins with a C3H JP opcode, so jumping at the copy re-executes the same jump and reaches the real DOS handler. Register Pair DE is left on 5410H.
550C
LD HL,4409H 21 09 44
Point Register Pair HL at 4409H, the resident @ERROR vector.
550F
LD C,04H 0E 04
Load Register C with 4, the number of bytes to save from @ERROR. Register B is already zero from the LDIR that has just finished.
5511
LDIR ED B0
Block move: source (Register Pair HL) 4409H, destination (Register Pair DE) 5410H, count 4 bytes, incrementing. The save area 540AH-5413H is now complete.
5513
LD HL,(40FDH) 2A FD 40
Fetch STREND from 40FDH into Register Pair HL again, the top of the block to be saved.
5516
LD DE,0AE00H 11 00 AE
Load Register Pair DE with 0AE00H, which is minus 5200H, the base of LBASIC's image.
5519
ADD HL,DE 19
ADD minus 5200H to STREND, giving the length of the block from 5200H up to STREND.
551A
LD (5572H),HL 22 72 55
Self-Modifying Code
Store that length (Register Pair HL) into 5572H, the operand of the LD BC,0000H at 5571H, which is the count for the outward LDDR at 5574H.
551D
PUSH HL E5
Save the block length (Register Pair HL) onto the stack; it is needed again at 5525H to compute the relocation base.
551E
LD DE,0F200H 11 00 F2
Load Register Pair DE with 0F200H, which is minus 0E00H, 3584 decimal. The restore is performed as two LDIRs, one of a fixed 0E00H bytes and one of the remainder.
5521
ADD HL,DE 19
ADD minus 0E00H to the block length, giving the residual count for the second restore LDIR.
5522
LD (55BAH),HL 22 BA 55
Self-Modifying Code
Store the residual count (Register Pair HL) into 55BAH, the operand of the LD BC,0000H at 55B9H in the restore routine.
5525
POP DE D1
Restore the block length from the stack into Register Pair DE.
5526
LD HL,(4049H) 2A 49 40
Fetch the current HIGH$ from 4049H into Register Pair HL. It is about to be lowered, so its present value must be preserved twice: once for the restore, and once as the link word of the resident module header planted at 5590H.
5529
LD (5592H),HL 22 92 55
Store the current HIGH$ (Register Pair HL) at 5592H, the link word of the resident module header at 5590H. LDOS chains high-memory modules by this word, so the block about to be protected becomes a proper link in that chain.
552C
LD (55BFH),HL 22 BF 55
Self-Modifying Code
Store the current HIGH$ (Register Pair HL) into 55BFH, the operand of the LD HL,0000H at 55BEH, so the restore routine can put HIGH$ back exactly as it was.
552F
LD HL,0000H 21 00 00
Load Register Pair HL with zero, ready to capture the Stack Pointer.
5532
ADD HL,SP 39
ADD the Stack Pointer to Register Pair HL, so Register Pair HL now holds the current Stack Pointer.
5533
LD (55C5H),HL 22 C5 55
Self-Modifying Code
Store the Stack Pointer (Register Pair HL) into 55C5H, the operand of the LD SP,0000H at 55C4H, so the restore routine can put the stack back.
5536
DEC HL 2B
DECrement Register Pair HL by 1, giving the highest byte the saved block may occupy.
5537
LD (556BH),HL 22 6B 55
Self-Modifying Code
Store the Stack Pointer less one (Register Pair HL) into 556BH, the operand of the LD DE,0000H at 556AH, which is the destination of the outward LDDR.
553A
XOR A AF
Set Register A to ZERO and clear all flags, clearing the CARRY FLAG for the subtraction that follows.
553B
SBC HL,DE ED 52
SUBtract the block length (Register Pair DE) from the Stack Pointer less one (Register Pair HL).
553D
INC HL 23
INCrement Register Pair HL by 1, giving the Stack Pointer less the block length, which is where 5200H will land after the move.
553E
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL. Register Pair DE now holds the relocation base, the address the byte at 5200H moves to, and every stub address that follows is formed by adding its offset from 5200H to it.
553F
DI F3
Disable interrupts. The RST 38H timer interrupt would otherwise fire in the middle of the move, while the stack and the DOS vectors are inconsistent.
5540
LD SP,41E0H 31 E0 41
Load the Stack Pointer with 41E0H, the top of the low-memory DOS stack area. The old stack is inside the block about to be moved, so a stack outside that block is needed for the duration.
5543
LD A,0C3H 3E C3
Load Register A with 0C3H, the Z80 opcode for an absolute JP. The three DOS vectors are rebuilt one opcode and one operand at a time.
5545
LD (402DH),A 32 2D 40
Store the JP opcode at 402DH, the @EXIT vector.
5548
LD (4030H),A 32 30 40
Store the JP opcode at 4030H, the @ABORT vector.
554B
LD (4409H),A 32 09 44
Store the JP opcode at 4409H, the @ERROR vector.
554E
LD HL,039CH 21 9C 03
Load Register Pair HL with 039CH, the offset of 559CH from 5200H. 559CH is the XOR A that marks a normal exit.
5551
ADD HL,DE 19
ADD the relocation base (Register Pair DE), giving the address 559CH will occupy after the move.
5552
LD (402EH),HL 22 2E 40
Store that address as the operand of the @EXIT vector at 402EH. A DOS command that finishes normally now lands on the relocated XOR A, which sets the Z FLAG.
5555
LD HL,039BH 21 9B 03
Load Register Pair HL with 039BH, the offset of 559BH from 5200H. 559BH is the OR 0AFH that marks an abort.
5558
ADD HL,DE 19
ADD the relocation base (Register Pair DE), giving the relocated address of 559BH.
5559
LD (4031H),HL 22 31 40
Store that address as the operand of the @ABORT vector at 4031H. A command that aborts now lands one byte lower than a normal exit, on an instruction that leaves the NZ FLAG set.
555C
LD HL,0396H 21 96 03
Load Register Pair HL with 0396H, the offset of 5596H from 5200H. 5596H is the AND A that begins the error stub.
555F
ADD HL,DE 19
ADD the relocation base (Register Pair DE), giving the relocated address of 5596H.
5560
LD (440AH),HL 22 0A 44
Store that address as the operand of the @ERROR vector at 440AH. A DOS error raised by the command now reaches LBASIC's own stub rather than the LDOS error dictionary.
5563
LD HL,0210H 21 10 02
Load Register Pair HL with 0210H, the offset of 5410H from 5200H. 5410H holds the saved copy of the real @ERROR vector, and because that copy starts with a C3H JP opcode it can be jumped to directly.
5566
ADD HL,DE 19
ADD the relocation base (Register Pair DE), giving the relocated address of the saved @ERROR bytes.
5567
LD (5598H),HL 22 98 55
Self-Modifying Code
Store that address into 5598H, the operand of the JP M,0000H at 5597H. An error that asks to return to its caller is therefore chained straight through to the genuine DOS error handler.
556A
LD DE,0000H 11 00 00
Self-Modifying Code
Load Register Pair DE with the operand held at 556BH, which 5537H set to the Stack Pointer less one. This is the top of the destination for the outward move. The listing shows the assembled 0000H.
556D
LD HL,(40FDH) 2A FD 40
Fetch STREND from 40FDH into Register Pair HL, the byte above the top of the block to be moved.
5570
DEC HL 2B
DECrement Register Pair HL by 1, giving the highest byte actually inside the block.
5571
LD BC,0000H 01 00 00
Self-Modifying Code
Load Register Pair BC with the operand held at 5572H, which 551AH set to the block length STREND minus 5200H. The listing shows the assembled 0000H.
5574
LDDR ED B8
Block move downward: source (Register Pair HL) the top of LBASIC's image and data, destination (Register Pair DE) just below the stack, count (Register Pair BC) the block length, decrementing. The whole of 5200H to STREND now sits under the stack, and Register Pair DE points just below it ready for the next block.
5576
LD HL,41E4H 21 E4 41
Point Register Pair HL at 41E4H, the last byte of the Level II BASIC workspace that has to be preserved.
5579
LD BC,0168H 01 68 01
Load Register Pair BC with 0168H, 360 decimal, which covers 407DH to 41E4H: the floating-point workspace, every BASIC pointer and flag, the DEFTBL type table, the accumulators and the whole Disk BASIC vector table.
557C
LDDR ED B8
Block move downward: source (Register Pair HL) 41E4H, destination (Register Pair DE) below the block already saved, count 360 bytes, decrementing. The DOS command is now free to use the whole of the low memory BASIC was occupying.
557E
LD HL,5595H 21 95 55
Point Register Pair HL at 5595H, the last byte of the six-byte resident module header at 5590H.
5581
LD C,06H 0E 06
Load Register C with 6, the length of that header. Register B is already zero from the LDDR that has just finished.
5583
LDDR ED B8
Block move downward: source (Register Pair HL) 5595H, destination (Register Pair DE) below everything already saved, count 6 bytes, decrementing. This plants a resident module header at the very bottom of the protected region, so the saved block appears in the LDOS high-memory module chain as a properly named module and the MEMORY command and the module lookup service at RST 28H code 0BCH both see it.
5585
EI FB
Enable interrupts. The stack, the vectors and the saved block are all consistent again.
5586
LD (4049H),DE ED 53 49 40
Store the address one below the module header (Register Pair DE) into HIGH$ 4049H. Everything above it is now protected, so a DOS command that respects HIGH$, which is every library command, cannot overwrite the saved BASIC session.
558A
LD HL,4318H 21 18 43
Point Register Pair HL at INBUF$ 4318H, which holds the carriage-return-terminated command line that 54E1H built.
558D
JUMP to the resident @CMNDI vector at 4405H, which executes the command line at Register Pair HL. Control does not come back here: the command finishes through one of the three patched vectors and lands in the relocated copy of the stubs at 5596H, 559BH or 559CH.

5590H - Resident Module Header and Return Stubs

Fourteen bytes serving two purposes. The first six are a standard LDOS resident high-memory module header, planted at the foot of the protected region by the LDDR at 5583H so that the saved BASIC session is a named entry in the module chain rather than an anonymous hole below HIGH$. The remainder are the three tiny stubs the patched @ERROR, @ABORT and @EXIT vectors point at, arranged so that all three converge on one EX AF,AF' and carry their outcome into the alternate flag set.

5590
The module header's first two bytes. 18H is the signature byte LDOS looks for when walking the high-memory module chain, and it is also the opcode of a relative jump, so the header doubles as the module's entry vector. The displacement 0FEH makes it jump to itself, an endless loop, which is correct for a module that is a protected data block and has no callable entry.
5592-5593
DEFW 0000H 00 00
Self-Modifying Code
The module chain link word, loaded at 5529H with the value of HIGH$ as it stood before the block was protected. Walking the chain from HIGH$ therefore reaches this module and then continues to whatever was resident before it.
5594
DEFB 01H 01
The module name length. The low nibble is the number of characters in the name that follows, so the name is one character long.
5595
DEFM '&' 26
The module name, a single ampersand. This is the name that appears when the MEMORY command lists resident modules while a CMD shell-out is in progress, and the name the module lookup service at RST 28H code 0BCH matches on.
5596
AND A A7
Error Stub
The relocated copy of this instruction is the target of the patched @ERROR vector, set at 5560H. Register A holds the LDOS error code with its option bits: bit 7 set means the caller asked to be returned to rather than aborted. AND A clears the CARRY FLAG and sets the S FLAG from bit 7 of Register A.
5597
JP M,0000H FA 00 00
Self-Modifying Code
If the S FLAG has been set, meaning bit 7 of the error code is set and the caller wants control back, JUMP to the operand held at 5598H. 5567H set it to the relocated address of the saved @ERROR bytes at 5410H, and because those bytes are still a C3H JP the jump chains straight into the genuine LDOS error handler. The listing shows the assembled 0000H.
559A
DEFB 01H 01
A single skip byte. Falling through from 5597H this is the opcode of a LD BC,0AFF6H that consumes the next two bytes harmlessly; entering at 559BH or 559CH instead executes those two bytes as instructions in their own right. This is the standard trick for giving one routine several entry points that differ only in the flag they set.
559B
OR 0AFH F6 AF
Abort Stub
The relocated copy of this instruction is the target of the patched @ABORT vector, set at 5559H. OR 0AFH forces Register A non-zero with bit 7 set, so the NZ FLAG and the S FLAG are both set, which the code at 55DEH reads as "the command aborted".
559C
XOR A AF
Normal Exit Stub
The relocated copy of this instruction is the target of the patched @EXIT vector, set at 5552H. Set Register A to ZERO and clear all flags, so the Z FLAG is set, which the code at 55DEH reads as "the command finished normally".
559D
EX AF,AF' 08
All three stubs converge here. Exchange Register A and the flags with the alternate set, parking the outcome where the restore routine cannot disturb it. The alternate register set is used because the restore performs LDIRs and arithmetic that would destroy the flags.

559EH - CMD String Shell, Return Half

Reached by falling out of whichever stub the DOS command triggered, with the outcome parked in the alternate flag set. This code runs from its relocated copy above HIGH$, not from 559EH itself, which is why every address it touches is either absolute or was patched before the move. It brings the saved block back, restores HIGH$, the Stack Pointer and the three DOS vectors, and then either returns to the BASIC statement that issued the CMD or raises an error.

559E
LD HL,(4049H) 2A 49 40
Fetch HIGH$ from 4049H into Register Pair HL. It still holds the lowered value that 5586H wrote, which is one byte below the module header at the foot of the saved block.
55A1
LD DE,0007H 11 07 00
Load Register Pair DE with 7, which steps past the one protected byte and the six-byte module header to the first byte of the saved Level II workspace.
55A4
ADD HL,DE 19
ADD 7 to HIGH$, giving the address of the saved copy of 407DH.
55A5
LD DE,407DH 11 7D 40
Point Register Pair DE at 407DH, the start of the Level II BASIC workspace that is to be restored.
55A8
LD BC,0168H 01 68 01
Load Register Pair BC with 360, the length of that workspace, matching the outward move at 5579H.
55AB
DI F3
Disable interrupts for the duration of the restore, since the vectors and the stack are about to be inconsistent again.
55AC
LDIR ED B0
Block move: source (Register Pair HL) the saved copy above HIGH$, destination (Register Pair DE) 407DH, count 360 bytes, incrementing. Every BASIC pointer, the accumulators and the whole Disk BASIC vector table at 4152H are back in place, so the disk keywords work again from this instruction onward.
55AE
LD DE,5200H 11 00 52
Point Register Pair DE at 5200H, the base of LBASIC's own image, for the second half of the restore.
55B1
LD BC,0E00H 01 00 0E
Load Register Pair BC with 0E00H, 3584 decimal. The restore is split into a fixed first LDIR and a remainder because the code doing the copying is itself inside the block being copied; moving a fixed leading chunk first gets the executing copy out of the way.
55B4
LDIR ED B0
Block move: source (Register Pair HL) the saved image, destination (Register Pair DE) 5200H, count 3584 bytes, incrementing. This restores 5200H-5FFFH, which includes every instruction from here to the end of the restore.
55B6
JUMP to 55B9H. This is an absolute jump to the address in the restored image rather than a fall-through, so execution transfers out of the relocated copy and back into the real one. Everything after this instruction runs at its proper address.
55B9
LD BC,0000H 01 00 00
Self-Modifying Code
Load Register Pair BC with the operand held at 55BAH, which 5522H set to the block length less 0E00H, the residue still to be restored. The listing shows the assembled 0000H.
55BC
LDIR ED B0
Block move: the remainder of the saved block, from 6000H up to STREND, back into place. The BASIC program, its variables and its strings are now exactly as they were before the CMD statement.
55BE
LD HL,0000H 21 00 00
Self-Modifying Code
Load Register Pair HL with the operand held at 55BFH, which 552CH set to the value HIGH$ held before the block was protected. The listing shows the assembled 0000H.
55C1
LD (4049H),HL 22 49 40
Store that value into HIGH$ 4049H, releasing the protected region and removing the module header from the chain in one operation.
55C4
LD SP,0000H 31 00 00
Self-Modifying Code
Load the Stack Pointer with the operand held at 55C5H, which 5533H set to the Stack Pointer as it stood when the CMD statement began. BASIC's own stack, including the return address into the interpreter, is live again.
55C7
LD HL,540AH 21 0A 54
Point Register Pair HL at the save area 540AH, which holds the original six bytes of @EXIT and @ABORT followed by the four bytes of @ERROR.
55CA
LD DE,402DH 11 2D 40
Point Register Pair DE at 402DH, the @EXIT vector.
55CD
LD C,06H 0E 06
Load Register C with 6, the combined length of @EXIT and @ABORT. Register B is already zero from the LDIR at 55BCH.
55CF
LDIR ED B0
Block move: restore the original @EXIT and @ABORT vectors from 540AH to 402DH, undoing the patches made at 5545H and 5552H.
55D1
LD DE,4409H 11 09 44
Point Register Pair DE at 4409H, the @ERROR vector. Register Pair HL is already on 5410H, the saved copy of it.
55D4
LD C,04H 0E 04
Load Register C with 4, the length of the @ERROR vector and its operand.
55D6
LDIR ED B0
Block move: restore the original @ERROR vector, undoing the patch made at 5560H. Every DOS vector is now back to what LDOS installed.
55D8
EI FB
Enable interrupts. Memory, the stack and the vectors are all consistent.
55D9
GOSUB to 5C7FH to re-establish LBASIC's device and interrupt state, the same routine initialization called at 52DAH. The DOS command may have changed the device table or the clock display, so this puts BASIC's view of them back.
55DC
POP HL E1
Restore Register Pair HL, the interpreter's BASIC text pointer, from the stack. It was pushed at 545AH before the dispatcher decided what kind of CMD this was.
55DD
EX AF,AF' 08
Exchange Register A and the flags with the alternate set, recovering the outcome that one of the three stubs at 5596H, 559BH or 559CH parked there.
55DE
RET Z C8
If the Z FLAG has been set, meaning the DOS command finished normally through @EXIT, RETurn to the BASIC statement that issued the CMD. This is the ordinary outcome.
55DF
LD E,8EH 1E 8E
Error Path
Load Register E with 8EH, the BASIC error number the ROM error handler at 19A2H expects for a command that aborted.
55E1
BIT 7,A CB 7F
Test bit 7 of Register A. The abort stub at 559BH set it with OR 0AFH; an error arriving through 5596H leaves it clear, because a set bit 7 there would have been chained to the real DOS handler instead.
55E3
If the NZ FLAG has been set, meaning the command aborted, JUMP to the ROM's error entry at 19A2H with the error number in Register E, which raises the corresponding BASIC error and returns to command level.
55E6
AND 3FH E6 3F
Mask Register A to bits 0 to 5, stripping the option bits and leaving the plain LDOS error number 0 to 45.
55E8
JUMP to 644FH, LBASIC's DOS-error-to-BASIC-error translator, which maps the LDOS error number in Register A onto the nearest BASIC error message and raises it.

55EBH - Variable Reference Parser

A short shared routine used by the FN evaluator at 55F7H and by DEF at 56DCH. Both need to read a function name from the BASIC text, and both must accept it as a variable-style name that may not exist yet, so the ROM's variable locator is entered with the create flag primed.

55EB
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 0BEH, the token for the FN keyword. If it is anything else a syntax error is raised; if it matches, the routine falls into the RST 10H logic and returns the next significant character.
55ED
LD A,80H 3E 80
Load Register A with 80H, the value that marks a function name lookup rather than an ordinary variable lookup.
55EF
LD (40DCH),A 32 DC 40
Store 80H into SUBFLG 40DCH, the ROM's subscript and FOR-variable flag. A non-zero SUBFLG tells the variable locator that the name being looked up is not a plain simple variable, which is what keeps a function name in its own namespace.
55F2
OR (HL) B6
OR the character at the address in Register Pair HL, the first letter of the function name, with 80H in Register A. Setting bit 7 of the first letter is how Level II BASIC distinguishes a user-defined function's variable entry from an ordinary variable of the same name.
55F3
LD B,A 47
Copy the marked first letter (Register A) into Register B, which is where the ROM's variable locator expects the first character of the name.
55F4
JUMP into the ROM's PTRGET routine at 2612H, the entry that takes the first character of the name already in Register B. It locates or creates the variable and returns its address in Register Pair DE. Because this is a JP rather than a CALL, PTRGET returns directly to whoever called 55EBH.

55F7H - FN, User-Defined Function Evaluation

Reached through vector slot 4155H whenever the interpreter meets an FN reference in an expression. Level II BASIC's own DEF FN allows one argument; LBASIC's allows several, which is why this routine is so much larger than the ROM's. The mechanism is the classic one for a recursive function: every parameter variable's current value is pushed on the stack, the argument expressions are evaluated into those variables, the definition text is evaluated, and the saved values are popped back. Register pair usage throughout is that Register Pair HL is the BASIC text pointer and Register Pair DE the address of a variable's value, with the two exchanged whenever the roles swap.

55F7
GOSUB to 55EBH to check for the FN token and locate the function's variable entry. It returns with Register Pair DE holding the address of that variable's two-byte value and Register Pair HL on the character after the function name.
55FA
LD A,(40AFH) 3A AF 40
Fetch VALTYP from 40AFH into Register A, the value-type code that the variable locator set from the function name's type character: 02H integer, 03H string, 04H single precision, 08H double precision. This is the type of the result the function will return.
55FD
AND A A7
Test Register A, the result type, against itself so the flags describe it.
55FE
PUSH AF F5
Save the result type and its flags (Register A and the flags) onto the stack for the whole of the evaluation; it is recovered at 5687H when the result is finally delivered.
55FF
LD (40F3H),HL 22 F3 40
Store the BASIC text pointer (Register Pair HL) into TEMP2 40F3H, a ROM scratch word. This is the position in the calling expression just after the function name, and 5657H reloads it to find the argument list.
5602
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL addresses the function variable's two-byte value.
5603
LD A,(HL) 7E
Fetch the low byte of the stored value into Register A. For a defined function that value is a pointer into the program text, at the character after the equals sign of its DEF FN.
5604
INC HL 23
INCrement Register Pair HL by 1, to the high byte of the stored pointer.
5605
LD H,(HL) 66
Fetch the high byte of the stored pointer into Register H.
5606
LD L,A 6F
Move the low byte from Register A into Register L, so Register Pair HL now holds the pointer to the function's definition text.
5607
OR H B4
OR Register H, the high byte of the definition pointer, into Register A, which still holds the low byte. The Z FLAG is set only when the whole pointer is 0000H, which is the state of a function that has never been DEFined.
5608
If the Z FLAG has been set, meaning the function has no definition, JUMP to 642FH, the first entry of the error-code table, which loads Register E with 2EH and raises the corresponding BASIC error through the ROM handler at 19A2H.
560B
LD A,(HL) 7E
Fetch the first character of the definition text into Register A.
560C
CP 28H FE 28
Compare it against 28H (ASCII: (). A definition that begins with an opening parenthesis has a parameter list; one that does not is a function of no arguments.
560E
If the NZ FLAG has been set, meaning there is no parameter list, JUMP to 5678H to evaluate the definition body directly.
5610
GOSUB through the RAM hook at 4003H to LBASIC's character fetch at 6579H, stepping Register Pair HL past the opening parenthesis to the first parameter name.
5611
LD (40D8H),HL 22 D8 40
Store the position of the first parameter name (Register Pair HL) into TEMP3 40D8H, a ROM scratch word. 565DH reloads it when the argument expressions are evaluated into the parameters.
5614
JUMP to 5618H, entering the parameter-saving loop past the comma check, because the first parameter is not preceded by one.
5616
Loop Start
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 2CH (ASCII: ,), the separator between parameter names. Each parameter after the first re-enters the loop here.
5618
LD C,04H 0E 04
Load Register C with 4, the number of stack slots the routine that follows is asked to guarantee.
561A
GOSUB to the ROM routine GETSTK at 1963H, which checks that at least the requested amount of stack space remains between the stack pointer and the top of the variable area and raises OUT OF MEMORY if not. Saving parameter values on the stack is what makes this check necessary.
561D
LD A,80H 3E 80
Load Register A with 80H, the marker that this lookup is for a function parameter rather than an ordinary variable.
561F
LD (40DCH),A 32 DC 40
Store 80H into SUBFLG 40DCH, the ROM's subscript and FOR-variable flag, so the variable locator treats the name as a parameter reference.
5622
GOSUB to the ROM routine PTRGET at 260DH, which reads the parameter name at Register Pair HL, locates or creates that variable, and returns its value address in Register Pair DE with VALTYP 40AFH set to its type.
5625
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL addresses the parameter's current value and Register Pair DE holds the text pointer.
5626
GOSUB through the RAM hook at 4009H to the ROM's GETYPR routine at 25D9H, which reads VALTYP 40AFH and returns the type in the flags: the Z FLAG set means string, the S FLAG set means integer, parity odd means single precision and parity even means double precision.
5627
SCF 37
Set the Carry flag. GETYPR does not disturb the CARRY FLAG, so this marks the numeric paths for the test at 5643H, and 564EH clears it again on the string path.
5628
If the Z FLAG has been set, meaning this parameter is a string, JUMP to 5645H, where a string descriptor rather than a numeric value is preserved.
562A
LD C,(HL) 4E
Fetch the first byte of the parameter's current value into Register C.
562B
INC HL 23
INCrement Register Pair HL by 1, advancing through the value.
562C
LD B,(HL) 46
Fetch the second byte of the value into Register B.
562D
PUSH BC C5
Save those two bytes (Register Pair BC) onto the stack. Every parameter's old value is stacked so that a function may call itself.
562E
If the S FLAG has been set by GETYPR, meaning the parameter is an integer and therefore two bytes long, JUMP to 5643H because the whole value is already saved.
5631
INC HL 23
INCrement Register Pair HL by 1, to the third byte of the value.
5632
LD C,(HL) 4E
Fetch the third byte of the value into Register C.
5633
INC HL 23
INCrement Register Pair HL by 1, to the fourth byte.
5634
LD B,(HL) 46
Fetch the fourth byte of the value into Register B.
5635
PUSH BC C5
Save the third and fourth bytes onto the stack.
5636
If parity is odd, which is how GETYPR reports single precision, JUMP to 5643H because a four-byte value is now completely saved.
5639
INC HL 23
INCrement Register Pair HL by 1, to the fifth byte of a double-precision value.
563A
LD C,(HL) 4E
Fetch the fifth byte into Register C.
563B
INC HL 23
INCrement Register Pair HL by 1, to the sixth byte.
563C
LD B,(HL) 46
Fetch the sixth byte into Register B.
563D
INC HL 23
INCrement Register Pair HL by 1, to the seventh byte.
563E
PUSH BC C5
Save the fifth and sixth bytes onto the stack.
563F
LD C,(HL) 4E
Fetch the seventh byte into Register C.
5640
INC HL 23
INCrement Register Pair HL by 1, to the eighth and last byte.
5641
LD B,(HL) 46
Fetch the eighth byte into Register B.
5642
PUSH BC C5
Save the seventh and eighth bytes onto the stack, completing an eight-byte double-precision value.
5643
If the CARRY FLAG has been set, which the SCF at 5627H did for every numeric parameter, JUMP to 564FH. The string path arrives here with the CARRY FLAG cleared by the XOR A at 564EH.
5645
String Parameter Path
If the NO CARRY FLAG has been set, JUMP to the ROM routine APPLOP at 2423H. Arriving here from 5628H the CARRY FLAG is set by the SCF at 5627H, so on that path the jump is not taken and execution falls through; the instruction serves the case where this address is reached with the flag clear.
5648
PUSH DE D5
Save the BASIC text pointer (Register Pair DE) onto the stack across the string call.
5649
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, putting the address of the string parameter's descriptor where the ROM routine expects it.
564A
GOSUB into the ROM's PUTNEW routine at 2888H, which registers a temporary string descriptor so that the parameter's existing string is preserved while the function runs.
564D
POP DE D1
Restore the BASIC text pointer (Register Pair DE) from the stack.
564E
XOR A AF
Set Register A to ZERO and clear all flags, which clears the CARRY FLAG so that 5643H distinguishes this string path from the numeric ones.
564F
PUSH HL E5
Save the address reached in the parameter's value (Register Pair HL) onto the stack. The unwind at 568CH pops these back in reverse order.
5650
PUSH AF F5
Save Register A and the flags, which record this parameter's type, onto the stack alongside its address.
5651
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, putting the BASIC text pointer back into Register Pair HL.
5652
LD A,(HL) 7E
Fetch the character following the parameter name into Register A.
5653
CP 29H FE 29
Compare it against 29H (ASCII: )), the end of the parameter list.
5655
If the NZ FLAG has been set, meaning more parameters follow, LOOP BACK to 5616H to require a comma and save the next one. Loop End
5657
LD HL,(40F3H) 2A F3 40
Fetch the saved position in the calling expression from TEMP2 40F3H into Register Pair HL, the character just after the function name in the statement being executed.
565A
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 28H (ASCII: (), the opening parenthesis of the argument list in the call.
565C
PUSH HL E5
Save the position in the calling argument list (Register Pair HL) onto the stack; the two pointers are swapped through the stack by the EX (SP),HL instructions that follow.
565D
LD HL,(40D8H) 2A D8 40
Fetch the saved position in the definition's parameter list from TEMP3 40D8H into Register Pair HL.
5660
Loop Start
GOSUB to the ROM routine PTRGET at 260DH to locate the next parameter variable named in the definition, returning its address in Register Pair DE.
5663
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, swapping the definition pointer for the caller's argument pointer. The two lists are walked in step, one on the stack and one in Register Pair HL.
5664
GOSUB into the ROM at 1F2BH, which evaluates the next argument expression from the caller's text and assigns it to the variable whose address is in Register Pair DE, converting the type if necessary.
5667
LD A,(HL) 7E
Fetch the character that ended the argument expression into Register A.
5668
CP 29H FE 29
Compare it against 29H (ASCII: )), the end of the caller's argument list.
566A
If the Z FLAG has been set, meaning the last argument has been assigned, JUMP to 5673H.
566C
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring a comma between arguments in the caller's list.
566E
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, bringing the definition's parameter list pointer back into Register Pair HL.
566F
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the matching comma in the definition's parameter list. The two lists must agree in length or a syntax error is raised here.
5671
LOOP BACK to 5660H for the next parameter and argument pair. Loop End
5673
GOSUB through the RAM hook at 4003H to the character fetch at 6579H, stepping Register Pair HL past the closing parenthesis of the caller's argument list.
5674
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, so Register Pair HL addresses the definition's parameter list and the caller's resumption point is on the stack.
5675
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the closing parenthesis of the definition's parameter list.
5677
LD A,0D5H 3E D5
Overlapping Code
On the parameter-list path this is a two-byte LD A,0D5H whose only purpose is to consume the 0D5H byte at 5678H so that it is not executed. The value left in Register A is never used. The other reading of that same byte is shown on the next row.
5678
PUSH DE D5
No-Parameter Entry
The second reading of the byte at 5678H, and the target of the JR NZ at 560EH taken when the definition has no parameter list. Entered here the byte is a PUSH DE, which saves Register Pair DE, the position in the CALLER's text at which evaluation must resume. That is precisely what the parameter-list path leaves on the stack through the EX (SP),HL at 5674H, so both paths arrive at 5679H with identical stacks.
5679
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 0D5H, the token for the equals sign that introduces the body of a DEF FN. Both entries converge here.
567B
GOSUB into the ROM's expression evaluator FRMPRN at 2337H to evaluate the body of the function definition, leaving the result in the floating-point accumulator at 4121H.
567E
DEC HL 2B
DECrement Register Pair HL by 1, so the character fetch that follows re-reads the character that ended the expression.
567F
GOSUB through the RAM hook at 4003H to the character fetch at 6579H, which sets the Z FLAG when the character is a statement terminator.
5680
If the NZ FLAG has been set, meaning something other than the end of the statement follows the function body, JUMP to the ROM error handling at 1997H to raise a syntax error. A DEF FN body must run to the end of its line.
5683
GOSUB through the RAM hook at 4009H to the ROM's GETYPR routine at 25D9H, reading VALTYP 40AFH to discover the type of the value the body produced.
5684
If the Z FLAG has been set, meaning the result is a string, JUMP to 56B5H, where a string result needs its descriptor moved out of the temporary area before the parameters are restored.
5686
POP DE D1
Restore into Register Pair DE the address within a parameter's value that 564FH stacked.
5687
POP AF F1
Loop Start
Restore into Register A and the flags the type record that 5650H stacked for this parameter, or, on the last pass, the function's own result type that 55FEH stacked.
5688
If the Z FLAG has been set, meaning this saved item is a string, JUMP to 56C9H to restore its descriptor.
568A
If the NO CARRY FLAG has been set, which is how 564EH marked the end of the saved list, JUMP to 56A8H to deliver the function's result. Loop End
568C
POP HL E1
Restore into Register Pair HL the address within the parameter's value that was stacked at 564FH, positioned on its last byte.
568D
POP BC C1
Restore into Register Pair BC the last two bytes of the parameter's original value.
568E
LD (HL),B 70
Store the higher of those two bytes (Register B) back into the parameter variable.
568F
DEC HL 2B
DECrement Register Pair HL by 1, working backward through the value exactly as the saving loop worked forward.
5690
LD (HL),C 71
Store the lower of those two bytes (Register C) back into the parameter variable.
5691
If the S FLAG has been set, meaning this parameter was an integer and only two bytes were saved, LOOP BACK to 5687H for the next stacked item.
5694
DEC HL 2B
DECrement Register Pair HL by 1, to the next byte down in the value.
5695
POP BC C1
Restore the next two saved bytes of the value into Register Pair BC.
5696
LD (HL),B 70
Store the higher byte (Register B) into the parameter variable.
5697
DEC HL 2B
DECrement Register Pair HL by 1.
5698
LD (HL),C 71
Store the lower byte (Register C) into the parameter variable.
5699
If parity is odd, meaning this parameter was single precision and its four bytes are now restored, LOOP BACK to 5687H for the next stacked item.
569C
DEC HL 2B
DECrement Register Pair HL by 1, continuing into the lower half of a double-precision value.
569D
POP BC C1
Restore two more saved bytes into Register Pair BC.
569E
LD (HL),B 70
Store the higher byte (Register B) into the parameter variable.
569F
DEC HL 2B
DECrement Register Pair HL by 1.
56A0
LD (HL),C 71
Store the lower byte (Register C) into the parameter variable.
56A1
DEC HL 2B
DECrement Register Pair HL by 1.
56A2
POP BC C1
Restore the final two saved bytes into Register Pair BC.
56A3
LD (HL),B 70
Store the higher byte (Register B) into the parameter variable.
56A4
DEC HL 2B
DECrement Register Pair HL by 1.
56A5
LD (HL),C 71
Store the lower byte (Register C) into the parameter variable, completing an eight-byte double-precision restore.
56A6
LOOP BACK to 5687H for the next stacked item.
56A8
PUSH DE D5
Result Delivery
Save Register Pair DE, which now holds the caller's resumption point in the BASIC text, onto the stack.
56A9
PUSH AF F5
Save the function's declared result type (Register A and the flags), recovered from the stack entry that 55FEH made, across the conversion that follows.
56AA
GOSUB through the RAM hook at 4009H to the ROM's GETYPR routine at 25D9H, reading VALTYP 40AFH to discover the type the body actually produced.
56AB
LD DE,40D3H 11 D3 40
Point Register Pair DE at DSCTMP 40D3H, the three-byte temporary string descriptor the ROM builds new strings in.
56AE
If the Z FLAG has been set, meaning the body produced a string, GOSUB into the ROM's PUTNEW routine at 2888H to register that descriptor as a live temporary string.
56B1
POP AF F1
Restore the function's declared result type (Register A and the flags) from the stack.
56B2
JUMP into the ROM's type conversion routine DOCNVF at 281AH, which coerces the value in the floating-point accumulator to the type the function name declared and RETurns to the caller's resumption point, which is on top of the stack.
56B5
LD HL,(40B3H) 2A B3 40
String Result Path
Fetch TEMPPT from 40B3H into Register Pair HL, the pointer to the next free slot in the temporary string descriptor area at 40B5H.
56B8
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, moving that pointer into Register Pair DE for the comparison.
56B9
LD HL,(4121H) 2A 21 41
Fetch the contents of FACLO 4121H into Register Pair HL. For a string result the floating-point accumulator holds the address of the string's descriptor rather than a number.
56BC
GOSUB through the RAM hook at 4006H to the ROM's DCOMPR routine at 1C90H, comparing Register Pair DE (the temporary descriptor high-water mark) against Register Pair HL (the result descriptor's address). The CARRY FLAG is set when the result descriptor lies below the high-water mark, meaning it is not a temporary that this function created.
56BD
If the CARRY FLAG has been set, meaning the result string already lives somewhere permanent, JUMP to 56C4H.
56BF
GOSUB into the ROM's string routines at 2843H, which copies the string into permanent string space so that it survives the temporary descriptor being released.
56C2
JUMP to 5686H to begin unwinding the saved parameter values.
56C4
POP DE D1
Restore into Register Pair DE the address that 564FH stacked for the outermost saved item.
56C5
LD HL,40D3H 21 D3 40
Point Register Pair HL at DSCTMP 40D3H, the temporary descriptor.
56C8
PUSH HL E5
Save that address onto the stack so the code at 56C9H treats the temporary descriptor as the destination.
56C9
String Restore
GOSUB to the ROM routine FRETMS at 29F5H, which releases the most recent temporary string descriptor and returns Register Pair HL pointing at it with Register Pair BC holding the string's address.
56CC
LD A,(HL) 7E
Fetch the string's length byte from the released descriptor into Register A.
56CD
LD (40B3H),HL 22 B3 40
Store the released descriptor's address (Register Pair HL) into TEMPPT 40B3H, lowering the temporary descriptor high-water mark so the slot is reusable.
56D0
POP HL E1
Restore the destination address from the stack into Register Pair HL, either a string parameter's descriptor or DSCTMP 40D3H.
56D1
LD (HL),A 77
Store the string's length (Register A) into the destination descriptor.
56D2
INC HL 23
INCrement Register Pair HL by 1, to the address field of the descriptor.
56D3
LD (HL),C 71
Store the low byte of the string's address (Register C) into the descriptor.
56D4
INC HL 23
INCrement Register Pair HL by 1, to the high half of the address field.
56D5
LD (HL),B 70
Store the high byte of the string's address (Register B) into the descriptor, completing the three-byte copy.
56D6
LOOP BACK to 5687H for the next stacked item.

56D8H - DEF, Function and USR Definition

Reached through vector slot 415BH. DEF introduces either DEF FN, which records where a function's definition text begins, or DEF USR, which records the machine-language entry point for one of the ten USR vectors. The two are told apart by the token that follows.

56D8
CP 0C1H FE C1
Compare Register A, the token following DEF, against 0C1H, the token for USR. If it matches the Z FLAG is set.
56DA
If the Z FLAG has been set, meaning this is DEF USR, JUMP to 572DH.
56DC
GOSUB to 55EBH to require the FN token and locate the function's variable entry, returning its address in Register Pair DE.
56DF
GOSUB to the ROM routine ERRDIR at 2828H, which raises ILLEGAL DIRECT if the statement was typed at the prompt rather than run from a program. A DEF FN records a pointer into program text, so it is meaningless in immediate mode.
56E2
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL addresses the function variable's two-byte value and Register Pair DE holds the BASIC text pointer.
56E3
LD (HL),E 73
Store the low byte of the text pointer (Register E) into the function variable.
56E4
INC HL 23
INCrement Register Pair HL by 1, to the high half of the stored value.
56E5
LD (HL),D 72
Store the high byte of the text pointer (Register D). The function variable now holds the address of its own definition, which is what 5603H reads when the function is later called.
56E6
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, putting the BASIC text pointer back into Register Pair HL.
56E7
LD A,(HL) 7E
Fetch the character following the function name into Register A.
56E8
CP 28H FE 28
Compare it against 28H (ASCII: (). A DEF FN must have a parameter list.
56EA
If the NZ FLAG has been set, meaning no parameter list follows, JUMP into the ROM at 1F05H to raise the error. The definition pointer has already been stored, so a malformed DEF FN leaves the variable pointing at the offending text.
56ED
GOSUB through the RAM hook at 4003H to the character fetch at 6579H, stepping past the opening parenthesis to the first parameter name.
56EE
Loop Start
GOSUB to the ROM routine PTRGET at 260DH, which creates the parameter variable if it does not exist. Creating them now means the evaluator at 5622H will always find them.
56F1
LD A,(HL) 7E
Fetch the character following the parameter name into Register A.
56F2
CP 29H FE 29
Compare it against 29H (ASCII: )), the end of the parameter list.
56F4
If the Z FLAG has been set, meaning the parameter list is complete, JUMP into the ROM at 1F05H, which resumes normal statement processing so that the rest of the DEF FN line is skipped rather than executed.
56F7
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring a comma before the next parameter name.
56F9
LOOP BACK to 56EEH to create the next parameter variable. Loop End

56FBH - USR, Machine-Language Function Call

Reached through vector slot 41A9H, which the ROM's USR processor at 27FEH CALLs. Level II BASIC has a single USR entry; Disk BASIC has ten, USR0 to USR9, held in the vector table at 661CH. This routine resolves the digit, fetches the address, and arranges for the machine-language routine to be entered with the argument available and for its result to be collected afterwards.

56FB
POP AF F1
Discard the return address the ROM's USR processor pushed, by popping it into Register A and the flags. This routine takes over the call rather than returning to it.
56FC
GOSUB to 571BH to read the optional digit after USR and return Register Pair DE pointing at the corresponding two-byte slot in the vector table at 661CH.
56FF
PUSH DE D5
Save the address of the vector table slot (Register Pair DE) onto the stack across the argument evaluation.
5700
GOSUB to the ROM routine PARCHK at 252CH, which evaluates the parenthesised argument expression and leaves the result in the floating-point accumulator at 4121H with its type in VALTYP 40AFH.
5703
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, so Register Pair HL addresses the vector table slot and the BASIC text pointer is preserved on the stack.
5704
LD C,(HL) 4E
Fetch the low byte of the machine-language entry address from the vector table slot into Register C.
5705
INC HL 23
INCrement Register Pair HL by 1, to the high half of the slot.
5706
LD B,(HL) 46
Fetch the high byte of the entry address into Register B, so Register Pair BC holds the routine to be called. For a USR number that has never been defined this is 642FH, the error-code table entry that raises the undefined-function error.
5707
LD HL,26E7H 21 E7 26
Point Register Pair HL at the ROM routine FINZER at 26E7H, which will be the return address the user routine RETurns to.
570A
PUSH HL E5
Push that return address onto the stack, beneath the entry address that follows, so a RET from the user's routine lands there.
570B
PUSH BC C5
Push the user routine's entry address (Register Pair BC) onto the stack, so the RET at 571AH transfers to it.
570C
LD A,(40AFH) 3A AF 40
Fetch VALTYP from 40AFH into Register A, the type of the argument that was evaluated.
570F
PUSH AF F5
Save the argument type (Register A and the flags) onto the stack across the string handling.
5710
CP 03H FE 03
Compare the argument type against 03H, the value-type code for a string.
5712
If the Z FLAG has been set, meaning the argument is a string, GOSUB to the ROM routine FREFAC at 29DAH, which releases the temporary string and returns its descriptor address, so the machine-language routine receives a usable pointer.
5715
POP AF F1
Restore the argument type (Register A and the flags) from the stack, so the user routine is entered with Register A holding the type code.
5716
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, which for a string argument puts the descriptor address into Register Pair DE.
5717
LD HL,4121H 21 21 41
Point Register Pair HL at FACLO 4121H, the base of the floating-point accumulator, so the machine-language routine can read its numeric argument and leave its result there.
571A
RET C9
RETurn, which transfers to the user's machine-language routine whose address 570BH pushed. When that routine RETurns it reaches FINZER at 26E7H, which tidies the variable-type state and delivers the result to the expression evaluator.

571BH - USR Number Resolver

Shared by USR at 56FCH and by DEF USR at 572DH. Reads the optional digit that follows the USR token and converts it into the address of one of the ten two-byte slots in the vector table at 661CH. An absent digit means USR0, which is what preserves compatibility with the single USR of Level II BASIC.

571B
GOSUB through the RAM hook at 4003H to LBASIC's character fetch at 6579H, which returns the next character in Register A and sets the CARRY FLAG when that character is a digit.
571C
LD BC,0000H 01 00 00
Load Register Pair BC with zero, the table offset for USR0, which is the value used when no digit follows.
571F
If the NO CARRY FLAG has been set, meaning the character is not a digit, JUMP to 5726H with the offset left at zero.
5721
SUB 30H D6 30
SUBtract 30H from Register A, converting the ASCII digit to the binary value 0 to 9.
5723
RLCA 07
Rotate Register A Left, which doubles it because the value is at most 9 and bit 7 is clear. The table holds two-byte entries, so the digit becomes a byte offset.
5724
LD C,A 4F
Copy the doubled digit (Register A) into Register C, giving Register Pair BC the table offset.
5725
GOSUB through the RAM hook at 4003H to the character fetch at 6579H, stepping Register Pair HL past the digit.
5726
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, moving the BASIC text pointer into Register Pair DE so Register Pair HL can be used for the address arithmetic.
5727
LD HL,661CH 21 1C 66
Point Register Pair HL at 661CH, the base of the ten-entry USR vector table that 5397H filled with 642FH during initialization.
572A
ADD HL,BC 09
ADD the doubled digit (Register Pair BC) to the table base, giving the address of the slot for the requested USR number.
572B
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL again, so Register Pair DE holds the slot address and Register Pair HL is back to being the BASIC text pointer.
572C
RET C9
RETurn to the caller with Register Pair DE addressing the vector table slot.

572DH - DEF USR, Set a Machine-Language Entry Point

Reached from 56DAH when DEF is followed by the USR token. The digit selects one of the ten vector slots and the expression after the equals sign supplies the address, which is why a DEF USR must be executed before the matching USR call is made.

572D
GOSUB to 571BH to read the optional digit and return Register Pair DE pointing at the corresponding slot in the vector table at 661CH.
5730
PUSH DE D5
Save the slot address (Register Pair DE) onto the stack across the expression evaluation.
5731
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 0D5H, the token for the equals sign.
5733
GOSUB to the ROM routine GETINT at 2B02H, which evaluates the expression that follows and returns it in Register Pair DE as a 16-bit integer, the entry address of the machine-language routine.
5736
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, so Register Pair HL addresses the vector table slot and the BASIC text pointer is held on the stack.
5737
LD (HL),E 73
Store the low byte of the entry address (Register E) into the vector table slot.
5738
INC HL 23
INCrement Register Pair HL by 1, to the high half of the slot.
5739
LD (HL),D 72
Store the high byte of the entry address (Register D), completing the vector. That USR number now points at the user's routine instead of at the error entry 642FH.
573A
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack.
573B
RET C9
RETurn to the interpreter to continue with the rest of the statement.

573CH - PRINT# Device Check

Reached through vector slot 41CAH, which the ROM CALLs at 206FH once it has begun a PRINT statement. Level II BASIC already understands PRINT #-1, the cassette form, so LBASIC must let that through untouched and claim only PRINT #n where n is a disk file number. It does that by peeking one character ahead: the token 0CEH is the minus sign, so a minus following the hash mark means the cassette form and LBASIC steps aside.

573C
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack, because the look-ahead that follows advances it and it must be put back either way.
573D
GOSUB through the RAM hook at 4003H to LBASIC's character fetch at 6579H, returning the character after the hash mark in Register A.
573E
CP 0CEH FE CE
Compare Register A against 0CEH, the Level II token for the minus sign. If it matches the Z FLAG is set, meaning the statement is PRINT #-1 and the destination is the cassette, not a disk file.
5740
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack, undoing the look-ahead. POP does not disturb the flags, so the result of the comparison survives.
5741
LD A,(HL) 7E
Fetch the character at the restored pointer back into Register A, the hash mark itself, which is what the caller expects to find there. LD A,(HL) does not disturb the flags either, so the comparison result still stands.
5742
RET Z C8
If the Z FLAG has been set, meaning a minus sign follows, RETurn to the ROM and let it handle the cassette form of PRINT# itself.
5743
LD C,02H 0E 02
Load Register C with 02H, the mode code for a file open for output. The file's buffer carries its mode in its first byte, and 5CE4H refuses the operation if the two do not agree.
5745
JUMP to 5CDCH, the shared file-reference parser, which requires the hash mark, evaluates the file number, checks it against MAXFIL 408EH, looks the buffer up in the pointer table at 65FAH, verifies the mode against Register C and records the buffer address in 65F8H as the current file.

5748H - CMD Switch Parser

Reads the second, string, argument of a two-part CMD statement such as CMD"B","OFF" and reports whether it says ON or OFF. The test is neat and cheap: it looks only at the second character of the string, which is N in "ON" and F in "OFF", so no string comparison is needed. It returns with the Z FLAG set when the switch is off.

5748
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 2CH (ASCII: ,), the comma that separates the two arguments of the CMD statement.
574A
GOSUB to the ROM's expression evaluator FRMPRN at 2337H to evaluate the switch argument, which must be a string.
574D
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack across the string handling.
574E
GOSUB to the ROM routine FRESTR at 29D7H, which releases the temporary string in the floating-point accumulator and returns Register Pair HL pointing at its three-byte descriptor: length, then address.
5751
LD A,(HL) 7E
Fetch the string's length from the descriptor into Register A.
5752
CP 02H FE 02
Compare the length against 2. If it is below 2 the CARRY FLAG is set. The test reads the second character, so a string shorter than two characters cannot be classified.
5754
If the CARRY FLAG has been set, meaning the switch string is shorter than two characters, JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH to raise ?FC.
5757
INC HL 23
INCrement Register Pair HL by 1, to the address field of the string descriptor.
5758
LD A,(HL) 7E
Fetch the low byte of the string's address into Register A.
5759
INC HL 23
INCrement Register Pair HL by 1, to the high half of the address field.
575A
LD H,(HL) 66
Fetch the high byte of the string's address into Register H.
575B
LD L,A 6F
Move the low byte from Register A into Register L, so Register Pair HL now points at the first character of the switch string.
575C
INC HL 23
INCrement Register Pair HL by 1, to the second character. That is the character that distinguishes the two words.
575D
LD A,(HL) 7E
Fetch the second character of the switch string into Register A: N from "ON", F from "OFF".
575E
RES 5,A CB AF
Reset bit 5 of Register A, the ASCII case bit, so that a lower-case switch string is accepted as readily as an upper-case one.
5760
CP 46H FE 46
Compare Register A against 46H (ASCII: F). The Z FLAG is set when the switch string is "OFF" and cleared when it is "ON". This is the result the caller reads.
5762
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack. POP does not disturb the flags, so the comparison result reaches the caller intact.
5763
RET C9
RETurn to the caller with the Z FLAG set for OFF and clear for ON.

5764H - CMD"L", Load a Machine-Language File

Reached from the CMD statement dispatcher at 57D3H. CMD"L","filespec" loads a machine-language load module into memory without executing it, which is how a BASIC program brings in the routine a USR vector will call. The first file buffer doubles as the scratch area in which the file control block is built, since no BASIC file need be open to use this command.

5764
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the comma that separates the letter from the filespec.
5766
LD BC,(661AH) ED 4B 1A 66
Fetch the address of the first file buffer from 661AH into Register Pair BC, the cell that 53E2H filled during initialization. The buffer is used as working space for the file control block.
576A
GOSUB to 62A1H, the shared filespec builder, which evaluates the string expression at Register Pair HL and parses it into a file control block in the buffer addressed by Register Pair BC, applying the default /BAS extension if the EXT parameter is on.
576D
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack across the load, which uses Register Pair HL for the transfer address.
576E
GOSUB to the resident @LOAD vector at 4430H, which reads the load module named by the control block into memory and returns with the Z FLAG set on success or an LDOS error code in Register A on failure.
5771
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack. POP does not disturb the flags, so the outcome of the load survives.
5772
If the NZ FLAG has been set, meaning the load failed, JUMP to 644FH, the DOS-error-to-BASIC-error translator, which loads BASIC/OV3 and raises the matching BASIC error.
5775
RET C9
RETurn to the interpreter. The module is in memory, and a DEF USR statement is what makes it callable.

5776H - CMD"P", Read the Printer Status

Reached from the CMD statement dispatcher at 57D8H. The Model I printer interface is memory-mapped, and reading 37E8H returns the printer's status lines in the high nibble: busy, out of paper, unit select and fault. This command hands that nibble to a BASIC variable so a program can test whether the printer is ready before it prints. The variable may be numeric or a string, and the routine converts as needed.

5776
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the comma before the variable name.
5778
GOSUB to the ROM routine PTRGET at 260DH to locate or create the variable that is to receive the status, returning its address in Register Pair DE and its type in VALTYP 40AFH.
577B
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack; it is restored at 579FH.
577C
LD (40DFH),DE ED 53 DF 40
Store the variable's address (Register Pair DE) into TEMP 40DFH, the ROM scratch word that the assignment routine at 1F33H reads to find its destination.
5780
PUSH DE D5
Save the variable's address (Register Pair DE) onto the stack as well.
5781
GOSUB through the RAM hook at 4009H to the ROM's GETYPR routine at 25D9H, reading VALTYP 40AFH so the flags describe the variable's type. The Z FLAG is set when it is a string.
5782
PUSH AF F5
Save the type flags (Register A and the flags) onto the stack across the hardware read.
5783
LD A,(37E8H) 3A E8 37
Read the memory-mapped printer port at 37E8H into Register A. On the Model I this address is the printer data latch when written and the printer status when read, and the status lines appear in the high nibble.
5786
AND 0F0H E6 F0
Mask Register A to its high nibble, discarding the low four bits which carry no status information.
5788
GOSUB into the ROM's POS routine at 27F8H, the entry that places the byte in Register A into the floating-point accumulator as an integer and sets VALTYP 40AFH accordingly.
578B
POP AF F1
Restore the variable's type flags (Register A and the flags) from the stack.
578C
If the NZ FLAG has been set, meaning the destination variable is numeric, JUMP to 579FH to assign the integer straight to it.
578E
PUSH AF F5
String Destination Path
Save the type flags again across the conversion.
578F
GOSUB to the ROM routine FOUT at 0FBDH, which converts the number in the floating-point accumulator to its ASCII representation in the buffer at 4130H.
5792
GOSUB to the ROM routine STRLIT at 2865H, which builds a string descriptor for the ASCII text FOUT produced.
5795
GOSUB to the ROM routine FREFAC at 29DAH, which releases that descriptor from the temporary area and returns its details.
5798
GOSUB into the ROM's string routines at 2843H, which copies the text into permanent string space so it survives beyond the temporary descriptor.
579B
GOSUB into the ROM's PUTNEW routine at 2888H, which registers the finished descriptor as the current value in the floating-point accumulator.
579E
POP AF F1
Restore the type flags (Register A and the flags) from the stack.
579F
POP HL E1
Restore the variable's address from the stack into Register Pair HL. It was pushed from Register Pair DE at 5780H, so this is another deliberate swap of register pairs through the stack.
57A0
JUMP into the ROM at 1F33H, which assigns the value now in the floating-point accumulator to the variable whose address is in TEMP 40DFH, converting the type if necessary, and RETurns to the interpreter.

57A3H - CMD"B", Enable or Disable the BREAK Key

Reached from the CMD statement dispatcher at 57F9H. CMD"B","OFF" stops the BREAK key interrupting a running program, which is how a finished application protects itself. Two things have to change together: the resident LDOS flag that the keyboard driver consults, and a patch byte inside the RST 28H dispatcher in SYS0.

57A3
GOSUB to 5748H to read the switch string, which returns with the Z FLAG set when it says OFF and clear when it says ON.
57A6
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack, because Register Pair HL is about to be used to address the resident flag byte.
57A7
Point Register Pair HL at SFLAG$ 430FH, the resident LDOS system flag byte, whose bit 4 is the BREAK-disabled flag.
57AA
If the Z FLAG has been set, meaning the switch said OFF, JUMP to 57B4H to disable the BREAK key.
57AC
RES 4,(HL) CB A6
Reset bit 4 of SFLAG$ 430FH, re-enabling the BREAK key so that a running program can be interrupted again.
57AE
XOR A AF
Set Register A to ZERO and clear all flags. 00H is the NOP opcode, the patch byte's original value.
57AF
Store Register A into 4BF0H, a patch byte inside SYS0's RST 28H dispatcher. 4BEFH begins the path taken for request codes below 80H, and 4BF0H is a NOP sitting inside it; writing 0C8H there turns it into a RET Z so the path returns at once, and writing 00H restores the NOP. The two writes always accompany the matching change to bit 4 of SFLAG$.
57B2
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack.
57B3
RET C9
RETurn to the interpreter.
57B4
SET 4,(HL) CB E6
Set bit 4 of SFLAG$ 430FH, marking the BREAK key disabled so the keyboard driver ignores it.
57B6
LD A,0C8H 3E C8
Load Register A with 0C8H, the opcode for RET Z, which is what the dispatcher patch byte becomes while BREAK is disabled.
57B8
JUMP to 57AFH to write that opcode into 4BF0H and return, sharing the tail with the enabling path.

57BAH - Exit to DOS

The single way out of LBASIC, reached by CMD"S" at 57E4H, by CMD"A" at 57E9H with the CARRY FLAG set, and by the two initialization failures at 538FH. The one thing that must not be forgotten on the way out is the RST 10H hook: LBASIC redirected it to its own character fetch at 6579H, and leaving it there would send every subsequent RST 10H in the machine into an image that is about to be overwritten.

57BA
LD HL,1D78H 21 78 1D
Load Register Pair HL with 1D78H, the address of the ROM's own CHRGTR routine, the character fetch that RST 10H reaches on a machine without Disk BASIC.
57BD
LD (4004H),HL 22 04 40
Store 1D78H into 4004H, the operand of the JP at 4003H that is the RST 10H hook. This undoes the redirection the LDIR at 530EH installed and hands the interpreter's inner loop back to the ROM.
57C0
If the CARRY FLAG has been set, JUMP to the resident @ABORT vector at 4030H. LDOS treats this as an aborted program, which stops a JCL job that was running BASIC. The CARRY FLAG is set by 538EH after an initialization failure and by 57E8H for CMD"A".
57C3
JUMP to the resident @EXIT vector at 402DH, the normal return to DOS Ready. The BASIC program and its variables are left untouched in memory, which is what allows BASIC * to re-enter the session later.

57C6H - CMD Statement

Reached through vector slot 4173H. CMD is the door between BASIC and the operating system, and it carries two quite different kinds of traffic: a quoted string of two characters or more is either an overlay call or an LDOS command line and goes to the dispatcher at 5454H, while a single letter selects one of a dozen built-in functions. The letter is folded to upper case with RES 5, which incidentally is what makes 2AH, the asterisk of CMD"*", arrive here as 0AH.

57C6
GOSUB to 5869H to evaluate the quoted string argument, which returns Register A holding its length and Register Pair DE pointing at its first character.
57C9
CP 02H FE 02
Compare the string length (Register A) against 2. If it is below 2 the CARRY FLAG is set, meaning a single-letter function; 2 or more sets the NO CARRY FLAG.
57CB
If the NO CARRY FLAG has been set, JUMP to the dispatcher at 5454H, which decides between an overlay call and an LDOS command line by looking at whether the second character is a space.
57CE
LD A,(DE) 1A
Fetch the single letter from the string into Register A.
57CF
RES 5,A CB AF
Reset bit 5 of Register A, the ASCII case bit, so that a lower-case letter is accepted. This also maps 2AH, the asterisk, to 0AH, which is the value tested at 57EFH.
57D1
CP 4CH FE 4C
Compare the letter against 4CH (ASCII: L), the load-a-machine-language-file function.
57D3
If the Z FLAG has been set, JUMP to 5764H to handle CMD"L","filespec".
57D6
CP 50H FE 50
Compare the letter against 50H (ASCII: P), the printer status function.
57D8
If the Z FLAG has been set, JUMP to 5776H to handle CMD"P",variable.
57DA
CP 54H FE 54
Compare the letter against 54H (ASCII: T), which disables interrupts for cassette work.
57DC
If the Z FLAG has been set, JUMP to 5810H, which is the middle of the four-byte body at 580EH and executes only the DI and the RET.
57DE
CP 52H FE 52
Compare the letter against 52H (ASCII: R), which re-enables interrupts after cassette work.
57E0
If the Z FLAG has been set, JUMP to 580EH, the top of that same four-byte body, where the first instruction is EI.
57E2
CP 53H FE 53
Compare the letter against 53H (ASCII: S), the exit to DOS Ready.
57E4
If the Z FLAG has been set, JUMP to 57BAH, the exit to DOS. The CARRY FLAG is clear on this path, so the exit is taken through @EXIT and the BASIC program is left intact for a later BASIC *.
57E6
CP 41H FE 41
Compare the letter against 41H (ASCII: A), the abort to DOS.
57E8
SCF 37
Set the Carry flag. This is done before the conditional jump rather than after it, because SCF does not disturb the Z FLAG, and the CARRY FLAG is exactly what 57C0H tests to choose @ABORT over @EXIT.
57E9
If the Z FLAG has been set, JUMP to 57BAH with the CARRY FLAG set, so the exit is taken through @ABORT and any JCL job driving BASIC is stopped.
57EB
CP 49H FE 49
Compare the letter against 49H (ASCII: I), which runs a DOS command and stays in DOS afterwards.
57ED
If the Z FLAG has been set, JUMP to 583AH to handle CMD"I","command".
57EF
CP 0AH FE 0A
Compare the letter against 0AH. No printable character is 0AH, but the RES 5 at 57CFH turned 2AH, the asterisk, into exactly this value, so the test recognises CMD"*".
57F1
If the Z FLAG has been set, JUMP to 5812H to copy the screen to the printer.
57F3
CP 4FH FE 4F
Compare the letter against 4FH (ASCII: O), the array sort.
57F5
If the Z FLAG has been set, JUMP to 5834H, which loads BASIC/OV3 and lets the overlay do the sorting.
57F7
CP 42H FE 42
Compare the letter against 42H (ASCII: B), the BREAK key switch.
57F9
If the Z FLAG has been set, JUMP to 57A3H to handle CMD"B","switch".
57FB
CP 45H FE 45
Compare the letter against 45H (ASCII: E), which reports the last DOS error.
57FD
If the Z FLAG has been set, JUMP to 584AH to display the last DOS error message.
57FF
CP 44H FE 44
Compare the letter against 44H (ASCII: D), the debugger control.
5801
If the Z FLAG has been set, JUMP to 5852H to enter or arm the system debugger.
5803
LD HL,1E4AH 21 4A 1E
Unrecognised Letter
Point Register Pair HL at the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH, which becomes the destination of the JP (HL) at 5479H if the second dispatcher does not recognise the letter either.
5806
LD C,01H 0E 01
Load Register C with 1, the string length, which the overlay path at 5481H uses as the byte count when it copies the string into INBUF$.
5808
JUMP to 5468H, the second entry of the overlay dispatcher, which tests the letter against N, X and V. This is why CMD"N" with no space and no parameters still loads BASIC/OV1 and renumbers the whole program; anything the matcher rejects falls out to the ?FC error already loaded into Register Pair HL.
580B
JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH. Nothing reaches this instruction: the JP at 5808H never returns, and no branch targets 580BH. It is the remains of the direct error path that 5803H-5808H replaced.

580EH - CMD"R" and CMD"T", Interrupt Control

Four bytes, two functions, and one of the neatest constructions in the file. Entered at 580EH the bytes read EI / LD A,0F3H / RET, which is CMD"R"; entered one byte later at 5810H the 0F3H is no longer an operand but an opcode in its own right, DI, giving DI / RET, which is CMD"T". Model I cassette input and output are timed by software and cannot tolerate the real-time clock interrupt, so a program that reads or writes tape brackets the operation with these two.

580E
EI FB
Enable interrupts, restoring the real-time clock and everything that depends on it. This is the entry taken by CMD"R" from 57E0H.
580F
LD A,0F3H 3E F3
Load Register A with 0F3H. On the CMD"R" path this instruction exists only to consume the 0F3H byte so that it is not executed; the value left in Register A is not used. On the CMD"T" path this instruction is never seen, because entry is at 5810H.
5810
DI F3
Disable interrupts. This is the entry taken by CMD"T" from 57DCH, where the byte that was the operand of the LD A above is executed as an opcode instead.
5811
RET C9
RETurn to the interpreter. Both entries share this one instruction.

5812H - CMD"*", Screen Print

Copies the whole video display to the printer, one 64-column row at a time, without needing the JKL screen-print function of the keyboard driver to be active. Video RAM runs from 3C00H to 3FFFH, so the loop ends when the high byte reaches 40H, which is tested with a single BIT instruction. Video control codes below 20H are shifted into the printable range before they are sent.

5812
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack, because Register Pair HL is used to walk video memory.
5813
LD HL,3C00H 21 00 3C
Point Register Pair HL at 3C00H, the top left corner of the Model I video display. The screen is 64 columns by 16 rows, 1024 bytes ending at 3FFFH.
5816
LD A,L 7D
Loop Start
Copy the low byte of the video address (Register L) into Register A, to test whether a new screen row is beginning.
5817
AND 3FH E6 3F
Mask Register A to its low six bits, the column number within the row. The Z FLAG is set at columns 0, that is at every multiple of 64, which is exactly the start of each screen row.
5819
LD A,0DH 3E 0D
Load Register A with 0DH, a carriage return, ready to be printed. LD does not disturb the flags, so the column test made two instructions ago still stands.
581B
If the Z FLAG has been set, meaning a new screen row is starting, GOSUB to the ROM printer routine $PRT at 003BH to send the carriage return in Register A, ending the previous printed line.
581E
BIT 6,H CB 74
Test bit 6 of the high byte of the video address (Register H). Throughout video memory Register H runs 3CH to 3FH, all of which have bit 6 clear; the first address past the screen is 4000H, where bit 6 is set. This is a one-instruction end-of-screen test.
5820
If the NZ FLAG has been set, meaning the whole screen has been sent, JUMP to 5879H to restore Register Pair HL and return.
5822
GOSUB to 5C81H, the keyboard and BREAK poll, which returns with the NZ FLAG set when the operator has pressed BREAK.
5825
If the NZ FLAG has been set, meaning BREAK was pressed, JUMP to 5879H to abandon the screen print. A full screen on a slow printer takes long enough that an escape is worth having.
5827
LD A,(HL) 7E
Fetch the byte at the current video address into Register A, the character code as the display hardware holds it.
5828
CP 20H FE 20
Compare Register A against 20H, the ASCII space. If it is below 20H the CARRY FLAG is set, meaning a video code that has no printable ASCII equivalent.
582A
If the NO CARRY FLAG has been set, meaning the byte is already printable, JUMP to 582EH and send it unchanged.
582C
ADD A,40H C6 40
ADD 40H to Register A. The Model I displays video codes 00H to 1FH as the upper-case letters and symbols that ASCII places at 40H to 5FH, so adding 40H converts what the operator can see on the screen into the character the printer will produce.
582E
INC HL 23
INCrement Register Pair HL by 1, advancing to the next screen position.
582F
GOSUB to the ROM printer routine $PRT at 003BH to send the character in Register A to the printer through the printer device control block at 4025H.
5832
LOOP BACK to 5816H for the next screen position. Loop End

5834H - CMD"O", Array Sort

Reached from 57F5H. CMD"O",numelem,firstelem sorts part of a singly dimensioned string array. LBASIC does none of the work itself: it selects function 3 and hands over to BASIC/OV3, the same overlay that holds the long error message text. The selector travels in the alternate accumulator, which is free because nothing between here and the overlay's entry uses it.

5834
LD A,03H 3E 03
Load Register A with 03H, the function selector that tells BASIC/OV3 to sort rather than to translate an error code. The other selector seen in this image is 02H, used by the error translator at 644FH.
5836
EX AF,AF' 08
Exchange Register A and the flags with the alternate set, parking the selector where the overlay loader cannot disturb it. The loader at 54B6H uses Register A for OVRLY$ and for the @LOAD error code, so the primary accumulator is not available.
5837
JUMP to the BASIC/OV3 loader at 54B6H, which either enters the overlay directly if OVRLY$ 430EH shows it is already resident or loads it to 4E00H first. The overlay reads the selector from the alternate accumulator and RETurns to the interpreter when the sort is finished.

583AH - CMD"I", Run a DOS Command and Stay in DOS

Reached from 57EDH. This is the deliberately one-way relative of the CMD string shell: the command is executed and control is left at DOS Ready rather than returned to BASIC. Because nothing comes back, none of the memory saving that 54ECH performs is needed, and the routine is nine instructions long instead of eighty.

583A
GOSUB to 5867H, the entry that first requires a comma and then evaluates the command string, returning Register A holding its length and Register Pair DE pointing at its first character.
583D
LD L,A 6F
Copy the string length (Register A) into Register L.
583E
LD H,00H 26 00
Load Register H with zero, so Register Pair HL holds the length as a 16-bit value.
5840
ADD HL,DE 19
ADD the string address (Register Pair DE) to the length (Register Pair HL), giving the address of the byte just past the end of the command string.
5841
LD (HL),0DH 36 0D
Store a carriage return there, giving the string the terminator LDOS expects. The command is executed in place in BASIC's string space rather than being copied to INBUF$, which is safe only because BASIC is never coming back.
5843
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL points at the first character of the command line.
5844
LD SP,41E0H 31 E0 41
Load the Stack Pointer with 41E0H, the top of the low-memory DOS stack area. BASIC's stack is abandoned along with everything else, which is what makes this a one-way trip.
5847
JUMP to the resident @CMNDI vector at 4405H to execute the command line at Register Pair HL. When it finishes, LDOS returns to DOS Ready rather than to BASIC.

584AH - CMD"E", Report the Last DOS Error

Reached from 57FDH. BASIC's error dictionary is much smaller than the operating system's, so several distinct DOS failures collapse into one BASIC message such as "Disk I/O error". This command displays the operating system's own wording for the most recent failure, which is how a programmer tells those cases apart. The code is kept in a single run-time byte at 65F7H, just below the file buffer pointer table.

584A
LD A,(65F7H) 3A F7 65
Fetch the last DOS error code from 65F7H into Register A. This one-byte run-time cell records the LDOS error number of the most recent failed operation.
584D
OR 0C0H F6 C0
Set bits 7 and 6 of the error code in Register A. Bit 6 asks @ERROR for the message text alone, without the "Error code =NN" framing and the trailing lines, and bit 7 asks it to return to the caller rather than abort the program.
584F
JUMP to the resident @ERROR vector at 4409H, which loads SYS4, expands the code into English from the packed word dictionary and displays it. Because bit 7 was set, @ERROR RETurns, and since this is a JP rather than a CALL that return goes straight back to the interpreter.

5852H - CMD"D", Debugger Control

Reached from 5801H. With no switch this enters the system debugger immediately. With a switch it arms or disarms the resident re-entry trap instead, so that the debugger is reached later by pressing BREAK while the BASIC program keeps running until then. The trap slot at 4315H holds a three-byte JP 400FH when armed; only the opcode byte is written here, because the operand at 4316H already holds 400FH and is never disturbed.

5852
DEC HL 2B
DECrement Register Pair HL by 1, so the character fetch that follows re-reads the character after the closing quote rather than skipping it.
5853
GOSUB through the RAM hook at 4003H to LBASIC's character fetch at 6579H, which sets the Z FLAG when the character is a statement terminator, meaning no switch argument follows.
5854
If the Z FLAG has been set, meaning the statement was a bare CMD"D", JUMP to 5863H to enter the debugger at once.
5856
GOSUB to 5748H to read the switch string, which returns with the Z FLAG set for OFF and clear for ON.
5859
LD A,0C3H 3E C3
Load Register A with 0C3H, the opcode for an absolute JP. Written into the trap slot this completes a JP 400FH, and 400FH is the RST 30H vector which reaches the @DEBUG launcher.
585B
If the NZ FLAG has been set, meaning the switch said ON, JUMP to 585FH to plant that opcode and arm the trap.
585D
LD A,0C9H 3E C9
Load Register A with 0C9H, the opcode for RET, which is the disarmed state of the trap slot.
585F
Store the opcode (Register A) into 4315H, the resident DEBUG re-entry trap slot. The RST 38H timer interrupt handler in SYS0 polls this byte on every tick and, when it finds 0C3H and BREAK has been pressed, redirects its own return into the debugger. Initialization cleared this byte at 52CDH.
5862
RET C9
RETurn to the interpreter, which carries on running the BASIC program.
5863
GOSUB to the resident @DEBUG vector at 440DH, which issues RST 28H request code 87H to load SYS5 into 4E00H and enter the monitor immediately. When the operator leaves the debugger with the G command, it returns here.
5866
RET C9
RETurn to the interpreter.

5867H - CMD Argument Evaluator

Two entry points into one routine. 5867H requires a comma first and is used by the two-argument forms; 5869H skips that check and is used by the CMD statement itself, which has already consumed the opening quote. Both return the argument string's length in Register A and its address in Register Pair DE, and both reject an empty string, since every CMD function needs at least one character to dispatch on.

5867
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 2CH (ASCII: ,). This entry exists so that a caller such as CMD"I","command" can check the separator and evaluate the argument in one call.
5869
GOSUB to the ROM's expression evaluator FRMPRN at 2337H to evaluate the argument, leaving a string descriptor in the floating-point accumulator. This is the entry the CMD statement uses at 57C6H.
586C
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack across the string handling.
586D
GOSUB to the ROM routine FRESTR at 29D7H, which releases the temporary string and returns Register Pair HL pointing at its three-byte descriptor: length, then address. A type mismatch is raised here if the argument was not a string.
5870
LD A,(HL) 7E
Fetch the string's length from the descriptor into Register A.
5871
AND A A7
Test the length against itself. The Z FLAG is set for an empty string.
5872
If the Z FLAG has been set, meaning the argument was an empty string, JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH to raise ?FC. There is nothing to dispatch on.
5875
INC HL 23
INCrement Register Pair HL by 1, to the address field of the descriptor.
5876
LD E,(HL) 5E
Fetch the low byte of the string's address into Register E.
5877
INC HL 23
INCrement Register Pair HL by 1, to the high half of the address field.
5878
LD D,(HL) 56
Fetch the high byte into Register D, so Register Pair DE points at the first character of the argument string.
5879
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack. The screen-print loop at 5820H and 5825H also jumps here, using this instruction and the RET below as its own exit, since it too has a single Register Pair HL saved on the stack.
587A
RET C9
RETurn to the caller with Register A holding the argument's length and Register Pair DE its address.

587BH - PRINT Completion Hook

Reached through vector slot 41BEH, which the ROM CALLs at 2174H as it finishes a PRINT statement and resets the output device back to the video display. LBASIC uses the opportunity to forget which disk file was current, so that a bare PRINT following a PRINT#n cannot accidentally write to the file. Register A is zero when the ROM arrives here, so both bytes of the pointer are cleared.

587B
LD (65F8H),A 32 F8 65
Store Register A into 65F8H, the low byte of the current file buffer pointer. The ROM reaches this hook with Register A cleared, so this begins zeroing the pointer.
587E
LD (65F9H),A 32 F9 65
Store Register A into 65F9H, the high byte of the current file buffer pointer. A pointer of 0000H means no file is current, which is what 5CD3H tests before it diverts printed output to a disk file.
5881
RET C9
RETurn to the ROM to finish the PRINT statement normally.

5882H - INPUT# and INPUT@

Reached through vector slot 41D6H, which the ROM CALLs at 219EH at the start of an INPUT statement. Two quite different extensions share this entry. A hash mark introduces INPUT#n, which reads from a disk file, and is passed through to the ROM if it turns out to be the cassette form INPUT#-1. An at sign introduces INPUT@, LBASIC's fielded screen input: it positions the cursor, optionally displays a prompt, and then accepts a string of a stated width into a highlighted field with a chosen fill character, optionally restricted to numeric characters and optionally terminating as soon as the field is full. The field description is packed into Register C, which is why it is built so carefully.

5882
CP 23H FE 23
Compare Register A, the character following the INPUT token, against 23H (ASCII: #). If it matches the Z FLAG is set, meaning a file or cassette input.
5884
If the NZ FLAG has been set, JUMP to 5899H to test for the at sign of INPUT@ instead.
5886
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack, because the look-ahead that follows advances it.
5887
GOSUB through the RAM hook at 4003H to LBASIC's character fetch at 6579H, returning the character after the hash mark in Register A.
5888
CP 0CEH FE CE
Compare Register A against 0CEH, the Level II token for the minus sign, which marks the cassette form INPUT#-1.
588A
POP HL E1
Restore the BASIC text pointer (Register Pair HL), undoing the look-ahead. POP does not disturb the flags.
588B
LD A,(HL) 7E
Fetch the hash mark back into Register A, which is what the ROM expects to find there. LD A,(HL) does not disturb the flags either.
588C
RET Z C8
If the Z FLAG has been set, meaning the cassette form, RETurn to the ROM and let it read from tape as it always has.
588D
POP BC C1
Discard the ROM's return address into Register Pair BC. From here LBASIC takes the INPUT statement over entirely and re-enters the ROM at a different point, so the original return is never used.
588E
GOSUB to 5CDAH, which loads Register C with 01H, the mode code for a file open for input, and falls into the shared file-reference parser. That requires the hash mark, evaluates the file number, checks it against MAXFIL 408EH, finds the buffer through the pointer table at 65FAH, verifies the file's mode and records it as current in 65F8H.
5891
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack, where the ROM's INPUT routine expects to find the position at which the statement continues.
5892
LD HL,(40A7H) 2A A7 40
Fetch BUFPNT from 40A7H into Register Pair HL, the keyboard line buffer, which initialization pointed at 5309H. The data read from the file is delivered into this buffer, so the ROM can parse it with exactly the code it uses for typed input.
5895
DEC HL 2B
DECrement Register Pair HL by 1, to the comma that 532BH planted at 5308H. The ROM's INPUT parser expects to be positioned on a separator before the first value, and RST 10H increments before fetching.
5896
JUMP into the ROM's INPUT routine at 21EBH, the entry that assigns values from the buffer at Register Pair HL to the variable list whose position is on the stack. This does not return.
5899
CP 40H FE 40
INPUT@ Path
Compare Register A, the character following the INPUT token, against 40H (ASCII: @).
589B
RET NZ C0
If the NZ FLAG has been set, meaning neither a hash mark nor an at sign follows, RETurn to the ROM to handle an ordinary INPUT from the keyboard.
589C
GOSUB to the ROM routine GETINT at 2B01H, which evaluates the screen position expression and returns it in Register Pair DE as a 16-bit integer, with the high byte also left in Register A.
589F
CP 04H FE 04
Compare the high byte of the position (Register A) against 4. The screen is 1024 bytes, so a valid position is below 0400H and the high byte must be 0 to 3.
58A1
If the NO CARRY FLAG has been set, meaning the position is 1024 or more and therefore off the screen, JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH to raise ?FC.
58A4
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack across the cursor positioning.
58A5
LD HL,3C00H 21 00 3C
Point Register Pair HL at 3C00H, the top left corner of the video display, the base the position is measured from.
58A8
GOSUB to 5A22H, the second entry of the cursor-wrap routine, which adds the offset in Register Pair DE to the screen base in Register Pair HL and stores the result at 4020H, the cursor field of the display device control block.
58AB
LD A,E 7B
Copy the low byte of the screen offset (Register E) into Register A, on the way to working out which column the cursor now occupies.
58AC
AND 3FH E6 3F
Mask Register A to its low six bits, giving the column number 0 to 63 within the row, because rows are 64 bytes long.
58AE
LD (40A6H),A 32 A6 40
Store the column (Register A) into TTYPOS 40A6H, the ROM's cursor column tracker. Without this the ROM's comma and TAB positioning would be working from a stale column.
58B1
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack.
58B2
POP AF F1
Discard the ROM's return address into Register A and the flags. As with the file path, LBASIC takes the statement over and re-enters the ROM elsewhere.
58B3
LD A,(HL) 7E
Fetch the character after the position expression into Register A.
58B4
CP 2CH FE 2C
Compare it against 2CH (ASCII: ,). A comma means a prompt message or a field description follows; a semicolon means the variable list follows immediately.
58B6
If the Z FLAG has been set, JUMP to 58BDH to parse what follows the comma.
58B8
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 3BH (ASCII: ;), the separator before the variable list.
58BA
JUMP into the ROM's INPUT routine at 21DBH, the entry that prompts the operator and assigns the typed values to the variable list at Register Pair HL. This is the plain form of INPUT@, with a position but no field description, and it does not return.
58BD
GOSUB through the RAM hook at 4003H to the character fetch at 6579H, stepping past the comma.
58BE
LD A,(HL) 7E
Fetch the character after the comma into Register A.
58BF
CP 22H FE 22
Compare it against 22H, the ASCII double quote. A quote introduces the optional prompt message.
58C1
If the NZ FLAG has been set, meaning no prompt message was given, JUMP to 58D2H to read the field width instead.
58C3
GOSUB into the ROM's STRLIT routine at 2866H, the entry that builds a descriptor for a quoted string literal taken straight from the program text.
58C6
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack across the display.
58C7
GOSUB to the ROM routine STRPRT at 28AAH, which displays the string described by that descriptor. Because 58A8H has already moved the cursor, the prompt appears at the requested screen position.
58CA
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack.
58CB
LD A,(HL) 7E
Fetch the character after the prompt string into Register A.
58CC
CP 3BH FE 3B
Compare it against 3BH (ASCII: ;). A semicolon here means the prompt was the whole of the extension and the variable list follows.
58CE
If the Z FLAG has been set, JUMP to 58B8H, which consumes the semicolon and hands the statement to the ROM's ordinary INPUT.
58D0
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the comma that separates the prompt from the field width.
58D2
GOSUB to the ROM routine GETBYT at 2B1CH, which evaluates the field width expression and returns it in Register A as an 8-bit value.
58D5
OR A B7
Test the field width (Register A) against itself. The Z FLAG is set for a width of zero.
58D6
If the Z FLAG has been set, meaning a field width of zero, JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH to raise ?FC.
58D9
CP 0F1H FE F1
Compare the field width against 0F1H, 241 decimal. The published limit is 1 to 240, which is also the capacity of the keyboard line buffer at 5309H.
58DB
If the NO CARRY FLAG has been set, meaning the width is 241 or more, JUMP to 1E4AH to raise ?FC.
58DE
PUSH AF F5
Save the validated field width (Register A and the flags) onto the stack while the field description string is parsed.
58DF
GOSUB to 5867H, which requires the comma and then evaluates the field description string, returning its length in Register A and its address in Register Pair DE.
58E2
LD B,A 47
Copy the length of the field description (Register A) into Register B, the count of characters still to be examined.
58E3
LD C,3FH 0E 3F
Load Register C with 3FH, the packed field description in its default state. Bits 7 and 6 are clear, meaning alphanumeric input and no automatic termination, and the low six bits hold 3FH, which 5901H's encoding makes the fill character 5FH, the underline the manual documents as the default.
58E5
LD A,(DE) 1A
Fetch the first character of the field description into Register A.
58E6
INC DE 13
INCrement Register Pair DE by 1, advancing through the description string.
58E7
CP 24H FE 24
Compare it against 24H (ASCII: $), which asks for any character in the range 32 to 127 to be accepted.
58E9
If the Z FLAG has been set, JUMP to 58F2H leaving bit 7 of Register C clear, which is the alphanumeric setting.
58EB
CP 23H FE 23
Compare it against 23H (ASCII: #), which restricts the field to the digits and to period, minus and plus.
58ED
If the NZ FLAG has been set, meaning the description begins with neither a dollar sign nor a hash mark, JUMP to the ROM error handling at 1997H to raise a syntax error. One of the two is mandatory.
58F0
SET 7,C CB F9
Set bit 7 of the packed field description in Register C, marking the field numeric only.
58F2
DEC B 05
DECrement Register B by 1, counting off the character just examined.
58F3
If the Z FLAG has been set, meaning the description was a single character, JUMP to 590CH with the defaults for everything else.
58F5
LD A,(DE) 1A
Fetch the second character of the field description into Register A.
58F6
INC DE 13
INCrement Register Pair DE by 1, advancing through the description string.
58F7
CP 2AH FE 2A
Compare it against 2AH (ASCII: *), which asks for the input to terminate as soon as the field is full instead of waiting for ENTER.
58F9
If the NZ FLAG has been set, this character is not an asterisk, so it must be the fill character; JUMP to 5901H to encode it. This ordering is why the manual notes that an asterisk cannot be used as the fill character unless automatic termination is also requested.
58FB
SET 6,C CB F1
Set bit 6 of the packed field description in Register C, marking automatic termination on a full field.
58FD
DEC B 05
DECrement Register B by 1, counting off the asterisk.
58FE
If the Z FLAG has been set, meaning the description ended with the asterisk, JUMP to 590CH leaving the fill character at its default underline.
5900
LD A,(DE) 1A
Fetch the third character of the field description into Register A, the fill character.
5901
SUB 20H D6 20
SUBtract 20H from the fill character in Register A, biasing it so that the printable range 20H to 7FH becomes 00H to 5FH and fits in six bits.
5903
CP 60H FE 60
Compare the biased fill character against 60H, 96 decimal. A value of 96 or more sets the NO CARRY FLAG and means the original character was outside the printable range.
5905
If the NO CARRY FLAG has been set, meaning the fill character is not printable, JUMP to the ROM error handling at 1997H to raise a syntax error.
5908
XOR C A9
XOR the packed field description in Register C into the biased fill character in Register A.
5909
XOR 3FH EE 3F
XOR 3FH into Register A. Taken together with the previous instruction this replaces the low six bits of Register C with the biased fill character while leaving bits 7 and 6, the numeric and automatic-termination flags, exactly as they were. The two exclusive-ORs cancel out over the flag bits and cancel the initial 3FH over the fill bits.
590B
LD C,A 4F
Copy the completed packed field description back into Register C: bit 7 numeric only, bit 6 terminate when full, bits 5 to 0 the fill character less 20H.
590C
POP AF F1
Restore the field width (Register A and the flags) from the stack, where 58DEH saved it.
590D
LD B,A 47
Copy the field width into Register B, so that Register Pair BC now carries the whole field specification: the width in Register B and the packed description in Register C.
590E
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the semicolon that separates the field description from the string variable that is to receive the input.
5910
JUMP to 5922H, the shared body that paints the fill characters across the field, collects the keystrokes under the restrictions in Register C and assigns the result to the string variable. LINE INPUT enters the same body from 5912H.

5912H - LINE INPUT

Reached through vector slot 41A3H, the LINE token. LINE must be followed by INPUT, and the statement then reads a whole line into a single string variable without treating commas or quotation marks as separators. A hash mark after INPUT diverts to the file form. The body from 5922H is shared with INPUT@: the two differ only in whether Register B arrives holding a field width or zero.

5912
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 89H, the token for INPUT. LINE on its own is not a statement, so anything else raises a syntax error here.
5914
GOSUB to the ROM routine ERRDIR at 2828H, which raises ILLEGAL DIRECT if the statement was typed at the prompt rather than run from a program.
5917
LD A,(HL) 7E
Fetch the character after the INPUT token into Register A.
5918
CP 23H FE 23
Compare it against 23H (ASCII: #), which introduces LINE INPUT#n, reading a whole record from a disk file.
591A
If the Z FLAG has been set, JUMP to 5D5DH, the file form of LINE INPUT.
591D
GOSUB into the ROM's INPUT routine at 21CDH, the entry that handles the optional prompt string and the semicolon that suppresses the question mark, leaving Register Pair HL on the variable name.
5920
LD B,00H 06 00
Load Register B with zero, the field width. A width of zero is what distinguishes a plain LINE INPUT from an INPUT@ with a field specification, and it selects the ROM's own line collector at 592FH.
5922
PUSH BC C5
Shared Input Body
Save the field specification (Register Pair BC) onto the stack: the width in Register B and, for INPUT@, the packed description in Register C. INPUT@ enters here from 5910H with both set; LINE INPUT arrives with Register B zero.
5923
GOSUB to the ROM routine PTRGET at 260DH to locate or create the destination variable, returning its address in Register Pair DE.
5926
GOSUB to the ROM routine CHKSTR at 0AF4H, which raises TYPE MISMATCH unless the destination is a string variable. Both LINE INPUT and INPUT@ always deliver a string.
5929
POP BC C1
Restore the field specification (Register Pair BC) from the stack, since PTRGET destroyed it.
592A
PUSH DE D5
Save the destination variable's address (Register Pair DE) onto the stack.
592B
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack, above the variable address.
592C
PUSH BC C5
Save the field specification (Register Pair BC) onto the stack again, because the routine that follows may destroy it.
592D
INC B 04
INCrement Register B by 1. This and the DEC that follows are a pair used purely to set the flags from Register B without disturbing Register Pair BC, which a plain OR or AND could not do without a spare register.
592E
DEC B 05
DECrement Register B by 1, restoring the field width and leaving the Z FLAG set when it is zero.
592F
If the Z FLAG has been set, meaning no field width was given, GOSUB to the ROM routine INLIN at 0361H, which collects an ordinary line of up to 240 characters into the buffer at BUFPNT 40A7H and returns with the CARRY FLAG set if BREAK was pressed.
5932
POP BC C1
Restore the field specification (Register Pair BC) from the stack.
5933
INC B 04
INCrement Register B by 1, again only to set the flags.
5934
DEC B 05
DECrement Register B by 1, restoring the width and setting the Z FLAG when it is zero.
5935
If the NZ FLAG has been set, meaning a field width was given, GOSUB to 5949H, the fielded input collector, which paints the field and collects the keystrokes under the restrictions packed into Register C. Exactly one of this call and the one at 592FH is taken.
5938
POP DE D1
Restore the BASIC text pointer from the stack into Register Pair DE. It was pushed from Register Pair HL at 592BH, so the register pairs are being swapped through the stack.
5939
POP BC C1
Restore the destination variable's address from the stack into Register Pair BC. It was pushed from Register Pair DE at 592AH.
593A
If the CARRY FLAG has been set, which either collector does when the operator pressed BREAK, JUMP into the ROM's END routine at 1DBEH to stop the program in the ordinary way.
593D
PUSH BC C5
Save the destination variable's address (Register Pair BC) onto the stack, where the ROM assignment routine at 1F32H expects to find it.
593E
PUSH DE D5
Save the BASIC text pointer (Register Pair DE) onto the stack above it.
593F
LD B,00H 06 00
Load Register B with zero, the terminator byte the string builder is to stop on. A zero rather than a comma or quotation mark is exactly what makes LINE INPUT take the whole line literally.
5941
GOSUB into the ROM's STRLIT routine at 2868H, the entry that builds a string descriptor for the text at Register Pair HL, stopping on the terminator in Register B.
5944
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack, leaving the variable address on top for the assignment.
5945
XOR A AF
Set Register A to ZERO and clear all flags, the state the assignment routine expects.
5946
JUMP into the ROM at 1F32H, which assigns the string just built to the variable whose address is on the stack and RETurns to the interpreter. This does not return here.

5949H - Fielded Input Collector

Called from 5935H when INPUT@ supplied a field width. It runs the field editor, then terminates the collected text in the buffer. Register B holds the field width and Register C the packed description that 58E3H onwards built.

5949
LD HL,(40A7H) 2A A7 40
Fetch BUFPNT from 40A7H into Register Pair HL, the keyboard line buffer that initialization placed at 5309H and which will receive the typed characters.
594C
GOSUB to 5960H, the field editor, which paints the field with the fill character, collects keystrokes into the buffer under the restrictions in Register C, and returns with Register B holding the number of characters entered and the CARRY FLAG set if BREAK was pressed.
594F
PUSH AF F5
Save Register A and the flags onto the stack, because the CARRY FLAG is the BREAK indication that the caller at 593AH tests and the instructions that follow would destroy it.
5950
PUSH HL E5
Save the buffer address (Register Pair HL) onto the stack.
5951
LD C,B 48
Copy the count of characters actually entered (Register B) into Register C. 5A00H sets Register B to that count on the way out of the field editor, and the packed description is finished with, so Register C is free to become the low half of a 16-bit offset.
5952
LD B,00H 06 00
Load Register B with zero, so Register Pair BC holds the field width as a 16-bit value.
5954
ADD HL,BC 09
ADD the entered length (Register Pair BC) to the buffer address (Register Pair HL), giving the byte just past the last character the operator typed.
5955
INC C 0C
INCrement Register C by 1, the first half of the flag-setting pair.
5956
DEC C 0D
DECrement Register C by 1, restoring the count and setting the Z FLAG only when nothing at all was entered.
5957
If the NZ FLAG has been set, meaning at least one character was entered, JUMP to 595BH to plant the terminator.
5959
LD (HL),A 77
Store Register A at the buffer address. This is the empty-entry case, where the operator pressed ENTER without typing anything.
595A
INC HL 23
INCrement Register Pair HL by 1, so the terminator that follows lands one byte higher.
595B
LD (HL),B 70
Store Register B, which is zero, after the entered text. This is the terminator the string builder at 5941H stops on, so the string handed to the variable is as long as what was typed rather than the full field width.
595C
POP HL E1
Restore the buffer address (Register Pair HL) from the stack.
595D
DEC HL 2B
DECrement Register Pair HL by 1, positioning it one byte below the text so that the ROM's string builder, whose fetch increments first, starts on the first character.
595E
POP AF F1
Restore Register A and the flags from the stack, bringing back the CARRY FLAG that reports whether BREAK was pressed.
595F
RET C9
RETurn to 5938H with the CARRY FLAG carrying the BREAK indication.

5960H - Field Editor

The heart of INPUT@. It paints the field with the fill character so the operator can see how much room there is, backs the cursor up to the start, and then runs a keystroke loop that accepts characters, honours backspace and erase-line, and ends on ENTER or BREAK. The fill character is planted into a self-modifying operand rather than kept in a register, because every register is spoken for.

5960
PUSH HL E5
Save the buffer address (Register Pair HL) onto the stack.
5961
PUSH BC C5
Save the field specification (Register Pair BC) onto the stack: the width in Register B and the packed description in Register C.
5962
LD A,C 79
Copy the packed field description (Register C) into Register A to extract the fill character from it.
5963
AND 3FH E6 3F
Mask Register A to its low six bits, discarding bit 7, the numeric-only flag, and bit 6, the terminate-when-full flag, and leaving the biased fill character.
5965
ADD A,20H C6 20
ADD 20H to Register A, undoing the bias that 5901H applied and giving the fill character itself. The default packed value 3FH becomes 5FH, the underline.
5967
LD (5A28H),A 32 28 5A
Self-Modifying Code
Store the fill character (Register A) into 5A28H, the operand of the LD A,nn at 5A27H. That instruction is the front of the display helper called immediately below, so each call now emits the chosen fill character.
596A
Loop Start
GOSUB to 5A27H, which loads Register A with the fill character from the operand just patched and sends it to the display, advancing the cursor one position.
596D
DECrement Register B and LOOP BACK to 596AH if not zero, painting one fill character for every position in the field. Loop End
596F
POP BC C1
Restore the field specification (Register Pair BC) from the stack, since the painting loop counted Register B down to zero.
5970
PUSH BC C5
Save it again immediately, because the loop that follows will count Register B down as well.
5971
Loop Start
GOSUB to 5A04H, which moves the cursor one position to the left.
5974
DECrement Register B and LOOP BACK to 5971H if not zero, walking the cursor back to the first position of the field now that the operator can see its full extent. Loop End
5976
POP BC C1
Restore the field specification (Register Pair BC) from the stack.
5977
PUSH BC C5
Save it once more, so the packed description in Register C survives the keystroke loop and the width is available if the field has to be repainted.
5978
LD C,B 48
Copy the field width (Register B) into Register C, which becomes the count of positions still free.
5979
LD A,0EH 3E 0E
Loop Start
Load Register A with 0EH, the video control code that turns the cursor on. Every pass through the keystroke loop re-enables the cursor, because some of the editing keys switch it off.
597B
GOSUB to the ROM display routine $DSP at 0033H to send the cursor-on code through the video device control block at 401DH.
597E
GOSUB to the ROM routine $KEY at 0049H, which scans the keyboard in a loop until a key is pressed and returns its code in Register A.
5981
CP 20H FE 20
Compare the key code against 20H, the ASCII space. Anything at or above it is an ordinary printable character; below it is a control key.
5983
If the NO CARRY FLAG has been set, meaning a printable character, JUMP to 59B2H to test it against the numeric-only restriction and store it.
5985
CP 0DH FE 0D
Compare the key code against 0DH, the ENTER key, which ends the input normally.
5987
If the Z FLAG has been set, JUMP to 59E2H to finish, leaving the CARRY FLAG clear so the caller knows the entry was completed.
5989
DEC A 3D
DECrement Register A by 1. From here on the tests are made against one less than the true key code, which lets a single DEC serve both as the BREAK test and as the bias for the two editing keys.
598A
SCF 37
Set the Carry flag, which is the caller's BREAK indication. SCF does not disturb the Z FLAG, so the result of the DEC above is still available to the jump below.
598B
If the Z FLAG has been set, meaning the key code was 01H, the BREAK key, JUMP to 59E2H to finish with the CARRY FLAG set.
598D
LD DE,5979H 11 79 59
Load Register Pair DE with 5979H, the top of the keystroke loop.
5990
PUSH DE D5
Push that address onto the stack as a return address, so that every path below reaches the top of the loop by executing a RET. This is what allows the RET NZ at 5997H to serve as "ignore this key and carry on".
5991
CP 07H FE 07
Compare the decremented key code against 07H, so the true code is 08H, the left arrow, which erases the character before the cursor.
5993
If the Z FLAG has been set, JUMP to 599EH to erase one character. The pushed return address takes it back to the top of the loop afterwards.
5995
CP 17H FE 17
Compare the decremented key code against 17H, so the true code is 18H, shifted left arrow, which erases the whole field back to its start.
5997
RET NZ C0
If the NZ FLAG has been set, meaning the key is a control code the editor does not recognise, RETurn. Because 5990H pushed the address of the loop top, this quietly ignores the key and waits for the next one.
5998
Loop Start
GOSUB to 599EH to erase one character, returning with the Z FLAG set once the cursor has reached the start of the field.
599B
If the NZ FLAG has been set, meaning characters remain, LOOP BACK to 5998H to erase the next one. Loop End
599D
RET C9
RETurn, which reaches the top of the keystroke loop at 5979H through the address pushed at 5990H.

599EH - Erase One Character

The backspace action of the field editor, called once for the left arrow and repeatedly from 5998H for the shifted left arrow. Erasing means putting the fill character back, not writing a space, so the field stays visibly the size it was. Register B counts the positions still free and Register C the field width, so the two are equal exactly when nothing has been typed.

599E
LD A,B 78
Copy the number of free positions (Register B) into Register A for comparison.
599F
CP C B9
Compare it against the field width (Register C). They are equal only when no character has been accepted yet, in which case there is nothing to erase and the Z FLAG is set.
59A0
RET Z C8
If the Z FLAG has been set, RETurn. The erase-whole-field loop at 599BH tests this same flag to know when it has reached the start of the field.
59A1
LD A,0FH 3E 0F
Load Register A with 0FH, the video control code that turns the cursor off, so that the repainting that follows is not seen as a flicker.
59A3
GOSUB to the ROM display routine $DSP at 0033H to send the cursor-off code.
59A6
GOSUB to 5A04H to move the cursor one position to the left, onto the character that is to be erased.
59A9
GOSUB to 5A27H to write the fill character, whose value 5967H patched into that routine's operand. Writing it advances the cursor one position to the right again.
59AC
GOSUB to 5A04H again to move the cursor back to the left, leaving it on the position just cleared and ready for the next keystroke.
59AF
DEC HL 2B
DECrement Register Pair HL by 1, backing the buffer pointer over the character that has been erased.
59B0
INC B 04
INCrement Register B by 1, returning one position to the free count. This also leaves the NZ FLAG set, which is what tells the erase-whole-field loop at 599BH to keep going.
59B1
RET C9
RETurn to the caller.

59B2H - Accept a Printable Character

Reached from 5983H when the key pressed is 20H or above. The character must survive three tests before it is stored: it must not be in the graphics and token range, there must be room left in the field, and if the field was declared numeric it must be a digit, a sign or a decimal point. Every rejection simply jumps back to the keystroke loop, so an unwanted key is silently ignored rather than beeping or erroring.

59B2
CP 80H FE 80
Compare the key code (Register A) against 80H. Codes from 80H upward are the block graphics and space compression codes, which have no place in a typed field.
59B4
If the NO CARRY FLAG has been set, meaning the code is 80H or above, LOOP BACK to 5979H and wait for another key.
59B6
INC B 04
INCrement Register B by 1, the first half of the flag-setting pair.
59B7
DEC B 05
DECrement Register B by 1, restoring the free-position count and setting the Z FLAG when the field is already full.
59B8
If the Z FLAG has been set, meaning the field is full, LOOP BACK to 5979H and ignore the key. A full field that was not declared to terminate automatically simply refuses further characters until ENTER or a backspace.
59BA
POP DE D1
Pop the copy of the field specification that 5977H saved into Register Pair DE, so that Register E holds the packed description.
59BB
PUSH DE D5
Push it straight back, leaving the stack as it was. This POP and PUSH pair is how the routine peeks at a saved value without a spare register.
59BC
BIT 7,E CB 7B
Test bit 7 of the packed field description in Register E, which 58F0H set when the description began with a hash mark, meaning numeric input only.
59BE
If the Z FLAG has been set, meaning the field accepts any printable character, JUMP to 59D4H to store it.
59C0
CP 3AH FE 3A
Compare the key code against 3AH, the character after 9. A code at or above this is past the digits.
59C2
If the NO CARRY FLAG has been set, meaning the character is above 9, LOOP BACK to 5979H and ignore it.
59C4
CP 30H FE 30
Compare the key code against 30H (ASCII: 0).
59C6
If the NO CARRY FLAG has been set, the character is between 0 and 9, so JUMP to 59D4H to store it.
59C8
CP 2DH FE 2D
Compare the key code against 2DH (ASCII: -), which a numeric field accepts as a sign.
59CA
If the Z FLAG has been set, JUMP to 59D4H to store the minus sign.
59CC
CP 2EH FE 2E
Compare the key code against 2EH (ASCII: .), the decimal point.
59CE
If the Z FLAG has been set, JUMP to 59D4H to store the decimal point.
59D0
CP 2BH FE 2B
Compare the key code against 2BH (ASCII: +), the remaining sign character.
59D2
If the NZ FLAG has been set, meaning the character is none of the four a numeric field allows, LOOP BACK to 5979H and ignore it. The set the routine accepts is digits, plus, minus and period, exactly as the manual documents.
59D4
LD (HL),A 77
Store the accepted character (Register A) into the input buffer at the address in Register Pair HL.
59D5
GOSUB to the ROM display routine $DSP at 0033H to show the character, which replaces the fill character at that position and advances the cursor.
59D8
INC HL 23
INCrement Register Pair HL by 1, advancing the buffer pointer.
59D9
DECrement Register B, the free-position count, and LOOP BACK to 5979H if the field is not yet full. Only when the last position has been filled does execution continue below.
59DB
POP DE D1
Pop the saved field specification into Register Pair DE to examine it again.
59DC
PUSH DE D5
Push it straight back, leaving the stack unchanged.
59DD
BIT 6,E CB 73
Test bit 6 of the packed field description, which 58FBH set when the description contained an asterisk, meaning the input is to end as soon as the field is full.
59DF
If the Z FLAG has been set, meaning automatic termination was not requested, LOOP BACK to 5979H to wait for ENTER. Further printable keys are now rejected by the full-field test at 59B8H, but backspace still works.
59E1
CP A BF
Compare Register A with itself, which sets the Z FLAG and clears the CARRY FLAG. The cleared CARRY FLAG is the point: it tells the caller that the entry completed normally rather than being broken off.

59E2H - Finish the Field

The common exit of the field editor, reached on ENTER from 5987H, on BREAK from 598BH with the CARRY FLAG set, and by falling through from 59E1H when a full field terminated itself. It clears the remaining fill characters so the display shows only what was typed, brings the ROM's column tracker back into step, and converts the free-position count into the number of characters actually entered.

59E2
POP DE D1
Discard the copy of the field specification that 5977H saved, into Register Pair DE. It is not needed again.
59E3
PUSH AF F5
Save Register A and the flags onto the stack. The CARRY FLAG is the BREAK indication and must survive everything that follows.
59E4
PUSH BC C5
Save the width and free-position counts (Register Pair BC) onto the stack across the blanking loop, which counts Register B down.
59E5
LD A,0FH 3E 0F
Load Register A with 0FH, the video control code that turns the cursor off.
59E7
GOSUB to the ROM display routine $DSP at 0033H to send it, so the cursor does not sit blinking in the middle of a finished field.
59EA
INC B 04
INCrement Register B by 1, the first half of the flag-setting pair.
59EB
DEC B 05
DECrement Register B by 1, restoring the free-position count and setting the Z FLAG when the field was filled exactly.
59EC
If the Z FLAG has been set, meaning no positions are left over, JUMP to 59F5H because there is nothing to blank.
59EE
LD A,20H 3E 20
Loop Start
Load Register A with 20H, an ASCII space, the character that replaces the unused fill positions.
59F0
GOSUB to the ROM display routine $DSP at 0033H to send the space, wiping one leftover fill character and advancing the cursor.
59F3
DECrement Register B and LOOP BACK to 59EEH if not zero, blanking every position the operator did not use. Loop End
59F5
POP BC C1
Restore the width and free-position counts (Register Pair BC) from the stack, since the blanking loop destroyed Register B.
59F6
LD A,(4020H) 3A 20 40
Fetch the low byte of the video driver's cursor address from 4020H into Register A. 4020H is the cursor field of the display device control block at 401DH and holds the address in video RAM at which the next character will be written.
59F9
AND 3FH E6 3F
Mask Register A to its low six bits, converting the cursor address into a column number 0 to 63, because each screen row is 64 bytes.
59FB
LD (40A6H),A 32 A6 40
Store the column (Register A) into TTYPOS 40A6H, the ROM's cursor column tracker. The field editor moved the cursor with its own code, so without this the ROM's comma and TAB positioning would be working from a stale column.
59FE
LD A,C 79
Copy the field width (Register C) into Register A.
59FF
SUB B 90
SUBtract the free-position count (Register B), giving the number of characters the operator actually entered.
5A00
LD B,A 47
Copy that count into Register B, which is how the caller at 5951H learns how long the entered string is. This is why the string handed to the variable is the length typed, not the full field width.
5A01
POP AF F1
Restore Register A and the flags from the stack, bringing back the CARRY FLAG that reports whether BREAK ended the entry.
5A02
POP HL E1
Restore the buffer address (Register Pair HL) that 5960H saved on entry.
5A03
RET C9
RETurn to 594FH with Register B holding the number of characters entered and the CARRY FLAG holding the BREAK indication.

5A04H - Move the Cursor Left

The display driver's own backspace moves the cursor left but will not step it back up to the end of the previous row, so the field editor has to detect that case itself and adjust the cursor address in the device control block before sending the control code. The port FFH shadow at 403DH is consulted because in the 32-character wide display mode each character occupies two screen positions.

5A04
PUSH HL E5
Save the buffer pointer (Register Pair HL) onto the stack, because Register Pair HL is needed for the cursor address.
5A05
LD HL,(4020H) 2A 20 40
Fetch the video driver's cursor address from 4020H into Register Pair HL, the position in video RAM at which the next character would be written.
5A08
GOSUB to 5A1BH, which does nothing unless the cursor is at the left margin, in which case it moves the cursor address back one whole row and writes it to 4020H.
5A0B
DEC HL 2B
DECrement Register Pair HL by 1, the local copy of the cursor address, so the margin test below is made against the position the cursor is about to occupy.
5A0C
LD A,(403DH) 3A 3D 40
Fetch the port FFH shadow byte from 403DH into Register A. Port FFH is write-only on the Model I, so the ROM keeps a copy of the last value written here; it carries the cassette motor state and the video mode.
5A0F
AND 08H E6 08
Mask Register A to bit 3, the 32-character wide display mode. In that mode each character occupies two positions in video RAM, so a single backspace has to step back twice as far.
5A11
If the NZ FLAG has been set, meaning the wide display mode is active, GOSUB to 5A1BH again to repeat the margin test at the new address.
5A14
LD A,18H 3E 18
Load Register A with 18H, the video control code for a backspace, which is what actually moves the visible cursor.
5A16
GOSUB to the ROM display routine $DSP at 0033H to send it through the display device control block at 401DH.
5A19
POP HL E1
Restore the buffer pointer (Register Pair HL) from the stack.
5A1A
RET C9
RETurn to the caller.

5A1BH - Wrap the Cursor to the Previous Row

Two entry points. 5A1BH tests whether the cursor is at the left margin and moves it back one row if it is; 5A22H is entered directly from 58A8H by INPUT@ to add an arbitrary offset in Register Pair DE to the screen base and install the result. Both end by writing the cursor address into the display device control block.

5A1B
LD A,L 7D
Copy the low byte of the cursor address (Register L) into Register A, to work out which column it represents.
5A1C
AND 3FH E6 3F
Mask Register A to its low six bits, the column within the row. The Z FLAG is set only at column 0, the left margin.
5A1E
RET NZ C0
If the NZ FLAG has been set, meaning the cursor is not at the left margin, RETurn without changing anything. The display driver's own backspace can handle that case unaided.
5A1F
LD DE,0FFC0H 11 C0 FF
Load Register Pair DE with 0FFC0H, which is minus 64, one screen row.
5A22
ADD HL,DE 19
ADD Register Pair DE to Register Pair HL. On the margin path this moves the cursor back one row; entered here from 58A8H it adds the INPUT@ screen position to the video base 3C00H that the caller loaded.
5A23
LD (4020H),HL 22 20 40
Store the resulting address (Register Pair HL) into 4020H, the cursor field of the display device control block, so the next character the driver writes appears there.
5A26
RET C9
RETurn to the caller.

5A27H - Display the Fill Character

Two instructions, one of which is a self-modifying operand. Keeping the fill character here rather than in a register is what lets the painting loop at 596AH and the erase routine at 59A9H share it without either of them having a register to spare.

5A27
LD A,5FH 3E 5F
Self-Modifying Code
Load Register A with the fill character held in the operand at 5A28H. 5967H writes the character the field description asked for into that byte before the field is painted. The assembled value 5FH is the underline, which is also what the default packed description 3FH decodes to.
5A29
JUMP to the ROM display routine $DSP at 0033H to send the character and advance the cursor. Because this is a JP rather than a CALL, the display routine RETurns directly to whoever called 5A27H.

5A2CH - LIST and INPUT Assignment Exit

Reached through vector slot 41DFH, which the ROM CALLs from two quite different places: 2278H, just after an INPUT statement has assigned a value, and 2B44H, in the middle of LIST. LBASIC needs to behave differently in each case and the ROM gives it nothing to distinguish them, so the routine works it out by comparing its own return address against the start of the LIST routine. It only does any of this when a disk file is current; otherwise it steps aside at once.

5A2C
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack while the current-file test is made.
5A2D
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL. It is 0000H unless a PRINT#, INPUT# or LIST to a file has set it.
5A30
LD A,H 7C
Copy the high byte of the pointer into Register A.
5A31
OR L B5
OR the low byte into it. The Z FLAG is set only when the whole pointer is zero, meaning no file is current.
5A32
POP HL E1
Restore the BASIC text pointer (Register Pair HL). POP does not disturb the flags, so the test result survives.
5A33
RET Z C8
If the Z FLAG has been set, meaning no file is current, RETurn to the ROM and let it carry on exactly as it would without Disk BASIC.
5A34
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, which puts the caller's return address into Register Pair HL and the BASIC text pointer onto the stack. That return address is the only evidence of which ROM routine called this vector.
5A35
PUSH DE D5
Save Register Pair DE onto the stack across the comparison.
5A36
LD DE,2B2EH 11 2E 2B
Load Register Pair DE with 2B2EH, the first address of the ROM's LIST routine. Everything at or above it belongs to LIST, and the INPUT caller at 2278H lies below it.
5A39
GOSUB through the RAM hook at 4006H to the ROM's DCOMPR routine at 1C90H, comparing Register Pair DE against the return address in Register Pair HL. The CARRY FLAG is set when the return address is below the start of LIST.
5A3A
POP DE D1
Restore Register Pair DE from the stack.
5A3B
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack. The caller's return address is discarded along with it, because neither destination below returns to it.
5A3C
If the NO CARRY FLAG has been set, meaning the call came from within LIST, JUMP into the ROM's LIST routine at 2B4AH to continue listing, with the current file as the destination.
5A3F
JUMP into the ROM at 2282H, the continuation of the INPUT and READ routines, to carry on assigning values from the file.

5A42H - Long Error Message Handler

Reached through vector slot 41A6H, the first of the twenty-one stubs the ROM leaves as bare RETs on a cassette machine. Level II BASIC reports errors as two-letter abbreviations; Disk BASIC spells them out. The message text is too bulky to keep resident, so it lives in BASIC/OV3 and is fetched on demand. The OV3 function selectors travel in the alternate accumulator: 01H expands an error code, 02H translates a DOS error number, 03H sorts an array, and 04H - used only by the RUN"filespec",V path at 5EB0H - parks the variables that are to survive a chained load.

5A42
POP HL E1
Discard the ROM's return address into Register Pair HL. The error is fatal to the statement in progress, so control never goes back to the caller.
5A43
LD A,(65F4H) 3A F4 65
Fetch the chain-state byte at 65F4H into Register A. It is one of the run-time cells inside the tail of the loaded image, and the value 0D6H means a RUN"filespec",V is part-way through, with the retained variables parked above STKTOP and the stack running on a temporary ceiling.
5A46
CP 0D6H FE D6
Compare Register A against 0D6H. If it matches the Z FLAG is set.
5A48
If the NZ FLAG has been set, JUMP to 5A4EH and report the error with the stack as it stands.
5A4A
GOSUB to 5F83H, the tail of the chained-load fixup, which restores STKTOP 40A0H and SAVSTK 40E8H from the values BASIC/OV3 saved, clears the chain-state byte and loads the Stack Pointer itself. IT NEVER COMES BACK: the LD SP,HL at 5F92H discards this CALL's own return address, and 5F93H and 5F96H carry on into the READY exit and the program start.
5A4D
LD SP,HL F9
Load the Stack Pointer from Register Pair HL. THIS INSTRUCTION CAN NEVER BE EXECUTED. The only way to reach it is by returning from the CALL at 5A4AH, and that routine ends by loading the Stack Pointer itself and running on into the interpreter; the NZ path at 5A48H jumps over it to 5A4EH. Document it mechanically and do not read a purpose into it.
5A4E
LD L,E 6B
Copy the BASIC error number (Register E) into Register L, which is where BASIC/OV3 expects to find it. Every ROM error path arrives at this vector with the number in Register E.
5A4F
LD A,01H 3E 01
Load Register A with 01H, the BASIC/OV3 function selector for expanding an error number into its full text.
5A51
EX AF,AF' 08
Exchange Register A and the flags with the alternate set, parking the selector where the overlay loader cannot disturb it. The loader needs the primary accumulator for OVRLY$ and for the load error code.
5A52
GOSUB to the BASIC/OV3 loader at 54B6H, which enters the overlay directly if OVRLY$ 430EH shows it is resident and otherwise loads it to 4E00H first. The overlay returns with Register Pair HL pointing at the message text.
5A55
GOSUB to the ROM routine STROUT at 28A7H to display the message text at Register Pair HL.
5A58
LD HL,(40EAH) 2A EA 40
Fetch ERRLIN from 40EAH into Register Pair HL, the number of the BASIC line in which the error occurred. It holds 0FFFFH when the failing statement was typed at the prompt rather than run from a program.
5A5B
GOSUB to 63EDH, which appends the line number to the message, or suppresses it when the value shows immediate mode.
5A5E
JUMP into the ROM at 1A11H, which returns BASIC to command level and prints the READY prompt. This does not return.

5A61H - TIME$

Reached through vector slot 4176H. TIME$ returns a seventeen-character string holding the date, a space and the time, built directly from the two resident LDOS services rather than from BASIC's own clock. The string is allocated first and the two services are asked to write straight into it, so no intermediate buffer is needed.

5A61
GOSUB through the RAM hook at 4003H to LBASIC's character fetch at 6579H, stepping Register Pair HL past the TIME$ token.
5A62
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack while the string is built.
5A63
LD A,11H 3E 11
Load Register A with 11H, 17 decimal: eight characters of date, one space and eight characters of time.
5A65
GOSUB to the ROM routine STRINI at 2857H, which reserves that many bytes of string space and builds a descriptor for them in DSCTMP at 40D3H.
5A68
LD HL,(40D4H) 2A D4 40
Fetch the address field of that descriptor from 40D4H into Register Pair HL, giving the first byte of the newly reserved string.
5A6B
GOSUB to the resident @DATE vector at 4470H, which writes the system date as eight characters at the address in Register Pair HL and leaves Register Pair HL past them.
5A6E
LD (HL),20H 36 20
Store an ASCII space between the date and the time.
5A70
INC HL 23
INCrement Register Pair HL by 1, past the space.
5A71
GOSUB to the resident @TIME vector at 446DH, which writes the system time as eight characters at the address in Register Pair HL, completing the seventeen.
5A74
JUMP to the ROM routine PUTNEW at 2884H, which registers the descriptor as a live temporary string and RETurns to the expression evaluator with the BASIC text pointer recovered from the stack.

5A77H - INSTR

Reached through vector slot 419DH. INSTR searches one string for another and returns the one-based position of the first match, or zero if there is none. The optional first argument is a starting position, and it is detected by evaluating the first parameter and asking what type it came out as: a string means there was no starting position and the search begins at 1. The result is delivered by pushing two ROM addresses as a chain of return addresses, so that a plain RET from the search loop converts Register A into a BASIC value and tidies up.

5A77
GOSUB through the RAM hook at 4003H to the character fetch at 6579H, stepping past the opening parenthesis of the function call.
5A78
GOSUB to the ROM's expression evaluator FRMPRN at 2335H to evaluate the first parameter, whatever it turns out to be.
5A7B
GOSUB through the RAM hook at 4009H to the ROM's GETYPR routine at 25D9H, which sets the Z FLAG when the value just evaluated is a string.
5A7C
LD A,01H 3E 01
Load Register A with 1, the default starting position, used when no explicit one was given.
5A7E
PUSH AF F5
Save the default starting position (Register A and the flags) onto the stack.
5A7F
If the Z FLAG has been set, meaning the first parameter was a string, JUMP to 5A92H. That string is the one to be searched and the starting position stays at 1.
5A81
POP AF F1
Discard the default starting position, because an explicit one was supplied and is about to be read.
5A82
GOSUB to the ROM routine CONINT at 2B1FH, which converts the value already in the floating-point accumulator to an 8-bit integer in Register A.
5A85
AND A A7
Test the starting position (Register A) against itself. The Z FLAG is set when it is zero.
5A86
If the Z FLAG has been set, meaning a starting position of zero, JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH. Positions are one-based.
5A89
PUSH AF F5
Save the validated starting position (Register A and the flags) onto the stack.
5A8A
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the comma after the starting position.
5A8C
GOSUB to the ROM's expression evaluator at 2337H to evaluate the string that is to be searched.
5A8F
GOSUB to the ROM routine CHKSTR at 0AF4H, which raises TYPE MISMATCH unless that value is a string.
5A92
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the comma before the string to search for. Both forms of the call converge here.
5A94
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack.
5A95
LD HL,(4121H) 2A 21 41
Fetch the contents of FACLO 4121H into Register Pair HL. For a string value the floating-point accumulator holds the address of its descriptor, so this is the descriptor of the string to be searched.
5A98
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, parking the target string's descriptor address on the stack and putting the BASIC text pointer back into Register Pair HL.
5A99
GOSUB to the ROM's expression evaluator at 2337H to evaluate the string being searched for.
5A9C
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the closing parenthesis of the function call.
5A9E
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack.
5A9F
GOSUB to the ROM routine FRESTR at 29D7H, which releases the search string from the temporary area and returns Register Pair HL pointing at its descriptor.
5AA2
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, moving the search string's descriptor address into Register Pair DE.
5AA3
POP BC C1
Restore the BASIC text pointer from the stack into Register Pair BC.
5AA4
POP HL E1
Restore the target string's descriptor address from the stack into Register Pair HL.
5AA5
POP AF F1
Restore the starting position from the stack into Register A.
5AA6
PUSH BC C5
Push the BASIC text pointer back onto the stack, at the bottom of the chain of return addresses being assembled.
5AA7
LD BC,26E7H 01 E7 26
Load Register Pair BC with 26E7H, the ROM routine FINZER, which resets the variable-type state after a function has delivered its value.
5AAA
PUSH BC C5
Push it as the second return address in the chain.
5AAB
LD BC,27F8H 01 F8 27
Load Register Pair BC with 27F8H, the ROM entry that places the byte in Register A into the floating-point accumulator as an integer.
5AAE
PUSH BC C5
Push it as the topmost return address. Every RET in the search below therefore delivers Register A as the function's result and then tidies up, which is why the not-found and found paths can both simply RET.
5AAF
PUSH AF F5
Save the starting position (Register A and the flags) onto the stack across the call below.
5AB0
PUSH DE D5
Save the search string's descriptor address (Register Pair DE) onto the stack.
5AB1
GOSUB into the ROM's FREFAC routine at 29DDH to release the target string from the temporary area as well, leaving Register Pair HL on its descriptor.
5AB4
POP DE D1
Restore the search string's descriptor address (Register Pair DE) from the stack.
5AB5
POP AF F1
Restore the starting position (Register A) from the stack.
5AB6
LD B,A 47
Copy the starting position into Register B, keeping the one-based value for the arithmetic at 5AC2H.
5AB7
DEC A 3D
DECrement Register A by 1, converting the position into a zero-based offset from the start of the target string.
5AB8
LD C,A 4F
Copy that offset into Register C, where it stays for the whole search and is added back at 5AF3H to turn a relative match into an absolute position.
5AB9
CP (HL) BE
Compare the offset against the target string's length, which is the first byte of its descriptor. The NO CARRY FLAG means the starting position is at or past the end of the string.
5ABA
LD A,00H 3E 00
Load Register A with zero, the result meaning "not found". LD does not disturb the flags, so the comparison just made still stands.
5ABC
RET NC D0
If the NO CARRY FLAG has been set, RETurn with zero. The RET goes to 27F8H, the first of the addresses pushed above, which turns Register A into the function's value.
5ABD
LD A,(HL) 7E
Fetch the target string's length into Register A.
5ABE
AND A A7
Test it against itself. The Z FLAG is set for an empty target string.
5ABF
RET Z C8
If the Z FLAG has been set, RETurn. Register A is zero, so an empty target string yields not-found.
5AC0
LD A,(DE) 1A
Fetch the search string's length from its descriptor into Register A.
5AC1
AND A A7
Test it against itself. The Z FLAG is set for an empty search string.
5AC2
LD A,B 78
Copy the starting position (Register B) into Register A, ready to be returned. LD does not disturb the flags.
5AC3
RET Z C8
If the Z FLAG has been set, meaning an empty search string, RETurn the starting position. An empty string is conventionally found immediately wherever the search begins.
5AC4
LD A,(HL) 7E
Fetch the target string's length into Register A again, this time to compute how much of it is left to search.
5AC5
INC HL 23
INCrement Register Pair HL by 1, to the address field of the target descriptor.
5AC6
LD B,(HL) 46
Fetch the low byte of the target string's address into Register B.
5AC7
INC HL 23
INCrement Register Pair HL by 1, to the high half of the address field.
5AC8
LD H,(HL) 66
Fetch the high byte of the target string's address into Register H.
5AC9
LD L,B 68
Move the low byte from Register B into Register L, so Register Pair HL now points at the first character of the target string.
5ACA
LD B,00H 06 00
Load Register B with zero, so Register Pair BC holds the zero-based starting offset as a 16-bit value.
5ACC
ADD HL,BC 09
ADD the offset to the string address, so Register Pair HL points at the character the search is to begin on.
5ACD
SUB C 91
SUBtract the offset from the target length in Register A, giving the number of characters from the starting position to the end of the string.
5ACE
LD B,A 47
Copy that count into Register B, which becomes the number of starting positions still to be tried.
5ACF
PUSH BC C5
Save Register Pair BC onto the stack: Register B the positions remaining and Register C the original offset. Both are needed at 5AF1H to turn a match into an absolute position.
5AD0
PUSH DE D5
Save the search string's descriptor address (Register Pair DE) onto the stack.
5AD1
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, so Register Pair HL addresses the search descriptor and the target position is on the stack.
5AD2
LD C,(HL) 4E
Fetch the search string's length into Register C, the count for the inner comparison loop.
5AD3
INC HL 23
INCrement Register Pair HL by 1, to the address field of the search descriptor.
5AD4
LD E,(HL) 5E
Fetch the low byte of the search string's address into Register E.
5AD5
INC HL 23
INCrement Register Pair HL by 1.
5AD6
LD D,(HL) 56
Fetch the high byte into Register D, so Register Pair DE now points at the first character of the search string.
5AD7
POP HL E1
Restore the target position from the stack into Register Pair HL.
5AD8
PUSH HL E5
Outer Loop Start
Save the current target position (Register Pair HL) onto the stack, so it can be recovered and advanced when this attempt fails.
5AD9
PUSH DE D5
Save the search string's start address (Register Pair DE) onto the stack, since the inner loop advances it.
5ADA
PUSH BC C5
Save the two counts (Register Pair BC) onto the stack: Register B the positions remaining and Register C the search length.
5ADB
LD A,(DE) 1A
Inner Loop Start
Fetch the next character of the search string into Register A.
5ADC
CP (HL) BE
Compare it against the character at the current position in the target string.
5ADD
If the NZ FLAG has been set, meaning the characters differ, JUMP to 5AF6H to abandon this attempt and try the next starting position.
5ADF
INC DE 13
INCrement Register Pair DE by 1, advancing through the search string.
5AE0
DEC C 0D
DECrement Register C by 1, counting off one matched character of the search string.
5AE1
If the Z FLAG has been set, meaning every character of the search string has matched, JUMP to 5AEDH to work out the position.
5AE3
INC HL 23
INCrement Register Pair HL by 1, advancing through the target string.
5AE4
DEC B 05
DECrement Register B by 1, counting off one character of the target string.
5AE5
If the NZ FLAG has been set, meaning target characters remain, LOOP BACK to 5ADBH to compare the next pair. Inner Loop End Falling through means the target ran out before the search string did.
5AE7
POP DE D1
Discard the counts saved at 5ADAH.
5AE8
POP DE D1
Discard the search address saved at 5AD9H.
5AE9
POP BC C1
Discard the target position saved at 5AD8H.
5AEA
POP DE D1
Discard the counts saved at 5ACFH, balancing the stack back to the chain of return addresses.
5AEB
XOR A AF
Set Register A to ZERO and clear all flags, the result meaning the search string was not found.
5AEC
RET C9
RETurn, which reaches 27F8H and delivers zero as the value of INSTR.
5AED
POP HL E1
Match Found
Restore the counts saved at 5ADAH into Register Pair HL, so Register H holds the number of target positions that were still untried when this match began.
5AEE
POP DE D1
Discard the search address saved at 5AD9H.
5AEF
POP DE D1
Discard the target position saved at 5AD8H.
5AF0
POP BC C1
Restore the original counts saved at 5ACFH into Register Pair BC: Register B the total positions to try and Register C the zero-based starting offset.
5AF1
LD A,B 78
Copy the total number of positions to try (Register B) into Register A.
5AF2
SUB H 94
SUBtract the number that were still untried when the match began (Register H), giving the number of positions the search advanced before it succeeded.
5AF3
ADD A,C 81
ADD the zero-based starting offset (Register C), converting the relative advance into an offset from the beginning of the whole string.
5AF4
INC A 3C
INCrement Register A by 1, turning the zero-based offset into the one-based position BASIC reports.
5AF5
RET C9
RETurn, which reaches 27F8H and delivers the position as the value of INSTR.
5AF6
POP BC C1
Mismatch, Try The Next Position
Restore the counts saved at 5ADAH into Register Pair BC, resetting the search length in Register C.
5AF7
POP DE D1
Restore the search string's start address (Register Pair DE) saved at 5AD9H, so the next attempt begins at its first character again.
5AF8
POP HL E1
Restore the target position (Register Pair HL) saved at 5AD8H.
5AF9
INC HL 23
INCrement Register Pair HL by 1, moving the attempt on to the next character of the target string.
5AFA
DEC B 05
DECrement Register B by 1, counting off one starting position.
5AFB
If the Z FLAG has been set, meaning every starting position has been tried, JUMP to 5AEAH to return zero.
5AFD
LOOP BACK to 5AD8H to try the match again from the new position. Outer Loop End

5AFFH - & Prefix, Hexadecimal and Octal Constants

Reached through vector slot 4194H when the expression evaluator meets an ampersand. &H introduces a hexadecimal constant and &O an octal one; an ampersand followed by anything else is taken as octal, so &777 works as well as &O777. Both paths accumulate into Register Pair HL and Register Pair DE alternately, exchanging the two at each step so that the ROM's character fetch always has the text pointer where it expects it. The result is delivered as a 16-bit integer.

5AFF
LD DE,0000H 11 00 00
Load Register Pair DE with zero, the accumulator in which the constant is built. It starts life in Register Pair DE because Register Pair HL is holding the BASIC text pointer.
5B02
GOSUB through the RAM hook at 4003H to LBASIC's character fetch at 6579H, returning the character after the ampersand in Register A.
5B03
CP 4FH FE 4F
Compare Register A against 4FH (ASCII: O), the marker for an octal constant.
5B05
If the Z FLAG has been set, JUMP to 5B2FH to read octal digits, entering past the DEC HL so that the letter is consumed.
5B07
CP 48H FE 48
Compare Register A against 48H (ASCII: H), the marker for a hexadecimal constant.
5B09
If the NZ FLAG has been set, meaning neither letter followed the ampersand, JUMP to 5B2EH, which backs the text pointer up one so the character is read again and then treats the constant as octal.
5B0B
LD B,05H 06 05
Load Register B with 5, the digit counter for the hexadecimal path. It is decremented after each digit is merged and reaching zero raises an overflow, so at most four hexadecimal digits are accepted, which is exactly what fits in sixteen bits.
5B0D
Loop Start
GOSUB through the RAM hook at 4003H to the character fetch at 6579H, returning the next character of the constant in Register A.
5B0E
NOP 00
A single padding byte with no effect, sitting between the character fetch and the classification call.
5B0F
GOSUB to the ROM routine ISLET at 1E3EH, which classifies the character in Register A. As used here the NO CARRY FLAG means an alphabetic character and the CARRY FLAG means anything else, and the two conversion paths below rely on that sense.
5B12
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL holds the accumulated value and Register Pair DE the BASIC text pointer. EX does not disturb the flags, so the classification survives.
5B13
If the NO CARRY FLAG has been set, meaning an alphabetic character, JUMP to 5B1BH, where a single subtraction converts A to F into 10 to 15.
5B15
CP 3AH FE 3A
Compare the character against 3AH, the code just above 9. Anything at or above it is neither a digit nor a letter and therefore ends the constant.
5B17
If the NO CARRY FLAG has been set, JUMP to 5B4BH to deliver the value accumulated so far. The character that ended the constant is left for the expression evaluator.
5B19
ADD A,07H C6 07
ADD 7 to Register A. The subtraction below is written for letters, so a digit is nudged up by the gap between 9 and A first, letting one instruction serve both cases.
5B1B
SUB 37H D6 37
SUBtract 37H from Register A, converting A to F into 10 to 15 directly, and a digit into 0 to 9 by way of the addition just made.
5B1D
CP 10H FE 10
Compare the converted value against 16. Anything at or above it came from a letter beyond F and is not a hexadecimal digit.
5B1F
If the NO CARRY FLAG has been set, JUMP to 5B4BH to deliver the value accumulated so far.
5B21
ADD HL,HL 29
ADD Register Pair HL to itself, doubling the accumulated value. This is the first of four such steps, which together shift it left by one hexadecimal digit.
5B22
ADD HL,HL 29
Double the accumulated value again.
5B23
ADD HL,HL 29
Double it again.
5B24
ADD HL,HL 29
Double it a fourth time, so the accumulated value has been multiplied by sixteen and the low four bits of Register L are clear.
5B25
OR L B5
OR Register L into the new digit in Register A. Because the shifting left Register L's low nibble clear, this merges the digit into the bottom of the accumulator without disturbing anything else.
5B26
LD L,A 6F
Copy the merged byte back into Register L, completing the accumulation of this digit.
5B27
DEC B 05
DECrement Register B by 1, counting off one of the five permitted digit positions.
5B28
If the Z FLAG has been set, meaning a fifth digit has been merged, JUMP to 07B2H in the ROM, the entry reached when a value will not fit. The octal path at 5B3FH reaches the same address on a carry out of the top of the accumulator.
5B2B
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, putting the BASIC text pointer back into Register Pair HL for the next character fetch.
5B2C
LOOP BACK to 5B0DH for the next hexadecimal digit. Loop End
5B2E
DEC HL 2B
Octal Path
DECrement Register Pair HL by 1, backing the text pointer over the character that was neither H nor O, so that the fetch below reads it again as the first octal digit.
5B2F
Loop Start
GOSUB through the RAM hook at 4003H to the character fetch at 6579H. It returns the character in Register A and sets the CARRY FLAG when that character is a digit, which is the test the octal path relies on.
5B30
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL holds the accumulated value and Register Pair DE the text pointer.
5B31
If the NO CARRY FLAG has been set, meaning the character is not a digit, JUMP to 5B4BH to deliver the value accumulated so far.
5B33
CP 38H FE 38
Compare the character against 38H (ASCII: 8). Octal admits only 0 to 7.
5B35
If the NO CARRY FLAG has been set, meaning the digit is 8 or 9, JUMP to the ROM error handling at 1997H to raise a syntax error. Note that the hexadecimal path merely ends the constant on a bad character where the octal path treats it as an error.
5B38
ADD HL,HL 29
ADD Register Pair HL to itself, the first of three doublings that shift the accumulator left by one octal digit.
5B39
If the CARRY FLAG has been set, meaning a bit was shifted out of the top of the accumulator, JUMP to 5B3FH to raise the overflow. Each doubling is checked separately because the carry does not survive the next one.
5B3B
ADD HL,HL 29
Double the accumulated value a second time.
5B3C
If the CARRY FLAG has been set, JUMP to 5B3FH to raise the overflow.
5B3E
ADD HL,HL 29
Double the accumulated value a third time, completing the multiplication by eight. The carry from this one is tested by the instruction that follows rather than by a jump.
5B3F
If the CARRY FLAG has been set, JUMP to 07B2H in the ROM, the entry reached when a value will not fit in sixteen bits. The two jumps above target this instruction, so all three carry tests converge here.
5B42
LD B,00H 06 00
Load Register B with zero, so that Register Pair BC will hold the digit as a 16-bit value.
5B44
SUB 30H D6 30
SUBtract 30H from the character in Register A, converting the ASCII digit to the binary value 0 to 7.
5B46
LD C,A 4F
Copy the digit value into Register C.
5B47
ADD HL,BC 09
ADD the digit (Register Pair BC) to the shifted accumulator (Register Pair HL). An addition is used rather than the OR of the hexadecimal path because three doublings leave only three low bits clear, which is exactly what an octal digit needs.
5B48
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, putting the BASIC text pointer back into Register Pair HL.
5B49
LOOP BACK to 5B2FH for the next octal digit. Loop End The octal path has no digit counter, relying entirely on the carry tests to catch a value that grows too large.
5B4B
GOSUB to the ROM routine MAKINT at 0A9AH, which stores the 16-bit value in Register Pair HL into the floating-point accumulator and sets VALTYP 40AFH to 02H, the integer type. Both paths arrive here with the accumulated constant in Register Pair HL.
5B4E
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, restoring the BASIC text pointer into Register Pair HL, positioned on the character that ended the constant.
5B4F
RET C9
RETurn to the expression evaluator with the constant in the floating-point accumulator.

5B50H - MID$ on the Left of an Assignment

Reached through vector slot 41D9H, which the ROM enters with a JP from 2AECH rather than a CALL. That makes it the only one of the Disk BASIC exits with no return address of its own, which is why this routine builds its own by planting the ROM's FINZER address on the stack before it starts copying. MID$(A$,n,m) = expr overwrites part of a string in place instead of building a new one, so the target's characters must be somewhere writable; 5B66H-5B6FH sees to that before anything is changed. The number of characters actually replaced is the smallest of the requested length, the room left in the target from position n, and the length of the source.

5B50
INC HL 23
INCrement Register Pair HL by 1, stepping the BASIC text pointer past the MID$ token.
5B51
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 28H (ASCII: ().
5B53
GOSUB to the ROM routine PTRGET at 260DH to locate the target variable, returning the address of its descriptor in Register Pair DE.
5B56
GOSUB to the ROM routine CHKSTR at 0AF4H, which raises TYPE MISMATCH unless the target is a string variable.
5B59
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack.
5B5A
PUSH DE D5
Save the target descriptor's address (Register Pair DE) onto the stack above it.
5B5B
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL addresses the target's three-byte descriptor.
5B5C
INC HL 23
INCrement Register Pair HL by 1, past the length byte to the address field of the descriptor.
5B5D
LD E,(HL) 5E
Fetch the low byte of the target string's data address into Register E.
5B5E
INC HL 23
INCrement Register Pair HL by 1, to the high half of the address field.
5B5F
LD D,(HL) 56
Fetch the high byte into Register D, so Register Pair DE now holds the address at which the target string's characters live.
5B60
LD HL,(40A0H) 2A A0 40
Fetch STKTOP from 40A0H into Register Pair HL, the top of string space and therefore the upper boundary of the area in which strings may be modified.
5B63
GOSUB through the RAM hook at 4006H to the ROM's DCOMPR routine at 1C90H, comparing Register Pair HL, the top of string space, against Register Pair DE, the target's data address. The CARRY FLAG is set when the data lies above the top of string space.
5B64
If the CARRY FLAG has been set, JUMP to 5B70H, skipping the two calls that give the target a private copy of its characters.
5B66
POP HL E1
Restore the target descriptor's address (Register Pair HL) from the stack.
5B67
PUSH HL E5
Push it straight back, the peek idiom, leaving the stack unchanged while giving the call below the address it needs.
5B68
GOSUB into the ROM's string routines at 2843H, which reserves room in string space for a copy of the target string. Modifying a string in place is only safe once its characters are in string space rather than pointing at program text, which is shared and must not be altered.
5B6B
POP HL E1
Restore the target descriptor's address (Register Pair HL) from the stack.
5B6C
PUSH HL E5
Push it straight back again, so the block move below has it while the stack stays balanced.
5B6D
GOSUB to the ROM block move at 09D3H, which copies the string's bytes into the space just reserved and updates the descriptor to point at the copy.
5B70
POP HL E1
Restore the target descriptor's address (Register Pair HL) from the stack.
5B71
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, putting the BASIC text pointer back into Register Pair HL and leaving the target descriptor's address on the stack, where it stays until 5B8FH.
5B72
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the comma before the starting position.
5B74
GOSUB to the ROM routine GETBYT at 2B1CH, which evaluates the starting position and returns it in Register A as an 8-bit value.
5B77
AND A A7
Test the starting position against itself. The Z FLAG is set when it is zero.
5B78
If the Z FLAG has been set, JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH. Positions within a string are one-based.
5B7B
PUSH AF F5
Save the starting position (Register A and the flags) onto the stack.
5B7C
LD A,(HL) 7E
Fetch the character after the starting position into Register A, which the parser below uses to decide whether a length was given.
5B7D
GOSUB to 5BC3H to read the optional length and the closing parenthesis, returning the length in Register E, or 0FFH when none was given.
5B80
PUSH DE D5
Save the length (Register Pair DE, in Register E) onto the stack.
5B81
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the character at Register Pair HL to be 0D5H, the token for the equals sign.
5B83
GOSUB to the ROM's expression evaluator at 2337H to evaluate the source expression on the right of the equals sign.
5B86
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack.
5B87
GOSUB to the ROM routine FRESTR at 29D7H, which releases the source string from the temporary area and returns Register Pair HL pointing at its descriptor. A type mismatch is raised here if the source was not a string.
5B8A
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, moving the source descriptor's address into Register Pair DE.
5B8B
POP HL E1
Restore the BASIC text pointer (Register Pair HL) from the stack.
5B8C
POP BC C1
Restore the length saved at 5B80H into Register Pair BC, so Register C holds it.
5B8D
POP AF F1
Restore the starting position saved at 5B7BH into Register A.
5B8E
LD B,A 47
Copy the starting position into Register B, so Register Pair BC now carries both numbers: the position in Register B and the requested length in Register C.
5B8F
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, retrieving the target descriptor's address that 5B71H left there and putting the BASIC text pointer on the stack.
5B90
PUSH HL E5
Save the target descriptor's address (Register Pair HL) onto the stack.
5B91
LD HL,26E7H 21 E7 26
Load Register Pair HL with 26E7H, the ROM routine FINZER, which resets the variable-type state after a value has been delivered.
5B94
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack, so Register Pair HL holds the target descriptor's address and 26E7H sits on the stack as the address the RETs below will reach. This is how a routine entered by a JP gives itself a return path.
5B95
LD A,C 79
Copy the requested length (Register C) into Register A.
5B96
AND A A7
Test it against itself. The Z FLAG is set when a length of zero was requested.
5B97
RET Z C8
If the Z FLAG has been set, RETurn without changing anything. Replacing zero characters is legal and does nothing.
5B98
LD A,(HL) 7E
Fetch the target string's length from the first byte of its descriptor into Register A.
5B99
SUB B 90
SUBtract the starting position (Register B). The CARRY FLAG is set when the position lies beyond the end of the target string.
5B9A
If the CARRY FLAG has been set, JUMP to the ROM's ILLEGAL FUNCTION CALL processor at 1E4AH. MID$ on the left may overwrite characters but may not extend the string.
5B9D
INC A 3C
INCrement Register A by 1, turning the difference into the count of characters available from the starting position to the end of the target.
5B9E
CP C B9
Compare the available count against the requested length in Register C. The CARRY FLAG is set when less room is available than was asked for.
5B9F
If the CARRY FLAG has been set, JUMP to 5BA2H keeping the available count in Register A, so the replacement is truncated at the end of the target.
5BA1
LD A,C 79
Copy the requested length into Register A, which is the smaller of the two and therefore the count to use.
5BA2
LD C,B 48
Copy the starting position (Register B) into Register C, ready to become an offset.
5BA3
DEC C 0D
DECrement Register C by 1, converting the one-based position into a zero-based offset from the start of the target string.
5BA4
LD B,00H 06 00
Load Register B with zero, so Register Pair BC holds that offset as a 16-bit value.
5BA6
PUSH DE D5
Save the source descriptor's address (Register Pair DE) onto the stack while Register Pair DE is needed elsewhere.
5BA7
INC HL 23
INCrement Register Pair HL by 1, from the target's length byte to its address field.
5BA8
LD E,(HL) 5E
Fetch the low byte of the target string's data address into Register E.
5BA9
INC HL 23
INCrement Register Pair HL by 1, to the high half.
5BAA
LD H,(HL) 66
Fetch the high byte of the target string's data address into Register H.
5BAB
LD L,E 6B
Move the low byte from Register E into Register L, so Register Pair HL points at the first character of the target string.
5BAC
ADD HL,BC 09
ADD the zero-based offset, so Register Pair HL now points at the first character that is to be overwritten.
5BAD
LD B,A 47
Copy the count of characters to replace into Register B, the loop counter for the copy below.
5BAE
POP DE D1
Restore the source descriptor's address (Register Pair DE) from the stack.
5BAF
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL, so Register Pair HL addresses the source descriptor and Register Pair DE holds the position in the target that is to be written.
5BB0
LD C,(HL) 4E
Fetch the source string's length into Register C, which becomes the second limit on how much is copied.
5BB1
INC HL 23
INCrement Register Pair HL by 1, to the address field of the source descriptor.
5BB2
LD A,(HL) 7E
Fetch the low byte of the source string's data address into Register A.
5BB3
INC HL 23
INCrement Register Pair HL by 1, to the high half.
5BB4
LD H,(HL) 66
Fetch the high byte into Register H.
5BB5
LD L,A 6F
Move the low byte from Register A into Register L, so Register Pair HL points at the first character of the source string.
5BB6
EX DE,HL EB
Exchange Register Pair DE and Register Pair HL again, so Register Pair HL is the destination within the target and Register Pair DE the source, which is the direction the copy loop expects.
5BB7
LD A,C 79
Copy the source string's length into Register A.
5BB8
AND A A7
Test it against itself. The Z FLAG is set for an empty source string.
5BB9
RET Z C8
If the Z FLAG has been set, RETurn without changing anything. Assigning an empty string replaces nothing.
5BBA
LD A,(DE) 1A
Loop Start
Fetch the next character of the source string into Register A.
5BBB
LD (HL),A 77
Store it over the corresponding character of the target string.
5BBC
INC DE 13
INCrement Register Pair DE by 1, advancing through the source.
5BBD
INC HL 23
INCrement Register Pair HL by 1, advancing through the target.
5BBE
DEC C 0D
DECrement Register C by 1, counting off one character of the source.
5BBF
RET Z C8
If the Z FLAG has been set, meaning the source has been exhausted, RETurn. This is the third of the three limits: the copy stops at whichever of the requested length, the room in the target and the source length runs out first.
5BC0
DECrement Register B, the count of characters to replace, and LOOP BACK to 5BBAH if it is not yet zero. Loop End
5BC2
RET C9
RETurn, having replaced the full count requested.

5BC3H - Optional Length Parser

Reads the optional third argument of MID$ on the left of an assignment and the closing parenthesis. When no length is given the default is 0FFH, larger than any string, so the limits at 5B9EH and 5BBFH reduce it to whatever is actually available. Entered with the character after the starting position already in Register A.

5BC3
LD E,0FFH 1E FF
Load Register E with 0FFH, 255, the default length. No string can be longer than 255 characters, so this always resolves to "as much as there is room for".
5BC5
CP 29H FE 29
Compare the character in Register A against 29H (ASCII: )). A closing parenthesis here means no length was supplied.
5BC7
If the Z FLAG has been set, JUMP to 5BCEH to consume the parenthesis, leaving the default in Register E.
5BC9
GOSUB through the RAM hook at 4000H to the ROM's SYNCHR routine at 1C96H, requiring the comma before the length.
5BCB
GOSUB to the ROM routine GETBYT at 2B1CH, which evaluates the length expression and leaves the 8-bit result in Register E as well as in Register A. Register E is the one that matters, because it is what the caller pushes at 5B80H.
5BCE
GOSUB to the ROM's SYNCHR routine at 1C96H, requiring the closing parenthesis of the MID$ reference. Both paths converge here.
5BD0
RET C9
RETurn to the caller with the length in Register E.

5BD1H - Keyboard Line Input Exit

Reached through vector slot 41AFH, which the ROM CALLs at 0368H whenever it is about to collect a line. When no file is current the whole thing is handed to 6459H and behaves as it always did. When a file is current LBASIC collects the line itself, with the loop below, rather than letting the ROM's own collector run: it discards the ROM's return address and never goes back. The loop is a compact line reader that stops on a carriage return, ignores a line feed that arrives before any character has been stored, and terminates the text with a zero byte.

5BD1
PUSH HL E5
Save the BASIC text pointer (Register Pair HL) onto the stack while the current-file test is made.
5BD2
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL. It is 0000H unless a file operation has set it.
5BD5
LD A,H 7C
Copy the high byte of the pointer into Register A.
5BD6
OR L B5
OR the low byte into it, so the Z FLAG is set only when the whole pointer is zero.
5BD7
POP HL E1
Restore the BASIC text pointer (Register Pair HL). POP does not disturb the flags.
5BD8
If the Z FLAG has been set, meaning no file is current, JUMP to 6459H, the ordinary line-collection path.
5BDB
POP AF F1
Discard the ROM's return address into Register A and the flags. From here LBASIC owns the line collection and re-enters the interpreter by its own route.
5BDC
PUSH BC C5
Save Register Pair BC onto the stack, since Register B becomes the character counter.
5BDD
PUSH DE D5
Save Register Pair DE onto the stack, preserving the caller's registers across the collection.
5BDE
LD B,0F0H 06 F0
Load Register B with 0F0H, 240 decimal, the capacity of the line buffer. The same limit governs the ROM's own collector and the fielded input at 58D9H.
5BE0
LD HL,(40A7H) 2A A7 40
Fetch BUFPNT from 40A7H into Register Pair HL, the line buffer that initialization placed at 5309H.
5BE3
Loop Start
GOSUB to the ROM routine INCHR at 0384H, which waits for a character and returns it in Register A.
5BE6
LD (HL),A 77
Store the character into the line buffer at the address in Register Pair HL. It is stored before it is examined, so a terminator overwrites nothing that matters.
5BE7
CP 0DH FE 0D
Compare the character against 0DH, a carriage return, which ends the line.
5BE9
If the Z FLAG has been set, JUMP to 5BF7H to terminate the collected text.
5BEB
CP 0AH FE 0A
Compare the character against 0AH, a line feed.
5BED
If the NZ FLAG has been set, meaning an ordinary character, JUMP to 5BF4H to keep it and move on.
5BEF
LD A,B 78
Copy the character counter (Register B) into Register A, to find out whether anything has been stored yet.
5BF0
CP 0F0H FE F0
Compare it against 0F0H, its starting value. They are equal only when no character has been accepted, which is how a line feed leading a line is told from one in the middle of it.
5BF2
If the Z FLAG has been set, LOOP BACK to 5BE3H without keeping the line feed. A carriage return and line feed pair arriving from the previous line is therefore swallowed rather than producing an empty line.
5BF4
INC HL 23
INCrement Register Pair HL by 1, keeping the character just stored and advancing to the next buffer position.
5BF5
DECrement Register B and LOOP BACK to 5BE3H if the buffer is not yet full. Loop End Falling through means 240 characters arrived without a carriage return.
5BF7
XOR A AF
Set Register A to ZERO and clear all flags, ready to plant the terminator.
5BF8
LD (HL),A 77
Store a zero byte over the carriage return, or after the last character when the buffer filled, giving the text the terminator the interpreter's parsers expect.
5BF9
POP DE D1
Restore Register Pair DE from the stack.
5BFA
POP BC C1
Restore Register Pair BC from the stack.
5BFB
LD HL,(40A7H) 2A A7 40
Fetch BUFPNT from 40A7H into Register Pair HL again, back at the start of the collected line rather than at its end.
5BFE
DEC HL 2B
DECrement Register Pair HL by 1, onto the comma that 532BH planted at 5308H. RST 10H increments before it fetches, so the first fetch returns the first character of the line.
5BFF
RET C9
RETurn. Because 5BDBH discarded the ROM's return address, this reaches whatever called the ROM routine that entered the vector, with Register Pair HL positioned ready for the interpreter.

5C00H - Byte Output To Any Device

Reached through vector slot 41C1H, which the ROM's character despatcher CALLs at 032CH. Every single character BASIC prints passes through here, so the test has to be as cheap as possible: if no file is current the routine returns at once and the ROM sends the character to the screen or printer as usual. If a file is current the character is written to it instead, and the two stacked return addresses are thrown away because the file writer takes over the statement.

5C00
PUSH HL E5
Save Register Pair HL onto the stack while the current-file test is made. The character despatcher's callers rely on Register Pair HL surviving.
5C01
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL.
5C04
LD A,H 7C
Copy the high byte of the pointer into Register A.
5C05
OR L B5
OR the low byte into it, setting the Z FLAG only when no file is current.
5C06
POP HL E1
Restore Register Pair HL from the stack. POP does not disturb the flags.
5C07
LD A,C 79
Copy the character to be output from Register C into Register A. The ROM's despatcher at 032CH carries it in Register C and expects to find it in Register A on return, so this is done on both paths. LD does not disturb the flags.
5C08
RET Z C8
If the Z FLAG has been set, meaning no file is current, RETurn with the character in Register A and let the ROM send it to the screen or the printer exactly as it would without Disk BASIC.
5C09
POP BC C1
Discard the first of the two stacked return addresses into Register Pair BC.
5C0A
POP BC C1
Discard the second. The file writer below does not come back through the ROM's output path, so both levels of return are abandoned.
5C0B
JUMP to 61D0H, the routine that writes the byte in Register A to the file whose buffer 65F8H names. This does not return here.

5C0EH - Keyboard Scan Exit

Reached through vector slot 41C4H, which the ROM's INKEY$ simulator ISCHAR CALLs at 0358H as its very first act. ISCHAR is the bottom of every keyboard path in Level II BASIC, so this hook sees every request for a key from anywhere in the interpreter - and it has to tell them apart, because LBASIC wants to answer some of them from a disk file and others from the real keyboard. It does that by reaching PAST its own caller and looking at who called ISCHAR: the return address two levels up is 0387H if the request came from INCHR, the ROM's wait-for-a-keypress loop, and 01A8H if it came from the INKEY$ function itself. Only the INCHR case is diverted to the file, which is exactly right - INKEY$ must keep polling the operator even while a program is being loaded from disk, whereas the line collector that INCHR feeds is precisely what an ASCII LOAD or MERGE wants to fill from the file. The whole test is done in the ALTERNATE register set so that the caller's registers are not disturbed, and note that EXX exchanges only BC, DE and HL: Register A and the flags pass straight through it, which is what lets the current-file test be made on one side and acted on from the other.

5C0E
EXX D9
Exchange Register Pairs BC, DE and HL with their alternates, so that the caller's copies are put safely away and the stack can be examined in the alternate set.
5C0F
POP HL E1
POP the first return address into Register Pair HL. That is 035BH, the address inside ISCHAR that this vector was CALLed from, and it is deliberately DISCARDED - the routine either rejoins the ROM at 035BH by jumping there or takes the request over completely.
5C10
POP HL E1
POP the SECOND return address into Register Pair HL, overwriting the first. This is the address ISCHAR itself will return to, and therefore the identity of the routine that wanted a key.
5C11
LD DE,0387H 11 87 03
Load Register Pair DE with 0387H, the address immediately after the CALL 0358H at the head of the ROM's INCHR routine. A return address of 0387H means the request came from the wait-for-a-keypress loop.
5C14
RESTART to the DCOMPR hook at 4006H, which compares Register Pair HL against Register Pair DE and sets the Z FLAG when they are equal and the CARRY FLAG when Register Pair HL is the lower.
5C15
If the NZ FLAG has been set, the caller was not INCHR, so JUMP to 5C53H where the other two possibilities are sorted out. The stack is still two words short at that point and 5C5AH puts one of them back.
5C17
PUSH HL E5
PUSH the 0387H return address back onto the stack. The 035BH level is gone for good, so whatever happens next returns straight into INCHR's own loop.
5C18
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into the alternate Register Pair HL. 0000H means no file is current.
5C1B
LD A,H 7C
Copy the high byte of the pointer into Register A.
5C1C
OR L B5
OR the low byte into it, setting the Z FLAG only when no file is current.
5C1D
EXX D9
Exchange the register pairs back, restoring the caller's BC, DE and HL. EXX does not touch Register A or the flags, so the answer just computed survives the swap.
5C1E
If the Z FLAG has been set, no file is current, so JUMP to 035BH, the point in ISCHAR just past the vector CALL. The keyboard is scanned exactly as it would be on a cassette machine and the RET at the end of ISCHAR delivers the key to INCHR at 0387H.
5C21
PUSH HL E5
A file IS current. Save Register Pair HL, the caller's text pointer, onto the stack; both exits below recover it.
5C22
GOSUB to 627BH, which reads the next byte from the current file or device and returns it in Register A with the CARRY FLAG clear, or returns the CARRY FLAG set at end of file.
5C25
If the CARRY FLAG is clear a byte was read, so JUMP to 26E7H, the two-byte tail of the ROM's FINZER block, which recovers the text pointer stacked at 5C21H and RETurns. Control lands in INCHR at 0387H with Register A holding the character, so the file byte is indistinguishable from a keystroke.
5C28
LD A,(65F4H) 3A F4 65
End of file. Fetch the chain-state byte at 65F4H into Register A. This is the same cell the long error message handler reads at 5A43H; 0D6H means a RUN"filespec",V is part-way through and the retained variables are still parked above STKTOP.
5C2B
CP 0D6H FE D6
Compare it against 0D6H, setting the Z FLAG on a match.
5C2D
LD HL,(40F9H) 2A F9 40
Fetch VARTAB from 40F9H into Register Pair HL. This is placed BETWEEN the comparison and the jump on purpose: LD alters no flag, and 5F5CH expects to be entered with Register Pair HL holding the new VARTAB.
5C30
If the Z FLAG has been set, JUMP to 5F54H, which moves the retained variables down behind the program that has just finished loading and then starts it. This does not return.
5C33
PUSH AF F5
Save Register A, the chain-state byte, onto the stack. It is needed again after the call below and is the flag that decides whether the loaded program is run or merely left in memory.
5C34
PUSH BC C5
Save Register Pair BC onto the stack.
5C35
PUSH DE D5
Save Register Pair DE onto the stack.
5C36
GOSUB to 5FC4H, the READY exit, which resets the output device to the video display and tidies up the file state now that the load is complete.
5C39
POP DE D1
Restore Register Pair DE from the stack.
5C3A
POP BC C1
Restore Register Pair BC from the stack.
5C3B
POP AF F1
Restore the chain-state byte into Register A.
5C3C
AND A A7
Set the flags from it. Zero means a plain LOAD with nothing to run afterwards; anything else - 0FFH from RUN"filespec", or the option letter or digit stored at 5E92H - means the program is to be started.
5C3D
LD HL,5FB1H 21 B1 5F
Load Register Pair HL with 5FB1H, the routine that starts a freshly loaded program at the line number held at 5FB2H. It is about to be planted on the stack as a return address rather than jumped to.
5C40
EX (SP),HL E3
Exchange Register Pair HL with the top of the stack. 5FB1H replaces the text pointer stacked at 5C21H, and Register Pair HL gets that text pointer back.
5C41
If the NZ FLAG has been set, the program is to be run, so JUMP to the ROM's RUNC entry, which performs the CLEAR that RUN implies. RUNC ends in a RET, and the address it finds is the 5FB1H just planted, so control arrives there with the run set up.
5C44
EX (SP),HL E3
A plain LOAD. Exchange back, taking 5FB1H off the stack again and putting the text pointer where 5C51H expects to find it.
5C45
PUSH BC C5
Save Register Pair BC onto the stack across the message display.
5C46
PUSH DE D5
Save Register Pair DE onto the stack across the message display.
5C47
LD HL,1929H 21 29 19
Load Register Pair HL with 1929H, the address of the ROM's own "READY" message text.
5C4A
GOSUB to the ROM routine STROUT, which displays the zero-terminated string at Register Pair HL. The operator sees READY as soon as the program has been loaded.
5C4D
POP DE D1
Restore Register Pair DE from the stack.
5C4E
POP BC C1
Restore Register Pair BC from the stack.
5C4F
LD A,0DH 3E 0D
Load Register A with 0DH, a carriage return. This is the character handed back in place of the byte the file could not supply, and it is what makes the ROM's line collector treat the end of the file as the end of a typed line.
5C51
POP HL E1
Restore the text pointer stacked at 5C21H into Register Pair HL.
5C52
RET C9
RETurn to 0387H inside INCHR with Register A holding the carriage return.

5C53H - BREAK And PAUSE Poll For Every Other Keyboard Request

The continuation of the keyboard scan exit, reached when the caller two levels up was not INCHR. If it was the INKEY$ function itself the request is handed straight back to the ROM. Everything else - and that means the polls Level II makes while a program is running, in order to notice a BREAK - is answered by LBASIC entirely, from the DOS keyboard flag KFLAG$ 4423H rather than by scanning the keyboard matrix. The routine returns 01H, the code the keyboard driver gives for BREAK, or 00H for no key, and because 5C0FH threw the ISCHAR return level away, that value goes straight back to whoever called ISCHAR without the rest of ISCHAR running at all. Answering from KFLAG$ is what lets a BREAK pressed at any moment - including one the DOS type-ahead buffer caught while a disk operation was in progress - be seen by a running BASIC program, and it is also where LDOS's shift-@ PAUSE is implemented.

5C53
LD (40A9H),A 32 A9 40
Store Register A, whatever the caller happened to be holding, into CASFLG at 40A9H. That is the Level II cassette-input flag, which LBASIC has no other use for. NOTHING IN THIS IMAGE EVER READS 40A9H BACK. Document it mechanically, as with the write to 5409H at 5428H, and do not invent a purpose for it.
5C56
LD DE,01A8H 11 A8 01
Load Register Pair DE with 01A8H, the address immediately after the CALL 0358H inside the ROM's INKEY$ routine. A return address of 01A8H means the BASIC program asked for INKEY$.
5C59
RESTART to the DCOMPR hook at 4006H, comparing the alternate Register Pair HL, which still holds the caller's return address, against Register Pair DE and setting the Z FLAG when they match.
5C5A
PUSH HL E5
PUSH that return address back onto the stack, undoing the second of the two POPs at 5C0FH and 5C10H. The ISCHAR level stays discarded.
5C5B
EXX D9
Exchange the register pairs back so that the caller's BC, DE and HL are live again before control can leave for the ROM. The Z FLAG set by the comparison is unaffected.
5C5C
If the Z FLAG has been set, the caller was the INKEY$ function, so JUMP to 035BH and let the ROM scan the keyboard normally. INKEY$ must always read the real keyboard, even in the middle of a load from disk.
5C5F
EXX D9
Exchange back to the alternate set, putting the caller's registers away again for the duration of the task call below.
5C60
GOSUB to @KITSK at 4300H, the resident keyboard task. It is what actually samples the keyboard and sets the BREAK, PAUSE and ENTER bits of KFLAG$ 4423H, so it must be given a chance to run before those bits are believed. Calling it with the alternate set live is what protects the caller's registers from the task chain.
5C63
EXX D9
Exchange the register pairs back once more, restoring the caller's BC, DE and HL for good.
5C64
GOSUB to 5C81H, which returns Register A holding KFLAG$ and the Z FLAG cleared when bit 0, the BREAK bit, is set.
5C67
If the NZ FLAG has been set, BREAK has been pressed, so JUMP to 5C7CH to report it.
5C69
BIT 1,A CB 4F
Test bit 1 of the KFLAG$ value in Register A. That is the PAUSE bit, set by the resident keyboard task when the operator presses shift-@.
5C6B
If the Z FLAG has been set, no pause is wanted either, so JUMP to 5C7AH and report that no key is waiting.
5C6D
GOSUB to the ROM's INCHR routine, which does not return until a key is pressed. This is the pause itself: the running program is frozen here until the operator does something.
5C70
CP 60H FE 60
Compare the key against 60H, the code the keyboard driver produces for the key combination that requested the pause.
5C72
If it matches, LOOP BACK to 5C6DH and keep waiting. Loop End The keystroke that asked for the pause is not allowed to resume it, so a key held down a moment too long does not throw the pause away.
5C74
GOSUB to 5C93H, which clears bits 0 to 2 of KFLAG$ - the BREAK, PAUSE and ENTER requests - so that the pause just serviced is not seen again on the next poll. Register A is preserved across the call.
5C77
DEC A 3D
DECrement Register A by 1, setting the Z FLAG only if the key that ended the pause was 01H, the BREAK code.
5C78
If the Z FLAG has been set, the operator ended the pause with BREAK, so JUMP to 5C7CH and report a BREAK to the running program.
5C7A
XOR A AF
Set Register A to ZERO, the code for "no key is waiting", which lets the interrupted program carry on.
5C7B
RET C9
RETurn. Because the ISCHAR return level was discarded at 5C0FH, this goes directly to whatever called ISCHAR, with Register A as its answer.
5C7C
LD A,01H 3E 01
Load Register A with 01H, the code the Model I keyboard driver returns for the BREAK key. Level II tests for exactly this value when it decides to stop a program.
5C7E
RET C9
RETurn to ISCHAR's caller with the BREAK code in Register A.

5C7FH - Clear The Pending BREAK, PAUSE And ENTER Requests

A two-byte routine that exists purely to give the body at 5C93H a second name. It is CALLed at 52DAH, near the top of initialization, and again at 55D9H when a CMD shell-out returns from DOS: both are moments at which a BREAK or a shift-@ left over from whatever went before must not be allowed to fall into the BASIC session that is about to start. NOTE THAT THIS IS NOT DEVICE OR INTERRUPT STATE SETUP, as the earlier reading of the two call sites suggested. All it does is clear the low three bits of KFLAG$ 4423H, which is the same "clear the pending key requests" idiom used at the head of several SYS6 and SYS7 command overlays.

5C7F
JUMP to 5C93H, the flag-clearing body, which RETurns to this routine's own caller.

5C81H - Keyboard And BREAK Poll

CALLed from 5C64H on the keyboard-scan path and from 5822H inside CMD"*", the screen print. It reads KFLAG$ 4423H, the resident keyboard flag that the interrupt-driven keyboard task maintains, and reports bit 0 - BREAK - in the Z FLAG while returning the whole byte in Register A so that the caller can look at the PAUSE bit as well. If either BREAK or PAUSE is pending it first empties the type-ahead buffer, because the characters queued behind a BREAK are stale by definition. The routine leaves KFLAG$ itself alone; clearing the bits is the job of 5C93H below, and separating the two is what lets 5C64H test the PAUSE bit, act on it, and only then clear it.

5C81
Fetch KFLAG$ from 4423H into Register A. Bit 0 is set when BREAK has been pressed, bit 1 when the PAUSE key has been pressed and bit 2 when ENTER has been pressed.
5C84
PUSH AF F5
Save Register A and the flags onto the stack, because the mask below destroys the copy that has to be returned to the caller.
5C85
AND 03H E6 03
Mask Register A down to bits 0 and 1, leaving a non-zero result if either BREAK or PAUSE is pending.
5C87
If the Z FLAG has been set, neither is pending, so JUMP to 5C8FH and leave the type-ahead buffer alone.
5C89
GOSUB to 035BH, the ROM's keyboard scan entered PAST the Disk BASIC vector CALL so that this routine does not re-enter itself. Register A comes back holding a queued character, or zero when there are none left.
5C8C
OR A B7
Set the flags from Register A, testing for the zero that means the buffer is now empty.
5C8D
If a character was returned, LOOP BACK to 5C89H and take the next one. Loop End Every keystroke typed ahead of the BREAK is thrown away, which is what stops a burst of type-ahead from being replayed into the program that the BREAK is about to stop.
5C8F
POP AF F1
Restore the untouched KFLAG$ value into Register A.
5C90
BIT 0,A CB 47
Test bit 0, the BREAK bit, setting the Z FLAG when it is clear. Register A is left holding the whole flag byte so that the caller can go on to examine the PAUSE bit.
5C92
RET C9
RETurn with Register A = KFLAG$ and the NZ FLAG set if BREAK is pending.
5C93
PUSH AF F5
The flag-clearing body, entered here from 5C74H and from 5C7FH. Save Register A and the flags onto the stack so that the caller's value survives.
5C94
Fetch KFLAG$ from 4423H into Register A.
5C97
AND 0F8H E6 F8
Clear bits 0, 1 and 2 - the BREAK, PAUSE and ENTER requests - and leave bits 3 to 7, which include the despool-enabled bit, exactly as they were.
5C99
Store the masked value back into KFLAG$.
5C9C
POP AF F1
Restore the caller's Register A and flags from the stack.
5C9D
RET C9
RETurn with nothing altered but KFLAG$.

5C9EH - Comma Fields And TAB

Reached through vector slot 41D3H, which the ROM CALLs from two places: 2108H, where a comma in a PRINT list has to be padded out to the next 16-column field, and 2141H, where TAB(n) has to pad to a requested column. Both decisions depend on knowing the current column, and the ROM's own column counter TTYPOS 40A6H tracks the SCREEN. When output is going to a file that is the wrong number entirely, so LBASIC substitutes the file's own column counter, which lives at offset 1 of the file buffer and is maintained by the byte writer at 61DEH-61ECH. Which of the two ROM continuations to rejoin is decided without any flag being passed in: the routine simply looks at the return address the ROM left, and 2108H's is below 2130H while 2137H's is above it.

5C9E
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL.
5CA1
LD A,H 7C
Copy the high byte of the pointer into Register A.
5CA2
OR L B5
OR the low byte into it, setting the Z FLAG only when no file is current.
5CA3
RET Z C8
If the Z FLAG has been set, RETurn at once and let the ROM pad against the screen column exactly as it would without Disk BASIC.
5CA4
INC HL 23
INCrement Register Pair HL by 1, stepping past the mode byte to offset 1 of the file buffer.
5CA5
LD A,(HL) 7E
Read the file's own print column into Register A. It is reset to zero by every carriage return written to the file and incremented for every character of 20H or above.
5CA6
POP HL E1
POP the ROM's return address into Register Pair HL. The routine does not intend to return; it identifies its caller from this address and then jumps into the appropriate continuation.
5CA7
PUSH DE D5
Save Register Pair DE onto the stack, because the comparison below needs it.
5CA8
PUSH AF F5
Save the file column and the flags onto the stack. Register A is about to be destroyed by the comparison and this is how it is brought back.
5CA9
LD DE,2130H 11 30 21
Load Register Pair DE with 2130H, an address that falls between the comma-field caller at 2108H and the TAB caller at 2137H, so that a single magnitude comparison separates them.
5CAC
RESTART to the DCOMPR hook at 4006H, comparing the return address in Register Pair HL against 2130H. The CARRY FLAG is set when the return address is the lower, that is, when the caller was the comma-field code.
5CAD
POP DE D1
POP the value pushed at 5CA8H into Register Pair DE, so that Register D receives the file column. This is a deliberate mismatched POP: it recovers Register A into a register that the following instruction can read, without disturbing the CARRY FLAG that has to survive.
5CAE
LD A,D 7A
Copy the file column back into Register A, which is where both ROM continuations expect the current column to be.
5CAF
POP DE D1
Restore the caller's Register Pair DE from the stack. POP alters no flag, so the CARRY FLAG set at 5CACH is still intact.
5CB0
If the CARRY FLAG is set the caller was the comma-field code at 2108H, so JUMP to 2130H, its continuation, with Register A holding the file's column instead of the screen's.
5CB3
Otherwise the caller was TAB at 2137H, so JUMP to 2156H, inside the ROM's TTYIST block, again with the file's column in Register A.

5CB6H - Line Table Exit

Reached through vector slot 41B5H, which the ROM CALLs at 1AECH once it has finished rebuilding the line-link table - that is, immediately after a program line has been entered, altered or deleted. Together with 5CC3H below it forms a save-and-restore pair around the ROM's edit housekeeping: this half parks the current file pointer in TEMP2 40F3H and points file slot zero at a dummy buffer, and 5CC3H puts both back. The dummy is the byte at 65F2H, which is inside the run-time cell block above the loaded image, ships as 00H and is never written by anything in the file; since offset 0 of a file buffer is its mode, a buffer that begins there always reads as CLOSED. The same trick is used again by the program loader at 5EB7H and undone at 5ED6H, which is the clearest statement of what it is for: while the ROM is free to reorganise memory, no reference to file zero may find a live buffer.

5CB6
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL.
5CB9
LD (40F3H),HL 22 F3 40
Store it into TEMP2 at 40F3H. TEMP2 is a Level II scratch word which LBASIC also uses in the FN evaluator at 55FFH and 5657H; the two uses cannot overlap because a function is never being evaluated while a program line is being edited.
5CBC
LD HL,65F2H 21 F2 65
Load Register Pair HL with 65F2H, the address of the permanently-zero byte used as a dummy file buffer.
5CBF
LD (65FAH),HL 22 FA 65
Store it into the first slot of the file buffer pointer table at 65FAH, so that any reference to file zero now finds a buffer whose mode byte is 00H and is therefore closed.
5CC2
RET C9
RETurn to the ROM at 1AEFH to carry on with the edit.

5CC3H - Line Table Plus CLEAR Exit, And The PRINT Carriage Return Exit

Reached through vector slot 41B8H, which the ROM CALLs at 1AF2H - the same line-table point as 41B5H, but on the path that also performs a CLEAR. It reverses everything 5CB6H did. Its closing RET at 5CCFH is ALSO the whole body of a second routine: vector slot 41D0H, the exit taken after PRINT has sent a carriage return, points at 5CCFH, so that hook is a bare RETurn. LBASIC has nothing to do there, and rather than assemble a RET of its own it points the slot at one that already exists.

5CC3
LD HL,(40F3H) 2A F3 40
Fetch the file buffer pointer that 5CB9H parked in TEMP2 at 40F3H into Register Pair HL.
5CC6
LD (65F8H),HL 22 F8 65
Store it back into 65F8H, making the same file current again.
5CC9
LD HL,(661AH) 2A 1A 66
Fetch the address of the FIRST file buffer from 661AH into Register Pair HL. It was written there at 53E2H when the buffer chain was built and is never changed afterwards.
5CCC
LD (65FAH),HL 22 FA 65
Store it into the first slot of the file buffer pointer table at 65FAH, replacing the dummy that 5CBFH installed and making file zero a real buffer again.
5CCF
RET C9
RETurn. Entered here from vector slot 41D0H this single byte is the entire PRINT carriage-return exit: the ROM CALLs it at 2103H after sending a CR and LBASIC has nothing to add.

5CD0H - Number Conversion Exit

Reached through vector slot 41CDH, which the ROM CALLs at 20C6H once it has converted a number to its ASCII form and is about to print it. When output is going to the screen or the printer LBASIC steps aside; when a file is current it takes the statement over completely, discarding the ROM's return address and jumping into the PRINT continuation that sends the string through the file writer. The current-file test here is the same five-instruction idiom used at 5A2CH, 5BD1H, 5C00H and 5C18H, and it depends on POP not disturbing the flags.

5CD0
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL.
5CD3
LD A,H 7C
Copy the high byte of the pointer into Register A.
5CD4
OR L B5
OR the low byte into it, setting the Z FLAG only when no file is current.
5CD5
RET Z C8
If the Z FLAG has been set, RETurn and let the ROM print the converted number to the screen or the printer as usual.
5CD6
POP HL E1
Discard the ROM's return address into Register Pair HL. The file path below does not come back through 20C6H.
5CD7
JUMP to 20E9H, inside the ROM's PRINT routine, which sends the string just built to the current output path - which, because a file is current, is diverted through the byte output hook at 5C00H and so into the file.

5CDAH - The Shared File Reference Parser

The routine six other sections depend on, and the reason the file statements are as short as they are. It has three entry points and they divide the work cleanly. 5CEFH is the parser proper: it evaluates a file number, range-checks it against MAXFIL, indexes the file buffer pointer table and returns the buffer address in Register Pair BC together with the buffer's MODE byte in Register A. The mode is 01H for a file opened for sequential input, 02H for sequential output and 03H for random or direct access, and 00H for a slot that is not open. 5CDCH wraps that in the syntax an OUTPUT statement wants - an optional leading "#", the required mode, a required comma - and makes the file current; PRINT# reaches it from 5745H with Register C = 02H. 5CDAH is the same thing with Register C preset to 01H for input, used by INPUT# at 588EH and LINE INPUT# at 5D5DH. GET and PUT bypass both wrappers and call 5CEFH directly at 6209H so that they can insist on mode 03H themselves, and FIELD does the same at 607EH.

5CDA
LD C,01H 0E 01
Load Register C with 01H, the mode number of a file opened for sequential INPUT. This is the input entry; the output entry at 5CDCH is reached with Register C already set to 02H by its caller.
5CDC
CP 23H FE 23
Compare the current character, which the caller has already fetched into Register A, against 23H, ASCII "#". A file reference always begins with it.
5CDE
RET NZ C0
If it is not a "#", RETurn to the caller with nothing consumed, leaving the statement to be treated as an ordinary screen operation.
5CDF
PUSH BC C5
Save Register Pair BC onto the stack, which carries the required mode in Register C past the parse below.
5CE0
GOSUB to the parser at 5CEFH, which returns Register Pair BC holding the address of the file's buffer and Register A holding that buffer's mode byte.
5CE3
POP DE D1
POP the saved Register Pair BC into Register Pair DE, so that the REQUIRED mode arrives in Register E while the buffer address stays in Register Pair BC.
5CE4
CP E BB
Compare the file's actual mode in Register A against the required mode in Register E.
5CE5
If they differ, JUMP to 6435H in the error-code table, which raises "Bad file mode". A file that is not open at all has mode 00H and fails here too.
5CE8
RESTART to the SYNCHR hook at 4000H with the in-line operand 2CH, requiring a comma at the current position and raising ?SN ERROR if it is missing. It leaves the character after the comma in Register A and Register Pair HL advanced past it.
5CEA
LD (65F8H),BC ED 43 F8 65
Store the buffer address into 65F8H, making this the CURRENT FILE. Every diverted hook in the extension - byte output at 5C00H, comma fields at 5C9EH, the keyboard scan at 5C18H, number conversion at 5CD0H - reads that one cell to discover it.
5CEE
RET C9
RETurn to the statement handler with the file selected and the text pointer past the comma.
5CEF
DEC HL 2B
DECrement Register Pair HL by 1 so that the fetch below returns the character already under the pointer rather than the next one. The parser proper starts here.
5CF0
RESTART to the CHRGET hook at 4003H, which LBASIC has re-pointed at its own 6579H. It advances Register Pair HL, skips blanks and returns the character in Register A with the CARRY FLAG set if it is a digit.
5CF1
CP 23H FE 23
Compare it against 23H, ASCII "#".
5CF3
If it is a "#", GOSUB to the ROM's CHRGTR to step over it. The character is OPTIONAL here, which is what allows GET, PUT and FIELD to write either GET 1 or GET #1.
5CF6
GOSUB to the ROM's FRMEVL, which evaluates a complete expression and leaves its value in the floating-point accumulator. The file number may therefore be any numeric expression, not just a constant.
5CF9
GOSUB to the ROM's CONINT, which reduces that value to an 8-bit integer in Register A and raises ?FC ERROR if it will not fit.
5CFC
LD E,A 5F
Copy the file number into Register E, out of the way of the comparison below.
5CFD
LD A,(408EH) 3A 8E 40
Fetch MAXFIL from 408EH into Register A. That is the number of file buffers the FILES parameter asked for, written at 53CCH during initialization.
5D00
CP E BB
Compare MAXFIL against the requested file number, setting the CARRY FLAG when MAXFIL is the smaller, that is, when the number is out of range.
5D01
If the CARRY FLAG is set, JUMP to 6438H in the error-code table, which raises "Bad file number". Note that the test is inclusive, so file number MAXFIL itself is legal and the table therefore has MAXFIL+1 slots, which is why 53CFH builds one more buffer than the FILES parameter asked for.
5D04
LD D,00H 16 00
Clear Register D so that Register Pair DE is the file number widened to sixteen bits, ready to be used as a table index.
5D06
PUSH HL E5
Save the BASIC text pointer onto the stack while Register Pair HL is used for the table walk.
5D07
LD HL,65FAH 21 FA 65
Load Register Pair HL with 65FAH, the base of the file buffer pointer table. Its first two bytes are the last two bytes of the loaded image and ship as 0000H, so the table starts inside the file and runs on past its end.
5D0A
ADD HL,DE 19
ADD the file number to the base.
5D0B
ADD HL,DE 19
ADD it a second time, which multiplies by two without needing a shift. Register Pair HL now addresses the two-byte slot belonging to this file.
5D0C
LD C,(HL) 4E
Read the low byte of the buffer address into Register C.
5D0D
INC HL 23
INCrement Register Pair HL by 1 to reach the high byte.
5D0E
LD B,(HL) 46
Read the high byte into Register B. Register Pair BC now holds the address of this file's 0122H-byte buffer.
5D0F
LD A,(BC) 0A
Read the byte at offset 0 of that buffer into Register A. This is the file's MODE: 00H not open, 01H sequential input, 02H sequential output, 03H random.
5D10
AND A A7
Set the flags from the mode, so a caller that only wants to know whether the file is open at all can test the Z FLAG without repeating the load.
5D11
POP HL E1
Restore the BASIC text pointer into Register Pair HL. POP does not disturb the flags just set.
5D12
RET C9
RETurn with Register Pair BC = the buffer address, Register A = the mode and the flags set from it.

5D13H, 5D16H, 5D19H - MKI$, MKS$ And MKD$

Vector slots 416AH, 416DH and 4170H. The three functions turn a number into the string of raw bytes that a random file record holds: two bytes for an integer, four for single precision and eight for double. One body serves all three, because the byte count and the Level II type code VALTYP are the same number - 02H integer, 04H single, 08H double - so a single constant in Register A drives both the conversion and the allocation. The three entries are built with the skip-byte overlap that appears twice in this file: the bytes are 3E 02 / 01 3E 04 / 01 3E 08, so entering at 5D13H executes LD A,02H and the 01H that follows swallows the next LD A as the operand of a LD BC, while entering at 5D16H or 5D19H executes LD A,04H or LD A,08H instead.

5D13
LD A,02H 3E 02
MKI$ entry. Load Register A with 02H, which is both the VALTYP code for an integer and the number of bytes an integer occupies.
5D15
LD BC,043EH 01 3E 04
Entered at 5D13H this is a harmless LD BC whose operand swallows the LD A,04H below, carrying control past it. Entered at 5D16H instead, the 3E 04 is executed as LD A,04H - the MKS$ entry - and single precision is selected.
5D18
LD BC,083EH 01 3E 08
The same trick again. Entered at 5D19H the 3E 08 is executed as LD A,08H, the MKD$ entry, selecting double precision. Register Pair BC is never read on any of the three paths.
5D1B
PUSH AF F5
Save the type-and-length constant onto the stack. It is needed twice and the call below destroys Register A.
5D1C
GOSUB to the ROM routine DOCNVF, which takes the wanted VALTYP in Register A and forces the value in the floating-point accumulator to that type. MKI$ of a double-precision expression therefore rounds it to an integer first, exactly as an assignment would.
5D1F
POP AF F1
Recover the constant into Register A, this time to be used as a byte count.
5D20
GOSUB to the ROM routine STRINI, which reserves a string of Register A bytes in string space and builds a temporary descriptor for it at DSCTMP 40D3H. It raises ?OS ERROR if string space is exhausted.
5D23
LD HL,(40D4H) 2A D4 40
Fetch the ADDRESS field of that descriptor from 40D4H into Register Pair HL, which is where the reserved bytes live.
5D26
GOSUB to the ROM's VMOVMF entry, which copies the accumulator to the address in Register Pair HL, moving as many bytes as the current VALTYP calls for. That is the whole conversion: the number's internal representation IS the string.
5D29
JUMP to 2A2BH, the tail of the ROM's CHR$ routine, which hands the temporary descriptor back to the expression evaluator as the function's string value.

5D2CH, 5D2FH, 5D32H - CVI, CVS And CVD

Vector slots 4152H, 4158H and 415EH, and the exact inverse of the three above: they take the raw bytes out of a string and read them as a number. The same three-entry overlap is used, but the constants are one LESS than the type code - 01H, 03H and 07H - because the first thing the routine does is compare the constant against the string's length, and the string must be LONGER than that to be long enough. Incrementing it afterwards produces the VALTYP to store. Note that the RAM reference page labels slot 4152H "ERCALL"; the vector table image at 4E00H points it at 5D2CH, which is CVI, and the three-entry construction proves the whole trio.

5D2C
LD A,01H 3E 01
CVI entry. Load Register A with 01H, one less than the two bytes an integer needs.
5D2E
LD BC,033EH 01 3E 03
Entered at 5D2CH this LD BC carries control over the next entry. Entered at 5D2FH the 3E 03 is executed as LD A,03H, the CVS entry, one less than the four bytes of single precision.
5D31
LD BC,073EH 01 3E 07
The same again. Entered at 5D32H the 3E 07 is executed as LD A,07H, the CVD entry, one less than the eight bytes of double precision.
5D34
PUSH AF F5
Save the constant onto the stack across the call below.
5D35
GOSUB to the ROM routine FRESTR, which insists that the argument is a string, releases it if it was a temporary, and returns Register Pair HL pointing at its three-byte descriptor - length first, then address.
5D38
POP AF F1
Recover the constant into Register A.
5D39
CP (HL) BE
Compare it against the string's LENGTH, the first byte of the descriptor. The CARRY FLAG is set only when the constant is the smaller, that is, when the string is long enough.
5D3A
If the CARRY FLAG is clear the string is too short, so JUMP to the ROM's FCERR entry and report ?FC ERROR. CVI needs two characters, CVS four and CVD eight.
5D3D
INC A 3C
INCrement Register A by 1, turning 01H, 03H or 07H into 02H, 04H or 08H - the VALTYP code for integer, single or double.
5D3E
INC HL 23
INCrement Register Pair HL by 1, stepping from the length byte to the low byte of the descriptor's address field.
5D3F
LD C,(HL) 4E
Read the low byte of the string's data address into Register C.
5D40
INC HL 23
INCrement Register Pair HL by 1 to reach the high byte.
5D41
LD H,(HL) 66
Read the high byte straight into Register H, which destroys the descriptor pointer but is safe because the low byte is already parked in Register C.
5D42
LD L,C 69
Copy the low byte into Register L. Register Pair HL now addresses the string's characters.
5D43
LD (40AFH),A 32 AF 40
Store the type code into VALTYP at 40AFH, so that the move below knows how many bytes to take and the expression evaluator knows what it has been given.
5D46
JUMP to the ROM's VMOVFM entry, which loads the accumulator from the address in Register Pair HL using the VALTYP just set, and RETurns to the expression evaluator with the number as the function's value.

5D49H - READ And INPUT Assignment Exit

Reached through vector slot 41DCH, which the ROM CALLs at 222DH as it is about to take the next field out of the input line and assign it to a variable. If no file is current LBASIC steps aside and the ROM reads from the keyboard buffer as usual. If a file IS current the whole assignment is taken over: the field is scanned out of the file a character at a time by the collector at 5D71H below, and the result is handed back to the ROM at 2243H, past the point where it would have looked at the keyboard line. The only things this entry has to establish are which characters end a field and where to rejoin the ROM. Register D and Register E become the two terminator characters - a comma at 2CH and a space at 20H for a numeric field, a comma in both for a string field, since a string may legally contain blanks - and Register Pair BC becomes the ROM address to RETurn to. The LD BC,2243H and LD DE,2C20H instructions are therefore loading four independent BYTE constants, not two addresses; the second is immediately halved by 5D5AH on the string path.

5D49
LD DE,(65F8H) ED 5B F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair DE.
5D4D
LD A,D 7A
Copy the high byte of the pointer into Register A.
5D4E
OR E B3
OR the low byte into it, setting the Z FLAG only when no file is current.
5D4F
RET Z C8
If the Z FLAG has been set, RETurn to 2230H and let the ROM assign from the keyboard input line exactly as it would without Disk BASIC.
5D50
POP AF F1
Discard the ROM's return address into Register AF. The assignment is being taken over, and 5D71H pushes a word back in its place so that the stack stays the same depth.
5D51
RESTART to the GETYPR hook at 4009H, which sets the flags from VALTYP 40AFH: the Z FLAG for a string, the SIGN FLAG for an integer, parity odd for single and parity even for double. Only the string test matters here.
5D52
LD BC,2243H 01 43 22
Load Register Pair BC with 2243H, the address inside the ROM's READ and INPUT code that the collector below RETurns to once the field has been converted. LD alters no flag, so the type just tested survives.
5D55
LD DE,2C20H 11 20 2C
Load Register D with 2CH and Register E with 20H - a comma and a space, the two characters that end a NUMERIC input field. Written as a 16-bit load because that is one byte shorter than two 8-bit loads.
5D58
If the NZ FLAG has been set the variable is numeric, so JUMP to the collector at 5D71H with both terminators as they stand.
5D5A
LD E,D 5A
The variable is a string. Copy the comma from Register D into Register E, so that BOTH terminators are commas and a blank no longer ends the field. This one byte is the whole difference between reading a number and reading a string, and it is also what 5E04H later tests to decide which conversion to apply.
5D5B
JUMP to the collector at 5D71H.

5D5DH - LINE INPUT#

Reached from the LINE INPUT statement at 591AH once it has found a "#" and knows the source is a file. LINE INPUT takes an entire line, so it wants NO terminator characters at all except the carriage return that the collector handles separately, and it can only assign to a string. It sets both terminator registers to zero, locates the destination variable itself with the ROM's PTRGET, and stacks two return addresses so that when the collector finishes the value is stored by the ROM's own LET code and the output device is then reset - which is how a routine reached by a jump from the middle of a statement still unwinds tidily.

5D5D
GOSUB to the file reference parser's input entry, which requires a "#", a file number open in mode 01H for sequential input and a following comma, and makes that file current at 65F8H.
5D60
GOSUB to the ROM routine PTRGET, which parses the variable name that follows and returns Register Pair DE pointing at that variable's value, creating it if it does not yet exist.
5D63
GOSUB to the ROM routine CHKSTR, which raises ?TM ERROR unless the variable just located is a string. LINE INPUT has nothing to convert a whole line into but a string.
5D66
LD BC,2169H 01 69 21
Load Register Pair BC with 2169H, the ROM routine FINPRT, which resets the output device flag to the video display.
5D69
PUSH BC C5
PUSH it as the OUTERMOST return address, so that FINPRT runs after everything else has finished and the statement leaves the device state clean.
5D6A
PUSH DE D5
PUSH the address of the destination variable. This is exactly what the ROM's LET store expects to find on the stack.
5D6B
LD BC,1F31H 01 31 1F
Load Register Pair BC with 1F31H, the point inside the ROM's LET routine that takes a value from the accumulator and stores it into the variable whose address is on the stack. The collector below RETurns to it.
5D6E
XOR A AF
Set Register A to ZERO.
5D6F
LD D,A 57
Clear Register D, the first terminator. A zero can never match, because 5DC6H filters zero bytes out before the terminator tests are reached, so nothing ends the field but a carriage return or the end of the file.
5D70
LD E,A 5F
Clear Register E, the second terminator. Because it is not 20H, the finish at 5E04H will build a string rather than convert a number.
5D71
PUSH AF F5
The shared FIELD COLLECTOR starts here, entered from 5D58H, 5D5BH and by falling through. PUSH Register A and the flags, which on the 5D49H path is the word that replaces the return address discarded at 5D50H and keeps the stack the right depth.
5D72
PUSH BC C5
PUSH the ROM address the collector is to RETurn to - 2243H for READ and INPUT, 1F31H for LINE INPUT. The RET at 5E0FH and 5E15H is what delivers control there.
5D73
PUSH HL E5
PUSH the BASIC text pointer, which Register Pair HL is about to be used for the input buffer instead. It is recovered by the POP HL at 5E0EH or 5E14H.
5D74
GOSUB to 627BH for the next byte of the file, returned in Register A with the CARRY FLAG clear, or the CARRY FLAG set at end of file.
5D77
If the CARRY FLAG is set the file has run out before the field even started, so JUMP to 643BH in the error-code table, which raises "Input past end".
5D7A
CP 20H FE 20
Compare the byte against 20H, a space.
5D7C
If it is not a space, JUMP to 5D82H and start looking at it properly.
5D7E
INC D 14
INCrement Register D by 1.
5D7F
DEC D 15
DECrement it again, restoring the value but setting the flags from it. This is the INC/DEC flag-setting idiom used all through the file: it tests one half of a register pair without needing a spare register to OR against.
5D80
If Register D is not zero this is READ or INPUT, which skips leading blanks, so LOOP BACK to 5D74H for another byte. Loop End For LINE INPUT Register D is zero and a leading blank is part of the line.
5D82
CP 22H FE 22
Compare the byte against 22H, a double quote.
5D84
If it is not a quote, JUMP to 5D94H and collect the field as it stands.
5D86
LD B,A 47
Park the quote character in Register B while Register A is borrowed for the test below.
5D87
LD A,E 7B
Copy the second terminator into Register A.
5D88
CP 2CH FE 2C
Compare it against a comma. Only a STRING field has a comma in both terminator registers, so this is the test for "is a quoted string meaningful here".
5D8A
LD A,B 78
Copy the quote character back into Register A. LD alters no flag.
5D8B
If this is not a string field, JUMP to 5D94H: for a numeric field or for LINE INPUT a quote is just another character.
5D8D
LD D,B 50
Set the first terminator to the quote character.
5D8E
LD E,B 58
Set the second to it as well. Inside quotes NOTHING ends the field but the closing quote, which is how a comma or a leading blank gets into a string read from a file. Register D holding 22H is also the flag that 5D9BH tests.
5D8F
GOSUB to 627BH for the first byte INSIDE the quotes; the opening quote itself is not part of the value.
5D92
If the CARRY FLAG is set the file ended immediately after the opening quote, so JUMP to 5DD7H and finish with an empty field.
5D94
LD HL,(40A7H) 2A A7 40
Fetch BUFPNT from 40A7H into Register Pair HL. That is 5309H, the 240-byte line buffer that initialization laid over its own code, and the field is assembled there exactly as a typed line would be.
5D97
LD B,0FFH 06 FF
Load Register B with 255 as the count of characters the field may contain. Note that this is FIFTEEN MORE than the 240 bytes the buffer is documented to hold, so a pathological field can reach 5408H; 5409H, the cell that 5428H writes 00H into and that nothing ever reads, is the byte immediately above that reach.
5D99
LD C,A 4F
Top of the collection loop. Park the current character in Register C while Register A is borrowed for the tests below.
5D9A
LD A,D 7A
Copy the first terminator into Register A.
5D9B
CP 22H FE 22
Compare it against a quote, which is true only when 5D8DH put one there and the collector is inside a quoted string.
5D9D
LD A,C 79
Copy the character back into Register A without disturbing the flags.
5D9E
If inside quotes, JUMP straight to the terminator test at 5DC6H, bypassing all the carriage return and line feed handling below. A CR inside quotes is stored like any other byte.
5DA0
CP 0DH FE 0D
Compare the character against 0DH, a carriage return.
5DA2
PUSH HL E5
PUSH the buffer write pointer, because the path taken on a match expects it stacked and the path not taken pops it straight back.
5DA3
If it is a carriage return the field is complete, so JUMP to 5DF1H, which also swallows a line feed if one follows.
5DA5
POP HL E1
Not a carriage return: take the write pointer straight back off the stack again.
5DA6
CP 0AH FE 0A
Compare the character against 0AH, a line feed.
5DA8
If it is not a line feed, JUMP to the ordinary terminator test at 5DC6H.
5DAA
LD C,A 4F
Park the line feed in Register C.
5DAB
LD A,E 7B
Copy the second terminator into Register A.
5DAC
CP 2CH FE 2C
Compare it against a comma, testing once more for a string field.
5DAE
LD A,C 79
Copy the line feed back into Register A.
5DAF
If this is NOT a string field, GOSUB to 5E16H and store the line feed in the buffer. In a string field a bare line feed is dropped instead, which is what lets a file written with CR-LF pairs be read back cleanly.
5DB2
GOSUB to 627BH for the byte after the line feed.
5DB5
If the CARRY FLAG is set the file has ended, so JUMP to 5DD7H and finish.
5DB7
CP 0DH FE 0D
Compare it against a carriage return, testing for the reversed LF-CR pair.
5DB9
If it is not, JUMP to 5DC6H and treat the byte normally.
5DBB
LD A,E 7B
Copy the second terminator into Register A once more.
5DBC
CP 20H FE 20
Compare it against a space, which identifies a NUMERIC field.
5DBE
If this is a numeric field, JUMP to 5DD2H: the line feed and carriage return were separators between numbers and collection carries on.
5DC0
CP 2CH FE 2C
Compare it against a comma, identifying a string field.
5DC2
LD A,0DH 3E 0D
Load Register A with the carriage return again, ready to be stored if the jump below is not taken. LD alters no flag.
5DC4
If this is a string field, JUMP to 5DD2H and carry on collecting.
5DC6
AND A A7
Set the flags from the character, testing for a zero byte.
5DC7
If it is zero, JUMP to 5DD2H and IGNORE it. This is why LINE INPUT can use 00H in both terminator registers and still never terminate on them.
5DC9
CP D BA
Compare the character against the first terminator.
5DCA
If it matches, the field is complete, so JUMP to 5DD7H.
5DCC
CP E BB
Compare the character against the second terminator.
5DCD
If it matches, JUMP to 5DD7H.
5DCF
GOSUB to 5E16H, which stores the character in the buffer, advances the write pointer and counts Register B down; if the buffer fills it does not come back here at all.
5DD2
GOSUB to 627BH for the next byte.
5DD5
If the CARRY FLAG is clear a byte was read, so LOOP BACK to 5D99H. Loop End Falling through means end of file, which ends the field without an error - only an EMPTY field at end of file is an error, and that was caught at 5D77H.
5DD7
PUSH HL E5
PUSH the buffer write pointer, matching the POP at 5DFDH; every route into the tail below arrives with it stacked.
5DD8
CP 22H FE 22
Compare the terminating character against a quote.
5DDA
If a closing quote ended the field, JUMP to 5DE0H to look for the separator that should follow it.
5DDC
CP 20H FE 20
Compare it against a space.
5DDE
If the field ended on anything other than a space or a quote - a comma, a carriage return or the end of the file - the separator has already been consumed, so JUMP to 5DFDH and finish.
5DE0
GOSUB to 627BH for the next byte, looking for the real separator past any trailing blanks.
5DE3
If the CARRY FLAG is set the file has ended, so JUMP to 5DFDH and finish.
5DE5
CP 20H FE 20
Compare it against a space.
5DE7
If it is a space, LOOP BACK to 5DE0H and keep skipping. Loop End Trailing blanks between a field and its comma are discarded.
5DE9
CP 2CH FE 2C
Compare it against a comma.
5DEB
If it is the comma, it has now been consumed, so JUMP to 5DFDH and finish.
5DED
CP 0DH FE 0D
Compare it against a carriage return.
5DEF
If it is none of those, it belongs to the NEXT field, so JUMP to 5DFAH to push it back into the file.
5DF1
A carriage return has been consumed. GOSUB to 627BH for the byte after it, which is entered here directly from 5DA3H as well.
5DF4
If the CARRY FLAG is set the file has ended, so JUMP to 5DFDH and finish.
5DF6
CP 0AH FE 0A
Compare it against a line feed.
5DF8
If it is, the CR-LF pair is complete and both have been consumed, so JUMP to 5DFDH and finish.
5DFA
GOSUB to 5E25H, which backs the file up by exactly one byte so that the character just examined is read again as the first character of the next field.
5DFD
POP HL E1
Recover the buffer write pointer into Register Pair HL.
5DFE
LD (HL),00H 36 00
Store a zero byte after the last character collected, giving the field the terminator that the ROM's converters expect. This is also where the buffer-full path at 5E1DH rejoins.
5E00
LD HL,(40A7H) 2A A7 40
Fetch BUFPNT from 40A7H into Register Pair HL, back at the START of the collected field.
5E03
DEC HL 2B
DECrement Register Pair HL by 1, onto the comma that 532BH planted at 5308H, because RST 10H increments before it fetches.
5E04
LD A,E 7B
Copy the second terminator into Register A. It has survived the whole collection untouched except inside quotes, and it is the only record of what kind of field this was.
5E05
SUB 20H D6 20
Subtract 20H, setting the Z FLAG only if the second terminator was a space - which is true for a NUMERIC field and for nothing else.
5E07
If the Z FLAG has been set, JUMP to 5E10H and convert the text to a number.
5E09
LD B,00H 06 00
Clear Register B, which tells the ROM's literal-string builder that there is no terminator character to look for beyond the zero byte already planted.
5E0B
GOSUB to the ROM routine STRLIT, entered past its own set-up, which builds a string descriptor for the characters at Register Pair HL and leaves it as the current value.
5E0E
POP HL E1
Recover the BASIC text pointer stacked at 5D73H.
5E0F
RET C9
RETurn to the address stacked at 5D72H: 2243H to let the ROM assign the value to the READ or INPUT variable, or 1F31H to let LET store it into the LINE INPUT variable.
5E10
RESTART to the CHRGET hook, stepping onto the first character of the collected field and returning it in Register A.
5E11
GOSUB to the ROM routine FIN, which parses the ASCII number at Register Pair HL into the floating-point accumulator and sets VALTYP to suit it. An empty field yields zero, which is why a missing number in a data file reads as 0.
5E14
POP HL E1
Recover the BASIC text pointer stacked at 5D73H.
5E15
RET C9
RETurn to the ROM address stacked at 5D72H.
5E16
AND A A7
The buffer store. Set the flags from the character in Register A.
5E17
RET Z C8
If it is a zero byte, RETurn without storing it. A zero would be read back as the field terminator, so it can never be allowed into the buffer.
5E18
LD (HL),A 77
Store the character at the write pointer.
5E19
INC HL 23
INCrement Register Pair HL by 1, advancing the write pointer.
5E1A
DEC B 05
DECrement the remaining-capacity count in Register B by 1.
5E1B
RET NZ C0
If there is still room, RETurn to the collector for the next character.
5E1C
POP BC C1
The buffer is full. Discard this routine's own return address into Register Pair BC, so that the jump below does not have to come back.
5E1D
JUMP to 5DFEH and finish the field where it stands, terminating it and converting it. The remainder of the line is left in the file to be read as the next field.
5E1F
LD D,01H 16 01
The shared "open the named program file" helper, CALLed by MERGE at 5E43H and by LOAD and RUN at 5E83H. Load Register D with 01H, selecting mode 01H, sequential input.
5E21
XOR A AF
Set Register A to ZERO, the file number, so that a program file is always opened as file zero.
5E22
JUMP to the OPEN worker at 6333H, whose RET carries control back to whichever of MERGE, LOAD or RUN called here.
5E25
LD HL,(65F8H) 2A F8 65
The back-up-one-byte helper. Fetch the current file buffer pointer from 65F8H into Register Pair HL.
5E28
LD BC,000BH 01 0B 00
Load Register Pair BC with 000BH, the offset from the start of the buffer to the file control block's LRL byte: two bytes to reach the control block plus its own offset 09.
5E2B
ADD HL,BC 09
ADD it, so that Register Pair HL addresses the logical record length.
5E2C
LD B,(HL) 46
Save the real LRL in Register B.
5E2D
LD (HL),01H 36 01
Force the LRL to 1. THIS IS THE WHOLE TRICK: @BKSP backs up by one LOGICAL RECORD, so temporarily declaring the record length to be a single byte turns it into a back-up-one-BYTE service, which is the only way a character-at-a-time reader can push a character back.
5E2F
PUSH HL E5
Save the address of the LRL byte, which has to be restored afterwards.
5E30
PUSH BC C5
Save the real LRL in Register B onto the stack.
5E31
LD BC,0FFF7H 01 F7 FF
Load Register Pair BC with minus nine.
5E34
ADD HL,BC 09
ADD it, stepping back from the LRL byte to offset 0 of the file control block itself, which is buffer offset 2.
5E35
PUSH DE D5
Save Register Pair DE, which carries the collector's two terminator characters and must survive.
5E36
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, putting the file control block address where the resident service expects it.
5E37
GOSUB to @BKSP at 4445H, the resident backspace service, which moves the file position back by one record - one byte, as things now stand.
5E3A
POP DE D1
Restore Register Pair DE and the terminator characters.
5E3B
POP BC C1
Recover the real LRL into Register B.
5E3C
POP HL E1
Recover the address of the LRL byte into Register Pair HL.
5E3D
LD (HL),B 70
Put the real logical record length back, undoing the deception before anything else can see it.
5E3E
RET Z C8
If @BKSP reported success, RETurn. The Z FLAG has survived all four POPs because none of them touches the flags.
5E3F
Otherwise JUMP to the DOS error translator at 644FH with the LDOS error number in Register A.

5E42H - MERGE

Vector slot 418BH. MERGE reads a program that was SAVEd in ASCII and folds its lines into the program already in memory, replacing any line whose number already exists. The remarkable thing about the implementation is how little of it there is: LBASIC opens the file, checks that it really is ASCII, backs the one byte it read out again, and jumps to the interpreter's ordinary command level. From there the ROM collects "typed" lines exactly as it always does - and the keyboard scan exit at 5C0EH quietly supplies each character from the file instead of from the keyboard, so the ROM's own line-insertion code does all the merging without knowing that anything is unusual. The end of the file is turned into a carriage return and a READY message by 5C4FH. This is the clearest illustration in the whole file of what the vector table is for.

5E42
POP BC C1
Discard the ROM's return address into Register Pair BC. MERGE never comes back to the statement executor; it ends by re-entering the interpreter at command level.
5E43
GOSUB to 5E1FH, which parses the filespec that follows and opens it as file zero in mode 01H, sequential input.
5E46
DEC HL 2B
DECrement Register Pair HL by 1 so that the fetch below returns the character already under the text pointer.
5E47
RESTART to the CHRGET hook, which skips blanks and returns the next character with the Z FLAG set at the end of the statement.
5E48
If the Z FLAG has been set the statement ends here, which is the only form MERGE accepts, so JUMP to 5E50H.
5E4A
Anything else is a syntax error, but the file has already been opened, so GOSUB to the READY exit at 5FC4H first to close it and reset the output device.
5E4D
JUMP to the ROM's SNERR entry and report ?SN ERROR.
5E50
XOR A AF
Set Register A to ZERO.
5E51
LD (65F4H),A 32 F4 65
Clear the chain-state byte at 65F4H, so that when the end of the file reaches 5C28H the READY message is displayed and nothing is run. A MERGE never starts a program.
5E54
GOSUB to 627BH for the first byte of the file.
5E57
If the CARRY FLAG is set the file is empty, so JUMP to the ROM's MAIN entry: there is nothing to merge and control simply returns to the READY prompt.
5E5A
INC A 3C
INCrement Register A by 1, setting the Z FLAG only if the first byte was 0FFH - the marker SAVE writes at the head of a TOKENIZED program file.
5E5B
If it was 0FFH, JUMP to 6435H in the error-code table, which raises "Bad file mode". The manual's rule that a program to be merged must have been saved in ASCII is enforced by exactly this one byte.
5E5E
GOSUB to 5E25H, which backs the file up by one byte so that the character just examined is read again. This is also the point that the LOAD path at 5EE6H jumps to when it finds an ASCII program.
5E61
JUMP to the ROM's MAIN entry, the interpreter's command level. It will now collect and insert "typed" lines one after another, every character of which comes out of the file through the keyboard scan exit at 5C0EH, until the end of the file terminates the last line and prints READY.

5E64H - The Optional Line Number Parameter

A small helper with two entry points, used three times by the RUN and LOAD parser. Entered at 5E64H it expects either the end of the statement or a comma followed by a line number; entered at 5E68H it goes straight to the line number, which is what 5E97H wants because the digit has already been seen. Either way the result is deposited in the operand cell at 5FB2H, where the "start the loaded program" tail reads it, and a value of zero there means "from the beginning".

5E64
RESTART to the CHRGET hook for the next character, with the Z FLAG set at the end of the statement.
5E65
RET Z C8
If the statement ends here there is no line number, so RETurn leaving 5FB2H as the caller set it - zero for RUN, or whatever a previous statement left for LOAD.
5E66
RESTART to the SYNCHR hook with the in-line operand 2CH, requiring a comma and raising ?SN ERROR if it is missing.
5E68
GOSUB to the ROM routine LINGET, which converts the decimal digits at Register Pair HL into a line number in Register Pair DE. The second entry point starts here.
5E6B
LD (5FB2H),DE ED 53 B2 5F
Store the line number into the operand of the LD DE,0000H at 5FB1H, which is the instruction that decides where a freshly loaded program starts.
5E6F
RET C9
RETurn to the parser.

5E70H And 5E78H - RUN With A Filename, And LOAD

Vector slots 41C7H and 4188H. RUN"filespec" and LOAD"filespec" are the same routine with one bit of difference, and the difference is carried in the chain-state byte 65F4H: RUN sets it to 0FFH so that the program is started when the load finishes, LOAD sets it to 00H so that READY is printed instead. The two entries are the skip-byte overlap once more - the bytes at 5E77H are F6 AF, which is OR 0AFH entered at 5E77H and XOR A entered at 5E78H - so the RUN entry falls through the flag-setting code it needs and the LOAD entry starts one byte later. Both then accept the documented options: a line number, "R" to leave open files open, or "V" to keep the variables as well. The V case is the one that reaches BASIC/OV3 with function selector 04H, which is the fourth selector and the only one no other call site uses. Everything from 5EB7H onwards is shared: protect the file table across the NEW, read the first byte, and take one of two completely different routes according to whether the file is tokenized or ASCII.

5E70
RET C D8
The RUN entry. RETurn at once if the CARRY FLAG is set, which the ROM's RUN code uses to say that no filespec followed the keyword; a bare RUN is then handled entirely by the ROM.
5E71
POP AF F1
Discard the ROM's return address into Register AF. From here on the statement is taken over completely.
5E72
LD A,0FFH 3E FF
Load Register A with 0FFH.
5E74
LD (6630H),A 32 30 66
Store it into 6630H, the run-time flag that 53A8H cleared during initialization and that 6367H, 63C4H and 63EFH read. Only the RUN path ever sets it, so it marks "this load is going to be executed".
5E77
OR 0AFH F6 AF
OR Register A with 0AFH, leaving 0FFH. The instruction's real purpose is that its OPERAND byte at 5E78H is the LOAD entry point, and reached there the 0AFH is executed as XOR A instead, so Register A becomes 00H and 6630H is left alone.
5E78
XOR A AF
The LOAD entry. Entered here rather than at 5E77H, the operand byte is executed in its own right and sets Register A to ZERO, which is the whole difference between LOAD and RUN.
5E79
LD (65F4H),A 32 F4 65
Store it into the chain-state byte at 65F4H: 0FFH for RUN, 00H for LOAD. 5C3CH tests it when the load finishes and 5FAAH tests it again after the ROM's RUNC has done the CLEAR.
5E7C
LD DE,0000H 11 00 00
Load Register Pair DE with zero, the "start at the first line" line number.
5E7F
LD (5FB2H),DE ED 53 B2 5F
Store it into the start-line operand cell at 5FB2H, so that a statement which specifies no line number cannot inherit one from an earlier RUN.
5E83
GOSUB to 5E1FH, which parses the filespec and opens it as file zero in mode 01H, sequential input.
5E86
LD A,(408EH) 3A 8E 40
Fetch MAXFIL from 408EH into Register A, the number of file buffers this session was started with.
5E89
LD (65F5H),A 32 F5 65
Park it at 65F5H. MAXFIL is about to be lied about, and 5ED0H puts it back.
5E8C
DEC HL 2B
DECrement Register Pair HL by 1 so that the fetch below returns the character already under the text pointer.
5E8D
RESTART to the CHRGET hook for the next character, with the Z FLAG set at the end of the statement.
5E8E
If the statement ends here there are no options, so JUMP to the shared part at 5EB7H.
5E90
RESTART to the SYNCHR hook with the in-line operand 2CH, requiring the comma that must precede any option, and returning the character after it in Register A with the CARRY FLAG set if that character is a digit.
5E92
LD (65F4H),A 32 F4 65
Store the option character into the chain-state byte, overwriting the 0FFH or 00H set at 5E79H. Any non-zero value there means "run the program afterwards", so a digit, an "R" or a "V" all imply a run even after a LOAD.
5E95
If the CARRY FLAG is clear the option is not a digit, so JUMP to 5E9CH to try the letters.
5E97
GOSUB to the line-number parser's second entry, which converts the digits straight into 5FB2H without looking for another comma.
5E9A
JUMP to 5EB3H, the join point after the options.
5E9C
CP 52H FE 52
Compare the option against 52H, ASCII "R". The manual defines it as "leave all currently open files open across the load".
5E9E
If it is not an "R", JUMP to 5EA5H.
5EA0
GOSUB to the line-number parser's first entry, which accepts a further comma and line number if one follows. The manual's rule that the letter must come before the line number falls straight out of this ordering.
5EA3
JUMP to 5EB3H.
5EA5
CP 56H FE 56
Compare the option against 56H, ASCII "V", which keeps the variables as well as the open files.
5EA7
If it is neither "R" nor "V" nor a digit, JUMP to the ROM's SNERR entry and report ?SN ERROR. Those are the only three options the statement allows.
5EAA
GOSUB to the line-number parser for an optional further comma and line number.
5EAD
LD A,04H 3E 04
Load Register A with 04H, a BASIC/OV3 function selector that no other site in the file uses.
5EAF
EX AF,AF' 08
Exchange Register AF with its alternate, putting the selector where the overlay loader expects it. The loader needs the primary accumulator for OVRLY$ and for the @LOAD error code, which is why every OV3 selector travels in the alternate set.
5EB0
GOSUB to the BASIC/OV3 loader at 54B6H. Selector 04H must be the routine that parks the current variables above STKTOP and fills in the five operands at 5F5EH, 5F65H, 5F71H, 5F7EH and 5F84H, and it must also be what writes 0D6H to the chain-state byte 65F4H, because nothing in BASIC/CMD writes any of them and 5F5CH is unusable without them. Confirm when BASIC/OV3 is disassembled.
5EB3
XOR A AF
Set Register A to ZERO.
5EB4
LD (408EH),A 32 8E 40
Store it into MAXFIL. Declaring that there are no file buffers at all is what stops the CLEAR performed below from walking the file table and closing the very file the program is being read from. The real value was parked at 5E89H and is restored at 5ED0H.
5EB7
LD HL,65F2H 21 F2 65
Load Register Pair HL with 65F2H, the permanently-zero byte used as a dummy file buffer, exactly as the line-table exit does at 5CBCH.
5EBA
LD (65FAH),HL 22 FA 65
Store it into slot zero of the file buffer pointer table, so that file zero reads as closed for the duration of the CLEAR below.
5EBD
LD HL,(40A0H) 2A A0 40
Fetch STKTOP from 40A0H into Register Pair HL, saving the real top of string space in a register.
5EC0
LD (40A0H),SP ED 73 A0 40
Store the STACK POINTER into STKTOP. For the duration of the CLEAR the top of string space is declared to be the current stack pointer, which protects everything above it - and on the V path that is precisely where BASIC/OV3 has parked the variables that must survive.
5EC4
EXX D9
Exchange the register pairs, putting the saved STKTOP in Register Pair HL out of reach of the ROM routine below.
5EC5
GOSUB to the ROM routine SCRATH, the body of NEW, which discards the current program and every variable and rebuilds the pointers. It also resets the Stack Pointer from the STKTOP just planted.
5EC8
EXX D9
Exchange back, recovering the saved STKTOP into Register Pair HL.
5EC9
LD SP,(40A0H) ED 7B A0 40
Reload the Stack Pointer from 40A0H, which still holds the value stored at 5EC0H, putting the stack back exactly where it was before the CLEAR.
5ECD
LD (40A0H),HL 22 A0 40
Store the saved STKTOP back into 40A0H, restoring the real top of string space.
5ED0
LD A,(65F5H) 3A F5 65
Fetch the parked MAXFIL from 65F5H into Register A.
5ED3
LD (408EH),A 32 8E 40
Store it back into MAXFIL, so the file table is live again now that the CLEAR is over.
5ED6
LD HL,(661AH) 2A 1A 66
Fetch the address of the first file buffer from 661AH into Register Pair HL.
5ED9
LD (65FAH),HL 22 FA 65
Store it into slot zero of the pointer table, replacing the dummy.
5EDC
LD (65F8H),HL 22 F8 65
Store it into 65F8H as well, making the program file the CURRENT file so that 627BH and the keyboard scan exit both find it.
5EDF
GOSUB to 627BH for the first byte of the program file.
5EE2
If the CARRY FLAG is set the file is empty, so JUMP to the ROM's MAIN entry and return to the READY prompt with an empty program.
5EE5
INC A 3C
INCrement Register A by 1, setting the Z FLAG only if the first byte was 0FFH, the marker at the head of a tokenized program file.
5EE6
If it was not 0FFH the file is ASCII, so JUMP to 5E5EH in the MERGE code: back the byte out and let the interpreter's command level read the program in as a stream of typed lines. LOADing an ASCII program is literally a MERGE into a program that has just been erased.
5EE9
LD IX,(65F8H) DD 2A F8 65
A tokenized program. Load Register Pair IX with the file buffer address, so that the file control block can be reached as IX+02H onwards throughout the block load below.
5EED
LD HL,(40A0H) 2A A0 40
Fetch STKTOP from 40A0H into Register Pair HL, the ceiling the program must stay below.
5EF0
LD DE,0FFAAH 11 AA FF
Load Register Pair DE with minus 86, the margin of stack and string space that must be left free above the program.
5EF3
ADD HL,DE 19
ADD it, lowering the ceiling by 86 bytes.
5EF4
LD DE,(40A4H) ED 5B A4 40
Fetch TXTTAB from 40A4H into Register Pair DE, the address the program text starts at and the destination of the load.
5EF8
XOR A AF
Set Register A to ZERO, which also clears the CARRY FLAG ready for the subtraction below.
5EF9
SBC HL,DE ED 52
SUBTRACT the start of program text from the lowered ceiling, leaving Register Pair HL holding the number of bytes available.
5EFB
If the CARRY FLAG is set the subtraction went negative and there is no room at all, so JUMP to 5F56H, which erases the program and reports ?OM ERROR.
5EFD
OR (IX+0FH) DD B6 0F
OR the zero in Register A with the byte at buffer offset 0FH, which is offset 0DH of the file control block - the HIGH byte of the ending record number. A file of 256 records or more is 64K and cannot possibly fit.
5F00
If it is not zero, JUMP to 5F56H and report ?OM ERROR.
5F02
LD B,(IX+0EH) DD 46 0E
Read the LOW byte of the ending record number into Register B, the number of 256-byte records in the file.
5F05
LD C,A 4F
Clear Register C from the zero still in Register A, so that Register Pair BC is the record count multiplied by 256 - the file's length in bytes.
5F06
SBC HL,BC ED 42
SUBTRACT that length from the space available. The CARRY FLAG is clear from the OR at 5EFDH, so no stray borrow enters the subtraction.
5F08
If the CARRY FLAG is set the program will not fit, so JUMP to 5F56H and report ?OM ERROR before a single byte has been moved.
5F0A
LD H,(IX+06H) DD 66 06
Read the HIGH byte of the file control block's buffer address, at control block offset 04, into Register H.
5F0D
LD L,(IX+05H) DD 6E 05
Read the LOW byte, at control block offset 03, into Register L. Register Pair HL now addresses the 256-byte sector area, which already holds the first sector of the file.
5F10
INC HL 23
INCrement Register Pair HL by 1, stepping over the 0FFH marker that 5EDFH has already consumed.
5F11
LD B,C 41
Clear Register B from the zero in Register C.
5F12
DEC C 0D
DECrement Register C to 0FFH, making Register Pair BC 00FFH - the 255 bytes of the first sector that follow the marker.
5F13
LDIR ED B0
Block-move those 255 bytes from the sector area to the start of program text. Register Pair DE is left one byte past the last, at TXTTAB+255.
5F15
LD (IX+06H),D DD 72 06
Store the high byte of that destination into the file control block's buffer address.
5F18
LD (IX+05H),E DD 73 05
Store the low byte. THIS IS THE HEART OF THE ROUTINE: the file's sector buffer has just been re-pointed INTO THE PROGRAM AREA, so every further read lands the sector straight where it belongs and no second copy is ever made.
5F1B
LD L,E 6B
Copy the low byte of the destination into Register L and leave it there. Because every subsequent read advances the buffer by a whole page, the low byte never changes again, and 5F3BH only has to supply the high byte to know where the program ended.
5F1C
PUSH IX DD E5
PUSH Register Pair IX onto the stack.
5F1E
POP DE D1
POP it straight back into Register Pair DE, which is the only way to copy IX to DE on a Z80.
5F1F
INC DE 13
INCrement Register Pair DE by 1.
5F20
INC DE 13
INCrement it again, so that Register Pair DE addresses the file control block at buffer offset 2, ready for the resident read service.
5F21
LD (IX+07H),B DD 70 07
Store the zero in Register B into buffer offset 07, which is offset 05 of the control block - the byte position within the current record. The record is being treated as consumed in full.
5F24
RES 7,(IX+03H) DD CB 03 BE
RESET bit 7 of buffer offset 03, which is offset 01 of the control block, its flags byte. Clearing it discards whatever state the byte-at-a-time reader had left behind so that the next read starts on a clean record boundary.
5F28
GOSUB to @READ at 4436H, the resident read-a-record service, which delivers the next 256 bytes into the buffer address held in the control block - which is now inside the program area.
5F2B
If the NZ FLAG has been set the read failed or the file has ended, so JUMP to 5F32H to find out which.
5F2D
INC (IX+06H) DD 34 06
INCrement the HIGH byte of the control block's buffer address, advancing the destination by exactly one 256-byte page.
5F30
LOOP BACK to 5F28H for the next record. Loop End The whole program is read in at full record speed with the block move done once, at 5F13H, for the odd first sector only.
5F32
CP 1CH FE 1C
Compare the returned code against 1CH, LDOS error 28, "end of file encountered".
5F34
If it matches, the file has simply run out, so JUMP to 5F3BH and finish.
5F36
CP 1DH FE 1D
Compare it against 1DH, LDOS error 29, "record number out of range", which is the other way a sequential read reports the end.
5F38
If it is neither, JUMP to the DOS error translator at 644FH and report a real disk error.
5F3B
LD H,(IX+06H) DD 66 06
Read the high byte of the control block's buffer address into Register H. With Register L untouched since 5F1BH, Register Pair HL is now the address the last record was read to.
5F3E
XOR A AF
Set Register A to ZERO.
5F3F
LD D,A 57
Clear Register D.
5F40
LD E,(IX+0AH) DD 5E 0A
Read buffer offset 0AH, which is offset 08 of the control block - the EOF byte, the number of bytes used in the file's LAST record.
5F43
DEC E 1D
DECrement Register E by 1, which borrows into Register D if the EOF byte was zero.
5F44
INC DE 13
INCrement the PAIR by 1. The two instructions together leave Register Pair DE holding the EOF byte, except that a zero becomes 0100H - which is exactly right, because an EOF byte of zero means a completely full 256-byte record.
5F45
ADD HL,DE 19
ADD it, so that Register Pair HL addresses the first byte past the end of the program.
5F46
GOSUB to the ROM routine LINKER, which walks the program from the beginning and rewrites the forward link at the head of every line. The links stored in the file belong to wherever the program was SAVEd from, so this is what makes a program loaded at a different address runnable.
5F49
INC HL 23
INCrement Register Pair HL by 1, past the end-of-program marker LINKER left it on.
5F4A
LD (40F9H),HL 22 F9 40
Store it into VARTAB at 40F9H, so that simple variables now begin immediately above the program just loaded.
5F4D
LD A,(65F4H) 3A F4 65
Fetch the chain-state byte at 65F4H into Register A.
5F50
CP 0D6H FE D6
Compare it against 0D6H, the value that means the variables of the previous program are parked above STKTOP and have to be brought back.
5F52
If it does not match, JUMP to 5F98H, the ordinary finish. Otherwise fall into 5F54H, which relocates the retained variables. This is the same test the keyboard scan exit makes at 5C2BH, from the ASCII rather than the tokenized side of the load.

5F54H - Move The Retained Variables Down Behind A Chained Program

The tail of the LOAD, RUN and MERGE code, reached from 5F52H when the chain-state byte 65F4H holds 0D6H. RUN"filespec",V is documented as loading a new program while keeping every variable and every open file, and this is the half of that mechanism which lives in BASIC/CMD: by the time it runs, the new program is in memory, VARTAB 40F9H has just been set to the byte after it, and the old variable area is sitting parked above STKTOP 40A0H where the NEW performed at 5EC5H could not reach it. The routine works out how far the variables have to travel, relocates ARYTAB 40FBH by that distance, block-moves the saved area down into place, restores STREND 40FDH, FRETOP 40D6H, STKTOP 40A0H, SAVSTK 40E8H and the Stack Pointer, clears the chain-state byte so the whole path is one-shot, and enters the new program. Five of its operands are the parked area's description - old VARTAB at 5F5EH, old ARYTAB at 5F65H, block length at 5F71H, saved FRETOP at 5F7EH and saved STKTOP at 5F84H - and all five are assembled as 0000H and are never written by any instruction in BASIC/CMD. The only candidate that can write them is BASIC/OV3, which the ",V" path CALLs at 5EB0H with function selector 04H, a selector no other call site uses; the same overlay is therefore also what writes 0D6H to 65F4H, because nothing in this file does. Confirm both when BASIC/OV3 is disassembled. Note that 5F56H-5F5BH is not part of this routine: it is the out-of-memory exit that the MERGE line-insertion loop above jumps into, and it happens to sit in the two bytes the JR at 5F54H steps over.

5F54
JUMP over the out-of-memory exit below to the relocation proper at 5F5CH. Register Pair HL still holds the new value of VARTAB, the address of the first byte above the program that has just been loaded.
5F56
GOSUB to the ROM routine SCRATH, which is the body of NEW: it discards the program text and every variable and resets the pointers. Reached from 5EFBH, 5F00H and 5F08H, each of which has discovered that the incoming program will not fit below the string area. The partly loaded program is unusable, so it is thrown away before the error is raised.
5F59
JUMP to the ROM's OMERR entry point, which reports "?OM ERROR", out of memory. This does not return.
5F5C
PUSH HL E5
Save Register Pair HL, the new VARTAB, onto the stack. It is needed twice: once as the subtrahend that gives the relocation distance and once as the destination of the block move.
5F5D
LD DE,0000H 11 00 00
Load Register Pair DE with the operand at 5F5EH, which is the OLD value of VARTAB, the address the retained variables started at under the previous program. Assembled as 0000H and filled in by BASIC/OV3.
5F60
AND A A7
Clear the CARRY FLAG so that the 16-bit subtraction below does not borrow. AND A alters no register.
5F61
SBC HL,DE ED 52
SUBTRACT the old VARTAB in Register Pair DE from the new VARTAB in Register Pair HL. Register Pair HL now holds the DISTANCE every retained pointer has to move, positive when the incoming program is longer than the one it replaced.
5F63
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, putting the relocation distance into Register Pair DE ready to be added to a pointer.
5F64
LD HL,0000H 21 00 00
Load Register Pair HL with the operand at 5F65H, the OLD value of ARYTAB, the address at which the arrays began within the parked block. Assembled as 0000H and filled in by BASIC/OV3.
5F67
ADD HL,DE 19
ADD the relocation distance in Register Pair DE, giving the address the arrays will occupy once the block has been moved down.
5F68
LD (40FBH),HL 22 FB 40
Store the relocated address into ARYTAB at 40FBH, the pointer that tells BASIC where simple variables end and arrays begin.
5F6B
POP DE D1
Recover the new VARTAB from the stack into Register Pair DE. It is the DESTINATION of the block move.
5F6C
LD HL,(40A0H) 2A A0 40
Fetch STKTOP from 40A0H into Register Pair HL. While the chain was in progress STKTOP was lowered to the byte below the parked variable block, so this is one less than the block's first byte.
5F6F
INC HL 23
INCrement Register Pair HL by 1, making it the first byte of the parked block and therefore the SOURCE of the block move.
5F70
LD BC,0000H 01 00 00
Load Register Pair BC with the operand at 5F71H, the LENGTH in bytes of the parked variable block. Assembled as 0000H and filled in by BASIC/OV3.
5F73
LD A,B 78
Copy the high byte of the length into Register A.
5F74
OR C B1
OR the low byte into it, setting the Z FLAG only when the length is zero. LDIR treats a count of zero as 65536, so a zero-length block must not be allowed to reach it.
5F75
If the Z FLAG has been set, meaning there are no variables to carry over, JUMP past the block move to 5F79H.
5F77
LDIR ED B0
Block-move Register Pair BC bytes from the parked area at Register Pair HL down to the new variable area at Register Pair DE. Register Pair DE is left pointing one byte past the last byte moved, which is exactly the new end of the variable area.
5F79
LD (40FDH),DE ED 53 FD 40
Store Register Pair DE into STREND at 40FDH, the first free byte above every variable. On the zero-length path Register Pair DE is still the new VARTAB, which is correct: with no variables the area is empty.
5F7D
LD HL,0000H 21 00 00
Load Register Pair HL with the operand at 5F7EH, the value FRETOP held before the chain began. Assembled as 0000H and filled in by BASIC/OV3.
5F80
LD (40D6H),HL 22 D6 40
Store it into FRETOP at 40D6H, the top of the live string space. The string data itself was never moved, which is why RUN with the V parameter keeps string values as well as numeric ones.
5F83
LD HL,0000H 21 00 00
Load Register Pair HL with the operand at 5F84H, the value STKTOP held before the chain began. Assembled as 0000H and filled in by BASIC/OV3. This is also the entry point the long error message handler uses at 5A4AH when an error is raised while the chain-state byte is set, to unwind the half-built chain before anything else is done.
5F86
LD (40A0H),HL 22 A0 40
Store it into STKTOP at 40A0H, putting the top of string space back where it was and releasing the space the parked block occupied.
5F89
DEC HL 2B
DECrement Register Pair HL by 1.
5F8A
DEC HL 2B
DECrement Register Pair HL by 1 again, so that Register Pair HL is two below the restored STKTOP. That is where BASIC's stack starts.
5F8B
LD (40E8H),HL 22 E8 40
Store it into SAVSTK at 40E8H, the stack pointer BASIC restores at the start of every statement.
5F8E
XOR A AF
Set Register A to ZERO and clear all flags.
5F8F
LD (65F4H),A 32 F4 65
Clear the chain-state byte at 65F4H. The 0D6H that steered control here is now spent, so the keyboard scan at 5C28H and the long error handler at 5A43H both go back to their ordinary behaviour and this path cannot be taken a second time.
5F92
LD SP,HL F9
Load the Stack Pointer from Register Pair HL. Everything the chain had stacked is abandoned in one instruction, including the return address of any CALL that reached here - which is why the CALL 5F83H at 5A4AH never comes back and the LD SP,HL that follows it at 5A4DH can never be executed.
5F93
GOSUB to 5FC4H, the READY exit, which resets the output device to the video display and tidies the file state. It is CALLed rather than jumped to because control has to come back for the line-number decision below.
5F96
JUMP to 5FB1H, the common "start the loaded program" tail, which runs it from the line number stored at 5FB2H or from the beginning if that word is zero.

5F98H - Finish The Load And Start The Program

The ordinary tail of LOAD and RUN, reached from 5F52H when no variables are being carried over. It performs the CLEAR that RUN implies, then reads the chain-state byte once more to decide between printing READY and running the program. The one subtlety is that MAXFIL is temporarily set to zero across the CLEAR, exactly as it is at 5EB3H, so that the ROM's CLEAR cannot close the buffers - the "R" and "V" options both promise that open files survive a chained load, and this is where that promise is kept. The second half, from 5FB1H, is the common "start the program" entry that 5C41H and 5F96H both arrange to reach.

5F98
LD HL,408EH 21 8E 40
Load Register Pair HL with the address of MAXFIL, which is about to be read, cleared and restored.
5F9B
LD A,(HL) 7E
Read the number of file buffers into Register A.
5F9C
LD (65F5H),A 32 F5 65
Park it at 65F5H, the same cell the parser used at 5E89H.
5F9F
LD (HL),00H 36 00
Declare that there are no file buffers, so that the CLEAR below leaves every open file exactly as it is.
5FA1
GOSUB to the ROM routine RUNC, which performs the CLEAR that always precedes a RUN: variables are discarded, the pointers are rebuilt and the stack is reset.
5FA4
LD A,(65F5H) 3A F5 65
Fetch the parked buffer count back into Register A.
5FA7
LD (408EH),A 32 8E 40
Store it back into MAXFIL, so the file table is usable again.
5FAA
LD A,(65F4H) 3A F4 65
Fetch the chain-state byte at 65F4H into Register A: 00H after a plain LOAD, 0FFH after RUN"filespec", or the option character stored at 5E92H.
5FAD
AND A A7
Set the flags from it.
5FAE
If the Z FLAG has been set this was a plain LOAD with no options, so JUMP to the ROM's READY entry and leave the program sitting in memory.
5FB1
LD DE,0000H 11 00 00
Load Register Pair DE from the operand cell at 5FB2H, the line number the program is to start at. 5E7FH clears it and 5E6BH fills it in when a line number is given. This is also the entry that 5C40H plants on the stack and 5F96H jumps to.
5FB4
LD A,D 7A
Copy the high byte of the line number into Register A.
5FB5
OR E B3
OR the low byte into it, setting the Z FLAG when no line number was given.
5FB6
If the Z FLAG has been set, JUMP to 5FC1H and start at the first line, which is where RUNC has already left the text pointer.
5FB8
GOSUB to the ROM routine FNDLIN, which searches the program for the line number in Register Pair DE and returns Register Pair BC pointing at it.
5FBB
If the NZ FLAG has been set the line does not exist, so JUMP to the ROM's USERR entry and report ?UL ERROR. The manual's warning that the line number given to RUN must exist is enforced here.
5FBE
DEC BC 0B
DECrement Register Pair BC by 1, onto the byte BEFORE the line, because the statement executor increments before it fetches.
5FBF
LD H,B 60
Copy the high byte into Register H.
5FC0
LD L,C 69
Copy the low byte into Register L, giving the statement executor its text pointer.
5FC1
JUMP to the ROM's NEWSTT entry and begin executing. The chained program is now running and nothing returns here.

5FC4H - READY Exit

Vector slot 41ACH, which the ROM CALLs at 1A1CH on its way to displaying the READY prompt, and also called directly from 5C36H, 5E4AH, 5F93H and 6040H whenever LBASIC finishes an operation that has to leave the machine at command level. It does three things: reset the output device to the video display, close every open file, and - only when it really was the ROM's READY path that called it - run the protection check at 63EDH. That last test is made by looking at the return address rather than by any flag, so the four internal callers get the file closing without the check.

5FC4
GOSUB to the ROM routine FINPRT, which sets the output device flag PRTFLG 409CH back to 00H, the video display, and turns the cassette off. Whatever a PRINT# left pointing at a file is undone here.
5FC7
GOSUB to 6194H, the close-one-file worker, with the file number in Register A. FINPRT has just left Register A at zero, so what is closed here is FILE ZERO - the internal file that LOAD, SAVE, MERGE and CMD"L" use. The operator's own files are untouched; only a CLOSE statement or the NEW and END exit at 6074H reaches them.
5FCA
POP HL E1
POP the return address into Register Pair HL so that the caller can be identified.
5FCB
PUSH DE D5
Save Register Pair DE onto the stack, because the comparison below needs it.
5FCC
LD DE,1A1FH 11 1F 1A
Load Register Pair DE with 1A1FH, the address immediately after the CALL 41ACH inside the ROM's READY code.
5FCF
RESTART to the DCOMPR hook at 4006H, setting the Z FLAG when the return address is 1A1FH and the caller was therefore the ROM itself rather than one of the four internal callers.
5FD0
POP DE D1
Restore Register Pair DE from the stack. POP alters no flag.
5FD1
PUSH HL E5
PUSH the return address back onto the stack so that the jump at 5FD5H eventually delivers control to it.
5FD2
If the caller was the ROM's READY code, GOSUB to 63EDH, which clears the run-in-progress flag 6630H and, if 6631H says that the DOS protection scheme has cleared memory, erases the program text, performs a NEW and raises error 90H, "Protection has cleared memory".
5FD5
JUMP to 1BAFH, the tail of the ROM's RUNC block, whose RET carries control to the address left on the stack at 5FD1H.

5FD8H - Post-Tokenize Exit

Vector slot 41B2H, which the ROM CALLs at 1AA1H the moment a line has been tokenized and it is about to decide whether that line is a program line or a direct statement. LBASIC only cares about the direct statement case, and only when a file is current - because a file is only current at this point during an ASCII LOAD or MERGE, and a "direct statement" arriving from a file means the file is not a BASIC program at all, or that one of its lines was longer than the 240 characters the collector allows. That is precisely the condition the manual describes for error 66.

5FD8
RET C D8
If the CARRY FLAG is set the line began with a line number and is a program line, so RETurn and let the ROM insert it. This is the path every line of a MERGE takes.
5FD9
POP AF F1
A direct statement. Discard the ROM's return address into Register AF; both exits below reach the ROM by a jump instead.
5FDA
PUSH HL E5
Save the text pointer onto the stack while Register Pair HL is borrowed for the current-file test.
5FDB
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL.
5FDE
LD A,H 7C
Copy the high byte of the pointer into Register A.
5FDF
OR L B5
OR the low byte into it, setting the Z FLAG only when no file is current.
5FE0
LD E,84H 1E 84
Load Register E with 84H, which is 132, the ROM error number for BASIC error 66, "Direct statement in file". LD alters no flag, so the test above survives.
5FE2
If a file IS current, JUMP to the ROM's error entry with the number in Register E. The saved text pointer is abandoned on the stack, which does not matter because the error path resets the Stack Pointer.
5FE5
POP HL E1
No file is current, so this is an ordinary direct statement typed by the operator. Recover the text pointer.
5FE6
GOSUB to 63EDH, the protection check, which clears the run-in-progress flag and refuses to let anything be executed if the DOS has cleared memory behind a protected program.
5FE9
JUMP to 1D5AH inside the ROM's statement executor, which fetches the first token of the line and runs it.

5FECH - SAVE

Vector slot 41A0H. SAVE is really two routines sharing an OPEN. SAVE"filespec",A hands the whole job to the ROM's LIST code at 2B2EH, so an ASCII save is literally a LIST with the file as the output device - which is exactly why an ASCII SAVE and a MERGE are inverses of one another. SAVE"filespec" with nothing after it writes the tokenized program straight out of the program area. It plants the 0FFH marker that the loader tests at 5EE9H in the byte below the text, re-points the file control block's own sector buffer INTO the program area so that every record is written from where the program already is, PRE-EXTENDS the file to its finished length before a single record goes out, and only then writes the records one after another. The pre-extension is the point of the @POSN at 601DH: if the disk is going to fill up, it fills up before any of the program has been written.

5FEC
LD D,02H 16 02
Load Register D with 02H, the mode code for a sequential OUTPUT file. The OPEN worker at 6333H stores the low two bits of Register D as the file's mode byte and reads the rest of it to choose between @INIT and @OPEN.
5FEE
GOSUB to 5E21H, which clears Register A and drops into the OPEN worker. Register A is doing two jobs there: it is the FILE NUMBER, so a program file is always opened as file zero, and it is the byte parked at 638CH whose being zero is what allows the default extension /BAS to be supplied at 6392H.
5FF1
DEC HL 2B
DECrement Register Pair HL by 1, backing the text pointer onto the character before the current one so that the fetch below re-reads the character the OPEN stopped on instead of stepping past it.
5FF2
RESTART to the character fetch hook at 4003H, which INCrements Register Pair HL again, skips blanks and sets the Z FLAG when the character is a statement terminator.
5FF3
If the Z FLAG has been set, nothing follows the filespec, so JUMP to 5FFCH and write the program in its tokenized form.
5FF5
RESTART to the SYNCHR hook at 4000H, requiring the character to be 2CH (ASCII: ,).
5FF7
and requiring the next one to be 41H (ASCII: A). The A option is a bare letter rather than a token, so it costs a second syntax check.
5FF9
JUMP to the ROM's LIST body. Every line of the program is now listed in the ordinary way - but a file is current, so the byte output hook at 5C00H diverts each character to the file instead of to the screen. That is the whole of an ASCII SAVE.
5FFC
LD HL,(40F9H) 2A F9 40
Load Register Pair HL with VARTAB at 40F9H, the first byte above the program text.
5FFF
LD DE,(40A4H) ED 5B A4 40
Load Register Pair DE with TXTTAB at 40A4H, the first byte of the program text.
6003
XOR A AF
Set Register A to ZERO, which also CLEARS the CARRY FLAG so that no stray borrow enters the subtraction below.
6004
SBC HL,DE ED 52
SUBTRACT Register Pair DE from Register Pair HL. Register Pair HL is now the length of the program text in bytes.
6006
DEC A 3D
DECrement Register A by 1, giving 0FFH.
6007
DEC DE 1B
DECrement Register Pair DE by 1, onto the byte immediately BELOW the program text.
6008
LD (DE),A 12
Store 0FFH there. That byte is normally the zero the ROM keeps below the program, and 0FFH is the marker the loader reads at 5EE9H to tell a tokenized file from an ASCII one. Planting it in memory rather than writing it separately is what lets record zero be written straight out of the program area with the marker already in place.
6009
EX DE,HL EB
Exchange the pair, so Register Pair HL addresses the marker byte and Register Pair DE holds the length. Register Pair HL keeps that address for the rest of the routine.
600A
PUSH DE D5
Save the length onto the stack; it is wanted again at 602CH, after the pre-extension.
600B
LD C,D 4A
Copy the HIGH byte of the length into Register C. The length divided by 256 is the record number of the LAST record the program will occupy.
600C
LD B,00H 06 00
Clear Register B, so that Register Pair BC is that record number.
600E
LD IX,(65F8H) DD 2A F8 65
Load Register IX with the current file buffer pointer from 65F8H, which the OPEN above has just set. Every offset below is measured from it: IX+02H is the 32-byte file control block, IX+05H and IX+06H its buffer address, IX+0AH its end-of-file byte.
6012
LD (IX+06H),H DD 74 06
Store the HIGH byte of the marker address into the control block's buffer address.
6015
LD (IX+05H),L DD 75 05
Store the LOW byte. The file's sector buffer now points at the marker byte, one below the program text, so every record written comes straight out of the program area and no second copy is ever made. This is the LOADER's trick at 5F15H run backwards.
6018
PUSH IX DD E5
Push Register IX onto the stack.
601A
POP DE D1
and POP it into Register Pair DE, which is the cheapest way to copy Register IX into a general register pair.
601B
INC DE 13
INCrement Register Pair DE by 1.
601C
INC DE 13
INCrement Register Pair DE by 1 again. Register Pair DE now addresses buffer offset 2, the file control block, which is what every DOS call below expects.
601D
GOSUB to @POSN, which positions the file at the record number in Register Pair BC. That is the LAST record the program will occupy, and the file has only just been created, so this call is deliberately made past the end of the file and its return code is not examined at all. Positioning at the last record and writing it once is the standard LDOS way of claiming a file's whole allocation in one go - SYS6's CREATE and the SPOOL module both do it - and it means that a disk which cannot hold the program says so before any of the program has been written.
6020
RES 6,(IX+03H) DD CB 03 B6
RESET bit 6 of buffer offset 03, which is offset 01 of the file control block, its flags byte. Two other places in the file touch that byte: the OPEN worker SETS this same bit 6 at 63E7H when, and only when, a file is opened in random mode, and the tokenized loader clears bit 7 at 5F24H. Clearing it here, immediately after the only @POSN this routine makes and immediately before a purely sequential write loop, puts the control block back into the state an ordinary sequential output file is in. What the DOS itself makes of bit 6 cannot be settled from this side of the interface and is not guessed at here.
6024
GOSUB to @WRITE, which writes 256 bytes from the file's buffer to the record just positioned. What goes out is the BEGINNING of the program rather than its end, which does not matter: the loop below writes that record again with the right contents. The only purpose of this call is to make the DOS allocate the space.
6027
If the write succeeded, GOSUB to @REW, which rewinds the file to record zero so that the real write can start at the beginning.
602A
If either call failed, JUMP to 6032H, which restores the byte below the program before the error is reported.
602C
POP BC C1
Recover the program length into Register Pair BC.
602D
INC C 0C
INCrement Register C by 1. The file is one byte longer than the program because of the marker, so the low byte of the length plus one is the number of bytes used in the last record - which is exactly what belongs in the control block's end-of-file byte, with zero meaning a full 256.
602E
INC B 04
INCrement Register B by 1, giving the number of whole records to write: a program of 0250H bytes occupies three records, one of 01FFH bytes exactly two. These are two independent increments and not an INC BC, and that is what makes both answers come out right whatever the low byte does.
602F
Loop Start
GOSUB to @WRITE and send one record out of the program area.
6032
LD (HL),00H 36 00
Restore the byte below the program text to ZERO. It is done inside the loop rather than before it because record zero contains that byte and has to go out with the 0FFH marker still in place; and it is done before the error test below because LD alters no flag, so the marker is cleaned up even when the write has failed. 602AH also lands here.
6034
If the write failed, JUMP to 644FH, the DOS error translator, which turns the LDOS error number into a BASIC error through BASIC/OV3.
6037
INC (IX+06H) DD 34 06
INCrement the HIGH byte of the control block's buffer address, moving the sector buffer 256 bytes further up the program area ready for the next record.
603A
DECrement Register B and JUMP back to 602FH while it is not zero, writing every remaining record.
603C
LD (IX+0AH),C DD 71 0A
Store the byte count computed at 602DH into buffer offset 0AH, which is offset 08 of the file control block, its end-of-file byte. The DOS copies it into the directory when the file is closed, and it is what the loader reads back at 5EF6H.
603F
LD (HL),B 70
Register B is zero after the loop, so this writes ZERO into the byte below the program text once more. The loop body has already done exactly that at 6032H on every path that reaches here, so the instruction changes nothing; it is recorded as it stands.
6040
JUMP to the READY exit, which resets the output device, CLOSES FILE ZERO through 6194H and returns the machine to command level. The file is closed there, not here.

6043H - CLOSE

Vector slot 4185H, and one of the neater pieces of housekeeping in the file. The ROM arrives with the flags the character fetch left when it ended the CLOSE token, so the Z FLAG on entry IS the argument test: Z means a bare CLOSE and every file is to be shut, NZ means a list of file numbers follows. Both halves end at the same worker at 6194H, which is entered by pushing its address and RETurning to it rather than by a CALL, because the address has to be carried in a register pair across the loop anyway. The all-files loop counts DOWN from MAXFIL to zero inclusive, so it closes LBASIC's own file zero as well as the operator's files - which is the reason 53CFH builds MAXFIL+1 buffers rather than MAXFIL.

6043
Load Register Pair BC with 6194H, the address of the close-one-file worker. It is held in a register pair because both loops below call it by pushing it and RETurning. LD alters no flag, so the condition the ROM arrived with is untouched.
6046
LD A,(408EH) 3A 8E 40
Load Register A with MAXFIL at 408EH, the number of file buffers. LD A,(nn) alters no flag either, which is what allows the entry condition to survive as far as the next instruction.
6049
If the NZ FLAG has been set, the fetch that ended the CLOSE token did not find a statement terminator, so a list of file numbers follows: JUMP to 6063H and read the first of them.
604B
PUSH HL E5
A bare CLOSE. Save the text pointer, which nothing below preserves.
604C
PUSH BC C5
Loop Start
Save the worker address.
604D
PUSH AF F5
Save the file number, which is counting down in Register A.
604E
LD DE,6054H 11 54 60
Load Register Pair DE with 6054H, the address to come back to.
6051
PUSH DE D5
Push it as the return address.
6052
PUSH BC C5
Push the worker address on top of it.
6053
RET C9
RETurn - to 6194H, with 6054H underneath as its return address. Two pushes and a RET are how a Z80 calls a routine whose address is in a register pair.
6054
POP AF F1
Recover the file number.
6055
POP BC C1
Recover the worker address, which the worker does not preserve.
6056
DEC A 3D
DECrement the file number, setting the SIGN FLAG once it goes below zero.
6057
While the file number is still zero or above, JUMP back and close the next one down. File zero, LBASIC's own, is closed last.
605A
POP HL E1
Recover the text pointer.
605B
RET C9
RETurn to the ROM.
605C
POP BC C1
Reached by the worker's RET when a file from a LIST has been closed. Recover the worker address into Register Pair BC, where 6063H expects to find it.
605D
POP HL E1
Recover the text pointer.
605E
LD A,(HL) 7E
Read the character the file number ended on.
605F
CP 2CH FE 2C
Compare it against 2CH (ASCII: ,).
6061
RET NZ C0
If it is not a comma the list has ended, so RETurn to the ROM.
6062
RESTART to the character fetch hook, stepping past the comma onto the next file number.
6063
PUSH BC C5
Loop Start
Save the worker address across the expression evaluation below.
6064
LD A,(HL) 7E
Read the current character.
6065
CP 23H FE 23
Compare it against 23H (ASCII: #), which may optionally precede a file number.
6067
If the hash mark is there, GOSUB to the ROM's CHRGTR to step past it. The ROM routine is called directly rather than through the RST 10H hook because no LBASIC keyword can follow a hash mark.
606A
GOSUB to the ROM's GETBYT, which evaluates the expression and reduces it to an 8-bit value in Register A - the file number.
606D
EX (SP),HL E3
Exchange the text pointer with the stacked worker address, so Register Pair HL holds the worker address and the text pointer is parked safely on the stack.
606E
PUSH HL E5
Push the worker address, which 605CH pops back.
606F
LD DE,605CH 11 5C 60
Load Register Pair DE with 605CH, the address to come back to.
6072
PUSH DE D5
Push it on top, so it is the return address the worker sees.
6073
JP (HL) E9
JUMP to the worker at 6194H with the file number in Register A.

6074H - NEW And END Close-Files Exit

Vector slot 41BBH, which the ROM CALLs at 1B8CH when NEW is executed and again at 1DB0H when a program reaches END or STOP. Both have to shut every open file, and all this routine does is manufacture the condition the CLOSE code reads as "no file list follows" and hand over to it.

6074
PUSH DE D5
Save Register Pair DE, which the ROM is using and the close code is not obliged to preserve.
6075
PUSH BC C5
Save Register Pair BC for the same reason.
6076
XOR A AF
Set Register A to ZERO, which also SETS the Z FLAG. That is precisely the condition 6049H reads as a bare CLOSE, so every file will be closed.
6077
GOSUB to the CLOSE code, which walks the file table from MAXFIL down to zero.
607A
POP BC C1
Restore Register Pair BC.
607B
POP DE D1
Restore Register Pair DE.
607C
XOR A AF
Set Register A to ZERO and the Z FLAG once more. The close leaves neither in a defined state, and the ROM continues from here on the assumption that both are clear.
607D
RET C9
RETurn to the ROM.

607EH - FIELD

Vector slot 417CH. FIELD divides a random file's 256-byte data area into named string variables, and it does it without moving a single character: each variable's DESCRIPTOR is simply pointed at a slice of the buffer, with the length the statement gave it. Everything that later assigns to one of those variables through LSET or RSET at 60D8H therefore writes directly into the sector that will go to disk. The loop is unusually economical with registers - Register Pair BC is the buffer address on the first pass round and the WIDTH of the field just placed on every pass after it, so the single ADD HL,BC at 6098H serves both as "the data area starts here" and as "the next field starts here".

607E
GOSUB to the file reference parser at 5CEFH, which reads an optional hash mark and a file number, range-checks it against MAXFIL and returns Register Pair BC pointing at that file's buffer with Register A holding its mode and the flags set from it.
6081
If the Z FLAG has been set the mode is zero and the file is not open, so JUMP to 6438H and raise "Bad file number".
6084
SUB 03H D6 03
SUBTRACT 3 from the mode, which gives zero only for a random file.
6086
If it is anything else, JUMP to 6435H and raise "Bad file mode". FIELD has no meaning on a sequential file.
6089
EX DE,HL EB
Exchange the pair, parking the text pointer in Register Pair DE while Register Pair HL is borrowed.
608A
LD HL,000BH 21 0B 00
Load Register Pair HL with 000BH.
608D
ADD HL,BC 09
ADD the buffer address. Register Pair HL now addresses buffer offset 0BH, which is offset 09 of the file control block, the logical record length.
608E
LD A,(HL) 7E
Read it.
608F
LD (65F3H),A 32 F3 65
Store it at 65F3H, the FIELD work cell, where the overflow test at 60BAH reads it back. Zero there means a record length of 256.
6092
XOR A AF
Set Register A to ZERO. It is the running total of the field widths declared so far.
6093
EX DE,HL EB
Bring the text pointer back into Register Pair HL.
6094
LD DE,0022H 11 22 00
Load Register Pair DE with 0022H, the offset from the start of a file buffer to its 256-byte data area.
6097
EX DE,HL EB
Loop Start
Exchange the pair. On the first pass Register Pair HL becomes 0022H while Register Pair BC is still the buffer address; on every pass after it Register Pair HL is the address of the field just placed and Register Pair BC is that field's width.
6098
ADD HL,BC 09
So one instruction says "the data area begins here" the first time round and "the next field begins here" every time afterwards.
6099
LD B,A 47
Copy the running total of the widths into Register B, where the overflow arithmetic below wants it.
609A
EX DE,HL EB
Exchange again, so Register Pair HL is the text pointer and Register Pair DE the address of the field about to be described.
609B
LD A,(HL) 7E
Read the next character of the statement.
609C
CP 2CH FE 2C
Compare it against 2CH (ASCII: ,).
609E
RET NZ C0
If it is not a comma there are no more fields, so RETurn.
609F
PUSH DE D5
Save the field address.
60A0
PUSH BC C5
Save the running total in Register B across the expression evaluation.
60A1
GOSUB to 2B1BH, the entry one byte below the ROM's GETBYT which fetches the next character first, returning the field width in Register A.
60A4
PUSH AF F5
Save the width.
60A5
RESTART to the SYNCHR hook requiring 41H (ASCII: A).
60A7
and again requiring 53H (ASCII: S). AS is not a token, so the two letters of the keyword cost one syntax check each.
60A9
GOSUB to the ROM's PTRGET, which locates or creates the variable named next and returns Register Pair DE pointing at its descriptor.
60AC
GOSUB to the ROM's CHKSTR, which raises ?TM ERROR unless that variable is a string.
60AF
POP AF F1
Recover the field width.
60B0
POP BC C1
Recover the running total in Register B.
60B1
EX (SP),HL E3
Exchange the text pointer with the stacked field address: Register Pair HL becomes the field address and the text pointer waits on the stack.
60B2
LD C,A 4F
Copy the width into Register C, where it becomes the step Register Pair BC supplies at 6098H next time round.
60B3
ADD A,B 80
ADD the running total to it.
60B4
If there was no carry the fields still fit inside 256 bytes, so JUMP to 60B9H.
60B6
A carry with a non-zero result means the fields have passed 256 bytes rather than landed exactly on them, so JUMP to 644AH and raise "Field overflow".
60B9
LD B,A 47
Register B becomes the new running total.
60BA
LD A,(65F3H) 3A F3 65
Read the record length back from the work cell.
60BD
CP B B8
Compare it against the running total.
60BE
If the record is at least as long as the fields declared so far, there is still room: JUMP to 60C4H.
60C0
AND A A7
Set the flags from the record length.
60C1
Unless it is zero - which means 256, and cannot be exceeded by a total that has not carried - JUMP to 644AH and raise "Field overflow".
60C4
LD A,B 78
Copy the running total into Register A.
60C5
AND A A7
Set the flags from it. Zero means the fields now fill the record exactly.
60C6
EX DE,HL EB
Register Pair DE becomes the field address and Register Pair HL the variable's descriptor.
60C7
LD (HL),C 71
Store the width as the string's LENGTH.
60C8
INC HL 23
INCrement Register Pair HL by 1.
60C9
LD (HL),E 73
Store the low byte of the field address.
60CA
INC HL 23
INCrement Register Pair HL by 1.
60CB
LD (HL),D 72
and the high byte. The variable now IS that slice of the sector buffer; nothing has been copied and nothing is in string space.
60CC
LD B,00H 06 00
Clear Register B so that Register Pair BC is just the width, ready to be added to the field address at 6098H.
60CE
POP HL E1
Recover the text pointer.
60CF
If the record is not yet exactly full, JUMP back for the next field.
60D2
LD A,C 79
The record is exactly full. Copy the last width into Register A.
60D3
AND A A7
Set the flags from it.
60D4
RET NZ C0
If that width was not zero the field list is complete, so RETurn.
60D5
A zero-width field placed nothing, so JUMP back and read the next one.

60D8H And 60D9H - RSET And LSET

Vector slots 419AH and 4197H, and another skip-byte overlap: the two bytes F6 37 are OR 37H entered at 60D8H, and the 37H alone is SCF entered at 60D9H. OR always CLEARS the CARRY FLAG, so RSET arrives with it clear and LSET with it set, and that one flag steers the whole routine to its two padding calls at 6145H and 6157H. Everything else is shared. The body assigns a string to a fielded variable WITHOUT altering that variable's length or its address: the characters are copied into the space FIELD gave it, truncated if there are too many and padded with spaces if there are too few - at the right-hand end for LSET, at the left-hand end for RSET. The one complication is a target whose characters live inside the program text, which must not be written in place; 60FDH-6115H detects that case and gives the target a private copy in string space first.

60D8
OR 37H F6 37
RSET, vector slot 419AH. The OR leaves Register A non-zero and, far more to the point, CLEARS the CARRY FLAG. Its operand byte at 60D9H is the other entry point.
60D9
SCF 37
LSET, vector slot 4197H. Entered here the 37H is executed as an opcode in its own right and SETS the CARRY FLAG. Two bytes, two opposite meanings - the same trick CMD"R" and CMD"T" play at 580EH.
60DA
PUSH AF F5
Save Register A and with it the CARRY FLAG that says which of the two statements this is. It is not looked at again until 6144H, after everything else has been settled.
60DB
GOSUB to the ROM's PTRGET for the target variable, returning Register Pair DE pointing at its descriptor.
60DE
GOSUB to the ROM's CHKSTR; ?TM ERROR unless the target is a string.
60E1
PUSH DE D5
Save the target descriptor address.
60E2
RESTART to the SYNCHR hook requiring token 0D5H, the equals sign.
60E4
GOSUB to the ROM's FRMEVL to evaluate the expression on the right of it.
60E7
POP BC C1
Recover the target descriptor address into Register Pair BC.
60E8
EX (SP),HL E3
Exchange the text pointer with the saved flags word, so Register Pair HL holds the flags word and the text pointer takes its place at the bottom of this routine's stack frame.
60E9
PUSH HL E5
Push the flags word back, now above the text pointer.
60EA
PUSH BC C5
Push the target descriptor address.
60EB
GOSUB to the ROM's FRESTR, which checks the result is a string, releases its temporary and returns Register Pair HL pointing at its descriptor.
60EE
LD B,(HL) 46
Read the SOURCE string's length into Register B.
60EF
EX (SP),HL E3
Exchange, so Register Pair HL is the target descriptor and the source descriptor waits on the stack.
60F0
LD A,(HL) 7E
Read the TARGET string's length.
60F1
LD C,A 4F
Copy it into Register C. Register Pair BC now carries both lengths.
60F2
PUSH BC C5
Save them.
60F3
PUSH HL E5
Save the target descriptor address.
60F4
PUSH AF F5
Save the target length.
60F5
INC HL 23
INCrement Register Pair HL by 1.
60F6
LD E,(HL) 5E
Read the low byte of the target's data address.
60F7
INC HL 23
INCrement Register Pair HL by 1.
60F8
LD D,(HL) 56
and the high byte. Register Pair DE is where the target's characters live.
60F9
AND A A7
Set the flags from the target length.
60FA
A zero-length target can hold nothing, so JUMP to 6152H, unwind the stack and return.
60FD
LD HL,(40A4H) 2A A4 40
Load Register Pair HL with TXTTAB, the bottom of the program text.
6100
RESTART to the DCOMPR hook, comparing TXTTAB against the target's data address.
6101
If the characters lie BELOW the program text they are in a file buffer - which is exactly what FIELD produces - and may be written in place, so JUMP to 612EH.
6103
LD HL,(40F9H) 2A F9 40
Load Register Pair HL with VARTAB, the top of the program text.
6106
Compare that against the target's data address as well.
6107
If the characters lie ABOVE the program text they are in string space and may also be written in place, so JUMP to 612EH.
6109
LD E,C 59
The characters are INSIDE the program text and must not be modified. Copy the target length into Register E.
610A
LD D,00H 16 00
Clear Register D, so Register Pair DE is that length.
610C
LD HL,(40A0H) 2A A0 40
Load Register Pair HL with STKTOP, the bottom of string space.
610F
ADD HL,DE 19
ADD the length, giving the lowest address a copy could occupy.
6110
EX DE,HL EB
Move it into Register Pair DE.
6111
LD HL,(40D6H) 2A D6 40
Load Register Pair HL with FRETOP, the bottom of the strings currently in use.
6114
Compare the two.
6115
If there is not room for the copy below the live strings, JUMP to 6165H, which forces a garbage collection.
6117
POP AF F1
Recover the target length.
6118
LD A,C 79
and put it in Register A, where the allocator wants it.
6119
GOSUB to the ROM's GETSPA, which reserves Register A bytes of string space, collecting garbage if it must, and returns their address in Register Pair DE.
611C
POP HL E1
Recover the target descriptor address.
611D
POP BC C1
Recover the two lengths.
611E
EX (SP),HL E3
Exchange with the stacked source descriptor.
611F
PUSH DE D5
Save the address of the newly reserved space.
6120
PUSH BC C5
Save the lengths again.
6121
GOSUB to the ROM's FRESTR once more.
6124
POP BC C1
Recover the lengths.
6125
POP DE D1
Recover the address of the reserved space.
6126
EX (SP),HL E3
Exchange, bringing the target descriptor address back into Register Pair HL.
6127
PUSH BC C5
Save the lengths.
6128
PUSH HL E5
Save the target descriptor address.
6129
INC HL 23
INCrement Register Pair HL by 1.
612A
LD (HL),E 73
Store the low byte of the reserved space's address into the target's descriptor.
612B
INC HL 23
INCrement Register Pair HL by 1.
612C
LD (HL),D 72
and the high byte. The target variable now points at a private copy instead of at the program text, and from here on the assignment is an ordinary one.
612D
PUSH AF F5
Push Register AF so that this path arrives at 612EH with the stack at the same depth as the two that jump there.
612E
POP AF F1
Recover the target length. This is where 6101H and 6107H land.
612F
POP HL E1
Recover the target descriptor address.
6130
INC HL 23
INCrement Register Pair HL by 1.
6131
LD E,(HL) 5E
Read the low byte of the target's data address.
6132
INC HL 23
INCrement Register Pair HL by 1.
6133
LD D,(HL) 56
and the high byte.
6134
POP BC C1
Recover the lengths: Register B the source, Register C the target.
6135
POP HL E1
Recover the source descriptor address.
6136
PUSH DE D5
Save the target's data address.
6137
INC HL 23
INCrement Register Pair HL by 1.
6138
LD E,(HL) 5E
Read the low byte of the source's data address.
6139
INC HL 23
INCrement Register Pair HL by 1.
613A
LD D,(HL) 56
and the high byte.
613B
EX DE,HL EB
Register Pair HL becomes the source.
613C
POP DE D1
and Register Pair DE the target.
613D
LD A,C 79
Copy the target length into Register A.
613E
CP B B8
Compare it against the source length.
613F
If the target is the longer, leave Register B alone.
6141
LD B,A 47
Otherwise Register B becomes the target length. Either way Register B is now the SMALLER of the two, the number of characters actually copied - a source longer than the field is simply truncated.
6142
SUB B 90
SUBTRACT it from the target length.
6143
LD C,A 4F
Register C is now the number of pad characters needed.
6144
POP AF F1
Recover the flags word saved at 60DAH. The CARRY FLAG is SET for LSET and CLEAR for RSET.
6145
RSET: pad FIRST, so that the characters end up at the RIGHT-hand end of the field.
6148
INC B 04
INCrement Register B by 1.
6149
DEC B 05
Loop Start
DECrement it again. The pair sets the flags from Register B without needing a second register to compare it against - the idiom this file uses everywhere.
614A
When every character has been copied, JUMP to 6157H.
614C
LD A,(HL) 7E
Read one character of the source.
614D
LD (DE),A 12
Store it into the field.
614E
INC HL 23
INCrement Register Pair HL by 1.
614F
INC DE 13
INCrement Register Pair DE by 1.
6150
JUMP back for the next character.
6152
POP BC C1
Reached from 60FAH when the target has zero length. Discard the first of the five words this routine has stacked.
6153
POP BC C1
Discard the second.
6154
POP BC C1
Discard the third.
6155
POP BC C1
Discard the fourth.
6156
POP BC C1
Discard the fifth, which is the flags word - but it goes into Register Pair BC rather than into Register AF, so the CARRY FLAG stays as the AND A at 60F9H left it and neither padding call below is made.
6157
LSET: pad LAST, so that the characters sit at the LEFT-hand end of the field.
615A
POP HL E1
Recover the text pointer.
615B
RET C9
RETurn to the ROM.
615C
LD A,20H 3E 20
The pad character: 20H, a space.
615E
INC C 0C
INCrement Register C by 1.
615F
DEC C 0D
Loop Start
DECrement it again, setting the flags from the pad count.
6160
RET Z C8
When there is no more padding to do, RETurn.
6161
LD (DE),A 12
Store one space.
6162
INC DE 13
INCrement Register Pair DE by 1.
6163
JUMP back for the next one.
6165
POP AF F1
Reached from 6115H when the target's characters lie inside the program text and there is not enough free string space below the live strings for a copy. POP AF also RESTORES THE FLAGS saved at 60F4H, which are those FRESTR left at 60EBH, and it is those flags that 616AH tests.
6166
POP HL E1
Recover the target descriptor address.
6167
POP BC C1
Recover the two lengths.
6168
EX (SP),HL E3
Exchange with the stacked source descriptor.
6169
EX DE,HL EB
Exchange the pair.
616A
On the NZ flags restored at 6165H, JUMP straight to 6175H and let 6117H do the allocation.
616C
PUSH BC C5
Save the lengths.
616D
LD A,B 78
Copy the source length into Register A.
616E
GOSUB to the ROM's STRINI, which reserves Register A bytes of string space - forcing a garbage collection if that is what it takes - and builds a temporary descriptor for them at DSCTMP 40D3H.
6171
GOSUB to the ROM's PUTNEW, which registers that descriptor as a temporary string result.
6174
POP BC C1
Recover the lengths.
6175
EX (SP),HL E3
Exchange, bringing the target descriptor address back into Register Pair HL.
6176
PUSH BC C5
Re-stack the lengths.
6177
PUSH HL E5
Re-stack the target descriptor address.
6178
PUSH AF F5
Re-stack the target length, so the frame is exactly the one 6117H expects.
6179
JUMP back into the in-place path, which now finds room for its allocation.

617BH - EOF

Vector slot 4161H. EOF(n) is a function rather than a statement, so the ROM's expression machinery has already evaluated its argument into the accumulator by the time this is reached, and the whole implementation is one DOS call: @CKEOF answers whether there is still a record to read. The answer is handed back as an integer - zero for false and minus one for true - which is what Level II expects of any relational result. A file open for sequential OUTPUT is rejected, because the question has no meaning there.

617B
GOSUB to 5CF9H, the entry of the shared file reference parser that begins at the ROM's CONINT: the argument is already in the accumulator, so all that is needed is to reduce it to a byte, range-check it against MAXFIL and index the pointer table at 65FAH. It returns Register Pair BC pointing at the file's buffer and Register A holding its mode, with the flags set.
617E
CP 02H FE 02
Compare the mode against 02H, sequential output.
6180
If it is, JUMP to 6435H and raise "Bad file mode".
6183
INC BC 03
INCrement Register Pair BC by 1.
6184
INC BC 03
INCrement Register Pair BC by 1 again, stepping over the mode and print-column bytes onto the file control block at buffer offset 2.
6185
LD D,B 50
Copy the high byte into Register D.
6186
LD E,C 59
and the low byte into Register E, which is where the DOS expects the control block address.
6187
GOSUB to @CKEOF, which returns the Z FLAG set while there is still a record to be read and NZ once the end of the file has been passed.
618A
LD HL,0000H 21 00 00
Load Register Pair HL with zero, BASIC's FALSE. LD alters no flag, so the answer survives.
618D
If there is more to read, JUMP to the ROM's MAKINT, which stores Register Pair HL into the accumulator as an integer and delivers it as the function's value.
6190
DEC HL 2B
Otherwise DECrement Register Pair HL to 0FFFFH, which is minus one, BASIC's TRUE.
6191
and deliver that instead.

6194H - Close One File

The worker behind every kind of close in LBASIC. The CLOSE statement reaches it once per file at 6053H and 6073H; the READY exit CALLs it directly at 5FC7H, where Register A is zero because the ROM's FINPRT has just cleared it on its way through the PRINT completion hook, so THAT call closes FILE ZERO - the internal file LOAD, SAVE, MERGE and CMD"L" all use. Whatever the file was, the last thing that happens is that the first 34 bytes of its buffer are zeroed, which makes the mode byte 00H and blanks the control block; that is what makes the file "not open" as far as the parser at 5CFCH is concerned, and it is why the buffer can be handed straight back out again. A file open for sequential INPUT skips the DOS call altogether: nothing has been written to it, so the directory needs no update and @CLOSE would only cost a disk access.

6194
GOSUB to 5CFCH, the entry of the file reference parser that takes the file number already in Register A. It range-checks it against MAXFIL inclusively, indexes the pointer table at 65FAH and returns Register Pair BC pointing at the buffer with Register A holding the mode and the flags set from it.
6197
RET Z C8
If the mode is zero the file is not open and there is nothing to do, so RETurn.
6198
CP 01H FE 01
Compare the mode against 01H, sequential input.
619A
PUSH BC C5
Save the buffer address. PUSH alters no flag, so the comparison survives it.
619B
If the file was only ever read, JUMP straight to the clear-down below without troubling the DOS.
619D
XOR A AF
Set Register A to ZERO.
619E
LD (BC),A 02
Write it into the mode byte at buffer offset 0 BEFORE the DOS call, so that an error reported by the close cannot leave a half-closed file still looking open.
619F
INC BC 03
INCrement Register Pair BC by 1.
61A0
INC BC 03
INCrement Register Pair BC by 1 again, onto the file control block.
61A1
LD D,B 50
Copy the high byte into Register D.
61A2
LD E,C 59
and the low byte into Register E.
61A3
GOSUB to @CLOSE, which flushes anything still in the buffer, writes the end-of-file byte and the extents into the directory and releases the file.
61A6
If the DOS reports an error, JUMP to 644FH, the error translator.
61A9
POP DE D1
Recover the buffer address into Register Pair DE.
61AA
LD B,22H 06 22
Load Register B with 22H, which is 34: the mode byte, the print column byte and the whole 32-byte file control block. The 256-byte data area above them is left alone.
61AC
XOR A AF
Set Register A to ZERO.
61AD
LD (DE),A 12
Loop Start
Store zero into the next byte.
61AE
INC DE 13
INCrement Register Pair DE by 1.
61AF
DECrement Register B and JUMP back while it is not zero.
61B1
RET C9
RETurn. The file number is free again and the parser at 5CFCH now reports the buffer as closed.

61B2H And 61B7H - LOC And LOF

Vector slots 4164H and 4167H. LOC(n) returns the current record number and LOF(n) the number of the last record - the length of the file in records. The two functions differ only in which DOS vector they call, so LOF loads @LOF into Register Pair HL, LOC loads @LOC, and both fall into a shared tail that makes the call and delivers the answer as an integer. The call itself is made through the little JP (HL) at 61CFH, which is nothing more than a way to CALL a routine whose address is in a register pair; the byte writer at 61D0H sits immediately after it and is unrelated.

61B2
LD HL,445AH 21 5A 44
LOC, vector slot 4164H. Load Register Pair HL with the address of @LOC, which reports the record last read or written.
61B5
JUMP to the shared tail.
61B7
LD HL,445DH 21 5D 44
LOF, vector slot 4167H. Load Register Pair HL with the address of @LOF, which reports the file's last record number, i.e. its length in 256-byte records.
61BA
GOSUB to the file reference parser entry at 5CF9H, the argument being already in the accumulator: it reduces it to a byte, range-checks it and returns Register Pair BC pointing at the file's buffer with the mode in Register A and the flags set.
61BD
If the mode is zero the file is not open, so JUMP to 6438H and raise "Bad file number".
61C0
INC BC 03
INCrement Register Pair BC by 1.
61C1
INC BC 03
INCrement Register Pair BC by 1 again, onto the file control block at buffer offset 2.
61C2
LD D,B 50
Copy the high byte into Register D.
61C3
LD E,C 59
and the low byte into Register E, the address the DOS routine expects.
61C4
GOSUB to 61CFH, which is a bare JP (HL) - so this calls whichever DOS routine 61B2H or 61B7H put in Register Pair HL, @LOC or @LOF, and comes back here with the record number in Register Pair BC.
61C7
If the DOS reported an error, JUMP to 07B2H, the ROM's OV error entry, reporting ?OV ERROR - the record number would not fit in the range BASIC can represent.
61CA
LD H,B 60
Copy the high byte of the record number into Register H.
61CB
LD L,C 69
and the low byte into Register L.
61CC
JUMP to the ROM's MAKINT, which stores Register Pair HL into the accumulator as an integer and delivers it as the function's value.
61CF
JP (HL) E9
A one-byte dispatcher: JUMP to the address in Register Pair HL. 61C4H uses it to reach @LOC or @LOF; it is written out as its own labelled byte only because it is CALLed rather than jumped to.

61D0H - The Byte Writer

Vector slot 41C1H, which the ROM CALLs at 032CH for every single byte it sends to any output device. Most of the time there is no file current and the byte is destined for the screen or the printer, and this routine passes it straight to the ROM's own $PUT and returns - but it also maintains, for whatever file IS current, that file's OWN print column at buffer offset 01, entirely separately from the screen's TTYPOS. That per-file column is what the comma-field and TAB hook at 5C9EH reads instead of TTYPOS when output has been diverted to a file, so that PRINT#1, with commas and TABs lines its columns up in the file exactly as it would on the screen. A carriage return resets the column to zero; any printable character advances it. The routine is entered with the current file pointer already known to be non-zero, so it does not repeat the current-file test.

61D0
PUSH HL E5
Save Register Pair HL, the caller's pointer.
61D1
PUSH AF F5
Save the byte to be written together with the flags.
61D2
LD HL,(65F8H) 2A F8 65
Load Register Pair HL with the current file buffer pointer.
61D5
LD A,(HL) 7E
Read the file's mode byte.
61D6
CP 01H FE 01
Compare it against 01H, sequential input.
61D8
If the file is open for input it cannot be written to, so JUMP to the ROM's POPAHT, which discards the two words just pushed and returns - the byte is quietly dropped, exactly as writing to a read-only device does nothing.
61DB
POP AF F1
Recover the byte and flags.
61DC
PUSH DE D5
Save Register Pair DE.
61DD
PUSH BC C5
Save Register Pair BC.
61DE
INC HL 23
INCrement Register Pair HL by 1, onto buffer offset 01, the file's print column.
61DF
LD B,00H 06 00
Clear Register B, which becomes zero the routine stores as the new column after a carriage return.
61E1
PUSH AF F5
Save the byte again across the column arithmetic.
61E2
LD D,(HL) 56
Read the file's current print column into Register D.
61E3
CP 0DH FE 0D
Compare the byte against 0DH, a carriage return.
61E5
LD (HL),B 70
Store zero into the print column. If the byte IS a carriage return this is the whole update; if it is not, the correct value is written again two instructions on. LD alters no flag, so the comparison survives.
61E6
If the byte was a carriage return the column is now correctly zero, so JUMP past the increment.
61E8
ADD A,0E0H C6 E0
ADD 0E0H to the byte. A character of 20H or above - a printable one - produces a carry here; a control character below 20H does not. The result in Register A is discarded; only the CARRY FLAG matters.
61EA
LD A,D 7A
Copy the old column back into Register A.
61EB
ADC A,B 88
ADD Register B, which is zero, plus the carry. So the column is incremented by one for a printable character and left unchanged for a control character.
61EC
LD (HL),A 77
Store the updated column back.
61ED
INC HL 23
INCrement Register Pair HL by 1, onto buffer offset 02, the file control block.
61EE
EX DE,HL EB
Move that address into Register Pair DE, where $PUT and $GET expect the control block.
61EF
POP AF F1
Recover the byte.
61F0
PUSH AF F5
and save it once more, because the write may have to be retried.
61F1
GOSUB to the $PUT vector at 001BH, which under LDOS writes the byte in Register A through the file control block addressed by Register Pair DE.
61F4
If the write returned NZ something needs looking at, so JUMP to 61FBH.
61F6
POP AF F1
The write succeeded. Discard the saved byte.
61F7
POP BC C1
Restore Register Pair BC.
61F8
POP DE D1
Restore Register Pair DE.
61F9
POP HL E1
Restore Register Pair HL.
61FA
RET C9
RETurn to the ROM.
61FB
LD C,A 4F
Save the returned code in Register C.
61FC
LD A,(DE) 1A
Read the file control block's type byte.
61FD
AND A A7
Set the SIGN FLAG from its bit 7.
61FE
LD A,C 79
Bring the returned code back into Register A without disturbing that flag.
61FF
For an open disk file - bit 7 set - a non-zero return is a real error, so JUMP to 644FH, the error translator.
6202
For a device - bit 7 clear - the non-zero return is the keyboard-style "busy, try again" answer and is simply ignored, so JUMP back and treat the byte as written.

6204H And 6205H - PUT And GET

Vector slots 4182H and 417FH, and the third of the file's F6-AF skip-byte overlaps. The two bytes F6 AF are OR 0AFH entered at 6204H and XOR A entered at 6205H; the first leaves Register A non-zero, the second leaves it zero, and that byte is stored at 65F6H as the discriminator the shared body reads at 624CH and 625BH to decide between @WRITE and @READ. So PUT and GET are one routine that transfers a whole 256-byte record between a random file's buffer and the disk, differing only in the direction of the transfer. An optional record number may follow; when it is present the file is @POSNed there first, and when it is absent the DOS simply uses the next record in sequence.

6204
OR 0AFH F6 AF
PUT, vector slot 4182H. The OR leaves Register A non-zero, the value that will select @WRITE.
6205
XOR A AF
GET, vector slot 417FH. Entered here the 0AFH is executed as XOR A, leaving Register A zero, the value that will select @READ. One byte, two directions.
6206
LD (65F6H),A 32 F6 65
Store the discriminator at 65F6H.
6209
GOSUB to the file reference parser at 5CEFH, which reads an optional hash mark and a file number and returns Register Pair BC pointing at the buffer with the mode in Register A.
620C
CP 03H FE 03
Compare the mode against 03H, random access.
620E
If the file is not a random file, JUMP to 6435H and raise "Bad file mode".
6211
PUSH BC C5
Save the buffer address.
6212
DEC HL 2B
DECrement Register Pair HL by 1, backing onto the character the parser stopped on.
6213
RESTART to the character fetch hook, which re-reads it and sets the Z FLAG at a statement terminator.
6214
If something follows, it is a record number, so JUMP to 6224H to read it.
6216
EX (SP),HL E3
No record number. Exchange the text pointer with the stacked buffer address.
6217
PUSH HL E5
Save the buffer address again.
6218
EX DE,HL EB
Move it into Register Pair DE.
6219
INC DE 13
INCrement Register Pair DE by 1.
621A
INC DE 13
INCrement Register Pair DE by 1 again, onto the file control block.
621B
LD HL,0020H 21 20 00
Load Register Pair HL with 0020H, the offset from the control block to the buffer's 256-byte data area (buffer offset 22H).
621E
ADD HL,DE 19
ADD the control block address, giving the data area address - which is where @CKEOF wants to look.
621F
GOSUB to @CKEOF, which for a random file with no explicit record number confirms the next record is within the file. Its result reaches the shared code at 6241H.
6222
JUMP to the shared transfer.
6224
LD A,(HL) 7E
A record number follows. Read the current character.
6225
CP 2CH FE 2C
Compare it against 2CH (ASCII: ,).
6227
If there is no comma before it, JUMP to the ROM's SNERR and raise ?SN ERROR.
622A
GOSUB to the ROM's GETINT, which fetches the next character and evaluates the record number into Register Pair DE as a 16-bit integer.
622D
EX (SP),HL E3
Exchange the text pointer with the stacked buffer address.
622E
PUSH HL E5
Save the buffer address again.
622F
LD A,D 7A
Copy the high byte of the record number into Register A.
6230
OR E B3
OR the low byte into it.
6231
A record number of zero is invalid, so JUMP to 6447H and raise "Bad record number".
6234
DEC DE 1B
DECrement Register Pair DE by 1: records are numbered from one to the user but from zero to the DOS.
6235
LD B,D 42
Copy the high byte of the adjusted number into Register B.
6236
LD C,E 4B
and the low byte into Register C, where @POSN expects the record number.
6237
EX DE,HL EB
Move the buffer address into Register Pair DE.
6238
INC DE 13
INCrement Register Pair DE by 1.
6239
INC DE 13
INCrement Register Pair DE by 1 again, onto the file control block.
623A
LD HL,0020H 21 20 00
Load Register Pair HL with 0020H.
623D
ADD HL,DE 19
ADD the control block address to reach the data area, although @POSN only reads the control block in Register Pair DE.
623E
GOSUB to @POSN, which positions the file at the record number in Register Pair BC.
6241
If the positioning or end-of-file check returned Z - success, or a record still to read - JUMP to 625BH, which is the GET-oriented entry of the transfer.
6243
CP 1CH FE 1C
It returned NZ. Compare the code against 1CH, LDOS error 28, end of file.
6245
A PUT past the end of the file simply extends it, so end of file here is not an error: JUMP to 624CH, the WRITE-oriented entry.
6247
CP 1DH FE 1D
Compare the code against 1DH, LDOS error 29, record number out of range.
6249
Any other code is a real error, so JUMP to 644FH, the error translator.
624C
LD A,(65F6H) 3A F6 65
Read the PUT/GET discriminator.
624F
AND A A7
Set the flags from it.
6250
If it is zero this is a GET, and reaching here means the record is genuinely past the end of the file, so JUMP to 626EH and return a cleared buffer.
6252
A PUT: GOSUB to @WRITE, which writes the 256-byte data area to the record just positioned, extending the file if need be.
6255
On any error, JUMP to the translator.
6258
POP AF F1
Discard the saved buffer address.
6259
POP HL E1
Recover the text pointer.
625A
RET C9
RETurn to the ROM.
625B
LD A,(65F6H) 3A F6 65
Read the discriminator.
625E
AND A A7
Set the flags from it.
625F
If it is non-zero this is a PUT, so JUMP back to the @WRITE at 6252H.
6261
A GET: GOSUB to @READ, which reads the positioned record into the 256-byte data area.
6264
If it returned NZ, JUMP to 6269H to see whether it was merely end of file.
6266
POP BC C1
The read succeeded. Discard the saved buffer address.
6267
POP HL E1
Recover the text pointer.
6268
RET C9
RETurn to the ROM.
6269
CP 1CH FE 1C
Compare the code against 1CH, end of file.
626B
Anything else is a real error, so JUMP to the translator.
626E
POP HL E1
A GET past the end of file. Recover the buffer address into Register Pair HL.
626F
LD DE,0022H 11 22 00
Load Register Pair DE with 0022H, the data-area offset.
6272
ADD HL,DE 19
ADD it, so Register Pair HL addresses the 256-byte data area.
6273
XOR A AF
Set Register A to ZERO, the fill byte.
6274
LD B,A 47
Clear Register B, so the loop below runs a full 256 times.
6275
LD (HL),A 77
Loop Start
Store zero into the next data byte.
6276
INC HL 23
INCrement Register Pair HL by 1.
6277
DECrement Register B and JUMP back while it is not zero, blanking the whole record so that a GET past the end yields an empty record rather than stale data.
6279
POP HL E1
Recover the text pointer.
627A
RET C9
RETurn to the ROM.

627BH - Read One Byte From The Current File Or Device

The single byte-input primitive of the whole extension, CALLed from nine places: the keyboard scan exit at 5C22H, the LINE INPUT# collector and the INPUT# field scanners at 5D74H, 5D8FH, 5DB2H, 5DD2H, 5DE0H and 5DF1H, the MERGE reader at 5E54H and the ASCII program loader at 5EDFH. It reads the next byte from whatever 65F8H currently names and returns it in Register A with the CARRY FLAG clear, or returns CARRY set to mean end of file. Registers BC, DE and HL are all preserved. The interesting part is the middle: LDOS uses two different result conventions for the same $GET call, and this routine picks between them by looking at the file control block's own type byte. For an OPEN DISK FILE (bit 7 of FCB+00 set) the Z FLAG means the transfer succeeded and NZ leaves an error number in Register A; for a DEVICE it is the keyboard convention, where a returned character sets NZ and the Z FLAG means nothing is available yet, so the routine simply asks again. That is what lets INPUT# read from *KI, *SI or a filtered device as naturally as from a disk file, and it is why the two exits are the same three POPs.

627B
PUSH BC C5
Save Register Pair BC onto the stack. Every caller is in the middle of a parse or a copy loop and needs it back.
627C
PUSH HL E5
Save Register Pair HL onto the stack. In most callers it is the BASIC text pointer.
627D
LD HL,(65F8H) 2A F8 65
Fetch the current file buffer pointer from 65F8H into Register Pair HL. It is the address of the 0122H-byte buffer built at 53CFH, whose first byte is the file's mode.
6280
PUSH DE D5
Save Register Pair DE onto the stack, because it is about to be loaded with the file control block address.
6281
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, moving the buffer address into Register Pair DE.
6282
INC DE 13
INCrement Register Pair DE by 1, stepping over the mode byte at buffer offset 0.
6283
INC DE 13
INCrement Register Pair DE by 1 again. Register Pair DE now addresses buffer offset 2, which is the 32-byte file control block; the 256-byte data area follows it at buffer offset 22H.
6284
GOSUB to the $GET vector at 0013H, which under LDOS reads one byte through the file control block addressed by Register Pair DE and returns it in Register A. This is the address the loop below comes back to when a device has nothing to give yet.
6287
PUSH AF F5
Save Register A AND THE FLAGS onto the stack. The flags are the whole result of the call and the next two instructions are about to destroy them.
6288
LD A,(DE) 1A
Read the byte at the file control block's offset 00 into Register A. That is the type and attribute byte: 80H marks an open disk file, 10H marks an open device.
6289
AND A A7
Set the flags from it. Only the SIGN FLAG matters here, and it is a copy of bit 7.
628A
If the SIGN FLAG is clear, meaning bit 7 of the type byte is zero and this is a DEVICE rather than an open disk file, JUMP to 629CH where the device convention is applied.
628D
POP AF F1
Restore Register A and the flags that $GET returned. This is the disk file path, where the Z FLAG means the byte was transferred and NZ leaves an LDOS error number in Register A.
628E
If the Z FLAG has been set, the read succeeded, so JUMP to 6297H to clear the CARRY FLAG and return with the byte in Register A.
6290
CP 1CH FE 1C
Compare the error number in Register A against 1CH, LDOS error 28, "end of file encountered". That is the one error this routine handles itself.
6292
If the NZ FLAG has been set, meaning any other error, JUMP to 644FH, the DOS error translator, which turns the LDOS number into a BASIC error through BASIC/OV3 and reports it. This does not return.
6295
SCF 37
SET the CARRY FLAG. Carry set is this routine's end-of-file answer, and every caller tests it immediately.
6296
LD B,0A7H 06 A7
Load Register B with 0A7H. The instruction exists only so that its OPERAND BYTE at 6297H can be entered as an opcode in its own right, and it is chosen because LD B,nn alters no flag, so the CARRY FLAG just set survives. Register B is reloaded from the stack four instructions later, so the value never matters.
6297
AND A A7
Entered here rather than at 6296H, the operand byte 0A7H is executed as AND A, which CLEARS the CARRY FLAG while leaving Register A untouched. This is the success exit: one byte serves both as the tail of an LD B,nn on the end-of-file path and as the carry-clearing instruction on the byte-delivered path.
6298
POP DE D1
Restore Register Pair DE from the stack. POP does not disturb the flags, so the CARRY FLAG set at 6295H or cleared at 6297H survives all three restores.
6299
POP HL E1
Restore Register Pair HL from the stack, giving the caller its text pointer back.
629A
POP BC C1
Restore Register Pair BC from the stack, which also discards the 0A7H that 6296H put in Register B.
629B
RET C9
RETurn with Register A holding the byte and the CARRY FLAG clear, or with the CARRY FLAG set to report end of file.
629C
POP AF F1
The device path. Restore Register A and the flags that $GET returned. A device driver answers by the keyboard convention: a character in Register A with the NZ FLAG set, or the Z FLAG set to say that nothing has arrived.
629D
If the Z FLAG has been set, no character is available yet, so LOOP BACK to 6284H and ask the driver again. Loop End The wait is unbounded, which is what makes INPUT# from a device block until the operator or the sender supplies a character.
629F
A character has arrived, so JUMP to 6297H, which clears the CARRY FLAG and returns it exactly as it would a byte read from a disk file. A device therefore never reports end of file through this routine.

62A1H - The Filespec Builder

A short helper CALLed by OPEN at 633EH and by KILL at 6416H. It evaluates the string expression that names the file, copies it to a scratch area at 5414H with a zero terminator, and passes it to @FSPEC, which parses "NAME/EXT:d" into the file control block. The scratch area at 5414H sits in the start-up code around the sign-on entry, which has already run by the time any file is opened, so it is reused as workspace - the same space-reclaiming trick the keyboard buffer and the DOS-vector save area use. Register Pair BC on entry is the file's buffer address; it survives the call so that both callers get it back.

62A1
PUSH BC C5
Save the buffer address, which @FSPEC does not preserve.
62A2
GOSUB to the ROM's FRMEVL to evaluate the filespec expression.
62A5
PUSH HL E5
Save the text pointer.
62A6
GOSUB to the ROM's FRESTR, returning Register Pair HL pointing at the string descriptor.
62A9
LD A,(HL) 7E
Read the string's length into Register A.
62AA
AND A A7
Set the flags from it.
62AB
An empty filespec, so JUMP to 6444H and raise "Bad file name".
62AE
CP 19H FE 19
Compare the length against 19H, 25.
62B0
A filespec of 25 characters or more will not fit the "NAME/EXT:d password" form, so JUMP to 6444H and raise "Bad file name".
62B3
INC HL 23
INCrement Register Pair HL by 1.
62B4
LD C,(HL) 4E
Read the low byte of the string's data address.
62B5
INC HL 23
INCrement Register Pair HL by 1.
62B6
LD B,(HL) 46
and the high byte, so Register Pair BC addresses the characters.
62B7
LD HL,5414H 21 14 54
Load Register Pair HL with 5414H, the scratch area in the once-only start-up code.
62BA
LD E,A 5F
Copy the length into Register E as the loop counter.
62BB
LD A,(BC) 0A
Loop Start
Read one character of the filespec.
62BC
LD (HL),A 77
Store it into the scratch area.
62BD
INC HL 23
INCrement the destination pointer.
62BE
INC BC 03
INCrement the source pointer.
62BF
DEC E 1D
DECrement the counter.
62C0
and JUMP back until every character has been copied.
62C2
LD (HL),E 73
Register E is now zero, so this writes the 00H terminator @FSPEC expects.
62C3
POP HL E1
Recover the text pointer.
62C4
POP DE D1
Recover the buffer address, saved at 62A1H, into Register Pair DE.
62C5
LD B,D 42
Copy the high byte into Register B.
62C6
LD C,E 4B
and the low byte into Register C, restoring Register Pair BC as the buffer address the caller wants back.
62C7
PUSH BC C5
Save it again across the call below.
62C8
PUSH HL E5
Save the text pointer.
62C9
LD HL,5414H 21 14 54
Load Register Pair HL with 5414H, the assembled filespec string.
62CC
INC DE 13
INCrement Register Pair DE by 1.
62CD
INC DE 13
INCrement Register Pair DE by 1 again, so Register Pair DE addresses the file control block at buffer offset 2.
62CE
GOSUB to @FSPEC, which parses the filespec at Register Pair HL into the control block at Register Pair DE, filling in the name, extension, drive and any password.
62D1
On a parse error, JUMP to 6444H and raise "Bad file name".
62D4
AND A A7
Set the flags from the return code once more.
62D5
and raise "Bad file name" on any non-zero result, whichever flag @FSPEC left it in.
62D8
POP HL E1
Recover the text pointer.
62D9
POP BC C1
Recover the buffer address.
62DA
RET C9
RETurn to the caller.

62DBH - OPEN

Vector slot 4179H, and the largest single routine in the extension. It parses the whole OPEN"mode",bufnum,"filespec"[,reclen] statement, decodes the one- or two-letter mode string into a byte of flags, then decides between @OPEN and @INIT according to whether the file must already exist. The mode letters follow the manual exactly: I input, O output, R random, E output-and-extend, with a second letter O ("must be old") or N ("must be new") allowed on the output modes. The routine returns to the ROM through FINPRT, whose address it pushes first. The bulk of it - the DOS-open decision and the buffer set-up - lives in the fall-through worker from 6333H, which SAVE, LOAD, MERGE and RUN also reach through 5E21H.

62DB
LD BC,2169H 01 69 21
Load Register Pair BC with 2169H, the ROM's FINPRT.
62DE
PUSH BC C5
Push it as the return address, so that when OPEN finishes it returns through FINPRT, which resets the output device to the screen.
62DF
GOSUB to the ROM's FRMEVL to evaluate the mode string.
62E2
PUSH HL E5
Save the text pointer.
62E3
GOSUB to the ROM's FRESTR, returning Register Pair HL pointing at the mode string's descriptor.
62E6
LD A,(HL) 7E
Read the mode string's length.
62E7
AND A A7
Set the flags from it.
62E8
An empty mode string, so JUMP to 6435H and raise "Bad file mode".
62EB
INC HL 23
INCrement Register Pair HL by 1.
62EC
LD C,(HL) 4E
Read the low byte of the string data address.
62ED
INC HL 23
INCrement Register Pair HL by 1.
62EE
LD B,(HL) 46
and the high byte, so Register Pair BC addresses the mode characters.
62EF
LD E,A 5F
Copy the length into Register E.
62F0
LD A,(BC) 0A
Read the FIRST mode character.
62F1
RES 5,A CB AF
RESET bit 5, which folds a lower-case letter to upper case so that "o" and "O" are the same.
62F3
CP 49H FE 49
Compare it against 49H (ASCII: I).
62F5
LD D,11H 16 11
Load Register D with 11H: file mode 01H (sequential input) in the low bits, plus bit 4 marking "the file must already exist". LD alters no flag, so the comparison survives.
62F7
If the letter was I, the mode is fully decided, so JUMP to 631FH.
62F9
CP 4FH FE 4F
Compare against 4FH (ASCII: O).
62FB
LD D,02H 16 02
Load Register D with 02H, sequential output.
62FD
If the letter was O, JUMP to 630CH to look for a second letter.
62FF
CP 52H FE 52
Compare against 52H (ASCII: R).
6301
LD D,0BH 16 0B
Load Register D with 0BH: file mode 03H (random) in the low bits, plus bit 3 marking "an LRL may follow".
6303
If the letter was R, JUMP to 630CH.
6305
CP 45H FE 45
Compare against 45H (ASCII: E).
6307
LD D,06H 16 06
Load Register D with 06H: file mode 02H (output) plus bit 2 marking "extend, position to the end after opening".
6309
If the letter was none of I, O, R or E, JUMP to 6435H and raise "Bad file mode".
630C
DEC E 1D
A second mode letter is allowed on O, R and E. DECrement the length counter.
630D
If the mode string was only one character, JUMP to 631FH.
630F
INC BC 03
INCrement the source pointer onto the second character.
6310
LD A,(BC) 0A
Read it.
6311
RES 5,A CB AF
Fold its case.
6313
CP 4FH FE 4F
Compare against 4FH (ASCII: O).
6315
If it is not O, skip the next instruction.
6317
SET 4,D CB E2
It was O ("old"): SET bit 4 of the flags, marking "the file must already exist" - the OO and EO modes.
6319
CP 4EH FE 4E
Compare against 4EH (ASCII: N).
631B
If it is not N, done.
631D
SET 5,D CB EA
It was N ("new"): SET bit 5 of the flags, marking "the file must NOT already exist" - the ON and EN modes.
631F
POP HL E1
Recover the text pointer.
6320
RESTART to the SYNCHR hook requiring 2CH (ASCII: ,).
6322
PUSH DE D5
Save the flags in Register D and the leftover length in Register E.
6323
CP 23H FE 23
Compare the next character against 23H (ASCII: #), which may optionally precede the buffer number.
6325
If the hash mark is there, GOSUB to the ROM's CHRGTR to step over it.
6328
GOSUB to the ROM's GETBYT, which evaluates the buffer number and returns it in both Register A and Register E.
632B
RESTART to the SYNCHR hook requiring another comma, before the filespec.
632D
LD A,E 7B
Copy the buffer number into Register A.
632E
AND A A7
Set the flags from it.
632F
Buffer zero is reserved for LBASIC's own internal file, so a user OPEN of file zero is refused: JUMP to 6438H and raise "Bad file number".
6332
CP 0D5H FE D5
The OPEN fall-through path. Compare the buffer number against 0D5H; the result is never tested. The comparison exists so that its OPERAND BYTE 0D5H at 6333H can be entered as an opcode in its own right - and 0D5H is PUSH DE, exactly the instruction the LOAD, SAVE and MERGE entry needs. The OPEN path, having already saved Register Pair DE at 6322H, must NOT push it a second time, so it swallows the byte as the harmless tail of a compare.
6333
PUSH DE D5
Entered here rather than at 6332H, from LOAD, SAVE, MERGE and RUN by the JP 6333H at 5E22H, the byte 0D5H is PUSH DE. It saves Register Pair DE - which those callers have set to the file mode - so that this entry arrives at 6334H with exactly the same one saved copy of Register Pair DE on the stack that the OPEN path set up at 6322H. One byte reconciles two entries with different stack histories.
6334
LD (638CH),A 32 8C 63
Store the buffer number into the operand at 638CH. That operand is read at 638BH, where a non-zero value SUPPRESSES the default extension - which is exactly right, because OPEN requires a complete filespec and only the internal file zero used by LOAD, SAVE, MERGE and RUN is entitled to have /BAS supplied.
6337
GOSUB to the file reference parser entry that takes the number already in Register A, returning Register Pair BC pointing at the buffer with its mode byte in Register A and the flags set.
633A
If the mode byte is non-zero the buffer is already in use, so JUMP to 643EH and raise "File already open".
633D
PUSH BC C5
Save the buffer address across the filespec build.
633E
GOSUB to the filespec builder, which @FSPECs the filespec into this buffer's control block.
6341
LD B,00H 06 00
Clear Register B, the logical record length, which stays zero (meaning 256) unless a random file supplies one.
6343
POP DE D1
Recover the buffer address into Register Pair DE.
6344
POP AF F1
Recover the flags saved at 6322H into Register A.
6345
PUSH AF F5
Save them again.
6346
PUSH DE D5
Save the buffer address again.
6347
BIT 3,A CB 5F
Test bit 3 of the flags, which is set only for a random file and means an LRL may follow.
6349
If it is clear this is a sequential file, which takes no LRL, so JUMP to 635FH.
634B
DEC HL 2B
A random file. DECrement Register Pair HL onto the character the buffer-number parse stopped on.
634C
RESTART to the character fetch hook, which sets the Z FLAG at a statement terminator.
634D
If nothing follows, no LRL was given, so JUMP to 635FH and leave it at the default of 256.
634F
RESTART to the SYNCHR hook requiring 2CH (ASCII: ,) before the LRL.
6351
GOSUB to the ROM's GETBYT, which evaluates the record length into Register A and Register E.
6354
LD A,(408FH) 3A 8F 40
Load Register A with VARREC 408FH, the blocked-file-mode flag set from the BLK parameter at start-up.
6357
AND A A7
Set the flags from it.
6358
If blocked mode is on, any LRL from 1 to 256 is allowed, so JUMP to 635EH and take it.
635A
OR E B3
Blocked mode is off. OR the record length into Register A.
635B
With BLK=OFF an OPEN may not specify a record length - 256 is forced - so a non-zero LRL here is an error: JUMP to 6432H and raise "Blocked file error".
635E
LD B,E 43
Store the record length in Register B.
635F
LD (40DFH),HL 22 DF 40
Save the text pointer into TEMP 40DFH, out of the way of the DOS calls below. It is read back at 63E9H.
6362
Load Register Pair HL with the address of SFLAG$ 430FH, the resident flag byte.
6365
RES 2,(HL) CB 96
RESET bit 2, "a program load initiated by @RUN is in progress".
6367
LD A,(6630H) 3A 30 66
Load Register A with the run-in-progress flag at 6630H.
636A
AND A A7
Set the flags from it.
636B
If no RUN is in progress, leave the bit clear.
636D
SET 2,(HL) CB D6
Otherwise SET bit 2 of SFLAG$, telling the DOS that this open belongs to an auto-run program. This is one of the two places that pin bit 2 of SFLAG$ to the @RUN-in-progress meaning.
636F
POP HL E1
Recover the buffer address into Register Pair HL.
6370
POP AF F1
Recover the flags into Register A.
6371
PUSH AF F5
Save them again.
6372
PUSH HL E5
Save the buffer address again.
6373
LD (65F8H),HL 22 F8 65
Store the buffer address at 65F8H, MAKING THIS FILE CURRENT even before the DOS open has succeeded - which is safe because any failure below reaches an error path that resets everything.
6376
EX DE,HL EB
Move the buffer address into Register Pair DE.
6377
INC DE 13
INCrement Register Pair DE by 1.
6378
INC DE 13
INCrement Register Pair DE by 1 again, onto the file control block.
6379
LD HL,0020H 21 20 00
Load Register Pair HL with 0020H, the offset from the control block to the 256-byte data area.
637C
ADD HL,DE 19
ADD the control block address. Register Pair HL now addresses the data area, which is the sector buffer the DOS will read into and write from.
637D
LD C,A 4F
Copy the flags into Register C, where the open decision below reads them.
637E
LD A,B 78
Copy the record length into Register A.
637F
AND A A7
Set the flags from it.
6380
If the record length is zero (a sequential file, or a random file defaulting to 256), leave the sector buffer at the data area.
6382
INC H 24
A random file with an explicit LRL. INCrement the HIGH byte of Register Pair HL, moving the sector buffer 256 bytes higher, to the SECOND 256-byte area that blocked mode reserves (which is why a blocked file buffer is 0222H bytes, not 0122H). The first area is left as the record the FIELD variables point into; the DOS deblocks into the second.
6383
PUSH HL E5
Save the sector buffer address across the default-extension test.
6384
LD HL,0FFFFH 21 FF FF
Load Register Pair HL with the EXT parameter cell at 6385H, assembled 0FFFFH so the default extension is ON, and set to 0000H by @PARAM if the E=OFF switch was given at start-up.
6387
LD A,H 7C
Copy the high byte into Register A.
6388
OR L B5
OR the low byte, giving 0FFH when EXT is on and 00H when it is off, and setting the Z FLAG accordingly.
6389
CPL 2F
Complement it: 00H when EXT is on, 0FFH when EXT is off.
638A
LD L,A 6F
Copy that into Register L.
638B
LD A,00H 3E 00
Load Register A with the operand at 638CH - the buffer number OPEN stored there at 6334H, or the assembled 00H that LOAD, SAVE, MERGE and RUN leave in place because they use the internal file zero.
638D
OR L B5
OR Register L into it. The Z FLAG is set ONLY when both the buffer number is zero AND the EXT switch is on - the one combination in which /BAS should be supplied.
638E
LD HL,548DH 21 8D 54
Load Register Pair HL with 548DH. The three bytes there are the letters "BAS" - the first three characters of the "BASIC/OV" overlay stem, reused as the default extension so that no separate "/BAS" string need be stored.
6391
PUSH BC C5
Save the flags in Register C.
6392
If and only if the file is the internal file zero and EXT is on, GOSUB to @FEXT, which supplies /BAS to a filespec that has no extension of its own. A user OPEN, whose buffer number is non-zero, never reaches this call - which is why OPEN demands a complete filespec.
6395
POP BC C1
Recover the flags.
6396
POP HL E1
Recover the sector buffer address.
6397
LD A,C 79
Copy the flags into Register A.
6398
CP 01H FE 01
Compare them against 01H, the value LOAD and MERGE plant at 5E1FH for a plain sequential-input open with no "must exist" bit.
639A
If so, JUMP to 63C0H, the input-open path that also runs the memory-protection check.
639C
BIT 4,C CB 61
Test bit 4 of the flags, "the file must already exist" - set by the I mode and by the O and N suffixes.
639E
If it is set, the file must exist, so JUMP to 63AFH and use @OPEN.
63A0
GOSUB to @INIT, which opens the file if it exists and creates it if it does not, returning the CARRY FLAG set when the file already existed.
63A3
On any error, JUMP to the DOS error translator.
63A6
If the file already existed, JUMP to 63B5H.
63A8
BIT 5,C CB 69
The file was newly created. Test bit 5, "the file must NOT already exist" - the N suffix. Since the file was new this condition is satisfied, but the test is written the other way for the existing-file path.
63AA
This branch is taken only when the file existed on the existing-file path; here on the new-file path it falls through. Its purpose is the ON and EN modes, which raise "File already exists" when the file is already there.
63AD
JUMP to 63B5H.
63AF
The file must exist. GOSUB to @OPEN, which opens an existing file and errors if it is not there.
63B2
On any error, JUMP to the translator.
63B5
BIT 2,C CB 51
Test bit 2 of the flags, the "extend" bit set by the E mode.
63B7
If it is set, GOSUB to @PEOF, which positions the file at its end so that the first PRINT# appends rather than overwrites. That is exactly what the manual says OPEN"E" does.
63BA
POP HL E1
Recover the buffer address.
63BB
POP AF F1
Recover the flags into Register A.
63BC
AND 03H E6 03
Mask to the low two bits, leaving the file mode alone: 01H input, 02H output, 03H random.
63BE
JUMP to the common tail that stores the mode byte.
63C0
The input-open path for LOAD and MERGE. GOSUB to @OPEN.
63C3
PUSH AF F5
Save the open's return code.
63C4
LD A,(6630H) 3A 30 66
Load Register A with the run-in-progress flag.
63C7
AND A A7
Set the flags from it.
63C8
If no RUN was in progress, skip the protection check.
63CA
XOR A AF
Set Register A to ZERO.
63CB
LD (6630H),A 32 30 66
Clear the run-in-progress flag.
63CE
Load Register A with SFLAG$ 430FH.
63D1
RRA 1F
Rotate bit 0 into the carry and shift right.
63D2
RRA 1F
Rotate again, so what was bit 1 of SFLAG$ is now in the CARRY FLAG.
63D3
SBC A,A 9F
Subtract Register A from itself with borrow, giving 0FFH when the carry was set and 00H when it was clear - a one-instruction "spread the carry across the byte".
63D4
LD (6631H),A 32 31 66
Store the result at 6631H. It is 0FFH exactly when SFLAG$ bit 1 says the DOS has cleared memory during this open, which is the condition the protection check at 63EDH acts on. This pins SFLAG$ bit 1 to "memory has been cleared by the DOS".
63D7
POP AF F1
Recover the open's return code.
63D8
On any error, JUMP to the translator.
63DB
POP HL E1
Recover the buffer address.
63DC
POP AF F1
Recover the flags, whose low two bits are the mode (01H input for this path).
63DD
LD (HL),A 77
Store the mode byte at buffer offset 0. The file is now open and the parser at 5CFCH will report it so.
63DE
INC HL 23
INCrement Register Pair HL by 1.
63DF
LD (HL),00H 36 00
Store zero at buffer offset 01, the file's own print column.
63E1
INC HL 23
INCrement Register Pair HL by 1.
63E2
INC HL 23
INCrement Register Pair HL by 1 again, onto the control block's flags byte at buffer offset 03.
63E3
CP 03H FE 03
Compare the mode against 03H, random.
63E5
If it is not a random file, skip the next instruction.
63E7
SET 6,(HL) CB F6
A random file: SET bit 6 of the control block's flags byte. This is the bit that SAVE clears at 6020H before its sequential write, confirming that bit 6 marks a control block set up for random access.
63E9
LD HL,(40DFH) 2A DF 40
Recover the text pointer from TEMP 40DFH.
63EC
RET C9
RETurn - through the FINPRT address pushed at 62DEH.

63EDH - The Memory-Protection Check

CALLed from the READY exit at 5FD2H (but only when the ROM's own READY code was the caller), from the post-tokenize exit at 5FE6H before any direct statement runs, and from the long error handler at 5A5BH. It clears the run-in-progress flag and then, if the open path at 63D4H recorded that the DOS cleared memory while a protected program was being loaded, it erases what is left of that program, does a NEW and raises error 90H. This is the mechanism behind the manual's "Protection has cleared memory" error: a program marked no-list, no-save cannot be left half-resident where the operator could examine it.

63ED
PUSH AF F5
Save Register A and the flags, which the callers rely on being preserved when no protection violation has occurred.
63EE
XOR A AF
Set Register A to ZERO.
63EF
LD (6630H),A 32 30 66
Clear the run-in-progress flag at 6630H. Whatever happens next, no RUN is in progress once this check has been made.
63F2
LD A,(6631H) 3A 31 66
Load Register A with the memory-cleared flag at 6631H, which 63D4H set to 0FFH if the DOS cleared memory during the last open.
63F5
AND A A7
Set the flags from it.
63F6
If it is zero, nothing was protected and cleared, so JUMP to 6410H and return normally.
63F8
LD HL,(40A4H) 2A A4 40
Memory was cleared behind a protected program. Load Register Pair HL with TXTTAB, the start of the program text.
63FB
LD D,H 54
Copy the high byte into Register D.
63FC
LD E,L 5D
and the low byte into Register E.
63FD
INC DE 13
INCrement Register Pair DE by 1, so Register Pair DE is one byte above TXTTAB.
63FE
LD BC,044CH 01 4C 04
Load Register Pair BC with 044CH, 1100 bytes - enough to blot out the head of any program.
6401
XOR A AF
Set Register A to ZERO.
6402
LD (HL),A 77
Store zero into the first byte of the program text, seeding the block fill below.
6403
LDIR ED B0
Block-fill 044CH bytes upward from TXTTAB with zero, destroying the program text so that nothing of the protected program survives in memory.
6405
LD (6631H),A 32 31 66
Clear the memory-cleared flag, so the check cannot fire a second time.
6408
GOSUB to the ROM's SCRATH, the body of NEW, which resets all the program and variable pointers.
640B
LD E,90H 1E 90
Load Register E with 90H, 144, the ROM error number for BASIC error 72, "Protection has cleared memory".
640D
JUMP to the ROM's error entry, which reports it and stops. This does not return.
6410
POP AF F1
No protection violation. Recover Register A and the flags saved at 63EDH.
6411
RET C9
RETurn to the caller with everything as it was found.

6412H - KILL

Vector slot 4191H. KILL deletes a named file from the disk. It has no buffer of its own to work in, so it borrows the internal file-zero buffer: it @FSPECs the filespec into that buffer's control block, @OPENs the file to get a valid control block, @KILLs it, and finally clears the borrowed buffer's mode byte so that file zero is left closed. There is no need to close the file after @KILL because @KILL releases it as it deletes it.

6412
XOR A AF
Set Register A to ZERO, the internal file number.
6413
GOSUB to the file reference parser entry that takes the number in Register A, returning Register Pair BC pointing at file zero's buffer. Its mode is not examined - the buffer is only being borrowed as scratch.
6416
GOSUB to the filespec builder, which evaluates the filespec and @FSPECs it into that buffer's control block.
6419
LD DE,(65FAH) ED 5B FA 65
Load Register Pair DE with the first entry of the buffer pointer table at 65FAH, which is file zero's buffer address.
641D
INC DE 13
INCrement Register Pair DE by 1.
641E
INC DE 13
INCrement Register Pair DE by 1 again, onto the file control block.
641F
GOSUB to @OPEN, which opens the named file - KILL needs an open control block with valid directory information before it can delete the file.
6422
On any error - typically the file not being found - JUMP to the DOS error translator.
6425
GOSUB to @KILL, which removes the file from the directory and frees its space on the disk.
6428
On any error, JUMP to the translator.
642B
DEC DE 1B
DECrement Register Pair DE by 1.
642C
DEC DE 1B
DECrement Register Pair DE by 1 again, back to the buffer's mode byte.
642D
LD (DE),A 12
Store zero there - Register A is zero because @KILL returned success - leaving file zero closed and its buffer free to be borrowed again.
642E
RET C9
RETurn to the ROM.

642FH - The Error-Code Table

Every "raise a fixed BASIC error" jump in the extension lands somewhere in this table, and the whole of it is one more application of the 01H skip-byte idiom - the same construction FDUBL uses at 5320H and PATCH at 5870H. Each entry is LD E,nn, two bytes, and between one entry and the next sits a single 01H byte, the opcode for LD BC,nnnn, which swallows the following LD E as its 16-bit operand when control arrives from above. So the table reads as a run of harmless LD BC instructions when entered at the top, but a jump straight to any entry executes just that one LD E,nn and falls through every 01H below it to the common JP 19A2H at 644CH, the ROM error entry. The number in Register E is TWICE the documented BASIC error code, because that is the convention the ROM's error routine uses for the disk errors. The very first entry, 642FH, is also the value planted in all ten USR vector slots at 5397H and the target of the FN evaluator's JP Z at 5608H, which is why "undefined user function" and "called an undefined USR" are the same error.

642F
LD E,2EH 1E 2E
Undefined user function / undefined USR. Load Register E with 2EH, 46, which is 2 x code 23. This is the default in every USR slot and the FN evaluator's "no definition" target.
6431
Entered from above this is a harmless LD BC that carries control past the next LD E. Entered at 6432H the bytes 1E 8C are LD E,8CH, 140, which is 2 x code 70, Blocked file error - raised at 635BH when BLK=OFF and an LRL is given, and at 60B6H / 60C1H by FIELD.
6434
Entered at 6435H, LD E,6CH, 108, 2 x code 54, Bad file mode - the wrong OPEN mode letter, or a statement used on a file whose mode does not allow it.
6437
Entered at 6438H, LD E,68H, 104, 2 x code 52, Bad file number - a file that is not open, or the reserved file zero.
643A
Entered at 643BH, LD E,7CH, 124, 2 x code 62, Input past end - an INPUT# or LINE INPUT# that has run off the end of the file.
643D
Entered at 643EH, LD E,6EH, 110, 2 x code 55, File already open - an OPEN of a buffer that is already in use.
6440
Entered at 6441H, LD E,74H, 116, 2 x code 58, File already exists - the ON and EN OPEN modes when the file is already there.
6443
Entered at 6444H, LD E,80H, 128, 2 x code 64, Bad file name - an empty or over-long filespec, or one @FSPEC will not parse.
6446
Entered at 6447H, LD E,7EH, 126, 2 x code 63, Bad record number - a GET or PUT record number of zero.
6449
Entered at 644AH, LD E,64H, 100, 2 x code 50, Field overflow - FIELD declaring more bytes than the record holds.
644C
The common tail. JUMP to the ROM's error entry with the number in Register E, which reports the message and stops. Every entry above falls through the 01H skip bytes to reach here.

644FH - The DOS Error Translator

The other way an error is raised. Where the table above turns a FIXED BASIC error number into a message, this routine turns an LDOS error number - the kind a DOS call leaves in Register A - into the matching BASIC error. It cannot do the translation itself, because the mapping and the message text live in BASIC/OV3, so it loads that overlay with selector 02H and lets it do the work, then reaches the same ROM error entry the table uses. Every "JP NZ,644FH" after a DOS call in the extension ends here.

644F
LD L,A 6F
Copy the LDOS error number from Register A into Register L, where BASIC/OV3 selector 02H expects to find it.
6450
LD A,02H 3E 02
Load Register A with 02H, the BASIC/OV3 function selector for "translate an LDOS error number".
6452
EX AF,AF' 08
Exchange it into the alternate accumulator, which is where the overlay loader at 54B6H reads its selector - the primary accumulator is needed for OVRLY$ and the @LOAD error code.
6453
GOSUB to the BASIC/OV3 loader, which brings the overlay in if it is not already resident and enters it. The overlay reads Register L, sets Register E to the matching BASIC error number and returns.
6456
JUMP to the ROM's error entry with the translated number in Register E, reporting it and stopping.

6459H - The Ready-Prompt Line Collector

The path 5BD8H hands control to when the ROM asks for a line and no file is current. It does far more than the ROM's own collector: at the READY prompt it turns the TRS-80's cursor-movement keys into a single-line editor, and it recognises one-letter command abbreviations. It only does any of this when the line is being collected for the ROM's MAIN direct-mode loop - it checks the return address two levels up against 1A7EH and steps quietly aside for an INPUT statement. The first keystroke is read on its own to decide which kind of line this is: an arrow or a full stop starts line navigation, a backspace or a tab edits the current line, and an ordinary character starts a normal line which is then collected by the ROM and, when complete, checked against the abbreviation table at 656BH. The TRS-80 video control codes used throughout are 0EH cursor on, 0FH cursor off, 1BH cursor up, 1DH move to the start of the line and 1EH erase to the end of the line.

6459
POP HL E1
POP the ROM's immediate return address into Register Pair HL.
645A
EX (SP),HL E3
Exchange it with the word beneath, so Register Pair HL now holds the return address TWO levels up - the caller of the routine that entered the vector - and the immediate return goes back on the stack.
645B
LD DE,1A7EH 11 7E 1A
Load Register Pair DE with 1A7EH, the address inside the ROM's MAIN direct-mode loop that collects a command line.
645E
RESTART to the DCOMPR hook, setting the Z FLAG when the caller two levels up is the MAIN loop.
645F
EX (SP),HL E3
Exchange back, restoring the stack to the depth it had on entry.
6460
PUSH HL E5
Save the immediate return address again, in case this turns out to be an ordinary line.
6461
RET NZ C0
If the request did not come from the MAIN loop - an INPUT statement, say - RETurn and let the ROM collect the line in the usual way. None of the editor behaviour applies.
6462
Load Register Pair HL with the address of SFLAG$ 430FH.
6465
BIT 5,(HL) CB 6E
Test bit 5 of SFLAG$.
6467
RET NZ C0
If it is set, RETurn and collect the line ordinarily. This is the switch that lets a program or the DOS suppress the interactive editor.
6468
POP HL E1
Discard the immediate return address saved at 6460H; from here LBASIC owns the line collection and re-enters the ROM by its own route.
6469
LD A,0EH 3E 0E
Load Register A with 0EH, the video code to turn the cursor on.
646B
GOSUB to the ROM's $DSP to display it, so the operator sees a cursor while the first key is awaited.
646E
GOSUB to the ROM's $KEY, which waits for one keystroke and returns it in Register A.
6471
PUSH AF F5
Save the keystroke and flags.
6472
GOSUB to 5C93H, which clears the low three bits of KFLAG$ 4423H, discarding any BREAK or PAUSE left pending from before the line began.
6475
CP 5BH FE 5B
Compare the keystroke against 5BH, the up-arrow.
6477
If it is, JUMP to the line-navigation code.
6479
CP 0AH FE 0A
Compare against 0AH, the down-arrow.
647B
and again JUMP to line navigation.
647D
CP 2EH FE 2E
Compare against 2EH (ASCII: .), which selects the current line.
647F
JUMP to line navigation.
6481
CP 2CH FE 2C
Compare against 2CH (ASCII: ,).
6483
JUMP to line navigation.
6485
LD HL,(40A4H) 2A A4 40
Load Register Pair HL with TXTTAB, the start of the program - the navigation code below wants it and this is a convenient place to load it whichever way the next tests go.
6488
CP 08H FE 08
Compare the keystroke against 08H, the backspace / left-arrow.
648A
If it is, JUMP to 64DFH, which edits the current line.
648C
LD DE,0FFFFH 11 FF FF
Load Register Pair DE with 0FFFFH, used below as the "no line found" sentinel.
648F
CP 09H FE 09
Compare the keystroke against 09H, the tab / right-arrow.
6491
If it is, JUMP into the navigation code at 64A7H.
6493
POP AF F1
An ordinary first character. Recover it into Register A.
6494
PUSH BC C5
Save Register Pair BC.
6495
LD HL,651AH 21 1A 65
Load Register Pair HL with 651AH, the address to process the completed line.
6498
PUSH HL E5
Push it as the return address the ROM collector will come back to.
6499
LD HL,(40A7H) 2A A7 40
Load Register Pair HL with BUFPNT 40A7H, the line buffer that initialization placed at 5309H.
649C
PUSH HL E5
Push the buffer address.
649D
LD B,0F0H 06 F0
Load Register B with 0F0H, 240, the buffer capacity.
649F
LD C,B 48
Copy it into Register C as well.
64A0
JUMP to the ROM's KEYIN line collector with the first character in Register A, the buffer and length stacked, and 651AH underneath as the return address. The rest of the line is gathered exactly as the ROM always gathers it.
64A3
LD DE,(40ECH) ED 5B EC 40
Line navigation. Load Register Pair DE with DOT 40ECH, the line number the EDIT and LIST commands remember as ".".
64A7
GOSUB to the ROM's FNDLIN, which searches for the line in Register Pair DE and returns Register Pair BC pointing at it, with the CARRY FLAG set if it was not found.
64AA
POP DE D1
Peek the keystroke back off the stack.
64AB
PUSH DE D5
and push it again - this is the peek idiom, reading the saved key without disturbing the stack.
64AC
LD A,D 7A
Copy the keystroke into Register A.
64AD
If FNDLIN found the line, JUMP to 64C3H.
64AF
CP 2EH FE 2E
The line was not found. Compare the key against 2EH (ASCII: .).
64B1
If it was the full stop, JUMP to 64DDH.
64B3
CP 2CH FE 2C
Compare against 2CH (ASCII: ,).
64B5
JUMP to 64DDH.
64B7
CP 0AH FE 0A
Compare against 0AH, the down-arrow.
64B9
If it was not, JUMP to 64C5H.
64BB
LD A,(HL) 7E
The down-arrow with no current line. Read the low byte of the link at the start of the program.
64BC
INC HL 23
INCrement Register Pair HL by 1.
64BD
OR (HL) B6
OR the high byte, so the Z FLAG is set only when the link is zero - an empty program.
64BE
DEC HL 2B
DECrement Register Pair HL by 1, back to the link.
64BF
If the program is not empty, JUMP to 64DFH and edit the first line.
64C1
Otherwise JUMP to 64DDH.
64C3
FNDLIN found the line. If the key sets the Z FLAG (it was the down-arrow, tested by the LD A,D / this JR pairing), JUMP to 64C9H to step to the next line.
64C5
CP 5BH FE 5B
Compare the key against 5BH, the up-arrow.
64C7
If it was not the up-arrow, JUMP to 64DDH and edit the found line as it stands.
64C9
LD HL,(40A4H) 2A A4 40
Step to the previous or next line. Load Register Pair HL with TXTTAB.
64CC
AND A A7
Clear the CARRY FLAG for the subtraction.
64CD
SBC HL,BC ED 42
SUBTRACT the found line's address, setting the Z FLAG when the found line IS the first line of the program.
64CF
If it is the first line, there is no previous line, so JUMP to 64DDH.
64D1
ADD HL,BC 09
Restore Register Pair HL to the found line's address.
64D2
LD D,B 50
Copy the found line address into Register Pair DE.
64D3
LD E,C 59
64D4
LD B,H 44
Loop Start
Copy the current candidate into Register Pair BC.
64D5
LD C,L 4D
64D6
LD A,(HL) 7E
Read the low byte of this line's forward link.
64D7
INC HL 23
INCrement Register Pair HL by 1.
64D8
LD H,(HL) 66
Read the high byte into Register H.
64D9
LD L,A 6F
and the low byte into Register L, so Register Pair HL is now the NEXT line.
64DA
RESTART to the DCOMPR hook, comparing the next line against the target in Register Pair DE.
64DB
While the next line is not yet the target, JUMP back, so Register Pair BC walks up the program and ends holding the line BEFORE the target - the previous line.
64DD
LD H,B 60
Move the chosen line address into Register Pair HL.
64DE
LD L,C 69
64DF
LD A,0FH 3E 0F
Load Register A with 0FH, the video code to turn the cursor off.
64E1
GOSUB to $DSP to display it.
64E4
LD A,1DH 3E 1D
Load Register A with 1DH, the video code to move the cursor to the start of the line.
64E6
GOSUB to $DSP.
64E9
POP AF F1
Discard the saved keystroke.
64EA
POP BC C1
Restore Register Pair BC.
64EB
LD B,H 44
Copy the chosen line address into Register Pair BC.
64EC
LD C,L 4D
64ED
INC HL 23
INCrement Register Pair HL by 1.
64EE
INC HL 23
INCrement Register Pair HL by 1 again, over the forward link to the line number.
64EF
LD E,(HL) 5E
Read the low byte of the line number.
64F0
INC HL 23
INCrement Register Pair HL by 1.
64F1
LD D,(HL) 56
and the high byte, so Register Pair DE is the line number.
64F2
CP 2CH FE 2C
Compare the keystroke against 2CH (ASCII: ,).
64F4
If it was the comma, JUMP into the ROM at 2E65H, an entry in the EDIT-command area. That label is not in the ROM reference to hand; the code reaches it only for the comma key with a line number in Register Pair DE and a line address in Register Pair BC.
64F7
PUSH DE D5
Save the line number.
64F8
PUSH BC C5
Save the line address.
64F9
LD HL,(4020H) 2A 20 40
Load Register Pair HL with the cursor's current video-RAM address from 4020H.
64FC
LD DE,0FFC0H 11 C0 FF
Load Register Pair DE with minus 64.
64FF
ADD HL,DE 19
ADD it, backing Register Pair HL up one screen line.
6500
LD B,05H 06 05
Load Register B with 5, the length of the word "READY".
6502
Load Register Pair DE with 1929H, the ROM's "READY" text.
6505
LD A,(DE) 1A
Loop Start
Read one character of "READY".
6506
CP (HL) BE
Compare it against the screen.
6507
If they differ, the line above is not the READY prompt, so JUMP to 6512H and leave it alone.
6509
INC DE 13
INCrement Register Pair DE by 1.
650A
INC HL 23
INCrement Register Pair HL by 1.
650B
DECrement Register B and JUMP back until all five characters have matched.
650D
LD A,1BH 3E 1B
The line above is the READY prompt. Load Register A with 1BH, the video code to move the cursor up.
650F
GOSUB to $DSP, moving onto the READY line so that it is overwritten rather than left above the listing.
6512
LD A,1EH 3E 1E
Load Register A with 1EH, the video code to erase to the end of the line.
6514
GOSUB to $DSP, clearing whatever was on the line before the chosen line is shown.
6517
JUMP to the ROM's LIST-one-line entry, which lists the line whose address is in Register Pair BC and whose number is in Register Pair DE, then returns to the READY loop with that line on screen ready to be edited.
651A
PUSH AF F5
Reached by RET from the ROM collector at 05E3H once an ordinary line is complete. Save Register A and the flags - the CARRY FLAG marks a line ended by BREAK.
651B
GOSUB to 5C93H to clear the pending BREAK/PAUSE/ENTER bits of KFLAG$ once more.
651E
POP AF F1
Recover the flags.
651F
PUSH AF F5
and save them again.
6520
If the line was ended by BREAK, JUMP to 6569H and hand it to the ROM unchanged.
6522
PUSH BC C5
Save Register Pair BC.
6523
LD A,(HL) 7E
Read the first character of the collected line.
6524
RES 5,A CB AF
Fold its case to upper.
6526
PUSH HL E5
Save the line pointer.
6527
LD HL,656BH 21 6B 65
Load Register Pair HL with 656BH, the base of the one-letter abbreviation table.
652A
INC HL 23
Loop Start
INCrement Register Pair HL onto the next table entry's letter.
652B
INC (HL) 34
INCrement the byte there.
652C
DEC (HL) 35
and DECrement it again, setting the Z FLAG when it is the 00H that ends the table without changing it.
652D
If the table has ended with no match, JUMP to 6567H and leave the line alone.
652F
CP (HL) BE
Compare the first character against this entry's letter.
6530
INC HL 23
INCrement Register Pair HL onto the entry's code byte.
6531
If the letter did not match, JUMP back for the next entry.
6533
LD C,(HL) 4E
A match. Read the entry's code into Register C.
6534
POP HL E1
Recover the line pointer.
6535
PUSH HL E5
and save it again.
6536
RESTART to the character fetch hook, stepping to the character after the abbreviation letter and setting the CARRY FLAG if it is a digit.
6537
A digit may follow (a line number), so JUMP to 6545H.
6539
CP 2EH FE 2E
Compare against 2EH (ASCII: .).
653B
The full stop is allowed, so JUMP to 6545H.
653D
CP 0DH FE 0D
Compare against 0DH, an immediate carriage return.
653F
If the letter stands alone, JUMP to 6552H and apply the abbreviation.
6541
CP 2DH FE 2D
Compare against 2DH (ASCII: -), the start of a line range.
6543
If it is none of these, the letter is the start of an ordinary word, not an abbreviation, so JUMP to 6567H and leave the line alone.
6545
Loop Start
RESTART to the character fetch hook, scanning forward through the line-range.
6546
CP 28H FE 28
Compare against 28H (ASCII: ().
6548
A parenthesis means this is a function reference, not a command, so JUMP to 6567H and abandon the match.
654A
CP 3DH FE 3D
Compare against 3DH (ASCII: =).
654C
An equals sign means an assignment, so JUMP to 6567H and abandon the match.
654E
CP 0DH FE 0D
Compare against 0DH, the end of the line.
6550
While the end has not been reached, JUMP back and keep scanning.
6552
POP HL E1
The abbreviation stands. Recover the line pointer.
6553
LD (HL),C 71
Overwrite the abbreviation letter with the code from the table.
6554
LD A,C 79
Copy the code into Register A.
6555
CP 07H FE 07
Compare it against 07H.
6557
If the code is 07H or above it is a BASIC keyword token, so the letter has just been expanded into a full statement in place: JUMP to 6568H to hand the amended line to the ROM.
6559
EX AF,AF' 08
A code below 07H is a BASIC/OV3 function selector - one of the two DOS-5 edit commands the manual notes live in that overlay. Exchange it into the alternate accumulator, where the overlay loader reads it.
655A
POP BC C1
Restore Register Pair BC.
655B
POP AF F1
Restore the flags word.
655C
EX DE,HL EB
Move the line pointer into Register Pair DE.
655D
LD L,B 68
Load Register L with Register B, the count of characters left in the line.
655E
LD H,00H 26 00
Clear Register H, so Register Pair HL is that count.
6560
ADD HL,DE 19
ADD the line pointer, giving the address just past the line's text.
6561
LD (HL),00H 36 00
Store a 00H terminator there, so the overlay reads a clean line.
6563
EX DE,HL EB
Move the line pointer back into Register Pair HL, one byte into the line as the overlay expects.
6564
JUMP to the BASIC/OV3 loader, which brings the editor overlay in and enters it with the selector in the alternate accumulator.
6567
POP HL E1
Recover the line pointer.
6568
POP BC C1
Restore Register Pair BC.
6569
JUMP to the ROM at 0375H, the line-input path just past the 41AFH hook, where the ROM tokenizes and acts on the line as usual. That label is not in the ROM reference to hand, but the two entries into it - a BREAK line and an ordinary line - and the register state (Register Pair HL at the line, the flags preserved) fix what it must be.
656C-656D
DEFB "L",0B4H 4C B4
Abbreviation table. The letter L and its code 0B4H. A code of 07H or above is a BASIC keyword token that replaces the letter, so a single L followed by a line range expands into the corresponding statement.
656E-656F
DEFB "D",0B6H 44 B6
The letter D and its keyword token 0B6H.
6570-6571
DEFB "A",0B7H 41 B7
The letter A and its keyword token 0B7H.
6572-6573
DEFB "E",9DH 45 9D
The letter E and its keyword token 9DH.
6574-6575
DEFB "M",05H 4D 05
The letter M and code 05H. Being below 07H it is a BASIC/OV3 selector, so M loads the editor overlay rather than expanding to a token.
6576-6577
DEFB "C",06H 43 06
The letter C and BASIC/OV3 selector 06H, the other DOS-5 edit command.
6578
DEFB 00H 00
The 00H that ends the table, found by the INC/DEC test at 652BH.

6579H - The RST 10H Replacement

The keystone. Initialization installed this address at 4003H in place of the ROM's own CHRGTR at 1D78H, so every "fetch the next character of the program" in the whole machine now passes through here - thousands of times a second. Almost always it is transparent: it does the real fetch and returns. The one thing it adds is that when, and only when, the caller is the statement executor at 1D5BH - the point in NEWSTT at which the ROM is about to act on a statement keyword - it looks at the token and handles the two statements the ROM's dispatcher does not know. Token 90H is RESTORE, which LBASIC extends to accept a line number; the ROM's own RESTORE always rewinds to the first DATA statement. The other is a two-token form beginning 83H that repositions a random file. Because this hook owns the interpreter's inner loop it is the mechanism behind every keyword LBASIC adds that the ROM's token table does not contain, and it is why the single exit to DOS at 57BAH is so careful to put 1D78H back into the operand at 4004H first: leaving 6579H there would send every subsequent fetch into an image about to be overwritten.

6579
GOSUB to the ROM's real CHRGTR, which advances Register Pair HL, skips blanks and returns the next character in Register A with the CARRY FLAG set if it is a digit and the Z FLAG set if it is a statement terminator. The work the hook exists to intercept is done first so that the common path is unchanged.
657C
RET Z C8
If the character is a statement terminator there is nothing to recognise, so RETurn at once. This is the fast path the overwhelming majority of fetches take.
657D
EXX D9
Exchange into the alternate register set so the caller-address test disturbs nothing - RST 10H must preserve every register but Register A and the flags.
657E
POP HL E1
POP the return address - where in the ROM this RST 10H returns to - into the alternate Register Pair HL.
657F
PUSH HL E5
and push it straight back, leaving the stack as it was.
6580
PUSH AF F5
Save Register A and the flags, because the subtraction below destroys them and the caller needs them.
6581
LD DE,1D5BH 11 5B 1D
Load Register Pair DE with 1D5BH, the address in NEWSTT the ROM returns to after fetching a statement keyword.
6584
AND A A7
Clear the CARRY FLAG for the subtraction.
6585
SBC HL,DE ED 52
SUBTRACT, setting the Z FLAG only when this fetch was made by the statement executor.
6587
EXX D9
Exchange back to the main register set. The Z FLAG survives the exchange.
6588
If the caller was anything other than the statement dispatcher, JUMP to 65F0H, restore Register A and return - the fetch is transparent. Every keyboard, PRINT and expression fetch takes this exit.
658A
CP 83H FE 83
The statement executor is about to act on a keyword. Compare the token in Register A against 83H.
658C
If it is not 83H, JUMP to 65D0H to test for the other added statement.
658E
PUSH HL E5
Token 83H, the start of a two-token statement that repositions a random file. Save the text pointer.
658F
RESTART - through this same hook - to fetch the SECOND token of the pair.
6590
CP 0E9H FE E9
Compare it against 0E9H, the token that must follow.
6592
If the second token is not 0E9H this is not the statement after all, so JUMP to 65EFH, recover the pointer and let the ROM raise its own syntax error.
6594
POP DE D1
Discard the saved text pointer.
6595
POP DE D1
Discard the return address into NEWSTT as well: from here LBASIC takes the statement over and re-enters the executor itself.
6596
RESTART to fetch the character after the two tokens.
6597
CP 23H FE 23
Compare it against 23H (ASCII: #), which may optionally precede the file number.
6599
If the hash mark is there, GOSUB to the ROM's CHRGTR to step over it.
659C
GOSUB to the ROM's GETBYT, evaluating the file number into Register A.
659F
AND A A7
Set the flags from it.
65A0
File zero is not a user file, so JUMP to 6438H and raise "Bad file number".
65A3
GOSUB to the file reference parser, returning Register Pair BC pointing at the buffer with the mode in Register A.
65A6
If the file is not open, JUMP to 6438H and raise "Bad file number".
65A9
CP 03H FE 03
Compare the mode against 03H, random access.
65AB
If the file is not a random file, JUMP to 6435H and raise "Bad file mode" - this statement applies only to random files.
65AE
LD D,B 50
Copy the buffer address into Register Pair DE.
65AF
LD E,C 59
65B0
INC DE 13
INCrement Register Pair DE by 1.
65B1
INC DE 13
INCrement Register Pair DE by 1 again, onto the file control block.
65B2
GOSUB to @CKEOF, which reports whether there is still a record beyond the current position.
65B5
If the end has been passed, there is nothing to reposition, so JUMP to 65CCH.
65B7
PUSH DE D5
Save the control block address.
65B8
POP IX DD E1
and POP it into Register IX for indexed access.
65BA
LD E,(IX+0AH) DD 5E 0A
Read the low byte of the next record number, at control block offset 0AH.
65BD
LD D,(IX+0BH) DD 56 0B
and the high byte at offset 0BH, so Register Pair DE is the next record number.
65C0
LD A,(IX+05H) DD 7E 05
Read the byte offset within the current record, at offset 05H.
65C3
LD (IX+0CH),E DD 73 0C
Store the record number into the ending-record-number field at offset 0CH.
65C6
LD (IX+0DH),D DD 72 0D
and its high byte at offset 0DH.
65C9
LD (IX+08H),A DD 77 08
Store the byte offset into the end-of-file byte at offset 08H. The three stores set the file's recorded end to the current position; the ROM keyword name for the 83H/0E9H pair would come from the token table, which is not among the inputs, but the effect is unambiguous from the fields written.
65CC
POP DE D1
Recover Register Pair DE. This is where 65B5H lands when there was nothing to reposition.
65CD
JUMP to NEWSTT to execute the next statement. LBASIC discarded the ROM's return address at 6595H, so this is how it hands control back to the interpreter.
65D0
CP 90H FE 90
Not token 83H. Compare the token against 90H, RESTORE.
65D2
If it is not RESTORE either, JUMP to 65F0H and return transparently - the ROM will dispatch whatever it was.
65D4
POP DE D1
RESTORE. Discard the text pointer.
65D5
POP DE D1
Discard the return address into NEWSTT: LBASIC takes RESTORE over.
65D6
LD DE,1D1EH 11 1E 1D
Load Register Pair DE with 1D1EH, NEWSTT.
65D9
PUSH DE D5
Push it as the return address, so every path below re-enters the statement executor when it finishes.
65DA
RESTART to fetch the character after the RESTORE token.
65DB
If it is a statement terminator, no line number was given, so JUMP to the ROM's own RESTORE at 1D91H, which rewinds to the first DATA statement. Plain RESTORE behaves exactly as it always did.
65DE
A line number follows. GOSUB to the ROM's LINGET, which parses the decimal digits into Register Pair DE.
65E1
PUSH HL E5
Save the text pointer.
65E2
GOSUB to the ROM's FNDLIN, which searches for that line and returns the CARRY FLAG set when it is found, with Register Pair BC pointing at it.
65E5
POP HL E1
Recover the text pointer.
65E6
If the line does not exist, JUMP to the ROM's USERR and raise ?UL ERROR.
65E9
DEC BC 0B
DECrement Register Pair BC by 1, onto the byte before the line, because READ increments before it fetches.
65EA
LD (40FFH),BC ED 43 FF 40
Store it into DATPTR 40FFH, the READ/DATA pointer. The next READ now takes its data from the named line - which is exactly the extension the ROM's RESTORE lacks.
65EE
RET C9
RETurn - to NEWSTT, through the address pushed at 65D9H.
65EF
POP HL E1
Reached from 6592H when the token after 83H was not 0E9H. Recover the text pointer.
65F0
POP AF F1
Restore Register A and the flags saved at 6580H, so the character the hook fetched is delivered exactly as CHRGTR would have delivered it. This is the transparent exit reached by 6588H and 65D2H.
65F1
RET C9
RETurn to the caller with the fetched character in Register A.

65F2H - The Run-Time Cells

The last ten bytes of the loaded image, all shipping as 00H and none of them code. They are the extension's scratch variables, described in full in Section 25 of the working notes; they are listed here so that the disassembly accounts for every byte to 65FBH. The pointer table that begins at 65FAH grows two bytes per open file, so it starts inside the image and continues into the memory above it.

65F2
DEFB 00H 00
Never written by anything; used as a DUMMY CLOSED FILE BUFFER so that file zero can be made to read as "closed" while the ROM reorganises memory (installed in slot zero at 5CBFH and 5EBAH, removed at 5CCCH and 5ED9H).
65F3
DEFB 00H 00
A FIELD work cell: the record length, written at 608FH and read back by the overflow test at 60BAH.
65F4
DEFB 00H 00
The CHAIN-STATE / post-load action byte: 00H a plain LOAD, 0FFH a RUN, the option character when one was given, or 0D6H for a chained load with variables. Written at 5E51H, 5E79H and 5E92H and cleared at 5F8FH.
65F5
DEFB 00H 00
A scratch save of MAXFIL 408EH across a load, which is how the R and V options keep open files open while the ROM's NEW or CLEAR runs.
65F6
DEFB 00H 00
The GET/PUT discriminator, written at 6206H by the F6 AF overlap and read at 624CH and 625BH to choose @WRITE or @READ.
65F7
DEFB 00H 00
The last DOS error code, read by CMD"E" at 584AH and OR 0C0H'd into @ERROR 4409H.
65F8-65F9
DEFW 0000H 00 00
The CURRENT FILE BUFFER POINTER; 0000H means no file is current. Set by the file-reference parser at 5CEAH, cleared by the PRINT completion hook at 587BH, and tested throughout by the current-file idiom.
65FA-65FB
DEFW 0000H 00 00
The first slot of the file buffer pointer table, built at 53E5H. Being the last two bytes of the image and shipping as 0000H, the table begins inside the file and continues into the memory beyond it.