view src/lwbasic.s @ 40:68253ccbb9dc

Immediate mode line insertion, removal, and replacement This implements a first pass untested version of handling program line insertion and deletion in immediate mode. This comes with auxilliary routines for parsing line numbers and fixing up the next line pointers.
author William Astle <lost@l-w.ca>
date Wed, 23 Nov 2022 21:34:21 -0700
parents e956d8b6e806
children 090db8c5d509
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
                *pragmapop list
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Various constants
console_curdel  equ 10                          ; delay between cursor blink cycles
keyb_bufflen    equ 64                          ; keyboard ring buffer length
keyb_repdeli    equ 40                          ; ticks before initial repeat (2/3 s)
keyb_repdelr    equ 6                           ; 10 repeats per second
keyb_caps       equ 0x80                        ; capslock enabled
keyb_alt        equ 0x04                        ; alt pressed
keyb_ctrl       equ 0x02                        ; ctrl pressed
keyb_shift      equ 0x01                        ; shift pressed
linebuffsize    equ 0x100                       ; the line input buffer (256 bytes)
                ifdef COCO3
; GIME INIT0
GIME_COCO       equ 0x80                        ; Set for coco2 compatible mode (video display)
GIME_MMUEN      equ 0x40                        ; Set to enable MMU
GIME_IEN        equ 0x20                        ; GIME IRQ enable
GIME_FEN        equ 0x10                        ; GIME FIRQ enable
GIME_FExx       equ 0x08                        ; Enable constant RAM at 0xFExx (comes from block 0x3f)
GIME_SCS        equ 0x04                        ; Set to enable standard SCS (switches 0xFF5x)
GIME_ROME16     equ 0x00                        ; 16K internal, 16K external ROM mode
GIME_ROME32     equ 0x03                        ; 32K external ROM
GIME_ROMI32     equ 0x02                        ; 32K internal ROM
; GIME INIT1
GIME_TMRFAT     equ 0x20                        ; TIMER ticks approx every 279.365 ns
GIME_TMRSLOW    equ 0x00                        ; TIMER ticks approx every 63.695 µs
GIME_TASK0      equ 0x00                        ; MMU task 0
GIME_TASK1      equ 0x01                        ; MMU task 1
; GIME interrupt enable/status bits
GIME_ITIMER     equ 0x20                        ; TIMER interrupt (timer reaches 0)
GIME_IHBORD     equ 0x10                        ; HSYNC interrupt (falling edge)
GIME_IVBORD     equ 0x08                        ; VSYNC interrupt (falling edge)
GIME_ISERIAL    equ 0x04                        ; Falling edge of signal on pin 4 of serial port
GIME_IKEYBOARD  equ 0x02                        ; Interrupt if a 0 bit appears on bits 6-0 of PIA0.DA
GIME_ICART      equ 0x01                        ; Interrupt on falling edge of pin 8 of cartridge port
; GIME VMODE
GIME_BP         equ 0x80                        ; enable bit plane mode
GIME_BPI        equ 0x20                        ; colour burst phase inversion (composite output only)
GIME_MONO       equ 0x10                        ; disable colour burst (composite output only)
GIME_H50        equ 0x08                        ; set to 50Hz operation
GIME_LPR1       equ 0x00                        ; one line per row
GIME_LPR2       equ 0x02                        ; two lines per row (also works on graphics)
GIME_LPR8       equ 0x03                        ; 8 lines per row
GIME_LPR9       equ 0x04                        ; 9 lines per row
GIME_LPR10      equ 0x05                        ; 10 lines per row
GIME_LPR11      equ 0x06                        ; 11 lines per row
GIME_LPRINF     equ 0x07                        ; "infinite" lines per row
; GIME VRES
GIME_LPF192     equ 0x00                        ; 192 lines on screen
GIME_LPF200     equ 0x40                        ; 200 lines on screen (actually 199 due to hardware bug)
GIME_LPF225     equ 0x60                        ; 225 lines on screen
GIME_BPR16      equ 0x00                        ; 16 bytes per row
GIME_BPR20      equ 0x04                        ; 20 bytes per row
GIME_BPR32      equ 0x08                        ; 32 bytes per row
GIME_BPR40      equ 0x0c                        ; 40 bytes per row
GIME_BPR64      equ 0x10                        ; 64 bytes per row
GIME_BPR80      equ 0x14                        ; 80 bytes per row
GIME_BPR128     equ 0x18                        ; 128 bytes per row
GIME_BPR160     equ 0x1c                        ; 160 bytes per row
GIME_TXT32      equ 0x00                        ; 32 characters per row
GIME_TXT40      equ 0x04                        ; 40 characters per row
GIME_TXT64      equ 0x10                        ; 64 characters per row
GIME_TXT80      equ 0x14                        ; 80 characters per row
GIME_BPP1       equ 0x00                        ; 1 bit per pixel
GIME_BPP2       equ 0x01                        ; 2 bits per pixel
GIME_BPP4       equ 0x02                        ; 4 bits per pixel
GIME_TXTATTR    equ 0x01                        ; text attributes enabled
                endc
                ifdef COCO3
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Stuff on the fixed memory page
                org 0xfe00
                rmb 0xed                        ; unused
INT.FLAG        rmb 1                           ; validity flag
INT.SWI3        rmb 3                           ; SWI3 bounce vector
INT.SWI2        rmb 3                           ; SWI2 bounce vector
INT.FIRQ        rmb 3                           ; FIRQ bounce vector
INT.IRQ         rmb 3                           ; IRQ bounce vector
INT.SWI         rmb 3                           ; SWI bounce vector
INT.NMI         rmb 3                           ; NMI bounce vector
                endc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Hardware definitions for the I/O page
                org 0xff00
PIA0            equ *                           ; Keyboard PIA
PIA0.DA         rmb 1                           ; PIA0 data/direction A
PIA0.CA         rmb 1                           ; PIA0 control A
PIA0.DB         rmb 1                           ; PIA0 data/direction B
PIA0.CB         rmb 1                           ; PIA0 control B
                rmb 28                          ; mirror images of PIA0
PIA1            equ *                           ; DA/misc stuff
PIA1.DA         rmb 1                           ; PIA1 data/direction A
PIA1.CA         rmb 1                           ; PIA1 control A
PIA1.DB         rmb 1                           ; PIA1 data/direction B
PIA1.CB         rmb 1                           ; PIA1 control B
                rmb 28                          ; mirror images of PIA1
                rmb 16                          ; SCS/Disk controller
                rmb 16                          ; second half of SCS area
                rmb 32                          ; miscelaneous hardware
                ifdef COCO3
                rmb 16                          ; *reserved* (unused but the GIME drives them)
