The Commodore PET INPUT Bug-Feature

Adventures in MS BASIC (Commodore PET edition).

Title illustration

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.

PET BASIC input with and empty value
PET BASIC 2 ending the program on an empty input value.

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:

PET BASIC input with and empty string
PET BASIC 2 ending the program on an empty input string (using white phosphor for a change).

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…

PET 2001 emulator with debugger
The PET 2001 emulator with debugger (right), dark mode.
(The debugger is toggled on/off by an icon on the top right.)

“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
PET BASIC with default value
Numeric INPUT fix for PET BASIC (here BASIC 4.0).

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.