Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 56 additions & 37 deletions editor.asm
Original file line number Diff line number Diff line change
Expand Up @@ -555,10 +555,15 @@ main_loop:
jsr do_settings_popover
jmp main_loop
@do_load:
jsr load_dirty_check ; unsaved changes? C=1 → cancelled
bcs @load_cancelled
jsr do_load_file
jmp main_loop
@do_load_source:
jsr load_dirty_check
bcs @load_cancelled
jsr do_load_source_file
@load_cancelled:
jmp main_loop
@do_save:
jsr do_save_file
Expand Down Expand Up @@ -660,26 +665,10 @@ move_begin:
do_new_file:
lda IS_DIRTY
beq @clear
; Prompt — reverse-video alert bar so it can't be mistaken for idle status.
lda #<new_dirty_text
ldx #>new_dirty_text
jsr modal_draw_text
@wait:
jsr GETIN
beq @wait
cmp #PET_STOP
beq @cancel
cmp #'Y'
beq @save
cmp #'y'
beq @save
cmp #'N'
beq @clear
cmp #'n'
beq @clear
jmp @wait
@save:
jsr do_save_file ; if save is cancelled, still proceed to clear
jsr dirty_prompt ; C=1 → cancelled (STOP, or the save didn't happen)
bcs @done
@clear:
; Reset gap buffer to empty
lda #<work_buf
Expand Down Expand Up @@ -710,8 +699,8 @@ do_new_file:
jsr render_status
jsr render_viewport
jsr draw_cursor
@cancel:
jsr modal_exit ; STOP: restore idle status bar before returning
jsr modal_exit ; restore idle status bar before returning
@done:
rts

new_dirty_text:
Expand All @@ -721,17 +710,52 @@ new_dirty_text:

; ============================================================================
; quit_dirty_prompt — "SAVE BEFORE QUIT (Y/N)?" on status bar.
;
; If Y: calls do_save_file, then returns C=0 (proceed to quit).
; If N: returns C=0 (quit without saving).
; If STOP: returns C=1 (cancel quit, back to editor).
; Clobbers: A, X, Y.
; Returns C=0 to proceed with the quit, C=1 to cancel (see dirty_prompt).
; ============================================================================

quit_dirty_prompt:
lda #<quit_dirty_text
ldx #>quit_dirty_text
jsr modal_draw_text
jmp dirty_prompt

quit_dirty_text:
; "SAVE BEFORE QUIT (Y/N)?" in screen codes
.byte $13,$01,$16,$05,$20,$02,$05,$06,$0F,$12,$05,$20,$11,$15,$09,$14
.byte $20,$28,$19,$2F,$0E,$29,$3F, 0

; ============================================================================
; load_dirty_check — F3/F6 guard: ask before a load discards unsaved changes.
; Returns C=0 to go ahead with the load, C=1 if the user cancelled.
; ============================================================================

load_dirty_check:
lda IS_DIRTY
beq @clean
lda #<load_dirty_text
ldx #>load_dirty_text
jmp dirty_prompt
@clean:
clc
rts

load_dirty_text:
; "SAVE BEFORE LOAD (Y/N)?" in screen codes
.byte $13,$01,$16,$05,$20,$02,$05,$06,$0F,$12,$05,$20,$0C,$0F,$01,$04
.byte $20,$28,$19,$2F,$0E,$29,$3F, 0

; ============================================================================
; dirty_prompt — "SAVE BEFORE xxx (Y/N)?" modal shared by new, load and quit.
;
; Entry: A/X = lo/hi of the prompt text (screen codes, modal_draw_text format).
; Y: calls do_save_file. Returns C=0 if the write succeeded; C=1 if the
; save was cancelled or failed (do_save_file leaves IS_DIRTY set then).
; N: returns C=0 — proceed, discarding the changes.
; STOP: returns C=1 — cancel; the idle status bar is restored via modal_exit.
; Clobbers: A, X, Y.
; ============================================================================

dirty_prompt:
jsr modal_draw_text ; reverse-video alert bar
@wait:
jsr GETIN
beq @wait
Expand All @@ -742,27 +766,22 @@ quit_dirty_prompt:
cmp #'y'
beq @save
cmp #'N'
beq @nosave
beq @proceed
cmp #'n'
beq @nosave
beq @proceed
jmp @wait
@save:
jsr do_save_file ; save — clears IS_DIRTY on success
clc
rts
@nosave:
jsr do_save_file ; clears IS_DIRTY only on a successful write
lda IS_DIRTY
bne @cancel ; save cancelled or failed: keep editing
@proceed:
clc
rts
@cancel:
jsr modal_exit ; restore idle status bar (caller returns to editor)
sec
rts

quit_dirty_text:
; "SAVE BEFORE QUIT (Y/N)?" in screen codes
.byte $13,$01,$16,$05,$20,$02,$05,$06,$0F,$12,$05,$20,$11,$15,$09,$14
.byte $20,$28,$19,$2F,$0E,$29,$3F, 0

; ============================================================================
; init_settings — write compiled-in defaults into the SETTING_* BSS bytes.
; Called on both cold and warm start before load_settings overlays from disk.
Expand Down
7 changes: 5 additions & 2 deletions loadsave.asm
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,12 @@ load_body:
@name_done:

; ---- OPEN ----
; The Kernal registers the logical file before the serial OPEN can fail
; (DEVICE NOT PRESENT etc.), so a failed OPEN must still CLOSE IO_LA or
; every later load/save fails with FILE OPEN for the rest of the session.
jsr OPEN
bcc :+
jmp @io_err
jmp @close_err
:
; Check drive status after OPEN
jsr READST
Expand Down Expand Up @@ -459,7 +462,7 @@ do_save_file:
; ---- OPEN ----
jsr OPEN
bcc :+
jmp @io_err
jmp @close_err ; LFN is registered even when OPEN fails
:
jsr READST
beq :+
Expand Down
27 changes: 20 additions & 7 deletions modasm.asm
Original file line number Diff line number Diff line change
Expand Up @@ -1934,8 +1934,11 @@ emit_instruction:
; FIX (branch offset): compute signed relative offset as a proper 16-bit
; subtraction: offset = target - (PC + 2).
; PC still points to the branch opcode (not yet advanced).
; The hi byte of the result must be $00 (forward) or $FF (backward);
; anything else means the target is more than 128 bytes away.
; The hi byte of the result must be $00 (forward) or $FF (backward), and
; the lo byte's sign must agree with it: $00xx needs bit 7 of xx clear
; (offset 0..+127), $FFxx needs it set (offset -128..-1). Checking the
; hi byte alone accepted +128..+255 and -129..-256 and emitted an offset
; that branched to the wrong place.
;
; Compute PC+2 into TMP2
lda ASM_PC_LO
Expand All @@ -1951,13 +1954,18 @@ emit_instruction:
sbc TMP2
sta TMP ; lo byte = branch offset to emit
lda ASM_VAL_HI
sbc TMP2+1 ; hi byte: $00=forward in range, $FF=backward in range
beq @rel_ok
sbc TMP2+1 ; hi byte: $00=forward, $FF=backward
beq @rel_fwd
cmp #$FF
bne @rel_range_err
lda TMP ; backward: lo must be $80..$FF
bpl @rel_range_err ; $FF00..$FF7F = -256..-129
bmi @rel_ok
@rel_fwd:
lda TMP ; forward: lo must be $00..$7F
bmi @rel_range_err ; $0080..$00FF = +128..+255
@rel_ok:
lda TMP
jsr emit_byte
jsr emit_byte ; A = offset lo byte
bcs @err
jmp @done

Expand Down Expand Up @@ -2387,7 +2395,7 @@ open_output:
jsr SETNAM

jsr OPEN
bcs @err
bcs @err_close ; LFN is registered even when OPEN fails

ldx #ASM_OUT_LA_VAL
jsr CHKOUT
Expand All @@ -2407,8 +2415,13 @@ open_output:
rts

@err_close:
; The Kernal registers the logical file before the serial OPEN can fail
; (DEVICE NOT PRESENT etc.): without this CLOSE every later assembly in
; the session fails with FILE OPEN on LFN 4. The caller expects an
; error to have been recorded, so record I/O ERROR here.
lda #ASM_OUT_LA_VAL
jsr CLOSE
jsr set_err_io
@err:
sec
rts
Expand Down
11 changes: 11 additions & 0 deletions modren.asm
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,17 @@ pass2:
jsr setup_staging ; linearise source, set SRC_PTR
bcs @error

; The source is now linear in [staging_start..BUF_END): the editor's gap
; no longer exists in it. advance_src (used by parse_line_number and
; skip_to_next_cr) still warps SRC_PTR from REN_GAP to REN_GAP_END when
; it lands on the old GAP_START — with a pre-gap larger than the gap,
; the staging area straddles that address and the warp silently dropped
; gap_size bytes of program mid-line. Make the warp a no-op from here on.
lda REN_GAP_END_LO
sta REN_GAP_LO
lda REN_GAP_END_HI
sta REN_GAP_HI

; DST_PTR = work_buf
lda REN_BUF_LO
sta DST_PTR
Expand Down
38 changes: 25 additions & 13 deletions modules.asm
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ MPOP_ITEM_ROW0 = (MPOP_TOP + 3) ; screen row of first item (title at TOP+1,
MOD_LOAD_SAVE_LO = $0228
MOD_LOAD_SAVE_HI = $0229
; Index of the most recently run module — lets mpop_show_error honor
; MODASM's rich-error flag ($C007) only when MODASM actually ran.
; MODASM's rich-error flag (ASM_ERR) only when MODASM actually ran.
MOD_LAST_IDX = $022A
MOD_IDX_MODASM = 0

Expand Down Expand Up @@ -837,6 +837,10 @@ run_sel_loaded:
sta CURSOR_ROW
sta CURSOR_COL
jsr ensure_cursor_visible ; scroll/compute so display agrees with the gap
; The module rewrote the buffer (renumber, tokenize...): the text no
; longer matches what is on disk, so the new/load/quit prompts must fire.
lda #$FF
sta IS_DIRTY
rts

@check_reposition:
Expand All @@ -856,6 +860,10 @@ run_sel_loaded:
sta GAP_END+1
; TOP_LINE intentionally not reset — ensure_cursor_visible (called by
; do_search_replace after we return) will scroll as needed.
; MODSFR reports $04 after a replace as well as after a plain find, and
; does not say which; a replace edits the buffer, so mark it dirty.
lda #$FF
sta IS_DIRTY
rts

@check_err:
Expand All @@ -880,15 +888,15 @@ run_sel_load_err:
; ============================================================================

mpop_show_error:
; If MODASM set ASM_ERR ($C007=$FF), show rich "ERR NNNN: MSGTEXT" display.
; If MODASM set ASM_ERR (=$FF), show rich "ERR NNNN: MSGTEXT" display.
; Gate on "MODASM was the module that just ran": five other modules
; LOAD at $C000, so $C007 is one of THEIR code bytes — a coincidental
; $FF there (or a stale flag from an earlier MODASM failure) used to
; display garbage as a rich assembler error for unrelated failures.
; LOAD at MOD_LO_BASE, so ASM_ERR is one of THEIR code bytes — a
; coincidental $FF there (or a stale flag from an earlier MODASM failure)
; used to display garbage as a rich assembler error for unrelated failures.
lda MOD_LAST_IDX
cmp #MOD_IDX_MODASM
bne @generic
lda $C007 ; ASM_ERR flag written by MODASM's set_err_common
lda ASM_ERR ; flag written by MODASM's set_err_common
cmp #$FF
beq asm_show_err ; MODASM error → rich display, includes wait
@generic:
Expand Down Expand Up @@ -916,8 +924,8 @@ mpop_show_error:
; ============================================================================
; asm_show_err — rich MODASM error display.
;
; Reads ASM_ERR_LINE_LO/HI ($C008/$C009) and ASM_ERR_MSG ($C00A, screen codes,
; zero-terminated) written by MODASM's set_err_common, and formats:
; Reads ASM_ERR_LINE_LO/HI and ASM_ERR_MSG (screen codes, zero-terminated)
; written by MODASM's set_err_common, and formats:
;
; ERR NNNN: MSGTEXT
;
Expand All @@ -932,9 +940,13 @@ mpop_show_error:
asm_err_prefix_txt:
.byte $05,$12,$12,$20 ; "ERR "

ASM_ERR_LINE_LO = $C008
ASM_ERR_LINE_HI = $C009
ASM_ERR_MSG = $C00A
; MODASM's error block sits at the start of its ASM_STATE block, which is
; MOD_LO_BASE (layout.inc): $C000 on the C64, $9000 on the C128. Offsets
; must match ASM_ERR / ASM_ERR_LINE_* / ASM_ERR_MSG in modasm.asm.
ASM_ERR = MOD_LO_BASE + $07
ASM_ERR_LINE_LO = MOD_LO_BASE + $08
ASM_ERR_LINE_HI = MOD_LO_BASE + $09
ASM_ERR_MSG = MOD_LO_BASE + $0A

asm_show_err:
; --- 1. Write "ERR " prefix ---
Expand All @@ -953,9 +965,9 @@ asm_show_err:
; Load 16-bit line number into AN_CUR (reuse existing BSS scratch).
; an_emit_number writes via do_insert; we can't use it here.
; Instead: same repeated-subtraction algorithm, writing to STATUS_ROW,Y.
lda $C008 ; ASM_ERR_LINE_LO
lda ASM_ERR_LINE_LO
sta AN_CUR
lda $C009 ; ASM_ERR_LINE_HI
lda ASM_ERR_LINE_HI
sta AN_CUR+1

; Reuse an_div_lo/an_div_hi table from editor.asm (same link unit).
Expand Down
20 changes: 20 additions & 0 deletions tests/test_include.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,26 @@ def chain(n):
equivalence("mixed directives across files", src, files)
print("ok: data + code directives across multiple files")

# 14. Relative branch range. A branch whose offset is +128..+255 or
# -129..-256 has a hi byte of $00/$FF like an in-range one, so the
# check must also look at the sign of the lo byte.
def nops(n):
return " NOP\n" * n

# forward: BEQ over 120 NOPs = +120, fine; over 130 = +130, out of range
src = " *= $C000\n BEQ FAR\n" + nops(120) + "FAR: RTS\n"
m = assemble(src)
expect_output(m, bytes.fromhex("00c0f078") + b"\xea" * 120 + b"\x60", "BEQ +120")
m = assemble(" *= $C000\n BEQ FAR\n" + nops(130) + "FAR: RTS\n")
expect_error(m, "BRANCH RANGE", "BEQ +130", line=2)
# backward: RTS; 125 NOPs; BNE BACK = -128, fine; 130 NOPs = -133, out of range
src = " *= $C000\nBACK: RTS\n" + nops(125) + " BNE BACK\n"
m = assemble(src)
expect_output(m, bytes.fromhex("00c060") + b"\xea" * 125 + b"\xd0\x80", "BNE -128")
m = assemble(" *= $C000\nBACK: RTS\n" + nops(130) + " BNE BACK\n")
expect_error(m, "BRANCH RANGE", "BNE -133", line=133)
print("ok: relative branch range (lo-byte sign checked)")

print("\nall include tests passed")


Expand Down
Loading