view src/interp.s @ 140:86f6f3a71e60 default tip

Fix some bugs in tokenization/parsing routine
author William Astle <lost@l-w.ca>
date Tue, 16 Jul 2024 22:30:07 -0600
parents e49bd0493baf
children
line wrap: on
line source

                *pragmapush list
                *pragma list
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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 mode 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Immediate mode handler. This operates as follows:
; 1. Write a newline if needed
; 2. Output the prompt if needed
; 3. Read an input line
; 4. If the line is terminated by BREAK/ESC, go back to 3
; 5. Find the first non-space character. If it is numeric, go on to 7
; 6. Tokenize the input line, interpret it, and go back to 1
; 7. Parse the line number. If it is invalid, do a syntax error
; 8. Find the line number in the program and remove it if present
; 9. If there is no non-space character after the number, go back to 3
; 10. Tokenize the line and insert it into the program
; 11. Go back to 3
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
immediate0a     lda ,x+                         ; do we have anything at all?
                beq immediate0                  ; brif not - just read another line
                cmpa #0x20                      ; space?
                beq immediate0a                 ; brif so
                leax -1,x                       ; compensate for extra increment above
                bsr setcifdigit                 ; do we have a line number?
                bcs immediate1                  ; brif so - go handle program editing
                jsr parse                       ; parse the line into tokens
                bcs immediatee                  ; brif there was a parse error
                jsr interpretline               ; go interpret the tokenized line
                bra immediate                   ; go handle another line
immediatee      jmp SNERROR                     ; go do a syntax error
immediate1      stx inputptr                    ; save input pointer for parsing purposes
                bsr parse_lineno                ; parse the line number that was provided
                bsr prog_delline                ; remove the line from the program if it exists
                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
                ldx inputptr                    ; point to line text
                jsr parse                       ; tokenize line, get length to D
                ldd binval                      ; get the line number
                jsr prog_addline                ; insert the encoded line at X into program as line Y
immediate6      ldx vartab                      ; clear out variables
                stx objecttab
                stx freestart
                bra immediate0                  ; go handle more input
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Parse a line number and return it in binval; raise syntax error if the line number overflows 16 bits unsigned.
; Preserves; registers except D. This will accept the entire 16 bit unsigned number range which is why there is
; a BCS after every shift or add. Enter with the input pointer pointing to the number to parse.
parse_lineno    ldd zero                        ; clear out accumlator but preserve carry flag
                std binval
                jsr curchar                     ; set flags on current character; skip spaces
                bcc parse_lineno1               ; brif first character wasn't a digit - default to zero
parse_lineno0   suba #0x30                      ; adjust to binary digit
                pshs a                          ; save digit so we can add it later
                ldd binval                      ; get accumulated number
                lslb                            ; multiply accumulator by 10
                rola                            ; times 2
                bcs parse_lineno2               ; brif overflow
                lslb
                rola                            ; times 4
                bcs parse_lineno2               ; brif overflow
                addd binval                     ; times 5 (add orignal value to times 4)
                bcs parse_lineno2               ; brif overflow
                lslb
                rola                            ; times 10
                bcs parse_lineno2               ; brif overflow
                addb ,s+                        ; add in accumulated digit
                adca #0
                bcs parse_lineno2               ; 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
parse_lineno2   jmp SNERROR                     ; relay to syntax error
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Find line number table entry
;
; Entry:
; D: the desired line number
;
; Exit:
; U: pointer to line number table entry
; CC.C: clear
;
; Error:
; CC.C: set
;
; This works by doing a binary search through the line number table.
prog_findline   ldu prog_linetab                ; point to program line table
                ldx prog_linetabp               ; get end of table
                leax -linetabent_size,u         ; move back to the start of the last entry
                pshs x,u                        ; save "high" at 0,s and "low" at 2,s
                tfr d,x                         ; save line number for later comparisons
prog_findline1  ldd ,s                          ; get high pointer
                subd 2,s                        ; get different with low pointer
                bcs prog_findline2              ; brif high is below low - we didn't find it
                lsra                            ; find half way
                rorb
                andb #0b11111100                ; round down for 4 bytes per entry
                addd prog_linetab               ; offset into line table
                tfr d,u                         ; move to a pointer
                cmpx linetabent_num,u           ; is the desired number less, equal, or greater?
                beq prog_findline2              ; brif match
                blo prog_findline3              ; brif desired line is lower
                leau linetabent_size,u          ; skip past this non-matching item
                stu 2,s                         ; save new low pointer
                bra prog_findline1              ; go do another iteration
