[
Index] [
Previous] [
Next]
1.5 The BASIC Prompt & Program Storage
STOP / END
The keywords STOP and END are synonymous. We don't need to do anything other than lose the return address and fall into Main.
01F7
C0
Stop
RNZ
Syntax Error if args.
01F8
C1
POP B
Lose return address.
Main
Here's where a BASIC programmer in 1975 spent most of their time : typing at an "OK" prompt, one line at a time. A line of input would either be exec'd immediately (eg "PRINT 2+2"), or it would be a line of a program to be RUN later. Program lines would be prefixed with a line number. The code below looks for that line number, and jumps ahead to Exec if it's not there.
Print "OK"
01F9
218D01
Main
LXI H,
szOK
Set current line number to -1, indicating we're in immediate mode.
01FF
21FFFF
GetNonBlankLine
LXI H,FFFF
Get a line of input
Get first char of input. Note that carry will be set if it's a digit.
If line is blank (ie A is the null byte terminating the input buffer) then loop back to get a new input line. Notice the interesting method of testing for 0 - inc followed by dec. This is done so as not to affect the carry flag, which CPI 0 and OR A would have both done.
0209
3C
INR A
020A
3D
DCR A
Preserve first-char-is-digit flag (carry).
020E
F5
PUSH PSW
If the line of input begins with a line number, this call will read that number into DE.
Preserve line number on stack and tokenize the rest of the line.
0212
D5
PUSH D
Tokenize returns with the length of the tokenized line content in C and with A=0. Here we load B with 0, so as to have the line length in the 16-bit register BC.
0216
47
MOV B,A
Restore line number to DE.
0217
D1
POP D
Restore first-char-is-digit flag (carry), and if the first char was /not/ a digit, then execute the line of input now.
0218
F1
POP PSW
First char was a digit, therefore it's a line of program which needs to be stored, so we can fall into StoreProgramLine...
StoreProgramLine
Here's where a program line has been typed, which we now need to store in program memory.
021C
D5
StoreProgramLine
PUSH D
Push line number
021D
C5
PUSH B
Push line length
021E
D7
RST
NextChar
Get first char of line
021F
B7
ORA A
Zero set if line is empty (ie removing a line)
0220
F5
PUSH PSW
Preserve line-empty flag
0224
C5
PUSH B
Push line address.
Carry was set by the call to FindProgramLine, meaning that the line already exists. So we have to remove the old program line before inserting the new one in it's place. To remove the program line we simply move the remainder of the program (ie every line that comes after it) down in memory.
0228
EB
RemoveProgramLine
XCHG
DE=Next line address.
022C
1A
LDAX D
Move byte of program remainder down
022D
02
STAX B
in memory.
022E
03
INX B
022F
13
INX D
0230
E7
RST
CompareHLDE
Loop until DE==VAR_BASE, ie whole
0231
C22C02
JNZ RemoveLine+4
program remainder done.
0234
60
MOV H,B
0235
69
MOV L,C
Update VAR_BASE from BC.
To insert the program line, firstly the program remainder (every line that comes after the one to be inserted) must be moved up in memory to make room.
0239
D1
InsertProgramLine
POP D
DE=Line address (from 224)
023A
F1
POP PSW
Restore line-empty flag (see above)
023B
CA6002
JZ
UpdateLinkedList
If line is empty, then we don't need to insert it so can jump ahead.
0241
E3
XTHL
HL = Line length (see 21D)
0242
C1
POP B
BC = VAR_BASE
0243
09
DAD B
HL = VAR_BASE + line length.
0244
E5
PUSH H
0245
CDA701
CALL
CopyMemoryUp
Move remainder of program so there's enough space for the new line.
0248
E1
POP H
0249
226701
SHLD
VAR_BASE
Update VAR_BASE
024C
EB
XCHG
HL=Line address, DE=VAR_BASE
024D
74
MOV M,H
???
024E
23
INX H
Skip over next line ptr (updated below)
024F
23
INX H
0250
D1
POP D
DE = line number (see 21C)
0251
73
MOV M,E
Write line number to program line memory.
0252
23
INX H
0253
72
MOV M,D
0254
23
INX H
0255
111301
CopyFromBuffer
LXI D,
LINE_BUFFER
Copy the line into the program.
0258
1A
LDAX D
0259
77
MOV M,A
025A
23
INX H
025B
13
INX D
025C
B7
ORA A
Now the program line has been inserted/removed, all the pointers from each line to the next need to be updated.
0260
CDA202
UpdateLinkedList
CALL
ResetAll
0263
23
INX H
0264
EB
XCHG
0265
62
MOV H,D
0266
6B
MOV L,E
0267
7E
MOV A,M
If the pointer to the next line is a null
0268
23
INX H
word then we've reached the end of the
0269
B6
ORA M
program, job is done, and we can jump back
026D
23
INX H
Skip over line number.
026E
23
INX H
026F
23
INX H
0270
AF
XRA A
0271
BE
CMP M
0272
23
INX H
0273
C27102
JNZ 0271
0276
EB
XCHG
0277
73
MOV M,E
0278
23
INX H
0279
72
MOV M,D
027A
C36502
JMP 0265
FindProgramLine
Given a line number in DE, this function returns the address of that progam line in BC. If the line doesn't exist, then BC points to the next line's address, ie where the line could be inserted. Carry flag is set if the line exists, otherwise carry reset.
0280
44
MOV B,H
BC=this line
0281
4D
MOV C,L
0282
7E
MOV A,M
If we've found two consecutive
0283
23
INX H
null bytes, then we've reached the end
0284
B6
ORA M
of the program and so return.
0285
2B
DCX H
0286
C8
RZ
0287
C5
PUSH B
Push this line address
028A
E1
POP H
HL = this line number
028C
E1
POP H
HL = next line address
028D
C1
POP B
BC = this line address
028E
3F
CMC
028F
C8
RZ
Return carry set if line numbers match.
0290
3F
CMC
0291
D0
RNC
Return if we've reached a line number greater than the one required.
New
Keyword NEW. Writes the null line number to the bottom of program storage (ie an empty program), updates pointer to variables storage, and falls into RUN which just happens to do the rest of the work NEW needs to do.
No arguments allowed for the NEW keyword.
0295
C0
New
RNZ
Write the two null bytes program terminator to the start of program storage.
0299
AF
XRA A
029A
77
MOV M,A
029B
23
INX H
029C
77
MOV M,A
029D
23
INX H
And set the base of variable storage to immediately follow the null program.
Run
Runs the program. We don't actually need to do anything here, except check that no arguments have been supplied! We can just fall into ResetAll which sets everything up ready to run the program, and we then return to ExecNext.
No arguments allowed for the RUN keyword.
02A1
C0
Run
RNZ
ResetAll
Resets everything.
Set PROG_PTR_TEMP to just before the start of the program.
02A5
2B
DCX H
Reset the data pointer
Reset variable pointers
Get return address in BC and reset the stack pointer to it's top.
02B5
C1
ResetStack
POP B
02B9
F9
SPHL
Push address of stack top module 256. Fixme - why???
02BA
AF
XRA A
02BB
6F
MOV L,A
02BC
E5
PUSH H
Put return address back on stack, set HL to ??? and return.
02BD
C5
PUSH B
02C1
C9
RET
InputLineWith'?'
Gets a line of input at a '? ' prompt.
02C2
3E3F
InputLineWith'?'
MVI A,'?'
Print '?'
02C5
3E20
MVI A,' '
Print ' '
02CB
23
INX H
Tokenize
Tokenises LINE_BUFFER, replacing keywords with their IDs. On exit, C holds the length of the tokenised line plus a few bytes to make it a complete program line.
02CC
0E05
Tokenize
MVI C,05
Initialise line length to 5.
02CE
111301
LXI D,
LINE_BUFFER
ie, output ptr is same as input ptr at start.
If char is a space, jump ahead to write it out.
02D1
7E
MOV A,M
02D2
FE20
CPI ' '
If char is a " (indicating a string literal) then freely copy up to the closing ". Obviously we don't want to tokenize string literals.
02D7
47
MOV B,A
02D8
FE22
CPI '\"'
If char is null then we've reached the end of input, and can exit this function.
02DD
B7
ORA A
Here's where we start to see if we've got a keyword.
02E1
D5
PUSH D
Preserve output ptr.
02E2
0600
MVI B,00
Initialise Keyword ID to 0.
02E7
E5
PUSH H
Preserve input ptr.
02E8
3E..
MVI A,..
LXI over get-next-char
fixme.
02E9
D7
KwCompare
RST SyntaxCheck0
Get next input char
02EA
13
INX D
02EB
1A
LDAX D
Get keyword char to compare with.
02EC
E67F
ANI 7F
Ignore bit 7 of keyword char.
02EE
CAFF02
JZ
NotAKeyword
If keyword char==0, then end of keywords reached.
02F1
BE
CMP M
Keyword char matches input char?
02F2
C21C03
JNZ
NextKeyword
If not, jump to get next keyword.
OK, so input char == keyword char. Now we test bit 7 of the keyword char : if it's 0 then we haven't yet reached the end of the keyword and so have to loop back to continue comparing.
02F5
1A
LDAX D
02F6
B7
ORA A
Matched a keyword! First thing we do is remove input ptr from the stack, as since we're matched to a keyword we don't need to go back and try to match another keyword - HL is already the correct input ptr. Then we set A to the keyword ID which gets written out in the next block but one (notice we LXI over the next block).
02FA
F1
POP PSW
Remove input ptr from stack. We don't need it.
02FB
78
MOV A,B
A=Keyword ID
02FC
F680
ORI 80
Set bit 7 (indicates a keyword)
02FE
F2....
JP ....
LXI trick again.
Here we have found that the input does not lead with a keyword, so we restore the input ptr and write out the literal character.
02FF
E1
NotAKeyword
POP H
Restore input ptr
0300
7E
MOV A,M
and get input char
Write character, and advance buffer pointers.
0301
D1
POP D
Restore output ptr
0302
23
WriteChar
INX H
Advance input ptr
0303
12
STAX D
Store output char
0304
13
INX D
Advance output ptr
0305
0C
INR C
C++ (arf!).
If we've just written the ID of keyword REM then we need to freecopy the rest of the line. Here we test for REM (8E) and jump back to the outer loop if it isn't. Note that if it is REM, then we set B to 0 so the freecopy won't stop prematurely.
0306
D68E
SUI 8E
If it's not the
030B
47
MOV B,A
B=0
Free copy loop. This loop copies from input to output without tokenizing, as needs to be done for string literals and comment lines. The B register holds the terminating character - when this char is reached the free copy is complete and it jumps back
030C
7E
FreeCopyLoop
MOV A,M
A=Input char
030D
B7
ORA A
If char is null then exit
0311
B8
CMP B
If input char is term char then
0312
CA0203
JZ
WriteChar
we're done free copying.
0315
23
FreeCopy
INX H
0316
12
STAX D
0317
0C
INR C
0318
13
INX D
NextKeyword. Advances keyword ptr in DE to point to the next keyword in the table, then jumps back to KwCompare to see if it matches. Note we also increment the keyword ID.
031C
E1
NextKeyword
POP H
Restore input ptr
031D
E5
PUSH H
031E
04
INR B
Keyword ID ++;
031F
EB
XCHG
HL=keyword table ptr
0320
B6
NextKwLoop
ORA M
Loop until
0321
23
INX H
bit 7 of previous
0325
EB
XCHG
DE=keyword ptr, HL=input ptr
Exit. Restore LINE_BUFFER to HL, null-terminated the tokenized line buffer (three times in fact - why?) and return.
032C
12
STAX D
032D
13
INX D
032E
12
STAX D
032F
13
INX D
0330
12
STAX D
0331
C9
RET
InputLine
Gets a line of input into LINE_BUFFER.
0332
05
Backspace
DCR B
Char count--;
0333
2B
DCX H
Input ptr--;
0334
DF
RST
OutChar
Print backspace char.
033F
0601
MVI B,01
Get a character and jump out of here if user has pressed 'Enter'.
0344
FE0D
CPI '\r'
If user has not given a printable character, then loop back until they do.
0349
FE20
CPI ' '
If < ' '
034E
FE7D
CPI 7D
> '}'
Deal with line-abort. The character for this key was '@'.
0353
FE40
CPI '@'
Deal with backspace. The character for this key was '_'.
0358
FE5F
CPI '_'
A normal character has been pressed. Here we store it in LINE_BUFFER, only we don't if the terminal width has been exceeded. If the terminal width is exceeded then we ring the bell (ie print ASCII code 7) and ignore the char. Finally we loop back for the next input character.
035D
4F
MOV C,A
035E
78
MOV A,B
035F
FE48
CPI 48
0361
3E07
MVI A,07
0363
D26A03
JNC 036A
0366
79
MOV A,C
Write char to LINE_BUFFER.
0367
71
MOV M,C
0368
23
INX H
0369
04
INR B
[Index] [Previous] [Next]