view src/lwbasic.s @ 73:2d52cd154ed1

Split some code into separate files for easier management Because the source for lwbasic is so large, split it into several different files to make it easier to navigate and modify. This is part one of the split.
author William Astle <lost@l-w.ca>
date Sun, 06 Aug 2023 00:12:29 -0600
parents f492fa6f6dc8
children e74d00ac6b79
line wrap: on
line source

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; LWBasic Version 0.1
; Copyright © 2022 Lost Wizard Enterprises Incorporated
;
; This is LWBasic, a replacement Basic ROM system for the TRS-80 Color Computer which
; is most definitely not binary compatible with the stock ROMs.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                *pragmapush list
                *pragma nolist
                *pragma noexpandcond
                *pragma cescapes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                
; Utility macros
;
; skip next byte; flags preserved
skip1           macro noexpand
                fcb 0x21                        ; opcode for BRN
                endm
; skip next byte and load nonzero to A
skip1lda        macro noexpand
                fcb 0x86                        ; opcode for LDA immediate
                endm
; skip next byte and load nonzero to B
skip1ldb        macro noexpand
                fcb 0xc6                        ; opcoe for LDB immediate
                endm
; skip next 2 bytes; clobbers flags
skip2           macro noexpand
                fcb 0x8c                        ; opcode for CMPX immediate
                endm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Include the various sub source files
                include defs.s
                include vars.s
                *pragmapop list
                org 0x8000                      ; the hardware puts the ROMs here; it's not negotiable
ROMSTART        equ *
                *pragmapush list
                *pragma nolist
                include init.s
                include keyb.s
                include irq.s
                include consscr.s
                *pragmapop list
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; General I/O handling package
;
; These routines operate on the I/O channel specified by filenum. The defined values of filenum are:
;
; 0: keyboard/screen console
;
; Read a line from the active file into linebuff. The resulting line will be NUL terminated leading to at most
; linbuffsize-1 character input. The trailing CR/LF will not be included. The input will be echoed if linebuffecho is
; enabled. Exit with the length of the input line in B.
readline        ldx #linebuff                   ; point to line input buffer
                clr ,x                          ; make sure buffer is NUL terminated
readline0       bsr readchr                     ; read an input character
                bcs readline1                   ; brif not EOF
                cmpa #0x0d                      ; CR (carriage return)
                beq readline1                   ; brif so - return
                cmpa #0x03                      ; BREAK?
                bne readline3                   ; brif not
                coma                            ; set carry for irregular exit
                skip1
readline1       clra                            ; clear carry for regular exit
                pshs cc                         ; save carry state
                lda readlinenoecho              ; are we echoing?
                bne readline2                   ; brif not
                lda #0x0d                       ; echo carriage return + line feed
                bsr writechr
readline2       tfr x,d                         ; get end address after input
                subd #linebuff                  ; subtract start of buffer; D is now length and C is clear
                clr ,x                          ; make sure line is NUL terminated
                puls cc,pc                      ; restore BREAK flag (C) and return
readline3       cmpa #0x08                      ; backspace?
                bne readline4                   ; brif not
                cmpx #linebuff                  ; at start of buffer?
                beq readline0                   ; brif so - do nothing
                leax -1,x                       ; move back buffer pointer
                bsr readlinee                   ; write a BS
                lda #0x20                       ; write a space
                bsr readlinee
                lda #0x08                       ; and finally a BS
                bsr readlinee
                bra readline0                   ; go process more characters
readline4       cmpa #0x0c                      ; form feed?
                bne readline5                   ; brif not
                bsr readlinee                   ; go echo character if needed
                bra readline                    ; go restart line entry
readline5       cmpa #0x20                      ; is it non-printing?
                blo readline0                   ; brif so - don't store it and continue
                bsr readlines                   ; stash character in buffer and echo if necessary
                bra readline0                   ; go get another character
readlines       cmpx #linebuff+linebuffsize-1   ; is the line buffer full?
                bhs readlinee0                  ; brif so - don't store character OR echo it
                sta ,x+                         ; stash character
readlinee       ldb readlinenoecho              ; are we echoing?
                bne readlinee0                  ; brif not
                bsr writechr                    ; echo the character
readlinee0      rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write a newline if not at left margin. This will unconditinally output a newline for devices where the horizontal
; position is not knowable.
writecondnl     lda filenum                     ; get file number
                bne writenl                     ; brif not screen - we'll do it unconditionally
                lda console_curptr+1            ; get LSB of cursor pointer
                anda #0x1f                      ; keep only the low 5 bits (32 characters per line)
                beq writecondnl0                ; brif no newline is needed
                ; fallthrough intended
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write a newline to the chosen device.
writenl         lda #0x0d                       ; code for carriage return - will serve as newline
                ; fallthrough intended