GIME.INIT0      rmb 1                           ; basic GIME system config
GIME.INIT1      rmb 1                           ; MMU task and timer rate
GIME.IRQ        rmb 1                           ; GIME IRQ enable/status register
GIME.FIRQ       rmb 1                           ; GIME FIRQ enable/status register
GIME.TIMER      rmb 2                           ; GIME programmable timer
                rmb 2                           ; *reserved*
GIME.VMODE      rmb 1                           ; GIME video mode setting
GIME.VRES       rmb 1                           ; GIME video resolution setting
                rmb 1                           ; *reserved* (used for MMU expansion on some memory boards)
GIME.BORDER     rmb 1                           ; GIME border colour
GIME.VSCROLL    rmb 1                           ; vertical scroll offset register/VDG screen mode variation
GIME.VOFFSET    rmb 2                           ; address of video memory (8 byte increments)
GIME.HOFFSET    rmb 1                           ; horizontal scroll offset
GIME.MMU        equ *                           ; MMU registers (two tasks)
GIME.MMU0       rmb 8                           ; MMU task 0
GIME.MMU1       rmb 8                           ; MMU task 1
GIME.PALETTE    rmb 16                          ; Palette registers
                else
                rmb 64                          ; unused on Coco 1/2 (GIME on Coco 3)
                endc
SAMREG          equ *                           ; the SAM configuration register
SAM.V0CLR       rmb 1                           ; SAM video mode bits
SAM.V0SET       rmb 1
SAM.V1CLR       rmb 1
SAM.V1SET       rmb 1
SAM.V2CLR       rmb 1
SAM.V2SET       rmb 1
SAM.F0CLR       rmb 1                           ; SAM screen address bits
SAM.F0SET       rmb 1
SAM.F1CLR       rmb 1
SAM.F1SET       rmb 1
SAM.F2CLR       rmb 1
SAM.F2SET       rmb 1
SAM.F3CLR       rmb 1
SAM.F3SET       rmb 1
SAM.F4CLR       rmb 1
SAM.F4SET       rmb 1
SAM.F5CLR       rmb 1
SAM.F5SET       rmb 1
SAM.F6CLR       rmb 1
SAM.F6SET       rmb 1
SAM.P1CLR       rmb 1                           ; SAM "page 1" selection (or extra memory type flag)
SAM.P1SET       rmb 1
SAM.R0CLR       rmb 1                           ; SAM R0 bit (address dependent speedup, not used on Coco3)
SAM.R0SET       rmb 1
SAM.R1CLR       rmb 1                           ; SAM R1 bit (full speedup/coco 3 speedup)
SAM.R1SET       rmb 1
SAM.M0CLR       rmb 1                           ; SAM M0/M1 bits (memory type, not used on Coco3)
SAM.M0SET       rmb 1
SAM.M1CLR       rmb 1
SAM.M1SET       rmb 1
SAM.TYCLR       rmb 1                           ; force ROM mode (map type 0)
SAM.TYSET       rmb 1                           ; set RAM mode (map type 1)
                rmb 18                          ; *MPU reserved*
CPU.SWI3        rmb 2                           ; CPU SWI3 vector
CPU.SWI2        rmb 2                           ; CPU SWI2 vector
CPU.FIRQ        rmb 2                           ; CPU FIRQ vector
CPU.IRQ         rmb 2                           ; CPU IRQ vector
CPU.SWI         rmb 2                           ; CPU SWI vector
CPU.NMI         rmb 2                           ; CPU NMI vector
CPU.RESET       rmb 2                           ; CPU RESET/startup vector
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Start of memory which has the direct page and other data.
                org 0
dpstart         equ *                           ; start of direct page
zero            rmb 2                           ; constant zero word used for faster zeroing of 16 bit registers
binval          rmb 2                           ; arbitary binary value, usually a line number or integer
memtop          rmb 2                           ; absolute top of memory in 64K memory map
memsize         rmb 2                           ; top of memory not reserved
freetop         rmb 2                           ; top of free memory (bottom of string space)
stringtab       rmb 2                           ; bottom of used string space
progtext        rmb 2                           ; pointer to start of program text
vartab          rmb 2                           ; pointer to start of integer scalars
objecttab       rmb 2                           ; pointer to start of arrays and other variable sized objects
freestart       rmb 2                           ; pointer to start of unallocated memory
readlinenoecho  rmb 1                           ; if nonzero, the readline routine won't echo its input
console_curptr  rmb 2                           ; current cursor pointer for console driver
console_blnkdel rmb 1                           ; cursor blink delay
console_truelc  rmb 1                           ; set to nonzero if the console supports true lower case (gfx, etc.)
filenum         rmb 1                           ; current input/output channel
fileeof         rmb 1                           ; flag for whether last read detected EOF
keyb_flags      rmb 1                           ; shift flags for the keyboard
keyb_joystate   rmb 1                           ; joystick button state
keyb_repdel     rmb 1                           ; repeat delay
keyb_curscan    rmb 1                           ; current repeating scan code
keyb_buffw      rmb 2                           ; keyboard ring buffer write pointer
keyb_buffr      rmb 2                           ; keyboard ring buffer read pointer
curline         rmb 2                           ; pointer to current line
contline        rmb 2                           ; pointer to line for CONT
contstmt        rmb 2                           ; interpretation pointer for CONT
curstmt         rmb 2                           ; start of statement currently being interpreted
endflag         rmb 1                           ; 00 = END, FF = STOP
                rmb 0x71-*                      ; align RSTFLG/RSTVEC for stock ROM compatibility
RSTFLG          rmb 1                           ; 0x55 if RSTVEC is valid
RSTVEC          rmb 2                           ; points to warm start routine (must start with NOP)
inputptr        rmb 2                           ; pointer to current program execution location
                rmb 0x100-*                     ; make sure the stuff that isn't direct page is outside of it
SW3VEC          rmb 3                           ; SWI3 vector (for compatibility)
SW2VEC          rmb 3                           ; SWI2 vector (for compatibility)
SWIVEC          rmb 3                           ; SWI vector (for compatibility)
NMIVEC          rmb 3                           ; NMI vector (for compatibility)
IRQVEC          rmb 3                           ; IRQ vector (for compatibility)
FRQVEC          rmb 3                           ; FIRQ vector (for compatibility)
keyb_state      rmb 8                           ; rollover table state
keyb_buff       rmb keyb_bufflen                ; the keyboard ring buffer
linebuff        rmb linebuffsize                ; the line input buffer
tokebuff        rmb linebuffsize+50             ; make it as long as line buffer plus a margin
                ifne *&0x1ff
                rmb 0x200-(*&0x1ff)
                endc
