URL
https://opencores.org/ocsvn/rtf68ksys/rtf68ksys/trunk
Subversion Repositories rtf68ksys
[/] [rtf68ksys/] [trunk/] [Software/] [TinyBasic.x68] - Rev 7
Compare with Previous | Blame | View Log
;*****************************************************************
; *
; Tiny BASIC for the Motorola MC68000 *
; *
; Derived from Palo Alto Tiny BASIC as published in the May 1976 *
; issue of Dr. Dobb's Journal. Adapted to the 68000 by: *
; Gordon Brandly *
; 12147 - 51 Street *
; Edmonton AB T5W 3G8 *
; Canada *
; (updated mailing address for 1996) *
; *
; This version is for MEX68KECB Educational Computer Board I/O. *
; *
;*****************************************************************
; Copyright (C) 1984 by Gordon Brandly. This program may be *
; freely distributed for personal use only. All commercial *
; rights are reserved. *
;*****************************************************************
; *
; Some Modifications by: Robert Finch *
; This version running on a Diligent Nexys2 board XC3S1200e *
; Running TG68 for a processor *
; - two character variable names *
; - TICK variable *
; - *
; *
;*****************************************************************
; Vers. 1.0 1984/7/17 - Original version by Gordon Brandly
; 1.1 1984/12/9 - Addition of '$' print term by Marvin Lipford
; 1.2 1985/4/9 - Bug fix in multiply routine by Rick Murray
; OPT FRS,BRS forward ref.'s & branches default to short
RANDOM EQU 0xFFDC0C00 ; Hardware random# gen. address
GRAPHICS EQU 0xFFDAE000 ; graphics (line draw) acceelerator address
SPRCTRL EQU 0xFFDAD000 ; sprite controller address
BUFLEN EQU 80 ; length of keyboard input buffer
;*
;* Internal variables follow:
;*
BSS
ORG 0x600
RANPNT:
DC.L START ; random number pointer
CURRNT:
DC.L 1 ;Current line pointer
STKGOS:
DC.L 1 ;Saves stack pointer in 'GOSUB'
STKINP:
DC.L 1 ;Saves stack pointer during 'INPUT'
LOPVAR:
DC.L 1 ;'FOR' loop save area
LOPINC:
DC.L 1 ;increment
LOPLMT:
DC.L 1 ;limit
LOPLN:
DC.L 1 ;line number
LOPPT:
DC.L 1 ;text pointer
TXTUNF:
DC.L 1 ;points to unfilled text area
VARBGN:
DC.L 1 ;points to variable area
STKLMT:
DC.L 1 ;holds lower limit for stack growth
BUFFER:
FILL.B BUFLEN,0x00 ; Keyboard input buffer
TXT EQU $ ;Beginning of program area
CODE
even
ORG 0xFFFF2400
; Tell the outside world about these symbols
; only needed for the assembler, since this file is included from another file
public START
public ENDMEM
public PRTNUM
public AUXIN
;*
;* Standard jump table. You can change these addresses if you are
;* customizing this interpreter for a different environment.
;*
START:
BRA.L CSTART ;Cold Start entry point
GOWARM: BRA.L WSTART ;Warm Start entry point
GOOUT: BRA.L OUTC ;Jump to character-out routine
GOIN: BRA.L INC ;Jump to character-in routine
GOAUXO: BRA.L AUXOUT ;Jump to auxiliary-out routine
GOAUXI: BRA.L AUXIN ;Jump to auxiliary-in routine
GOBYE: BRA.L BYEBYE ;Jump to monitor, DOS, etc.
;*
;* Modifiable system constants:
;*
; Give Tiny Basic 3MB
TXTBGN DC.L 0xC00000 ;beginning of program memory
ENDMEM DC.L 0xF00000 ; end of available memory
;*
;* The main interpreter starts here:
;*
CSTART:
LEA START,A0
MOVE.L A0,RANPNT
MOVE.L ENDMEM,SP ;initialize stack pointer
LEA INITMSG,A6 ;tell who we are
BSR.L PRMESG
MOVE.L TXTBGN,TXTUNF ;init. end-of-program pointer
MOVE.L ENDMEM,D0 ;get address of end of memory
SUB.L #2048,D0 ;reserve 2K for the stack
MOVE.L D0,STKLMT
SUB.L #4104,D0 ;reserve variable area (27 long words)
MOVE.L D0,VARBGN
WSTART:
CLR.L D0 ;initialize internal variables
MOVE.L D0,LOPVAR
MOVE.L D0,STKGOS
MOVE.L D0,CURRNT ;current line number pointer = 0
MOVE.L ENDMEM,SP ;init S.P. again, just in case
LEA OKMSG,A6 ;display "OK"
BSR.L PRMESG
ST3:
MOVE.B #'>',D0 ; Monitor with a '>' and
BSR.L GETLN ; read a line.
BSR.L TOUPBUF ; convert to upper case
MOVE.L A0,A4 ; save pointer to end of line
LEA BUFFER,A0 ;point to the beginning of line
BSR.L TSTNUM ; is there a number there?
BSR.L IGNBLK ; skip trailing blanks
TST D1 ;does line no. exist? (or nonzero?)
BEQ.L DIRECT ; if not, it's a direct statement
CMP.L #0xFFFF,D1 ;see if line no. is <= 16 bits
BCC.L QHOW ;if not, we've overflowed
MOVE.B D1,-(A0) ;store the binary line no.
ROR #8,D1 ;(Kludge to store a word on a
MOVE.B D1,-(A0) ;possible byte boundary)
ROL #8,D1
BSR.L FNDLN ; find this line in save area
MOVE.L A1,A5 ; save possible line pointer
BNE ST4 ; if not found, insert
BSR.L FNDNXT ;find the next line (into A1)
MOVE.L A5,A2 ;pointer to line to be deleted
MOVE.L TXTUNF,A3 ;points to top of save area
BSR.L MVUP ;move up to delete
MOVE.L A2,TXTUNF ;update the end pointer
ST4:
MOVE.L A4,D0 ;calculate the length of new line
SUB.L A0,D0
CMP.L #3,D0 ;is it just a line no. & CR?
BEQ ST3 ;if so, it was just a delete
MOVE.L TXTUNF,A3 ;compute new end
MOVE.L A3,A6
ADD.L D0,A3
MOVE.L VARBGN,D0 ;see if there's enough room
CMP.L A3,D0
BLS.L QSORRY ;if not, say so
MOVE.L A3,TXTUNF ;if so, store new end position
MOVE.L A6,A1 ;points to old unfilled area
MOVE.L A5,A2 ;points to beginning of move area
BSR.L MVDOWN ;move things out of the way
MOVE.L A0,A1 ;set up to do the insertion
MOVE.L A5,A2
MOVE.L A4,A3
BSR.L MVUP ;do it
BRA 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', A0 should point to the string, A1 should point to
;* the character table, and A2 should point to the execution
;* table. At 'DIRECT', A0 should point to the string, A1 and
;* A2 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 16-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:
TAB1:
DC.B 'LIS',('T'+0x80) ; Direct commands
DC.B 'LOA',('D'+0x80)
DC.B 'NE',('W'+0x80)
DC.B 'RU',('N'+0x80)
DC.B 'SAV',('E'+0x80)
DC.B 'CL',('S'+0x80)
TAB2:
DC.B 'NEX',('T'+0x80) ; Direct / statement
DC.B 'LE',('T'+0x80)
DC.B 'I',('F'+0x80)
DC.B 'GOT',('O'+0x80)
DC.B 'GOSU',('B'+0x80)
DC.B 'RETUR',('N'+0x80)
DC.B 'RE',('M'+0x80)
DC.B 'FO',('R'+0x80)
DC.B 'INPU',('T'+0x80)
DC.B 'PRIN',('T'+0x80)
DC.B 'POK',('E'+0x80)
DC.B 'STO',('P'+0x80)
DC.B 'BY',('E'+0x80)
DC.B 'CAL',('L'+0x80)
DC.B 'LIN',('E'+0x80)
DC.B 'POIN',('T'+0x80)
DC.B 'PENCOLO',('R'+0x80)
DC.B 'FILLCOLO',('R'+0x80)
DC.B 'SPRPO',('S'+0x80)
DC.B 0
TAB4:
DC.B 'PEE',('K'+0x80) ; Functions
DC.B 'RN',('D'+0x80)
DC.B 'AB',('S'+0x80)
DC.B 'SIZ',('E'+0x80)
DC.B 'TIC',('K'+0x80)
DC.B 'TEM',('P'+0x80)
DC.B 'SG',('N'+0x80)
DC.B 0
TAB5:
DC.B 'T',('O'+0x80) ; "TO" in "FOR"
DC.B 0
TAB6:
DC.B 'STE',('P'+0x80) ; "STEP" in "FOR"
DC.B 0
TAB8:
DC.B '>',('='+0x80) ; Relational operators
DC.B '<',('>'+0x80)
DC.B ('>'+0x80)
DC.B ('='+0x80)
DC.B '<',('='+0x80)
DC.B ('<'+0x80)
DC.B 0
; DC.B 0 ;<- for aligning on a word boundary
even
;* Execution address tables:
TAB1_1:
DC.W LIST_ ;Direct commands
DC.W LOAD
DC.W NEW
DC.W RUN
DC.W SAVE
DC.W CLS
TAB2_1:
DC.W NEXT ;Direct / statement
DC.W LET
DC.W IF
DC.W GOTO
DC.W GOSUB
DC.W RETURN
DC.W REM
DC.W FOR
DC.W INPUT
DC.W PRINT
DC.W POKE
DC.W STOP_
DC.W GOBYE
DC.W CALL
DC.W LINE
DC.W POINT
DC.W PENCOLOR
DC.W FILLCOLOR
DC.W SPRPOS
DC.W DEFLT
TAB4_1:
DC.W PEEK ;Functions
DC.W RND
DC.W ABS
DC.W SIZE_
DC.W TICK
DC.W TEMP
DC.W SGN
DC.W XP40
TAB5_1:
DC.W FR1 ; "TO" in "FOR"
DC.W QWHAT
TAB6_1:
DC.W FR2 ; "STEP" in "FOR"
DC.W FR3
TAB8_1:
DC.W XP11; >= Relational operators
DC.W XP12 ;<>
DC.W XP13 ;>
DC.W XP15 ;=
DC.W XP14 ;<=
DC.W XP16 ;<
DC.W XP17
;*
DIRECT:
LEA TAB1,A1
LEA TAB1_1,A2
EXEC:
BSR.L IGNBLK; ignore leading blanks
MOVE.L A0,A3 ;save the pointer
CLR.B D2 ;clear match flag
EXLP:
MOVE.B (A0)+,D0; get the program character
MOVE.B (A1),D1 ;get the table character
BNE EXNGO ;If end of table,
MOVE.L A3,A0 ;; restore the text pointer and...
BRA EXGO ;execute the default.
EXNGO:
MOVE.B D0,D3 ; Else check for period...
AND.B D2,D3 ;and a match.
CMP.B #'.',D3
BEQ EXGO ;if so, execute
AND.B #0x7F,D1 ; ignore the table's high bit
CMP.B D0,D1 ; is there a match?
BEQ EXMAT
ADDQ.L #2,A2 ; if not, try the next entry
MOVE.L A3,A0 ; reset the program pointer
CLR.B D2 ;sorry, no match
EX1:
TST.B (A1)+ ; get to the end of the entry
BPL EX1
BRA EXLP ;back for more matching
EXMAT:
MOVEQ #-1,D2; we've got a match so far
TST.B (A1)+ ; end of table entry?
BPL EXLP ;if not, go back for more
EXGO:
LEA 0xFFFF0000,A3 ; execute the appropriate routine
move.w (a2),a2
JMP (A3,A2.W)
CLS:
jsr ClearScreen
clr.w CursorRow
clr.w CursorCol
bra WSTART
;*
;*******************************************************************
;*
;* 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 'LIST', '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
;*
;* 'STOP<CR>' goes back to WSTART
;*
;* '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.
;*
;* 'GOTO expr<CR>' evaluates the expression, finds the target
;* line, and jumps to 'RUNTSL' to do it.
;*
NEW:
BSR.L ENDCHK
MOVE.L TXTBGN,TXTUNF ;set the end pointer
STOP_:
BSR.L ENDCHK
BRA WSTART
RUN:
BSR.L ENDCHK
MOVE.L TXTBGN,A0 ;set pointer to beginning
MOVE.L A0,CURRNT
RUNNXL:
TST.L CURRNT ; executing a program?
BEQ.L WSTART ;if not, we've finished a direct stat.
CLR.L D1 ;else find the next line number
MOVE.L A0,A1
BSR.L FNDLNP
BCS WSTART ;if we've fallen off the end, stop
RUNTSL:
MOVE.L A1,CURRNT ;set CURRNT to point to the line no.
MOVE.L A1,A0 ;set the text pointer to
ADDQ.L #2,A0 ;the start of the line text
RUNSML:
BSR.L CHKIO ; see if a control-C was pressed
LEA TAB2,A1 ;find command in TAB2
LEA TAB2_1,A2
BRA EXEC ;and execute it
GOTO:
BSR.L EXPR ; evaluate the following expression
BSR.L ENDCHK ;must find end of line
MOVE.L D0,D1
BSR.L FNDLN ;find the target line
BNE.L QHOW ;no such line no.
BRA RUNTSL ;go do it
;*
;*******************************************************************
;*
;* *** LIST *** PRINT ***
;*
;* LIST 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.
;*
;* 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.
;*
LIST_:
BSR.L TSTNUM ; see if there's a line no.
BSR.L ENDCHK ;if not, we get a zero
BSR.L FNDLN ;find this or next line
LS1:
BCS WSTART ;warm start if we passed the end
BSR.L PRTLN ; print the line
BSR.L CHKIO ; check for listing halt request
BEQ LS3
CMP.B #CTRLS,D0 ;pause the listing?
BNE LS3
LS2:
BSR.L CHKIO ;if so, wait for another keypress
BEQ LS2
LS3:
BSR.L FNDLNP ;find the next line
BRA LS1
PRINT:
MOVE #11,D4 ; D4 = number of print spaces
BSR.L TSTC ;if null list and ":"
DC.B ':',PR2-$
BSR.L CRLF1 ;give CR-LF and continue
BRA RUNSML ;execution on the same line
PR2:
BSR.L TSTC ;if null list and <CR>
DC.B CR,PR0-$
BSR.L CRLF1 ;also give CR-LF and
BRA RUNNXL ;execute the next line
PR0:
BSR.L TSTC ;else is it a format?
DC.B '#',PR1-$
BSR.L EXPR ;yes, evaluate expression
MOVE D0,D4 ;and save it as print width
BRA PR3 ;look for more to print
PR1:
BSR.L TSTC ;is character expression? (MRL)
DC.B '$',PR4-$
BSR.L EXPR ;yep. Evaluate expression (MRL)
BSR GOOUT ;print low byte (MRL)
BRA PR3 ;look for more. (MRL)
PR4:
BSR.L QTSTG ; is it a string?
BRA.S PR8 ;if not, must be an expression
PR3:
BSR.L TSTC ; if ",", go find next
DC.B ',',PR6-$
BSR.L FIN ;in the list.
BRA PR0
PR6:
BSR.L CRLF1 ; list ends here
BRA FINISH
PR8:
MOVE D4,-(SP) ;save the width value
BSR.L EXPR ;evaluate the expression
MOVE (SP)+,D4 ;restore the width
MOVE.L D0,D1
BSR.L PRTNUM ;print its value
BRA PR3 ;more to print?
FINISH:
BSR.L FIN ; Check end of command
BRA.L 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.
;*
;* '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.
;*
GOSUB:
BSR.L PUSHA ; save the current 'FOR' parameters
BSR.L EXPR ;get line number
MOVE.L A0,-(SP) ;save text pointer
MOVE.L D0,D1
BSR.L FNDLN ;find the target line
BNE.L AHOW ;if not there, say "How?"
MOVE.L CURRNT,-(SP) ;found it, save old 'CURRNT'...
MOVE.L STKGOS,-(SP) ;and 'STKGOS'
CLR.L LOPVAR ;load new values
MOVE.L SP,STKGOS
BRA RUNTSL
RETURN:
BSR.L ENDCHK ; there should be just a <CR>
MOVE.L STKGOS,D1 ;get old stack pointer
BEQ.L QWHAT ;if zero, it doesn't exist
MOVE.L D1,SP ;else restore it
MOVE.L (SP)+,STKGOS ;and the old 'STKGOS'
MOVE.L (SP)+,CURRNT ;and the old 'CURRNT'
MOVE.L (SP)+,A0 ;and the old text pointer
BSR.L POPA ;and the old 'FOR' parameters
BRA 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 consisits 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)
;*
;* '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.
;*
FOR:
BSR.L PUSHA ;save the old 'FOR' save area
BSR.L SETVAL ;set the control variable
MOVE.L A6,LOPVAR ;save its address
LEA TAB5,A1 ;use 'EXEC' to test for 'TO'
LEA TAB5_1,A2
BRA EXEC
FR1:
BSR.L EXPR ;evaluate the limit
MOVE.L D0,LOPLMT ;save that
LEA TAB6,A1 ;use 'EXEC' to look for the
LEA TAB6_1,A2 ;word 'STEP'
BRA EXEC
FR2:
BSR.L EXPR ; found it, get the step value
BRA FR4
FR3:
MOVEQ #1,D0 ; not found, step defaults to 1
FR4:
MOVE.L D0,LOPINC ;save that too
FR5:
MOVE.L CURRNT,LOPLN ;save address of current line number
MOVE.L A0,LOPPT ;and text pointer
MOVE.L SP,A6 ;dig into the stack to find 'LOPVAR'
BRA FR7
FR6:
ADD.L #20,A6 ;look at next stack frame
FR7:
MOVE.L (A6),D0 ;is it zero?
BEQ FR8 ;if so, we're done
CMP.L LOPVAR,D0 ;same as current LOPVAR?
BNE FR6 ;nope, look some more
MOVE.L SP,A2 ; Else remove 5 long words from...
MOVE.L A6,A1 ; inside the stack.
LEA 20,A3
ADD.L A1,A3
BSR.L MVDOWN
MOVE.L A3,SP ; set the SP 5 long words up
FR8:
BRA FINISH ;and continue execution
NEXT:
BSR.L TSTV; get address of variable
BCS.L QWHAT ; if no variable, say "What?"
MOVE.L D0,A1 ; save variable's address
NX0:
MOVE.L LOPVAR,D0; If 'LOPVAR' is zero, we never...
BEQ.L QWHAT ; had a FOR loop, so say "What?"
CMP.L D0,A1 ;; else we check them
BEQ NX3 ; OK, they agree
BSR.L POPA ; nope, let's see the next frame
BRA NX0
NX3:
MOVE.L (A1),D0 ; get control variable's value
ADD.L LOPINC,D0; add in loop increment
BVS.L QHOW ; say "How?" for 32-bit overflow
MOVE.L D0,(A1) ; save control variable's new value
MOVE.L LOPLMT,D1; get loop's limit value
TST.L LOPINC
BPL NX1 ; branch if loop increment is positive
EXG D0,D1
NX1:
CMP.L D0,D1; test against limit
BLT NX2; branch if outside limit
MOVE.L LOPLN,CURRNT ;Within limit, go back to the...
MOVE.L LOPPT,A0 ;saved 'CURRNT' and text pointer.
BRA FINISH
NX2:
BSR.L POPA ;purge this loop
BRA FINISH
;*
;*******************************************************************
;*
;* *** REM *** IF *** INPUT *** LET (& DEFLT) ***
;*
;* 'REM' can be followed by anything and is ignored by the
;* interpreter.
;*
;* '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.
;*
;* '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'.
;*
;* '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'.
;*
REM:
BRA IF2 ;skip the rest of the line
IF:
BSR.L EXPR ; evaluate the expression
IF1:
TST.L D0 ;is it zero?
BNE RUNSML ;if not, continue
IF2:
MOVE.L A0,A1
CLR.L D1
BSR.L FNDSKP ; if so, skip the rest of the line
BCC RUNTSL ;and run the next line
BRA.L WSTART ; if no next line, do a warm start
INPERR:
MOVE.L STKINP,SP; restore the old stack pointer
MOVE.L (SP)+,CURRNT; and old 'CURRNT'
ADDQ.L #4,SP
MOVE.L (SP)+,A0 ;and old text pointer
INPUT:
MOVE.L A0,-(SP); save in case of error
BSR.L QTSTG ;is next item a string?
BRA.S IP2 ;nope
BSR.L TSTV ; yes, but is it followed by a variable?
BCS IP4 ;if not, branch
MOVE.L D0,A2 ; put away the variable's address
BRA IP3 ;if so, input to variable
IP2:
MOVE.L A0,-(SP); save for 'PRTSTG'
BSR.L TSTV ; must be a variable now
BCS.L QWHAT ; "What?" it isn't?
MOVE.L D0,A2 ; put away the variable's address
MOVE.B (A0),D2 ; get ready for 'PRTSTG'
CLR.B D0
MOVE.B D0,(A0)
MOVE.L (SP)+,A1
BSR.L PRTSTG ; print string as prompt
MOVE.B D2,(A0) ; restore text
IP3:
MOVE.L A0,-(SP); save in case of error
MOVE.L CURRNT,-(SP) ;also save 'CURRNT'
MOVE.L #-1,CURRNT ;flag that we are in INPUT
MOVE.L SP,STKINP ;save the stack pointer too
MOVE.L A2,-(SP) ;save the variable address
MOVE.B #':',D0 ; print a colon first
BSR.L GETLN ;then get an input line
LEA BUFFER,A0 ;point to the buffer
BSR.L EXPR ; evaluate the input
MOVE.L (SP)+,A2 ;restore the variable address
MOVE.L D0,(A2) ;save value in variable
MOVE.L (SP)+,CURRNT ;restore old 'CURRNT'
MOVE.L (SP)+,A0; and the old text pointer
IP4:
ADDQ.L #4,SP ; clean up the stack
BSR.L TSTC ; is the next thing a comma?
DC.B ',',IP5-$
BRA INPUT ; yes, more items
IP5:
BRA FINISH
DEFLT:
CMP.B #CR,(A0); empty line is OK
BEQ LT1 ;else it is 'LET'
LET:
BSR.L SETVAL ;do the assignment
BSR.L TSTC ;check for more 'LET' items
DC.B ',',LT1-$
BRA LET
LT1:
BRA 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 68000.
;*
LOAD:
MOVE.L TXTBGN,A0 ;set pointer to start of prog. area
MOVE.B #CR,D0 ;For a CP/M host, tell it we're ready...
BSR GOAUXO ;by sending a CR to finish PIP command.
LOD1:
BSR GOAUXI ; look for start of line
BEQ LOD1
CMP.B #'@',D0 ; end of program?
BEQ LODEND
CMP.B #':',D0 ; if not, is it start of line?
BNE LOD1 ;if not, wait for it
BSR GBYTE ;get first byte of line no.
MOVE.B D1,(A0)+ ;store it
BSR GBYTE ;get 2nd bye of line no.
MOVE.B D1,(A0)+ ; store that, too
LOD2:
BSR GOAUXI ; get another text char.
BEQ LOD2
MOVE.B D0,(A0)+ ;store it
CMP.B #CR,D0 ;is it the end of the line?
BNE LOD2 ;if not, go back for more
BRA LOD1 ;if so, start a new line
LODEND:
MOVE.L A0,TXTUNF ;set end-of program pointer
BRA WSTART ;back to direct mode
GBYTE:
MOVEQ #1,D2 ; get two hex characters from auxiliary
CLR D1 ;and store them as a byte in D1
GBYTE1:
BSR GOAUXI ; get a char.
BEQ GBYTE1
CMP.B #'A',D0
BCS GBYTE2
SUBQ.B #7,D0 ; if greater than 9, adjust
GBYTE2:
AND.B #0xF,D0 ;strip ASCII
LSL.B #4,D1 ;put nybble into the result
OR.B D0,D1
DBRA D2,GBYTE1 ;get another char.
RTS
SAVE:
MOVE.L TXTBGN,A0; set pointer to start of prog. area
MOVE.L TXTUNF,A1 ;set pointer to end of prog. area
SAVE1:
MOVE.B #CR,D0 ; send out a CR & LF (CP/M likes this)
BSR GOAUXO
MOVE.B #LF,D0
BSR GOAUXO
CMP.L A0,A1 ;are we finished?
BLS SAVEND
MOVE.B #':',D0 ; if not, start a line
BSR GOAUXO
MOVE.B (A0)+,D1 ;send first half of line no.
BSR PBYTE
MOVE.B (A0)+,D1 ;and send 2nd half
BSR PBYTE
SAVE2:
MOVE.B (A0)+,D0; get a text char.
CMP.B #CR,D0 ;is it the end of the line?
BEQ SAVE1 ;if so, send CR & LF and start new line
BSR GOAUXO ;send it out
BRA SAVE2 ;go back for more text
SAVEND:
MOVE.B #'@',D0 ; send end-of-program indicator
BSR GOAUXO
MOVE.B #CR,D0 ; followed by a CR & LF
BSR GOAUXO
MOVE.B #LF,D0
BSR GOAUXO
MOVE.B #0x1A,D0 ;and a control-Z to end the CP/M file
BSR GOAUXO
BRA WSTART ;then go do a warm start
PBYTE:
MOVEQ #1,D2 ; send two hex characters from D1's low byte
PBYTE1:
ROL.B #4,D1 ; get the next nybble
MOVE.B D1,D0
AND.B #0xF,D0 ; strip off garbage
ADD.B #'0',D0 ; make it into ASCII
CMP.B #'9',D0
BLS PBYTE2
ADDQ.B #7,D0 ;adjust if greater than 9
PBYTE2:
BSR GOAUXO ;send it out
DBRA D2,PBYTE1 ;then send the next nybble
RTS
;*
;*******************************************************************
;*
;* *** POKE *** & CALL ***
;*
;* 'POKE expr1,expr2' stores the byte from 'expr2' into the memory
;* address specified by 'expr1'.
;*
;* 'CALL 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.
;*
POKE:
BSR EXPR ;get the memory address
BSR.L TSTC ;it must be followed by a comma
DC.B ',',PKER-$
MOVE.L D0,-(SP) ;save the address
BSR EXPR ;get the byte to be POKE'd
MOVE.L (SP)+,A1 ;get the address back
MOVE.B D0,(A1) ;store the byte in memory
BRA FINISH
PKER:
BRA.L QWHAT ; if no comma, say "What?"
;*
;*******************************************************************
;* Graphics Commands added R. Finch
POINT:
BSR EXPR
BSR TSTC
DC.B ',',PKER-$
MOVE.L D0,-(SP)
BSR EXPR
MOVE.L (SP)+,D1
MOVE.L D0,D2
BSR DrawPixel
BRA FINISH
PENCOLOR:
BSR EXPR
MOVE.L d0,GRAPHICS
BRA FINISH
FILLCOLOR:
BSR EXPR
MOVE.L d0,GRAPHICS+4
BRA FINISH
SPRPOS:
BSR EXPR
BSR TSTC
DC.B ',',LINEERR1-$
MOVE.L D0,-(SP)
BSR EXPR
BSR TSTC
DC.B ',',LINEERR2-$
MOVE.L D0,-(SP)
BSR EXPR
MOVE.L D1,-(SP)
MOVE.L 8(SP),D1 ; D1 = sprite number
ANDI.L #7,D1
ASL.L #4,D1 ; D1 * 16
ADD.L #SPRCTRL,D1 ; D1 = register base
MOVE.L A1,-(SP) ; save off A1
MOVE.L D1,A1 ; A1 = register base
MOVE.W D0,2(A1) ; set Y position
MOVE.L 8(SP),D0
MOVE.W D0,0(A1) ; set X position
MOVE.L (SP)+,A1 ; get back A1
MOVE.L (SP)+,D1 ; get D1 back
ADD.L #8,SP ; pop stack
BRA FINISH
LINE:
BSR EXPR
BSR TSTC
DC.B ',',LINEERR1-$
MOVE.L D0,-(SP)
BSR EXPR
BSR TSTC
DC.B ',',LINEERR2-$
MOVE.L D0,-(SP)
BSR EXPR
BSR TSTC
DC.B ',',LINEERR3-$
MOVE.L D0,-(SP)
BSR EXPR
MOVE.W d0,GRAPHICS+14
MOVE.L (SP)+,d0
MOVE.W d0,GRAPHICS+12
MOVE.L (SP)+,d0
MOVE.W d0,GRAPHICS+10
MOVE.L (SP)+,d0
MOVE.W d0,GRAPHICS+8
MOVE.W #G_DRAWLINE,GRAPHICS+30
BRA FINISH
LINEERR1:
BRA.L QWHAT
LINEERR2:
ADDQ #4,SP
BRA.L QWHAT
LINEERR3:
ADD.L #8,SP
BRA.L QWHAT
;*
;*******************************************************************
;*
CALL:
BSR EXPR ;get the subroutine's address
TST.L D0 ;make sure we got a valid address
BEQ.L QHOW ; if not, say "How?"
MOVE.L A0,-(SP); save the text pointer
MOVE.L D0,A1
JSR (A1) ;jump to the subroutine
MOVE.L (SP)+,A0 ;restore the text pointer
BRA FINISH
;*
;*******************************************************************
;*
;* *** EXPR ***
;*
;* 'EXPR' evaluates arithmetical or logical expressions.
;* <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>::=(+,-,&,|)<EXPR3>(+,-,&,|)<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.
;*
EXPR:
BSR EXPR2
MOVE.L D0,-(SP); save <EXPR2> value
LEA TAB8,A1 ;look up a relational operator
LEA TAB8_1,A2
BRA EXEC ;go do it
XP11:
BSR XP18 ; is it ">="?
BLT XPRT0 ;no, return D0=0
BRA XPRT1 ;else return D0=1
XP12:
BSR XP18 ; is it "<>"?
BEQ XPRT0 ;no, return D0=0
BRA XPRT1 ;else return D0=1
XP13:
BSR XP18 ; is it ">"?
BLE XPRT0 ;no, return D0=0
BRA XPRT1 ;else return D0=1
XP14:
BSR XP18 ; is it "<="?
BGT XPRT0 ;no, return D0=0
BRA XPRT1 ;else return D0=1
XP15:
BSR XP18 ; is it "="?
BNE XPRT0 ;if not, return D0=0
BRA XPRT1 ;else return D0=1
XP15RT:
RTS
XP16:
BSR XP18 ; is it "<"?
BGE XPRT0 ;if not, return D0=0
BRA XPRT1 ;else return D0=1
XP16RT:
RTS
XPRT0:
CLR.L D0 ; return D0=0 (false)
RTS
XPRT1:
MOVEQ #1,D0; return D0=1 (true)
RTS
XP17:
MOVE.L (SP)+,D0 ;it's not a rel. operator
RTS ;return D0=<EXPR2>
XP18:
MOVE.L (SP)+,D0 ;reverse the top two stack items
MOVE.L (SP)+,D1
MOVE.L D0,-(SP)
MOVE.L D1,-(SP)
BSR EXPR2 ;do second <EXPR2>
MOVE.L (SP)+,D1
CMP.L D0,D1 ; compare with the first result
RTS ;return the result
EXPR2:
BSR.L TSTC ;negative sign?
DC.B '-',XP20-$
CLR.L D0 ; yes, fake '0-'
BRA XP26
XP20:
BSR.L TSTC
DC.B '!',XP21-$
CLR.L D0
MOVE.L D0,-(SP)
BSR EXPR3
NOT.L D0
JMP XP24
XP21:
BSR.L TSTC ; positive sign? ignore it
DC.B '+',XP22-$
XP22:
BSR EXPR3 ;first <EXPR3>
XP23:
BSR.L TSTC ; add?
DC.B '+',XP25-$
MOVE.L D0,-(SP) ;yes, save the value
BSR EXPR3 ;get the second <EXPR3>
XP24:
MOVE.L (SP)+,D1
ADD.L D1,D0 ; add it to the first <EXPR3>
BVS.L QHOW ; branch if there's an overflow
BRA XP23 ; else go back for more operations
XP25:
BSR.L TSTC ;subtract?
DC.B '-',XP27-$ ; was XP42-$
XP26:
MOVE.L D0,-(SP) ;yes, save the result of 1st <EXPR3>
BSR EXPR3 ;get second <EXPR3>
NEG.L D0 ;change its sign
JMP XP24 ;and do an addition
XP27:
BSR.L TSTC
DC.B '&',XP28-$
MOVE.L D0,-(SP)
BSR EXPR3
MOVE.L (SP)+,D1
AND.L D1,D0
BRA XP23
XP28:
BSR.L TSTC
DC.B '|',XP42-$
MOVE.L D0,-(SP)
BSR EXPR3
MOVE.L (SP)+,D1
OR.L D1,D0
BRA XP23
EXPR3:
BSR EXPR4 ;get first <EXPR4>
XP31:
BSR.L TSTC ; multiply?
DC.B '*',XP34-$
MOVE.L D0,-(SP); yes, save that first result
BSR EXPR4 ;get second <EXPR4>
MOVE.L (SP)+,D1
BSR.L MULT32 ; multiply the two
BRA XP31 ;then look for more terms
XP34:
BSR.L TSTC; divide?
DC.B '/',XP42-$
MOVE.L D0,-(SP); save result of 1st <EXPR4>
BSR EXPR4 ;get second <EXPR4>
MOVE.L (SP)+,D1
EXG D0,D1
BSR.L DIV32 ; do the division
BRA XP31 ;go back for any more terms
EXPR4:
LEA TAB4,A1 ; find possible function
LEA TAB4_1,A2
BRA EXEC
XP40:
BSR TSTV ; nope, not a function
BCS XP41 ;nor a variable
MOVE.L D0,A1
CLR.L D0
MOVE.L (A1),D0 ; if a variable, return its value in D0
EXP4RT:
RTS
XP41:
BSR.L TSTNUM ; or is it a number?
MOVE.L D1,D0
TST D2 ;(if not, # of digits will be zero)
BNE EXP4RT ; if so, return it in D0
PARN:
BSR.L TSTC ; else look for ( EXPR )
DC.B '(',XP43-$
BSR EXPR
BSR.L TSTC
DC.B ')',XP43-$
XP42:
RTS
XP43:
BRA.L QWHAT ; else say "What?"
;*
;* ===== Test for a valid variable name. Returns Carry=1 if not
;* found, else returns Carry=0 and the address of the
;* variable in D0.
TSTV:
BSR.L IGNBLK
CLR.L D0
MOVE.B (A0),D0 ; look at the program text
SUB.B #'@',D0
BCS TSTVRT ; C=1: not a variable
BNE TV1 ;branch if not "@" array
ADDQ #1,A0 ; If it is, it should be
BSR PARN ;followed by (EXPR) as its index.
ADD.L D0,D0
BCS.L QHOW ; say "How?" if index is too big
ADD.L D0,D0
BCS.L QHOW
MOVE.L D0,-(SP) ;save the index
BSR.L SIZE_ ;get amount of free memory
MOVE.L (SP)+,D1 ;get back the index
CMP.L D1,D0 ;see if there's enough memory
BLS.L QSORRY ;if not, say "Sorry"
MOVE.L VARBGN,D0 ;put address of array element...
SUB.L D1,D0 ;into D0
RTS
TV1:
CMP.B #27,D0 ;if not @, is it A through Z?
EOR #1,CCR
BCS TSTVRT ;if not, set Carry and return
ADDQ #1,A0 ; else bump the text pointer
;
CLR.L D1
MOVE.B (a0),D1
BSR CVT26
cmpi.b #0xff,d1
beq tv2
ADDQ #1,A0 ; bump text pointer
asl.l #5,D1
ADD.L D1,D0
tv2:
ADD D0,D0 ;compute the variable's address
ADD D0,D0
MOVE.L VARBGN,D1
ADD D1,D0 ;and return it in D0 with Carry=0
TSTVRT:
RTS
CVT26:
cmpi.b #'A',d1
blo CVT26a
cmpi.b #'Z',d1
bhi CVT26a
subi.b #'A',d1
rts
CVT26a:
moveq #-1,d1
rts
;*
;* ===== Multiplies the 32 bit values in D0 and D1, returning
;* the 32 bit result in D0.
;*
MULT32:
MOVE.L D1,D4
EOR.L D0,D4 ; see if the signs are the same
TST.L D0 ;take absolute value of D0
BPL MLT1
NEG.L D0
MLT1:
TST.L D1 ; take absolute value of D1
BPL MLT2
NEG.L D1
MLT2:
CMP.L #0xFFFF,D1 ;is second argument <= 16 bits?
BLS MLT3 ; OK, let it through
EXG D0,D1 ; else swap the two arguments
CMP.L #0xFFFF,D1 ;and check 2nd argument again
BHI.L QHOW ;one of them MUST be 16 bits
MLT3:
MOVE D0,D2 ; prepare for 32 bit X 16 bit multiply
MULU D1,D2 ;multiply low word
SWAP D0
MULU D1,D0 ;multiply high word
SWAP D0
;*** Rick Murray's bug correction follows:
TST D0 ;if lower word not 0, then overflow
BNE.L QHOW ; if overflow, say "How?"
ADD.L D2,D0 ; D0 now holds the product
BMI.L QHOW ; if sign bit set, it's an overflow
TST.L D4 ;were the signs the same?
BPL MLTRET
NEG.L D0 ;if not, make the result negative
MLTRET:
RTS
;*
;* ===== Divide the 32 bit value in D0 by the 32 bit value in D1.
;* Returns the 32 bit quotient in D0, remainder in D1.
;*
DIV32:
TST.L D1 ;check for divide-by-zero
BEQ.L QHOW ;if so, say "How?"
MOVE.L D1,D2
MOVE.L D1,D4
EOR.L D0,D4 ;see if the signs are the same
TST.L D0 ;take absolute value of D0
BPL DIV1
NEG.L D0
DIV1:
TST.L D1 ; take absolute value of D1
BPL DIV2
NEG.L D1
DIV2:
MOVEQ #31,D3 ; iteration count for 32 bits
MOVE.L D0,D1
CLR.L D0
DIV3:
ADD.L D1,D1 ; (This algorithm was translated from
ADDX.L D0,D0 ;the divide routine in Ron Cain's
BEQ DIV4 ;Small-C run time library.)
CMP.L D2,D0
BMI DIV4
ADDQ.L #1,D1
SUB.L D2,D0
DIV4:
DBRA D3,DIV3
EXG D0,D1 ; put rem. & quot. in proper registers
TST.L D4 ; were the signs the same?
BPL DIVRT
NEG.L D0 ; if not, results are negative
NEG.L D1
DIVRT:
RTS
;*
;* ===== The PEEK function returns the byte stored at the address
;* contained in the following expression.
;*
PEEK:
BSR PARN ; get the memory address
MOVE.L D0,A1
CLR.L D0 ;upper 3 bytes will be zero
MOVE.B (A1),D0 ; get the addressed byte
RTS ;and return it
;*
;* ===== The RND function returns a random number from 1 to
;* the value of the following expression in D0.
;* Uses hardware rand# generator R. Finch
RND:
BSR PARN ; get the upper limit
TST.L D0 ; it must be positive and non-zero
BEQ.L QHOW
BMI.L QHOW
MOVE.L D0,D1
MOVE.W RANDOM+2,D0
SWAP D0
MOVE.W RANDOM,D0
BCLR #31,D0 ; make sure it's positive
BSR DIV32 ;RND(n)=MOD(number,n)+1
MOVE.L D1,D0 ; MOD is the remainder of the div.
ADDQ.L #1,D0
RTS
;*
;* ===== The ABS function returns an absolute value in D0.
;*
ABS:
BSR PARN ;get the following expr.'s value
TST.L D0
BPL ABSRT
NEG.L D0 ;if negative, complement it
BMI.L QHOW ; if still negative, it was too big
ABSRT:
RTS
;* RTF
;* ===== The SGN function returns the sign value in D0.
;*
SGN:
BSR PARN ;get the following expr.'s value
TST.L D0
BEQ SGNRT
BMI SGNMI
MOVEQ #1,d0
SGNRT:
RTS
SGNMI:
MOVEQ #-1,d0
RTS
;*
;* ===== The SIZE function returns the size of free memory in D0.
;*
SIZE_:
MOVE.L VARBGN,D0 ;get the number of free bytes...
SUB.L TXTUNF,D0 ;between 'TXTUNF' and 'VARBGN'
RTS ;return the number in D0
;* RTF
;* ===== return the millisecond time value
;*
TICK:
move.l Milliseconds,d0
rts
TEMP:
bsr ReadTemp
andi.l #0xffff,d0
rts
;*
;*******************************************************************
;*
;* *** 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.
;*
;* '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.
;*
;* 'ENDCHK' checks if a command is ended with a CR. This is
;* required in certain commands, such as GOTO, RETURN, STOP, etc.
;*
;* 'ERROR' prints the string pointed to by A0. 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?".
;*
SETVAL:
BSR TSTV ; variable name?
BCS QWHAT ;if not, say "What?"
MOVE.L D0,-(SP); save the variable's address
BSR.L TSTC ; get past the "=" sign
DC.B '=',SV1-$
BSR EXPR ; evaluate the expression
MOVE.L (SP)+,A6
MOVE.L D0,(A6) ; and save its value in the variable
RTS
SV1:
BRA QWHAT ; if no "=" sign
FIN:
BSR.L TSTC ; *** FIN ***
DC.B ':',FI1-$
ADDQ.L #4,SP ; if ":", discard return address
BRA RUNSML ; continue on the same line
FI1:
BSR.L TSTC ; not ":", is it a CR?
DC.B CR,FI2-$
ADDQ.L #4,SP ; yes, purge return address
BRA RUNNXL ;execute the next line
FI2:
RTS ;else return to the caller
ENDCHK:
BSR.L IGNBLK
CMP.B #CR,(A0); does it end with a CR?
BNE QWHAT ; if not, say "WHAT?"
RTS
QWHAT:
MOVE.L A0,-(SP)
AWHAT:
LEA WHTMSG,A6
ERROR:
BSR.L PRMESG ; display the error message
MOVE.L (SP)+,A0 ;restore the text pointer
MOVE.L CURRNT,D0 ;get the current line number
BEQ WSTART ;if zero, do a warm start
CMP.L #-1,D0 ;is the line no. pointer = -1?
BEQ INPERR ;if so, redo input
MOVE.B (A0),-(SP) ;save the char. pointed to
CLR.B (A0) ;put a zero where the error is
MOVE.L CURRNT,A1 ;point to start of current line
BSR.L PRTLN ;display the line in error up to the 0
MOVE.B (SP)+,(A0) ;restore the character
MOVE.B #'?',D0 ; display a "?"
BSR GOOUT
CLR D0
SUBQ.L #1,A1 ;point back to the error char.
BSR.L PRTSTG ;display the rest of the line
BRA WSTART ;and do a warm start
QSORRY:
MOVE.L A0,-(SP)
ASORRY:
LEA SRYMSG,A6
BRA ERROR
QHOW:
MOVE.L A0,-(SP) ;Error: "How?"
AHOW:
LEA HOWMSG,A6
BRA ERROR
;*
;*******************************************************************
;*
;* *** GETLN *** FNDLN (& friends) ***
;*
;* 'GETLN' reads in input line into 'BUFFER'. It first prompts with
;* the character in D0 (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:
BSR GOOUT ;display the prompt
MOVE.B #' ',D0 ; and a space
BSR GOOUT
LEA BUFFER,A0; A0 is the buffer pointer
GL1:
BSR.L CHKIO; check keyboard
BEQ GL1 ; wait for a char. to come in
CMP.B #CTRLH,D0 ;delete last character?
BEQ GL3 ; if so
CMP.B #CTRLX,D0; delete the whole line?
BEQ GL4 ; if so
CMP.B #CR,D0 ; accept a CR
BEQ GL2
CMP.B #' ',D0 ; if other control char., discard it
BCS GL1
GL2:
MOVE.B D0,(A0)+; save the char.
BSR GOOUT ;echo the char back out
CMP.B #CR,D0 ; if it's a CR, end the line
BEQ GL7
CMP.L #(BUFFER+BUFLEN-1),A0 ;any more room?
BCS GL1 ; yes: get some more, else delete last char.
GL3:
MOVE.B #CTRLH,D0 ;delete a char. if possible
BSR GOOUT
MOVE.B #' ',D0
BSR GOOUT
CMP.L #BUFFER,A0 ;any char.'s left?
BLS GL1 ;if not
MOVE.B #CTRLH,D0; if so, finish the BS-space-BS sequence
BSR GOOUT
SUBQ.L #1,A0 ; decrement the text pointer
BRA GL1 ;back for more
GL4:
MOVE.L A0,D1 ; delete the whole line
SUB.L #BUFFER,D1; figure out how many backspaces we need
BEQ GL6 ;if none needed, branch
SUBQ #1,D1 ; adjust for DBRA
GL5:
MOVE.B #CTRLH,D0 ;and display BS-space-BS sequences
BSR GOOUT
MOVE.B #' ',D0
BSR GOOUT
MOVE.B #CTRLH,D0
BSR GOOUT
DBRA D1,GL5
GL6:
LEA BUFFER,A0 ;reinitialize the text pointer
BRA GL1 ;and go back for more
GL7:
MOVE.B #LF,D0 ; echo a LF for the CR
BSR GOOUT
RTS
;*
;*******************************************************************
;*
;* *** FNDLN (& friends) ***
;*
;* 'FNDLN' finds a line with a given line no. (in D1) in the
;* text save area. A1 is used as the text pointer. If the line
;* is found, A1 will point to the beginning of that line
;* (i.e. the high byte of the line no.), and flags are NC & Z.
;* If that line is not there and a line with a higher line no.
;* is found, A1 points there and flags are NC & NZ. If we reached
;* the end of the text save area and cannot find the line, flags
;* are C & NZ.
;* 'FNDLN' will initialize A1 to the beginning of the text save
;* area to start the search. Some other entries of this routine
;* will not initialize A1 and do the search.
;* 'FNDLNP' will start with A1 and search for the line no.
;* 'FNDNXT' will bump A1 by 2, find a CR and then start search.
;* 'FNDSKP' uses A1 to find a CR, and then starts the search.
;*
FNDLN:
CMP.L #0xFFFF,D1 ;line no. must be < 65535
BCC QHOW
MOVE.L TXTBGN,A1 ;init. the text save pointer
FNDLNP:
MOVE.L TXTUNF,A2 ;check if we passed the end
SUBQ.L #1,A2
CMPA.L A1,A2
BCS FNDRET ; if so, return with Z=0 & C=1
MOVE.B (A1),D2 ;if not, get a line no.
LSL.W #8,D2
MOVE.B 1(A1),D2
CMP.W D1,D2 ;is this the line we want?
BCS FNDNXT ;no, not there yet
FNDRET:
RTS ;return the cond. codes
FNDNXT:
ADDQ.L #2,A1; find the next line
FNDSKP:
CMP.B #CR,(A1)+ ;try to find a CR
BNE FNDSKP ;keep looking
BRA FNDLNP ;check if end of text
;*
;*******************************************************************
;*
;* *** MVUP *** MVDOWN *** POPA *** PUSHA ***
;*
;* 'MVUP' moves a block up from where A1 points to where A2 points
;* until A1=A3
;*
;* 'MVDOWN' moves a block down from where A1 points to where A3
;* points until A1=A2
;*
;* 'POPA' restores the 'FOR' loop variable save area from the stack
;*
;* 'PUSHA' stacks for 'FOR' loop variable save area onto the stack
;*
MVUP:
CMP.L A1,A3 ; see the above description
BEQ MVRET
MOVE.B (A1)+,(A2)+
BRA MVUP
MVRET:
RTS
MVDOWN:
CMP.L A1,A2 ; see the above description
BEQ MVRET
MOVE.B -(A1),-(A3)
BRA MVDOWN
POPA:
MOVE.L (SP)+,A6 ;A6 = return address
MOVE.L (SP)+,LOPVAR ;restore LOPVAR, but zero means no more
BEQ PP1
MOVE.L (SP)+,LOPINC ;if not zero, restore the rest
MOVE.L (SP)+,LOPLMT
MOVE.L (SP)+,LOPLN
MOVE.L (SP)+,LOPPT
PP1:
JMP (A6) ; return
PUSHA:
MOVE.L STKLMT,D1 ;Are we running out of stack room?
SUB.L SP,D1
BCC QSORRY ;if so, say we're sorry
MOVE.L (SP)+,A6 ;else get the return address
MOVE.L LOPVAR,D1 ;save loop variables
BEQ PU1 ;if LOPVAR is zero, that's all
MOVE.L LOPPT,-(SP) ;else save all the others
MOVE.L LOPLN,-(SP)
MOVE.L LOPLMT,-(SP)
MOVE.L LOPINC,-(SP)
PU1:
MOVE.L D1,-(SP)
JMP (A6) ;return
;*
;*******************************************************************
;*
;* *** PRTSTG *** QTSTG *** PRTNUM *** PRTLN ***
;*
;* 'PRTSTG' prints a string pointed to by A1. 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 D0 by the
;* caller.
;*
;* '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 2 bytes of the caller are
;* skipped over (usually a short branch instruction).
;*
;* 'PRTNUM' prints the 32 bit number in D1, leading blanks are added if
;* needed to pad the number of spaces to the number in D4.
;* However, if the number of digits is larger than the no. in
;* D4, all digits are printed anyway. Negative sign is also
;* printed and counted in, positive sign is not.
;*
;* 'PRTLN' prints the saved text line pointed to by A1
;* with line no. and all.
;*
PRTSTG:
MOVE.B D0,D1 ; save the stop character
PS1:
MOVE.B (A1)+,D0 ;get a text character
CMP.B D0,D1 ;same as stop character?
BEQ PRTRET ;if so, return
BSR GOOUT ;display the char.
CMP.B #CR,D0 ;;is it a C.R.?
BNE PS1 ;no, go back for more
MOVE.B #LF,D0 ; yes, add a L.F.
BSR GOOUT
PRTRET:
RTS ;then return
QTSTG:
BSR.L TSTC; *** QTSTG ***
DC.B '"',QT3-$
MOVE.B #'"',D0 ; it is a "
QT1:
MOVE.L A0,A1
BSR PRTSTG ;print until another
MOVE.L A1,A0
MOVE.L (SP)+,A1; pop return address
CMP.B #LF,D0 ; was last one a CR?
BEQ RUNNXL ;if so, run next line
QT2:
ADDQ.L #2,A1 ; skip 2 bytes on return
JMP (A1) ;return
QT3:
BSR.L TSTC ; is it a single quote?
DC.B '\'',QT4-$
MOVE.B #'''',D0 ; if so, do same as above
BRA QT1
QT4:
BSR.L TSTC ;is it an underline?
DC.B '_',QT5-$
MOVE.B #CR,D0 ;if so, output a CR without LF
BSR.L GOOUT
MOVE.L (SP)+,A1 ;pop return address
BRA QT2
QT5:
RTS ;none of the above
PRTNUM:
movem.l d0/d1/d4/a1/a5,-(a7)
lea scratch1,a5
move.l d1,d0
jsr HEX2DEC
lea scratch1,a5
PN8:
move.b (a5)+,d0
beq PN7
dbra d4,PN8
PN7:
tst.w d4
bmi PN9
MOVE.B #' ',D0 ; display the required leading spaces
BSR GOOUT
DBRA D4,PN7
PN9:
lea scratch1,a1
jsr DisplayString
movem.l (a7)+,d0/d1/d4/a1/a5
rts
;PRTNUM
; MOVE.L D1,D3 ; save the number for later
; MOVE.L D4,-(SP) ;save the width value
; MOVE.W #0xFFFF,-(SP) ;flag for end of digit string
; TST.L D1 ;is it negative?
; BPL PN1 ;if not
; NEG.L D1 ; else make it positive
; SUBQ #1,D4 ; one less for width count
;PN1:
; DIVU #10,D1 ; get the next digit
; BVS PNOV ; overflow flag set?
; MOVE.L D1,D0 ; if not, save remainder
; AND.L #0xFFFF,D1 ;strip the remainder
; BRA TOASCII ;skip the overflow stuff
;PNOV:
; MOVE D1,D0 ; prepare for long word division
; CLR.W D1 ;zero out low word
; SWAP D1 ;high word into low
; DIVU #10,D1 ; divide high word
; MOVE D1,D2 ; save quotient
; MOVE D0,D1 ; low word into low
; DIVU #10,D1 ; divide low word
; MOVE.L D1,D0 ; D0 = remainder
; SWAP D1 ; R/Q becomes Q/R
; MOVE D2,D1 ; D1 is low/high
; SWAP D1 ; D1 is finally high/low
;TOASCII:
; SWAP D0 ; get remainder
; MOVE.W D0,-(SP); stack it as a digit
; SWAP D0
; SUBQ #1,D4 ; decrement width count
; TST.L D1 ;if quotient is zero, we're done
; BNE PN1
; SUBQ #1,D4 ; adjust padding count for DBRA
; BMI PN4 ;skip padding if not needed
;PN3:
; MOVE.B #' ',D0 ; display the required leading spaces
; BSR GOOUT
; DBRA D4,PN3
;PN4:
; TST.L D3 ;is number negative?
; BPL PN5
; MOVE.B #'-',D0 ; if so, display the sign
; BSR GOOUT
;PN5:
; MOVE.W (SP)+,D0 ;now unstack the digits and display
; BMI PNRET ;until the flag code is reached
; ADD.B #'0',D0 ; make into ASCII
; BSR GOOUT
; BRA PN5
;PNRET:
; MOVE.L (SP)+,D4 ;restore width value
; RTS
PRTLN:
CLR.L D1
MOVE.B (A1)+,D1 ;get the binary line number
LSL #8,D1
MOVE.B (A1)+,D1
MOVEQ #5,D4 ;display a 5 digit line no.
BSR PRTNUM
MOVE.B #' ',D0 ; followed by a blank
BSR GOOUT
CLR D0 ;stop char. is a zero
BRA PRTSTG ; display the rest of the line
;*
;* ===== Test text byte following the call to this subroutine. If it
;* equals the byte pointed to by A0, return to the code following
;* the call. If they are not equal, branch to the point
;* indicated by the offset byte following the text byte.
;*
TSTC:
BSR IGNBLK ;ignore leading blanks
MOVE.L (SP)+,A1 ;get the return address
MOVE.B (A1)+,D1 ;get the byte to compare
CMP.B (A0),D1 ;is it = to what A0 points to?
BEQ TC1 ;if so
CLR.L D1 ;If not, add the second
MOVE.B (A1),D1 ; byte following the call to
ADD.L D1,A1 ; the return address.
JMP (A1) ;jump to the routine
TC1:
ADDQ.L #1,A0 ; if equal, bump text pointer
ADDQ.L #1,A1 ; Skip the 2 bytes following
JMP (A1) ;the call and continue.
;*
;* ===== See if the text pointed to by A0 is a number. If so,
;* return the number in D1 and the number of digits in D2,
;* else return zero in D1 and D2.
;*
TSTNUM:
CLR.L D1 ;initialize return parameters
CLR D2
BSR IGNBLK ;skip over blanks
TN1:
CMP.B #'0',(A0) ; is it less than zero?
BCS TSNMRET ;if so, that's all
CMP.B #'9',(A0) ; is it greater than nine?
BHI TSNMRET ;if so, return
CMP.L #214748364,D1 ;see if there's room for new digit
BCC QHOW ;if not, we've overflowd
MOVE.L D1,D0 ; quickly multiply result by 10
ADD.L D1,D1
ADD.L D1,D1
ADD.L D0,D1
ADD.L D1,D1
MOVE.B (A0)+,D0 ;add in the new digit
AND.L #0xF,D0
ADD.L D0,D1
ADDQ #1,D2 ;increment the no. of digits
BRA TN1
TSNMRET:
RTS
;*
;* ===== Skip over blanks in the text pointed to by A0.
;*
IGNBLK:
CMP.B #' ',(A0) ; see if it's a space
BNE IGBRET ;if so, swallow it
IGB1:
ADDQ.L #1,A0 ; increment the text pointer
BRA IGNBLK
IGBRET:
RTS
;*
;* ===== Convert the line of text in the input buffer to upper
;* case (except for stuff between quotes).
;*
TOUPBUF:
LEA BUFFER,A0 ;set up text pointer
CLR.B D1 ;clear quote flag
TOUPB1:
MOVE.B (A0)+,D0 ;get the next text char.
CMP.B #CR,D0 ;is it end of line?
BEQ TOUPBRT ;if so, return
CMP.B #'"',D0 ; a double quote?
BEQ DOQUO
CMP.B #'''',D0 ; or a single quote?
BEQ DOQUO
TST.B D1 ;inside quotes?
BNE TOUPB1 ;if so, do the next one
BSR TOUPPER ;convert to upper case
MOVE.B D0,-(A0); store it
ADDQ.L #1,A0
BRA TOUPB1 ;and go back for more
TOUPBRT:
RTS
DOQUO:
TST.B D1 ; are we inside quotes?
BNE DOQUO1
MOVE.B D0,D1 ; if not, toggle inside-quotes flag
BRA TOUPB1
DOQUO1:
CMP.B D0,D1 ; make sure we're ending proper quote
BNE TOUPB1 ;if not, ignore it
CLR.B D1 ;else clear quote flag
BRA TOUPB1
;*
;* ===== Convert the character in D0 to upper case
;*
TOUPPER:
CMP.B #'a',D0 ; is it < 'a'?
BCS TOUPRET
CMP.B #'z',D0 ; or > 'z'?
BHI TOUPRET
SUB.B #32,D0 ;if not, make it upper case
TOUPRET:
RTS
;*
;* 'CHKIO' checks the input. If there's no input, it will return
;* to the caller with the Z flag set. If there is input, the Z
;* flag is cleared and the input byte is in D0. However, if a
;* control-C is read, 'CHKIO' will warm-start BASIC and will not
;* return to the caller.
;*
CHKIO:
BSR.L GOIN ; get input if possible
BEQ CHKRET ;if Zero, no input
CMP.B #CTRLC,D0 ;is it control-C?
BNE CHKRET ;if not
BRA.L WSTART ;if so, do a warm start
CHKRET:
RTS
;*
;* ===== Display a CR-LF sequence
;*
CRLF1:
LEA CLMSG,A6
;*
;* ===== Display a zero-ended string pointed to by register A6
;*
PRMESG:
MOVE.B (A6)+,D0 ;get the char.
BEQ PRMRET ;if it's zero, we're done
BSR GOOUT ;else display it
BRA PRMESG
PRMRET:
RTS
;******************************************************
;* The following routines are the only ones that need *
;* to be changed for a different I/O environment. *
;******************************************************
;UART EQU 0xFFDC0A00
;UART_LS EQU UART+1
;UART_CTRL EQU UART+7
;KEYBD EQU 0xFFDC0000
;*
;* ===== Output character to the console (Port 1) from register D0
;* (Preserves all registers.)
;*
OUTC:
MOVEM.L D0/D1,-(SP)
MOVE.L D0,D1
JSR DisplayChar
MOVEM.L (SP)+,D0/D1
RTS
;*
;* ===== Input a character from the console into register D0 (or
;* return Zero status if there's no character available).
;*
INC:
MOVE.W KEYBD,D0 ;is character ready?
BPL INCRET0 ;if not, return Zero status
CLR.W KEYBD+2 ; clear keyboard strobe line
AND.W #0xFF,D0 ;zero out the high bit
RTS
INCRET0
MOVEQ #0,D0
RTS
;*
;* ===== Output character to the host (Port 2) from register D0
;* (Preserves all registers.)
;*
AUXOUT:
BTST #5,UART_LS ;is port ready for a character?
BEQ AUXOUT ;if not, wait for it
MOVE.B D0,UART ;out it goes.
RTS
;*
;* ===== Input a character from the host into register D0 (or
;* return Zero status if there's no character available).
;*
AUXIN:
BTST #0,UART_LS ;is character ready?
BEQ AXIRET ;if not, return Zero status
MOVE.B UART,D0 ;else get the character
AND.B #0x7F,D0 ;zero out the high bit
AXIRET:
RTS
;*
;* ===== Return to the resident monitor, operating system, etc.
;*
BYEBYE:
JMP Monitor
; MOVE.B #228,D7 ;return to Tutor
; TRAP #14
INITMSG:
DC.B CR,LF,'Gordo\'s MC68000 Tiny BASIC, v1.3',CR,LF,LF,0
OKMSG:
DC.B CR,LF,'OK',CR,LF,0
HOWMSG:
DC.B 'How?',CR,LF,0
WHTMSG:
DC.B 'What?',CR,LF,0
SRYMSG:
DC.B 'Sorry.'
CLMSG:
DC.B CR,LF,0
; DC.B 0 ;<- for aligning on a word boundary
even
LSTROM EQU $
; end of possible ROM area