What I don't understand is why this quirk exists and how it works.
As usual, lousy programming. It's a routine that exists in next to every Microsoft Basic, but often modified by the receiving company. It's used to list a line. On the 6502 version space was a premium, so they tried to cut down as much as possible in tests. And lets be serious, a 'real' terminal can not produce codes above 128 so there is no chance someone would enter a token code somewhere. And what computer engineer in his right mind would create a machine using other codes than plain ASCII?
Is this a bug or a feature of CBM BASIC?
It's a combination of several little bits of 'less than perfect' ending up creating a bug.
and why is it present only on the C64 and VIC-20?
Because here the token table is exact 127 bytes of token names plus one byte of zero.
If it's a bug, what exactly causes the LIST command to choke on a shifted L in a
As described already, the list routine iterates over all program lines and in each it displays the line number and then parses all token/text and print it. Tokens are recognized by having the high bit set. Tokens on the C64 reach from $80 (
END) to $CA (
GO) plus $FF for
PI. Whenever a token is detected, the token table is searched for (
PI is handled separately) in the most primitive way: $7F is subtracted and then tokens are scanned and skipped with that value decremented until zero is reached. Then this word pointing at is printed. Not really fast, but who cares during listing.This all works fine and stable - even with unused tokens. Try enter a Shift + M, and you get a
FOR ($81) listed.
Just $CB fails, as it points to the combination of a single zero and an 128 Byte overflow.
In conclusion it's a combination of a missing upper boundary check together with an addressing problem that only appears when using a character set not designed for and an input routine not catching it either.
The original routine can be found in all MS-Basic 1.0. For newer MS-Basics the token table is of a different length and the routine is modified - usually by using a 16 Bit Pointer instead of a fixed address plus offset - as their token tables are larger than 127 bytes.