prog_findline2  leas 4,s                        ;* clean up the temporaries (C clear from compare above or set from
                rts                             ;* subtraction above
prog_findline3  leau -linetabent_size,u         ; move before this non-matching entry
                stu ,s                          ; save new top entry pointer
                bra prog_findline1              ; go do another iteration
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Delete a line from the program:
;
; Entry:
; D: the line number to delete
;
; This routine removes a line from the program. This works by deallocating the line data and moving all subsequent
; line data forward to close the gap. The line table pointer will also be removed and the subsequent line table
; entries will also be brought forward to fill the gap. While closing the gap in the line table, the line data
; pointers will be adjusted to account for the relocation of the line data following the deleted line. The line number
; table size allocation will not be adjusted.
prog_delline    bsr prog_findline               ; get a pointer to the desired line table entry
                bcs prog_delline3               ; brif the line wasn't in the program - we have nothing to do
                ldd linetabent_size+linetabent_ptr,u ; get pointer to next line data
                subd linetabent_ptr,u           ; now D is the length of the line data to collapse out
                pshs d                          ; save the calculated length - we need it for later
                ldy linetabent_ptr,u            ; get pointer to the line data to delete
                leax d,y                        ; point to data to move
                bra prog_delline1               ; go handle the loop, including the case where we copy nothing
prog_delline0   lda ,x+                         ; copy a byte down
                sta ,y+
prog_delline1   cmpx vartab                     ; at the end of the program?
                blo prog_delline0               ; brif not
                sty vartab                      ; save new variable table location
prog_delline2   ldx linetabent_size+linetabent_num,u ; get number of next line
                ldd linetabent_size+linetabent_ptr,u ; get pointer for next line
                subd ,s                         ; adjust for length removed in the line data
                std ,u++                        ; save in the vacated entry
                stx ,u++
                cmpu prog_linetabp              ; at the end of allocated table entries?
                blo prog_delline2               ; brif not
                leau -linetabent_size,u         ; move back to the last actual entry
                stu prog_linetabp               ; update the line table pointer
                bsr prog_shrinklt               ; shrink the line table if appropriate
                jmp cmd_newvars                 ; clear out the variable table
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; prog_shrinklt: shrink the line table to its minimum possible size, but keep it a multiple of linetab_stride entries.
; This will leave the state of the variable table undefined so it needs to be cleared out properly before doing anything
; else after using this routine.
prog_shrinklt   ldd prog_linetabp               ; get the end of the table entries
                subd prog_linetab               ; now we have the length of the table
                pshs d                          ; save original size
                bitb #(linetabent_size*linetab_stride)-1 ; is there a remainder?
                beq prog_shrinklt0              ; brif not
                andb #~((linetabent_size*linetab_stride)-1) ; actually round down now
                addd #linetabent_size*linetab_stride ; and go up to the next stride
prog_shrinklt0  subd ,s++                       ; now D is the difference in size; will be negative if shrinking
                bne prog_resizelt               ; brif there is something to do - go resize the table
prog_delline3   rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; prog_expandlt: expand the line table by one stride size.
prog_expandlt   ldd #linetabent_size*linetab_stride ; get size of an expansion strider
                ; fall through to the resize routine
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Adjust the size of the program line table, either larger or smaller.
;
; Enter:
; D: the number of bytes to expand the table by; negative to shrink it; must be a multiple of the table entry size
;
; Exit:
; D: the number of bytes the table was expanded (negative means shrinkage)
; * prog_text: points to the new start of the program text
; * vartab: points to the new start of the variable table
; * all program line pointers are adjusted by D bytes
;
; Error:
; * OM error if there isn't enough memory to expand the table by D bytes
prog_resizelt   pshs d                          ; we're going to need the size number later
                ldu prog_text                   ; point to start of program text
                leax d,u                        ; point to the new address to copy to
                addd vartab                     ; calculate the new start of the variable table
                tfr d,y                         ; save it for later
                jsr mem_checkptr                ; throw an error if it doesn't fit
                ldd vartab                      ; get the end of the program text
                subd prog_text                  ; now D is the number of bytes to copy
                stx prog_text                   ; save new program text pointer
                sty vartab                      ; save new variable table pointer
                jsr mem_copy                    ; copy the program text up or down
                ldx prog_linetab                ; point to first entry in the program line table
prog_resizelt0  ldd linetabent_ptr,x            ; get the pointer for this entry
                addd ,s                         ; add in the offset
                std linetabent_ptr,x            ; save updated pointer
                leax 4,x                        ; move to next entry
                cmpx prog_text                  ; end of table?
                blo prog_resizelt0              ; brif not
                puls d,pc                       ; clean up stack and return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Insert a line into the program.
;
; Note that we do a sequential walk back from the end of the line table looking for the insert location because we can
; update all the line data pointers on the way through and that has to be done one by one anyway.
;
; Entry:
; D: length of line to insert
; X: pointer to line data to insert *which MUST be within the prog_text...vartab area*
; U: the line number we're adding
prog_addline    pshs d,x,u                      ; save length and pointer
                ldx prog_linetabp               ; get line table pointer
                leax linetabent_size,x          ; add in space for new entry
                cmpx prog_text                  ; did we run into the program?
                blo prog_addline0               ; brif not
                bsr prog_expandlt               ; expand the program line table
                ldx 2,s                         ; adjust the pointer for the line to insert
                leax d,x
                stx 2,s
prog_addline0   ldu prog_linetabp               ; point to the end of the line table
                leax linetabent_size,x          ; point to the new end of the table
                stx prog_linetabp               ; save new end of table pointer
                ldd linetabent_ptr,u            ; move the dummy end of table pointer up one slot
                addd ,s                         ; (and adjust for the insert)
                std linetabent_ptr,x
                ldx 4,s                         ; get the new line number
prog_addline1   cmpu prog_linetab               ; are we at the start of the line table?
                beq prog_addline2               ; brif so - we're inserting here
                cmpx -linetabent_size+linetabent_num,u ; how does this line compare with the previous table entry?
                bhs prog_addline2               ; brif our number is larger (or equal) to previous entry number
                ldd -linetabent_size+linetabent_num,u ; copy line number up one slot
                std linetabent_num,u
                ldd -linetabent_size+linetabent_ptr,u ; get pointer of that entry
                addd ,s                         ; add the size of the newly inserted line to adjust for after insert
                std linetabent_ptr,u            ; save it in the new slot
                leau -linetabent_size,u         ; move back a slot
                bra prog_addline1               ; go check another slot
prog_addline2   ldd linetabent_size+linetabent_ptr,u ; get pointer to the line to move out of the way
                subd ,s                         ; unadjust for the insert size - get original location
                std linetabent_ptr,u            ; set the data pointer for the new entry
                stx linetabent_num,u            ; set the line number for this entry
                tfr d,x                         ; set insertion point
                ldu 2,s                         ; get the source data to insert
                ldd ,s                          ; get length of source data
                jsr mem_insert                  ; shift the block into place
                jmp cmd_newvars                 ; erase variables and return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Main interpretation loop
;
; Enter at interpret with inputptr pointing to the code stream to interpret.
; Enter at interpretline with X pointing to the command stream to interpret which will return to the caller one the
;     command stream has completed. STOP or BREAK will return with carry set while END or falling off the end of the
;     code will return with carry clear. In the event of an error, the usual error processing will be done and control
;     will return to immediate mode with the stack reset.
interpret       jsr breakcheck                  ; check for BREAK
                bcs cmd_stop0                   ; brif BREAK detected - go stop the program
                ldx inputptr                    ; get interpration address
                stx curstmt                     ; save address of the current statement (needed for some stuff)
                lda ,x+                         ; are we at the end of the line?
                beq interpret0                  ; brif so
                cmpa #':                        ; end of statement?
                beq interpret3                  ; brif so - do a statement
SNERROR         ldb #err_sn                     ; raise a syntax error
                jmp ERROR
interpret0      sta endflag                     ; flag the program exit state as "END" (will be zero)
                ldd curline                     ; were we in immediate mode?
                bne interpret1                  ; brif not
                clra                            ; clear carry to indicate normal exit
                rts                             ; return to caller
interpret1      ldd ,x                          ; are we at the end of the program?
                beq interpret4                  ; brif so - bail out
                stx curline                     ; save pointer to current line
                leax 3,x                        ; set input pointer one before the start of the line text
interpret2      stx inputptr
interpret3      jsr nextchar                    ; fetch first character of next statement
                beq interpret                   ; brif end of statement - do the next statement dance
                tsta                            ; set flags properly for token
                lbpl cmd_let                    ; brif no command - do assignment (LET command is optional)
                ldx #exectab_cmd                ; point to jump table
                leax a,x                        ; get half way to the correct offset
                ldx a,x                         ; get the address the other half of the way from here
                jsr nextchar                    ; skip past token and set flags
                jsr ,x                          ; call the routine
                bra interpret                   ; go handle the next statement dance
interpret4      bsr cmd_stop1                   ; make sure stack is aligned correctly (will not return)
interpretline   clr curline                     ; blank out current line pointer (for immediate mode)
                clr curline+1
                leax -1,x                       ; move back before start of code stream
                bra interpret2                  ; go interpret this statement and then continue with stuff
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Check for character in B and raise a syntax error if not found at current input pointer. If it is found, fetch the
; next input character.
syncheckb       cmpb [inputptr]                 ; do we have a syntax match?
                bne SNERROR                     ; brif not
                jmp nextchar                    ; return next input character
                *pragmapop list