changeset 132:917b4893bb3d

Checkpoint before redoing a bunch of code for clarity
author William Astle <lost@l-w.ca>
date Mon, 24 Jun 2024 23:44:39 -0600
parents 95f174bf459b
children c7f2f63cbcfe
files src/defs.s src/interp.s src/parse.s src/progctrl.s src/vars.s
diffstat 5 files changed, 274 insertions(+), 128 deletions(-) [+]
line wrap: on
line diff
--- a/src/defs.s	Sat May 18 00:41:46 2024 -0600
+++ b/src/defs.s	Mon Jun 24 23:44:39 2024 -0600
@@ -14,6 +14,21 @@
 stringstacknum  equ 20                          ; number of entries on the anonymous string descriptor stack
 stackheadroom   equ 50                          ; required headroom for the stack on OM checks
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Program storage structure
+;
+; The program itself consists of two sets of data. The first is a table of line numbers. Each consists of a 16 bit
+; pointer to the line data and a 16 bit unsigned line number. These entries are stored in line number order. The line
+; data is also stored in line number order. The length of each line is the difference between the current line pointer
+; and the next line pointer. There is a final dummy line number entry after the last program line which holds the
+; pointer to the first free byte after the program. This is stored there to allow for simpler code for calculating line
+; lengths.
+;
+; Note: linetab_stride + linetabent_size must be a power of two *less than* 256
+linetabent_ptr  equ 0                           ; offset of the pointer to the line data (must be first)
+linetabent_num  equ 2                           ; offset to line number
+linetabent_size equ 4                           ; size of a line number table entry, must be power of two
+linetab_stride  equ 16                          ; expand the line table in blocks of 16 entries, must be power of two
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Data structure used for calculations. Calculations are handled via structures called value accumulators. A value
 ; accumulator consists of a data type flag (at the end of the structure) and a data area whose layout varies based
 ; on the actual data type. The layouts for each value type are described below.
--- a/src/interp.s	Sat May 18 00:41:46 2024 -0600
+++ b/src/interp.s	Mon Jun 24 23:44:39 2024 -0600
@@ -79,7 +79,7 @@
                 beq immediate0                  ; brif not - just read another line
                 cmpa #0x20                      ; space?
                 bne immediate0c                 ; brif not
-immediate0b     leax 1,x                        ; move past the 
+immediate0b     leax 1,x                        ; move past the space
                 bra immediate0a                 ; keep looking for the start of input
 immediate0c     bsr setcifdigit                 ; do we have a line number?
                 bcs immediate1                  ; brif so - go handle program editing
@@ -90,85 +90,181 @@
                 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
+                jsr prog_remove                 ; remove the line from the program if it exists
 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 parse                       ; 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
+                ldy binval                      ; get the line number
+                jsr prog_insert                 ; 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
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; 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.
+; Find line number table entry
+;
+; Entry:
+; D: the desired line number
 ;
-; 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.
+; Exit:
+; U: pointer to line number table entry
+; CC.C: clear
+;
+; Error:
+; CC.C: set
 ;