textscreen      rmb 0x200                       ; the actual text screen (must be on 512 byte alignment)
heapstart       equ *                           ; start of dynamically allocated stuff
                org 0x8000                      ; the hardware puts the ROMs here; it's not negotiable
ROMSTART        equ *
START           orcc #0x50                      ; make sure interrupts are disabled if we come here in an unusual way
                ifdef COCO3
                ldu #gime_inite                 ; point to end of GIME initializer
                ldx #GIME.INIT0+(gime_inite-gime_init) ; point to end of GIME registers
                ldb #gime_inite-gime_init       ; number of bytes to transfer
initc0          lda ,-u                         ; copy byte to GIME (count down so we init MMU before turning it on)
                sta ,-x
                decb                            ; done?
                bne initc0                      ; brif not
                endc
                ldd #0xff34                     ; initizer for below
                tfr a,dp                        ; set DP to I/O page
                setdp 0xff                      ; tell assembler about DP value
                clr PIA0.CA                     ; set PIA0 A to direction mode
                clr PIA0.CB                     ; set PIA0 B to direction mode
                clr PIA0.DA                     ; set PIA0 A to all inputs (comparator, keyboard rows)
                sta PIA0.DB                     ; set PIA0 B to all outputs (keyboard columns)
                stb PIA0.CA                     ; set PIA0 A to data mode, interrupt disabled, MUX to source 0
                stb PIA0.CB                     ; set PIA0 B to data mode, interrupt disabled, MUX to source 0
                clr PIA1.CA                     ; set PIA1 A to direction mode
                clr PIA1.CB                     ; set PIA1 B to direction mode
                deca                            ; set PIA1 A bits 7-1 output (DAC, RS232), 0 input (cassette)
                sta PIA1.DA
                lda #0xf8                       ;* set PIA1 B bits 7-3 output (VDG stuff), 2-0 input (single bit sound,
                sta PIA1.DB                     ;* RS232 input, ram size input)
                stb PIA1.CA                     ; set PIA1 A to data mode, interrupt disabled, cassette motor off
                stb PIA1.CB                     ; set PIA1 B to data mode, interrupt disabled, sound off
                lda #2                          ; set RS232 output to "marking" (stop bit)
                sta PIA1.DA
                lda #16                         ; clear 16 SAM register bits
                ldu #SAMREG                     ; point to SAM register bits
init0           sta ,u++                        ; clear SAM bit
                deca                            ; done all?
                bne init0                       ; brif not
; set the SAM to point to the text screen, which the code will handle at any
; arbitrary 512 byte aligned address in memory
                ifne (textscreen)&0x200
                sta SAM.F0SET
                endc
                ifne (textscreen)&0x400
                sta SAM.F1SET
                endc
                ifne (textscreen)&0x800
                sta SAM.F2SET
                endc
                ifne (textscreen)&0x1000
                sta SAM.F3SET
                endc
                ifne (textscreen)&0x2000
                sta SAM.F4SET
                endc
                ifne (textscreen)&0x4000
                sta SAM.F5SET
                endc
                ifne (textscreen)&0x8000
                sta SAM.F6SET
                endc
                ifdef COCO2B
; The following SAM configuration sequence is different from the one in the usual
; one used by the earlier models of the Coco because the Coco2B has the '785 variant
; of the SAM instead of the '783 variant. The '785 variant supports 16Kx4 RAMs which
; are used in Coco2B systems. Hence why there is a different version of this ROM
; just for the Coco2B.
                clr PIA0.DB                     ; strobe RAM size low
                ldb #4                          ; is input low?
                bitb PIA1.DB
                beq init1                       ; brif not
                sta SAM.M0SET                   ; program SAM for 16Kx4 RAMs
                sta SAM.P1SET
                skip2
init1           sta SAM.M1SET                   ; program SAM for 64Kx1 RAMs
                else
                ifndef COCO3
