The Commodore PET INPUT Bug-Feature
Adventures in MS BASIC (Commodore PET edition).
As it happens, I recently added a debugger to the PET 2001 emulator, with the expressed purpose of facilitating code reverse engineering. To celebrate this, we’ll risk a glimpse into a peculiar bug — or is it a feature? — of the BASIC flavor found on Commodore’s PET computers. Particularly, it’s about BASIC bailing out into a warm start, whenever an empty string is entered by just pressing RETURN on the INPUT prompt.
As we can see, BASIC just ends the execution of the program, as an empty input value is supplied and nothing beyond line 10 is executed. This is not only true for numeric variables, but for string variables, as well:
This behavior is consistent over any versions of BASIC for the PET, BASIC 1 for the original PET 2001, BASIC 2, and BASIC 4.0 (available for the PET 2001N and found on newer models).
Begging the question, is it a bug or a feature?
Certainly, this is not what we find on other versions of MS BASIC. E.g., with BASIC-80, also known as MBASIC:
BASIC-80 Rev. 5.21 [CP/M Version] Copyright 1977-1981 (C) by Microsoft Created: 28-Jul-81 39730 Bytes free Ok 10 INPUT A 20 PRINT A 30 PRINT "END OF PROGRAM":END RUN ? 0 END OF PROGRAM Ok
Even on Commodore’s own C64 (and VIC-20) the INPUT
commands receives a default value of 0
:
**** COMMODORE 64 BASIC V2 **** 64K RAM SYSTEM 38911 BASIC BYTES FREE READY. 10 INPUT A 20 PRINT A 30 PRINT "END OF PROGRAM":END RUN ? 0 END OF PROGRAM READY. █
So, why is the PET BASIC special, but consitently so? Is this replicating a bug found in the original, but known buggy BASIC 1? Let’s investigate…
“The Hunt is on” — BASIC 2
Let’s start with our good, old default, BASIC 2 (depending on your fraction of Commodore PET version counting also known as BASIC 3, or, back in the days before BASIC 4.0, as "New ROM"), which is also the ancestor of BASIC V2 as found on the VIC-20 and C64. And, since it’s a Commodore 8-bit, it’s 6502 code.
We start to intercept the code, where the question mark is prompted, in order to compare a run with an empty input with a run, where we supply a value of “1
”. To get an idea of what we’re dealing with, we only follow the major outlines of the code, using the “Next Block” function of the emulator, which traces the code along JMP
, JSR
, and RTS
:
BASIC 2: empty value CAFA A5 0E LDA $0E ... CAFE 20 43 CA JSR $CA43 ... CA4E 60 RTS CB01 20 39 CA JSR $CA39 ... CA4E 60 RTS CB04 4C 6F C4 JMP $C46F ... C471 20 81 C4 JSR $C481 ... C494 60 RTS C474 C9 0D CMP #$0D ... ... ... ... ... ... C47E 4C D5 C9 JMP $C9D5 C9E4 20 45 CA JSR $CA45 ... CA4E 60 RTS C9E7 A9 0A LDA #$0A ... C9E9 20 45 CA JSR $CA45 ... CA4E 60 RTS C9EC 49 FF EOR #$FF C9EE 60 RTS CADD A5 0E LDA $0E ... CAF7 4C 51 C7 JMP $C751 ... C768 4C 89 C3 JMP $C389 ... C38F 20 1C CA JSR $CA1C ... C9EE 60 RTS C392 20 6F C4 JSR $C46F ...
BASIC 2: value 1 CAFA A5 0E LDA $0E ... CAFE 20 43 CA JSR $CA43 ... CA4E 60 RTS CB01 20 39 CA JSR $CA39 ... CA4E 60 RTS CB04 4C 6F C4 JMP $C46F ... C471 20 81 C4 JSR $C481 ... C494 60 RTS C474 C9 0D CMP #$0D ... C471 20 81 C4 JSR $C481 ... C494 60 RTS C474 C9 0D CMP #$0D ... C47E 4C D5 C9 JMP $C9D5 C9E4 20 45 CA JSR $CA45 ... CA4E 60 RTS C9E7 A9 0A LDA #$0A ... C9E9 20 45 CA JSR $CA45 ... CA4E 60 RTS C9EC 49 FF EOR #$FF C9EE 60 RTS CADD A5 0E LDA $0E ... CB16 20 6D CF JSR $CF6D ... D077 60 RTS CB19 85 46 STA $46 ... CB2D 20 76 00 JSR $0076 ... 0087 60 RTS CB30 D0 20 BNE $CB52 ...
As may be discerned, the code paths (apart from spending another subroutine call for the extra input character in the second version) diverge after $CADD
, being the last instruction both traces have in common.
Let’s have a closer look at this and single-step through the code at $CADD
, using an empty input value:
addr instr disass |AC XR YR SP|nvdizc|# CADD A5 0E LDA $0E |F5 FF 01 F8|100000|3 ;load current I/O device CADF F0 0C BEQ $CAED |00 FF 01 F8|000010|4 ;output supressed, if non-zero CAED AD 00 02 LDA $0200 |00 FF 01 F8|000010|4 ;first char from input buffer CAF0 D0 1C BNE $CB0E |00 FF 01 F8|000010|2 ;to input parsing <–––––––– CAF2 A5 0E LDA $0E |00 FF 01 F8|000010|3 ;check I/O mode CAF4 D0 E4 BNE $CADA |00 FF 01 F8|000010|2 CAF6 18 CLC |00 FF 01 F8|000010|2 ;clear carry: warm start CAF7 4C 51 C7 JMP $C751 |00 FF 01 F8|000010|3 ;into STOP/END/break routine C751 A5 36 LDA $36 |00 FF 01 F8|000010|3 ;current BASIC line (byte 1) C753 A4 37 LDY $37 |0A FF 01 F8|000000|3 ;current BASIC line (byte 2) C755 85 38 STA $38 |0A FF 00 F8|000010|3 ;store as last BASIC line C757 84 39 STY $39 |0A FF 00 F8|000010|3 ;for CONTINUE (not used) C759 68 PLA |0A FF 00 F8|000010|4 C75A 68 PLA |FC FF 00 F9|100000|4 C75B A9 A2 LDA #$A2 |C6 FF 00 FA|100000|2 ;message address (new line) C75D A0 C2 LDY #$C2 |A2 FF 00 FA|100000|2 ;into A,Y - overwritten later C75F A2 00 LDX #$00 |A2 FF C2 FA|100000|2 C761 86 0D STX $0D |A2 00 C2 FA|000010|3 ;reset input mode C763 90 03 BCC $C768 |A2 00 C2 FA|000010|4 ;was cleared at $CAF6 C768 4C 89 C3 JMP $C389 |A2 00 C2 FA|000010|3 ;print "READY." ...
So, first we check, whether we’re going to suppress output or not, depending on the value in $0E
. Then, we check the first byte in the input buffer.
This is what our input buffer at $200
looks like, just then:
0200: 00 00 4E 00 22 2C 38 00 ··n·",8·
0208: 2C 00 00 44 44 44 44 44 ,··ddddd
The first byte is a terminating zero-byte, followed by some garbage and leftovers.
Compare what it looks like with an input value of “1
”:
0200: 31 00 4E 00 22 2C 38 00 1·n·",8·
0208: 2C 00 00 44 44 44 44 44 ,··ddddd
The load instruction is followed by an explicit check for this first character not being a terminating zero-byte, indicating an empty input. On which we immediately go for a BASIC warm start.
This looks deliberate. Maybe, this is replicating accidental behaviour in an earlier version?
BASIC on the Job — BASIC 1
Let’s go back to the roots, and by this, the truth of the matter, in order to find out, what this is all about. What horrible bug may be hidden there, now as mercyfully as gracefully covered by this branch instruction?
So, let’s have a closer look at BASIC 1, which is not exactly the root, but already a patched version. This is also, where the schism in PET version counting originates: Prior to this, there was already a rather buggy version of MS BASIC for the PET, but — as far as I understand it — this was already replaced with the patched ROMs in any production models shipped (and most pre-production models had their ROMs replaced — so it may be difficult to stumble upon a copy of this “in the wild”.) Depending on where you start counting, from the prerelease version or the first version released, this is either BASIC 2 or BASIC 1 — and the version after this either BASIC 3 or BASIC 2. Even Commodore documents are somewhat ambigue in what they are referring to as the “Level II ROM set”. Anyway, in the days of the PET 2001, before there was BASIC 4.0, this was known as “Old ROM” (as opposed to the “New ROM”, we’ve just seen above), no numbers required.
(BASIC 4, besides adding disk commands, is very similar to BASIC 2, “New ROM”, and shares the same zero-page addresses and system variables. Call address of various systrem routines are however different, and there’s a new editor, which requires an additional 4K ROM slot that wasn’t available before the PET 2001N and CBM 2001/B.)
Trigger warning: This is going to be a bit of a ride. But it may be interesting to follow along and see what a task like this actually involves. I promise to insert some snarky comments and, as a bonus for those willing to endure, to even present a workaround at the very end.
Mira Monstrum
So, as there’s no use in procrastinating by dwelling in Commodore history, let’s face the beast and have a closer look at the monster that is MS BASIC, as it churns along. With an watchful eye on any lions and dragons that may dwell hidden by its code. Here, we enter right at the start of the INPUT
routine.
addr instr disass |AC XR YR SP|nvdizc|# CAE0 46 64 LSR $64 |41 04 0A FC|000001|5 ;shift left the flag in I/O device CAE2 C9 22 CMP #$22 |41 04 0A FC|000010|2 ;is the next char a quote? CAE4 D0 0B BNE $CAF1 |41 04 0A FC|000001|4 ;no CAF1 20 8B D2 JSR $D28B |41 04 0A FC|000001|6 D28B A6 89 LDX $89 |41 04 0A FA|000001|3 ;current line number D28D E8 INX |41 00 0A FA|000011|2 ; a zero indicates direct mode D28E D0 A2 BNE $D232 |41 01 0A FA|000001|4 ;is it zero? (no) D232 60 RTS |41 01 0A FA|000001|6
The first instruction prepares a flag in $64
for things to come. This holds the current output device and is used as a flag to supress print, if it is anything else than the default value of 0
. As we go along, $64
will grow as a familiar friend to us. The next instruction tests for any left-over from previous parsing, indicating that we had to print a prompt string, which is not the case.
With no prompt string to print and these preliminaries out of the way, we’re ready to prepare for the actual input:
CAF4 A9 2C LDA #$2C |41 01 0A FC|000001|2 ;load const. $2C (",") CAF6 85 09 STA $09 |2C 01 0A FC|000001|3 ;store it as input delimiter CAF8 20 17 CB JSR $CB17 |2C 01 0A FC|000001|6 CB17 A5 03 LDA $03 |2C 01 0A FA|000001|3 ;check output mode CB19 D0 06 BNE $CB21 |00 01 0A FA|000011|2 ;print supressed? CB1B 20 47 CA JSR $CA47 |00 01 0A FA|000011|6
$09
is here used as temporal storage, which receives a comma as the delimiter for input related string parsing.
Then, we meet what will become another good friend, the “LDA $03 – BNE …
” idiom. Zero-page address $03
holds the active I/O channel. Similar to $64
any non-zero value means that output is supressed, thus skipping any print routines. Here, we’ll always print.
Thus, we arrive at the point, where we print the “?
” prompt:
CA47 A9 3F LDA #$3F |00 01 0A F8|000011|2 ;load $3F ("?") CA49 24 64 BIT $64 |3F 01 0A F8|000001|3 ;current I/O device (00) CA4B 30 27 BMI $CA74 |3F 01 0A F8|000011|2 CA4D 48 PHA |3F 01 0A F8|000011|3 ;$3F -> stack CA4E C9 1D CMP #$1D |3F 01 0A F7|000011|2 ;is it $1D [CRSR RIGHT]? CA50 F0 18 BEQ $CA6A |3F 01 0A F7|000001|2 CA52 C9 9D CMP #$9D |3F 01 0A F7|000001|2 ;is it $9D [CRSR LEFT]? CA54 F0 04 BEQ $CA5A |3F 01 0A F7|100000|2 CA56 C9 14 CMP #$14 |3F 01 0A F7|100000|2 ;is it $14 [DELETE]? CA58 D0 0A BNE $CA64 |3F 01 0A F7|000001|4 CA64 29 7F AND #$7F |3F 01 0A F7|000001|2 ;mask basic case / unshift CA66 C9 20 CMP #$20 |3F 01 0A F7|000001|2 ;is it printable ([SPACE] or higher)? CA68 90 06 BCC $CA70 |3F 01 0A F7|000001|2 CA6A A5 03 LDA $03 |3F 01 0A F7|000001|3 ;load i/O device? CA6C 20 CC E1 JSR $E1CC |00 01 0A F7|000011|6 E1CC D0 0A BNE $E1D8 |00 01 0A F5|000011|2 E1CE E6 05 INC $05 |00 01 0A F5|000011|5 ;column next print E1D0 D0 06 BNE $E1D8 |00 01 0A F5|000001|4 ;print suppressed? (compare $CA6A) E1D8 60 RTS |00 01 0A F5|000001|6
This is a bit amusing and even a bit sad. Anyway, it looks a bit like hackage. Mind how we first load the hard coded value $3F
, only to run several test on it.
I get it, Microsoft, it is about organizing code in reusable modules, but my precious CPU cycles!
“LDA #$3F – JMP $CA6A
“ would have done it, perfectly. I understand, it’s 3 bytes more, but…
CA6F EA NOP |00 01 0A F7|000001|2
Oh, forget that bit about extra bytes…
CA70 68 PLA |00 01 0A F7|000001|4 ;pull $3F ("?") from stack CA71 20 D2 FF JSR $FFD2 |3F 01 0A F8|000001|6 ;print routine
By this, we pull $3F
from the stack and call the KERNAL print routine at $FFD2
, which expects the character code to print in the accumulator.
addr instr disass |AC XR YR SP|nvdizc|# FFD2 4C 30 F2 JMP $F230 |3F 01 0A F6|000001|3 ;KERNAL entry for $FFD2 F230 48 PHA |3F 01 0A F6|000001|3 ;push char to stack (again) F231 AD 64 02 LDA $0264 |3F 01 0A F5|000001|4 ;check default output device F234 D0 03 BNE $F239 |03 01 0A F5|000001|4 ;print supressed? F239 C9 03 CMP #$03 |03 01 0A F5|000001|2 ;is it 3? F23B D0 04 BNE $F241 |03 01 0A F5|000011|2 F23D 68 PLA |03 01 0A F5|000011|4 ;pull $3F ("?") F23E 4C EA E3 JMP $E3EA |3F 01 0A F6|000001|3 ;print a PETSCII char to screen
In it’s never-ending wealth of means to store I/O modes and devices, BASIC checks $0264
, for a change. By this, we’ve found out that we’re going print to the screen. And, in the fashion of a true bully, we continue to push and pull $3F
around…
E3EA 48 PHA |3F 01 0A F6|000001|3 ;push $3F ("?") to stack E3EB 85 F6 STA $F6 |3F 01 0A F5|000001|3 ;store char in I/O temp E3ED 8A TXA |3F 01 0A F5|000001|2 E3EE 48 PHA |01 01 0A F5|000001|3 E3EF 98 TYA |01 01 0A F4|000001|2 E3F0 48 PHA |0A 01 0A F4|000001|3 E3F1 A9 00 LDA #$00 |0A 01 0A F3|000001|2 ;set input device to keyboard E3F3 8D 60 02 STA $0260 |00 01 0A F3|000011|4 ;input device (0: keyboard, 1:screen) E3F6 A4 E2 LDY $E2 |00 01 0A F3|000011|3 ;logical cursor pos (column) E3F8 A5 F6 LDA $F6 |00 01 00 F3|000011|3 ;load char from I/O temp E3FA 10 03 BPL $E3FF |3F 01 00 F3|000001|4 ;not a shifted char? E3FF C9 0D CMP #$0D |3F 01 00 F3|000001|2 ;is it [RETURN]? E401 D0 03 BNE $E406 |3F 01 00 F3|000001|4 E406 C9 20 CMP #$20 |3F 01 00 F3|000001|2 ;is [SPACE] or higher (printable)? E408 90 08 BCC $E412 |3F 01 00 F3|000001|2 E40A 29 3F AND #$3F |3F 01 00 F3|000001|2 ;mask to first 16 printable chars E40C 20 49 E3 JSR $E349 |3F 01 00 F3|000001|6 E349 C9 22 CMP #$22 |3F 01 00 F1|000001|2 ;is it a quote? E34B D0 08 BNE $E355 |3F 01 00 F1|000001|4 E355 60 RTS |3F 01 00 F1|000001|6
By this, we've set up I/O and done some preliminary tests to sort out some cases that may require special treatment. Here we go, preparing the cursor:
E40F 4C 58 E3 JMP $E358 |3F 01 00 F3|000001|3 ;update cursor E358 AE 0E 02 LDX $020E |3F 01 00 F3|000001|4 ;flag reverse on E35B F0 02 BEQ $E35F |3F 00 00 F3|000011|4 ;is it 0? E35F A6 FB LDX $FB |3F 00 00 F3|000011|3 ;number of keyboard inserts E361 F0 02 BEQ $E365 |3F 00 00 F3|000011|4 ;none?
As this is about early PETs with a nick for screen snow (due to the shared static RAM), we wait for the vertical blank interval (V-BLANK) as indicated by the video retrace signal in bit 5 of VIA PORT B going low.
E365 20 AC E7 JSR $E7AC |3F 00 00 F3|000011|6 ;wait for screen update E7AC A8 TAY |3F 00 00 F1|000011|2 E7AD AD 40 E8 LDA $E840 |3F 00 3F F1|000001|4 ;load VIA PORT B E7B0 29 20 AND #$20 |98 00 3F F1|100001|2 ;mask $20 (video retrace flag) E7B2 D0 F9 BNE $E7AD |00 00 3F F1|000011|2 ;loop, unless low E7B4 98 TYA |00 00 3F F1|000011|2 E7B5 A4 E2 LDY $E2 |3F 00 3F F1|000001|3 ;pointer cursor column position E7B7 91 E0 STA ($E0),Y |3F 00 00 F1|000011|6 ;display it in video memory E7B9 60 RTS |3F 00 00 F1|000011|6 E368 E6 E2 INC $E2 |3F 00 00 F3|000011|5 ;inc cursor column position E36A A4 F2 LDY $F2 |3F 00 00 F3|000001|3 ;max line length E36C C4 E2 CPY $E2 |3F 00 27 F3|000001|3 ;are we at end? E36E B0 1A BCS $E38A |3F 00 27 F3|000001|4 E38A 68 PLA |3F 00 27 F3|000001|4 E38B A8 TAY |0A 00 27 F4|000001|2 E38C A5 FB LDA $FB |0A 00 0A F4|000001|3 ;number of characters to print E38E F0 02 BEQ $E392 |00 00 0A F4|000011|4 ;none? E392 68 PLA |00 00 0A F4|000011|4 E393 AA TAX |01 00 0A F5|000001|2 E394 68 PLA |01 01 0A F5|000001|4 E395 58 CLI |3F 01 0A F6|000001|2 E396 60 RTS |3F 01 0A F6|000001|6
Zero-page location $E0
(in tandem with $E1
) holds a pointer to the screen location corresponding to the current cursor line. Thus, with a bit of indirect-indexed addressing, we finally store the character in screen memory, from which it will be displayed. After incrementing the cursor position and checking for any overlow, we"re ready to go for a CURSOR RIGHT, providing some separating space for the actual input, which is still to come. Here we go:
CA74 29 FF AND #$FF |3F 01 0A F8|000001|2 ;--?--
Sorry, it’s just some filler. But here we go in ernest:
CA76 60 RTS |3F 01 0A F8|000001|6 CB1E 20 44 CA JSR $CA44 |3F 01 0A FA|000001|6 ;routine for cursor right CA44 A9 1D LDA #$1D |3F 01 0A F8|000001|2 ;load $1D [CRSR RIGHT] CA46 2C A9 3F BIT $3FA9 |1D 01 0A F8|000001|4 ; $3FA9 ??? CA49 24 64 BIT $64 |1D 01 0A F8|000001|3 ;test output supressed? CA4B 30 27 BMI $CA74 |1D 01 0A F8|000011|2
The “BIT $3FA9
” is somewhat interesting: On the one hand it’s loading from the BASIC user area (from the high regions of a 16K machine, when PETs still came with 4K stock), on the other hand, any effects of this are immediately overwritten by the next “BIT $64
”. Oh, it’s some filler, again. (But it may stand out on a logic analyzer?)
CA4D 48 PHA |1D 01 0A F8|000011|3 ;push $1D CA4E C9 1D CMP #$1D |1D 01 0A F7|000011|2 ;is it [CRSR RIGHT]? CA50 F0 18 BEQ $CA6A |1D 01 0A F7|000011|4 ;yes, it is! CA6A A5 03 LDA $03 |1D 01 0A F7|000011|3 ;active I/O channel CA6C 20 CC E1 JSR $E1CC |00 01 0A F7|000011|6 E1CC D0 0A BNE $E1D8 |00 01 0A F5|000011|2 ;if not zero, suppress (RTS) E1CE E6 05 INC $05 |00 01 0A F5|000011|5 ;increment column next print E1D0 D0 06 BNE $E1D8 |00 01 0A F5|000001|4 E1D8 60 RTS |00 01 0A F5|000001|6
By this, we’ve managed to increment the cursor position for the actual print routine.
I appreciate the instructions at $CA4E
, where we make sure that universe still stands and that the $1D
, which we have loaded hard coded before at $CA44
, is still $1D
. In solidity, we trust…
(I admit being a bit weary about risking a glance at the “universe may be unstable” branch.)
CA6F EA NOP |00 01 0A F7|000001|2
Admittedly, this is as good an occasion for a filler than any other.
Anyway, time to handle our actual print character, which we pull from the stack…
CA70 68 PLA |00 01 0A F7|000001|4 ;pull $1D from stack CA71 20 D2 FF JSR $FFD2 |1D 01 0A F8|000001|6 ;print it
Time for another visit to $FFD2
(and some pushing and pulling):
addr instr disass |AC XR YR SP|nvdizc|# FFD2 4C 30 F2 JMP $F230 |1D 01 0A F6|000001|3 ;KERNAL jump address F230 48 PHA |1D 01 0A F6|000001|3 ;push char ($1D) to stack F231 AD 64 02 LDA $0264 |1D 01 0A F5|000001|4 ;check default output device F234 D0 03 BNE $F239 |03 01 0A F5|000001|4 ;is it zero F239 C9 03 CMP #$03 |03 01 0A F5|000001|2 ;is it 3? F23B D0 04 BNE $F241 |03 01 0A F5|000011|2 F23D 68 PLA |03 01 0A F5|000011|4 ;get char from stack F23E 4C EA E3 JMP $E3EA |1D 01 0A F6|000001|3 E3EA 48 PHA |1D 01 0A F6|000001|3 ;push char to stack E3EB 85 F6 STA $F6 |1D 01 0A F5|000001|3 ;store it in I/O temp E3ED 8A TXA |1D 01 0A F5|000001|2 E3EE 48 PHA |01 01 0A F5|000001|3 E3EF 98 TYA |01 01 0A F4|000001|2 E3F0 48 PHA |0A 01 0A F4|000001|3 E3F1 A9 00 LDA #$00 |0A 01 0A F3|000001|2 ;set output device to 0 E3F3 8D 60 02 STA $0260 |00 01 0A F3|000011|4 E3F6 A4 E2 LDY $E2 |00 01 0A F3|000011|3 ;logical cursor pos (column) E3F8 A5 F6 LDA $F6 |00 01 01 F3|000001|3 ;load char from I/O temp E3FA 10 03 BPL $E3FF |1D 01 01 F3|000001|4 E3FF C9 0D CMP #$0D |1D 01 01 F3|000001|2 ;is it [RETURN]? E401 D0 03 BNE $E406 |1D 01 01 F3|000001|4 E406 C9 20 CMP #$20 |1D 01 01 F3|000001|2 ;is it [BLANK]? E408 90 08 BCC $E412 |1D 01 01 F3|100000|4 E412 A6 FB LDX $FB |1D 01 01 F3|100000|3 ;number of keyboard inserts into X E414 F0 03 BEQ $E419 |1D 00 01 F3|000010|4 ;none? E419 C9 14 CMP #$14 |1D 00 01 F3|000010|2 ;is it [DEL]? E41B D0 1C BNE $E439 |1D 00 01 F3|000001|4 E439 A6 EA LDX $EA |1D 00 01 F3|000001|3 ;flag for quote mode into X E43B F0 03 BEQ $E440 |1D 00 01 F3|000011|4 ;off? E440 C9 12 CMP #$12 |1D 00 01 F3|000011|2 ;is it [REVERSE]? E442 D0 03 BNE $E447 |1D 00 01 F3|000001|4 E447 C9 13 CMP #$13 |1D 00 01 F3|000001|2 ;is it [HOME]? E449 D0 03 BNE $E44E |1D 00 01 F3|000001|4 E44E C9 1D CMP #$1D |1D 00 01 F3|000001|2 ;is it [CRSR RIGHT]? E450 D0 12 BNE $E464 |1D 00 01 F3|000011|2 E452 C8 INY |1D 00 01 F3|000011|2 ;inc logical cursor pos in Y E453 84 E2 STY $E2 |1D 00 02 F3|000001|3 ;store it E455 88 DEY |1D 00 02 F3|000001|2 ;decrement it again E456 C4 F2 CPY $F2 |1D 00 01 F3|000001|3 ;compare with max line length? E458 90 07 BCC $E461 |1D 00 01 F3|100000|4 E461 4C 8A E3 JMP $E38A |1D 00 01 F3|100000|3 E38A 68 PLA |1D 00 01 F3|100000|4 E38B A8 TAY |0A 00 01 F4|000000|2 E38C A5 FB LDA $FB |0A 00 0A F4|000000|3 ;number of keyboard inserts E38E F0 02 BEQ $E392 |00 00 0A F4|000010|4 ;none E392 68 PLA |00 00 0A F4|000010|4 E393 AA TAX |01 00 0A F5|000000|2 E394 68 PLA |01 01 0A F5|000000|4 E395 58 CLI |1D 01 0A F6|000000|2 E396 60 RTS |1D 01 0A F6|000000|6
This time, we go through some additional test, until we arrive at our special case.
Hard work, for which we earned another filler:
CA74 29 FF AND #$FF |1D 01 0A F8|000000|2 CA76 60 RTS |1D 01 0A F8|000000|6
(Arguably, this does achieve something, namely, adjusting the negative and the zero flags to a known state, reflecting the contents of the accumulator, as we leave the service routine. It’s just that I can’t see this used anwhere. So I opt for the 2-byte, 2-cycles filler.)
By this we have accomplished the monumental feat of putting a question mark and a CURSOR RIGHT onto the screen. IDK, but it seems there must be a more efficient way to do this. I get that it is about general purpose routines, but you should be aware of this kind of overhead when calling KERNAL routines for simple tasks. A better part of the code is used for checking and adjusting I/O state over and over again and for copying things around. If you know the state of the machine, you may be better off without these routines.
(Rant: In the light of this, the shiny optimizations, like having the system pointers and even the CHARGET and CHARGOT routines in the zero-page, do not seem to matter that much. I guess, you have to put them *somewhere* and user memory on a 4K machine is sparse?)
Well, let’s read some input characters:
CB21 4C 68 C4 JMP $C468 |1D 01 0A FA|000000|3 C468 A2 00 LDX #$00 |1D 01 0A FA|000000|2 C46A 20 79 C4 JSR $C479 |1D 00 0A FA|000010|6 ;input a character C479 20 CF FF JSR $FFCF |1D 00 0A F8|000010|6 FFCF 4C DF F1 JMP $F1DF |1D 00 0A F6|000010|3 F1DF AD 63 02 LDA $0263 |1D 00 0A F6|000010|4 ;default input device F1E2 D0 0D BNE $F1F1 |00 00 0A F6|000010|2 ; is keyboard F1E4 A5 E2 LDA $E2 |00 00 0A F6|000010|3 ;cursor column pos F1E6 8D 21 02 STA $0221 |02 00 0A F6|000000|4 ;store col for input routine F1E9 A5 F5 LDA $F5 |02 00 0A F6|000000|3 ;current screen line F1EB 8D 20 02 STA $0220 |05 00 0A F6|000000|4 ;store row F1EE 4C FA E2 JMP $E2FA |05 00 0A F6|000000|3 ;read from input device E2FA 98 TYA |05 00 0A F6|000000|2 E2FB 48 PHA |0A 00 0A F6|000000|3 E2FC 8A TXA |0A 00 0A F5|000000|2 E2FD 48 PHA |00 00 0A F5|000010|3 E2FE AD 60 02 LDA $0260 |00 00 0A F4|000010|4 ;input device (0: kbd, 1: screen) E301 F0 94 BEQ $E297 |00 00 0A F4|000010|6 ;is keyboard E297 AD 0D 02 LDA $020D |00 00 0A F4|000010|4 ;keybuffer index E29A 8D 24 02 STA $0224 |01 00 0A F4|000000|4 ;cursor blink flag (0: on, else off) E29D F0 F8 BEQ $E297 |01 00 0A F4|000000|2 ;loop, if zero
If the keyboard buffer index is incremented (by the V-BLANK interrupt service routine), we shift the first input character from the keyboard buffer and shift any remaining queue left by one position.
E29F 78 SEI |01 00 0A F4|000000|2 ;disable interrupts E2A0 AD 27 02 LDA $0227 |01 00 0A F4|000100|4 ;flag cursor on/off E2A3 F0 0B BEQ $E2B0 |00 00 0A F4|000110|4 ;(is on) E2B0 20 7D E2 JSR $E27D |00 00 0A F4|000110|6 ;get character from keyboard buffer E27D AC 0F 02 LDY $020F |00 00 0A F2|000110|4 ;get first char in keyboard buffer E280 A2 00 LDX #$00 |00 00 0D F2|000100|2 ; shift keyboard buffer left E282 BD 10 02 LDA $0210,X |00 00 0D F2|000110|4 ;load next char E285 9D 0F 02 STA $020F,X |00 00 0D F2|000110|5 ;store it in prev. position E288 E8 INX |00 00 0D F2|000110|2 ;nect pos E289 EC 0D 02 CPX $020D |00 01 0D F2|000100|4 ;compare to keybuffer index: done? E28C D0 F4 BNE $E282 |00 01 0D F2|000111|2 ;if not equal, loop E28E CE 0D 02 DEC $020D |00 01 0D F2|000111|6 ;decrement keyboard buffer index E291 98 TYA |00 01 0D F2|000111|2 ;move Y into A (current char) E292 58 CLI |0D 01 0D F2|000101|2 ;enable interrupts again E293 60 RTS |0D 01 0D F2|000001|6
Thus, we have our input character in the accumulator, ready for closer inspection:
E2B3 C9 83 CMP #$83 |0D 01 0D F4|000001|2 ;is it [SHIFT STOP]? E2B5 D0 11 BNE $E2C8 |0D 01 0D F4|100000|4 E2C8 C9 0D CMP #$0D |0D 01 0D F4|100000|2 ;is it [RETURN]? E2CA D0 C8 BNE $E294 |0D 01 0D F4|000011|2 ;yes, it is
As our second test triggers already, we have identified the RETURN key. Thus we commence by searching for the end of the input line, in order to then collect anything in front of it as the actual input.
addr instr disass |AC XR YR SP|nvdizc|#
E2CC A4 F2 LDY $F2 |0D 01 0D F4|000011|3 ;input line length
E2CE 8C 60 02 STY $0260 |0D 01 27 F4|000001|4 ;input device (0: keyboard, 1:screen)
E2D1 B1 E0 LDA ($E0),Y |0D 01 27 F4|000001|5 ;indexed from pointer start of line cursor loc
E2D3 C9 20 CMP #$20 |20 01 27 F4|000001|2 ;is it [SPACE]?
E2D5 D0 03 BNE $E2DA |20 01 27 F4|000011|2
E2D7 88 DEY |20 01 27 F4|000011|2 ;decrement line pos
E2D8 D0 F7 BNE $E2D1 |20 01 26 F4|000001|4 ;is it zero / at start of line?
E2D1 B1 E0 LDA ($E0),Y |20 01 26 F4|000001|5 ;read input char
E2D3 C9 20 CMP #$20 |20 01 26 F4|000001|2 ;is it [SPACE]?
E2D5 D0 03 BNE $E2DA |20 01 26 F4|000011|2
E2D7 88 DEY |20 01 26 F4|000011|2 ;read next char
E2D8 D0 F7 BNE $E2D1 |20 01 25 F4|000001|4
E2D1 B1 E0 LDA ($E0),Y |20 01 25 F4|000001|5
E2D3 C9 20 CMP #$20 |20 01 25 F4|000001|2
E2D5 D0 03 BNE $E2DA |20 01 25 F4|000011|2
E2D7 88 DEY |20 01 25 F4|000011|2 ;read next char
E2D8 D0 F7 BNE $E2D1 |20 01 24 F4|000001|4
E2D1 B1 E0 LDA ($E0),Y |20 01 24 F4|000001|5
E2D3 C9 20 CMP #$20 |20 01 24 F4|000001|2
E2D5 D0 03 BNE $E2DA |20 01 24 F4|000011|2
E2D7 88 DEY |20 01 24 F4|000011|2 ;read next char
E2D8 D0 F7 BNE $E2D1 |20 01 23 F4|000001|4
E2D1 B1 E0 LDA ($E0),Y |20 01 23 F4|000001|5
E2D3 C9 20 CMP #$20 |20 01 23 F4|000001|2
E2D5 D0 03 BNE $E2DA |20 01 23 F4|000011|2
E2D7 88 DEY |20 01 23 F4|000011|2 ;read next char
...
Eventually, decimal 35 (hex $23) iterations later, we reach the start of the line…
...
E2D7 88 DEY |20 01 01 F4|000011|2 ;read next char
E2D8 D0 F7 BNE $E2D1 |20 01 00 F4|000011|2 ;reached pos 0!
E2DA C8 INY |20 01 00 F4|000011|2 ;increment pos again
E2DB 8C 1E 02 STY $021E |20 01 01 F4|000001|4 ;end of line pointer (# of chars)
E2DE A0 00 LDY #$00 |20 01 01 F4|000001|2
E2E0 84 E2 STY $E2 |20 01 00 F4|000011|3 ;set cursor column to 0
E2E2 84 EA STY $EA |20 01 00 F4|000011|3 ;unset quote mode
E2E4 AD 20 02 LDA $0220 |20 01 00 F4|000011|4 ;load temp row number
E2E7 30 1A BMI $E303 |0C 01 00 F4|000001|2 ;is it negative?
E2E9 C5 F5 CMP $F5 |0C 01 00 F4|000001|3 ;is it the current screen line?
E2EB D0 16 BNE $E303 |0C 01 00 F4|000011|2
E2ED AD 21 02 LDA $0221 |0C 01 00 F4|000011|4 ;load temp column number
E2F0 85 E2 STA $E2 |02 01 00 F4|000001|3 ;store it
E2F2 CD 1E 02 CMP $021E |02 01 00 F4|000001|4 ;is it the end of line?
E2F5 90 0C BCC $E303 |02 01 00 F4|000001|2 ;greater or equal?
E2F7 4C 27 E3 JMP $E327 |02 01 00 F4|000001|3
So we finally know from where to where we’ll have to read:
E327 A9 00 LDA #$00 |02 01 00 F4|000001|2 ;reset input device E329 8D 60 02 STA $0260 |00 01 00 F4|000011|4 ;input device (0:kbd, 1:screen) E32C A9 0D LDA #$0D |00 01 00 F4|000011|2 ;load [RETURN] E32E AE 64 02 LDX $0264 |0D 01 00 F4|000001|4 ;default output device E331 E0 03 CPX #$03 |0D 03 00 F4|000001|2 ;is it 3? E333 F0 03 BEQ $E338 |0D 03 00 F4|000011|4 E338 A9 0D LDA #$0D |0D 03 00 F4|000011|2 ;load [RETURN] -- again! E33A 85 F6 STA $F6 |0D 03 00 F4|000001|3 ;store it in I/O temp E33C 68 PLA |0D 03 00 F4|000001|4 E33D AA TAX |00 03 00 F5|000011|2 E33E 68 PLA |00 00 00 F5|000011|4 E33F A8 TAY |0A 00 00 F6|000001|2 E340 A5 F6 LDA $F6 |0A 00 0A F6|000001|3 ;load char from I/O temp E342 C9 DE CMP #$DE |0D 00 0A F6|000001|2 ;is it $DE ("π")? E344 D0 02 BNE $E348 |0D 00 0A F6|000000|4 E348 60 RTS |0D 00 0A F6|000000|6 C47C A4 03 LDY $03 |0D 00 0A F8|000000|3 ;active I/O channel C47E D0 0C BNE $C48C |0D 00 00 F8|000010|2 ;is it non-zero? (supressed?) C480 C9 0F CMP #$0F |0D 00 00 F8|000010|2 ;is char $0F (related to terminal?) C482 D0 08 BNE $C48C |0D 00 00 F8|100000|4 C48C 60 RTS |0D 00 00 F8|100000|6 C46D C9 0D CMP #$0D |0D 00 00 FA|100000|2 ;is it [RETURN]? C46F F0 05 BEQ $C476 |0D 00 00 FA|000011|4 ;yes it is! C476 4C C8 C9 JMP $C9C8 |0D 00 00 FA|000011|3
Phew! Let's terminate the input buffer and output a line break (some of this, we’ve seen already):
C9C8 A0 00 LDY #$00 |0D 00 00 FA|000011|2 ;load $00 input termination C9CA 94 0A STY $0A,X |0D 00 00 FA|000011|4 ;store it at end of input buffer C9CC A2 09 LDX #$09 |0D 00 00 FA|000011|2 ;9 into X C9CE A5 03 LDA $03 |0D 09 00 FA|000001|3 ;active I/O channel C9D0 D0 26 BNE $C9F8 |00 09 00 FA|000011|2 ;prompt supressed? C9D2 A5 03 LDA $03 |00 09 00 FA|000011|3 ;load active I/O channel again C9D4 D0 02 BNE $C9D8 |00 09 00 FA|000011|2 ;prompt supressed? C9D6 85 05 STA $05 |00 09 00 FA|000011|3 ;store it as next print column (0) C9D8 A9 0D LDA #$0D |00 09 00 FA|000011|2 ;load [RETURN] C9DA 20 49 CA JSR $CA49 |0D 09 00 FA|000001|6 ;print it CA49 24 64 BIT $64 |0D 09 00 F8|000001|3 ;test output supressed? CA4B 30 27 BMI $CA74 |0D 09 00 F8|000011|2 ;no! CA4D 48 PHA |0D 09 00 F8|000011|3 CA4E C9 1D CMP #$1D |0D 09 00 F7|000011|2 ;is it [CRSR RIGHT]? CA50 F0 18 BEQ $CA6A |0D 09 00 F7|100000|2 CA52 C9 9D CMP #$9D |0D 09 00 F7|100000|2 ;is it 9D [CRSR LEFT]? CA54 F0 04 BEQ $CA5A |0D 09 00 F7|000000|2 CA56 C9 14 CMP #$14 |0D 09 00 F7|000000|2 ;is it 14 [DELETE]? CA58 D0 0A BNE $CA64 |0D 09 00 F7|100000|4 CA64 29 7F AND #$7F |0D 09 00 F7|100000|2 ;mask basic case CA66 C9 20 CMP #$20 |0D 09 00 F7|000000|2 ;is it printable? CA68 90 06 BCC $CA70 |0D 09 00 F7|100000|4 CA70 68 PLA |0D 09 00 F7|100000|4 CA71 20 D2 FF JSR $FFD2 |0D 09 00 F8|000000|6 ;print it
$FF2D
, again:
FFD2 4C 30 F2 JMP $F230 |0D 09 00 F6|000000|3 ;print vector F230 48 PHA |0D 09 00 F6|000000|3 F231 AD 64 02 LDA $0264 |0D 09 00 F5|000000|4 ;default output device F234 D0 03 BNE $F239 |03 09 00 F5|000000|4 ;zero? F239 C9 03 CMP #$03 |03 09 00 F5|000000|2 ;is it 3? F23B D0 04 BNE $F241 |03 09 00 F5|000011|2 F23D 68 PLA |03 09 00 F5|000011|4 F23E 4C EA E3 JMP $E3EA |0D 09 00 F6|000001|3 ;print the char… E3EA 48 PHA |0D 09 00 F6|000001|3 E3EB 85 F6 STA $F6 |0D 09 00 F5|000001|3 ;store char in I/O temp E3ED 8A TXA |0D 09 00 F5|000001|2 E3EE 48 PHA |09 09 00 F5|000001|3 E3EF 98 TYA |09 09 00 F4|000001|2 E3F0 48 PHA |00 09 00 F4|000011|3 E3F1 A9 00 LDA #$00 |00 09 00 F3|000011|2 ;set input device to keyboard E3F3 8D 60 02 STA $0260 |00 09 00 F3|000011|4 E3F6 A4 E2 LDY $E2 |00 09 00 F3|000011|3 ;load logical cursor pos (column) E3F8 A5 F6 LDA $F6 |00 09 02 F3|000001|3 ;load char from I/O temp E3FA 10 03 BPL $E3FF |0D 09 02 F3|000001|4 ;not a shifted char? E3FF C9 0D CMP #$0D |0D 09 02 F3|000001|2 ;is it [RETURN]? E401 D0 03 BNE $E406 |0D 09 02 F3|000011|2 E403 4C 48 E5 JMP $E548 |0D 09 02 F3|000011|3 ;yes, it is!
Having identified the character, we’re ready to go…
E548 A9 00 LDA #$00 |0D 09 02 F3|000011|2 E54A 85 FB STA $FB |00 09 02 F3|000011|3 ;reset keyboard inserts to 0 E54C 8D 0E 02 STA $020E |00 09 02 F3|000011|4 ;reset key buffer index E54F 85 EA STA $EA |00 09 02 F3|000011|3 ;reset quote mode E551 85 E2 STA $E2 |00 09 02 F3|000011|3 ;logical cursor pos (column) to 0 E553 20 30 E5 JSR $E530 |00 09 02 F3|000011|6 ;adjust the screen line E530 38 SEC |00 09 02 F1|000011|2 E531 4E 20 02 LSR $0220 |00 09 02 F1|000011|6 ;shift row number left (×2) E534 A6 F5 LDX $F5 |00 09 02 F1|000001|3 ;load current screen line E536 E8 INX |00 05 02 F1|000001|2 ;increment it E537 E0 19 CPX #$19 |00 06 02 F1|000001|2 ;is it beyond the last line (25)? E539 D0 03 BNE $E53E |00 06 02 F1|100000|4 ;no! E53E BD 29 02 LDA $0229,X |00 06 02 F1|100000|4 ;load from table of row pointers, low-byte E541 10 F3 BPL $E536 |80 06 02 F1|100000|2 ;is it not extended? - it is! E543 86 F5 STX $F5 |80 06 02 F1|100000|3 ;store the incremented screen line E545 4C DB E5 JMP $E5DB |80 06 02 F1|100000|3 ;position the cursor… E5DB A6 F5 LDX $F5 |80 06 02 F1|100000|3 ;load current screen line E5DD BD 29 02 LDA $0229,X |80 06 02 F1|000000|4 ;load from table of row pointers, low-byte E5E0 09 80 ORA #$80 |80 06 02 F1|100000|2 ;set sign bit E5E2 85 E1 STA $E1 |80 06 02 F1|100000|3 ;pointer to current cursor pos, low-byte E5E4 BD BC E7 LDA $E7BC,X |80 06 02 F1|100000|4 ;load from table of row pointers, hi-byte E5E7 85 E0 STA $E0 |F0 06 02 F1|100000|3 ;pointer to current cursor pos, hi-byte E5E9 A9 27 LDA #$27 |F0 06 02 F1|100000|2 ;load 39 E5EB 85 F2 STA $F2 |27 06 02 F1|000000|3 ;line length E5ED E0 18 CPX #$18 |27 06 02 F1|000000|2 ;last row? E5EF F0 09 BEQ $E5FA |27 06 02 F1|100000|2 E5F1 BD 2A 02 LDA $022A,X |27 06 02 F1|100000|4 ;load from table of of screen positions, E5F4 30 04 BMI $E5FA |81 06 02 F1|100000|4 ; hi-byte E5FA A5 E2 LDA $E2 |81 06 02 F1|100000|3 ;logical cursor pos E5FC C9 28 CMP #$28 |00 06 02 F1|000010|2 ;greater or equal 40? E5FE 90 04 BCC $E604 |00 06 02 F1|100000|4 E604 60 RTS |00 06 02 F1|100000|6 E556 4C 8A E3 JMP $E38A |00 06 02 F3|100000|3 E38A 68 PLA |00 06 02 F3|100000|4 E38B A8 TAY |00 06 02 F4|000010|2 E38C A5 FB LDA $FB |00 06 00 F4|000010|3 ;number of keyboard inserts E38E F0 02 BEQ $E392 |00 06 00 F4|000010|4 ;none? E392 68 PLA |00 06 00 F4|000010|4 E393 AA TAX |09 06 00 F5|000000|2 E394 68 PLA |09 09 00 F5|000000|4 E395 58 CLI |0D 09 00 F6|000000|2 E396 60 RTS |0D 09 00 F6|000000|6 CA74 29 FF AND #$FF |0D 09 00 F8|000000|2 ;filler… CA76 60 RTS |0D 09 00 F8|000000|6
Now print a line feed:
C9DD A9 0A LDA #$0A |0D 09 00 FA|000000|2 ;load LF C9DF 20 49 CA JSR $CA49 |0A 09 00 FA|000000|6 CA49 24 64 BIT $64 |0A 09 00 F8|000000|3 ;test output supressed? CA4B 30 27 BMI $CA74 |0A 09 00 F8|000010|2 CA4D 48 PHA |0A 09 00 F8|000010|3 CA4E C9 1D CMP #$1D |0A 09 00 F7|000010|2 ;is it [CRSR RIGHT]? CA50 F0 18 BEQ $CA6A |0A 09 00 F7|100000|2 CA52 C9 9D CMP #$9D |0A 09 00 F7|100000|2 ;is it 9D [CRSR LEFT]? CA54 F0 04 BEQ $CA5A |0A 09 00 F7|000000|2 CA56 C9 14 CMP #$14 |0A 09 00 F7|000000|2 ;is it 14 [DELETE]? CA58 D0 0A BNE $CA64 |0A 09 00 F7|100000|4 CA64 29 7F AND #$7F |0A 09 00 F7|100000|2 ;mask basic case CA66 C9 20 CMP #$20 |0A 09 00 F7|000000|2 ;is it printable? CA68 90 06 BCC $CA70 |0A 09 00 F7|100000|4 CA70 68 PLA |0A 09 00 F7|100000|4 CA71 20 D2 FF JSR $FFD2 |0A 09 00 F8|000000|6 ;print it
Yet another go for $FFD2
…
FFD2 4C 30 F2 JMP $F230 |0A 09 00 F6|000000|3 F230 48 PHA |0A 09 00 F6|000000|3 F231 AD 64 02 LDA $0264 |0A 09 00 F5|000000|4 F234 D0 03 BNE $F239 |03 09 00 F5|000000|4 F239 C9 03 CMP #$03 |03 09 00 F5|000000|2 F23B D0 04 BNE $F241 |03 09 00 F5|000011|2 F23D 68 PLA |03 09 00 F5|000011|4 F23E 4C EA E3 JMP $E3EA |0A 09 00 F6|000001|3 E3EA 48 PHA |0A 09 00 F6|000001|3 E3EB 85 F6 STA $F6 |0A 09 00 F5|000001|3 E3ED 8A TXA |0A 09 00 F5|000001|2 E3EE 48 PHA |09 09 00 F5|000001|3 E3EF 98 TYA |09 09 00 F4|000001|2 E3F0 48 PHA |00 09 00 F4|000011|3 E3F1 A9 00 LDA #$00 |00 09 00 F3|000011|2 ;reset input device E3F3 8D 60 02 STA $0260 |00 09 00 F3|000011|4 E3F6 A4 E2 LDY $E2 |00 09 00 F3|000011|3 ;load cursor column E3F8 A5 F6 LDA $F6 |00 09 00 F3|000011|3 ;load char from I/O temp E3FA 10 03 BPL $E3FF |0A 09 00 F3|000001|4 ;not shifted? E3FF C9 0D CMP #$0D |0A 09 00 F3|000001|2 ;is it [RETURN]? E401 D0 03 BNE $E406 |0A 09 00 F3|100000|4 E406 C9 20 CMP #$20 |0A 09 00 F3|100000|2 ;is [SPACE] or higher (printable)? E408 90 08 BCC $E412 |0A 09 00 F3|100000|4 E412 A6 FB LDX $FB |0A 09 00 F3|100000|3 ;number of chars to print E414 F0 03 BEQ $E419 |0A 00 00 F3|000010|4 E419 C9 14 CMP #$14 |0A 00 00 F3|000010|2 ;is it [DELETE]? E41B D0 1C BNE $E439 |0A 00 00 F3|100000|4 E439 A6 EA LDX $EA |0A 00 00 F3|100000|3 ;quote flag E43B F0 03 BEQ $E440 |0A 00 00 F3|000010|4 E440 C9 12 CMP #$12 |0A 00 00 F3|000010|2 ;is it [REVERSE]? E442 D0 03 BNE $E447 |0A 00 00 F3|100000|4 E447 C9 13 CMP #$13 |0A 00 00 F3|100000|2 ;is it [HOME]? E449 D0 03 BNE $E44E |0A 00 00 F3|100000|4 E44E C9 1D CMP #$1D |0A 00 00 F3|100000|2 ;is it [CRSR RIGHT]? E450 D0 12 BNE $E464 |0A 00 00 F3|100000|4 E464 C9 11 CMP #$11 |0A 00 00 F3|100000|2 ;is it [CRSR DOWN]? E466 D0 0E BNE $E476 |0A 00 00 F3|100000|4 E476 4C 8A E3 JMP $E38A |0A 00 00 F3|100000|3 E38A 68 PLA |0A 00 00 F3|100000|4 E38B A8 TAY |00 00 00 F4|000010|2 E38C A5 FB LDA $FB |00 00 00 F4|000010|3 ;number of keyboard inserts E38E F0 02 BEQ $E392 |00 00 00 F4|000010|4 E392 68 PLA |00 00 00 F4|000010|4 E393 AA TAX |09 00 00 F5|000000|2 E394 68 PLA |09 09 00 F5|000000|4 E395 58 CLI |0A 09 00 F6|000000|2 E396 60 RTS |0A 09 00 F6|000000|6
By which we have earned a rest with some honest filler:
CA74 29 FF AND #$FF |0A 09 00 F8|000000|2 CA76 60 RTS |0A 09 00 F8|000000|6
With renewed energy, we close the I/O related tasks:
C9E2 A5 03 LDA $03 |0A 09 00 FA|000000|3 ;active I/O channel C9E4 D0 12 BNE $C9F8 |00 09 00 FA|000010|2 ;prompt supressed? C9E6 8A TXA |00 09 00 FA|000010|2 C9E7 48 PHA |09 09 00 FA|000000|3 C9E8 A6 04 LDX $04 |09 09 00 F9|000000|3 ;nulls to print for CRLF C9EA F0 08 BEQ $C9F4 |09 00 00 F9|000010|4 ; (actually for terminal use) C9F4 86 05 STX $05 |09 00 00 F9|000010|3 ;store it (0) as next print column C9F6 68 PLA |09 00 00 F9|000010|4 C9F7 AA TAX |09 00 00 FA|000000|2 C9F8 60 RTS |09 09 00 FA|000000|6
And now — *fanfare* — we’re ready to evaluate the input:
CAFB A5 03 LDA $03 |09 09 00 FC|000000|3 ;check output mode CAFD F0 0D BEQ $CB0C |00 09 00 FC|000010|6 CB0C A5 0A LDA $0A |00 09 00 FC|000010|3 ;load first byte from input buffer CB0E D0 19 BNE $CB29 |00 09 00 FC|000010|2 ;not null termination? (oops, it is) CB10 A5 03 LDA $03 |00 09 00 FC|000010|3 ;check output mode CB12 D0 E4 BNE $CAF8 |00 09 00 FC|000010|2 CB14 4C 9B E1 JMP $E19B |00 09 00 FC|000010|3 ;warm start E19B 18 CLC |00 09 00 FC|000010|2 ;clear carry E19C 4C 2B C7 JMP $C72B |00 09 00 FC|000010|3 ;perform program END, STOP, break
That’s it! After all that hard work, there’s simply no code for handling this case. Instead, we head directly into the BASIC warm start routine!
But, no horrible bug to be seen, anwhere. There is just no code to handle any defaults, instead, we directly bail out of execution. Thus, we never reach any parts of the code, where the input string would be evaluated, like in the Floating-Point Accumulator.
On the other hand, if we were to ever reach these routines, there’s error handling code indeed, as can be seen in the ROM listing below. (Mind the end of that listing.)
So, no cheesy Monsters from the Planet of Terror, just the abstract, existential horror of the void, made somewhat more bearable by a conciliatory BASIC warm start.
Speaking of which…
— Bonus Intermission: BASIC Warm Start —
C72B A5 88 LDA $88 |00 09 00 FC|000010|3 ;current line being executed low C72D A4 89 LDY $89 |0A 09 00 FC|000000|3 ;current line being executed high C72F 85 8A STA $8A |0A 09 00 FC|000010|3 ;store it as line for CONTINUE command C731 84 8B STY $8B |0A 09 00 FC|000010|3 C733 68 PLA |0A 09 00 FC|000010|4 C734 68 PLA |EE 09 00 FD|100000|4 C735 A9 A4 LDA #$A4 |C6 09 00 FE|100000|2 ;to be overwritten: message addr "BREAK", low-byte C737 A0 C2 LDY #$C2 |A4 09 00 FE|100000|2 ;to be overwritten: message addr "BREAK"", hi-byte C739 A2 00 LDX #$00 |A4 09 C2 FE|100000|2 ;set flag for normal output (not supressed) C73B 86 64 STX $64 |A4 00 C2 FE|000010|3 C73D 90 03 BCC $C742 |A4 00 C2 FE|000010|4 ;(carry cleared at $E19B) C742 4C 8B C3 JMP $C38B |A4 00 C2 FE|000010|3
Simple enough. Now set up the “READY.” prompt (overwriting some of which have just done):
C38B 46 64 LSR $64 |A4 00 C2 FE|000010|5 ;shift output mode C38D A9 99 LDA #$99 |A4 00 C2 FE|000010|2 C38F A0 C2 LDY #$C2 |99 00 C2 FE|100000|2 C391 20 27 CA JSR $CA27 |99 00 C2 FE|100000|6 CA27 20 6B D3 JSR $D36B |99 00 C2 FC|100000|6 ;output message
Scan for the message length to setup string pointers…
D36B A2 22 LDX #$22 |99 00 C2 FA|100000|2 ; D36D 86 5A STX $5A |99 22 C2 FA|000000|3 ;search char or offset for line scanning D36F 86 5B STX $5B |99 22 C2 FA|000000|3 ;quote marker D371 85 BE STA $BE |99 22 C2 FA|000000|3 ;used as pointer to table ($C299), lo-byte D373 84 BF STY $BF |99 22 C2 FA|000000|3 ;used as pointer hi-byte D375 85 B1 STA $B1 |99 22 C2 FA|000000|3 ;FPAC 1, byte 0 D377 84 B2 STY $B2 |99 22 C2 FA|000000|3 ;FPAC 1, byte 1 D379 A0 FF LDY #$FF |99 22 C2 FA|000000|2 D37B C8 INY |99 22 FF FA|100000|2 ;00 on first iteration D37C B1 BE LDA ($BE),Y |99 22 00 FA|000010|5 ;read byte from $C299 (CR, LF, "READY.", CR, LF) D37E F0 0C BEQ $D38C |0D 22 00 FA|000000|2 ;ist it zero? D380 C5 5A CMP $5A |0D 22 00 FA|000000|3 ;is it "Z"? D382 F0 04 BEQ $D388 |0D 22 00 FA|100000|2 D384 C5 5B CMP $5B |0D 22 00 FA|100000|3 ;is it "["? D386 D0 F3 BNE $D37B |0D 22 00 FA|100000|4 D37B C8 INY |0D 22 00 FA|100000|2 ;inc offset D37C B1 BE LDA ($BE),Y |0D 22 01 FA|000000|5 ;load char D37E F0 0C BEQ $D38C |0A 22 01 FA|000000|2 ;null termination? D380 C5 5A CMP $5A |0A 22 01 FA|000000|3 ;is it "Z"? D382 F0 04 BEQ $D388 |0A 22 01 FA|100000|2 D384 C5 5B CMP $5B |0A 22 01 FA|100000|3 ;is it "["? D386 D0 F3 BNE $D37B |0A 22 01 FA|100000|4 D37B C8 INY |0A 22 01 FA|100000|2 ;inc offset D37C B1 BE LDA ($BE),Y |0A 22 02 FA|000000|5 ;read char ("R") D37E F0 0C BEQ $D38C |52 22 02 FA|000000|2 D380 C5 5A CMP $5A |52 22 02 FA|000000|3 D382 F0 04 BEQ $D388 |52 22 02 FA|000001|2 D384 C5 5B CMP $5B |52 22 02 FA|000001|3 D386 D0 F3 BNE $D37B |52 22 02 FA|000001|4 D37B C8 INY |52 22 02 FA|000001|2 ;inc offset D37C B1 BE LDA ($BE),Y |52 22 03 FA|000001|5 ;read "E" D37E F0 0C BEQ $D38C |45 22 03 FA|000001|2 D380 C5 5A CMP $5A |45 22 03 FA|000001|3 D382 F0 04 BEQ $D388 |45 22 03 FA|000001|2 D384 C5 5B CMP $5B |45 22 03 FA|000001|3 D386 D0 F3 BNE $D37B |45 22 03 FA|000001|4 D37B C8 INY |45 22 03 FA|000001|2 D37C B1 BE LDA ($BE),Y |45 22 04 FA|000001|5 ;read "A" D37E F0 0C BEQ $D38C |41 22 04 FA|000001|2 D380 C5 5A CMP $5A |41 22 04 FA|000001|3 D382 F0 04 BEQ $D388 |41 22 04 FA|000001|2 D384 C5 5B CMP $5B |41 22 04 FA|000001|3 D386 D0 F3 BNE $D37B |41 22 04 FA|000001|4 D37B C8 INY |41 22 04 FA|000001|2 D37C B1 BE LDA ($BE),Y |41 22 05 FA|000001|5 ;read "D" D37E F0 0C BEQ $D38C |44 22 05 FA|000001|2 D380 C5 5A CMP $5A |44 22 05 FA|000001|3 D382 F0 04 BEQ $D388 |44 22 05 FA|000001|2 D384 C5 5B CMP $5B |44 22 05 FA|000001|3 D386 D0 F3 BNE $D37B |44 22 05 FA|000001|4 D37B C8 INY |44 22 05 FA|000001|2 D37C B1 BE LDA ($BE),Y |44 22 06 FA|000001|5 ;read "Y" D37E F0 0C BEQ $D38C |59 22 06 FA|000001|2 D380 C5 5A CMP $5A |59 22 06 FA|000001|3 D382 F0 04 BEQ $D388 |59 22 06 FA|000001|2 D384 C5 5B CMP $5B |59 22 06 FA|000001|3 D386 D0 F3 BNE $D37B |59 22 06 FA|000001|4 D37B C8 INY |59 22 06 FA|000001|2 D37C B1 BE LDA ($BE),Y |59 22 07 FA|000001|5 ;read "." D37E F0 0C BEQ $D38C |2E 22 07 FA|000001|2 D380 C5 5A CMP $5A |2E 22 07 FA|000001|3 D382 F0 04 BEQ $D388 |2E 22 07 FA|000001|2 D384 C5 5B CMP $5B |2E 22 07 FA|000001|3 D386 D0 F3 BNE $D37B |2E 22 07 FA|000001|4 D37B C8 INY |2E 22 07 FA|000001|2 D37C B1 BE LDA ($BE),Y |2E 22 08 FA|000001|5 ;read CR D37E F0 0C BEQ $D38C |0D 22 08 FA|000001|2 D380 C5 5A CMP $5A |0D 22 08 FA|000001|3 D382 F0 04 BEQ $D388 |0D 22 08 FA|100000|2 D384 C5 5B CMP $5B |0D 22 08 FA|100000|3 D386 D0 F3 BNE $D37B |0D 22 08 FA|100000|4 D37B C8 INY |0D 22 08 FA|100000|2 D37C B1 BE LDA ($BE),Y |0D 22 09 FA|000000|5 ;read LF D37E F0 0C BEQ $D38C |0A 22 09 FA|000000|2 D380 C5 5A CMP $5A |0A 22 09 FA|000000|3 D382 F0 04 BEQ $D388 |0A 22 09 FA|100000|2 D384 C5 5B CMP $5B |0A 22 09 FA|100000|3 D386 D0 F3 BNE $D37B |0A 22 09 FA|100000|4 D37B C8 INY |0A 22 09 FA|100000|2 D37C B1 BE LDA ($BE),Y |0A 22 0A FA|000000|5 ;read 00 (termination) D37E F0 0C BEQ $D38C |00 22 0A FA|000010|4 ;done D38C 18 CLC |00 22 0A FA|000010|2 D38D 84 B0 STY $B0 |00 22 0A FA|000010|3 ;store length D38F 98 TYA |00 22 0A FA|000010|2 D390 65 BE ADC $BE |0A 22 0A FA|000000|3 ;compute address of message end D392 85 C0 STA $C0 |A3 22 0A FA|100000|3 ;store it in $C0,$C1 D394 A6 BF LDX $BF |A3 22 0A FA|100000|3 D396 90 01 BCC $D399 |A3 C2 0A FA|100000|4 D399 86 C1 STX $C1 |A3 C2 0A FA|100000|3 D39B A5 BF LDA $BF |A3 C2 0A FA|100000|3 D39D D0 0B BNE $D3AA |C2 C2 0A FA|100000|4 D3AA A6 65 LDX $65 |C2 C2 0A FA|100000|3 ;index to next string pointer D3AC E0 71 CPX #$71 |C2 68 0A FA|000000|2 ;is it $71? D3AE D0 05 BNE $D3B5 |C2 68 0A FA|100000|4 D3B5 A5 B0 LDA $B0 |C2 68 0A FA|100000|3 ;load string length D3B7 95 00 STA $00,X |0A 68 0A FA|000000|4 ;store it at $68 D3B9 A5 B1 LDA $B1 |0A 68 0A FA|000000|3 ;load pointer to table ($C299), lo-byte D3BB 95 01 STA $01,X |99 68 0A FA|100000|4 ;store it at $69 D3BD A5 B2 LDA $B2 |99 68 0A FA|100000|3 ;load pointer to table ($C299), hi-byte D3BF 95 02 STA $02,X |C2 68 0A FA|100000|4 ;store it at $6A D3C1 A0 00 LDY #$00 |C2 68 0A FA|100000|2 ;load 00 D3C3 86 B3 STX $B3 |C2 68 00 FA|000010|3 ;store X ($68) as pointer in FPAC 1 D3C5 84 B4 STY $B4 |C2 68 00 FA|000010|3 ;store Y ($00) as pointer in FPAC 1 + 1 D3C7 88 DEY |C2 68 00 FA|000010|2 ;decrement Y D3C8 84 5E STY $5E |C2 68 FF FA|100000|3 ;store it as variable flag (not 0: string) D3CA 86 66 STX $66 |C2 68 FF FA|100000|3 ;store it as pointer last string, high-byte D3CC E8 INX |C2 68 FF FA|100000|2 D3CD E8 INX |C2 69 FF FA|000000|2 D3CE E8 INX |C2 6A FF FA|000000|2 D3CF 86 65 STX $65 |C2 6B FF FA|000000|3 ;store X+3 as pointer last string, lo-byte D3D1 60 RTS |C2 6B FF FA|000000|6
Now discard this temp string again, by restting the string pointer to start of last string.
CA2A 20 7E D5 JSR $D57E |C2 6B FF FC|000000|6 D57E A5 B3 LDA $B3 |C2 6B FF FA|000000|3 D580 A4 B4 LDY $B4 |68 6B FF FA|000000|3 D582 85 71 STA $71 |68 6B 00 FA|000010|3 D584 84 72 STY $72 |68 6B 00 FA|000010|3 D586 20 B3 D5 JSR $D5B3 |68 6B 00 FA|000010|6 D5B3 C4 67 CPY $67 |68 6B 00 F8|000010|3 D5B5 D0 0C BNE $D5C3 |68 6B 00 F8|000011|2 D5B7 C5 66 CMP $66 |68 6B 00 F8|000011|3 D5B9 D0 08 BNE $D5C3 |68 6B 00 F8|000011|2 D5BB 85 65 STA $65 |68 6B 00 F8|000011|3 D5BD E9 03 SBC #$03 |68 6B 00 F8|000011|2 D5BF 85 66 STA $66 |65 6B 00 F8|000001|3 D5C1 A0 00 LDY #$00 |65 6B 00 F8|000001|2 D5C3 60 RTS |65 6B 00 F8|000011|6
Phew! And we haven’t even attempted to put anything on the screen, by now. I guess, there won’t be much of a protest, if I do spare everyone from the rest of this procedure. I think, we did get the idea…
Anyways, I think it was worth it: for sure, whenever I’ll input a character into an 8-bit machine, I will do it with due respect for the poor thing! :-)
— End of Bonus Intermission —
Look, ROM Code!
Finally, we may have a look at the ROM code, here for BASIC 2 (“New ROM”). I’m not so sure, we’d figure out soon, what’s actually going on.
However, have a look at the error handling code at the end!
Emphasis and annotations in red are mine.
(Mind, how we merely fall through, both on the guarding condition and on the check for the current device. Consequently, this had been interpreted as the beginning of the "READ" routine.)
INPUT, Commodore PET BASIC 2, “New ROM” CAC1 46 0D LSR $0D ;0D: PRINT supresssed when negative CAC3 C9 22 CMP #$22 ;is it a quote? (") CAC5 D0 0B BNE iCAD2 ;no prompt string CAC7 20 B8 CD JSR $CDB8 ;print prompt string CACA A9 3B LDA #$3B ;validate that is it a semicolon (;) CACC 20 FA CD JSR $CDFA ;CKTERM CACF 20 1F CA JSR $CA1F ;OPSTRB CAD2 20 80 D2 iCAD2 JSR $D280 ;CKIDIR CAD5 A9 2C LDA #$2C ;load comma (,) CAD7 8D FF 01 STA $01FF ;CONBUF-1 (LOAD/utility pointer) CADA 20 FA CA iCADA JSR iCAFA ;GETIP CADD A5 0E LDA $0E ;load CURDVC CADF F0 0C BEQ iCAED ;PFPB CAE1 A5 96 LDA $96 ;load STATUS CAE3 29 02 AND #$02 ;check for STOP CAE5 F0 06 BEQ iCAED CAE7 20 B7 CA JSR $CAB7 CAEA 4C 00 C8 JMP $C800 ;prompt "?" & put input into buffer CAED AD 00 02 iCAED LDA $0200 ;CONBUF CAF0 D0 1C BNE iCB0E ;guard condition: non empty input CAF2 A5 0E LDA $0E ;load CURDVC CAF4 D0 E4 BNE iCADA ;not INPUT CAF6 18 CLC ;Perform READ, clear carry for warm start CAF7 4C 51 C7 JMP $C751 ;to program END, STOP, break ;'subroutine for input' CAFA A5 0E iCAFA LDA $0E ;load CURDVC CAFC D0 06 BNE iCB04 ;skip, if not input CAFE 20 43 CA JSR $CA43 CB01 20 39 CA JSR $CA39 CB04 4C 6F C4 iCB04 JMP $C46F CB07 A6 3E LDX $3E ;READ CB09 A4 3F LDY $3F ;load DATPTR+1 CB0B A9 98 LDA #$98 CB0D 2C A9 00 BIT $00A9 CB10 85 0B STA $0B CB12 86 40 STX $40 CB14 84 41 STY $41 CB16 20 6D CF iCB16 JSR $CF6D CB19 85 46 STA $46 CB1B 84 47 STY $47 CB1D A5 77 LDA $77 CB1F A4 78 LDY $78 CB21 85 48 STA $48 CB23 84 49 STY $49 CB25 A6 40 LDX $40 CB27 A4 41 LDY $41 CB29 86 77 STX $77 CB2B 84 78 STY $78 CB2D 20 76 00 JSR $0076 CB30 D0 20 BNE iCB52 CB32 24 0B BIT $0B CB34 50 0C BVC iCB42 CB36 20 E4 FF JSR $FFE4 CB39 8D 00 02 STA $0200 CB3C A2 FF LDX #$FF CB3E A0 01 LDY #$01 CB40 D0 0C BNE iCB4E CB42 30 75 iCB42 BMI iCBB9 CB44 A5 0E LDA $0E CB46 D0 03 BNE iCB4B CB48 20 43 CA JSR $CA43 CB4B 20 FA CA iCB4B JSR iCAFA CB4E 86 77 iCB4E STX $77 CB50 84 78 STY $78 CB52 20 70 00 iCB52 JSR $0070 ;CHRGET CB55 24 07 BIT $07 ;test type flag, $FF: string, $00: numeric CB57 10 31 BPL iCB8A ;is numeric CB59 24 0B BIT $0B CB5B 50 09 BVC iCB66 ;branch if not GET CB5D E8 INX CB5E 86 77 STX $77 CB60 A9 00 LDA #$00 CB62 85 03 STA $03 CB64 F0 0C BEQ iCB72 CB66 85 03 iCB66 STA $03 CB68 C9 22 CMP #$22 ;load quote CB6A F0 07 BEQ iCB73 CB6C A9 3A LDA #$3A ;load colon CB6E 85 03 STA $03 CB70 A9 2C LDA #$2C CB72 18 iCB72 CLC ;assign string to var CB73 85 04 iCB73 STA $04 CB75 A5 77 LDA $77 CB77 A4 78 LDY $78 CB79 69 00 ADC #$00 CB7B 90 01 BCC iCB7E CB7D C8 INY CB7E 20 67 D3 iCB7E JSR $D367 CB81 20 BD D6 JSR $D6BD CB84 20 E2 C8 JSR $C8E2 CB87 4C 92 CB JMP iCB92 ;assign number to var CB8A 20 FF DB iCB8A JSR $DBFF ;get FAC1 from string CB8D A5 08 LDA $08 ;type flag, $80: integer, $00: float CB8F 20 CA C8 JSR $C8CA ;round, fix and store FAC CB92 20 76 00 iCB92 JSR $0076 ;CHRGOT: get last char CB95 F0 07 BEQ iCB9E ;branch if ":" or [EOL] CB97 C9 2C CMP #$2C ;is it a comma? (,) CB99 F0 03 BEQ iCB9E CB9B 4C 4F CA JMP $CA4F ;error CB9E A5 77 iCB9E LDA $77 ;save pointers CBA0 A4 78 LDY $78 CBA2 85 40 STA $40 CBA4 84 41 STY $41 CBA6 A5 48 LDA $48 CBA8 A4 49 LDY $49 CBAA 85 77 STA $77 CBAC 84 78 STY $78 CBAE 20 76 00 JSR $0076 ;CHRGOT CBB1 F0 2C BEQ iCBDF ;VAREND CBB3 20 F8 CD JSR $CDF8 ;CHKCOM, check for comma CBB6 4C 16 CB JMP iCB16 ;next CBB9 20 0E C8 iCBB9 JSR $C80E ;scan for DATA CBBC C8 INY CBBD AA TAX CBBE D0 12 BNE iCBD2 CBC0 A2 2A LDX #$2A CBC2 C8 INY CBC3 B1 77 LDA ($77),Y CBC5 F0 6D BEQ $CC34 CBC7 C8 INY CBC8 B1 77 LDA ($77),Y CBCA 85 3C STA $3C CBCC C8 INY CBCD B1 77 LDA ($77),Y CBCF C8 INY CBD0 85 3D STA $3D CBD2 B1 77 iCBD2 LDA ($77),Y CBD4 AA TAX CBD5 20 03 C8 JSR $C803 CBD8 E0 83 CPX #$83 CBDA D0 DD BNE iCBB9 CBDC 4C 52 CB JMP iCB52 CBDF A5 40 iCBDF LDA $40 CBE1 A4 41 LDY $41 CBE3 A6 0B LDX $0B CBE5 10 03 BPL iCBEA ;end of buffer (0)? CBE7 4C 3A C7 JMP $C73A ;exit CBEA A0 00 iCBEA LDY #$00 CBEC B1 40 LDA ($40),Y CBEE F0 0B BEQ iCBFB ;skip to RTS CBF0 A5 0E LDA $0E ;CURDVC CBF2 D0 07 BNE iCBFB CBF4 A9 FC LDA #$FC CBF6 A0 CB LDY #$CB CBF8 4C 1C CA JMP $CA1C ;OPSTRA CBFB 60 iCBFB RTS CBFC: 3F 45 58 54 ?EXT CC00: 52 41 20 49 47 4E 4F 52 RA IGNOR CC08: 45 44 0D 0A 00 3F 52 45 ED...?RE CC10: 44 4F 20 46 52 4F 4D 20 DO FROM CC18: 53 54 41 52 54 0D 0A 00 START... ;end of VRESTR (restore pointers and exit/return) C73A 85 3E STA $3E ;DATPTR C73C 84 3F STY $3F ;DATPTR+1 C73E 60 RTS ;OPSTRA CA1C 20 61 D3 iCA1C JSR $D361 ;OPSTRB CA1F 20 80 D5 JSR $D580 CA22 AA TAX CA23 A0 00 LDY #$00 CA25 E8 INX CA26 CA iCA26 DEX CA27 F0 C5 BEQ $C9EE CA29 B1 1F LDA ($1F),Y CA2B 20 45 CA JSR iCA45 ;CONOUT CA2E C8 INY CA2F C9 0D CMP #$0D ;is it RETURN? CA31 D0 F3 BNE iCA26 CA33 20 EC C9 JSR $C9EC CA36 4C 26 CA JMP iCA26 CA39 A5 0E LDA $0E ;CURDVC CA3B F0 03 BEQ iCA40 CA3D A9 20 LDA #$20 ;load SPACE CA3F 2C A9 1D BIT $1DA9 CA42 2C A9 3F BIT $3FA9 CA45 24 0D iCA45 BIT $0D CA47 30 03 BMI iCA4C CA49 20 D2 FF JSR $FFD2 ;OUTCH CA4C 29 FF iCA4C AND #$FF CA4E 60 RTS ;error (same as C64) CA4F A5 0B LDA $0B ;mode: $00 = INPUT, $40 = GET, $98 = READ CA51 F0 11 BEQ iCA64 ;branch on INPUT CA53 30 04 BMI iCA59 ;branch for READ CA55 A0 FF LDY #$FF CA57 D0 04 BNE iCA5D ;unconditional jump for GET ;READ error CA59 A5 3C iCA59 LDA $3C ;get current DATA line (low, high) CA5B A4 3D LDY $3D ;GET error, READ error continued CA5D 85 36 iCA5D STA $36 ;CURLNO CA5F 84 37 STY $37 ;CURLNO+1 CA61 4C 03 CE JMP $CE03 ;PRSYNE ('SYNTAX ERROR') ;INPUT error CA64 A5 0E iCA64 LDA $0E ;CURDVC CA66 F0 05 BEQ iCA6D ;keyboard… CA68 A2 BF LDX #$BF ;err-string addr CA6A 4C 57 C3 JMP $C357 ;PRERR, print error + warm start CA6D A9 0D iCA6D LDA #$0D ;>REDOK 'REDO FROM START' CA6F A0 CC LDY #$CC ;<REDOK CA71 20 1C CA JSR $CA1C ;OPSTRA, print null terminated string CA74 A5 3A LDA $3A ;get continue pointer (low, high) CA76 A4 3B LDY $3B CA78 85 77 STA $77 ;CTXPTR CA7A 84 78 STY $78 ;CTXPTR+1 CA7C 60 RTS ;return to re-execute
I guess, we may appreciate the difference of theese two views and experiences. Much like numbers in motion (as in music) versus static numbers (as in math).
Bonus Content for the Enduring — a Workaround
As we have seen, the input routine gobbeles up whatever is in the keyboard buffer, without any notion where this came from. And it’s this what we can use to our advantage. What, if we prefilled the keyboard buffer with something usefull, just before we execute any BASIC INPUT
command? Like a “0
”, followed by a CURSOR LEFT?
This way, the cursor would be sitting on the zero, initially. If the user is entering any value, this will overwrite our default input, and, otherwise, the zero would be used and parsed as the input. There is also no race condition, since any user input will be added to the keyboard buffer after our artifical keystrokes.
In order to do this as a universal routine, we’ll have to address both BASIC 1 on the one hand and BASIC 2 and 4.0 on the other hand, since these have the keyboard buffer and the keyboard buffer index in different locations. How could we discern them in BASIC?
This is actually simple: BASIC 1 features a rudimentary copy protection, which was removed in subsequent revisions, since it’s obviously rather useless: If we try to PEEK()
into any address of the ROM at $C000
, BASIC will return 0
, and this reliably so. Therefore, all we to do is to peek into any address that is known non-zero in any (other) ROMs, et voilà! — If the PEEK()
returns 0
, it must be BASIC 1.
For the test address, well use $C000
, or decimal 49152
.
The PETSCII value for “0
” is (decimal) 48
and that for a CURSOR LEFT is 157
. The keyboard buffer is at location 527
for BASIC 1 and at 623
for any other versions, and the keyboard buffer index is found at 525
and 158
, respectively.
Thus, we may emulate the behavior of any other flavors of MS BASIC by:
10 GOSUB 3000:INPUT A 20 PRINT A 30 END 2999 REM DEFAULT 0 FOR INPUT 3000 IF PEEK(49152)=0 THEN POKE 527,48:POKE 528,157:POKE 525,2:RETURN 3001 POKE 623,48:POKE 624,157:POKE 158,2:RETURN
Of course, you can expand on this and insert any default value, you may wish for. “ASC()
” is your friend for determining the PETSCII value. (Here, we just wanted to use no variable, whatsoever, to keep this truly universal. Also, a default of 0
is consistent with other versions of MS BASIC.)
For strings, it’s a different story, as we can’t simply use PETSCII 32
(SPACE) as a preseed, since this will be ignored and overun by the part of the routine that finds the end of the input string. For this, we had to use any other character but SPACE or RETURN.
And that’s it, for this time.
Norbert Landsteiner,
Vienna, 2024-03-17