; Write a character to the active file; all registers preserved but C will be set if the output file cannot handle
; an output character (doesn't exist, etc.)
writechr        tst filenum                     ; is it screen?
                beq writechr_scr                ; brif writing to screen
                orcc #1                         ; unknown device flag
writecondnl0    rts
; Handle output to the screen. This is where we convert CR to CRLF
writechr_scr    jsr console_outchr              ; output the character
                cmpa #0x0d                      ; was it CR?
                bne writechr_scr0               ; brif not
                lda #0x0a                       ; ouptut an LF
                jsr console_outchr
                lda #0x0d                       ; restore original value
writechr_scr0   andcc #0xfe                     ; clear error flag
                rts
; Read a character from the active file and return it in A; in the event that EOF is detected, readeof will be nonzero
; and the call will return with carry set.
readchr         clr fileeof                     ; flag not end of file (and clear carry)
                lda filenum                     ; get input file number
                beq readchr_kb                  ; brif keyboard input
                com fileeof                     ; flag end of file (C set and fileeof nonzero)
                rts
; Read character from keyboard; blink cursor while doing so
readchr_kb      pshs b                          ; preserve B as temp storage
                ldb [console_curptr]            ; get character at cursor
                inc console_blnkdel             ; activate cursor blinking (first interrupt will cycle it)
readchr_kb0     jsr keyb_getkey                 ; read keyboard
                bcc readchr_kb1                 ; brif we got a result
                cwai #0xaf                      ; wait for interrupt to scan keyboard
                bra readchr_kb0                 ; see if we have something yet
readchr_kb1     clr console_blnkdel             ; disable cursor blinking
                stb [console_curptr]            ; restore screen character
                clrb                            ; clear carry to indicate not eof
                puls b,pc                       ; restore temp and return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write a character to the selected output device. If the device is one that does not support actual lower case, then
; conver the character to upper case. Otherwise, pass it through as is. Currently, only the console screen falls into
; this category. This *will* modify the character in A if a change is made.
writechrconduc  tst filenum                     ; is it screen?
                bne writechr                    ; brif not - just output it
                tst console_truelc              ; does the current text screen support actual lower case?
                bne writechr                    ; brif so - just output character
                cmpa #'a                        ; is it lower case?
                blo writechr                    ; brif not
                cmpa #'z                        ; is it still lower case?
                bhi writechr                    ; brif not
                suba #0x20                      ; shift to upper case
                bra writechr                    ; go output it
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write a NUL terminated string at X to the screen. Conditionally convert to upper case based on the screen type.
writestrconduc0 bsr writechrconduc              ; output the character
writestrconduc  lda ,x+                         ; fetch character from string
                bne writestrconduc0             ; brif not end of string
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fetch next input character, skip spaces. This is structured the way it is to avoid burning any register except A
; which is used for the returned value. Z will be set if the input character is NUL or a colon. C will be set if the
; input character is an ASCII digit. This allows testing Z to identify the end of a command due to either a colon or
; the end of a line.
;
; Compared to Color Basic, the instruction sequence only varies in the handling of the LDA. In Color Basic, the sequence
; is an LDA extended followed by a JMP extended. This totals to 9 cycles (5 for LDA, 4 for JMP). In LWBasic, an LDA
; with extended indirect addressing is used. This also totals 9 cycles. The only other difference is when a space is
; detected where the branch can be direct to the nextchar code instead of having to branch around a direct page JUMP
; which saves 3 cycles for the case where a space is detected. In other words, this is only slower by virtue of the
; fact that it is called with an extended JSR instead of a direct JSR which causes one extra cycle to be used there
; and one extra byte for each call to nextchar or curchar.
;
; On 6309, native move saves an extra cycle in the LDA sequence using the LDA extended followed by JMP extended
; sequence.
;
; This whole thing could be sped up by keeping the input pointer in a register. However, retaining the ability to
; use Y without having to save it first is likely more beneficial.
nextchar        inc inputptr+1                  ; bump LSB of input pointer
                bne curchar                     ; brif no carry
                inc inputptr                    ; bump MSB
curchar         lda [inputptr]                  ; read the byte
                cmpa #'9+1                      ; clear C if above ASCII digits, Z if colon
                bhs curchar0                    ; brif above the ASCII digits
                cmpa #0x20                      ; is it a space?
                beq nextchar                    ; brif so - skip over it
                suba #'0                        ; clever way to set C if >= ASCII 0, Z if zero
                suba #-'0
curchar0        rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This is exactly the same as nextchar except it doesn't skip spaces. Unfortunately, for efficiency purposes, we need
; to actually duplicate code here.
nextcharraw     inc inputptr+1                  ; bump LSB of input pointer
                bne curchar                     ; brif no carry
                inc inputptr                    ; bump MSB
curcharraw      lda [inputptr]                  ; fetch the byte
                cmpa #'9+1                      ; clear C if above digits, set Z if colon
                bhs curcharraw0                 ; brif above digits
                suba #'0                        ; clever way to set C if >= ASCII 0, Z if zero
                suba #-'0
curcharraw0     rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The error handler
;
; Enter with the error number in B. This routine will do some cleanup and handle any ON ERROR GOTO handler that
; may be active.
;
; Note the error message lookup does not need to be efficient which is why the lookup just runs through the list
; of error messages in sequence looking for NUL terminators. The specific handling of B (error number) below avoids
; issues if there happen to be error codes above 128.
ERROR           clr filenum                     ; reset display device to console
                jsr writecondnl                 ; do a newline if needed (will preserve B)
                ldx #errormsg                   ; point to error message list
                incb                            ; account for decb below
                bra ERROR1                      ; go search for correct message
ERROR0          lda ,x+                         ; end of message?
                bne ERROR0                      ; brif not end of message
ERROR1          decb                            ; at the correct one?
                bne ERROR0                      ; brif not - skip to next one
ERROR2          jsr writestrconduc              ; output error message
                ldu curline                     ; are we in immediate mode?
                beq ERROR3                      ; brif so
                ldx #inmsg                      ; point to " in "
                jsr writestrconduc              ; output " in "
                ldd 2,u                         ; get line number
                jsr print_uint16d               ; display the line number
ERROR3          lds freetop                     ; reset the stack pointer (error routine could be called anywhere)
                clr ,-s                         ; reset the call stack
                sts stackptr
                ; fall through to immediate mode intentional
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Immediate mode handler
immediate       jsr writecondnl                 ; do newline if required
                ldx #prompt                     ; point to prompt string
                jsr console_outstrn
immediate0      jsr readline                    ; read input line
                bcs immediate0                  ; brif ended with BREAK
                ldx #linebuff                   ; point to start of line input buffer
                stx inputptr                    ; set input pointer
                jsr curchar                     ; skip spaces and set flags
                bcs immediate1                  ; brif there's a line number
                tsta                            ; is there anything there at all (end of line)?
                beq immediate0                  ; brif not - read another line
                ldx inputptr                    ; get the modified input pointer processing above
                jsr tokenize                    ; tokenize the line at inputptr, return with result at tokebuff and X
                jsr interpretline               ; go interpret the tokenized line
                bra immediate                   ; go handle another line
immediate1      bsr parse_lineno                ; parse the line number
                bsr prog_findline               ; go see if the line is in the program
                bne immediate3                  ; brif not - no need to delete it
                ldu ,x                          ; get next line pointer which is where we start the copy from
                leay ,x                         ; use temp pointer for copying
immediate2      lda ,u+                         ; get source byte
                sta ,y+                         ; stash it
                cmpu vartab                     ; did we reach the end of the program text?
                blo immediate2                  ; brif not
                sty vartab                      ; save new end of program
immediate3      jsr curchar                     ; skip any spaces after line number
                tsta                            ; is it the end of input (don't test for colon)
                beq immediate6                  ; brif so - we don't need to insert a line
                pshs x                          ; save program insert location and line number
                ldx inputptr                    ; point to line text
                jsr tokenize                    ; tokenize line, get length to D
                leay ,x                         ; save tokenized line pointer
                addd #4                         ; account for next line pointer and line number
                ldx vartab                      ; get start of copy location
                leau d,x                        ; set destination copy location D bytes further up
                stu vartab                      ; save new end of program
immediate4      lda ,-x                         ; get byte from program
                sta ,-u                         ; stash it above the empty space
                cmpx ,s                         ; did we reach the insertion point?
                bne immediate4                  ; brif not - keep going
                leas 2,s                        ; clear insertion location
                stu ,x++                        ; set next line pointer to not null
                ldd binval                      ; set the line number for the program
                std ,x++
immediate5      lda ,y+                         ; get byte from tokenized line
                sta ,x+                         ; stash it in the program
                bne immediate5                  ; brif not at end of tokenized line (see note for fixlineptrs)
immediate6      bsr prog_fixlineptrs            ; fix up line pointers (all of them)
                ldx vartab                      ; clear out variables
                stx objecttab
                stx freestart
                bra immediate0                  ; go handle more input
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fix up next line pointers. Enter at prog_fixlineptrs to do the entire program. Enter at prog_fixlineptrsx to start
; at the line pointered to by X, which MUST NOT point to the end of the program.
;
; Works by simply scanning for a NUL in the program text after a line header (pointer to next line and line number)
; and uses that as the new next line pointer. A NULL next line pointer flags the end of the program.
;
; Observation: if the program text format is changed such that it can include NULs embedded within a line, this routine
; will need to be updated to grok that.
prog_fixlineptrs
                ldx progtext                    ; point to start of program
prog_fixlineptrsx
                ldu ,x                          ; are we at the end of the program?
                beq prog_findline2              ; brif not (borrow RTS from findline)
                leau 4,x                        ; point to line text (past pointer and line number)
prog_fixlineptrs1
                lda ,u+                         ; are we at the end of this line?
                bne prog_fixlineptrs1           ; brif not
                stu ,x                          ; set the next pointer for the previous line
                leax ,u                         ; move to the next line
                bra prog_fixlineptrsx           ; go handle the next line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Find a line in the program. Returns with C set and Z clear if no match and C clear and Z set if a match is found. X
; will point to either the exact matched line *or* the line that would be immediately after the desired line number if
; the line had been present, which could be the end of the program. D and U are clobbered. Enter at prog_findlinex to
; start searching at the line pointed to by X. Enter at prog_findline to start at the beginning of the program. Enter
; with the desired line number in binval.
prog_findlinecl ldx curline                     ; get current line pointer
                beq prog_findline               ; brif immediate mode
                ldd binval                      ; get desired line number
                cmpd 2,x                        ; is the desired line number >= current line?
                beq prog_findline2              ; brif this is the right line (optimizes goto self)
                bhi prog_findlinex              ; brif desired line higher: start here instead of program start
prog_findline   ldx progtext                    ; point to start of program
prog_findlinex  ldu binval                      ; get line number to search for
prog_findline0  ldd ,x                          ; end of program?
                beq prog_findline1              ; brif not
                cmpu 2,x                        ; does line number match? Z set if so, clear if not; C set not found
                bls prog_findline2
                ldx ,x                          ; move to next line
                bra prog_findline0              ; see if we found the line yet
prog_findline1  coma                            ; set carry for not found; also clears Z because D is zero from above
prog_findline2  rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Parse a line number and return it in binval; raise syntax error if the line number overflows 16 bits unsigned.
; Preserves; registers except D. This will accept the entire 16 bit unsigned number range which is why there is
; a BCS after every shift or add. Enter with the input pointer pointing to the number to parse.
parse_lineno    ldd zero                        ; clear out accumlator but preserve carry flag
                std binval
                jsr curchar                     ; set flags on current character; skip spaces
                bcc parse_lineno1               ; brif first character wasn't a digit - default to zero
parse_lineno0   suba #0x30                      ; adjust to binary digit
                pshs a                          ; save digit so we can add it later
                ldd binval                      ; get accumulated number
                lslb                            ; multiply accumulator by 10
                rola                            ; times 2
                bcs SNERROR                     ; brif overflow
                lslb
                rola                            ; times 4
                bcs SNERROR                     ; brif overflow
                addd binval                     ; times 5 (add orignal value to times 4)
                bcs SNERROR                     ; brif overflow
                lslb
                rola                            ; times 10
                bcs SNERROR                     ; brif overflow
                addb ,s+                        ; add in accumulated digit
                adca #0
                bcs SNERROR                     ; brif overflow
                std binval                      ; save accumulated number
                jsr nextcharraw                 ; get next input character; DO NOT skip spaces
                bcs parse_lineno0               ; brif it's also a digit
parse_lineno1   rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The NEW command.
;
; This also includes several useful entry points:
;
; cmd_newraw: does the whole NEW but without any syntax checks
; cmd_newinptr: skips clearing the program text
; cmd_newvars: clears variables and resets the stack and other misc state
; cmd_newstack: just reset the stack and other misc state
cmd_new         bne parse_lineno1               ; brif there was an argument - don't wipe things out on syntax error
cmd_newraw      ldx progtext                    ; point to start of program
                clr -1,x                        ; make sure there's a NUL before the start of the program
                clr ,x+                         ; put a NULL pointer at the start of the program
                clr ,x+
                stx vartab                      ; set start of variables after that
cmd_newinptr    ldx progtext                    ;* set input pointer to the NUL before the program; this will cause the
                leax -1,x                       ;* the interpreter to drop to immediate mode no matter what it was
                stx inputptr                    ;* executing before this call if called from the main loop
cmd_newvars     ldx memsize                     ; get top of memory
                stx stringtab                   ; clear out string space
                ldx vartab                      ; get start of variables
                stx objecttab                   ; set start of large objects (arrays) there too (clear vars)
                stx freestart                   ; set start of free memory (end of large objects) (clear arrays)
cmd_newstack    ldx #stringstackend             ; reset string stack (string stack counts down)
                stx stringstackptr
                ldx ,s                          ; get return address
                lds freetop                     ; reset stack to top of memory
                clr ,-s                         ; put a flag to stop stack searches (NEXT, RETURN)
                sts stackptr                    ; reset pointer for call stack
                clr contstmt                    ; clear "CONT" destination
                clr contstmt+1
                jmp ,x                          ; return to caller
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Main interpretation loop
;
; Enter at interpret with inputptr pointing to the code stream to interpret.
; Enter at interpretline with X pointing to the command stream to interpret which will return to the caller one the
;     command stream has completed. STOP or BREAK will return with carry set while END or falling off the end of the
;     code will return with carry clear. In the event of an error, the usual error processing will be done and control
;     will return to immediate mode with the stack reset.
interpret       jsr breakcheck                  ; check for BREAK
                bcs cmd_stop0                   ; brif BREAK detected - go stop the program
                ldx inputptr                    ; get interpration address
                stx curstmt                     ; save address of the current statement (needed for some stuff)
                lda ,x+                         ; are we at the end of the line?
                beq interpret0                  ; brif so
                cmpa #':                        ; end of statement?
                beq interpret3                  ; brif so - do a statement
SNERROR         ldb #err_sn                     ; raise a syntax error
                jmp ERROR
interpret0      sta endflag                     ; flag the program exit state as "END" (will be zero)
                ldd curline                     ; were we in immediate mode?
                bne interpret1                  ; brif not
                clra                            ; clear carry to indicate normal exit
                rts                             ; return to caller
interpret1      ldd ,x                          ; are we at the end of the program?
                beq interpret4                  ; brif so - bail out
                stx curline                     ; save pointer to current line
                leax 3,x                        ; set input pointer one before the start of the line text
interpret2      stx inputptr
interpret3      jsr nextchar                    ; fetch first character of next statement
                beq interpret                   ; brif end of statement - do the next statement dance
                tsta                            ; set flags properly for token
                lbpl cmd_let                    ; brif no command - do assignment (LET command is optional)
                ldx #primaryjump                ; point to jump table
                anda #0x7f                      ; lose bit 7
                leax a,x                        ; get half way to the correct offset
                ldx a,x                         ; get the address the other half of the way from here
                jsr nextchar                    ; skip past token and set flags
                jsr ,x                          ; call the routine
                bra interpret                   ; go handle the next statement dance
interpret4      bsr cmd_stop1                   ; make sure stack is aligned correctly (will not return)
interpretline   clr curline                     ; blank out current line pointer (for immediate mode)
                clr curline+1
                leax -1,x                       ; move back before start of code stream
                bra interpret2                  ; go interpret this statement and then continue with stuff
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Check for character in B and raise a syntax error if not found at current input pointer. If it is found, fetch the
; next input character.
syncheckb       cmpb [inputptr]                 ; do we have a syntax match?
                bne SNERROR                     ; brif not
                jmp nextchar                    ; return next input character
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The END command.
cmd_end         bne SNERROR                     ; error out if there is an argument
                ;jsr closeall                    ; close all files for END
                clra                            ; flag END (clear carry)
                bra cmd_stop0                   ; go do the stop/end
cmd_stop        bne SNERROR                     ; raise error if there was an argument
                coma                            ; flag STOP - set carry
cmd_stop0       ror endflag                     ; set stop/end flag
cmd_stop1       clr filenum                     ; reset I/O to console
                ldx curline                     ; in immediate mode?
                beq cmd_stop2                   ; brif so - don't save the continue pointers
                stx contline                    ; save pointer to current line for CONT
                ldx curstmt                     ; get current statement address
                stx contstmt                    ; save it for CONT
cmd_stop2       rol endflag                     ; get STOP/END to C (1=STOP)
                bcc cmd_stop3                   ; brif END - don't do message
                ldx #breakmsg                   ; do "BREAK IN"
                jmp ERROR2                      ; the bottom half of the error handler can deal with the details
cmd_stop3       puls x,pc                       ; lose return address and return to caller of interpretation loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; REM and ' commands; also ELSE comes here since it needs to skip the rest of the line in that case.
cmd_else
cmd_apos
cmd_rem         clra                            ; clear carry
                ldx curline                     ; get start of current line
                beq cmd_stop3                   ; brif immediate mode - fall back to caller
                ldx ,x                          ; get address of next line
                leax -1,x                       ; move back one
                stx inputptr                    ; put input pointer there
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; DATA command
;
; need to skip to the end of the current statement, which is either the end of the line OR a colon not included inside
; a quoted string
cmd_data        ldx inputptr                    ; get input pointer
cmd_data0       lda ,x+                         ; get character at pointer
                beq cmd_data1                   ; brif end of line
                cmpa #':                        ; end of statement?
                bne cmd_data2                   ; brif not
cmd_data1       leax -1,x                       ; move back to the NUL or colon
                stx inputptr                    ; reset input pointer for interpreter
                rts
cmd_data2       cmpa #'"                        ; start of constant string?
                bne cmd_data0                   ; brif not - process more characters
cmd_data3       lda ,x+                         ; get next string character
                beq cmd_data1                   ; brif end of line
                cmpa #'"                        ; string delimiter?
                bne cmd_data3                   ; brif not - keep going
                bra cmd_data0                   ; process stuff outside string
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; RUN command
cmd_run         ;jsr closeall                   ; close all files
                jsr curchar                     ; what do we have as an argument?
                bcs cmd_goto                    ; brif a digit - it's a line number (RUN ###); do GOTO
                lbne SNERROR                    ; brif anything else on the line - not legit command
                ldx progtext                    ; point to start of program
                bra cmd_goto0                   ; go transfer control to the start of the program
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GOTO command
cmd_goto        jsr parse_lineno                ; parse the line number
cmd_gosub0      jsr prog_findlinecl             ; go look up line number
                bcc cmd_goto0                   ; brif line found
ULERROR         ldb #err_ul                     ; raise undefined line error
                jmp ERROR
cmd_goto0       stx curline                     ; make sure we aren't flagging immediate mode
                leax -1,x                       ; move input pointer to NUL before destination line
                stx inputptr                    ; put input pointer there
                rts                             ; resume interpretation at the new location                
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GOSUB command
cmd_gosub       jsr parse_lineno                ; parse the destination line so return location is after the line number
                ldd #tok_gosub*256+4            ; stack frame details
                bsr callstack_alloc             ; make a stack frame
                ldx curline                     ; save current line pointer
                stx ,u
                ldx inputptr                    ; save current input pointer
                stx 2,u
                bra cmd_gosub0                  ; go finish up as a GOTO
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; RETURN command
; POP command
;
; RETURN will search the call stack for the first GOSUB frame and remove all other placeholders it finds. A frame type
; of 0 will cause it to stop.
cmd_pop         skip1lda                        ; set nonzero for POP
cmd_return      clra                            ; set zero for RETURN
                pshs a                          ; save operation type
                bsr callstack_first             ; get first entry on call stack
                bne cmd_return1                 ; brif there's a frame - don't error
RG_ERROR        ldb #err_rg                     ; raise RETURN without GOSUB
                jmp ERROR
cmd_return0     bsr callstack_next              ; move to next entry
                beq RG_ERROR                    ; brif end of stack - raise error
cmd_return1     cmpb #tok_gosub                 ; do we have a GOSUB frame?
                bne cmd_return0                 ; brif not - try again
                lda ,s+                         ; is it "POP"?
                bne cmd_return2                 ; brif so - don't change flow control but clear stack frame
                ldx ,u                          ; get back saved line pointer
                stx curline
                ldx 2,u                         ; get back saved input pointer
                stx inputptr
cmd_return2     bsr callstack_pop               ; clean up call stack
                bra cmd_data                    ; move to end of statement (move past any "ON GOSUB" entries
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Point to the first entry on the call stack; yes this is trivial but it points to the payload, not the header. Also
; sets Z if there is nothing on the stack.
callstack_first ldu stackptr                    ; get stack pointer
                ldb ,u++                        ; set flags on frame type and adjust pointer
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Move to the next frame on the call stack; enter with U pointing to a stack frame payload area
callstack_next  ldb -1,u                        ; get length of this frame
                leau b,u                        ; move to the next frame
                ldb -2,u                        ; set flags on frame type code
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Create a stack frame. Enter with the frame type flag in A and the size in B.
;
; The stack frame of size B bytes plus 2 bytes for the length and type flag will be allocated between the actual
; hardware stack and the current call stack pointer. Return with the pointer to the allocated frame in U. As long as
; there are no pointers to anything on the hardware stack, this will allow the stack to be entirely intact after
; the call.
callstack_alloc addb #2                         ; account for the header bytes
                pshs a,b                        ; save the type and length
                negb                            ; need a negative offset
                leax ,s                         ; point to current bottom of stack
                leas b,s                        ; make a hole below the stack
                leau ,s                         ; get a pointer to the destination for copying
callstack_alloc0
                lda ,x+                         ; copy a byte down
                sta ,u+
                cmpx stackptr                   ; have we reached the top of the stack?
                blo callstack_alloc0            ; brif not
                stu stackptr                    ; save the new call stack pointer
                puls d                          ; get back the type and length values
                std ,u++                        ; save type and length
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Pop the call stack to the end of the frame pointed to by U; this will relocate the hardware stack to close the
; newly made gap in memory.
callstack_pop   leau -2,u                       ; move back to header
                ldb 1,u                         ; get length of frame
                leax b,u                        ; point to element after this frame
                sts ,--s                        ; save the current bottom of the stack
                stx stackptr                    ; save new call stack pointer
callstack_pop0  lda ,-u                         ; copy a byte up
                sta ,-x
                cmpu ,s                         ; at the bottom of the call stack?
                bhi callstack_pop0              ; brif not
                leas 2,x                        ; reset the stack pointer (and lose the saved stack pointer value)
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Miscelaneous strings
prompt          fcn 'OK'                        ; general prompt
breakmsg        fcn 'BREAK'                     ; "BREAK" message
inmsg           fcn ' in '                      ; " in " message
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Print out an unsigned 16 bit value in D to the selected output stream
print_uint16d   pshs d,x,y,u                    ; save number and make some temporaries on the stack
                leay 2,s                        ; point to start of buffer
                ldu #10000                      ; do the 10000s digit
                bsr print_uint16d4
                ldu #1000                       ; do the 1000s digit
                bsr print_uint16d4
                ldu #100                        ; do the 100s digit
                bsr print_uint16d4
                ldu #10                         ; do the 10s digit
                bsr print_uint16d4
                puls d                          ; get back number residue and clean up stack
                addb #0x30                      ; convert 1s digit to number
                stb ,y                          ; stash it
                clr 1,y                         ; NUL terminate it
                leay ,s                         ; point to start of converted number
print_uint16d0  lda ,y                          ; get digit at start
                cmpa #0x30                      ; zero digit?
                bne print_uint16d1              ; brif not - we can just show the number from here
                ldb 1,y                         ; end of number?
                beq print_uint16d1              ; brif so - show the zero anyway
                leay 1,y                        ; move past the zero
                bra print_uint16d0              ; see if we have more zeroes to skip
print_uint16d1  lda ,y+                         ; get number digit
                beq print_uint16d2              ; brif end of number
                jsr writechr                    ; output the digit
                bra print_uint16d1              ; handle next digit
print_uint16d2  leas 6,s                        ; clean up the stack
                rts
print_uint16d4  lda #0x30-1                     ; init digit value
                pshs a,u                        ; save the digit position and digit value
                ldd 5,s                         ; get back residue
print_uint16d5  inc ,s                          ; bump digit
                subd 1,s                        ; subtract out place value
                bcc print_uint16d5              ; brif we haven't got the right digit yet
                addd 1,s                        ; restore residue
                std 5,s                         ; save new residue
                puls a,u                        ; get back digit and place value off stack
                sta ,y+                         ; save digit in buffer
                rts

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; PRINT command
cmd_print       beq cmd_printeol                ; brif no argument - do a newline
cmd_print0      cmpa #';                        ; semicolon?
                bne cmd_print1                  ; brif not
                jsr nextchar                    ; skip the semicolon
                bne cmd_print0                  ; brif not end of the statement
                rts
cmd_print1      jsr eval_expr                   ; evaluate the expression
                ldb val0+val.type               ; get value type
                cmpb #valtype_int               ; integer?
                beq cmd_printint                ; brif so - print integer
                lda #'!                         ; flag unknown expression type
                jsr console_outchr
                jsr console_outchr
                jsr console_outchr
cmd_printnext   jsr curchar                     ; see what we have here
                bra cmd_print                   ; and go process
cmd_printeol    jmp console_outnl               ; do a newline and return
cmd_printint    leas -12,s                      ; make a buffer
                leay ,s                         ; point to buffer
                lda #0x20                       ; default sign (positive)
                ldb val0+val.int                ; is it negative?
                bpl cmd_printint0               ; brif not
                jsr val_negint32                ; negate the integer
                lda #'-                         ; negative sign
cmd_printint0   sta ,y+                         ; save sign
                ldu #cmd_printintpc             ; point to positive constant table
                ldx #10                         ; there are 10 constants to process
; subtraction loop - positive residue
cmd_printint1   lda #'0-1                       ; initialize digit
                sta ,y
cmd_printint2   inc ,y                          ; bump digit
                ldd val0+val.int+2              ; subtract constant
                subd 2,u
                std val0+val.int+2
                ldd val0+val.int
                sbcb 1,u
                sbca ,u
                std val0+val.int
                bcc cmd_printint2               ; brif we didn't go negative
                ldd val0+val.int+2              ; undo last subtract
                addd 2,u
                std val0+val.int+2
                ldd val0+val.int
                adcb 1,u
                adca ,u
                std val0+val.int
                leay 1,y                        ; move to next digit in buffer
                leau 4,u                        ; move to next constant
                leax -1,x                       ; done all constants?
                bne cmd_printint1               ; brif not - done all
cmd_printint5   clr ,y                          ; NUL terminate the string
                leax 1,s                        ; point past the sign
cmd_printint6   lda ,x+                         ; get digit
                beq cmd_printint8               ; brif end of number
                cmpa #'0                        ; is it a zero?
                beq cmd_printint6               ; brif so - skip it
cmd_printint7   lda ,s                          ; get the sign
                sta ,--x                        ; put it at the start of the number
                jsr console_outstr              ; display the number
                leas 12,s                       ; clean up stack
                bra cmd_printnext               ; go print the next thing
cmd_printint8   leax -1,x                       ; restore one of the zeros
                bra cmd_printint7               ; go finish up
cmd_printintpc  fqb 1000000000                  ; 10^9
                fqb 100000000                   ; 10^8
                fqb 10000000                    ; 10^7
                fqb 1000000                     ; 10^6
                fqb 100000                      ; 10^5
                fqb 10000                       ; 10^4
                fqb 1000                        ; 10^3
                fqb 100                         ; 10^2
                fqb 10                          ; 10^1
                fqb 1                           ; 10^0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Error messages
;
; Each error begins with a deferr macro invocation which will define a symbol err_slug with the next error number
;
;               deferr slug
;
; This is then followed by the error message defined with fcn.
;
; Real error numbers start at 1; 0 is used to indicate no error.
                *pragmapush list
                *pragma nolist
__errnum        set 0
deferr          macro noexpand
err_{1}         equ __errnum
__errnum        set __errnum+1
                endm
                *pragmapop list
errormsg        deferr none
                fcn 'No error'
                deferr nf
                fcn 'NEXT without FOR'
                deferr sn
                fcn 'Syntax error'
                deferr ul
                fcn 'Undefined line number'
                deferr rg
                fcn 'RETURN without GOSUB'
                deferr ov
                fcn 'Overflow'
                deferr tm
                fcn 'Type mismatch'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The LET command which is the default if no token begins a statement
cmd_let         jmp SNERROR                     ; not yet implemented
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Expression Evaluation Package
;
; This is the expression evaluator. It handles everything from parsing numbers to dispatching function calls. The main
; entry point is eval_expr which will evaluate an arbitrary expression. It returns as soon as it reaches something it
; doesn't understand as part of an expression.
;
; The special handling for relational operators is required because Basic allows them in all 
eval_expr       clrb                            ; flag previous operator as minimum precdence (end of expression)
eval_expraux    jsr eval_term                   ; evaluate the first term of the expression
eval_expr0      jsr curchar                     ; fetch current input
                beq eval_expr1                  ; brif end of expression - we're done
                cmpa #tok_or                    ; is it above operators?
                bhi eval_expr1                  ; brif so
                suba #tok_plus                  ; offset to zero for first operator token
                bcc eval_expr2                  ; brif it is an operator
eval_expr1      rts
eval_expr2      pshs b                          ; save previous operator precedence
                ldx #oper_tab                   ; point to operator table
                tfr a,b                         ; shift to B for "ABX"
                abx                             ; add three times (3 bytes per entry)
                abx                             ; OBS: TFR + ABX + ABX + ABX is faster than LDB + MUL + ABX
                abx                             ; now X points to the operator entry in the table
                ldb ,x                          ; get precedence of current operation
                cmpb ,s                         ; is it higher than the current operation?
                bhi eval_expr3                  ; brif so - process this operator
                puls b,pc                       ; return current value to complete previous operation
eval_expr3      jsr nextchar                    ; eat the operator token
                ldx 1,x                         ; get handler address of this operator
                leas -val.size,s                ; make room for the result accumulator
                pshs x                          ; save handler address for later
                lda val0+val.type               ; get current value type
                ldx val0                        ; get value accumlator contents (6 bytes)
                ldy val0+2
                ldu val0+4
                pshs a,x,y,u                    ; save it on the stack
                jsr eval_expraux                ; evaluate the following term and higher precedence expressions
                puls a,x,y,u                    ; get back saved value
                stx val1                        ; save it to the second value accumulator
                sty val1+2
                stu val1+4
                sta val1+val.type               ; save previous value type
                ldx #val1                       ; point to left operand
                ldu #val0                       ; point to right operand
                leay 2,s                        ; point to return value location
                jsr [,s++]                      ; go handle the operator
                puls a,x,y,u                    ; get return value
                sta val0
                stx val0+1
                sty val0+3
                stu val0+5
                puls b                          ; get back the previous operator precedence
                bra eval_expr0                  ; go process another operator or end of expression
eval_term       jsr curchar                     ; get current input character
                beq eval_term0                  ; brif end of input - this is an error
                bcs eval_number                 ; brif digit - we have a number
;                bmi eval_func                   ; brif we have a token - handle function call
                cmpa #'.                        ; decimal point?
                beq eval_number                 ; brif so - evaluate number
                cmpa #'-                        ; negative sign?
                beq eval_number                 ; brif so - evaluate number
                cmpa #'+                        ; positive sign?
                beq eval_number                 ; brif so - evaluate number
eval_term0      jmp SNERROR                     ; we have something unrecognized - raise error
; Evaluate a number constant. Currently this only handles 32 bit integers.
eval_number     ldb #valtype_int                ; start with integer value
                stb val0+val.type               ; set return value
                ldx zero                        ; blank out the value
                stx val0
                stx val0+2
                stx val0+4
                bra eval_number1                ; go do the parsing
eval_number0    jsr nextchar                    ; fetch next input
                beq eval_numberr                ; brif end of expression - bail
eval_number1    cmpa #'-                        ; negative (ascii sign)?
                beq eval_number3                ; brif so
                cmpa #tok_minus                 ; negative (operator negative)?
                bne eval_number2                ; brif not
eval_number3    com val0+val.fpsign             ; invert sign
                bra eval_number0                ; deal with next input
eval_number2    cmpa #'+                        ; unary +?
                beq eval_number0                ; brif so - skip it
eval_number5    cmpa #'.        	; decimal point?
	beq eval_float                  ; brif decimal - force float
                cmpa #'0                        ; is it a number?
                blo eval_numberr                ; brif below digit
                cmpa #'9                        ; is it still a number?
                bhi eval_numberr                ; brif above digit
                suba #'0                        ; offset to binary digit value
                pshs a                          ; save digit value
                ldx val0+val.int                ; get current value for later (for quick multiply by 10)
                ldd val0+val.int+2
                pshs d,x                        ; stored with words swapped on stack for efficiency for later
                lsl val0+val.int+3              ; times 2
                rol val0+val.int+2
                rol val0+val.int+1
                rol val0+val.int
                bcs OVERROR                     ; brif overflowed
                lsl val0+val.int+3              ; times 4
                rol val0+val.int+2
                rol val0+val.int+1
                rol val0+val.int
                bcs OVERROR                     ; brif overflowed
                ldd val0+val.int+2              ; times 5 (add original value)
                addd ,s++
                std val0+val.int+2
                ldd val0+val.int
                adcb 1,s
                adca ,s++
                std val0+val.int
                bcs OVERROR
                lsl val0+val.int+3              ; times 10
                rol val0+val.int+2
                rol val0+val.int+1
                rol val0+val.int
                bcs OVERROR                     ; brif overflowed
                ldd val0+val.int+2              ; get low word
                addb ,s+                        ; add in current digit
                adca #0
                std val0+val.int+2
                ldd val0+val.int
                adcb #0
                adca #0
                std val0+val.int
                bcs OVERROR                     ; brif overflowed
                bpl eval_number4                ; brif we haven't wrapped negative
                cmpd #0x8000                    ; is it valid negative two's complement?
                bhi OVERROR                     ; brif not
                ldd val0+val.int+2              ; is it still valid two's complement (max negative)?
                bne OVERROR                     ; brif so
eval_number4    jsr nextchar                    ; fetch next input character
                bra eval_number5                ; go handle it
OVERROR         ldb #err_ov                     ; flag overflow
                jmp ERROR
eval_numberr    ldb val0+val.fpsign             ; is the number we want negative?
                beq eval_numberr0               ; brif not
                jsr val_negint32                ; negate the integer
eval_numberr0   rts
eval_float      jmp SNERROR                     ; we don't handle floating point yet
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Operator table
;
; Each entry starts with the precedence value followed by the handler routine. Each handler will receive its left
; operand in val1 and its right operand in val0 and should return its result in val0.
oper_tab        fcb 0x79                        ; addition
                fdb oper_plus
                fcb 0x79                        ; subtraction
                fdb oper_minus
                fcb 0x7b                        ; multiplication
                fdb SNERROR
                fcb 0x7b                        ; division
                fdb SNERROR
                fcb 0x7f                        ; exponentiation
                fdb SNERROR
                fcb 0x64                        ; less than
                fdb SNERROR
                fcb 0x64                        ; equal to
                fdb SNERROR
                fcb 0x64                        ; greater than
                fdb SNERROR
                fcb 0x64                        ; less than or equal to
                fdb SNERROR
                fcb 0x64                        ; greater than or equal to
                fdb SNERROR
                fcb 0x64                        ; not equal to
                fdb SNERROR
                fcb 0x50                        ; boolean AND
                fdb SNERROR
                fcb 0x46                        ; boolean OR
                fdb SNERROR
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Set carry if upper/lower case alpha
setcifalpha     cmpa #'z+1                      ; is it above lower case Z?
                bhs setcifalpha0                ; brif so, C clear
                suba #'a                        ; set C if >= lower case A
                suba #-'a
                bcs setcifalpha0                ; brif lower case alpha
setcifualpha    cmpa #'Z+1                      ; is it above upper case Z?
                bhs setcifalpha0                ; brif so, C clear
                suba #'A                        ; set C if >= upper case A
                suba #-'A
setcifalpha0    rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Set carry if digit
setcifdigit     cmpa #'9+1                      ; is it above digit 9?
                bhs setcifdigit0                ; brif so, C clear
                suba #'0                        ; set C if >= digit 0
                suba #-'0
setcifdigit0    rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Operator handling routines
;
; binary plus: addition and concatenation
oper_plus       ldb val.type,x                  ; get type of the left operand
                cmpb valtype_string             ; is it string?
                bne oper_plus0                  ; brif not
                cmpb val.type,u                 ; is right operand also string?
                lbeq SNERROR                    ; brif so - do string concatenation
oper_plus0      bsr val_matchtypes              ; go match data types
                jmp val_add                     ; go add the values
; binary minus: subtraction
oper_minus      bsr val_matchtypes              ; go match data types
                jmp val_sub                     ; do subtraction
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Arithmetic package
;
; This section contains routines that handle floating point and integer arithmetic.
;
; Most routines take a pointer to a value accumulator in X. Some take two pointers with the second in U.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Match operands for a numeric calculation. This works as follows:
;
; * If both operands are the same, ensure the type is numeric and return
; * If one operand is floating point, convert the other to floating point, as long as it is numeric
; * If one or both oeprands are not numeric, raise a type mismatch
; The operands are in (X) and (U)
val_matchtypes  ldb val.type,x                  ; get the type of first argument
                cmpb #valtype_int               ; is it integer?
                beq val_matchtypes0             ; brif so
                cmpb #valtype_float             ; is it floating point?
                beq val_matchtypes1             ; brif so
TMERROR         ldb #err_tm                     ; raise a type mismatch
                jmp ERROR
val_matchtypes0 ldb val.type,u                  ; get type of second operand
                cmpb #valtype_int               ; is it integer?
                bne val_matchtypes2             ; brif not
val_matchtypes3 rts
val_matchtypes2 cmpb #valtype_float             ; is it floating point?
                bne TMERROR                     ; brif not - raise error
                pshs u                          ; save pointer to second operand
                bsr val_int32tofp               ; convert first argument to floating point
                puls u,pc                       ; restore second operand pointer and return
val_matchtypes1 ldb val.type,u                  ; get second argument type
                cmpb #valtype_float             ; is it floating point?
                beq val_matchtypes3             ; brif so - we're good
                cmpb #valtype_int               ; is it integer?
                bne TMERROR                     ; brif not - invalid type combination
                pshs x,u                        ; save value pointers
                leax ,u                         ; convert (U) to floating point
                bsr val_int32tofp
                puls x,u,pc                     ; restore argument pointers and return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Negate the 32 bit integer (for fp mantissa) at (X)
val_negint32    ldd zero                        ; subtract integer value from zero
                subd val.int+2,x
                std val.int+2,x
                ldd zero
                sbcb val.int+1,x
                sbca val.int,x
                std val.int,x
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Convert integer value at (X) to floating point value at (X). Enter at val_uint32tofp to treat the 32 bit value as
; unsigned. Otherwise enter at val_int32tofp to treat it as signed.
val_uint32tofp  clr val.fpsign,x                ; for positive sign
                bra val_int32tofpp              ; go process as positive
val_int32tofp   ldb val.int,x                   ; get sign to A
                sex
                sta val.fpsign,x                ; set sign of result
                bpl val_int32tofpp              ; brif positive - don't need to do a two's complement adjustment
                bsr val_negint32                ; negate the integer value
val_int32tofpp  ldb valtype_float               ; set result to floating point
                stb val.type,x
                ldb #0xa0                       ; exponent to have binary point to the right of the mantissa
                stb val.fpexp,x                 ; set the exponent
                clrb                            ; clear out extra precision bits
                ; fall through to normalize the value at (X)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Normalize floating point value at (X); this will shift the mantissa until there is a one in the leftmost
; bit of the mantissa. The algorithm is as follows:
;
; 1. Shift the mantissa left until a 1 bit is found in the high bit of the mantissa.
; 1a. If more than 40 bits of left shifts occur, determine that the value is zero and return
; 2. Adjust exponent based on number of shifts
; 2a. If new exponent went below -127, then underflow occurred and zero out value
; 2b. If new exponent went above +127, raise an overflow
; 3. If bit 7 of the extra precision byte is clear, return the resulting value
; 4. Add one to the mantissa
; 5. If a carry in (4) occurred, then set high bit of mantissa and bump exponent
; 6. If new exponent carries, then raise overflow
; 7. Return result.
;
; Note that if we carried in (4), the only possible result is that the mantissa
; rolled over to all zeroes so there is no need to shift the entire mantissa right
; nor is there any reason to check for additional rounding.
;
; The above algorithm has some optimizations in the code sequence below.
fp_normalize    pshs b                          ; save extra bits
                clrb                            ; set shift counter/exponent adjustment
fp_normalize0   lda val.fpmant,x                ; set flags on high word of mantissa
                bne fp_normalize2               ; brif we don't have a full byte to shift
                addb #8                         ; account for a while byte of shifts
                ldu val.fpmant+1,x              ; shift mantissa left 8 bits
                stu val.fpmant,x
                lda val.fpmant+3,x
                sta val.fpmant+2,x
                lda ,s                          ; and include extra bits
                sta val.fpmant+3,x
                clr ,s                          ; and blank extra bits
                cmpb #40                        ; have we shifted 40 bits?
                blo fp_normalize0               ; brif not - keep shifting
                bra fp_normalize7               ; go zero out the value
fp_normalize1   incb                            ; account for one bit of shifting
                lsl ,s                          ; shift mantissa and extra bits left (will not be more than 7 shifts)
                rol val.fpmant+3,x
                rol val.fpmant+2,x
                rol val.fpmant+1,x
                rol val.fpmant,x
fp_normalize2   bpl fp_normalize1               ; brif we have to do a bit shift
                pshs b                          ; apply exponent counter to exponent
                lda val.fpexp,x
                suba ,s+
                bls fp_normalize6               ; brif we underflowed to zero
                bcc fp_normalize3               ; brif we did not overflow
OVERROR2        jmp OVERROR                     ; raise overflow
fp_normalize3   lsl ,s+                         ; set C if the high bit of extra precision is set
                bcs fp_normalize5               ; brif bit set - we have to do rounding
fp_normalize4   rts                             ; return if no rounding
fp_normalize5   ldu val.fpmant+2,x              ; add one to mantissa
                leau 1,u
                stu val.fpmant+2,x
                bne fp_normalize4               ; brif low word doesn't carry
                ldu val.fpmant,x
                leau 1,u
                stu val.fpmant,x
                bne fp_normalize4               ; brif high word doesn't carry
                ror val.fpmant,x                ; shift right C in to high bit of mantissa (already set to get here)
                inc val.fpexp,x                 ; bump exponent for a right shift
                beq OVERROR2                    ; brif it overflows (> +127)
                rts                             ; return result (only possible result was mantissa wrapped to zero)
fp_normalize6   clr val.fpmant,x                ; clear mantissa
                clr val.fpmant+1,x
                clr val.fpmant+2,x
                clr val.fpmant+3,x
fp_normalize7   clr val.fpexp,x                 ; clear exponent and sign
                clr val.fpsign,x
                puls b,pc                       ; clean up stack and return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Addition and subtraction of values; must enter with values of matching types
;
; Calculates (X) + (U) -> (Y) (addition)
; Calculates (X) - (U) -> (Y) (subtraction)
val_add         ldb val.type,x                  ; get type of left operand
                stb val.type,y                  ; set result type
                cmpb #valtype_float             ; is it float?
                beq fp_add                      ; brif so
                ldd val.int+2,x                 ; do the addition
                addd val.int+2,u
                std val.int+2,y
                ldd val.int,x
                adcb val.int+1,u
                adca val.int,u
                std val.int,y
                lbvs OVERROR                    ; brif calculation overflowed
                rts
val_sub         ldb val.type,x                  ; get type of left operand
                stb val.type,y                  ; set result type
                cmpb #valtype_float             ; floating point?
                beq fp_sub                      ; brif so
                ldd val.int+2,x                 ; do the subtraction
                subd val.int+2,u
                std val.int+2,y
                ldd val.int,x
                sbcb val.int+1,u
                sbca val.int,u
                std val.int,y
                lbvs OVERROR                    ; brif overflow
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FP subtraction: just invert the sign of the second operand and add; operands must be writable and they should be
; considered to be clobbered
fp_sub          com val.fpsign,u                ; negate right operand
                ; fall through to addition
; FP addition: this requires that *both operands* are writable and they may be clobbered
fp_add          ldb val.fpexp,u                 ; is the second operand zero?
                beq fp_add0                     ; brif so - it's a no-op - copy the left operand to the output
                lda val.fpexp,x                 ; is left operand zero?
                bne fp_add1                     ; brif not - we have to do the add
                leau ,x                         ; copy the right operand to the output
fp_add0         ldd ,u                          ; copy the value across
                std ,y
                ldd 2,u
                std 2,y
                ldd 4,u
                std 4,y
                rts
fp_add1         subb val.fpexp,x                ; get difference in exponents
                beq fp_add6                     ; brif they're the same - no denormalizing is needed
                bhi fp_add2                     ; brif second one is bigger, need to right-shift the mantissa of first
                exg x,u                         ; swap the operands (we can do that for addition)l second is now biggest
                negb                            ; invert the shift count
fp_add2         cmpb #32                        ; are we shifting more than 32 bits?
                blo fp_add0                     ; brif so - we're effectively adding zero so bail out
fp_add3         cmpb #8                         ; have 8 bits to move?
                bhs fp_add5                     ; brif not
                lda val.fpmant+2,x              ; shift 8 bits right
                sta val.fpmant+3,x
                lda val.fpmant+1,x
                sta val.fpmant+2,x
                lda val.fpmant,x
                sta val.fpmant+1,x
                clr val.fpmant,x
                subb #8                         ; account for 8 shifts
                bra fp_add3                     ; see if we have a whole byte to shift
fp_add4         lsr val.fpmant,x                ; shift right one bit
                ror val.fpmant+1,x
                ror val.fpmant+2,x
                ror val.fpmant+3,x
fp_add5         decb                            ; done all shifts?
                bmi fp_add4                     ; brif not - do a shift
fp_add6         ldb val.fpexp,u                 ; set exponent of result
                stb val.fpexp,y
                ldb val.fpsign,u                ; fetch sign of larger value
                stb val.fpsign,y                ; set result sign
                cmpb val.fpsign,x
                bne fp_add8                     ; brif not - need to subtract the operands
                ldd val.fpmant+2,u              ; add the mantissas
                addd val.fpmant+2,x
                std val.fpmant+2,y
                ldd val.fpmant,u
                adcb val.fpmant+1,x
                adca val.fpmant,x
                std val.fpmant,y
                clrb                            ; clear extra precision bits
                bcc fp_add7                     ; brif no carry
                ror val.fpmant,y                ; shift carry into mantissa
                ror val.fpmant+1,y
                ror val.fpmant+2,y
                ror val.fpmant+3,y
                rorb                            ; keep bits for founding
                inc val.fpexp,y                 ; bump exponent to account for shift
                lbeq OVERROR                    ; brif it overflowed
fp_add7         leax ,y                         ; point to result
                jmp fp_normalize                ; go normalize the result
fp_add8         ldd val.fpmant+2,u              ; subtract operands
                subd val.fpmant+2,x
                std val.fpmant+2,y
                ldd val.fpmant,u
                sbcb val.fpmant+1,x
                sbca val.fpmant,x
                std val.fpmant,y
                bcc fp_add7                     ; brif we didn't carry - no need to fix up
                ldd zero                        ; negate the mantissa bits since we use sign+magnitude
                subd val.fpmant+2,y
                std val.fpmant+2,y
                ldd zero
                sbcb val.fpmant+1,y
                sbca val.fpmant,y
                std val.fpmant,y
                neg val.fpsign,y                ; invert sign of result since we went past zero
                clrb                            ; clear extra precision bits
                bra fp_add7                     ; go normalize the result and return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Pack a floating point value at (X)
fp_packval      ldb val.fpsign,x                ; get sign
                bmi fp_packval                  ; brif negative - the default 1 bit will do
                ldb val.fpmant,x                ; clear high bit of mantissa for positive
                andb #0x7f
                stb val.fpmant,x
fp_packval0     rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Unpack a floating point value at (X)
fp_unpackval0   ldb val.fpmant,x                ; get high byte of mantissa
                sex                             ; now A is value for sign byte
                sta val.fpsign,x                ; set sign
                orb #0x80                       ; set high bit of mantissa
                stb val.fpmant,x
                rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The LIST command.
;
; Syntax:
; LIST
; LIST <line>
; LIST <line>-
; LIST -<line>
; LIST <start>-<end>
cmd_list        bne cmd_list1                   ; brif we have arguments
                ldx progtext                    ; point to start of program
cmd_list0       ldd #65535                      ; set last line to list to max line number
                std binval
                bra cmd_list2                   ; go do the listing
cmd_list1       jsr parse_lineno                ; parse starting line number (will default to 0)
                jsr prog_findline               ; find the line or the one after where it would be
                jsr curchar                     ; are we at the end of the command?
                beq cmd_list2                   ; brif so - we have a single line (binval will have the start line #)
                ldb #tok_minus                  ; insist on a - for a range if more than one line number
                jsr syncheckb
                beq cmd_list0                   ; brif open ended ending - set to max line number
                jsr parse_lineno                ; parse ending of range
cmd_list2       ldd ,x                          ; are we at the end of the program?
                bne cmd_list4                   ; brif not
cmd_list3       rts
cmd_list4       ldd 2,x                         ; get line number
                cmpd binval                     ; have we reached the end of the range?
                bhi cmd_list3                   ; brif so - we're done
                jsr print_uint16d               ; print out line number
                lda #0x20                       ; and a space
                jsr writechr
                pshs x                          ; save start of this line (in case detokenizing exits early)
                leax 4,x                        ; move past line header
                bsr detokenize                  ; detokenize line to current output stream
                ldx [,s++]                      ; point to next line using saved pointer and clear it from the stack
                ; need to add a break check here
                bra cmd_list2                   ; go handle another line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Detokenize a line to the current output stream
detokenize      lda ,x+                         ; get character from tokenized line
                bmi detokenize1                 ; brif it's a keyword token
                lbeq writecondnl                ; do a newline if needed and return
                cmpa #':                        ; is it a colon?
                bne detokenize0                 ; brif not
                ldb ,x                          ; fetch subsequent character
                cmpb #tok_apos                  ; apostrophe version of REM?
                beq detokenize                  ; brif so - skip the colon
                cmpb #tok_else                  ; ELSE?
                beq detokenize                  ; brif so - skip the colon
detokenize0     jsr writechr                    ; output it unmolested
                bra detokenize                  ; go handle another character
detokenize1     ldu #primarydict                ; point to primary dictionary table
                cmpa #0xff                      ; is it a secondary token?
                bne detokenize3                 ; brif not
                ldu #secondarydict              ; point to secondary dictionary table
                lda ,x+                         ; get secondary token value
                bne detokenize3                 ; brif not end of line
                leax -1,x                       ; don't consume the NUL
detokenize2     lda #'!                         ; invalid token flag
                bra detokenize0                 ; output it and continue
detokenize3     anda #0x7f                      ; lose the high bit
                beq detokenize6                 ; brif already at the right place
detokenize4     ldb ,u                          ; end of dictionary table?
                beq detokenize2                 ; brif so - show invalid tokenf lag
detokenize5     ldb ,u+                         ; fetch character in this keyboard
                bpl detokenize5                 ; brif not end of keyword (high bit set)
                deca                            ; at the right token?
                bne detokenize4                 ; brif not - skip another
detokenize6     lda ,u+                         ; get keyword character
                bmi detokenize7                 ; brif end of keyword
                jsr writechr                    ; output it
                bra detokenize6                 ; go fetch another
detokenize7     anda #0x7f                      ; lose the high bit
                bra detokenize0                 ; write it and move on with the input
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Canonicalize certain sequences; ALL the rewrite sequences must make the result shorter or keep it the same size
makecanontab    fcb tok_less,2
                fcb tok_greater,tok_notequal
                fcb tok_equal,tok_lessequal
                fcb tok_greater,2
                fcb tok_less,tok_notequal
                fcb tok_equal,tok_greaterequal
                fcb tok_equal,2
                fcb tok_greater,tok_greaterequal
                fcb tok_less,tok_lessequal
                fcb 0
makecanon       leay ,x                         ; point output to start of the buffer
makecanon0      lda ,x+                         ; get current byte
                sta ,y+                         ; save in output
                bne makecanon1                  ; brif not end of line
                rts
makecanon1      bpl makecanon0                  ; brif not a token
                cmpa #0xff                      ; is it secondary?
                bne makecanon2                  ; brif not
                leax 1,x                        ; move past second half
                bra makecanon0                  ; go handle next byte
makecanon2      ldu #makecanontab               ; point to replacement table
makecanon3      cmpa ,u+                        ; is it this entry?
                beq makecanon4                  ; brif so
                ldb ,u+                         ; get number of entries
                lslb                            ; 2 bytes per
                leau b,u                        ; move past entry
                ldb ,u                          ; end of table?
                bne makecanon3                  ; brif not
                bra makecanon0                  ; no substitutions found
makecanon4      pshs x                          ; save original source pointer
makecanon5      lda ,x+                         ; get next character
                cmpa #0x20                      ; is it space?
                beq makecanon5                  ; brif so - skip it
                ldb ,u+                         ; get number of replacement candidates
makecanon6      cmpa ,u++                       ; does it match?
                beq makecanon7                  ; brif so
                decb                            ; seen all of them?
                bne makecanon6                  ; brif not
                puls x                          ; restore input pointer
                bra makecanon0                  ; go handle next input
makecanon7      leas 2,s                        ; clear saved input pointer
                lda -1,u                        ; get replacement token
                sta -1,y                        ; put it in the output
                bra makecanon0                  ; go handle more input
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Tokenize line to tokebuff
;
; Enter with X pointing to the text to tokenize.
; Exit with X pointing to the start of the tokenized line and D holding the length of the tokenized line.
tokenize        clr tok_skipkw                  ; clear "not token" flag
                clr tok_skipdt                  ; clear the "in data" flag
                ldy #tokebuff                   ; point to destination buffer
                pshs y                          ; set return value
tokenize0       lda ,x+                         ; get input character
                bne tokenize3                   ; brif not end of input
tokenize1       sta ,y+                         ; blank out final byte in result
tokenize2       ldx #tokebuff                   ; point to start of tokenized line
                bsr makecanon                   ; canonicalize certain sequences
                tfr y,d                         ; get end address to accumulator
                subd #tokebuff                  ; subtract out start; gives length of result
                puls x,pc                       ; set return pointer and return
tokenize3       tst tok_skipkw                  ; are we in the middle of a "not token"?
                beq tokenize6                   ; brif not
                jsr setcifalpha                 ; is it alpha
                bcs tokenize4                   ; brif so - store it and continue
                jsr setcifdigit                 ; is it numeric?
                bcc tokenize5                   ; brif not
tokenize4       sta ,y+                         ; save output character
                bra tokenize0                   ; check for another
tokenize5       clr tok_skipkw                  ; clear the "not token" flag
tokenize6       cmpa #'"                        ; is it a string?
                bne tokenize8                   ; brif not
                sta ,y+                         ; save string delimiter
tokenize7       lda ,x+                         ; get input character
                beq tokenize1                   ; brif end of input
                sta ,y+                         ; save it in output
                cmpa #'"                        ; end of string?
                bne tokenize7                   ; brif not
                bra tokenize0                   ; brif 
tokenize8       cmpa #':                        ; end of statement?
                bne tokenize9                   ; brif not
                clr tok_skipdt                  ; reset "in data" flag
                bra tokenize4                   ; stash it and continue
tokenize9       cmpa #0x20                      ; is it a space?
                beq tokenize4                   ; brif so - stash it unmodified
                tst tok_skipdt                  ; are we "in data"?
                bne tokenize4                   ; brif so - don't tokenize it
                cmpa #'?                        ; PRINT shortcut?
                bne tokenize10                  ; brif not
                lda #tok_print                  ; load token for PRINT
                bra tokenize4                   ; move stash it and move on
tokenize10      cmpa #''                        ; ' shortcut for remark?
                bne tokenize12                  ; brif not
                ldd #':*256+tok_apos            ; put token for ' and an implied colon
                std ,y++                        ; stash it
tokenize11      lda ,x+                         ; fetch byte from input
                sta ,y+                         ; stash in output
                bne tokenize11                  ; brif not end of input
                bra tokenize2                   ; go finish up
tokenize12      jsr setcifdigit                 ; is it a digit?
                bcs tokenize4                   ; brif so - pass it through
                tsta                            ; is the high bit set?
                bmi tokenize0                   ; ignore it if so
                ldu #primarydict                ; point to keyword table
                leax -1,x                       ; back up input to start of potential token
                clr tok_kwtype                  ; set secondary table flag to primary table
                clr tok_kwmatch                 ; clear the matched token
                clr tok_kwmatch+1
                clr tok_kwmatchl                ; set length matched
                pshs x                          ; save start of input token
tokenize13      clr tok_kwnum                   ; clear keyword number
tokenize14      ldb ,u                          ; are we at the end of the table?
                bne tokenize16                  ; brif not
                ldu #secondarydict              ; point to secondary token dictionary
                com tok_kwtype                  ; flip to secondary token flag
                bne tokenize13                  ; brif we haven't already done the secondaries
                puls x                          ; get back input pointer
                ldb tok_kwmatchl                ; get length of best match
                beq tokenize15                  ; brif we don't have a match
                abx                             ; move input pointer past matched token
                ldd tok_kwmatch                 ; get matched token number
                tsta                            ; is it a primary?
                beq tokenize24                  ; brif so
                bra tokenize23                  ; go stash two byte token
tokenize15      com tok_skipkw                  ; set "not token flag"
                lda ,x+                         ; get character
                bra tokenize4                   ; stash it and continue
tokenize16      ldx ,s                          ; get back start of input token
                clra                            ; initalize match length counter
tokenize17      inca                            ; bump length counter
                ldb ,x+                         ; get input character
                cmpb #'z                        ; is it above lower case Z?
                bhi tokenize18                  ; brif so
                cmpb #'a                        ; is it below lower case A?
                blo tokenize18                  ; brif so
                subb #0x20                      ; convert to upper case
tokenize18      subb ,u+                        ; does it match?
                beq tokenize17                  ; brif so - check another
                cmpb #0x80                      ; did it match with high bit set?
                beq tokenize21                  ; brif so - exact match
                leau -1,u                       ; back up to current test character
tokenize19      ldb ,u+                         ; end of token?
                bpl tokenize19                  ; brif not
tokenize20      inc tok_kwnum                   ; bump token counter
                bra tokenize14                  ; go check another one
tokenize21      cmpa tok_kwmatchl               ; is it a longer match?
                bls tokenize20                  ; brif not, ignore it
                sta tok_kwmatchl                ; save new match length
                ldd tok_kwtype                  ; get the matched token count
                orb #0x80                       ; set token flag
                std tok_kwmatch                 ; save matched token
                bra tokenize20                  ; keep looking through the tables
tokenize22      lda #':                         ; for putting implied colons in
tokenize23      std ,y++                        ; put output into buffer
                jmp tokenize0                   ; go handle more input
tokenize24      cmpb #tok_else                  ; is it ELSE?
                beq tokenize22                  ; brif so - stash it with colon
                cmpb #tok_data                  ; is it DATA?
                bne tokenize26                  ; brif not
                stb tok_skipdt                  ; set "in data" flag
tokenize25      stb ,y+                         ; stash token
                jmp tokenize0                   ; go handle more
tokenize26      cmpb #tok_rem                   ; is it REM?
                beq tokenize28                  ; brif so
                cmpb #tok_apos                  ; apostrophe REM?
                bne tokenize25                  ; brif not - stash token and continue
                lda #':                         ; stash the implied colon
                sta ,y+
                bra tokenize28
tokenize27      ldb ,x+                         ; fetch next input character
tokenize28      stb ,y+                         ; stash the character
                bne tokenize27                  ; brif not end of input - do another
                jmp tokenize2                   ; stash end of buffer and handle cleanup
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Special tokenization handling
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Keyword dictionaries and jump tables. These are defined by several macros which ensure that each command or function
; entry has an associated jump table entry. These macros are:
;
;               defcmd string,symbase
;               deffunc string,symbase,flags
;               cmdtab
;               functab
;               cmdjump
;               funcjump
; defcmd and deffunc will add an entry into the relevant dictionary table as well as adding one to the relevant jump
; tables. The cmdtab, functab, cmdjump, and funcjump will output the table definitions.
                *pragmapush list
                *pragma nolist
__cmdnum        set 0x80
__funcnum       set 0x80
defcmd          macro noexpand
                setstr __cmdtab="%(__cmdtab)\tfcs {1}\n"
                ifstr ne,"{3}",""
                setstr __cmdjump="%(__cmdjump)\tfdb {3}\n"
                else
                setstr __cmdjump="%(__cmdjump)\tfdb cmd_{2}\n"
                endc
tok_{2}         equ __cmdnum
__cmdnum        set __cmdnum+1
                endm
deffunc         macro noexpand
                setstr __functab="%(__functab)\tfcs {1}\n"
                ifstr ne,"{4}",""
                setstr __funcjump="%(__funcjump)\tfcb {3}\n\tfdb {4}\n"
                else
                setstr __funcjump="%(__funcjump)\tfcb {3}\n\tfdb func_{2}\n"
                endc
tok_{2}         equ __funcnum
__funcnum       set __funcnum+1
                endm
cmdtab          macro
                *pragmapush list
                *pragma nolist
                includestr "%(__cmdtab)"
                *pragmapop list
                fcb 0                           ; flag end of table
                endm
functab         macro
                *pragmapush list
                *pragma nolist
                includestr "%(__functab)"
                *pragmapop list
                fcb 0                           ; flag end of table
                endm
cmdjump         macro
                *pragmapush nolist
                *pragma nolist
                includestr "%(__cmdjump)"
                *pragmapop list
                endm
funcjump        macro
                *pragmapush nolist
                *pragma nolist
                includestr "%(__funcjump)"
                *pragmapop list
                endm
                *pragmapop list
                defcmd 'REM',rem
                defcmd /'/,apos
                defcmd 'DATA',data
                defcmd 'ELSE',else
                defcmd 'END',end
                defcmd 'STOP',stop
                defcmd 'LET',let
                defcmd 'NEW',new
                defcmd 'PRINT',print
                defcmd 'LIST',list
                defcmd 'RUN',run
                defcmd 'GOTO',goto
                defcmd 'GOSUB',gosub
                defcmd 'RETURN',return
                defcmd 'POP',pop
                defcmd '+',plus,SNERROR         ; IMPORTANT: the operators from + to OR MUST stay in this exact sequence
                defcmd '-',minus,SNERROR        ; with no gaps because a secondary lookup table is used for operator
                defcmd '*',times,SNERROR        ; handling during binary operator handling.
                defcmd '/',divide,SNERROR
                defcmd '^',power,SNERROR
                defcmd '<',less,SNERROR
                defcmd '>',greater,SNERROR
                defcmd '=',equal,SNERROR
                defcmd '<=',lessequal,SNERROR
                defcmd '>=',greaterequal,SNERROR
                defcmd '<>',notequal,SNERROR
                defcmd 'AND',and,SNERROR
                defcmd 'OR',or,SNERROR
                defcmd 'NOT',not,SNERROR
primarydict     cmdtab
secondarydict   functab
primaryjump     cmdjump
secondaryjump   funcjump
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Need to ensure the vectors are at 0xbff2
                zmb 0xbff2-*                    ; pad ROM up to the vector point
                fdb SW3VEC                      ; SWI3 vector
                fdb SW2VEC                      ; SWI2 vector
                fdb FRQVEC                      ; FIRQ vector
                fdb IRQVEC                      ; IRQ vector
                fdb SWIVEC                      ; SWI vector
                fdb NMIVEC                      ; NMI vector
                fdb START                       ; RESET vector (ROM entry point)
                endc
                ifdef COCO3
                zmb 0xfff2-*                    ; pad ROM to bottom of vectors
                fdb INT.SWI3                    ; SWI3 vector
                fdb INT.SWI2                    ; SWI2 vector
                fdb INT.FIRQ                    ; FIRQ vector
                fdb INT.IRQ                     ; IRQ vector
                fdb INT.SWI                     ; SWI vector
                fdb INT.NMI                     ; NMI vector
                fdb START                       ; RESET vector (ROM entry point)
                else
                zmb 0x10000-*                   ; pad ROM to full size
                endc