; Detect the installed memory size so the SAM ('783 variant) can be correctly
; programmed for the installed memory. Note that this sequence is replaced with
; a different one for the Coco2B which has the '785 variant of the SAM.
                ldb #0xff                       ; strobe RAM size high
                stb PIA0.DB
                ldb #4                          ; mask for ram size check
                bitb PIA1.DB                    ; is the bit set on ram size input?
                beq init2                       ; brif not - 4Kx1 RAMs
                sta PIA0.DB                     ; clear RAM size output to see what happens (A is 0 from above)
                bitb PIA1.DB                    ; is it set now?
                beq init1                       ; brif not - 64Kx1 RAMs
                leau -2,u                       ; adjust pointer so we set the other RAM size bit for the SAM (16Kx1)
init1           sta -3,u                        ; set M0 (16Kx1) or M1 (64Kx1)
                endc
                endc
init2           tfr a,dp                        ; set DP to bottom of memory (A is 0 from above)
                setdp 0                         ; tell assembler about it
                lds #textscreen                 ; put the stack just below the text screen
                ifdef COCO3
; Check if we need to do a ROM/RAM copy, which will happen if the interrupt vectors are
; not flagged valid OR the reset vector isn't valid
                ldb INT.FLAG                    ; are the bounce vectors valid?
                cmpb #0x55
                bne initc4                      ; brif not - do ROM/RAM copy
                ldb RSTFLG                      ; is reset vector valid?
                bne initc2                      ; brif not - check secondary location
                ldx RSTVEC                      ; get reset vector
                ldb ,x                          ; is it valid?\
                cmpb #0x12
                bne initc2                      ; brif not
initc1          jmp ,x                          ; transfer control to warm start routine
initc2          clr GIME.MMU0                   ; check again with block 0 in the direct page
                ldb RSTFLG                      ; get new RSTFLG
                cmpb #0x55                      ; valid?
                bne initc3                      ; brif not
                ldx RSTVEC                      ; get new RSTVEC
                ldb ,x                          ; is it valid?
                cmpb #0x12
                beq initc1                      ; brif so - transfer control
initc3          ldb #0x38                       ; restore MMU
                stb GIME.MMU0
initc4          ldx #initc6                     ; point to helper
                ldu #textscreen                 ; point to text screen
                ldb #initc7-initc6              ; bytes to copy
initc5          lda ,x+                         ; copy byte
                sta ,u+
                decb                            ; done?
                bne initc5                      ; brif not
                ldu #0x8000                     ; point to start of ROM
                jmp textscreen                  ; transfer control to helper in RAM
initc6          sta SAM.TYCLR                   ; drop to ROM mode
                pulu d,x,y,s                    ; grab 8 bytes
                sta SAM.TYSET                   ; go to RAM mode
                pshu d,x,y,s                    ; stick the bytes in RAM
                leau 8,u                        ; move to next 8 bytes
                cmpu #0xfe00                    ; end of stuff to copy?
                blo initc6                      ; brif not
                jmp initc7                      ; go back to mainline
initc7          lds #textscreen                 ; reset stack to somewhere safe
                lda #0x12                       ; activate ROM warm start handler
                sta warmstart
                ldx #INT.FLAG                   ; point to bounce vector destination
                ldu #int_init                   ; point to initializer for bounce vectors
                ldb #int_inite-int_init         ; number of bytes to copy
initc8          lda ,u+                         ; copy byte
                sta ,x+
                decb                            ; done?
                bne initc8                      ; brif not
; now recheck for warm start in case ROM/RAM copy made things valid
                endc
                ldb RSTFLG                      ; is the reset vector valid?
                cmpb #0x55
                bne coldstart                   ; brif not - do cold start
                ldx RSTVEC                      ; get warm start routine pointer
                ldb ,x                          ; does it start with NOP?
                cmpb #0x12
                bne coldstart                   ; brif not - do cold start
                jmp ,x                          ; transfer control to warm start routine
                ifdef COCO3
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; GIME register initializer
gime_init       fcb GIME_COCO|GIME_MMUEN|GIME_FExx|GIME_SCS|GIME_ROMI32 ; enable MMU, SCS, constant page, internal ROM
                fcb GIME_TASK0                  ; use MMU task 0
                fcb 0                           ; do not enable IRQ sources
                fcb 0                           ; do not enable FIRQ sources
                fdb 0xfff                       ; set timer to max value
                fdb 0                           ; *reserved placeholder*
                fcb 0,0,0,0                     ; SG4 screen settings with black border
                fcb 0x0f,0xe0,0x00,0x00         ; (puts screen in bottom 64K of memory)
                fcb 0x38,0x39,0x3a,0x3b         ; MMU task 0 (bottom of top 64K of RAM)
                fcb 0x3c,0x3d,0x3e,0x3f         ; (ROM shadow must be in 3c...3f)
                fcb 0x38,0x39,0x3a,0x3b         ; MMU task 1 (copy of task 0)
                fcb 0x3c,0x3d,0x3e,0x3f
                fcb 18,54,9,36,63,27,45,38      ; palette values (RGB)
                fcb 0,18,0,63,0,18,0,38
gime_inite      equ *
int_init        fcb 0x55                        ; vectors valid flag
                jmp SW3VEC                      ; bounce to stock ROM compatibility vector
                jmp SW2VEC                      ; bounce to stock ROM compatibility vector
                jmp FRQVEC                      ; bounce to stock ROM compatibility vector
                jmp IRQVEC                      ; bounce to stock ROM compatibility vector
                jmp SWIVEC                      ; bounce to stock ROM compatibility vector
                jmp NMIVEC                      ; bounce to stock ROM compatibility vector
int_inite       equ *
                endc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Cold start handling
coldstart       ldx #dpstart                    ; point to start of direct page
                ldd #0                          ; set up for blanking
coldstart0      std ,x++                        ; blank a couple of bytes
                cmpx #textscreen                ; end of low memory?
                blo coldstart0                  ; brif not
                ifndef COCO3
; This is the memory size detection sequence. This runs through memory starting at the bottom of memory
; and stops when it reaches something that can't be modified successfully. This is basically the same
; algorithm used by the stock ROM. It takes less space than doing a more pointed set of probes. The end
; result will be X pointing to the byte one below the top of RAM. This is intentional to ensure there
; is one writeable byte at the top of string space. Note that X will point to the byte after the end
; of the text screen when we get here.
                ldx #heapstart                  ; point to start of heap
coldstart1      lda 2,x                         ; get original value at test location
                coma                            ; invert all bits
                sta 2,x                         ; write it to the memory location
                cmpa 2,x                        ; did it take?
                bne coldstart2                  ; brif not
                com 2,x                         ; restore memory byte
                leax 1,x                        ; move pointer forward
                bra coldstart1                  ; go check next byte
                else
; For the Coco3, we do not need to concern ourselves about where the top actual memory is so we don't
; bother doing a memory scan in the default 64K memory map. Because we always run from RAM, we can actually
; set the top of memory to the actual top of the 32K space without having to ensure there is an extra byte
; available above the string space.
                ldx #ROMSTART-1                 ; point to top of memory
                endc
coldstart2      stx memtop                      ; save absolute top of memory
                stx memsize                     ; save top of unreserved memory
                stx stringtab                   ; mark string space as empty
                leax -200,x                     ; allocate 200 bytes of string space
                stx freetop                     ; save top of free memory
                leas ,x                         ; put the stack there
                ldx #heapstart                  ; point to start of free memory
                stx progtext                    ; put the start of the program there
                clr ,x+                         ; put a NULL pointer to mark end of program
                clr ,x+
                stx vartab                      ; put start of integer variables at end of program
                stx objecttab                   ; also put the start of large objects there
                stx freestart                   ; mark the start of free memory
                lda #keyb_caps                  ; enable caps lock but disable all other shift states
                sta keyb_flags
                ldx #warmstart                  ; set up warm start handler
                stx RSTVEC
                lda #0x55                       ; activate warm start handler
                sta RSTFLG
                ldd #0x7e3b                     ; opcodes for JMP extended and RTI
                ldx #irqhandler                 ; enable IRQ handler with a JMP at the vector
                sta IRQVEC
                stx IRQVEC+1
                sta FRQVEC                      ; initialize FIRQ handler with JMP
                ldx #firqhandler
                stx FRQVEC+1
                stb NMIVEC                      ; initialize NMI to RTI
                stb SW3VEC                      ; initialize SWI3 to RTI
                stb SW2VEC                      ; initialize SWI2 to RTI
                stb SWIVEC                      ; initialize SWI to RTI
                ldx #greeting                   ; display greeting
                jsr console_outstr
                bra warmstartb                  ; finish up initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Warm start handling
                ifdef COCO3
warmstart       fcb 0xff                        ; set to 0xff to force ROM/RAM copy on reset
                else
warmstart       nop                             ; flag warm start routine as valid
                endc
                jsr console_clear               ; clear screen
                clr filenum                     ; reset I/O channel to the screen
warmstartb      jsr keyb_reset                  ; reset the keyboard
                lda #0x35                       ; enable VSYNC interrupt in PIA
                sta PIA0.CB
                andcc #0xaf                     ; enable interrupts at the cpu
                jmp immediate                   ; go start immediate mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; System startup message
; (start with form feed to clear screen; saves 2 bytes over 'jsr console_clear' in cold start)
greeting        fcc '\fLWBASIC VERSION 2022.0\r\n'
                fcc 'COPYRIGHT (C) 2022 BY LOST\r\n'
                fcc 'WIZARD ENTERPRISES INC.\r\n'
                fcn '\n'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; IRQ handler
;
; Note that the interrupt flag in the PIA is cleared at the start of the interrupt handler. That means that if it takes
; a long time to process this interrupt, or processing this interrupt was delayed somewhat, it is far less likely that
; an interrupt gets missed. In that case, we may end up re-interrupting immediately on RTI, but it should reduce the
; number of missed interrupts.
irqhandler      lda PIA0.CB                     ; was it VSYNC?
                bmi irqhandler0                 ; brif so
                lda PIA0.DA                     ; clear HSYNC flag so we don't get stuck if it gets enabled
                ifdef COCO3
                lda GIME.IRQ                    ; clear GIME IRQ state flags
                endc
                rti
irqhandler0     lda PIA0.DB                     ; clear VSYNC flag
                clra                            ; make sure DP is pointing to the right place
                tfr a,dp
                lda console_blnkdel             ; is the cursor blinking?
                beq irqhandler1                 ; brif not
                dec console_blnkdel             ; time to cycle cursor?
                bne irqhandler1                 ; brif not
                lda #console_curdel             ; reset blink counter
                sta console_blnkdel
                lda [console_curptr]            ; get character at cursor
                adda #0x10                      ; move to next colour
                ora #0x8f                       ; force it to be a full 4x4 colour block
                sta [console_curptr]            ; update cursor on screen
irqhandler1     bsr keyb_read                   ; go handle the keyboard
                rti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FIRQ handler
;
; This handler is present to prevent accidentally enabling the interrupt and thus hanging to system. It may seem to be
; a waste of code space, but consider it a self defense situation.
firqhandler     pshs a                          ; need a scratch register
                ifdef COCO3
                lda GIME.FIRQ                   ; clear GIME FIRQ state flags
                endc
                lda PIA1.DA                     ; clear interrupt flags
                lda PIA1.DB
                lda PIA1.CA                     ; disable interrupts to prevent system hang
                anda #0xfe
                sta PIA1.CA
                lda PIA1.CB
                anda #0xfe
                sta PIA1.CB
                puls a                          ; restore register
                rti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Console keyboard input driver
;
; Reset the keyboard state, which means clearing the buffer and state flags
keyb_reset      ldx #keyb_buff                  ; point to start of keyboard ring buffer
                stx keyb_buffw                  ; set write point there
                stx keyb_buffr                  ; set read point there (pointers equal means empty buffer)
                lda keyb_flags                  ; reset keyboard state flags but keep capslock
                anda #keyb_caps
                sta keyb_flags
                clr keyb_joystate               ; clear joystick button state
                clr keyb_curscan                ; stop any keyboard repeating
                ldx #0xffff                     ; mark all key state as "unpressed"
                stx keyb_state
                stx keyb_state+2
                stx keyb_state+4
                stx keyb_state+6
                rts
; Read character from keyboard ring buffer; return with C set if buffer empty; this doesn't actually need to have
; interrupts disabled because the interrupt only ever updates the write pointer and then only to increase it. As a
; result, worst case is that we don't detect the contents added to the buffer on this call and have to wait for the
; next.
keyb_getkey     pshs x                          ; save register
                ldx keyb_buffr                  ; get read pointer
                cmpx keyb_buffw                 ; same as write pointer?
                bne keyb_getkey0                ; brif not - we have a result
                coma                            ; set carry for empty buffer
                puls x,pc                       ; restore register and return
keyb_getkey0    lda ,x+                         ; get character from buffer
                cmpx #keyb_buff+keyb_bufflen    ; did we run off end of buffer?
                blo keyb_getkey1                ; brif not
                ldx #keyb_buff                  ; reset to start
keyb_getkey1    stx keyb_buffr                  ; save new read pointer
                andcc #0xfe                     ; flag key retrieved
                puls x,pc                       ; restore register and return
; The PIA reading loop is specifically set up to NOT read PIA0.DB to avoid prematurely clearing the VSYNC interrupt flag
; since that could lead to missing interrupts. Reading PIA0.DA will clear the HSYNC interrupt flag but that's less of a
; problem because that interrupt is basically useless.
;
; As a note, doing the PIA read in a loop ends up using an extra 27 CPU cycles for the BCS instruction. However, it
; saves 70 code bytes. The trade off seems worth it in this case.
;
; Once keyboard state is read, we do the following:
;
; * update the state of SHIFT, CTRL, ALT
; * decode all other keys in a loop
keyb_read0a     pshs b                          ; save flag bit
                ldb a,y                         ; get state flag 
                bitb #0x40                      ; did it change state?
                bne keyb_read0d                 ; brif so
                puls b,pc                       ; clean up and return
keyb_read0d     andb #0xbf                      ; flag it as not changed
                stb a,y
                ldb a,u                         ; get current modifier state
                eorb #0x40                      ; flip the state bit
                stb a,u                         ; save new state flags
                bitb #0x40                      ; Z set if not down
                puls b                          ; get back flag bit
                beq keyb_read0b                 ; brif key is pressed
                comb                            ; invert bit flag
                andb keyb_flags                 ; clear bit in flags
                bra keyb_read0c                 ; finish up
keyb_read0b     orb keyb_flags                  ; set the flag
keyb_read0c     stb keyb_flags                  ; update flags
                rts
keyb_read       leas -9,s                       ; make temporary buffer
                leay 1,s                        ; point to temporary state buffer
                clra                            ;* set to 0xff with C clear; start by strobing no columns for joystick
                deca                            ;* then rotate the 0 bit through to do the actual keyboard columns
                ldu #keyb_state                 ; point to end of keyboard state buffer
                sta PIA0.DB                     ; strobe no columns
                ldb PIA0.DA                     ; get joystick button state
                stb keyb_joystate               ; save it for later when needed
                andb #0x7f                      ; mask off comparator (pretend "button" down)
                stb ,s                          ; save button/comparator state mask
                rola                            ; set up for first column
keyb_read0      sta PIA0.DB                     ; set column strobe
                ldb PIA0.DA                     ; read row data
                eorb ,u+                        ; set bits if state changed
                andb ,s                         ; mask off comparator and active buttons
                stb ,y+                         ; save state change information
                rola                            ; shift to next column
                bcs keyb_read0                  ; brif we haven't done the last column
                sta PIA0.DB                     ; reset column strobe to none
                ldd #0xff00|keyb_shift
                bsr keyb_read0a
                ldd #0xfc00|keyb_ctrl
                bsr keyb_read0a
                ldd #0xfb00|keyb_alt
                bsr keyb_read0a
keyb_read3      ldd #0x0701                     ; initialize bit probe and counter
keyb_read4      leay -1,y                       ; move pointers to next byte
                leau -1,u
keyb_read5      bitb ,y                         ; did this key change state?
                bne keyb_read7                  ; brif so
keyb_read6      adda #8                         ; adjust scan code
                lslb                            ; shift bit probe
                bpl keyb_read5                  ; brif we haven't done all bits
                ldb ,y                          ; update state flags for this byte
                eorb ,u
                stb ,u
                ldb #1                          ; reset bit probe
                anda #0x07                      ; reset scan code
                deca                            ; adjust for next column
                bpl keyb_read4                  ; brif not - do another
                leas 9,s                        ; clean up stack
                ldb keyb_curscan                ; is key repeating?
                bne keyb_read9                  ; brif so
keyb_reada      rts
keyb_read7      bitb ,u                         ; get current state
                bne keyb_read8                  ; brif key pressed (make)
                cmpa keyb_curscan               ; is it the currently repeating key?
                bne keyb_read6                  ; brif not - don't need to do anything
                clr keyb_curscan                ; clear the current repeat
                bra keyb_read6
keyb_read8      sta keyb_curscan                ; set the current scan code that is repeating
                pshs d                          ; save current bit probe and scan code
                ldb #keyb_repdeli               ; intialize repeat delay
                stb keyb_repdel
                bsr keyb_tobuff                 ; decode key to buffer
                puls d                          ; restore scan code and bit probe
                bra keyb_read6                  ; go handle the next bit
keyb_read9      dec keyb_repdel                 ; is it time to repeat it?
                bne keyb_reada                  ; brif not
                ldb #keyb_repdelr               ; reset repeat delay
                stb keyb_repdel
                lda keyb_curscan                ; get current scan code
keyb_tobuff     tsta                            ; @?
                beq keyb_tobuff7                ; brif so
                cmpa #26                        ; is it alpha or @?
                bhi keyb_tobuff6                ; brif not
                ldb keyb_flags                  ; get shift flags
                bitb #keyb_ctrl|keyb_alt        ; ALT or CTRL?
                bne keyb_tobuff4                ; brif one or both
                ora #0x60                       ; make lower case
                bitb #keyb_caps                 ; capslock enabled?
                beq keyb_tobuff0                ; brif not
                eora #0x20                      ; flip to upper case
keyb_tobuff0    bitb #keyb_shift                ; shifted?
                beq keyb_tobuff1                ; brif not
                eora #0x20                      ; flip case if shifted
keyb_tobuff1    ldx keyb_buffw                  ; get write pointer for keyboard buffer
                sta ,x+                         ; put it in the buffer
                cmpx #keyb_buff+keyb_bufflen    ; end of buffer?
                blo keyb_tobuff2                ; brif not
                ldx #keyb_buff                  ; reset pointer to start
keyb_tobuff2    cmpx keyb_buffr                 ; did we run into the read pointer?
                beq keyb_tobuff3                ; brif so - there wasn't room so don't save pointer
                stx keyb_buffw                  ; update the write pointer
keyb_tobuff3    rts
keyb_tobuff4    bitb #keyb_alt                  ; is ALT?
                beq keyb_tobuff1                ; brif not - scan code is CTRL-<letter> code
                ora #0x80                       ; set bit 7 for "ALT" codes
                bitb #keyb_shift                ; shifted?
                beq keyb_tobuff5                ; brif not
                ora #0x20                       ; set bit 5
keyb_tobuff5    bitb #keyb_ctrl                 ; ctrl?
                beq keyb_tobuff1                ; brif not - stash it in the buffer
                ora #0x40                       ; set bit 6 for "ctrl
                bra keyb_tobuff1                ; stash it the buffer
keyb_tobuff6    suba #26                        ; codes above 26 down to 1; @ will be 0
keyb_tobuff7    cmpa #6                         ; is it "0"?
                bne keyb_tobuff8                ; brif not
                ldb keyb_flags                  ; get shift flags
                bitb #keyb_shift|keyb_ctrl      ; CTRL-0 or SHIFT-0?
                beq keyb_tobuff8                ; brif not - not "capslock"
                eorb #keyb_caps                 ; flip the capslock state
                stb keyb_flags
keyb_tobuffa    rts                             ; and don't put it in the buffer
keyb_tobuff8    cmpa #25                        ; is it at or above ALT?
                blo keyb_tobuff9                ; brif not
                suba #2                         ; close gap for ALT/CTRL
keyb_tobuff9    ldb #8                          ;* 8 codes; multiply by 8 and move to B
                mul                             ;*
                ldx #keyb_codetab               ; point to special code table
                abx                             ; now X points to the base entry in the table
                ldb keyb_flags                  ; get shift flags
                andb #keyb_shift|keyb_ctrl|keyb_alt ; keep only shift/ctrl/alt
                lda b,x                         ; fetch key code
                beq keyb_tobuffa                ; brif no code to return
                bra keyb_tobuff1                ; go stash it in the buffer
; This is the keyboard code table; there are 8 bytes per entry in the following order:
; 0: unmodified
; 1: shift
; 2: ctrl
; 3: ctrl-shift
; 4: alt
; 5: alt-shift
; 6: alt-ctrl
; 7: alt-ctrl-shift
;
; No entries for ALT, CTRL, SHIFT, or letters
keyb_codetab    fcb 0x40,0x13,0x40,0x40,0x80,0xa0,0xc0,0xe0     ; @
                fcb 0x5e,0x5f,0x00,0x00,0x00,0x00,0x00,0x00     ; <UP>
                fcb 0x0a,0x5b,0x00,0x00,0x00,0x00,0x00,0x00     ; <DOWN>
                fcb 0x08,0x15,0x00,0x00,0x00,0x00,0x00,0x00     ; <LEFT>
                fcb 0x09,0x5d,0x00,0x00,0x00,0x00,0x00,0x00     ; <RIGHT>
                fcb 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20     ; <SPACE>
                fcb 0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00     ; 0 (shift/ctrl variants shadowed above)
                fcb 0x31,0x21,0x00,0x00,0x00,0x00,0x00,0x00     ; 1 !
                fcb 0x32,0x22,0x00,0x00,0x00,0x00,0x00,0x00     ; 2 "
                fcb 0x33,0x23,0x00,0x00,0x00,0x00,0x00,0x00     ; 3 #
                fcb 0x34,0x24,0x00,0x00,0x00,0x00,0x00,0x00     ; 4 $
                fcb 0x35,0x25,0x00,0x00,0x00,0x00,0x00,0x00     ; 5 %
                fcb 0x36,0x26,0x00,0x00,0x00,0x00,0x00,0x00     ; 6 &
                fcb 0x37,0x27,0x00,0x00,0x00,0x00,0x00,0x00     ; 7 '
                fcb 0x38,0x28,0x00,0x00,0x00,0x00,0x00,0x00     ; 8 (
                fcb 0x39,0x29,0x00,0x00,0x00,0x00,0x00,0x00     ; 9 )
                fcb 0x3a,0x2a,0x00,0x00,0x00,0x00,0x00,0x00     ; : *
                fcb 0x3b,0x2b,0x00,0x00,0x00,0x00,0x00,0x00     ; ; +
                fcb 0x2c,0x3c,0x00,0x00,0x00,0x00,0x00,0x00     ; , <
                fcb 0x2d,0x3d,0x00,0x00,0x00,0x00,0x00,0x00     ; - =
                fcb 0x2e,0x3e,0x00,0x00,0x00,0x00,0x00,0x00     ; . >
                fcb 0x2f,0x3f,0x00,0x00,0x00,0x00,0x00,0x00     ; / ?
                fcb 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d     ; <ENTER>
                fcb 0x0c,0x5c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c     ; <CLEAR>
                fcb 0x03,0x03,0x1b,0x1b,0x9b,0xbb,0xdb,0xfb     ; <BREAK>
                fcb 0x1c,0x1d,0x1c,0x1d,0x00,0x00,0x00,0x00     ; <F1>
                fcb 0x1e,0x1f,0x1e,0x1f,0x00,0x00,0x00,0x00     ; <F2>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Console screen output driver
;
; Clear screen
console_clear   ldb #0x60                       ; VDG space character
                ldx #textscreen                 ; point to text screen
                stx console_curptr              ; set cursor pointer to start of screen
console_clear0  stb ,x+                         ; blank a character
                cmpx #textscreen+0x200          ; end of screen?
                blo console_clear0              ; brif not
                rts
; Output NUL terminated string
console_outstr0 bsr console_outchr              ; output the character
console_outstr  lda ,x+                         ; get byte from string
                bne console_outstr0             ; brif not end of string
                rts
; Output NUL terminated string followed by a newline
console_outstrn bsr console_outstr              ; output the string
                ; fallthrough intentional
; Output a newline (CR LF)
console_outnl   lda #0x0d                       ; do the CR
                bsr console_outchr
                lda #0x0a                       ; do the LF
                ; fallthrough intentional
; Output a single character to the screen; enter with character in A
console_outchr  pshs d,x                        ; save registers
                ldx console_curptr              ; get current cursor pointer
                cmpa #0x20                      ; printable character?
                blo console_outchr5             ; brif not
                tsta                            ; is it a graphics block?
                bmi console_outchr1             ; brif so - don't do anything to it
                cmpa #0x40                      ; number or most non-alpha characters?
                blo console_outchr0             ; brif so - will need to flip bit 6
                cmpa #0x60                      ; upper case?
                blo console_outchr1             ; brif so - don't need to do anything to it
                anda #0xdf                      ; clear bit 5 of lower case; moves it to bottom of character set
console_outchr0 eora #0x40                      ; flip bit 6 - the "inversion" bit
console_outchr1 sta ,x+                         ; stick it on screen
console_outchr2 stx console_curptr              ; save new cursor pointer
                cmpx #textscreen+0x200          ; end of screen?
                blo console_outchr4             ; brif not
                leax -32,x                      ; move pointer back one line
                stx console_curptr
                ldx #textscreen                 ; point to start of screen
console_outchr3 ldd 32,x                        ; get bytes from next line
                std ,x++                        ; stick them here
                cmpx #textscreen+0x1e0          ; at last row?
                blo console_outchr3             ; brif not
                ldb #0x60                       ; space character for VDG screen
                bsr console_clear0              ; blank out last row (borrowing screen clear loop)
console_outchr4 puls d,x,pc                     ; restore registers and return
console_outchr5 cmpa #0x0c                      ; form feed?
                bne console_outchr6             ; brif not
                bsr console_clear               ; clear screen
                puls d,x,pc                     ; restore registers and return
console_outchr6 cmpa #0x0d                      ; carriage return?
                bne console_outchr7             ; brif not
                ldb console_curptr+1            ; get current screen pointer LSB
                andb #0xe0                      ; reset offset to start of line
                stb console_curptr+1            ; save new pointer LSB
                puls d,x,pc                     ; restore registers and return
console_outchr7 cmpa #0x0a                      ; line feed?
                bne console_outchr8             ; brif not
                ldx console_curptr              ; get cursor pointer
                leax 32,x                       ; move it forward exactly one line
                bra console_outchr2             ; go update stuff check for scroll
console_outchr8 cmpa #0x08                      ; backspace?
                bne console_outchr9             ; brif not
                cmpx #textscreen                ; at start of screen?
                beq console_outchr4             ; brif so - backspace does nothing
                leax -1,x                       ; back up pointer (backspace is non-destructive)
                bra console_outchr2             ; go update pointers, etc.
console_outchr9 cmpa #0x09                      ; TAB character?
                bne console_outchr4             ; brif not
                ldb console_curptr              ; get LSB of pointer
                andb #7                         ; 8 space tabs - only keep low 3 bits
                lda #0x60                       ; space character (tab is destructive)
console_outchra sta ,x+                         ; put a space out
                incb                            ; bump counter
                cmpb #8                         ; at next tab stop?
                blo console_outchra             ; brif not
                bra console_outchr2             ; go update details and check for scroll
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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          lda ,x+                         ; get character to output
                beq ERROR3                      ; brif end of message
                jsr writechrconduc              ; output the character, converted to upper case situationally
                bra ERROR2                      ; handle another character
ERROR3          lds freetop                     ; reset the stack pointer (error routine could be called anywhere)
                ; 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
                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_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 first character in A with C set for digits and clear otherwise.
parse_lineno    ldd zero                        ; clear out accumlator but preserve carry flag
                std binval
                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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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       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 1,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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The END command.
cmd_end         bne SNERROR                     ; error out if there is an argument
                ;jsr closeall                    ; close all files for END
                clra                            ; flag END
                bra cmd_stop0                   ; go do the stop/end
cmd_stop        bne SNERROR                     ; raise error if there was an argument
                lda #0xff                       ; flag STOP
cmd_stop0       sta 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_data2                   ; brif end of line
                cmpa #':                        ; end of statement?
                bne cmd_data1                   ; 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Miscelaneous strings
prompt          fcn 'OK'                        ; general prompt
breakmsg        fcn 'BREAK'                     ; "BREAK" message
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The LET command which is the default if no token begins a statement
cmd_let         jmp SNERROR                     ; not yet implemented
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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        clra                            ; clear "not token" flag
                clrb                            ; clear the "in data" flag
                ldy #tokebuff                   ; point to destination buffer
                pshs d,y                        ; set return value, the "not token" flag, and the "in data" flag
tokenize0       lda ,x+                         ; get input character
                bne tokenize1                   ; brif not end of input
tokenize0a      sta ,y+                         ; blank out final byte in result
tokenize0b      leas 2,s                        ; clean up temporaries on stack
                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
tokenize1       tst ,s                          ; are we in the middle of a "not token"?
                beq tokenize3a                  ; brif not
                bsr setcifalpha                 ; is it alpha
                bcs tokenize2                   ; brif so - store it and continue
                bsr setcifdigit                 ; is it numeric?
                bcc tokenize3                   ; brif not
tokenize2       sta ,y+                         ; save output character
                bra tokenize0                   ; check for another
tokenize3       clr ,s                          ; clear the "not token" flag
tokenize3a      cmpa #'"                        ; is it a string?
                bne tokenize5                   ; brif not
                sta ,y+                         ; save string delimiter
tokenize4       lda ,x+                         ; get input character
                beq tokenize0a                  ; brif end of input
                sta ,y+                         ; save it in output
                cmpa #'"                        ; end of string?
                bne tokenize4                   ; brif not
                bra tokenize0                   ; brif 
tokenize5       cmpa #':                        ; end of statement?
                bne tokenize6                   ; brif not
                clr 1,s                         ; reset "in data" flag
                bra tokenize0a                  ; stash it and continue
tokenize6       cmpa #0x20                      ; is it a space?
                beq tokenize0a                  ; brif so - stash it unmodified
                tst 1,s                         ; are we "in data"?
                bne tokenize0a                  ; brif so - don't tokenize it
                cmpa #''                        ; ' shortcut for remark?
                bne tokenize9                   ; brif not
                ldd #':*256+tok_apos            ; put token for ' and an implied colon
                std ,y++                        ; stash it
tokenize8       lda ,x+                         ; fetch byte from input
                sta ,y+                         ; stash in output
                bne tokenize8                   ; brif not end of input
                bra tokenize0b                  ; go finish up
tokenize9       bsr setcifdigit                 ; is it a digit?
                bcs tokenize0a                  ; 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
                clrb                            ; initialize the token number
                clra                            ; initialize secondary table flag
                pshs d,x                        ; save start of input token and the token counter
tokenize10      ldb ,u                          ; are we at the end of the table?
                bne tokenize11                  ; brif not
                ldu #secondarydict              ; point to secondary token dictionary
                clr ,s                          ; reset token counter
                com 1,s                         ; flip to secondary token flag
                bne tokenize10                  ; brif we haven't already done the secondaries
                puls d,x                        ; get back input pointer and clear stack temporaries
                com ,s                          ; set "not token flag"
                lda ,x+                         ; get character
                bra tokenize0a                  ; stash it and continue
tokenize11      ldx 2,s                         ; get back start of input token
tokenize12      ldb ,x+                         ; get input character
                cmpb #'z                        ; is it above lower case Z?
                bhi tokenize13                  ; brif so
                cmpb #'a                        ; is it below lower case A?
                blo tokenize13                  ; brif so
                subb #0x20                      ; convert to upper case
tokenize13      subb ,u+                        ; does it match?
                beq tokenize12                  ; brif so - check another
                cmpb #0x80                      ; did it match with high bit set?
                beq tokenize15                  ; brif so - exact match
                leau -1,u                       ; back up to current test character
tokenize14      ldb ,u+                         ; end of token?
                bpl tokenize14                  ; brif not
                inc ,s                          ; bump token counter
                bra tokenize10                  ; go check another one
tokenize15      orb ,s+                         ; merge token number with the high bit (bit 7 set from above)
                lda ,s+                         ; get back secondary flag and set flags on it
                leas 2,s                        ; clean up saved input pointer from stack
                bpl tokenize17                  ; brif primary token
                skip2
tokenize18      lda #':                         ; for putting implied colons in
tokenize16      std ,y++                        ; put output into buffer
                jmp tokenize0                   ; go handle more input
tokenize17      cmpb #tok_else                  ; is it ELSE?
                beq tokenize18                  ; brif so - stash it with colon
                cmpb #tok_data                  ; is it DATA?
                bne tokenize18a                 ; brif not
                stb 1,s                         ; set "in data" flag
tokenize20      stb ,y+                         ; stash token
                jmp tokenize0                   ; go handle more
tokenize18a     cmpb #tok_rem                   ; is it REM?
                beq tokenize19                  ; brif so
                cmpb #tok_apos                  ; apostrophe REM?
                bne tokenize20                  ; brif not - stash token and continue
                lda #':                         ; stash the implied colon
                sta ,y+
                bra tokenize19
tokenize19a     ldb ,x+                         ; fetch next input character
tokenize19      stb ,y+                         ; stash the character
                bne tokenize19a                 ; brif not end of input - do another
                jmp tokenize0b                  ; stash end of buffer and handle cleanup
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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"
                setstr __cmdjump="%(__cmdjump)\tfdb cmd_{2}\n"
tok_{2}         equ __cmdnum
__cmdnum        set __cmdnum+1
                endm
deffunc         macro noexpand
                setstr __functab="%(__functab)\tfcs {1}\n"
                setstr __funcjump="%(__funcjump)\tfcb {3}\n\tfdb func_{2}\n"
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
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