-; 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
+; 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 -prog_lineentl,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 prog_lineentl,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)
+                rts
+prog_findline3  leau -prog_lineentl,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
+                leas 2,s                        ; clear out the temp we no longer need
+                jsr cmd_newvars                 ; clear the variables out
+prog_delline3   rts
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; 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
+; prog_shrinklt: shrink the line table to its minimum possible size, but keep it a multiple of linetab_stride entries
+prog_shrinklt   ldd prog_linetabp               ; get the end of the table entries
+                subd prog_linetab               ; now we have the length of the table
+                andb #(linetabent_size*linetab_stride)-1 ; is there a remainder?
+                beq prog_shrinklt0              ; brif not
+                addd #linetabent_size*linetab_strider
+prog_shrinklt0  tfr d,u                         ; put in a pointer register
+                leau linetabent_size,x          ; move to the end of the phantom entry
+                cmpu prog_text                  ; anything to do?
+                beq prog_shrinklt2              ; brif not
+                ldx prog_text                   ; point to source copy point
+prog_shrinklt1  lda ,x+                         ; copy a byte down
+                sta ,u+
+                cmpx vartab                     ; end of program?
+                blo prog_shrinklt1              ; brif not
+                stu vartab                      ; save new end of program
+                jmp cmd_newvars                 ; clear variables
+prog_shrinklt2  rts
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Insert a line into the program
+;
+; Entry:
+; D: length of line to insert
+; X: pointer to line data to insert
+; 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
+                addd #linetabent_size*linetab_stride ; add in space for expanded line table
+prog_addline0   addd vartab                     ; calculate the new end of program data
+                jsr checkmem_addr               ; verify there is enough memory
+                cmpx prog_text                  ; do we need to expand the line number table?
+                blo prog_addline3               ; brif not
+                ldx vartab                      ; point to byte past end of program
+                leau linetab_stride*linetabent_size,x ; set up destination pointer
+                stu vartab                      ; set up new end of program text
+prog_addline1   lda ,-x                         ; copy a byte up
+                sta ,-u
+                cmpx prog_text                  ; did we hit the start of the program?
+                bne prog_addline1               ; brif not
+                ldx prog_linetab                ; point to start of line table
+prog_addline2   ldd linetabent_ptr,x            ; get pointer to this line
+                addd #linetab_stride*linetabent_size ; adjust offset for the expanded table
+                std linetabent_ptr,x
+                leax linetabent_size,x          ; move to next entry
+                cmpx prog_linetabp              ; at end of table?
+                bls prog_addline2               ; brif we're at the end of the table
+prog_addline3   ldx prog_linetabp               ; repoint to first "free" table entry
+                ldd linetabent_ptr,x            ; get pointer for the end of the program
+                std linetabent_ptr+linetabent_size,x ; move it a slot forward
+                ldd 4,s                         ; get desired line number
+                std linetabent_num,x            ; put line number in
+                bra prog_addline5               ; brif so - this is where we add it
+prog_linetab4   cmpd -linetabent_size+linetabent_num,x ; is our line number less than previous entry?
+                bhs prog_addline6               ; brif not - we're at the right place
+                leax -linetabent_size,x         ; move back an entry
+                ldu linetabent_num,x            ; move line number to next entry
+                stu linetabent_num+linetabent_size,x
+                ldu linetabent_ptr,x            ; and move the pointer
+                stu linetabent_ptr+linetabent_size,x
+prog_linetab5   cmpx prog_linetab               ; at the start of the table?
+                bhi prog_addline4               ; brif not
+prog_linetab6   ldu vartab                      ; point to end of program data
+                ldd ,s                          ; get length of line
+                leay d,u                        ; Y points to the destination of the move
+                sty vartab                      ; save new end of program text
+                bra prog_linetab8               ; jump into loop in case nothing to copy
+prog_linetab7   lda ,-u                         ; copy a byte up
+                sta ,-y
+prog_linetab8   cmpu linetabent_ptr,x           ; finished the copy?
+                bne prog_linetab7               ; brif not
+prog_linetab9   leax linetabent_size,x          ; move to next entry
+                ldd linetabent_ptr,x            ; adjust the pointer for the newly inserted line
+                addd ,s
+                std linetabent_ptr,x
+                cmpx prog_linetabp              ; run through the whole table?
+                blo prog_linetab9               ; brif not
+                puls y                          ; get copy length to counter
+                puls x                          ; get pointer to line data
+prog_linetab10  lda ,x+                         ; copy a byte into the program data
+                sta ,u+
+                leay -1,y                       ; done all of it?
+                bne prog_linetab10              ; brif not
+                leas 2,s                        ; lose line number
+                jmp cmd_newvar                  ; erase variables
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; 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/src/parse.s	Sat May 18 00:41:46 2024 -0600
+++ b/src/parse.s	Mon Jun 24 23:44:39 2024 -0600
@@ -3,7 +3,13 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; This is the overall parsing package. This is responsible for converting program text into the internal byte code and
 ; reporting any syntax errors and anything else reasonably detectable at parse time without having overly complicated
