URL
https://opencores.org/ocsvn/rtf65002/rtf65002/trunk
Subversion Repositories rtf65002
[/] [rtf65002/] [trunk/] [software/] [asm/] [TinyBasic65002.asm] - Rev 39
Compare with Previous | Blame | View Log
;****************************************************************;; ;; Tiny BASIC for the Raptor64 ;; ;; Derived from a 68000 derivative of Palo Alto Tiny BASIC as ;; published in the May 1976 issue of Dr. Dobb's Journal. ;; Adapted to the 68000 by: ;; Gordon brndly ;; 12147 - 51 Street ;; Edmonton AB T5W 3G8 ;; Canada ;; (updated mailing address for 1996) ;; ;; Adapted to the RTF65002 by: ;; Robert Finch ;; Ontario, Canada ;; robfinch<remove>@opencores.org ;;****************************************************************;; Copyright (C) 2012 by Robert Finch. This program may be ;; freely distributed for personal use only. All commercial ;; rights are reserved. ;;****************************************************************;;; Register Usage; r8 = text pointer (global usage); r3,r4 = inputs parameters to subroutines; r2 = return value;;* Vers. 1.0 1984/7/17 - Original version by Gordon brndly;* 1.1 1984/12/9 - Addition of '0x' print term by Marvin Lipford;* 1.2 1985/4/9 - Bug fix in multiply routine by Rick Murray;; Standard jump table. You can change these addresses if you are; customizing this interpreter for a different environment.;CR EQU 0x0D ;ASCII equatesLF EQU 0x0ATAB EQU 0x09CTRLC EQU 0x03CTRLH EQU 0x08CTRLI EQU 0x09CTRLJ EQU 0x0ACTRLK EQU 0x0BCTRLM EQU 0x0DCTRLS EQU 0x13CTRLX EQU 0x18XON EQU 0x11XOFF EQU 0x13CursorFlash EQU 0x7C4IRQFlag EQU 0x7C6OUTPTR EQU 0x778INPPTR EQU 0x779FILENAME EQU 0x6C0FILEBUF EQU 0x01F60000OSSP EQU 0x700TXTUNF EQU 0x701VARBGN EQU 0x702LOPVAR EQU 0x703STKGOS EQU 0x704CURRNT EQU 0x705BUFFER EQU 0x706BUFLEN EQU 84LOPPT EQU 0x760LOPLN EQU 0x761LOPINC EQU 0x762LOPLMT EQU 0x763NUMWKA EQU 0x764STKINP EQU 0x774STKBOT EQU 0x775usrJmp EQU 0x776IRQROUT EQU 0x777cpu rtf65002codeorg $FFFFEC80GOSTART:jmp CSTART ; Cold Start entry pointGOWARM:jmp WSTART ; Warm Start entry pointGOOUT:jmp OUTC ; Jump to character-out routineGOIN:jmp INCH ;Jump to character-in routineGOAUXO:jmp AUXOUT ; Jump to auxiliary-out routineGOAUXI:jmp AUXIN ; Jump to auxiliary-in routineGOBYE:jmp BYEBYE ; Jump to monitor, DOS, etc.;; Modifiable system constants:;align 4;THRD_AREA dw 0x04000000 ; threading switch area 0x04000000-0x40FFFFF;bitmap dw 0x00100000 ; bitmap graphics memory 0x04100000-0x417FFFFTXTBGN dw 0x01800000 ;TXT ;beginning of program memoryENDMEM dw 0x018EFFFF ; end of available memorySTACKOFFS dw 0x018FFFFF ; stack offset - leave a little room for the BIOS stacks;; The main interpreter starts here:;; Usage; r1 = temp; r8 = text buffer pointer; r12 = end of text in text buffer;align 4message "CSTART"public CSTART:; First save off the link register and OS sp valuetsxstx OSSPldx STACKOFFS>>2 ; initialize stack pointertxsjsr RequestIOFocusjsr HomeCursorlda #0 ; turn off keyboard echoingjsr SetKeyboardEchostz CursorFlashldx #0x10000020 ; black chars, yellow background; stx charToPrintjsr ClearScreenlda #msgInit ; tell who we arejsr PRMESGlda TXTBGN>>2 ; init. end-of-program pointersta TXTUNFlda ENDMEM>>2 ; get address of end of memorysub #4096 ; reserve 4K for the stacksta STKBOTsub #16384 ; 1000 varssta VARBGNjsr clearVars ; clear the variable areastz IRQROUTlda VARBGN ; calculate number of bytes freeldy TXTUNFsub r1,r1,r3ldx #12 ; max 12 digitsjsr PRTNUMlda #msgBytesFreejsr PRMESGWSTART:stz LOPVAR ; initialize internal variablesstz STKGOSstz CURRNT ; current line number pointer = 0ldx ENDMEM>>2 ; init S.P. again, just in casetxslda #msgReady ; display "Ready"jsr PRMESGST3:lda #'>' ; Prompt with a '>' andjsr GETLN ; read a line.jsr TOUPBUF ; convert to upper caseld r12,r8 ; save pointer to end of lineld r8,#BUFFER ; point to the beginning of linejsr TSTNUM ; is there a number there?jsr IGNBLK ; skip trailing blanks; does line no. exist? (or nonzero?)cpx #0beq DIRECT ; if not, it's a direct statementcmp #$FFFF ; see if line no. is <= 16 bitsbcc ST2beq ST2lda #msgLineRange ; if not, we've overflowedjmp ERRORST2:; ugliness - store a character at potentially an; odd address (unaligned).tax ; r2 = line numberdec r8stx (r8) ;jsr FNDLN ; find this line in save areald r13,r9 ; save possible line pointercmp #0beq ST4 ; if not found, insert; here we found the line, so we're replacing the line; in the text area; first step - delete the linelda #0jsr FNDNXT ; find the next line (into r9)cmp #0bne ST7cmp r9,TXTUNFbeq ST6 ; no more linesbcs ST6cmp r9,r0beq ST6ST7:ld r1,r9 ; r1 = pointer to next lineld r2,r13 ; pointer to line to be deletedldy TXTUNF ; points to top of save areasub r1,r3,r9 ; r1 = length to move TXTUNF-pointer to next line; dea ; count is one lessld r2,r9 ; r2 = pointer to next lineld r3,r13 ; r3 = pointer to line to deletepush r4ST8:ld r4,(x)st r4,(y)inxinydeabne ST8pop r4; mvn; jsr MVUP ; move up to deletesty TXTUNF ; update the end pointer; we moved the lines of text after the line being; deleted down, so the pointer to the next line; needs to be resetld r9,r13bra ST4; here there were no more lines, so just move the; end of text pointer downST6:st r13,TXTUNFld r9,r13ST4:; here we're inserting because the line wasn't found; or it was deleted from the text areasub r1,r12,r8 ; calculate the length of new linecmp #2 ; is it just a line no. & CR? if so, it was just a deletebeq ST3bcc ST3; compute new end of textld r10,TXTUNF ; r10 = old TXTUNFadd r11,r10,r1 ; r11 = new top of TXTUNF (r1=line length)cmp r11,VARBGN ; see if there's enough roombcc ST5lda #msgTooBig ; if not, say sojmp ERROR; open a space in the text areaST5:st r11,TXTUNF ; if so, store new end positionld r1,r10 ; points to old end of textld r2,r11 ; points to new end of textld r3,r9 ; points to start of line after insert linejsr MVDOWN ; move things out of the way; copy line into text spaceld r1,r8 ; set up to do the insertion; move from bufferld r2,r13 ; to vacated spaceld r3,r12 ; until end of bufferjsr MVUP ; do itjmp ST3 ; go back and get another line;******************************************************************;; *** Tables *** DIRECT *** EXEC ***;; This section of the code tests a string against a table. When; a match is found, control is transferred to the section of; code according to the table.;; At 'EXEC', r8 should point to the string, r9 should point to; the character table, and r10 should point to the execution; table. At 'DIRECT', r8 should point to the string, r9 and; r10 will be set up to point to TAB1 and TAB1_1, which are; the tables 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'.;; There are two tables: the character table and the execution; table. The character table consists of any number of text items.; Each item is a string of characters with the last character's; high bit set to one. The execution table holds a 32-bit; execution addresses that correspond to each entry in the; character table.;; The end of the character table is a 0 byte which corresponds; to the default routine in the execution table, which is; executed if none of the other table items are matched.;; Character-matching tables:message "TAB1"TAB1:db "LIS",'T'+0x80 ; Direct commandsdb "LOA",'D'+0x80db "NE",'W'+0x80db "RU",'N'+0x80db "SAV",'E'+0x80TAB2:db "NEX",'T'+0x80 ; Direct / statementdb "LE",'T'+0x80db "I",'F'+0x80db "GOT",'O'+0x80db "GOSU",'B'+0x80db "RETUR",'N'+0x80db "RE",'M'+0x80db "FO",'R'+0x80db "INPU",'T'+0x80db "PRIN",'T'+0x80db "POK",'E'+0x80db "STO",'P'+0x80db "BY",'E'+0x80db "SY",'S'+0x80db "CL",'S'+0x80db "CL",'R'+0x80db "RDC",'F'+0x80db "ONIR",'Q'+0x80db "WAI",'T'+0x80db 0TAB4:db "PEE",'K'+0x80 ;Functionsdb "RN",'D'+0x80db "AB",'S'+0x80db "SG",'N'+0x80db "TIC",'K'+0x80db "SIZ",'E'+0x80db "US",'R'+0x80db 0TAB5:db "T",'O'+0x80 ;"TO" in "FOR"db 0TAB6:db "STE",'P'+0x80 ;"STEP" in "FOR"db 0TAB8:db '>','='+0x80 ;Relational operatorsdb '<','>'+0x80db '>'+0x80db '='+0x80db '<','='+0x80db '<'+0x80db 0TAB9:db "AN",'D'+0x80db 0TAB10:db "O",'R'+0x80db 0;* Execution address tables:; We save some bytes by specifiying only the low order 16 bits of the address;TAB1_1:dh LISTX ;Direct commandsdh LOAD3dh NEWdh RUNdh SAVE3TAB2_1:dh NEXT ; Direct / statementdh LETdh IFdh GOTOdh GOSUBdh RETURNdh IF2 ; REMdh FORdh INPUTdh PRINTdh POKEdh STOPdh GOBYEdh SYSXdh _clsdh _clrdh _rdcfdh ONIRQdh WAITIRQdh DEFLTTAB4_1:dh PEEK ;Functionsdh RNDdh ABSdh SGNdh TICKXdh SIZEXdh USRXdh XP40TAB5_1dh FR1 ;"TO" in "FOR"dh QWHATTAB6_1dh FR2 ;"STEP" in "FOR"dh FR3TAB8_1dh XP11 ;>= Relational operatorsdh XP12 ;<>dh XP13 ;>dh XP15 ;=dh XP14 ;<=dh XP16 ;<dh XP17TAB9_1dh XP_ANDdh XP_ANDXTAB10_1dh XP_ORdh XP_ORX;*; r3 = match flag (trashed); r9 = text table; r10 = exec table; r11 = trashedmessage "DIRECT"DIRECT:ld r9,#TAB1ld r10,#TAB1_1EXEC:jsr IGNBLK ; ignore leading blanksld r11,r8 ; save the pointereor r3,r3,r3 ; clear match flagEXLP:lda (r8) ; get the program characterinc r8lb r2,$0,r9 ; get the table characterbne EXNGO ; If end of table,ld r8,r11 ; restore the text pointer and...bra EXGO ; execute the default.EXNGO:cmp r1,r3 ; Else check for period... if so, executebeq EXGOand r2,r2,#0x7f ; ignore the table's high bitcmp r2,r1 ; is there a match?beq EXMATinc r10 ;if not, try the next entryinc r10ld r8,r11 ; reset the program pointereor r3,r3,r3 ; sorry, no matchEX1:lb r1,0,r9 ; get to the end of the entryinc r9bit #$80 ; test for bit 7 setbeq EX1bra EXLP ; back for more matchingEXMAT:ldy #'.' ; we've got a match so farlb r1,0,r9 ; end of table entry?inc r9bit #$80 ; test for bit 7 setbeq EXLP ; if not, go back for moreEXGO:; execute the appropriate routinelb r1,1,r10 ; get the low mid order byteasl r1,r1,#8orb r1,r1,0,r10 ; get the low order byteor r1,r1,#$FFFF0000 ; add in ROM basejmp (r1);******************************************************************;; What follows is the code to execute direct and statement; commands. Control is transferred to these points via the command; table lookup code of 'DIRECT' and 'EXEC' in the last section.; After the command is executed, control is transferred to other; sections as follows:;; For 'LISTX', 'NEW', and 'STOP': go back to the warm start point.; For 'RUN': go execute the first stored line if any; else go; back to the warm start point.; For 'GOTO' and 'GOSUB': go execute the target line.; For 'RETURN' and 'NEXT'; go back to saved return line.; For all others: if 'CURRNT' is 0, go to warm start; else go; execute next command. (This is done in 'FINISH'.);;******************************************************************;; *** NEW *** STOP *** RUN (& friends) *** GOTO ***;; 'NEW<CR>' sets TXTUNF to point to TXTBGN;NEW:jsr ENDCHKlda TXTBGN>>2sta TXTUNF ; set the end pointerjsr clearVars; 'STOP<CR>' goes back to WSTART;STOP:jsr ENDCHKjmp WSTART ; WSTART will reset the stack; 'RUN<CR>' finds the first stored line, stores its address; in CURRNT, and starts executing it. Note that only those; commands in TAB2 are legal for a stored program.;; There are 3 more entries in 'RUN':; 'RUNNXL' finds next line, stores it's address and executes it.; 'RUNTSL' stores the address of this line and executes it.; 'RUNSML' continues the execution on same line.;RUN:jsr ENDCHKld r8,TXTBGN>>2 ; set pointer to beginningst r8,CURRNTjsr clearVarsRUNNXL ; RUN <next line>lda CURRNT ; executing a program?beq WSTART ; if not, we've finished a direct stat.lda IRQROUT ; are we handling IRQ's ?beq RUN1ld r0,IRQFlag ; was there an IRQ ?beq RUN1stz IRQFlagjsr PUSHA_ ; the same code as a GOSUBpush r8lda CURRNTpha ; found it, save old 'CURRNT'...lda STKGOSpha ; and 'STKGOS'stz LOPVAR ; load new valuestsxstx STKGOSld r9,IRQROUTbra RUNTSLRUN1lda #0 ; else find the next line numberld r9,r8jsr FNDLNP ; search for the next line; cmp #0; bne RUNTSLcmp r9,TXTUNF; if we've fallen off the end, stopbeq WSTARTbcs WSTARTRUNTSL ; RUN <this line>st r9,CURRNT ; set CURRNT to point to the line no.add r8,r9,#1 ; set the text pointer toRUNSML ; RUN <same line>jsr CHKIO ; see if a control-C was pressedld r9,#TAB2 ; find command in TAB2ld r10,#TAB2_1jmp EXEC ; and execute it; 'GOTO expr<CR>' evaluates the expression, finds the target; line, and jumps to 'RUNTSL' to do it.;GOTOjsr OREXPR ;evaluate the following expression; jsr DisplayWordld r5,r1jsr ENDCHK ;must find end of lineld r1,r5jsr FNDLN ; find the target linecmp #0bne RUNTSL ; go do itlda #msgBadGotoGosubjmp ERROR ; no such line no._clr:jsr clearVarsjmp FINISH; Clear the variable area of memoryclearVars:push r6ld r6,#2048 ; number of words to clearlda VARBGNcv1:stz (r1)inadec r6bne cv1pop r6rts;******************************************************************; ONIRQ <line number>; ONIRQ sets up an interrupt handler which acts like a specialized; subroutine call. ONIRQ is coded like a GOTO that never executes.;******************************************************************;ONIRQ:jsr OREXPR ;evaluate the following expressionld r5,r1jsr ENDCHK ;must find end of lineld r1,r5jsr FNDLN ; find the target linecmp #0bne ONIRQ1stz IRQROUTjmp FINISHONIRQ1:st r9,IRQROUTjmp FINISHWAITIRQ:jsr CHKIO ; see if a control-C was pressedld r0,IRQFlagbeq WAITIRQjmp FINISH;******************************************************************; LIST;; LISTX has two forms:; 'LIST<CR>' lists all saved lines; 'LIST #<CR>' starts listing at the line #; Control-S pauses the listing, control-C stops it.;******************************************************************;LISTX:jsr TSTNUM ; see if there's a line no.ld r5,r1jsr ENDCHK ; if not, we get a zerold r1,r5jsr FNDLN ; find this or next lineLS1:cmp #0bne LS4cmp r9,TXTUNFbeq WSTARTbcs WSTART ; warm start if we passed the endLS4:ld r1,r9jsr PRTLN ; print the lineld r9,r1 ; set pointer for nextjsr CHKIO ; check for listing halt requestcmp #0beq LS3cmp #CTRLS ; pause the listing?bne LS3LS2:jsr CHKIO ; if so, wait for another keypresscmp #0beq LS2LS3:lda #0jsr FNDLNP ; find the next linebra LS1;******************************************************************; PRINT command is 'PRINT ....:' or 'PRINT ....<CR>'; where '....' is a list of expressions, formats, back-arrows,; and strings. These items a separated by commas.;; A format is a pound sign followed by a number. It controls; the number of spaces the value of an expression is going to; be printed in. It stays effective for the rest of the print; command unless changed by another format. If no format is; specified, 11 positions will be used.;; A string is quoted in a pair of single- or double-quotes.;; An underline (back-arrow) means generate a <CR> without a <LF>;; A <CR LF> is generated after the entire list has been printed; or if the list is empty. If the list ends with a semicolon,; however, no <CR LF> is generated.;******************************************************************;PRINT:ld r5,#11 ; D4 = number of print spacesldy #':'ld r4,#PR2jsr TSTC ; if null list and ":"jsr CRLF ; give CR-LF and continuejmp RUNSML ; execution on the same linePR2:ldy #CRld r4,#PR0jsr TSTC ;if null list and <CR>jsr CRLF ;also give CR-LF andjmp RUNNXL ;execute the next linePR0:ldy #'#'ld r4,#PR1jsr TSTC ;else is it a format?jsr OREXPR ; yes, evaluate expressionld r5,r1 ; and save it as print widthbra PR3 ; look for more to printPR1:ldy #'$'ld r4,#PR4jsr TSTC ; is character expression? (MRL)jsr OREXPR ; yep. Evaluate expression (MRL)jsr GOOUT ; print low byte (MRL)bra PR3 ;look for more. (MRL)PR4:jsr QTSTG ; is it a string?; the following branch must occupy only two bytes!bra PR8 ; if not, must be an expressionPR3:ldy #','ld r4,#PR6jsr TSTC ; if ",", go find nextjsr FIN ;in the list.bra PR0PR6:jsr CRLF ;list ends herejmp FINISHPR8:jsr OREXPR ; evaluate the expressionld r2,r5 ; set the widthjsr PRTNUM ; print its valuebra PR3 ; more to print?FINISH:jsr FIN ; Check end of commandjmp QWHAT ; print "What?" if wrong;*******************************************************************;; *** GOSUB *** & RETURN ***;; 'GOSUB expr:' or 'GOSUB expr<CR>' is like the 'GOTO' command,; except that the current text pointer, stack pointer, etc. are; saved so that execution can be continued after the subroutine; 'RETURN's. 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 on the stack.; If we are in the main routine, 'STKGOS' is zero (this was done; in the initialization section of the interpreter), but we still; save it as a flag for no further 'RETURN's.;******************************************************************;GOSUB:jsr PUSHA_ ; save the current 'FOR' parametersjsr OREXPR ; get line numberjsr FNDLN ; find the target linecmp #0bne gosub1lda #msgBadGotoGosubjmp ERROR ; if not there, say "How?"gosub1:push r8lda CURRNTpha ; found it, save old 'CURRNT'...lda STKGOSpha ; and 'STKGOS'stz LOPVAR ; load new valuestsxstx STKGOSjmp RUNTSL;******************************************************************; 'RETURN<CR>' undoes everything that 'GOSUB' did, and thus; returns 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.;******************************************************************;RETURN:jsr ENDCHK ; there should be just a <CR>ldx STKGOS ; get old stack pointerbne return1lda #msgRetWoGosubjmp ERROR ; if zero, it doesn't existreturn1:txs ; else restore itplasta STKGOS ; and the old 'STKGOS'plasta CURRNT ; and the old 'CURRNT'pop r8 ; and the old text pointerjsr POPA_ ;and the old 'FOR' parametersjmp FINISH ;and we are back home;******************************************************************; *** FOR *** & NEXT ***;; 'FOR' has two forms:; 'FOR var=exp1 TO exp2 STEP exp1' and 'FOR var=exp1 TO exp2'; The second form means the same thing as the first form with a; STEP of positive 1. The interpreter will find the variable 'var'; and set its value to the current value of 'exp1'. It also; evaluates 'exp2' and 'exp1' and saves 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 something in the save area (indicated by a non-zero; 'LOPVAR'), then the old save area is saved on the stack before; the new values are stored. The interpreter 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. (i.e. purged from the stack);******************************************************************;FOR:jsr PUSHA_ ; save the old 'FOR' save areajsr SETVAL ; set the control variablesta LOPVAR ; save its addressld r9,#TAB5ld r10,#TAB5_1 ; use 'EXEC' to test for 'TO'jmp EXECFR1:jsr OREXPR ; evaluate the limitsta LOPLMT ; save thatld r9,#TAB6ld r10,#TAB6_1 ; use 'EXEC' to test for the word 'STEPjmp EXECFR2:jsr OREXPR ; found it, get the step valuebra FR4FR3:lda #1 ; not found, step defaults to 1FR4:sta LOPINC ; save that tooFR5:ldx CURRNTstx LOPLN ; save address of current line numberst r8,LOPPT ; and text pointertsxtxy ; dig into the stack to find 'LOPVAR'ld r6,LOPVARbra FR7FR6:add r3,r3,#5 ; look at next stack frameFR7:ldx (y) ; is it zero?beq FR8 ; if so, we're donecmp r2,r6bne FR6 ; same as current LOPVAR? nope, look some moretya ; Else remove 5 long words from...add r2,r3,#5 ; inside the stack.tsxtxyjsr MVDOWNpla ; set the SP 5 long words upplaplaplaplaFR8:jmp FINISH ; and continue execution;******************************************************************; 'NEXT var' serves as the logical (not necessarily physical) end; of the 'FOR' loop. The control variable 'var' is checked with; the 'LOPVAR'. If they are not the same, the interpreter digs in; the stack to find the right one and purges all those that didn't; match. Either way, it then adds the 'STEP' to that variable and; checks the result with against the limit value. If it is within; the limit, control loops back to the command following the; 'FOR'. If it's outside the limit, the save area is purged and; execution continues.;******************************************************************;NEXT:lda #0 ; don't allocate itjsr TSTV ; get address of variablecmp #0bne NX4lda #msgNextVarbra ERROR ; if no variable, say "What?"NX4:ld r9,r1 ; save variable's addressNX0:lda LOPVAR ; If 'LOPVAR' is zero, we never...bne NX5 ; had a FOR looplda #msgNextForbra ERRORNX5:cmp r1,r9beq NX2 ; else we check them OK, they agreejsr POPA_ ; nope, let's see the next framebra NX0NX2:lda (r9) ; get control variable's valueldx LOPINCadd r1,r1,r2 ; add in loop increment; BVS.L QHOW say "How?" for 32-bit overflowsta (r9) ; save control variable's new valueldy LOPLMT ; get loop's limit valuecmp r2,#1beq NX1bpl NX1 ; check loop increment, branch if loop increment is positivecmp r1,r3beq NX3bmi NXPurge ; test against limitbra NX3NX1:cmp r1,r3beq NX3bpl NXPurgeNX3:ld r8,LOPLN ; Within limit, go back to the...st r8,CURRNTld r8,LOPPT ; saved 'CURRNT' and text pointer.jmp FINISHNXPurge:jsr POPA_ ; purge this loopjmp FINISH;******************************************************************; *** REM *** IF *** INPUT *** LET (& DEFLT) ***;; 'REM' can be followed by anything and is ignored by the; interpreter.;;REM; br IF2 ; skip the rest of the line; 'IF' is followed by an expression, as a condition and one or; more commands (including other 'IF's) separated by colons.; Note that the word 'THEN' is not used. The interpreter evaluates; the expression. If it is non-zero, execution continues. If it; is zero, the commands that follow are ignored and execution; continues on the next line.;******************************************************************;IF:jsr OREXPR ; evaluate the expressionIF1:cmp #0bne RUNSML ; is it zero? if not, continueIF2:ld r9,r8 ; set lookup pointerlda #0 ; find line #0 (impossible)jsr FNDSKP ; if so, skip the rest of the linecmp #0bcs WSTART ; if no next line, do a warm startIF3:jmp RUNTSL ; run the next line;******************************************************************; INPUT is called first and establishes a stack frameINPERR:ldx STKINP ; restore the old stack pointertxsplasta CURRNT ; and old 'CURRNT'pop r8 ; and old text pointertsxadd r2,r2,#5 ; fall through will subtract 5txs; 'INPUT' 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 underline (back 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 the interpreter waits for; an expression to be typed in. The variable is then set to the; value of this expression. If the variable is preceeded by a; string (again in single or double quotes), the string will be; displayed followed by a colon. The interpreter the waits for an; expression to be entered and sets the variable equal to the; expression's value. If the input expression is invalid, the; interpreter will print "What?", "How?", or "Sorry" and reprint; the prompt and redo the input. The execution will not terminate; unless you press control-C. This is handled in 'INPERR'.;INPUT:push r7tsr sp,r7sub r7,r7,#5 ; allocate five words on stacktrs r7,spst r5,4,r7 ; save off r5 into stack varIP6:st r8,(r7) ; save in case of errorjsr QTSTG ; is next item a string?bra IP2 ; nope - this branch must take only two byteslda #1 ; allocate varjsr TSTV ; yes, but is it followed by a variable?cmp #0beq IP4 ; if not, brnchor r10,r1,r0 ; put away the variable's addressbra IP3 ; if so, input to variableIP2:st r8,1,r7 ; save off in stack var for 'PRTSTG'lda #1jsr TSTV ; must be a variable nowcmp #0bne IP7lda #msgInputVaradd r7,r7,#5 ; cleanup stacktrs r7,sppop r7 ; so we can get back r7bra ERROR ; "What?" it isn't?IP7:or r10,r1,r0 ; put away the variable's addressld r5,(r8) ; get ready for 'PRTSTG' by null terminatingstz (r8)lda 1,r7 ; get back text pointerjsr PRTSTG ; print string as promptst r5,(r8) ; un-null terminateIP3st r8,1,r7 ; save in case of errorlda CURRNTsta 2,r7 ; also save 'CURRNT'lda #-1sta CURRNT ; flag that we are in INPUTstx STKINP ; save the stack pointer toost r10,3,r7 ; save the variable addresslda #':' ; print a colon firstjsr GETLN ; then get an input lineld r8,#BUFFER ; point to the bufferjsr OREXPR ; evaluate the inputld r10,3,r7 ; restore the variable addresssta (r10) ; save value in variablelda 2,r7 ; restore old 'CURRNT'sta CURRNTld r8,1,r7 ; and the old text pointerIP4:ldy #','ld r4,#IP5 ; is the next thing a comma?jsr TSTCbra IP6 ; yes, more itemsIP5:ld r5,4,r7add r7,r7,#5 ; cleanup stacktrs r7,sppop r7jmp FINISHDEFLT:lda (r8)cmp #CRbeq FINISH ; empty line is OK else it is 'LET';******************************************************************; 'LET' is followed by a list of items separated by commas.; Each item consists of a variable, an equals sign, and an; expression. The interpreter evaluates the expression and sets; the variable to that value. The interpreter will also handle; 'LET' commands without the word 'LET'. This is done by 'DEFLT'.;******************************************************************;LET:jsr SETVAL ; do the assignmentldy #','ld r4,#FINISHjsr TSTC ; check for more 'LET' itemsbra LETLT1:jmp FINISH ; until we are finished.;******************************************************************; *** LOAD *** & SAVE ***;; These two commands transfer a program to/from an auxiliary; device such as a cassette, another computer, etc. The program; is converted to an easily-stored format: each line starts with; a colon, the line no. as 4 hex digits, and the rest of the line.; At the end, a line starting with an '@' sign is sent. This; format can be read back with a minimum of processing time by; the RTF65002;******************************************************************;LOADld r8,TXTBGN>>2 ; set pointer to start of prog. arealda #CR ; For a CP/M host, tell it we're ready...jsr GOAUXO ; by sending a CR to finish PIP command.LOD1:jsr GOAUXI ; look for start of linecmp #0beq LOD1bcc LOD1cmp #'@' ; end of program?beq LODENDcmp #$1Abeq LODEND ; or EOF markercmp #':'bne LOD1 ; if not, is it start of line? if not, wait for itjsr GCHAR ; get line numbersta (r8) ; store itinc r8LOD2:jsr GOAUXI ; get another text char.cmp #0beq LOD2bcc LOD2sta (r8)inc r8 ; store itcmp #CRbne LOD2 ; is it the end of the line? if not, go back for morebra LOD1 ; if so, start a new lineLODEND:st r8,TXTUNF ; set end-of program pointerjmp WSTART ; back to direct mode; get character from input (32 bit value)GCHAR:push r5push r6ld r6,#8 ; repeat eight timesld r5,#0GCHAR1:jsr GOAUXI ; get a charcmp #0beq GCHAR1bcc GCHAR1jsr asciiToHexasl r5,r5,#4or r5,r5,r1dec r6bne GCHAR1ld r1,r5pop r6pop r5rts; convert an ascii char to hex code; input; r1 = char to convertasciiToHex:cmp #'9' ; less than '9'beq a2h1bcc a2h1sub #7 ; shift 'A' to '9'+1a2h1:sub #'0'and #15 ; make sure a nybblertsGetFilename:ldy #'"'ld r4,#gfn1jsr TSTCldy #0gfn2:ld r1,(r8) ; get text characterinc r8cmp #'"'beq gfn3cmp #0beq gfn3sb r1,FILENAME,yinycpy #32bne gfn2rtsgfn3:lda #' 'sb r1,FILENAME,yinycpy #32bne gfn3rtsgfn1:jmp WSTARTLOAD3:jsr GetFilenamejsr AUXIN_INITjmp LOAD; jsr OREXPR ;evaluate the following expression; lda #5000ldx #$E00jsr SDReadSectorinaldx TXTBGN>>2asl r2,r2,#2LOAD4:phajsr SDReadSectoradd r2,r2,#512plainald r4,TXTBGN>>2asl r4,r4,#2add r4,r4,#65536cmp r2,r4bmi LOAD4LOAD5:bra WSTARTSAVE3:jsr GetFilenamejsr AUXOUT_INITjmp SAVEjsr OREXPR ;evaluate the following expression; lda #5000 ; starting sectorldx #$E00 ; starting address to writejsr SDWriteSectorinaldx TXTBGN>>2asl r2,r2,#2SAVE4:phajsr SDWriteSectoradd r2,r2,#512plainald r4,TXTBGN>>2asl r4,r4,#2add r4,r4,#65536cmp r2,r4bmi SAVE4bra WSTARTSAVE:ld r8,TXTBGN>>2 ;set pointer to start of prog. areald r9,TXTUNF ;set pointer to end of prog. areaSAVE1:jsr AUXOCRLF ; send out a CR & LF (CP/M likes this)cmp r8,r9bcs SAVEND ; are we finished?lda #':' ; if not, start a linejsr GOAUXOlda (r8) ; get line numberinc r8jsr PWORD ; output line number as 4-digit hexSAVE2:lda (r8) ; get a text char.inc r8cmp #CRbeq SAVE1 ; is it the end of the line? if so, send CR & LF and start new linejsr GOAUXO ; send it outbra SAVE2 ; go back for more textSAVEND:lda #'@' ; send end-of-program indicatorjsr GOAUXOjsr AUXOCRLF ; followed by a CR & LFlda #$1A ; and a control-Z to end the CP/M filejsr GOAUXOjsr AUXOUT_FLUSHbra WSTART ; then go do a warm start; output a CR LF sequence to auxillary output; Registers Affected; r3 = LFAUXOCRLF:lda #CRjsr GOAUXOlda #LFjsr GOAUXOrts; output a word in hex format; tricky because of the need to reverse the order of the charsPWORD:push r5ld r5,#NUMWKA+7or r4,r1,r0 ; r4 = valuepword1:or r1,r4,r0 ; r1 = valuelsr r4,r4,#4 ; shift over to next nybblejsr toAsciiHex ; convert LS nybble to ascii hexsta (r5) ; save in work areasub r5,r5,#1cmp r5,#NUMWKAbeq pword1bcs pword1pword2:add r5,r5,#1lda (r5) ; get char to outputjsr GOAUXO ; send itcmp r5,#NUMWKA+7bcc pword2pop r5rts; convert nybble in r2 to ascii hex char2; r2 = character to converttoAsciiHex:and #15 ; make sure it's a nybblecmp #10 ; > 10 ?bcc tah1add #7 ; bump it up to the letter 'A'tah1:add #'0' ; bump up to ascii '0'rts;******************************************************************; *** POKE ***;; 'POKE expr1,expr2' stores the word from 'expr2' into the memory; address specified by 'expr1'.;******************************************************************;POKE:jsr OREXPR ; get the memory addressldy #','ld r4,#PKER ; it must be followed by a commajsr TSTC ; it must be followed by a commapha ; save the addressjsr OREXPR ; get the byte to be POKE'dplx ; get the address backsta (x) ; store the byte in memoryjmp FINISHPKER:lda #msgCommajmp ERROR ; if no comma, say "What?";******************************************************************; 'SYSX expr' jumps to the machine language subroutine whose; starting address is specified by 'expr'. The subroutine can use; all registers but must leave the stack the way it found it.; The subroutine returns to the interpreter by executing an RTS.;******************************************************************SYSX:jsr OREXPR ; get the subroutine's addresscmp #0bne sysx1 ; make sure we got a valid addresslda #msgSYSBadjmp ERRORsysx1:push r8 ; save the text pointerjsr (r1) ; jump to the subroutinepop r8 ; restore the text pointerjmp FINISH;******************************************************************; *** EXPR ***;; 'EXPR' evaluates arithmetical or logical expressions.; <OREXPR>::= <ANDEXPR> OR <ANDEXPR> ...; <ANDEXPR>::=<EXPR> AND <EXPR> ...; <EXPR>::=<EXPR2>; <EXPR2><rel.op.><EXPR2>; where <rel.op.> is one of the operators in TAB8 and the result; of these operations is 1 if true and 0 if false.; <EXPR2>::=(+ or -)<EXPR3>(+ or -)<EXPR3>(...; where () are optional and (... are optional repeats.; <EXPR3>::=<EXPR4>( <* or /><EXPR4> )(...; <EXPR4>::=<variable>; <function>; (<EXPR>); <EXPR> is recursive so that the variable '@' can have an <EXPR>; as an index, functions can have an <EXPR> as arguments, and; <EXPR4> can be an <EXPR> in parenthesis.;; <OREXPR>::=<ANDEXPR> OR <ANDEXPR> ...;OREXPR:jsr ANDEXPR ; get first <ANDEXPR>XP_OR1:pha ; save <ANDEXPR> valueld r9,#TAB10 ; look up a logical operatorld r10,#TAB10_1jmp EXEC ; go do itXP_OR:jsr ANDEXPRplxor r1,r1,r2bra XP_OR1XP_ORX:plarts; <ANDEXPR>::=<EXPR> AND <EXPR> ...;ANDEXPR:jsr EXPR ; get first <EXPR>XP_AND1:pha ; save <EXPR> valueld r9,#TAB9 ; look up a logical operatorld r10,#TAB9_1jmp EXEC ; go do itXP_AND:jsr EXPRplxand r1,r1,r2bra XP_AND1XP_ANDX:plarts; Determine if the character is a digit; Parameters; r1 = char to test; Returns; r1 = 1 if digit, otherwise 0;isDigit:cmp #'0'bcc isDigitFalsecmp #'9'+1bcs isDigitFalselda #1rtsisDigitFalse:lda #0rts; Determine if the character is a alphabetic; Parameters; r1 = char to test; Returns; r1 = 1 if alpha, otherwise 0;isAlpha:cmp #'A'bcc isAlphaFalsecmp #'Z'beq isAlphaTruebcc isAlphaTruecmp #'a'bcc isAlphaFalsecmp #'z'+1bcs isAlphaFalseisAlphaTrue:lda #1rtsisAlphaFalse:lda #0rts; Determine if the character is a alphanumeric; Parameters; r1 = char to test; Returns; r1 = 1 if alpha, otherwise 0;isAlnum:tax ; save test charjsr isDigitcmp #0bne isDigitx ; if it is a digittxa ; get back test charjsr isAlphaisDigitx:rtsEXPR:jsr EXPR2pha ; save <EXPR2> valueld r9,#TAB8 ; look up a relational operatorld r10,#TAB8_1jmp EXEC ; go do itXP11:plajsr XP18 ; is it ">="?cmp r2,r1bpl XPRT1 ; no, return r2=1bra XPRT0 ; else return r2=0XP12:plajsr XP18 ; is it "<>"?cmp r2,r1bne XPRT1 ; no, return r2=1bra XPRT0 ; else return r2=0XP13:plajsr XP18 ; is it ">"?cmp r2,r1beq XPRT0bpl XPRT1 ; no, return r2=1bra XPRT0 ; else return r2=0XP14:plajsr XP18 ; is it "<="?cmp r2,r1beq XPRT1 ; no, return r2=1bmi XPRT1bra XPRT0 ; else return r2=0XP15:plajsr XP18 ; is it "="?cmp r2,r1beq XPRT1 ; if not, return r2=1bra XPRT0 ; else return r2=0XP16:plajsr XP18 ; is it "<"?cmp r2,r1bmi XPRT1 ; if not, return r2=1bra XPRT0 ; else return r2=0XPRT0:lda #0 ; return r1=0 (false)rtsXPRT1:lda #1 ; return r1=1 (true)rtsXP17: ; it's not a rel. operatorpla ; return r2=<EXPR2>rtsXP18:phajsr EXPR2 ; do a second <EXPR2>plxrts; <EXPR2>::=(+ or -)<EXPR3>(+ or -)<EXPR3>(...message "EXPR2"EXPR2:ldy #'-'ld r4,#XP21jsr TSTC ; negative sign?lda #0 ; yes, fake '0-'phabra XP26XP21:ldy #'+'ld r4,#XP22jsr TSTC ; positive sign? ignore itXP22:jsr EXPR3 ; first <EXPR3>XP23:pha ; yes, save the valueldy #'+'ld r4,#XP25jsr TSTC ; add?jsr EXPR3 ; get the second <EXPR3>XP24:plxadd r1,r1,r2 ; add it to the first <EXPR3>; BVS.L QHOW brnch if there's an overflowbra XP23 ; else go back for more operationsXP25:ldy #'-'ld r4,#XP45jsr TSTC ; subtract?XP26:jsr EXPR3 ; get second <EXPR3>sub r1,r0,r1 ; change its signbra XP24 ; and do an additionXP45:plarts; <EXPR3>::=<EXPR4>( <* or /><EXPR4> )(...EXPR3:jsr EXPR4 ; get first <EXPR4>XP31:pha ; yes, save that first resultldy #'*'ld r4,#XP34jsr TSTC ; multiply?jsr EXPR4 ; get second <EXPR4>plxmuls r1,r1,r2 ; multiply the twobra XP31 ; then look for more termsXP34:ldy #'/'ld r4,#XP47jsr TSTC ; divide?jsr EXPR4 ; get second <EXPR4>taxpladivs r1,r1,r2 ; do the divisionbra XP31 ; go back for any more termsXP47:plarts; Functions are jsred through EXPR4; <EXPR4>::=<variable>; <function>; (<EXPR>)EXPR4:ld r9,#TAB4 ; find possible functionld r10,#TAB4_1jmp EXEC ; branch to function which does subsequent rts for EXPR4XP40: ; we get here if it wasn't a functionlda #0jsr TSTVcmp #0beq XP41 ; nor a variablelda (r1) ; if a variable, return its value in r1rtsXP41:jsr TSTNUM ; or is it a number?cmp r2,#0bne XP46 ; (if not, # of digits will be zero) if so, return it in r1jsr PARN ; check for (EXPR)XP46:rts; Check for a parenthesized expressionPARN:ldy #'('ld r4,#XP43jsr TSTC ; else look for ( OREXPR )jsr OREXPRldy #')'ld r4,#XP43jsr TSTCXP42:rtsXP43:pla ; get rid of return addresslda #msgWhatjmp ERROR; ===== Test for a valid variable name. Returns Z=1 if not; found, else returns Z=0 and the address of the; variable in r1.; Parameters; r1 = 1 = allocate if not found; Returns; r1 = address of variable, zero if not foundTSTV:push r5ld r5,r1 ; r5=allocate flagjsr IGNBLKlda (r8) ; look at the program textcmp #'@'bcc tstv_notfound ; C=1: not a variablebne TV1 ; brnch if not "@" arrayinc r8 ; If it is, it should bejsr PARN ; followed by (EXPR) as its index.; BCS.L QHOW say "How?" if index is too bigpha ; save the indexjsr SIZEX ; get amount of free memoryplx ; get back the indexcmp r2,r1bcc TV2 ; see if there's enough memoryjmp QSORRY ; if not, say "Sorry"TV2:lda VARBGN ; put address of array element...sub r1,r1,r2 ; into r1 (neg. offset is used)bra TSTVRTTV1:jsr getVarName ; get variable namecmp #0beq TSTVRT ; if not, return r1=0ld r2,r5jsr findVar ; find or allocateTSTVRT:pop r5rts ; r1<>0 (found)tstv_notfound:pop r5lda #0 ; r1=0 if not foundrts; Returns; r1 = 2 character variable name + type;getVarName:push r5lda (r8) ; get first characterpha ; save off current namejsr isAlphacmp #0beq gvn1ld r5,#2 ; loop two more times; check for second/third charactergvn4:inc r8lda (r8) ; do we have another char ?jsr isAlnumcmp #0beq gvn2 ; nopepla ; get varnameaslaslaslaslaslaslaslaslldx (r8)or r1,r1,r2 ; add in new charpha ; save off name againdec r5bne gvn4; now ignore extra variable name charactersgvn6:inc r8lda (r8)jsr isAlnumcmp #0bne gvn6 ; keep looping as long as we have identifier chars; check for a variable typegvn2:lda (r8)cmp #'%'beq gvn3cmp #'$'beq gvn3lda #0dec r8; insert variable type indicator and returngvn3:inc r8plxasl r2,r2asl r2,r2asl r2,r2asl r2,r2asl r2,r2asl r2,r2asl r2,r2asl r2,r2or r1,r1,r2 ; add in variable typepop r5rts ; return Z = 0, r1 = varname; not a variable namegvn1:plapop r5lda #0 ; return Z = 1 if not a varnamerts; Find variable; r1 = varname; r2 = allocate flag; Returns; r1 = variable address, Z =0 if found / allocated, Z=1 if not foundfindVar:push r7ldy VARBGNfv4:ld r7,(y) ; get varname / typebeq fv3 ; no more vars ?cmp r1,r7beq fv1 ; match ?iny ; move to next varinyld r7,STKBOTcmp r3,r7bcc fv4 ; loop back to look at next var; variable not found; no more memorylda #msgVarSpacejmp ERROR; lw lr,[sp]; lw r7,4[sp]; add sp,sp,#8; lw r1,#0; rts; variable not found; allocate new ?fv3:cpx #0beq fv2sta (r3) ; save varname / type; found variable; return addressfv1:add r1,r3,#1pop r7rts ; Z = 0, r1 = address; didn't find var and not allocatingfv2:pop r7lda #0 ; Z = 1, r1 = 0rts; ===== The PEEK function returns the byte stored at the address; contained in the following expression.;PEEK:jsr PARN ; get the memory addresslda (r1) ; get the addressed byterts; user function jsr; call the user function with argument in r1USRX:jsr PARN ; get expression valuepush r8 ; save the text pointerldx #0jsr (usrJmp,x) ; get usr vector, jump to the subroutinepop r8 ; restore the text pointerrts; ===== The RND function returns a random number from 1 to; the value of the following expression in D0.;RND:jsr PARN ; get the upper limitcmp #0beq rnd2 ; it must be positive and non-zerobcc rnd1tax;gran ; generate a random number;mfspr r1,rand ; get the numbertsr LFSR,r1; jsr modu4 ; RND(n)=MOD(number,n)+1mod r1,r1,r2inartsrnd1:lda #msgRNDBadjmp ERRORrnd2:tsr LFSR,r1; gran; mfspr r1,randrts; r = a mod b; a = r1; b = r2; r = r6;modu4:; push r3; push r5; push r6; push r7; ld r7,#31 ; n = 32; eor r5,r5,r5 ; w = 0;; eor r6,r6,r6 ; r = 0;mod2:; rol ; a <<= 1; and r3,r1,#1; asl r6 ; r <<= 1; or r6,r6,r3; and #-2; cmp r2,r6; bmi mod1 ; b < r ?; sub r6,r6,r2 ; r -= b;mod1:; dec r7 ; n--; bne mod2; ld r1,r6; pop r7; pop r6; pop r5; pop r3; rts;; ===== The ABS function returns an absolute value in r2.;ABS:jsr PARN ; get the following expr.'s valuecmp #0bmi ABS1rtsABS1:sub r1,r0,r1rts;==== The TICK function returns the cpu tick value in r1.;TICKX:tsr TICK,r1rts; ===== The SGN function returns the sign in r1. +1,0, or -1;SGN:jsr PARN ; get the following expr.'s valuecmp #0beq SGN1bmi SGN2lda #1rtsSGN2:lda #-1rtsSGN1:rts; ===== The SIZE function returns the size of free memory in r1.;SIZEX:lda VARBGN ; get the number of free bytes...ldx TXTUNF ; between 'TXTUNF' and 'VARBGN'sub r1,r1,r2rts ; return the number in r1;******************************************************************;; *** SETVAL *** FIN *** ENDCHK *** ERROR (& friends) ***;; 'SETVAL' expects a variable, followed by an equal sign and then; an expression. It evaluates the expression and sets the variable; to that value.;; returns; r2 = variable's address;SETVAL:lda #1 ; allocate varjsr TSTV ; variable name?cmp #0bne sv2lda #msgVarjmp ERRORsv2:pha ; save the variable's addressldy #'='ld r4,#SV1jsr TSTC ; get past the "=" signjsr OREXPR ; evaluate the expressionplx ; get back the variable's addresssta (x) ; and save value in the variabletxa ; return r1 = variable addressrtsSV1:jmp QWHAT ; if no "=" sign; 'FIN' checks the end of a command. If it ended with ":",; execution continues. If it ended with a CR, it finds the; the next line and continues from there.;FIN:ldy #':'ld r4,#FI1jsr TSTC ; *** FIN ***pla ; if ":", discard return addressjmp RUNSML ; continue on the same lineFI1:ldy #CRld r4,#FI2jsr TSTC ; not ":", is it a CR?; else return to the callerpla ; yes, purge return addressjmp RUNNXL ; execute the next lineFI2:rts ; else return to the caller; 'ENDCHK' checks if a command is ended with a CR. This is; required in certain commands, such as GOTO, RETURN, STOP, etc.;; Check that there is nothing else on the line; Registers Affected; r1;ENDCHK:jsr IGNBLKlda (r8)cmp #CRbeq ec1 ; does it end with a CR?lda #msgExtraCharsjmp ERRORec1:rts; 'ERROR' prints the string pointed to by r1. It then prints the; line pointed to by CURRNT with a "?" inserted at where the; old text pointer (should be on top of the stack) points to.; Execution of Tiny BASIC is stopped and a warm start is done.; If CURRNT is zero (indicating a direct command), the direct; command is not printed. If CURRNT is -1 (indicating; 'INPUT' command in progress), the input line is not printed; and execution is not terminated but continues at 'INPERR'.;; Related to 'ERROR' are the following:; 'QWHAT' saves text pointer on stack and gets "What?" message.; 'AWHAT' just gets the "What?" message and jumps to 'ERROR'.; 'QSORRY' and 'ASORRY' do the same kind of thing.; 'QHOW' and 'AHOW' also do this for "How?".;TOOBIG:lda #msgTooBigbra ERRORQSORRY:lda #SRYMSGbra ERRORQWHAT:lda #msgWhatERROR:jsr PRMESG ; display the error messagelda CURRNT ; get the current line pointerbeq ERROR1 ; if zero, do a warm startcmp #-1beq INPERR ; is the line no. pointer = -1? if so, redo inputld r5,(r8) ; save the char. pointed tostz (r8) ; put a zero where the error islda CURRNT ; point to start of current linejsr PRTLN ; display the line in error up to the 0ld r6,r1 ; save off end pointerst r5,(r8) ; restore the characterlda #'?' ; display a "?"jsr GOOUTldx #0 ; stop char = 0sub r1,r6,#1 ; point back to the error char.jsr PRTSTG ; display the rest of the lineERROR1:jmp WSTART ; and do a warm start;******************************************************************;; *** GETLN *** FNDLN (& friends) ***;; 'GETLN' reads in input line into 'BUFFER'. It first prompts with; the character in r3 (given by the caller), then it fills the; buffer and echos. It ignores LF's but still echos; them back. Control-H is used to delete the last character; entered (if there is one), and control-X is used to delete the; whole line and start over again. CR signals the end of a line,; and causes 'GETLN' to return.;;GETLN:push r5jsr GOOUT ; display the promptlda #1sta CursorFlash ; turn on cursor flashlda #' ' ; and a spacejsr GOOUTld r8,#BUFFER ; r8 is the buffer pointerGL1:jsr CHKIO ; check keyboardcmp #0beq GL1 ; wait for a char. to come incmp #CTRLHbeq GL3 ; delete last character? if socmp #CTRLXbeq GL4 ; delete the whole line?cmp #CRbeq GL2 ; accept a CRcmp #' 'bcc GL1 ; if other control char., discard itGL2:sta (r8) ; save the char.inc r8phajsr GOOUT ; echo the char back outpla ; get char back (GOOUT destroys r1)cmp #CRbeq GL7 ; if it's a CR, end the linecmp r8,#BUFFER+BUFLEN-1 ; any more room?bcc GL1 ; yes: get some more, else delete last char.GL3:lda #CTRLH ; delete a char. if possiblejsr GOOUTlda #' 'jsr GOOUTcmp r8,#BUFFER ; any char.'s left?bcc GL1 ; if notbeq GL1lda #CTRLH ; if so, finish the BS-space-BS sequencejsr GOOUTdec r8 ; decrement the text pointerbra GL1 ; back for moreGL4:ld r1,r8 ; delete the whole linesub r5,r1,#BUFFER ; figure out how many backspaces we needbeq GL6 ; if none needed, brnchdec r5 ; loop count is one lessGL5:lda #CTRLH ; and display BS-space-BS sequencesjsr GOOUTlda #' 'jsr GOOUTlda #CTRLHjsr GOOUTdec r5bne GL5GL6:ld r8,#BUFFER ; reinitialize the text pointerbra GL1 ; and go back for moreGL7:lda #0 ; turn off cursor flashstz (r8) ; null terminate linestz CursorFlashlda #LF ; echo a LF for the CRjsr GOOUTpop r5rts; 'FNDLN' finds a line with a given line no. (in r1) in the; text save area. r9 is used as the text pointer. If the line; is found, r9 will point to the beginning of that line; (i.e. the high byte of the line no.), and r1 = 1.; If that line is not there and a line with a higher line no.; is found, r9 points there and r1 = 0. If we reached; the end of the text save area and cannot find the line, flags; r9 = 0, r1 = 0.; r1=1 if line found; r0 = 1 <= line is found; r9 = pointer to line; r0 = 0 <= line is not found; r9 = zero, if end of text area; r9 = otherwise higher line number;; 'FNDLN' will initialize r9 to the beginning of the text save; area to start the search. Some other entries of this routine; will not initialize r9 and do the search.; 'FNDLNP' will start with r9 and search for the line no.; 'FNDNXT' will bump r9 by 2, find a CR and then start search.; 'FNDSKP' uses r9 to find a CR, and then starts the search.; return Z=1 if line is found, r9 = pointer to line;; Parameters; r1 = line number to find;FNDLN:cmp #$FFFFbcc fl1 ; line no. must be < 65535lda #msgLineRangejmp ERRORfl1:ld r9,TXTBGN>>2 ; init. the text save pointerFNDLNP:cmp r9,TXTUNF ; check if we passed the endbeq FNDRET1bcs FNDRET1 ; if so, return with r9=0,r1=0ldx (r9) ; get line numbercmp r1,r2beq FNDRET2bcs FNDNXT ; is this the line we want? no, not there yetFNDRET:lda #0 ; line not found, but r9=next line pointerrts ; return the cond. codesFNDRET1:; eor r9,r9,r9 ; no higher linelda #0 ; line not foundrtsFNDRET2:lda #1 ; line foundrtsFNDNXT:inc r9 ; find the next lineFNDSKP:ldx (r9)inc r9cpx #CRbne FNDSKP ; try to find a CR, keep lookingbra FNDLNP ; check if end of text;******************************************************************; 'MVUP' moves a block up from where r1 points to where r2 points; until r1=r3;MVUP1:ld r4,(r1)st r4,(r2)inainxMVUP:cmp r1,r3bne MVUP1MVRET:rts; 'MVDOWN' moves a block down from where r1 points to where r2; points until r1=r3;MVDOWN1:deadexld r4,(r1)st r4,(r2)MVDOWN:cmp r1,r3bne MVDOWN1rts; 'POPA_' restores the 'FOR' loop variable save area from the stack;; 'PUSHA_' stacks for 'FOR' loop variable save area onto the stack;; Note: a single zero word is stored on the stack in the; case that no FOR loops need to be saved. This needs to be; done because PUSHA_ / POPA_ is called all the time.message "POPA_"POPA_:plyplasta LOPVAR ; restore LOPVAR, but zero means no morebeq PP1plasta LOPINCplasta LOPLMTplasta LOPLNplasta LOPPTPP1:jmp (y)PUSHA_:plylda STKBOT ; Are we running out of stack room?add r1,r1,#5 ; we might need this many wordstsxcmp r2,r1bcc QSORRY ; out of stack spaceldx LOPVAR ; save loop variablesbeq PU1 ; if LOPVAR is zero, that's alllda LOPPTphalda LOPLNphalda LOPLMTphalda LOPINCphaPU1:phxjmp (y);******************************************************************;; 'PRTSTG' prints a string pointed to by r1. It stops printing; and returns to the caller when either a CR is printed or when; the next byte is the same as what was passed in r2 by the; caller.;; 'PRTLN' prints the saved text line pointed to by r3; with line no. and all.;; r1 = pointer to string; r2 = stop character; return r1 = pointer to end of line + 1PRTSTG:push r5push r6push r7ld r5,r1 ; r5 = pointerld r6,r2 ; r6 = stop charPS1:ld r7,(r5) ; get a text characterinc r5cmp r7,r6beq PRTRET ; same as stop character? if so, returnld r1,r7jsr GOOUT ; display the char.cmp r7,#CRbne PS1 ; is it a C.R.? no, go back for morelda #LF ; yes, add a L.F.jsr GOOUTPRTRET:ld r2,r7 ; return r2 = stop charld r1,r5 ; return r1 = line pointerpop r7pop r6pop r5rts ; then return; 'QTSTG' looks for an underline (back-arrow on some systems),; single-quote, or double-quote. If none of these are found, returns; to the caller. If underline, outputs a CR without a LF. If single; or double quote, prints the quoted string and demands a matching; end quote. After the printing, the next i-word of the caller is; skipped over (usually a branch instruction).;QTSTG:ldy #'"'ld r4,#QT3jsr TSTC ; *** QTSTG ***ldx #'"' ; it is a "QT1:ld r1,r8jsr PRTSTG ; print until anotherld r8,r1cpx #CRbne QT2 ; was last one a CR?jmp RUNNXL ; if so run next lineQT3:ldy #''''ld r4,#QT4jsr TSTC ; is it a single quote?ldx #'''' ; if so, do same as abovebra QT1QT4:ldy #'_'ld r4,#QT5jsr TSTC ; is it an underline?lda #CR ; if so, output a CR without LFjsr GOOUTQT2:pla ; get return addressina ; add 2 to it in order to skip following branchinajmp (r1) ; skip over next i-word when returningQT5: ; not " ' or _rts; Output a CR LF sequence;prCRLF:lda #CRjsr GOOUTlda #LFjsr GOOUTrts; 'PRTNUM' prints the 32 bit number in r1, leading blanks are added if; needed to pad the number of spaces to the number in r2.; However, if the number of digits is larger than the no. in; r2, all digits are printed anyway. Negative sign is also; printed and counted in, positive sign is not.;; r1 = number to print; r2 = number of digits; Register Usage; r5 = number of padding spacespublic PRTNUM:push r3push r5push r6push r7ld r7,#NUMWKA ; r7 = pointer to numeric work areald r6,r1 ; save number for laterld r5,r2 ; r5 = min number of charscmp #0bpl PN2 ; is it negative? if notsub r1,r0,r1 ; else make it positivedec r5 ; one less for width countPN2:; ld r3,#10PN1:mod r2,r1,#10 ; r2 = r1 mod 10div r1,r1,#10 ; r1 /= 10 divide by 10add r2,r2,#'0' ; convert remainder to asciistx (r7) ; and store in bufferinc r7dec r5 ; decrement widthcmp #0bne PN1PN6:cmp r5,r0bmi PN4 ; test pad count, skip padding if not neededbeq PN4PN3:lda #' ' ; display the required leading spacesjsr GOOUTdec r5bne PN3PN4:cmp r6,r0bpl PN5 ; is number negative?lda #'-' ; if so, display the signjsr GOOUTPN5:dec r7lda (r7) ; now unstack the digits and displayjsr GOOUTcmp r7,#NUMWKAbeq PNRETbcs PN5PNRET:pop r7pop r6pop r5pop r3rts; r1 = number to print; r2 = number of digitspublic PRTHEXNUM:push r4push r5push r6push r7push r8ld r7,#NUMWKA ; r7 = pointer to numeric work areald r6,r1 ; save number for later; setlo r5,#20 ; r5 = min number of charsld r5,r2ld r4,r1cmp r4,r0bpl PHN2 ; is it negative? if notsub r4,r0,r4 ; else make it positivedec r5 ; one less for width countPHN2ld r8,#10 ; maximum of 10 digitsPHN1:ld r1,r4and #15cmp #10bcc PHN7add #'A'-10bra PHN8PHN7:add #'0' ; convert remainder to asciiPHN8:sta (r7) ; and store in bufferinc r7dec r5 ; decrement widthlsr r4,r4lsr r4,r4lsr r4,r4lsr r4,r4beq PHN6 ; is it zero yet ?dec r8bne PHN1PHN6: ; test pad countcmp r5,r0beq PHN4bcc PHN4 ; skip padding if not neededPHN3:lda #' ' ; display the required leading spacesjsr GOOUTdec r5bne PHN3PHN4:cmp r6,r0bcs PHN5 ; is number negative?lda #'-' ; if so, display the signjsr GOOUTPHN5:dec r7lda (r7) ; now unstack the digits and displayjsr GOOUTcmp r7,#NUMWKAbeq PHNRETbcs PHN5PHNRET:pop r8pop r7pop r6pop r5pop r4rts; r1 = pointer to line; returns r1 = pointer to end of line + 1PRTLN:push r5ld r5,r1 ; r5 = pointerlda (r5) ; get the binary line numberinc r5ldx #5 ; display a 0 or more digit line no.jsr PRTNUMlda #' ' ; followed by a blankjsr GOOUTldx #0 ; stop char. is a zerold r1,r5jsr PRTSTG ; display the rest of the linepop r5rts; ===== Test text byte following the call to this subroutine. If it; equals the byte pointed to by r8, return to the code following; the call. If they are not equal, brnch to the point; indicated in r4.;; Registers Affected; r3,r8; Returns; r8 = updated text pointer;TSTCphajsr IGNBLK ; ignore leading blankslda (r8)cmp r3,r1beq TC1 ; is it = to what r8 points to? if soplaply ; increment stack pointer (get rid of return address)jmp (r4) ; jump to the routineTC1:inc r8 ; if equal, bump text pointerplarts; ===== See if the text pointed to by r8 is a number. If so,; return the number in r2 and the number of digits in r3,; else return zero in r2 and r3.; Registers Affected; r1,r2,r3,r4; Returns; r1 = number; r2 = number of digits in number; r8 = updated text pointer;TSTNUM:phyjsr IGNBLK ; skip over blankslda #0 ; initialize return parametersldx #0ld r15,#10TN1:ldy (r8)cpy #'0' ; is it less than zero?bcc TSNMRETcpy #'9'+1 ; is it greater than nine?bcs TSNMRETcmp r1,#$7FFFFFF ; see if there's room for new digitbcc TN2beq TN2lda #msgNumTooBigjmp ERROR ; if not, we've overflowdTN2:inc r8 ; adjust text pointermul r1,r1,r15 ; quickly multiply result by 10and r3,r3,#$0F ; add in the new digitadd r1,r1,r3inx ; increment the no. of digitsbra TN1TSNMRET:plyrts;===== Skip over blanks in the text pointed to by r8.;; Registers Affected:; r8; Returns; r8 = pointer updateded past any spaces or tabs;IGNBLK:phaIGB2:lda (r8) ; get charcmp #' 'beq IGB1 ; see if it's a spacecmp #'\t'bne IGBRET ; or a tabIGB1:inc r8 ; increment the text pointerbra IGB2IGBRET:plarts; ===== Convert the line of text in the input buffer to upper; case (except for stuff between quotes).;; Registers Affected; r1,r3; Returns; r8 = pointing to end of text in buffer;TOUPBUF:ld r8,#BUFFER ; set up text pointereor r3,r3,r3 ; clear quote flagTOUPB1:lda (r8) ; get the next text char.inc r8cmp #CRbeq TOUPBRT ; is it end of line?cmp #'"'beq DOQUO ; a double quote?cmp #''''beq DOQUO ; or a single quote?cpy #0bne TOUPB1 ; inside quotes?jsr toUpper ; convert to upper casesta -1,r8 ; store itbra TOUPB1 ; and go back for moreDOQUO:cpy #0bne DOQUO1; are we inside quotes?tay ; if not, toggle inside-quotes flagbra TOUPB1DOQUO1:cmp r3,r1bne TOUPB1 ; make sure we're ending proper quoteeor r3,r3,r3 ; else clear quote flagbra TOUPB1TOUPBRT:rts; ===== Convert the character in r1 to upper case;toUppercmp #'a' ; is it < 'a'?bcc TOUPRETcmp #'z'+1 ; or > 'z'?bcs TOUPRETsub #32 ; if not, make it upper caseTOUPRETrts; 'CHKIO' checks the input. If there's no input, it will return; to the caller with the r1=0. If there is input, the input byte is in r1.; However, if a control-C is read, 'CHKIO' will warm-start BASIC and will; not return to the caller.;message "CHKIO"CHKIO:jsr GOIN ; get input if possiblecmp #0beq CHKRET2 ; if Zero, no inputcmp #CTRLCbne CHKRET ; is it control-C?pla ; dump return addressjmp WSTART ; if so, do a warm startCHKRET2:lda #0CHKRET:rts; ===== Display a CR-LF sequence;CRLF:lda #CLMSG; ===== Display a zero-ended string pointed to by register r1; Registers Affected; r1,r2,r4;PRMESG:push r5or r5,r1,r0 ; r5 = pointer to messagePRMESG1:inc r5lb r1,-1,r5 ; get the char.beq PRMRETjsr GOOUT ;else display it trashes r4bra PRMESG1PRMRET:or r1,r5,r0pop r5rts; ===== Display a zero-ended string pointed to by register r1; Registers Affected; r1,r2,r3;PRMESGAUX:phytay ; y = pointerPRMESGA1:inylb r1,-1,y ; get the char.beq PRMRETAjsr GOAUXO ;else display itbra PRMESGA1PRMRETA:tyaplyrts;*****************************************************; The following routines are the only ones that need *; to be changed for a different I/O environment. *;*****************************************************; ===== Output character to the console (Port 1) from register r1; (Preserves all registers.);OUTC:jmp DisplayChar; ===== Input a character from the console into register R1 (or; return Zero status if there's no character available).;INCH:; jsr KeybdCheckForKeyDirect; cmp #0; beq INCH1jsr KeybdGetCharcmp #-1beq INCH1rtsINCH1:ina ; return a zero for no-charrts;*;* ===== Input a character from the host into register r1 (or;* return Zero status if there's no character available).;*AUXIN_INIT:stz INPPTRlda #FILENAMEldx #FILEBUF<<2ldy #$10000jsr do_loadrtsAUXIN:phxldx INPPTRlb r1,FILEBUF<<2,xinxstx INPPTRplxrts; jsr SerialGetChar; cmp #-1; beq AXIRET_ZERO; and #$7F ;zero out the high bit;AXIRET:; rts;AXIRET_ZERO:; lda #0; rts; ===== Output character to the host (Port 2) from register r1; (Preserves all registers.);AUXOUT_INIT:stz OUTPTRrtsAUXOUT:phxldx OUTPTRsb r1,FILEBUF<<2,xinxstx OUTPTRplxrtsAUXOUT_FLUSH:lda #FILENAMEldx #FILEBUF<<2ldy OUTPTRjsr do_saverts; jmp SerialPutChar ; call boot rom routine_clsjsr ClearScreenjsr HomeCursorjmp FINISH_wait10rts_getATAStatusrts_waitCFNotBusyrts_rdcfjmp FINISHrdcf6bra ERROR; ===== Return to the resident monitor, operating system, etc.;BYEBYE:jsr ReleaseIOFocusldx OSSPtxsrts; MOVE.B #228,D7 return to Tutor; TRAP #14msgInit db CR,LF,"RTF65002 Tiny BASIC v1.0",CR,LF,"(C) 2013 Robert Finch",CR,LF,LF,0OKMSG db CR,LF,"OK",CR,LF,0msgWhat db "What?",CR,LF,0SRYMSG db "Sorry."CLMSG db CR,LF,0msgReadError db "Compact FLASH read error",CR,LF,0msgNumTooBig db "Number is too big",CR,LF,0msgDivZero db "Division by zero",CR,LF,0msgVarSpace db "Out of variable space",CR,LF,0msgBytesFree db " words free",CR,LF,0msgReady db CR,LF,"Ready",CR,LF,0msgComma db "Expecting a comma",CR,LF,0msgLineRange db "Line number too big",CR,LF,0msgVar db "Expecting a variable",CR,LF,0msgRNDBad db "RND bad parameter",CR,LF,0msgSYSBad db "SYS bad address",CR,LF,0msgInputVar db "INPUT expecting a variable",CR,LF,0msgNextFor db "NEXT without FOR",CR,LF,0msgNextVar db "NEXT expecting a defined variable",CR,LF,0msgBadGotoGosub db "GOTO/GOSUB bad line number",CR,LF,0msgRetWoGosub db "RETURN without GOSUB",CR,LF,0msgTooBig db "Program is too big",CR,LF,0msgExtraChars db "Extra characters on line ignored",CR,LF,0align 4LSTROM equ * ; end of possible ROM area; END;*;* ===== Return to the resident monitor, operating system, etc.;*;BYEBYE:; jmp Monitor; MOVE.B #228,D7 ;return to Tutor; TRAP #14
