[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  
01FC CDA305 CALL PrintString
Set current line number to -1, indicating we're in immediate mode.
01FF 21FFFF GetNonBlankLine LXI H,FFFF
0202 226101 SHLD CURRENT_LINE
Get a line of input
0205 CD3C03 CALL InputLine  
Get first char of input. Note that carry will be set if it's a digit.
0208 D7 RST NextChar  
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  
020B CAFF01 JZ GetNonBlankLine  
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.
020F CD9D04 CALL LineNumberFromStr  
Preserve line number on stack and tokenize the rest of the line.
0212 D5 PUSH D  
0213 CDCC02 CALL Tokenize  
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  
0219 D23E04 JNC Exec  
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
0221 CD7D02 CALL FindProgramLine Get nearest program line address in BC.
0224 C5 PUSH B Push line address.
0225 D23902 JNC InsertProgramLine If line doesn't exist, jump ahead to insert it.
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.
0229 2A6701 LHLD VAR_BASE
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.
0236 226701 SHLD VAR_BASE
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.
023E 2A6701 LHLD VAR_BASE
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
025D C25802 JNZ CopyFromBuffer+3
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
026A CAFF01 JZ GetNonBlankLine to let the user type in the next line.
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.

027D 2A6501 FindProgramLine LHLD PROGRAM_BASE
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
0288 F7 RST PushNextWord Push (next line address)
0289 F7 RST PushNextWord Push (this line number)
028A E1 POP H HL = this line number
028B E7 RST CompareHLDE Compare line numbers
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.
0292 C38002 JMP FindProgramLine+3

 

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.
0296 2A6501 LHLD PROGRAM_BASE  
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.
029E 226701 SHLD VAR_BASE  

 

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.
02A2 2A6501 ResetAll LHLD PROGRAM_BASE
02A5 2B DCX H
02A6 225D01 SHLD PROG_PTR_TEMP
Reset the data pointer
02A9 CD6904 CALL Restore
Reset variable pointers
02AC 2A6701 LHLD VAR_BASE
02AF 226901 SHLD VAR_ARRAY_BASE
02B2 226B01 SHLD VAR_TOP
Get return address in BC and reset the stack pointer to it's top.
02B5 C1 ResetStack POP B
02B6 2A6301 LHLD STACK_TOP
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
02BE 2A5D01 LHLD PROG_PTR_TEMP
02C1 C9 RET

 

InputLineWith'?'

Gets a line of input at a '? ' prompt.

02C2 3E3F InputLineWith'?' MVI A,'?' Print '?'
02C4 DF RST OutChar
02C5 3E20 MVI A,' ' Print ' '
02C7 DF RST OutChar
02C8 CD3C03 CALL InputLine
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 ' '
02D4 CA0203 JZ WriteChar
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 '\"'
02DA CA1503 JZ FreeCopy
If char is null then we've reached the end of input, and can exit this function.
02DD B7 ORA A
02DE CA2903 JZ Exit
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.
02E4 115600 LXI D,KEYWORDS-1
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
02F7 F2E902 JP KwCompare
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
0308 C2D102 JNZ Tokenize+5
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
030E CA2903 JZ 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
0319 C30C03 JMP FreeCopyLoop
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
0322 F22003 JP NextKwLoop keyword char is set.
0325 EB XCHG DE=keyword ptr, HL=input ptr
0326 C3EB02 JMP KwCompare+2
Exit. Restore LINE_BUFFER to HL, null-terminated the tokenized line buffer (three times in fact - why?) and return.
0329 211201 Exit LXI H,LINE_BUFFER
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.
0335 C24103 JNZ InputNext
0338 DF ResetInput RST OutChar
0339 CD8A05 CALL NewLine
033C 211301 InputLine LXI H,LINE_BUFFER
033F 0601 MVI B,01
Get a character and jump out of here if user has pressed 'Enter'.
0341 CD8203 InputNext CALL InputChar
0344 FE0D CPI '\r'
0346 CA8505 JZ TerminateInput
If user has not given a printable character, then loop back until they do.
0349 FE20 CPI ' ' If < ' '
034B DA4103 JC InputNext or
034E FE7D CPI 7D > '}'
0350 D24103 JNC InputNext then loop back.
Deal with line-abort. The character for this key was '@'.
0353 FE40 CPI '@'
0355 CA3803 JZ ResetInput
Deal with backspace. The character for this key was '_'.
0358 FE5F CPI '_'
035A CA3203 JZ Backspace
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
036A DF RST OutChar
036B C34103 JMP InputNext

 


[Index] [Previous] [Next]