-; code analysis. In almost all cases, the returned error will be a syntax error.
+; code analysis. In almost all cases, the returned error will be a syntax error. The internal byte code shares the same
+; token number allocations as the parser. Some allocated tokens cannot be identified by the lexer (parse_nexttok) but
+; are used at runtime and when "decompiling" to text.
+;
+; In the event of a parse error, everything up to the next end of statement is retained as is using a special token
+; that preserves the unparsable text and parsing resumes. Only the first error is referenced by the return error
+; pointer.
 ;
 ; This is a recursive descent parser.
 ;
@@ -12,14 +18,20 @@
 ; B             Nonzero to prevent generating any output (error check/length calculation only)
 ;
 ; Exit:
-; U             Points to the encoded line
+; X             Points to the encoded line
 ; D             Length of the encoded line
 ; CC.C          clear
 
 ; Error Exit:
-; B             Error code
-; U             Offset to error input
+; X             Points to the encoded line
+; D             Length of the encoded line
+; Y             Pointer to the first error location in the input
+; U             Error code
 ; CC.C          set
+;
+; This is the error handler. It is responsible for resetting the stack to bail out to the top level
+; parsing loop. It must also store the input pointer if this is the first error. Finally, it has to
+; output all the text up to either the end of the line *or* the next valid statement separator.
 parse_errorsn   ldb #err_sn
 parse_error     lds parse_stackptr              ; restore the original stack pointer so we can call from down stack
                 puls u                          ; get back original free pointer
@@ -82,6 +94,11 @@
                 leay 1,y                        ; move to next input character
 parse_curchar   lda ,y                          ; fetch input character
                 rts
+parse_nexttokc  bsr parse_nexttok               ; fetch next token
+parse_iseos     cmpb #token_eot                 ; end of text?
+                beq parse_iseos0                ; brif so
+                cmpb #token_stmtsep             ; is it a statement separator
+parse_iseos0    rts
 parse_nexttok   bsr parse_curchar               ; fetch current input
                 beq parse_nexttok1              ; brif end of input
 parse_nexttok0  cmpa #0x20                      ; space?
@@ -137,10 +154,6 @@
                 bhi parse_toupper0              ; brif not
                 suba #0x20                      ; adjust to upper case alpha
 parse_toupper0  rts                             ; Z only set here if input was zero entering from parse_nextcharu
-parse_iseos     cmpa #token_stmtsep             ; end of statement?
-                beq parse_iseos0                ; brif so
-                cmpa #token_eot                 ; end of text?
-parse_iseos0    rts
 parse_number    jmp parse_tokerr
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Parse a statement that consists of just the command token
@@ -154,49 +167,6 @@
                 stb parse_curtok
                 rts                             ; return, pass back the C result from parse_write
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; Parse an optional line number range which may be [lineno][-[lineno]]
-parse_range     jsr parse_write                 ; output the token
-                jsr parse_nexttok               ; fetch input token
-                ldx zero                        ; set default start and end line numbers - whole program
-                leau -1,x
-                pshs x,u
-                bsr parse_iseos                 ; are there arguments?
-                beq parse_range3                ; brif so
-                cmpa #token_int32               ; is it an integer (line number)?
-                bne parse_range0                ; brif not
-                ldd val0+val.int                ; is the upper 16 bits set?
-                beq parse_rangee                ; brif yes - we have an error
-                ldd val0+val.int+2              ; set the start line number
-                std ,s
-                jsr parse_nexttok               ; see what's after the line number
-parse_range0    cmpa #token_minus               ; do we have a range?
-                beq parse_range1                ; brif so
-                bsr parse_iseos                 ; end of statement?
-                bne parse_rangee                ; brif not - error
-                ldd ,s                          ; set end line to start line
-                std 2,s
-                bra parse_range3                ; go output things
-parse_range1    jsr parse_nexttok               ; skip the -
-                bsr parse_iseos                 ; end of statement?
-                beq parse_range3                ; brif so
-                cmpa #token_int32               ; is it an integer?
-                bne parse_rangee                ; brif not
-                ldx val0+val.int                ; upper 16 bits set?
-                bne parse_rangee                ; brif so - invalid number
-                ldx val0+val.int+2              ; get end line number
-                stx 2,s                         ; save end line number
-                cmpx ,s                         ; is end line lower than start line?
-                blo parse_rangee                ; brif so - error
-parse_range3    puls a                          ; write out the range
-                jsr parse_write
-                puls a
-                jsr parse_write
-                puls a
-                jsr parse_write
-                puls a
-                jmp parse_write
-parse_rangee    jmp parse_errorsn               ; go raise the parse error
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; This routine parses tokens using the table at parse_wordtab. The table is structured as follows:
 ;
 ; * two bytes which contain the length of the table less the two bytes for this length value
