.segment "CODE" LOADSAVE_LFN = $FF ; ============================================================================ ; COPY STRING EXPRESSION TO FILE_NAME_BUFFER ; ; Entry: ; TXTPTR points at string expression ; ; Exit: ; FILE_NAME_BUFFER contains NUL-terminated filename ; ; Clobbers: ; A,X,Y,INDEX,STRNG2 ; ============================================================================ PARSE_LOADSAVE_FILENAME: jsr FRMEVL jsr CHKSTR jsr FREFAC ; FREFAC: ; A = length ; INDEX = string address sta STRNG2 ldy #$00 @copy: cpy STRNG2 beq @done lda (INDEX),y sta FILE_NAME_BUFFER,y iny bne @copy @done: lda #$00 sta FILE_NAME_BUFFER,y rts ; ============================================================================ ; SET UP THE COMMON FILE ARGUMENTS ; ============================================================================ LOADSAVE_SET_FILE: lda #LOADSAVE_LFN sta IO_FILE_DESCRIPTOR lda #FILE_NAME_BUFFER sta IO_FILE_DATA+1 rts ; ============================================================================ ; WAIT FOR FILE COMMAND COMPLETION ; ; Jumps to IQERR on host-side failure. ; ============================================================================ LOADSAVE_WAIT: lda IO_FILE_STATUS and #FILE_STATUS_READY beq LOADSAVE_WAIT lda IO_FILE_STATUS and #FILE_STATUS_ERROR beq @ok jmp IQERR @ok: rts ; ============================================================================ ; OPEN FOR READING ; ============================================================================ LOADSAVE_OPEN_R: jsr LOADSAVE_SET_FILE lda #IO_COMMAND_OPEN_R sta IO_FILE_COMMAND jmp LOADSAVE_WAIT ; ============================================================================ ; OPEN FOR WRITING / TRUNCATE ; ============================================================================ LOADSAVE_OPEN_W: jsr LOADSAVE_SET_FILE lda #IO_COMMAND_OPEN_W sta IO_FILE_COMMAND jmp LOADSAVE_WAIT ; ============================================================================ ; CLOSE INTERNAL LOAD/SAVE FILE ; ============================================================================ LOADSAVE_CLOSE: lda #LOADSAVE_LFN sta IO_FILE_DESCRIPTOR lda #IO_COMMAND_CLOSE sta IO_FILE_COMMAND jsr LOADSAVE_WAIT jsr CLRCH rts ; ============================================================================ ; SAVE "filename" ; ; Saves plain ASCII BASIC source, equivalent to LIST output. ; ============================================================================ SAVE: jsr PARSE_LOADSAVE_FILENAME jsr LOADSAVE_OPEN_W ; Route OUTDO/CHROUT to our internal file. ldx #LOADSAVE_LFN jsr CHKOUT stx CURDVC ; Start with first BASIC line. lda TXTTAB sta LOWTRX lda TXTTAB+1 sta LOWTRX+1 SAVE_NEXT_LINE: ; The high byte of the forward link is zero at end of program. ldy #$01 lda (LOWTRX),y beq SAVE_DONE ; ------------------------------------------------------------ ; Print line number. ; ; record: ; +0/+1 = next line ; +2/+3 = line number ; +4... = tokenized body ; ------------------------------------------------------------ iny ; Y=2 lda (LOWTRX),y tax iny ; Y=3 lda (LOWTRX),y jsr LINPRT lda #' ' jsr OUTDO ; DATAFLG follows LIST's quoting/token rules. lda #$01 sta DATAFLG ldy #$04 SAVE_LINE_CHAR: lda (LOWTRX),y beq SAVE_LINE_DONE ; Test whether it's plain ASCII/token BEFORE doing CMPs bpl SAVE_PLAIN_CHAR cmp #$FF beq SAVE_PLAIN_CHAR bit DATAFLG bmi SAVE_PLAIN_CHAR ; Token sec sbc #$7F tax sty FORPNT ldy #$FF SAVE_FIND_TOKEN: dex beq SAVE_TOKEN_FOUND SAVE_SKIP_TOKEN: iny lda TOKEN_NAME_TABLE,y bpl SAVE_SKIP_TOKEN bmi SAVE_FIND_TOKEN SAVE_TOKEN_FOUND: iny lda TOKEN_NAME_TABLE,y bmi SAVE_TOKEN_LAST jsr OUTDO bne SAVE_TOKEN_FOUND SAVE_TOKEN_LAST: and #$7F jsr OUTDO ldy FORPNT iny jmp SAVE_LINE_CHAR SAVE_PLAIN_CHAR: and #$7F jsr OUTDO ; Quote state must be updated AFTER output/test. cmp #$22 bne SAVE_PLAIN_DONE lda DATAFLG eor #$FF sta DATAFLG SAVE_PLAIN_DONE: iny jmp SAVE_LINE_CHAR SAVE_LINE_DONE: ; Every saved source line ends in LF. jsr LFDO ; Follow next-line pointer. ldy #$00 lda (LOWTRX),y tax iny lda (LOWTRX),y stx LOWTRX sta LOWTRX+1 jmp SAVE_NEXT_LINE SAVE_DONE: jsr LOADSAVE_CLOSE rts ; ============================================================================ ; LOAD "filename" ; ; Replaces current program. ; Reads ASCII source one line at a time, tokenizes through the normal ; BASIC parser, and inserts using NUMBERED_LINE. ; ; IMPORTANT: ; NUMBERED_LINE eventually reaches FIX_LINKS -> SETPTRS -> STKINI, which ; resets the hardware stack. Therefore LOAD cannot expect NUMBERED_LINE to ; return with RTS. FIX_LINKS jumps back to LOAD_NEXT_LINE while LOAD_ACTIVE ; is set. ; ============================================================================ LOAD: ; Copy the filename before SCRTCH destroys temporary strings. jsr PARSE_LOADSAVE_FILENAME jsr LOADSAVE_OPEN_R ; Delete current program. jsr SCRTCH ; Route CHRIN to the file. ldx #LOADSAVE_LFN jsr CHKIN stx CURDVC ; Tell FIX_LINKS that program insertion belongs to LOAD. lda #$01 sta LOAD_ACTIVE ; ---------------------------------------------------------------------------- ; Entry point used both initially and by FIX_LINKS after inserting each line. ; Must be a globally visible label if FIX_LINKS lives in another source file. ; ---------------------------------------------------------------------------- LOAD_NEXT_LINE: ldx #$00 ; ---------------------------------------------------------------------------- ; Read one physical source line into INPUTBUFFER. ; ; "10 PRINT 1\n" -> normal line ; "10 PRINT 1" -> final line still accepted ; EOF with X=0 -> done loading ; ---------------------------------------------------------------------------- @read_char: jsr CHRIN ; If host says EOF, A is not considered valid data. pha lda IO_FILE_STATUS and #FILE_STATUS_EOF bne @eof pla ; LF terminates a source line. cmp #LF beq @line_complete ; Accept CRLF files too. cmp #$0D beq @read_char ; Same practical line limit as interactive INLIN. cpx #$47 bcs @line_too_long sta INPUTBUFFER,x inx jmp @read_char @eof: pla ; EOF with no accumulated characters means real end of file. cpx #$00 beq @load_done ; Otherwise process the unterminated final line. @line_complete: lda #$00 sta INPUTBUFFER,x ; Point CHRGET at INPUTBUFFER-1, exactly like INLIN returns. lda #<(INPUTBUFFER-1) sta TXTPTR lda #>(INPUTBUFFER-1) sta TXTPTR+1 jsr CHRGET ; Ignore empty physical lines. beq LOAD_NEXT_LINE ; A source file must consist of numbered program lines. ; ; CHRGET leaves carry clear for a leading decimal digit, ; exactly like the normal RESTART path. bcc @numbered jmp @syntax_error @numbered: ; NUMBERED_LINE performs: ; ; LINGET ; PARSE_INPUT_LINE ; FNDLIN ; PUT_NEW_LINE ; FIX_LINKS ; ; DO NOT JSR here. ; ; FIX_LINKS eventually calls SETPTRS/STKINI, which resets the ; hardware stack. FIX_LINKS will detect LOAD_ACTIVE and jump back ; to LOAD_NEXT_LINE. jmp NUMBERED_LINE @line_too_long: @syntax_error: lda #$00 sta LOAD_ACTIVE jsr CLRCH jsr LOADSAVE_CLOSE jmp SYNERR @load_done: lda #$00 sta LOAD_ACTIVE jsr CLRCH jsr LOADSAVE_CLOSE ; Do NOT JSR SETPTRS here. ; ; Every successfully inserted line already passed through ; FIX_LINKS -> SETPTRS -> LE33D. ; ; Also do not RTS: SETPTRS/STKINI during loading may have destroyed ; the original LOAD command's return frame. jmp RESTART