No step forward
Consider this PRINT command:
PRINT 12;CHR$ 9;34
You might, because of the cursor-right control character (CHR$ 9) between '12' and '34', expect it to print:
12 34
But in fact it prints:
1234
The reason is that the routine at PO_RIGHT, which is responsible for handling CHR$ 9, does not update the print position after overprinting a space to the right of the '2', and so effectively prints nothing at all.
Note, however, that even if the print position were updated, CHR$ 9 would still not be a true cursor-right control character, because it overprints a space using the current colours rather than leaving the colours as they are. The following PRINT command demonstrates this bug:
PRINT AT 0,0;1; INK 2;AT 0,0;CHR$ 9
No step back
Consider the following PRINT commands:
PRINT 1
PRINT CHR$ 8;2
PRINT CHR$ 8;3
They should print '2' at the right end of the top line of the display - because of the cursor-left character (CHR$ 8) before the '2' - but instead the '2' is printed at the beginning of the second line, as if the CHR$ 8 were not there. However, the '3' is (correctly) printed at the right end of the second line.
The reason is that the instruction at 0A32 is 'LD A,$18' instead of 'LD A,$19', which prevents CHR$ 8 from moving the print position up to the top line of the display.
A step back too far
In addition to sometimes not working when it should, the cursor-left character (CHR$ 8) also sometimes works when it shouldn't - specifically, when the print position is at the top-left of the screen. For example:
PRINT AT 0,0;CHR$ 8;12
Here the CHR$ 8 should have no effect, but instead the '1' is printed at (-1,0), which the ROM calculates to be at address 58FF, or (7,31) in the attribute file.
The reason, again, is that the instruction at 0A32 is 'LD A,$18' instead of 'LD A,$19', which allows CHR$ 8 to move the print position up beyond the top line of the display.
Saving a simple string
It is possible to save a simple string (as opposed to an array) to tape, but it cannot be restored. For example:
LET a$="12"
SAVE "a" DATA a$()
saves the simple string variable a$ to tape without giving an error. When the same variable is loaded from tape:
LOAD "" DATA a$()
no error is given until a$ is accessed - both PRINT a$ and PRINT a$(1) result in a '3 Subscript wrong' error.
The reason it's possible to save a simple string to tape is that the routine at SAVE_ETC, when it finds the string variable to be saved, does not check whether it is an array.
The neverending RESTORE
When the following program is run, the Spectrum will enter an infinite loop:
10 RESTORE 60000
The reason is that the routine at NEXT_ONE - which is called from LINE_ADDR to find the next line in the BASIC program - does not stop at the end of the BASIC program (if there even is one), but keeps going through the variables area and the rest of the RAM. Depending on the contents of the RAM, the routine can end up examining the same memory area over and over again, never finding anything that looks like a BASIC line with a suitably high number.
Curse the cursor
In some circumstances, pressing EDIT can bring the current line cursor down to the editing area along with the current line, and the cursor must be removed before the line is re-entered. For example, type in the following program, pressing ENTER where shown:
10 PRINT<ENTER>
11<ENTER>
Then press EDIT, and see line 10 come down to the editing area with the cursor in tow.
The reason this happens is that the routine at ED_EDIT decrements the line number at E-PPC (from 11 to 10) so as to avoid printing the cursor, but doesn't take into account that the number of the line that will be brought down for editing is one less than its current value.
STR$ and small numbers
Because of a bug in the handling of the calculator stack at PF_SMALL, a binary expression with STR$ on the right hand side is evaluated as if its left hand side is empty. For example:
PRINT "2"+STR$ 0.5
prints '0.5' instead of '20.5'.
Note that the bug is triggered only if the argument of STR$ is a non-zero number strictly between -1 and 1.
GO TO and large numbers
Since BASIC line numbers cannot be greater than 9999, you might expect a 'GO TO n' command where n>9999 to either do nothing or give an appropriate error message. But GO TO does not behave consistently when n is large.
The command 'GO TO n' always (appropriately) does nothing when n<32768, so long as there is no line number greater than or equal to n and there are no variables present.
But if there are variables present, the command 'GO TO n' can give a 'C Nonsense in BASIC' error when 9999<n<=32767; for example:
LET a=1: GO TO 24832
gives the error 'C Nonsense in BASIC, H832:1'. This happens because the routine at LINE_ADDR (called from LINE_NEW) finds the definition of the variable 'a' in the variables area, the first two bytes of which are 97 (the ASCII code for 'a') and 0, and takes it to be line number 24832 (97*256).
The command 'GO TO n' gives an 'N Statement lost, 0:255' error when 32768<=n<=61439. (See the check at 1B83: a line number greater than 32767 indicates the editing area.)
The command 'GO TO n' (appropriately) gives a 'B Integer out of range' error when n>=61440. (See the check at 1E6E.)
Don't close the streams
It can be dangerous to close a stream, especially if it's aleady been closed or was never opened. For example, on a freshly booted Spectrum:
CLOSE #4
makes the computer hang.
The root cause is that there is no end marker in the close stream lookup table. This makes the 'JP (HL)' at the end of the routine at CLOSE_2 jump to 175B, and the 'RET' that follows at 175C causes an indirect jump to 5C1E (an address pushed onto the stack earlier at CLOSE_2). After proceeding through the RAM at 5C1E onwards, and disabling interrupts along the way, the Spectrum finally hangs on the 'HALT' instruction at MAIN_4.
If there were an end marker (+00) in the close stream lookup table, the 'JP (HL)' at the end of the routine at CLOSE_2 would jump to CLOSE_STR, the 'RET' that follows at 171D would return to 16EB, and the stream would be closed successfully.
Press (almost) any key
Not every key besides 'n', 'N', BREAK and STOP will work at the 'scroll?' and 'Start tape then press any key.' prompts: pressing CAPS LOCK, or GRAPHICS, or CAPS SHIFT and SYMBOL SHIFT together at these prompts causes the previous edit line to appear at the bottom of the screen instead.
This is because the routine at KEY_INPUT, which is used (via WAIT_KEY) to decode a keypress, copies the edit line to the lower part of the screen when the mode changes (from 'L').
The scrolling tokens
When the program
10 PRINT "a": GO TO 10
is RUN, pressing CAPS LOCK (or GRAPHICS, or CAPS SHIFT and SYMBOL SHIFT together) at the 'scroll?' prompt places 'RUN' in the edit area, and then pressing ENTER fills the screen with BASIC tokens. Pressing ENTER at the next 'scroll?' prompt leads to more BASIC tokens and other characters being printed, and then the program stops with a 'K Invalid colour' error.
This happens because the DE register pair - which is used by the section of code at PR_STRING to hold the address of the next character to be printed - is corrupted during the call to PRINT_A_1 (made by the 'RST $10' instruction at 2042) and ends up pointing at 00B8 after CAPS LOCK and ENTER are pressed at the 'scroll?' prompt. DE is then incremented through the token table until it reaches 01F4 and the 'scroll?' prompt is printed again. After ENTER is pressed at this prompt, DE is incremented through the remainder of the token table, and then through the key tables, and then into the routine at KEY_SCAN, until it reaches 0290, where it finds the control character +11 (PAPER) followed by +FF (the first two bytes of the 'LD DE,$FFFF' instruction at 0290), which produces the 'K Invalid colour' error.
This bug would be prevented if DE were saved before the call to WAIT_KEY at 0CB3, and restored afterwards.
The impatient 'scroll?' prompt
When the program
10 FOR n=1 TO 100: PRINT n: NEXT n
is RUN, pressing TRUE VIDEO or INV. VIDEO at the 'scroll?' prompt causes the screen to scroll once, prints a 'scroll?' prompt, scrolls the screen again without waiting for a keypress, and then prints another 'scroll?' prompt indented by 7 spaces.
This happens because when TRUE VIDEO or INV. VIDEO is pressed at the initial 'scroll?' prompt, the routine at KEY_INPUT interprets the keypress as an INVERSE control code (+04 or +05) and changes the input address for the current channel from KEY_INPUT to KEY_NEXT (see KEY_DATA). This means that after the next 'scroll?' prompt is printed and WAIT_KEY is called, KEY_NEXT (which picks up the parameter for the INVERSE control code instead of waiting for a keypress, and does not reset the print position for the lower part of the display in preparation for the next 'scroll?' prompt) is executed instead of KEY_INPUT (which would wait for a keypress, and then reset the print position for the lower part of the display).
Anything equals SCREEN$ (x,y)
Some care is needed when using SCREEN$, because it doesn't always work as you would expect. For example:
IF ""=SCREEN$ (0,0) THEN PRINT "?"
will print "?" no matter what is on the screen at (0,0) at the time.
The reason is that the routine at S_SCRN_S exits via STK_STO, which results in the character found at (0,0) being stored on the calculator stack twice instead of just once. So when the '=' operation above is performed, the last two items on the stack - the two copies of SCREEN$ (0,0) - are equal, and the result of the operation is always true, regardless of what's on the left hand side of the equation.
Note that the calculator stack is cleared before each statement of a BASIC program (or the edit line) is executed, so the bug is confined to the statement that contains SCREEN$, and will not affect any other statements.
The 34th bit of division
In the division loop at DIV_LOOP, the 34th bit of the quotient is always set to 0, meaning that some results - such as 1/10 and 1/1000 - are not rounded up as they should be. For example:
PRINT 1/2-.5
prints '2.3283064E-10' instead of '0', because while '1/2' is calculated correctly, '.5' is evaluated as '5 * 1/10', which is not calculated correctly. Specifically, '1/10' is represented as 7D 4C CC CC CC (0.0999999999767) in floating point form, but if the 34th bit were included, it would be rounded up to 7D 4C CC CC CD (0.100000000006), which is more accurate.
The problem is caused by the jump at 31FF, which should be to DIV_34TH instead of DIV_START.
The trouble with -65536
In some circumstances, the number -65536 is stored in short form instead of full floating point form, which causes problems. For example:
PRINT -65535-1
prints '-1E-38', and:
PRINT INT -65536
prints '-1'.
The first of these problems is caused by the routine at addition, which fails to detect an overflow when adding two small negative integers whose sum is -65536. Specifically, when 302B is reached, the carry flag is set, A holds +FF (the sign byte of the second number), and (HL) also holds +FF (the sign byte of the first number). Then:
302B ADC A,(HL) A=+FF (+FF plus +FF plus carry).
302C RRCA A=+FF, carry flag set.
302D ADC A,$00 A=+00.
302F JR NZ,ADDN_OFLW This jump is not made.
The result is then stored as 00 FF 00 00 00, which other ROM routines do not always handle correctly.
The second problem is caused by the routine at truncate. Specifically, the section of code at 3225 checks whether the argument x satisfies -65537<x<=-65536; if it does, its floating point form is (erroneously) modified to 00 FF 00 00 00. (-65537 in floating point form is 91 80 00 80 00, which explains why the mask +80 is used when testing the fourth byte of x at 3230.)