@@ -277,6 +247,63 @@
                 coma                            ; make sure C is set for no match
                 puls a,x,pc                     ; clean up stack and return
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Validate a line number. Must enter with the token type in B. Will return the line number in X. It will return a
+; syntax error if the line number is invalid or out of range. It will also consume a valid line number token.
+parse_linenum   cmpb #token_int32               ; is it an integer?
+                beq parse_linenum1              ; brif so
+parse_linenum0  ldb #err_sn                     ; flag syntax error
+                coma                            ; flag error
+                rts
+parse_linenum1  ldx val0+val.int                ; get high word of integer
+                bne parse_linenum0              ; brif not a valid line number
+                ldx val0+val.int+2              ; get actual line number
+                pshs x                          ; save it
+                jsr parse_nexttok               ; consume line number
+                puls x,pc                       ; get back line number and return it
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Parse a line number range which is one of the following forms:
+; <linenum1>
+; <linenum1>-
+; <linenum1>-<linenum2>
+; -<linenum2>
+; The result will store two line numbers. If no - token appears, then both line numbers will be the same. Otherwise,
+; if <linenum1> is omitted, it will be assumed to be 0. If <linenum2> is omitted, it will be assumed to be 65535. Those
+; are the minimum and maximum line numbers.
+;
+; Parsing works by first looking for an integer token that is in range. If it finds one, it looks for an optional -
+; followed by an optional integer token that is in range. If the first token is not an integer, it must be a - which may
+; be optionally followed by another integer in range.
+;
+; It is technically valid to have a single - with no line numbers.
+;
+; Enter with the current token in B.
+;
+; The resulting line numbers will be returned in parse_buff
+parse_linerange ldx zero                        ; default start line number
+                leau -1,x                       ; default end line number
+                pshs x,u                        ; save the return range
+                cmpb #token_minus               ; range with no start?
+                beq parse_linerang1             ; brif so
+                bsr parse_linenum               ; verify line number, return in X
+                bcs parse_linerang4             ; bail out on error
+                stx ,s                          ; save new start line number
+                jsr parse_nexttokc              ; fetch next token, set Z if end of statement
+                bne parse_linerang0             ; brif not end of line
+                ldx ,s                          ; get end line to use as start line
+                bra parse_linerang2             ; go set range end and return
+parse_linerang0 cmpb #token_minus               ; do we have a range character?
+                bne parse_linerang3             ; brif not - we have an error
+parse_linerang1 jsr parse_nexttokc              ; parse what comes after the range mark     
+                beq parse_linerang2             ; brif end of statement - use the default range end
+                bsr parse_linenum               ; make sure it's a valid line number
+                bcs parse_linerang4             ; bail out on error
+parse_linerang2 stx 2,s                         ; set range end
+                clra                            ; make sure C is clear
+                puls x,u,pc                     ; fetch return values and return
+parse_linerang3 ldb #err_sn                     ; flag a syntax error
+                coma                            ; make sure C is set
+parse_linerang4 puls x,u,pc                     ; clean up stack and return error condition
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; This table defines the various handler routines for the various bytecode tokens. Each token is defined as follows:
 ;               parse_tokdefT <sym>,<parse>,<list>,<exec>
 ; where:
