; for AKI-Z80 MACLIB Z80 MONITOR EQU 0100H ORG 0FD1H ; OUTPUT CHAR on A ; BS -> BS, SP, BS ; CR -> CR, LF RST3: CPI 08H JRNZ OUTCH0 RST 3 MVI A, 20H RST 3 MVI A, 08H OUTCH0: RST 3 CPI CR RNZ MVI A, LF RST 3 MVI A, CR RET ; INPUT CHAR to A ; Z-flag if not ; ctrl-C -> break ; ctrl-S -> pause ; DEL => BS RST2: RST 1 RZ RST 2 CPI 7FH ; DEL? JRNZ CHK0 MVI A, 08H ; => BS CHK0: CPI 03H ; break? JZ RSTART CPI 13H ; pause? RNZ CHK1: RST 1 JRZ CHK1 RST 2 CPI 03H ; break? JZ RSTART RET ; PCWPATB.ASM ; ----------- ; ; SOFTWARE: ED, MAC, SID ; HARDWARE: Amstrad PCW8256, CP/M Plus v1.4 ; (The only hardware specific part is the ; SID restart point, at label SID: ) ; ; Palo Alto Tiny BASIC Interpreter Version 3.0 ; ; See "Dr. DOBB's Journal" Vol.1 No.1 to 5. ; My advice is to buy the Volume 1 of DDJ: ; M&T Publishing Inc. ; 501 Galveston Drive ; REDWOOD CITY ; CA 94063 ; USA ; --- ; ; PALO ALTO TINY BASIC V3.0 ; OK ; > ; ; Type Palo Alto Tiny BASIC commands in upper cases. ; ; ; ------------------------------- ; CR equ 0DH LF equ 0AH ; ; ------------------------------- ; tstc macro char, label call tstch db char db low label-$-1 endm ; ; ------------------------------- ; item macro first, second if nul first db high second + 8000H db low second else db first db high second + 8000H db low second endif endm ; ; The following is the original code (slighty edited for CP/M). ; --------------------------------------------------------------- ; ; P A T B ; PALO ALTO TINY BASIC INTERPRETER ; VERSION 3.0 ; FOR 8080 SYSTEM ; LI-CHEN WANG ; 26 APRIL, 1977 ; ; --------------------------------------------------------------- ; ; *** MEMORY USAGE *** ; ; 0080-01FF are for variables, input line and stack ; 2000-3FFF are for Tiny BASIC text & array ; F000-F7FF are for PATB code ; ; botrom equ 01000H ; JIRO> BASIC botscr equ 08800H ; JIRO> data topscr equ 09000H ; JIRO> | botram equ 09000H ; JIRO> program dftlmt equ 0FF00H ; JIRO> | ; ; Define variables, buffer and stack in RAM ; ORG BOTSCR txtlmt ds 2 ; -> limit of text area varbgn ds 2*26 ; TB variables A-Z currnt ds 2 ; points to current line stkgos ds 2 ; saves SP in 'GOSUB' varnxt ds 0 ; temporary storage stkinp ds 2 ; saves SP in 'INPUT' lopvar ds 2 ; 'FOR' loop save area lopinc ds 2 ; increment loplmt ds 2 ; limit lopln ds 2 ; line number loppt ds 2 ; text pointer ranpnt ds 2 ; random number pointer ds 1 ; extra byte for buffer buffer ds 132 ; input buffer bufend ds 0 ; buffer end ds 4 ; extra bytes for stack stklmt ds 0 ; soft limit for stack ; ORG TOPSCR ; stack ds 0 ; stack starts here ; ORG BOTRAM ; txtunf ds 2 ; unfilled text save area text ds 2 ; text save area ; ; --------------------------------------------------------------- ; ; *** INITIALIZE ; ;;;;;;;; ; START ;;;;;;;; ORG BOTROM INIT: LXI SP, STACK CALL CRLF LXI H, DFTLMT ;and set default value SHLD TXTLMT ;in 'TXTLMT' MVI A, HIGH BOTROM ;initialize RANPNT STA RANPNT+1 PURGE: LXI H, TEXT+4 ;purge text area SHLD TXTUNF MVI H, 0FFH SHLD TEXT TELL: LXI D, MSG ; tell user CALL PRTSTG ; ************************* JMP RSTART MSG: db 'PALO ' db 'ALTO ' db 'TINY ' db 'BASIC' db ' V3.0' db '/AKI', CR ; OK: db 'OK' db CR ; WHAT: db 'WHAT?' db CR ; HOW: db 'HOW?' db CR ; SORRY: db 'SORRY' db CR ; ; --------------------------------------------------------------- ; ; *** DIRECT COMMAND / TEXT COLLECTER *** ; ; PATB prints out "OK(CR)", and then it prompts ">" and reads ; a line. If the line starts with a non-zero number, this ; number is the line number. The line number (in 16 bit ; binary) and the rest of the line (including CR) is stored ; in the memory. If a line with the same line number is already ; there, it is replaced by the new one. If the rest of the line ; consists of a CR only, it is not stored and any existing line ; with the same line number is deleted. ; ; After a line is inserted, replaced, or deleted, the program ; loops back and ask for another line. This loop will be ; terminated when it reads a line with zero or no line number; ; and control is transfered to "DIRECT". ; ; Tiny BASIC program save area starts at the memory location ; labeled "TEXT". The end of text is marked by 2 bytes XX FF. ; Following these are 2 bytes reserved for the array element ; @(0). The content of location labeled "TXTUNF" points to one ; after @(0). ; ; The memory location "CURRNT" points to the line number that ; is currently being interpreted. While we are in this loop ; or while we are interpreting a direct command (see next ; section), "CURRNT" should point to a 0. ; RSTART: LXI SP, STACK ; re-initialize stack LXI H, ST1+1 ; literal 0 SHLD CURRNT ; CURRNT->line # = 0 ST1: LXI H, 0 SHLD LOPVAR SHLD STKGOS LXI D, OK ; DE->string CALL PRTSTG ; print string until CR ST2: MVI A, '>' ; prompt '>' and CALL GETLN ; read a line PUSH D ; DE->end of line LXI D, BUFFER ; DE->beginning of line CALL TSTNUM ; test if it is a number CALL IGNBLK MOV A, H ; HL=value of the # or ORA L ; 0 if no # was found POP B ; BC->end of line JZ DIRECT DCX D ; backup DE and save MOV A, H ; value of line # there STAX D DCX D MOV A, L STAX D PUSH B ; BC, DE -> begin, end PUSH D MOV A, C SUB E PUSH PSW ; A=# of bytes in line CALL FNDLN ; find this line in save PUSH D ; area, DE->save area JNZ ST3 ; NZ=not found, insert PUSH D ; Z=found, delete it CALL FNDNXT ; set DE->next line POP B ; BC->line to be deleted LHLD TXTUNF ; HL->unfilled save area CALL MVUP ; move up to delete MOV H, B ; TXTUNF->unfilled area MOV L, C SHLD TXTUNF ; update ST3: POP B ; get ready to insert LHLD TXTUNF ; but first check if POP PSW ; the length of new line PUSH H ; is 3 (line # and CR) CPI 3 ; then do not insert JZ RSTART ; must clear the stack ADD L ; compute new TXTUNF MOV E, A MVI A, 0 ADC H MOV D, A ; DE->new unfilled area LHLD TXTLMT ; check to see if there XCHG CALL COMP ; is enough space JNC QSORRY ; sorry, no room for it SHLD TXTUNF ; ok, update TXTUNF POP D ; DE->old unfilled area CALL MVDOWN POP D ; DE->begin, HL->end POP H CALL MVUP ; move new line to JMP ST2 ; save area ; ; --------------------------------------------------------------- ; ; *** DIRECT *** & EXEC *** ; ; This section of the code tests a string against a table. ; When a match is found, control is transfered to the section ; of code according to the table. ; ; At 'EXEC', DE should point to the string and HL should point ; to the table-1. At 'DIRECT', DE should point to the string, ; HL will be set up to point to tab1-1, which is the table of ; all direct and statement commands. ; ; A '.' in the string will terminate the test and the partial ; match will be considered as a match, e.g., 'P.', 'PR.', ; 'PRI.', 'PRIN.' or 'PRINT' will all match 'PRINT'. ; ; The table consists of any number of items. Each item is a ; string of characters with bit 7 set to 0 and a jump address ; stored hi-low with bit 7 of the high byte set to 1. ; ; End of table is an item with a jump address only. If the ; string does not match any of the other items, it will match ; this null item as default. ; DIRECT: LXI H, TAB1-1 ; *** DIRECT *** ; EXEC: CALL IGNBLK ; *** EXEC *** PUSH D ; save pointer EX1: LDAX D ; if found '.' in string INX D ; before any mismatch CPI '.' ; we declare a match JZ EX3 INX H ; HL->table CMP M ; if match, test next JZ EX1 MVI A, 07FH ; else, see if bit 7 DCX D ; of table is set, which CMP M ; is the jump address (HIGH) JC EX5 ; C=yes, matched EX2: INX H ; NC=no, find jump address CMP M JNC EX2 INX H ; bump to next table item POP D ; restore string pointer JMP EXEC ; test again next item EX3: MVI A, 07FH ; partial match, find EX4: INX H ; jump address, which is CMP M ; flagged by bit 7 JNC EX4 EX5: MOV A, M ; load HL with the jump INX H ; address from the table MOV L, M ; **************** ANI 07FH ; *** ANI 07FH *** MOV H, A ; **************** POP PSW ; clean up the garbage PCHL ; and we go do it ; ; --------------------------------------------------------------- ; ; What follows is the code to execute direct and statement ; commands. Control is transfered to these points via the ; command table lookup code of 'DIRECT' and 'EXEC' in last ; section. After the command is executed, control is transfered ; to other sections as follows: ; ; For 'LIST', 'NEW', and 'STOP': go back to 'RSTART'. ; For 'RUN': go execute the first stored line if any; ; else go back to 'RSTART'. ; For 'GOTO' and 'GOSUB': go execute the target line. ; For 'RETURN' and 'NEXT': go back to saved return line. ; For all others: if 'CURRNT' -> 0, go to 'RSTART', ; else go execute next command. (This is done in 'FINISH'.) ; ; --------------------------------------------------------------- ; ; *** NEW *** STOP *** RUN (& friends) *** & GOTO *** ; ; 'NEW(CR)' resets 'TXTUNF'. ; ; 'STOP(CR)' goes back to 'RSTART'. ; ; 'RUN(CR)' finds the first stored line, store its address ; (in 'CURRNT'), and start execute it. Note that only those ; commands in TAB2 are legal for stored program. ; ; There are 3 more entries in 'RUN': ; 'RUNNXL' finds next line, stores its address and executes it. ; 'RUNTSL' stores the address of this line and execute it. ; 'RUNSML' continues the execution on same line. ; ; 'GOTO expr(CR)' evaluates the expression, find the target ; line, and jump to 'RUNTSL' to do it. ; NEW: CALL ENDCHK ; *** NEW(CR) *** JMP PURGE ; STOP: CALL ENDCHK ; *** STOP(CR) *** JMP RSTART ; RUN: CALL ENDCHK ; *** RUN(CR) *** LXI D, TEXT ; first saved line ; RUNNXL: LXI H, 0 ; *** RUNNXL *** CALL FNDLP ; find whatever line # JC RSTART ; C=passed TXTUNF, quit ; RUNTSL: XCHG ; *** RUNTSL *** SHLD CURRNT ; set 'CURRNT'->line # XCHG INX D ; bump pass line # INX D ; RUNSML: CALL CHKIO ; *** RUNSML *** LXI H, TAB2-1 ; find command in TAB2 JMP EXEC ; and execute it ; GOTO: CALL EXPR ; *** GOTO expr *** PUSH D ; save for error routine CALL ENDCHK ; must find a CR CALL FNDLN ; find the target line JNZ AHOW ; no such line # POP PSW ; clear the "PUSH DE" JMP RUNTSL ; go do it ; ; --------------------------------------------------------------- ; ; *** LIST *** & PRINT *** ; ; LIST has three forms: ; 'LIST(CR)' lists all saved lines. ; 'LIST n(CR)' start list at line n. ; 'LIST n1, n2(CR)' start list at line n1 for n2 lines. ; (You can stop the listing by Control-C key.) ; ; PRINT command is 'PRINT .....;' or 'PRINT ....(CR)' ; where '...' is a list of expressions, formats, and/or strings. ; These items are separated by commas. ; ; A format is a number sign followed by a number. It controls ; the number of spaces the value of a expression is going ; to be printed. It stays effective for the rest of the print ; command unless changed by another format. If no format is ; specified, 8 positions will be used. ; ; A string is quoted in a pair of single quotes or a pair of ; double quotes. ; ; Control characters and lower case letters can be included ; inside the quotes. Another (better) way of generating control ; characters on the output is use the up-arrow character ; followed by a letter. L means FF, I means HT, ; G means BELL, etc. ; ; A (CRLF) is generated after the entire list has been printed ; or if the list is a null list. Howewer if the list ended with ; a comma, no (CRLF) is generated. ; LIST: CALL TSTNUM ; test if there is a # PUSH H LXI H, 0FFFFH tstc ',', ls1 CALL TSTNUM LS1: XTHL CALL ENDCHK ; if no #, we get a 0 CALL FNDLN ; find this or next line LS2: JC RSTART ; C=passed TXTUNF XTHL MOV A, H ORA L JZ RSTART DCX H XTHL CALL PRTLN ; print the line CALL PRTSTG CALL CHKIO CALL FNDLP ; find next line JMP LS2 ; and loop back ; PRINT: MVI C, 8 ; C=# of spaces tstc ';', PR1 ;if null list & ";" CALL CRLF ; give CR-LF and JMP RUNSML ; continue same line PR1: tstc CR, PR6 ; if null list (CR) CALL CRLF ; also give CR-LF and JMP RUNNXL ; go to next line PR2: tstc '#', PR4 ; else, is it format? PR3: CALL EXPR ; yes, evaluate expr. MVI A, 0C0H ANA L ORA H JNZ QHOW MOV C, L ; and save it in C JMP PR5 ; look for more to print PR4: CALL QTSTG ; or is it a string? JMP PR9 ; if not, must be expr. PR5: tstc ',', PR8 ; if ",", go find next PR6: tstc ',', PR7 MVI A, ' ' CALL OUTCH JMP PR6 PR7: CALL FIN ; in the list JMP PR2 ; list continues PR8: CALL CRLF ; list ends JMP FINISH PR9: CALL EXPR ; evaluate the expr PUSH B CALL PRTNUM ; print the value POP B JMP PR5 ; more to print? ; ; --------------------------------------------------------------- ; ; *** GOSUB *** & RETURN *** ; ; 'GOSUB expr;' or 'GOSUB expr (CR)' is like the 'GOTO' command, ; except that the current text pointer, stack pointer etc. are ; save so that execution can be continued after the subroutine ; 'RETURN'. In order that 'GOSUB' can be nested (and even ; recursive), the save area must be stacked. The stack pointer ; is saved in 'STKGOS'. The old 'STKGOS' is saved in the stack. ; If we are in the main routine, 'STKGOS' is zero (this was done ; by the "main" section of the code), but we still save it as ; a flag for no further 'RETURN's. ; ; 'RETURN(CR)' undos everything that 'GOSUB' did, and thus ; return the execution to the command after the most recent ; 'GOSUB'. If 'STKGOS' is zero, it indicates that we never ; had a 'GOSUB' and is thus an error. ; GOSUB: CALL PUSHA ; save the current "FOR" CALL EXPR ; parameters PUSH D ; and text pointer CALL FNDLN ; find the target line JNZ AHOW ; not there, say "HOW?" LHLD CURRNT ; save old PUSH H ; 'CURRNT' old 'STKGOS' LHLD STKGOS PUSH H LXI H, 0 ; and load new ones SHLD LOPVAR DAD SP SHLD STKGOS JMP RUNTSL ; then run that line ; RETURN: CALL ENDCHK ; there must be a CR LHLD STKGOS ; old stack pointer MOV A, H ; 0 means not exist ORA L JZ QWHAT ; so, we say: "WHAT?" SPHL ; else, restore it RESTOR: POP H SHLD STKGOS ; and the old 'STKGOS' POP H SHLD CURRNT ; and the old 'CURRNT' POP D ; old text pointer CALL POPA ; old "FOR" parameters JMP FINISH ; ; --------------------------------------------------------------- ; ; *** FOR *** & NEXT *** ; ; 'FOR' has two forms: 'FOR VAR=EXP1 TO EXP2 STEP EXP3' and ; 'FOR VAR=EXP1 TO EXP2' the second form means the same thing ; as the first form with EXP3=1 (i.e., with a step of +1). ; PATB will find the variable var. and set its value to the ; current value of EXP1. It also evaluates EXP2 and EXP3 and ; save all these together with the text pointer etc. in the ; 'FOR' save area, which consists of 'LOPVAR', 'LOPINC', ; 'LOPLMT', 'LOPLN', and 'LOPPT'. If there is already some- ; thing in the save area (this is indicated by a non-zero ; 'LOPVAR'), then the old save area is saved in the stack ; before the new one overwrites it. PATB will then dig in the ; stack and find out if this same variable was used in another ; currently active 'FOR' loop. If that is the case, then the ; old 'FOR' loop is deactivated (Purged from the stack). ; ; 'NEXT var' serves as the logical (not necessarilly physical) ; end of the 'FOR' loop. The control variable var. is checked ; with the 'LOPVAR'. If they are not the same, PATB digs in the ; stack to find the right one and purges all those that did not ; match. Either way, PATB then adds the 'STEP' to that variable ; and check the result with the limit. If it is within the limit, ; control loops back to the command following the 'FOR'. ; If outside the limit, the save area is purged and execution ; continues. ; FOR: CALL PUSHA ; save the old save area CALL SETVAL ; set the control var. DCX H ; HL is its address SHLD LOPVAR ; save that LXI H, TAB4-1 ; use 'EXEC' to look JMP EXEC ; for the word 'TO' FR1: CALL EXPR ; evaluate the limit SHLD LOPLMT ; save that LXI H, TAB5-1 ; use 'EXEC' to look JMP EXEC ; for the word 'STEP' FR2: CALL EXPR ; found it, get step JMP FR4 FR3: LXI H, 1 ; not found, set to 1 FR4: SHLD LOPINC ; save that too LHLD CURRNT ; save current line # SHLD LOPLN XCHG ; and text pointer SHLD LOPPT LXI B, 10 ; dig into stack to LHLD LOPVAR ; find 'LOPVAR' XCHG MOV H, B MOV L, B ; HL=0 now DAD SP ; here is the stack JMP FR6 FR5: DAD B ; each level is 10 deep FR6: MOV A, M ; get that old 'LOPVAR' INX H ORA M JZ FR7 ; 0 says no more in it MOV A, M DCX H CMP D ; same as this one? JNZ FR5 MOV A, M ; the other half? CMP E JNZ FR5 XCHG ; yes, found one LXI H, 0 DAD SP ; try to move SP MOV B, H MOV C, L LXI H, 10 DAD D CALL MVDOWN ; and purge 10 words SPHL ; in the stack FR7: LHLD LOPPT ; job done, restore DE XCHG JMP FINISH ; and continue ; NEXT: CALL TSTV ; get address of var. JC QWHAT ; no variable, "WHAT?" SHLD VARNXT ; yes, save it NX1: PUSH D ; save text pointer XCHG LHLD LOPVAR ; get var. in 'FOR' MOV A, H ORA L ; 0 says never had one JZ AWHAT ; so we ask: "WHAT?" CALL COMP ; else, we check them JZ NX2 ; ok, they agree POP D ; no, let's see CALL POPA ; purge current loop LHLD VARNXT ; and pop one level JMP NX1 ; go check again NX2: MOV E, M ; come here when agreed INX H MOV D, M ; DE=value of var. LHLD LOPINC PUSH H MOV A, H XRA D ; S=sign differ MOV A, D ; A=sign of DE DAD D ; add one step JM NX3 ; cannot overflow XRA H ; may overflow JM NX5 ; and it did NX3: XCHG LHLD LOPVAR ; put it back MOV M, E INX H MOV M, D LHLD LOPLMT ; HL=limit POP PSW ; old HL ORA A JP NX4 ; step > 0 XCHG ; step < 0 NX4: CALL CKHLDE ; compare with limit POP D ; restore text pointer JC NX6 ; outside limit LHLD LOPLN ; within limit, so SHLD CURRNT ; back to the saved LHLD LOPPT ; 'CURRNT' and text XCHG ; pointer JMP FINISH NX5: POP H ; overflow, purge POP D ; garbage in stack NX6: CALL POPA ; purge this loop JMP FINISH ; ; --------------------------------------------------------------- ; ; *** REM *** IF *** INPUT *** & LET (& DEFLT) *** ; ; 'REM' can be followed by anything and is ignored by PATB. ; PATB treats it like an 'IF' with a false condition. ; ; 'IF' is followed by an expression as a condition and one or ; more commands (including other 'IF's) separated by semi-colons. ; Note that the word 'THEN' is not used. PATB evaluates the expr. ; If it is non-zero, execution continues. If the expr. is zero, ; the commands that follows are ignored and execution continues ; at the next line. ; ; 'INPUT' command is like the 'PRINT' command, and is followed ; by a list of items. If the item is a string in single or ; double quotes, or is an up-arrow, it has the same effect as ; in 'PRINT'. If an item is a variable, this variable name is ; printed out followed by a colon. Then PATB waits for an expr. ; to be typed in. The variable is then set to the value of this ; expr. If the variable is proceded by a string (again in single ; or double quotes), the string will be printed followed by a ; colon. PATB then waits for input expr. and set the variable ; to the value of the expr. ; ; If the input expression is invalid, PATB will print "WHAT?", ; "HOW?" or "SORRY" and reprint the prompt and redo the input. ; The execution will not terminate unless you type Control-C. ; This is handled in 'INPERR'. ; ; 'LET' is followed by a list of items separated by commas. ; Each item consists of a variable, an equal sign, and an expr. ; PATB evaluates the expr. and set the variable to that value. ; PATB will also handle 'LET' command without the word 'LET'. ; This is done by 'DEFLT'. ; REM: LXI H, 0 ; *** REM *** JMP IF1 ; this is like 'IF 0' ; IFF: CALL EXPR ; *** IF *** IF1: MOV A, H ; is the expression = 0? ORA L JNZ RUNSML ; no, continue CALL FNDSKP ; yes, skip rest of line JNC RUNTSL ; and run the next line JMP RSTART ; if no next, re-start ; INPERR: LHLD STKINP ; *** INPERR *** SPHL ; restore old SP POP H ; and old 'CURRNT' SHLD CURRNT POP D ; and old text pointer POP D ; read input ; INPUT ds 0 IP1: PUSH D ; save in case of error CALL QTSTG ; is next item a string? JMP IP8 ; no IP2: CALL TSTV ; yes, but followed by a JC IP5 ; variable? no. IP3: CALL IP12 LXI D, BUFFER ; points to buffer CALL EXPR ; evaluate input CALL ENDCHK POP D ; ok, get old HL XCHG MOV M, E ; save value in var. INX H MOV M, D IP4: POP H ; get old 'CURRNT' SHLD CURRNT POP D ; and old text pointer IP5: POP PSW ; purge junk in stack IP6: tstc ',', IP7 ; is next char. ","? JMP INPUT ; yes, more items. IP7: JMP FINISH IP8: PUSH D ; save for 'PRTSTG' CALL TSTV ; must be variable now JNC IP11 IP10: JMP QWHAT ; "WHAT?" it is not? IP11: MOV B, E POP D CALL PRTCHS ; print those as prompt JMP IP3 ; yes, input variable IP12: POP B ; return address PUSH D ; save text pointer XCHG LHLD CURRNT ; also save 'CURRNT' PUSH H LXI H, IP1 ; a negative number SHLD CURRNT ; as a flag LXI H, 0 ; save SP too DAD SP SHLD STKINP PUSH D ; old HL MVI A, ' ' ; print a space PUSH B JMP GETLN ; and get a line ; DEFLT: LDAX D ; *** DEFLT *** CPI CR ; empty line is ok JZ LT4 ; else, it is 'LET' ; LET: ds 0 ; *** LET *** LT2: CALL SETVAL LT3: tstc ',', LT4 ; set value to var. JMP LET ; item by item LT4: JMP FINISH ; until finish ; ; --------------------------------------------------------------- ; ; *** EXPR *** ; ; 'EXPR' evaluates arithmetical or logical expressions. ; : : = ; ; where is one of the operators in TAB6 and the result ; of these operations is 1 if true and 0 if false. ; : : =(+ or -)(+ or -)(.....) ; where () are optional and (.....) are optional repeats. ; : : =(<* or />)(.....) ; : : = ; ; () ; is recursive so that variable '@' can have an ; as index. Functions can have an as arguments, ; and can be an in parenthese. ; EXPR: CALL EXPR1 ; *** EXPR *** PUSH H ; save value LXI H, TAB6-1 ; lookup REL.OP. JMP EXEC ; go do it XPR1: CALL XPR8 ; REL.OP.">=" RC ; no, return HL=0 MOV L, A ; yes, return HL=1 RET XPR2: CALL XPR8 ; REL.OP."#" RZ ; false, return HL=0 MOV L, A ; true, return HL=1 RET XPR3: CALL XPR8 ; REL.OP.">" RZ ; false RC ; also false, HL=0 MOV L, A ; true, HL=1 RET XPR4: CALL XPR8 ; REL.OP."<=" MOV L, A ; set HL=1 RZ ; REL.OP. true, return RC MOV L, H ; else, set HL=0 RET XPR5: CALL XPR8 ; REL.OP."=" RNZ ; false, return HL=0 MOV L, A ; else set HL=1 RET XPR6: CALL XPR8 ; REL.OP."<" RNC ; false, return HL=0 MOV L, A ; else set HL=1 RET XPR7: POP H ; not REL.OP. RET ; return HL= XPR8: MOV A, C ; subroutine for all POP H ; REL.OP.'s POP B PUSH H ; reverse top of stack PUSH B MOV C, A CALL EXPR1 ; set 2nd XCHG ; value in DE now XTHL ; 1st in HL CALL CKHLDE ; compare 1st with 2nd POP D ; restore text pointer LXI H, 0 ; set HL=0, A=1 MVI A, 1 RET ; EXPR1: tstc '-', XP11 ; negative sign? LXI H,0 ; yes, fake '0-' JMP XP16 ; treat like subtract XP11: tstc '+', XP12 ; positive sign? ignore XP12: CALL EXPR2 ; 1st XP13: tstc '+', XP15 ; add? PUSH H ; yes, save value CALL EXPR2 ; get 2nd XP14: XCHG ; 2nd in DE XTHL ; 1st in HL MOV A, H ; compare sign XRA D MOV A, D DAD D POP D ; restore text pointer JM XP13 ; 1st 2nd sign differ XRA H ; 1st 2nd sign equal JP XP13 ; so is equal JMP QHOW ; else, we have overflow XP15: tstc '-', XPR9 ; subtract? XP16: PUSH H ; yes, save 1st CALL EXPR2 ; get 2nd CALL CHGSGN ; negate JMP XP14 ; and add them ; EXPR2: CALL EXPR3 ; get 1st XP21: tstc '*', XP24 ; multiply? PUSH H ; yes, save 1st CALL EXPR3 ; and get 2nd MVI B, 0 ; clear B for sign CALL CHKSGN ; check sign XTHL ; 1st in HL CALL CHKSGN ; check sign of 1st XCHG XTHL MOV A, H ; is HL > 255 ? ORA A JZ XP22 ; no MOV A, D ; yes, how about DE ORA D XCHG ; put smaller in HL JNZ AHOW ; also >, will overflow XP22: MOV A, L ; this is dump LXI H, 0 ; clear result ORA A ; add and count JZ XP25 XP23: DAD D JC AHOW ; overflow DCR A JNZ XP23 JMP XP25 ; finished XP24: tstc '/', XPR9 ; divide? PUSH H ; yes, save 1st CALL EXPR3 ; and get 2nd one MVI B, 0 ; clear B for sign CALL CHKSGN ; check sign of 2nd XTHL ; get 1st in HL CALL CHKSGN ; check sign of 1st XCHG XTHL XCHG MOV A, D ; divide by 0? ORA E JZ AHOW ; say "HOW?" PUSH B ; else, save sign CALL DIVIDE ; use subroutine MOV H, B ; result in HL now MOV L, C POP B ; get sign back XP25: POP D ; and text pointer MOV A, H ; HL must be + ORA A JM QHOW ; else, it is overflow MOV A, B ORA A CM CHGSGN ; change sign if needed JMP XP21 ; look for more terms ; EXPR3: LXI H, TAB3-1 ; find function in TAB3 JMP EXEC ; and go do it NOTF: CALL TSTV ; no, not a function JC XP32 ; nor a variable MOV A, M ; variable INX H MOV H, M ; value in HL MOV L, A RET XP32: CALL TSTNUM ; or is it a number MOV A, B ; # of digit ORA A RNZ ; ok PARN: tstc '(', XPR0 ; no digit, must be PARNP: CALL EXPR ; "(EXPR)" tstc ')', XPR0 XPR9: RET XPR0: JMP QWHAT ; else, say: "WHAT?" ; RND: CALL PARN ; *** RND(EXPR) *** MOV A, H ; expr must be + ORA A JM QHOW ORA L ; and non-zero JZ QHOW PUSH D ; save both PUSH H LHLD RANPNT ; get memory as random LXI D, RANEND CALL COMP JC RA1 ; wrap around if last LXI H, BOTROM RA1: MOV E, M INX H MOV D, M SHLD RANPNT POP H XCHG PUSH B CALL DIVIDE ; RND(N)=MOD(M,N)+1 POP B POP D INX H RET ; ABS: CALL PARN ; *** ABS(EXPR) *** DCX D CALL CHKSGN ; check sign INX D RET ; SIZE: LHLD TXTUNF ; *** SIZE *** PUSH D ; get the number of free XCHG ; bytes between 'TXTUNF' LHLD TXTLMT ; and 'TXTLMT' CALL SUBDE POP D RET ; ; --------------------------------------------------------------- ; ; *** DIVIDE *** SUBDE *** CHKSGN *** CHGSGN *** & CKHLDE *** ; ; 'DIVIDE' divides HL by DE. Result in BC, remainder in HL. ; ; 'SUBDE' subtracts DE from HL. ; ; 'CHKSGN' checks sign of HL. If +, no change. If -, change sign ; and flip sign of B. ; ; 'CHGSGN' changes sign of HL and B unconditionnally. ; ; 'CKHLDE' checks sign of HL and DE. If different, HL and DE ; are interchanged. If same sign, not interchanged. Either case, ; HL DE are then compared to set the flags. ; DIVIDE: PUSH H ; *** DIVIDE *** MOV L, H ; divide H by DE MVI H, 0 CALL DV1 MOV B, C ; save result in B MOV A, L ; (remainder+L)/DE POP H MOV H, A DV1: MVI C, -1 ; result in C DV2: INR C ; dumb routine CALL SUBDE ; divide by subtract JNC DV2 ; and count DAD D RET ; SUBDE: MOV A, L ; *** SUBDE *** SUB E ; subtract DE from MOV L, A ; HL MOV A, H SBB D MOV H, A RET ; CHKSGN: MOV A, H ; *** CHKSGN *** ORA A ; check sign of HL RP ; if ), change sign ; CHGSGN: MOV A, H ; *** CHGSGN *** ORA L RZ MOV A, H PUSH PSW CMA ; change sign of HL MOV H, A MOV A, L CMA MOV L, A INX H POP PSW XRA H JP QHOW MOV A, B ; and also flip B XRI 80H MOV B, A RET ; CKHLDE: MOV A, H ; *** CKHLDE *** XRA D ; same sign? JP CK1 ; yes, compare XCHG ; no, xch and comp CK1: CALL COMP RET ; COMP: MOV A, H ; *** COMP *** CMP D ; compare HL with DE RNZ ; return correct C and MOV A, L ; Z flags CMP E ; but old A is lost RET ; ; --------------------------------------------------------------- ; ; *** SETVAL *** FIN *** ENDCHK *** & ERROR (& friends) *** ; ; 'SETVAL' expects a variable, followed by an equal sign and ; then an expr. It evaluates the expr. and set the variable ; to that value. ; ; 'FIN' checks the end of a command. If it ended with ";", ; execution continues. If it ended with a CR, it finds the next ; line and continue from there. ; ; 'ENDCHK' checks if a command is ended with CR. This is ; required in certain commands. (GOTO, RETURN, and STOP etc.) ; ; 'ERROR' prints the string pointed by DE (and ends with CR). ; It then prints the line pointed by 'CURRNT' with a "?" ; inserted at where the old text pointer (should be on top of ; the stack) points to. Execution of TB is stopped and PATB is ; restarted. Howewer, if 'CURRNT' -> zero (indicating a direct ; command), the direct command is not printed, and if 'CURRNT' ; -> negative # (indicating 'INPUT' command), the input line is ; not printed and execution is not terminated but continued at ; 'INPERR'. ; ; Related to 'ERROR' are the following: 'QWHAT' saves text ; pointer in stack and get message "WHAT?". 'AWHAT' just get ; message "WHAT?" and jump to 'ERROR'. 'QSORRY' and 'ASORRY' ; do same kind of thing. 'QHOW' and 'AHOW' in the zero page ; section also do this. ; SETVAL: CALL TSTV ; *** SETVAL *** JC QWHAT ; "WHAT?" no variable PUSH H ; push address of var. tstc '=', SV1 ; pass "=" sign CALL EXPR ; evaluate expr. MOV B, H ; value in BC now MOV C, L POP H ; get address MOV M, C ; save value INX H MOV M, B RET ; FINISH: CALL FIN ; check end of command SV1: JMP QWHAT ; print "WHAT?" if wrong ; FIN: tstc ';', FI1 ;*** FIN *** POP PSW ; ";", purge RET address JMP RUNSML ; continue same line FI1: tstc CR, FI2 ; not ";", is it CR? POP PSW ; yes, purge RET address JMP RUNNXL ; run next line FI2: RET ; else, return to caller ; IGNBLK: LDAX D ; *** IGNBLK *** CPI ' ' ; ignore blanks RNZ ; in text (where DE->) INX D ; and return the first JMP IGNBLK ; non-blank char. in A ; ENDCHK: CALL IGNBLK ; *** ENDCHK *** CPI CR ; end with CR? RZ ; ok, else say: "WHAT?" ; QWHAT: PUSH D ; *** QWHAT *** AWHAT: LXI D, WHAT ; *** AWHAT *** ERROR: CALL CRLF CALL PRTSTG ; print error message LHLD CURRNT ; get current line # PUSH H MOV A, M ; check the value INX H ORA M POP D JZ TELL ; if zero, just restart MOV A, M ; if negative ORA A JM INPERR ; redo input CALL PRTLN ; else print the line POP B MOV B, C CALL PRTCHS MVI A, '?' ; print a "?" CALL OUTCH CALL PRTSTG ; line JMP TELL ; then restart QSORRY: PUSH D ; *** QSORRY *** ASORRY: LXI D, SORRY ; *** ASORRY *** JMP ERROR ; ; --------------------------------------------------------------- ; ; *** FNDLN (& friends) *** ; ; 'FNDLN' finds a line with a given line # (in HL) in the text ; save area. DE is used as the text pointer. If the line is ; found, DE will point to the beginning of that line (i.e., the ; low byte of the line #), and flags are NC & Z. If that line is ; not there and a line with a higher line # is found, DE points ; to there and flags are NC & NZ. If we reached the end of text ; save area and cannot find the line, flags are C & NZ. 'FNDLN' ; will initialize DE to the beginning of the text save area to ; start the search. Some other entries of this routine will not ; initialize DE and do the search. 'FNDLP' will start with DE ; and search for the line #. 'FNDNXT' will bump DE by 2, find ; a CR and then start search. 'FNDSKP' use DE to find a CR, ; and then start search. ; FNDLN: MOV A, H ; *** FNDLN *** ORA A ; check sign of HL JM QHOW ; it cannot be - LXI D, TEXT ; init. text pointer ; FNDLP: INX D ; is it EOT mark? LDAX D DCX D ADD A RC ; C, NZ passed. end. LDAX D ; we did not, get byte 1 SUB L ; is this the line? MOV B, A ; compare low order INX D LDAX D ; get byte 2 SBB H ; compare high order JC FL1 ; no, not there yet DCX D ; else we either found ORA B ; it, or it is not there RET ; NC, Z=found; NC, NZ=no ; FNDNXT: INX D ; find next line FL1: INX D ; just passed byte 1 & 2 ; FNDSKP: LDAX D ; *** FNDSKP *** CPI CR ; try to find CR JNZ FL1 ; keep looking INX D ; found CR, skip over JMP FNDLP ; check if end of text ; TSTV: CALL IGNBLK ; *** TSTV *** SUI '@' ; test variables RC ; C=not a variable JNZ TV1 ; not "@" array INX D ; it is the "@" array CALL PARN ; @ should be followed DAD H ; by (EXPR) as its index JC QHOW ; is index too big? TSTB: PUSH D ; will it fit? XCHG CALL SIZE ; find size of free CALL COMP ; and check that JC ASORRY ; if not, say: "SORRY" CALL LOCR ; if fits, get address DAD D ; of @(EXPR) and put it POP D ; in HL RET ; C flag is cleared TV1: CPI 27 ; not @, is it A to Z? CMC ; if not return C flag RC INX D ; if A through Z LXI H, VARBGN-2 RLC ; HL->variable ADD L ; return MOV L, A ; with C flag cleared MVI A, 0 ADC H MOV H, A RET ; ; --------------------------------------------------------------- ; ; *** TSTCH *** TSTNUM *** ; ; 'TSTCH' is used to test non-blank character in the text ; (pointed by DE) against the character that follows the call. ; If they do not match, n bytes of code will be skipped over, ; where n is between 0 and 255 and is stored in the second byte ; following the call. ; ; 'TSTNUM' is used to chack wether the text (pointed by DE) is a ; number. If a number is found, B will be non-zero and HL will ; contain the value (in binary) of the number, else B and HL ; are 0. ; TSTCH: XTHL ; *** TSTCH *** CALL IGNBLK ; ignore leading blanks CMP M ; and test the character INX H ; compare the byte that JZ TC1 ; follows the call inst. PUSH B ; with the text (DE->) MOV C, M ; if not =, add the 2nd MVI B, 0 ; byte that follows the DAD B ; call to the old PC POP B ; i.e., do a relative DCX D ; jump if not = TC1: INX D ; if =, skip those bytes INX H ; and continue XTHL RET ; TSTNUM: LXI H, 0 ; *** TSTNUM *** MOV B, H ; test if the text is CALL IGNBLK ; a number TN1: CPI '0' ; if not, return 0 in RC ; B and HL CPI 03AH ; if numbers, convert RNC ; to binary in HL and MVI A, 0F0H ; set B to # of digits ANA H ; if H>255, there is no JNZ QHOW ; room for next digit INR B ; B counts # of digits PUSH B MOV B, H ; HL=10*HL+(new digit) MOV C, L DAD H ; where 10* is done by DAD H ; shift and add DAD B DAD H LDAX D ; and (digit) is from INX D ; stripping the ASCII ANI 0FH ; code ADD L MOV L, A MVI A, 0 ADC H MOV H, A POP B LDAX D ; do this digit after JP TN1 ; digit. S say overflow QHOW: PUSH D ; *** QHOW *** AHOW: LXI D, HOW ; *** AHOW *** JMP ERROR ; ; --------------------------------------------------------------- ; ; *** MVUP *** MVDOWN *** POPA *** & PUSHA *** ; ; 'MVUP' moves a block up from where DE-> to where BC-> until ; DE = HL. ; ; 'MVDOWN' moves a block down from where DE-> to where HL-> ; until DE = BC. ; ; 'POPA' restores the 'FOR' loop variable save area from the ; stack ; ; 'PUSHA' stacks the 'FOR' loop variable save area into the ; stack. ; MVUP: CALL COMP ; *** MVUP *** RZ ; DE = HL, return LDAX D ; get one byte STAX B ; move it INX D ; increase both pointers INX B JMP MVUP ; until done ; MVDOWN: MOV A, B ; *** MVDOWN *** SUB D ; test if DE = BC JNZ MD1 ; no, go move MOV A, C ; maybe, other byte? SUB E RZ ; yes, return MD1: DCX D ; else move a byte DCX H ; but first decrease LDAX D ; both pointers and MOV M, A ; then do it JMP MVDOWN ; loop back ; POPA: POP B ; BC = return address POP H ; restore LOPVAR, but SHLD LOPVAR ; =0 means no more MOV A, H ORA L JZ PP1 ; yep, go return POP H ; nop, restore others SHLD LOPINC POP H SHLD LOPLMT POP H SHLD LOPLN POP H SHLD LOPPT PP1: PUSH B ; BC = return address RET ; PUSHA: LXI H, STKLMT ; *** PUSHA *** CALL CHGSGN POP B ; BC = return address DAD SP ; is stack near the top? JNC QSORRY ; yes, sorry for that. LHLD LOPVAR ; esle, save loop var.s MOV A, H ; but if lopvar is 0 ORA L ; that will be all JZ PU1 LHLD LOPPT ; else, more to save PUSH H LHLD LOPLN PUSH H LHLD LOPLMT PUSH H LHLD LOPINC PUSH H LHLD LOPVAR PU1: PUSH H PUSH B ; BC = return address RET LOCR: LHLD TXTUNF DCX H DCX H RET ; ; --------------------------------------------------------------- ; ; *** PRTSTG *** *** QTSTG *** *** PRTNUM *** & PRTLN *** ; ; 'PRTSTG' prints a string pointed by DE. It stops printing and ; returns to caller when either a CR is printed or when the next ; byte is zero. Registers A and B are changed. Register DE ; points to what follows the CR or to the zero. ; ; 'QTSTG' looks for up-arrow, single quote, or double-quote. ; If none of these, return to caller. If up-arrow, output a ; control character. If single or double quote, print the ; string in the quote and demands a matching unquote. ; After the printing, the next 3 bytes of the caller is ; skipped over (usually a jump instruction). ; ; 'PRTNUM' prints the number in HL. Leading blanks are added ; if needed to pad the number of spaces to the number in C. ; Howewer, if the number of digits is larger than the number ; in C, all digits are printed anyway. Negative sign is also ; printed and counted in. Positive sign is not. ; ; 'PRTLN' finds a saved line, prints the line number and ; a space. ; PRTSTG: SUB A ; *** PRTSTG *** PS1: MOV B, A PS2: LDAX D ; get a character INX D ; bump pointer CMP B ; same as old A? RZ ; yes, return CALL OUTCH ; else, print it CPI CR ; was it a CR? JNZ PS2 ; no, next RET ; yes, return ; QTSTG: tstc '"', QT3 ; *** QTSTG *** MVI A, '"' ; it is a " (double quote) QT1: CALL PS1 ; print until another QT2: CPI CR ; was last one a CR? POP H ; return address JZ RUNNXL ; was CR, run next line INX H ; skip 3 bytes on return INX H INX H PCHL ; return QT3: tstc 27H, QT4 ; is it a ' (single quote) ? MVI A, 27H ; yes, do same JMP QT1 ; as in " QT4: tstc 5EH, QT5 ; is it an up-arrow? LDAX D ; yes, convert character XRI 40H ; to control-char. CALL OUTCH LDAX D ; just in case it is a CR INX D JMP QT2 QT5: RET ; none of the above PRTCHS: MOV A, E CMP B RZ LDAX D CALL OUTCH INX D JMP PRTCHS ; PRTNUM ds 0 ; *** PRTNUM *** PN3: MVI B, 0 ; B=sign CALL CHKSGN ; check sign JP PN4 ; no sign MVI B, '-' ; B=sign DCR C ; '-' takes space PN4: PUSH D LXI D, 10 ; decimal PUSH D ; save as a flag DCR C ; C=spaces PUSH B ; save sign & space PN5: CALL DIVIDE ; divide HL by 10 MOV A, B ; result O? ORA C JZ PN6 ; yes, we got all XTHL ; no, save remainder DCR L ; and count space PUSH H ; HL is old BC MOV H, B ; move result to BC MOV L, C JMP PN5 ; and divide by 10 PN6: POP B ; we got all digits in PN7: DCR C ; the stack MOV A, C ; look at space count ORA A JM PN8 ; no leading blanks MVI A, ' ' ; leading blanks CALL OUTCH JMP PN7 ; more? PN8: MOV A, B ; print sign? ORA A CNZ OUTCH ; maybe - or null MOV E, L ; last remainder in E PN9: MOV A, E ; check digit in E CPI 10 ; 10 is flag for no more POP D RZ ; if so, return ADI '0' ; else, convert to ASCII CALL OUTCH ; and print the digit JMP PN9 ; go back for more ; PRTLN: LDAX D ; *** PRTLN *** MOV L, A ; low order line # INX D LDAX D ; high order MOV H, A INX D MVI C, 4 ; print 4 digit line # CALL PRTNUM MVI A, ' ' ; followed by a blank CALL OUTCH RET ; TAB1: item 'LIST', list ; direct commands item 'NEW', new item 'RUN', run item 'LOAD', load item 'MON', mon ; TAB2: item 'NEXT', next ; direct/statement item 'LET', let item 'IF', iff item 'GOTO', goto item 'GOSUB',gosub item 'RETURN',return item 'REM', rem item 'FOR', for item 'INPUT',input item 'PRINT',print item 'STOP', stop item , morec ; ************************ MOREC: JMP DEFLT ; *** JMP USER-COMMAND *** ; ************************ TAB3: item 'RND', rnd ; functions item 'ABS', abs item 'SIZE', size item , moref ; ************************* MOREF: JMP NOTF ; *** JMP USER-FUNCTION *** ; ************************* TAB4: item 'TO', FR1 ; "FOR" command item , QWHAT ; TAB5: item 'STEP', FR2 ; "FOR" command item , FR3 ; TAB6: item '>=', XPR1 ; relation operators item '#', XPR2 item '>', XPR3 item '=', XPR5 item '<=', XPR4 item '<', XPR6 item , XPR7 ; RANEND EQU $ ; ; PATB original code> ; --------------------------------------------------------------- ; ; *** INPUT OUTPUT ROUTINES *** ; ; User must verify and/or modify these routines ; ; --------------------------------------------------------------- ; ; *** CRLF *** OUTCH *** ; ; 'CRLF' will output a CR. Only A & flags may change at return. ; ; 'OUTCH' will output the character in A. If the character is CR, ; it will also outut a LF and three nulls. Flags may change at ; return. Others registers do not. ; ; *** CHKIO *** GETLN *** ; ; 'CHKIO' checks to see if there is any input. If no input, ; it returns with Z flag. If there is input, it further checks ; wether input is Control-C. If not Control-C, it returns the ; character in A with Z flag cleared. If input is Control-C, ; 'CHKIO' jumps to 'INIT' and will not return. Only A & flags ; may change at return. ; ; 'GETLN' reads a input line into 'BUFFER'. It first prompt the ; character in A (given by the caller), then it fills the buffer ; and echos. Back-space is used to delete the last character ; (if there is one). CR signals the end of the line, and cause ; 'GETLN' to return. When buffer is full, 'GETLN' will accept ; back-space or CR only and will ignore (and will not echo) ; other characters. After the input line is stored in the buffer ; two more bytes of FF are also stored and DE points to the ; last FF. A & flags are also changed at return. ; ; INPUT LINE to BUFFER ; ctrl-U -> cancel line ; BS -> back 1 char ; DEL -> back 1 char ; CR -> end of input ; other control -> ignore GL3: MVI A, CR CALL OUTCH MVI A, '#' ;; *********************** GETLN: LXI D, BUFFER ; *** MODIFY THIS ******* ;; *********************** GL1: CALL OUTCH ; prompt or echo GL2: CALL CHKIO ; get a character JRZ GL2 ; wait for input CPI 15H ; ctrl-U JRZ GL3 STAX D CPI 08H ; is it Back-Space? JRNZ GL4 ; no, more tests MOV A, E ; yes, delete? CPI LOW BUFFER JRZ GL2 ; nothing to delete LDAX D DCX D JR GL1 GL4: CPI CR ; was it CR? JRZ GL5 ; yes, end of line CPI 20H JRC GL2 MOV A, E ; else, more free room? CPI LOW BUFEND JRZ GL2 ; no, wait for CR/Rub-Out LDAX D ; yes, bump pointer INX D JR GL1 GL5: INX D ; end of line INX D ; bump pointer MVI A, 0FFH ; put marker after it STAX D DCX D CRLF: MVI A, CR ; CR in A OUTCH: JMP RST3 CHKIO: JMP RST2 ; ------------------------------- ; use MONITOR MON: CALL ENDCHK CALL MONITOR ; MONITOR JMP TELL LOAD: CALL ENDCHK LXI H, 1800H LXI D, BOTRAM LXI B, 1800H LDIR JMP TELL END