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:
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:
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 2610 is 'LD A,24' instead of 'LD A,25', 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 22783, or (7,31) in the attribute file.
The reason, again, is that the instruction at 2610 is 'LD A,24' instead of 'LD A,25', 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:
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.
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:
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 5979, and the 'RET' that follows at 5980 causes an indirect jump to 23582 (an address pushed onto the stack earlier at 5889). After proceeding through the RAM at 23582 onwards, and disabling interrupts along the way, the Spectrum finally hangs on the 'HALT' instruction at MAIN_4.
If there were an end marker (0) 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 5917 would return to 5867, 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').
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 12799, 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 12331 is reached, the carry flag is set, A holds 255 (the sign byte of the second number), and (HL) also holds 255 (the sign byte of the first number). Then:
12331 ADC A,(HL) A=255 (255+255+carry).
12332 RRCA A=255, carry flag set.
12333 ADC A,0 A=0.
12335 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 12837 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 0x80 is used when testing the fourth byte of x at 12848.)