@@ -369,11 +396,15 @@
                 *pragmapop nolist
                 endm
                 *pragmapop list
+                ; the tokens defined in this section all have special parsing or meaning
                 parse_tokendefp error           ; Used to mark errors; should always be first so it's token #0 
                 parse_tokendefp eot             ; End of input marker or special handling in word tables
                 parse_tokendefp int32           ; 32 bit integer (has special parsing)
                 parse_tokendefp float           ; floating point value (has special parsing)
                 parse_tokendefp ident           ; identifier (has special parsing)
+                parse_tokendefp linenum         ; a 16 bit unsigned integer treated as a line number
+                parse_tokendefp linerange       ; a pair of 16 bit unsigned integers treated as line numbers
+                ; everything below here references keywords or particle characters
                 parse_tokendefp stmtsep         ; statement separator
                 parse_tokendefp times           ; times (multiplication) operator (*)
                 parse_tokendefp plus            ; addition operator
--- a/src/progctrl.s	Sat May 18 00:41:46 2024 -0600
+++ b/src/progctrl.s	Mon Jun 24 23:44:39 2024 -0600
@@ -30,14 +30,16 @@
 ; cmd_newvars: clears variables and resets the stack and other misc state
 ; cmd_newstack: just reset the stack and other misc state
 cmd_new         bne cmd_new0                    ; brif there was an argument - don't wipe things out on syntax error
-cmd_newraw      ldx progtext                    ; point to start of program
-                clr -1,x                        ; make sure there's a NUL before the start of the program
-                clr ,x+                         ; put a NULL pointer at the start of the program
-                clr ,x+
+cmd_newraw      ldx prog_linetab                ; get start of program line table
+                stx prog_linetabp               ; clear out all line entries
+                leax linetabent_size*(linetab_stride+1),x ; make room for default program line entries and one extra
+                stx [prog_linetabp]             ; set the pointer for the dummy ending entry
+                stx prog_text                   ; set end of program line table
                 stx vartab                      ; set start of variables after that
-cmd_newinptr    ldx progtext                    ;* set input pointer to the NUL before the program; this will cause the
-                leax -1,x                       ;* the interpreter to drop to immediate mode no matter what it was
-                stx inputptr                    ;* executing before this call if called from the main loop
+cmd_newinptr    ldx zero                        ; blank out current line pointer - we'll fall back to immediate mode
+                stx curline
+                ldx #zero                       ; point to a zero input byte - will read as end of line
+                stx inputptr
 cmd_newvars     ldx memsize                     ; get top of memory
                 stx stringtab                   ; clear out string space
                 ldx vartab                      ; get start of variables
--- a/src/vars.s	Sat May 18 00:41:46 2024 -0600
+++ b/src/vars.s	Mon Jun 24 23:44:39 2024 -0600
@@ -11,8 +11,10 @@
 freetop         rmb 2                           ; top of free memory (bottom of string space)
 stringtab       rmb 2                           ; bottom of used string space
 cstackptr       rmb 2                           ; bottom of the "stack frame" stack (the actual stack is below here)
-progtext        rmb 2                           ; pointer to start of program text
-vartab          rmb 2                           ; pointer to start of integer scalars
+prog_linetab    rmb 2                           ; pointer to start of the program line table
+prog_linetabp   rmb 2                           ; pointer to the first free program line table entry
+prog_text       rmb 2                           ; start of the program text data (immediately after the line table)
+vartab          rmb 2                           ; pointer to start of integer scalars (the runtime heap)
 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