diff src/interp.s @ 74:e74d00ac6b79

Split some code into separate files for easier management (2) Because the source for lwbasic is so large, split it into several different files to make it easier to navigate and modify. This is part two of the split. Also includes fix for dependency tracking related to the split in the make file.
author William Astle <lost@l-w.ca>
date Sun, 06 Aug 2023 00:36:48 -0600
parents
children 5f8f0b0781e8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/interp.s	Sun Aug 06 00:36:48 2023 -0600
@@ -0,0 +1,250 @@
+                *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 move saves an extra cycle in the LDA sequence using the LDA extended followed by JMP extended
+; sequence.
+;
+; This whole thing could be sped up by keeping the input pointer in a register. However, retaining the ability to
+; use Y without having to save it first is likely more beneficial.
+nextchar        inc inputptr+1                  ; bump LSB of input pointer
+                bne curchar                     ; brif no carry
+                inc inputptr                    ; bump MSB
+curchar         lda [inputptr]                  ; read the byte
+                cmpa #'9+1                      ; clear C if above ASCII digits, Z if colon
+                bhs curchar0                    ; brif above the ASCII digits
+                cmpa #0x20                      ; is it a space?
+                beq nextchar                    ; brif so - skip over it
+                suba #'0                        ; clever way to set C if >= ASCII 0, Z if zero
+                suba #-'0
+curchar0        rts
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; This is exactly the same as nextchar except it doesn't skip spaces. Unfortunately, for efficiency purposes, we need
+; to actually duplicate code here.
+nextcharraw     inc inputptr+1                  ; bump LSB of input pointer
+                bne curchar                     ; brif no carry
+                inc inputptr                    ; bump MSB
+curcharraw      lda [inputptr]                  ; fetch the byte
+                cmpa #'9+1                      ; clear C if above digits, set Z if colon
+                bhs curcharraw0                 ; brif above digits
+                suba #'0                        ; clever way to set C if >= ASCII 0, Z if zero
+                suba #-'0
+curcharraw0     rts
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; The error handler
+;
+; Enter with the error number in B. This routine will do some cleanup and handle any ON ERROR GOTO handler that
+; may be active.
+;
+; Note the error message lookup does not need to be efficient which is why the lookup just runs through the list
+; of error messages in sequence looking for NUL terminators. The specific handling of B (error number) below avoids
+; issues if there happen to be error codes above 128.
+ERROR           clr filenum                     ; reset display device to console
+                jsr writecondnl                 ; do a newline if needed (will preserve B)
+                ldx #errormsg                   ; point to error message list
+                incb                            ; account for decb below
+                bra ERROR1                      ; go search for correct message
+ERROR0          lda ,x+                         ; end of message?
+                bne ERROR0                      ; brif not end of message
+ERROR1          decb                            ; at the correct one?
+                bne ERROR0                      ; brif not - skip to next one
+ERROR2          jsr writestrconduc              ; output error message
+                ldu curline                     ; are we in immediate mode?
+                beq ERROR3                      ; brif so
+                ldx #inmsg                      ; point to " in "
+                jsr writestrconduc              ; output " in "
+                ldd 2,u                         ; get line number
+                jsr print_uint16d               ; display the line number
+ERROR3          lds freetop                     ; reset the stack pointer (error routine could be called anywhere)
+                clr ,-s                         ; reset the call stack
+                sts stackptr
+                ; fall through to immediate mode intentional
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Immediate mode handler
+immediate       jsr writecondnl                 ; do newline if required
+                ldx #prompt                     ; point to prompt string
+                jsr console_outstrn
+immediate0      jsr readline                    ; read input line
+                bcs immediate0                  ; brif ended with BREAK
+                ldx #linebuff                   ; point to start of line input buffer
+                stx inputptr                    ; set input pointer
+                jsr curchar                     ; skip spaces and set flags
+                bcs immediate1                  ; brif there's a line number
+                tsta                            ; is there anything there at all (end of line)?
+                beq immediate0                  ; brif not - read another line
+                ldx inputptr                    ; get the modified input pointer processing above
+                jsr tokenize                    ; tokenize the line at inputptr, return with result at tokebuff and X
+                jsr interpretline               ; go interpret the tokenized line
+                bra immediate                   ; go handle another line
+immediate1      bsr parse_lineno                ; parse the line number
+                bsr prog_findline               ; go see if the line is in the program
+                bne immediate3                  ; brif not - no need to delete it
+                ldu ,x                          ; get next line pointer which is where we start the copy from
+                leay ,x                         ; use temp pointer for copying
+immediate2      lda ,u+                         ; get source byte
+                sta ,y+                         ; stash it
+                cmpu vartab                     ; did we reach the end of the program text?
+                blo immediate2                  ; brif not
+                sty vartab                      ; save new end of program
+immediate3      jsr curchar                     ; skip any spaces after line number
+                tsta                            ; is it the end of input (don't test for colon)
+                beq immediate6                  ; brif so - we don't need to insert a line
+                pshs x                          ; save program insert location and line number
+                ldx inputptr                    ; point to line text
+                jsr tokenize                    ; tokenize line, get length to D
+                leay ,x                         ; save tokenized line pointer
+                addd #4                         ; account for next line pointer and line number
+                ldx vartab                      ; get start of copy location
+                leau d,x                        ; set destination copy location D bytes further up
+                stu vartab                      ; save new end of program
+immediate4      lda ,-x                         ; get byte from program
+                sta ,-u                         ; stash it above the empty space
+                cmpx ,s                         ; did we reach the insertion point?
+                bne immediate4                  ; brif not - keep going
+                leas 2,s                        ; clear insertion location
+                stu ,x++                        ; set next line pointer to not null
+                ldd binval                      ; set the line number for the program
+                std ,x++
+immediate5      lda ,y+                         ; get byte from tokenized line
+                sta ,x+                         ; stash it in the program
+                bne immediate5                  ; brif not at end of tokenized line (see note for fixlineptrs)
+immediate6      bsr prog_fixlineptrs            ; fix up line pointers (all of them)
+                ldx vartab                      ; clear out variables
+                stx objecttab
+                stx freestart
+                bra immediate0                  ; go handle more input
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Fix up next line pointers. Enter at prog_fixlineptrs to do the entire program. Enter at prog_fixlineptrsx to start
+; at the line pointered to by X, which MUST NOT point to the end of the program.
+;
+; Works by simply scanning for a NUL in the program text after a line header (pointer to next line and line number)
+; and uses that as the new next line pointer. A NULL next line pointer flags the end of the program.
+;
+; Observation: if the program text format is changed such that it can include NULs embedded within a line, this routine
+; will need to be updated to grok that.
+prog_fixlineptrs
+                ldx progtext                    ; point to start of program
+prog_fixlineptrsx
+                ldu ,x                          ; are we at the end of the program?
+                beq prog_findline2              ; brif not (borrow RTS from findline)
+                leau 4,x                        ; point to line text (past pointer and line number)
+prog_fixlineptrs1
+                lda ,u+                         ; are we at the end of this line?
+                bne prog_fixlineptrs1           ; brif not
+                stu ,x                          ; set the next pointer for the previous line
+                leax ,u                         ; move to the next line
+                bra prog_fixlineptrsx           ; go handle the next line
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Find a line in the program. Returns with C set and Z clear if no match and C clear and Z set if a match is found. X
+; will point to either the exact matched line *or* the line that would be immediately after the desired line number if
+; the line had been present, which could be the end of the program. D and U are clobbered. Enter at prog_findlinex to
+; start searching at the line pointed to by X. Enter at prog_findline to start at the beginning of the program. Enter
+; with the desired line number in binval.
+prog_findlinecl ldx curline                     ; get current line pointer
+                beq prog_findline               ; brif immediate mode
+                ldd binval                      ; get desired line number
+                cmpd 2,x                        ; is the desired line number >= current line?
+                beq prog_findline2              ; brif this is the right line (optimizes goto self)
+                bhi prog_findlinex              ; brif desired line higher: start here instead of program start
+prog_findline   ldx progtext                    ; point to start of program
+prog_findlinex  ldu binval                      ; get line number to search for
+prog_findline0  ldd ,x                          ; end of program?
+                beq prog_findline1              ; brif not
+                cmpu 2,x                        ; does line number match? Z set if so, clear if not; C set not found
+                bls prog_findline2
+                ldx ,x                          ; move to next line
+                bra prog_findline0              ; see if we found the line yet
+prog_findline1  coma                            ; set carry for not found; also clears Z because D is zero from above
+prog_findline2  rts
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Parse a line number and return it in binval; raise syntax error if the line number overflows 16 bits unsigned.
+; Preserves; registers except D. This will accept the entire 16 bit unsigned number range which is why there is
+; a BCS after every shift or add. Enter with the input pointer pointing to the number to parse.
+parse_lineno    ldd zero                        ; clear out accumlator but preserve carry flag
+                std binval
+                jsr curchar                     ; set flags on current character; skip spaces
+                bcc parse_lineno1               ; brif first character wasn't a digit - default to zero
+parse_lineno0   suba #0x30                      ; adjust to binary digit
+                pshs a                          ; save digit so we can add it later
+                ldd binval                      ; get accumulated number
+                lslb                            ; multiply accumulator by 10
+                rola                            ; times 2
+                bcs SNERROR                     ; brif overflow
+                lslb
+                rola                            ; times 4
+                bcs SNERROR                     ; brif overflow
+                addd binval                     ; times 5 (add orignal value to times 4)
+                bcs SNERROR                     ; brif overflow
+                lslb
+                rola                            ; times 10
+                bcs SNERROR                     ; brif overflow
+                addb ,s+                        ; add in accumulated digit
+                adca #0
+                bcs SNERROR                     ; brif overflow
+                std binval                      ; save accumulated number
+                jsr nextcharraw                 ; get next input character; DO NOT skip spaces
+                bcs parse_lineno0               ; brif it's also a digit
+parse_lineno1   rts
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Main interpretation loop
+;
+; Enter at interpret with inputptr pointing to the code stream to interpret.
+; Enter at interpretline with X pointing to the command stream to interpret which will return to the caller one the
+;     command stream has completed. STOP or BREAK will return with carry set while END or falling off the end of the
+;     code will return with carry clear. In the event of an error, the usual error processing will be done and control
+;     will return to immediate mode with the stack reset.
+interpret       jsr breakcheck                  ; check for BREAK
+                bcs cmd_stop0                   ; brif BREAK detected - go stop the program
+                ldx inputptr                    ; get interpration address
+                stx curstmt                     ; save address of the current statement (needed for some stuff)
+                lda ,x+                         ; are we at the end of the line?
+                beq interpret0                  ; brif so
+                cmpa #':                        ; end of statement?
+                beq interpret3                  ; brif so - do a statement
+SNERROR         ldb #err_sn                     ; raise a syntax error
+                jmp ERROR
+interpret0      sta endflag                     ; flag the program exit state as "END" (will be zero)
+                ldd curline                     ; were we in immediate mode?
+                bne interpret1                  ; brif not
+                clra                            ; clear carry to indicate normal exit
+                rts                             ; return to caller
+interpret1      ldd ,x                          ; are we at the end of the program?
+                beq interpret4                  ; brif so - bail out
+                stx curline                     ; save pointer to current line
+                leax 3,x                        ; set input pointer one before the start of the line text
+interpret2      stx inputptr
+interpret3      jsr nextchar                    ; fetch first character of next statement
+                beq interpret                   ; brif end of statement - do the next statement dance
+                tsta                            ; set flags properly for token
+                lbpl cmd_let                    ; brif no command - do assignment (LET command is optional)
+                ldx #primaryjump                ; point to jump table
+                anda #0x7f                      ; lose bit 7
+                leax a,x                        ; get half way to the correct offset
+                ldx a,x                         ; get the address the other half of the way from here
+                jsr nextchar                    ; skip past token and set flags
+                jsr ,x                          ; call the routine
+                bra interpret                   ; go handle the next statement dance
+interpret4      bsr cmd_stop1                   ; make sure stack is aligned correctly (will not return)
+interpretline   clr curline                     ; blank out current line pointer (for immediate mode)
+                clr curline+1
+                leax -1,x                       ; move back before start of code stream
+                bra interpret2                  ; go interpret this statement and then continue with stuff
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Check for character in B and raise a syntax error if not found at current input pointer. If it is found, fetch the
+; next input character.
+syncheckb       cmpb [inputptr]                 ; do we have a syntax match?
+                bne SNERROR                     ; brif not
+                jmp nextchar                    ; return next input character
+                *pragmapop list