Generated programs kept their state in system zero page $C0-$FE, which on the C128 is KERNAL and screen-editor state while CHROUT runs: the print runtime's pointer and length sat on TCOLOR/RVS/QTSW ($F2-$F4), and the KERNAL IRQ clears its index at $C0 (CAS1). HELLO.PRG printed "HELLO FROM NOVA1ELLO FROM NOVA128" in reverse video, whichmain8f8cf00had hidden by masking bit 7 in the check. The ENTRY stub now masks IRQs, installs CR $0E (D129), points P0 at page $1A, sets the frame base, calls CODE and START, then restores P0, CR and flags. prgprint switches P0 back to $0000 only around each CHROUT, inlined to stay position independent. The driver test no longer masks reverse video. The larger runtime exposed an 8-bit overflow in the driver's CODE base (ENTRY + START + PRINT now exceeds 255 bytes, which surfaced as DISK ERROR); the driver now passes the 16-bit address it already computes. Full regression: 25 of 26 suites pass; parser fails only on baseline hashes that predate221e0c5.
| @@ -65,8 +65,9 @@ The on-machine driver (`src/compiler/main.s`, E07-S05) turns those pieces into | |||||
| `sz_plan_code` so absolute targets match the loaded image, and writes STUB, | `sz_plan_code` so absolute targets match the loaded image, and writes STUB, | ||||
| ENTRY, START, PRINT and CODE through `output.s`. The image is `$1C01` stub, | ENTRY, START, PRINT and CODE through `output.s`. The image is `$1C01` stub, | ||||
| `$1C0D` entry, the runtime startup, the position-independent print runtime, | `$1C0D` entry, the runtime startup, the position-independent print runtime, | ||||
| then CODE; the entry sets the D129 frame base, calls the body, runs the | |||||
| startup mailbox and restores BASIC's frame pointer. The MAP records all five | |||||
| then CODE; the entry masks IRQs, installs CR `$0E` and the program's private | |||||
| zero page (P0 at page `$1A`), sets the D129 frame base, calls the body, runs | |||||
| the startup mailbox, and restores P0, CR and flags (D157). The MAP records all five | |||||
| sections. The driver prints `SOURCE`, `OUTPUT` and the diagnostic count, keeps | sections. The driver prints `SOURCE`, `OUTPUT` and the diagnostic count, keeps | ||||
| the previous output byte-identical when a compile fails or the output already | the previous output byte-identical when a compile fails or the output already | ||||
| exists, and reports emitter gaps rather than emitting a pointer outside the | exists, and reports emitter gaps rather than emitting a pointer outside the | ||||
| @@ -90,7 +90,8 @@ documented default. `Proposed` means a later change still needs approval. | |||||
| | D154 | E07-S05 adds the on-machine driver `src/compiler/main.s`, built by `scripts/build-driver.ps1` into `build/n128c.prg` with the whole compiler, `output.s` and `src/runtime/startup.s`. The driver prompts `SOURCE:` through KERNAL `CHRIN`, accepts up to 15 characters, folds PETSCII lowercase to uppercase, loads with `src_load`, initialises `diag_init`, sets `em_on`, and plans the code base as `PRG_ENTRY_ADDR + gen_startup_size` with `sz_plan_code` before `parse_file_base`, so absolute targets match the PRG. It then writes STUB, START and CODE with `out_begin`/`out_startup`/`out_section`/`out_bytes`/`out_finish` and prints `SOURCE name`, `OUTPUT name`, `n ERRORS`. The three `em_pr_*` cells are explicitly zeroed because the generated image does not carry `src/runtime/print.s` yet: a `print` source fails with E227 instead of emitting a call into the compiler's own copy of the routine. Refusals print `OUTPUT EXISTS`, `NAME TOO LONG`, `DISK ERROR`, `LOAD FAILED`, `COMPILE FAILED, n ERRORS` or `NO CODE GENERATED`, and `out_abort` scratches only files the run created. `scripts/launch-compiler.ps1` gains `-Keys`/`-KeyDelay` so a test can type the source name, and `scripts/verify-driver-test.ps1` drives the summary, success, syntax-failure, output-collision and generated-program runs on a fresh D81. | The driver is wiring around contracts that already existed, so it adds no new compiler phase: the only new decisions are the fixed image layout (CODE follows the startup at `$1C57` for the current startup size), the name limit, and keeping print honest rather than emitting a pointer into the compiler. A clean code-only source now produces a runnable container (`COUNTER.PRG`, 263 bytes, CODE at `$1C57`, 95-byte MAP) whose generated program reports `MB_ST_PASS` in a second x128 session; a syntax error writes `BAD.ERR` (`E024`, then `E227`) and leaves an earlier `COUNTER.PRG` byte-identical, as does a refused second compile. Text `print` stays a known gap until the generated-program print runtime is emitted into the container. | | | D154 | E07-S05 adds the on-machine driver `src/compiler/main.s`, built by `scripts/build-driver.ps1` into `build/n128c.prg` with the whole compiler, `output.s` and `src/runtime/startup.s`. The driver prompts `SOURCE:` through KERNAL `CHRIN`, accepts up to 15 characters, folds PETSCII lowercase to uppercase, loads with `src_load`, initialises `diag_init`, sets `em_on`, and plans the code base as `PRG_ENTRY_ADDR + gen_startup_size` with `sz_plan_code` before `parse_file_base`, so absolute targets match the PRG. It then writes STUB, START and CODE with `out_begin`/`out_startup`/`out_section`/`out_bytes`/`out_finish` and prints `SOURCE name`, `OUTPUT name`, `n ERRORS`. The three `em_pr_*` cells are explicitly zeroed because the generated image does not carry `src/runtime/print.s` yet: a `print` source fails with E227 instead of emitting a call into the compiler's own copy of the routine. Refusals print `OUTPUT EXISTS`, `NAME TOO LONG`, `DISK ERROR`, `LOAD FAILED`, `COMPILE FAILED, n ERRORS` or `NO CODE GENERATED`, and `out_abort` scratches only files the run created. `scripts/launch-compiler.ps1` gains `-Keys`/`-KeyDelay` so a test can type the source name, and `scripts/verify-driver-test.ps1` drives the summary, success, syntax-failure, output-collision and generated-program runs on a fresh D81. | The driver is wiring around contracts that already existed, so it adds no new compiler phase: the only new decisions are the fixed image layout (CODE follows the startup at `$1C57` for the current startup size), the name limit, and keeping print honest rather than emitting a pointer into the compiler. A clean code-only source now produces a runnable container (`COUNTER.PRG`, 263 bytes, CODE at `$1C57`, 95-byte MAP) whose generated program reports `MB_ST_PASS` in a second x128 session; a syntax error writes `BAD.ERR` (`E024`, then `E227`) and leaves an earlier `COUNTER.PRG` byte-identical, as does a refused second compile. Text `print` stays a known gap until the generated-program print runtime is emitted into the container. | | ||||
| | D155 | Generated programs carry their own print runtime. `src/runtime/prgprint.s` is position independent (relative branches, no self-addressed stores, only the fixed `CHROUT` address) and is copied verbatim into a `PRINT` section between `START` and `CODE`. It works in four zero-page bytes (`$C0`, `$F2-$F4`) that a generated `ENTRY` stub saves before calling the body and restores before returning to BASIC; the stub also sets the D129 frame base to `$BF80`. `em_print` now emits `JSR` to the value in `em_pr_text`/`em_pr_byte`/`em_pr_cr` instead of a compiler trampoline, and the driver sets those cells to `PRG_ENTRY_ADDR + gen_startup_size` plus each routine's offset before parsing. The code base is planned as entry plus startup plus runtime size. The one captured string literal is copied into a `DATA` section after CODE and the driver rewrites the emitted `LDA #<gen_text`/`LDX #>gen_text` pair to that copy, so the PRG never references compiler memory. | Closes the image gap E07-S05 hit: `print` compiles to a call inside the PRG, `COUNTER.MAP` gains a `PRINT` section, and `hello.n8` displays its string from a standalone PRG. Emitted sizes are unchanged (the `JSR` is still three bytes), so existing lengths and in-place recorder fixtures hold; the old trampolines remain for fixtures that point the cells at their own recorders. | | |||||
| | D155 | Generated programs carry their own print runtime. `src/runtime/prgprint.s` is position independent (relative branches, no self-addressed stores, only the fixed `CHROUT` address) and is copied verbatim into a `PRINT` section between `START` and `CODE`. It works in four zero-page bytes (`$C0`, `$F2-$F4`) that a generated `ENTRY` stub saves before calling the body and restores before returning to BASIC; the stub also sets the D129 frame base to `$BF80`. *(Zero-page placement superseded by D157: these bytes now live in the program's private P0 page and are no longer saved.)* `em_print` now emits `JSR` to the value in `em_pr_text`/`em_pr_byte`/`em_pr_cr` instead of a compiler trampoline, and the driver sets those cells to `PRG_ENTRY_ADDR + gen_startup_size` plus each routine's offset before parsing. The code base is planned as entry plus startup plus runtime size. The one captured string literal is copied into a `DATA` section after CODE and the driver rewrites the emitted `LDA #<gen_text`/`LDX #>gen_text` pair to that copy, so the PRG never references compiler memory. | Closes the image gap E07-S05 hit: `print` compiles to a call inside the PRG, `COUNTER.MAP` gains a `PRINT` section, and `hello.n8` displays its string from a standalone PRG. Emitted sizes are unchanged (the `JSR` is still three bytes), so existing lengths and in-place recorder fixtures hold; the old trampolines remain for fixtures that point the cells at their own recorders. | | |||||
| | D157 | Generated programs own a private zero page. The `ENTRY` stub does `PHP`/`SEI`, saves the entry CR and installs CR `$0E` (D129), and points P0 at bank 0 page `$1A` (`PRG_ZP_PAGE`, the compiler's own private page, idle once BASIC runs the PRG); it sets the frame base, calls CODE then START, then restores P0 to `$0000`, the CR and the flags. Every zero-page cell a generated program uses (frame ABI, `MB_SELF`, `EM_LP`/`EM_TMP`, `IF_RESULT`, for-end/step values, the print runtime's `$C0`/`$F2-$F4`) is therefore private. `prgprint.s` switches P0 back to `$0000` only around each `CHROUT` (inlined, because a JSR to a shared helper is not position independent) and re-masks IRQs before restoring P0. `ENTRY` is 49 bytes and no longer saves system bytes. The driver passes the 16-bit CODE address to pass 3 instead of an 8-bit offset sum. Supersedes the zero-page part of D155 and the "only free zero page" premise of D153. | D153/D155 placed generated-program state in system zero page `$C0-$FE`, which on the C128 is KERNAL and screen-editor state while CHROUT runs: `$F2-$F4` are TCOLOR/RVS/QTSW, `$D0-$F9` hold the editor's line pointers, cursor and flags, and the KERNAL IRQ clears `$C0` (CAS1). HELLO.PRG printed `HELLO FROM NOVA1ELLO FROM NOVA128` in reverse video. A private P0 page removes the whole class of overlap with one stub change and costs a few cycles per character; it mirrors the compiler's own P0 + KERNAL-wrapper pattern (D117). Risks: an NMI other than RESTORE that uses zero page would see the private page while P0 is relocated (the ROM NMI entry itself uses none); IRQs stay masked for the whole program, so a future input runtime must enable them only with P0 at `$0000`. Approved by the user on 2026-11-11. | | |||||
| ## Deferred | ## Deferred | ||||
| @@ -21,6 +21,8 @@ later stories. | |||||
| | Sections | ascending addresses, zero-filled gaps | | | Sections | ascending addresses, zero-filled gaps | | ||||
| | MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size`, `CLASS name address size destroy count ifdir`, `METHOD name address` | | | MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size`, `CLASS name address size destroy count ifdir`, `METHOD name address` | | ||||
| | Startup | clears mailbox, producer `$03`, status PASS, then `RTS` | | | Startup | clears mailbox, producer `$03`, status PASS, then `RTS` | | ||||
| | Entry stub (D157) | 49 bytes: `PHP`/`SEI`, save CR and install CR `$0E`, P0 -> page `$1A`, frame base `$BF80`, `JSR` CODE, `JSR` START, P0 -> `$0000`, restore CR, `PLP`, `RTS` | | |||||
| | Section order | `STUB`, `ENTRY`, `START`, `PRINT` (`prgprint.s`), `CODE`, `DATA` | | |||||
| The fixed startup is copied from `src/runtime/startup.s`; `output.s` refuses | The fixed startup is copied from `src/runtime/startup.s`; `output.s` refuses | ||||
| existing `NAME.PRG`/`NAME.MAP` and scratches outputs it created on failure. | existing `NAME.PRG`/`NAME.MAP` and scratches outputs it created on failure. | ||||
| @@ -280,6 +282,17 @@ variables and buffers (and anything else above `$3FFF`) before it calls | |||||
| `mmu_shutdown`. Two test programs read wrong values this way before the rule | `mmu_shutdown`. Two test programs read wrong values this way before the rule | ||||
| was written down. | was written down. | ||||
| ### Generated programs (D157) | |||||
| A generated program runs with P0 at bank 0 page `$1A` (`PRG_ZP_PAGE`) from its | |||||
| `ENTRY` stub until it returns, with IRQs masked throughout. Every zero-page | |||||
| address it uses (`$C0-$FE`: print runtime, `IF_RESULT`, for values, `EM_LP`, | |||||
| `EM_TMP`, `MB_SELF`, `SP`/`FP`) is physically `$1Axx`, so the KERNAL and screen | |||||
| editor state in system zero page is never touched. On the C128 that state | |||||
| includes `$F2-$F4` (TCOLOR, RVS, QTSW), `$D0-$F9` (editor), and `$C0` (CAS1, | |||||
| cleared by the KERNAL IRQ). `prgprint.s` sets P0 to `$0000` only for each | |||||
| `CHROUT` call and re-masks IRQs before relocating it again. | |||||
| ## Interrupts | ## Interrupts | ||||
| - **IRQ:** masked in both compiler states. The ROM IRQ path (`$FF17`) jumps via | - **IRQ:** masked in both compiler states. The ROM IRQ path (`$FF17`) jumps via | ||||
| @@ -77,9 +77,10 @@ function Get-ScreenText { | |||||
| $ram = Read-Nova128Memory $Session 0x0400 1000 | $ram = Read-Nova128Memory $Session 0x0400 1000 | ||||
| $rows = for ($r = 0; $r -lt 25; $r++) { | $rows = for ($r = 0; $r -lt 25; $r++) { | ||||
| $chars = for ($c = 0; $c -lt 40; $c++) { | $chars = for ($c = 0; $c -lt 40; $c++) { | ||||
| # The C128 40-column display stores the printed bytes with bit 7 | |||||
| # set ($88 for H, $A0 for space), so mask it before decoding. | |||||
| $code = $ram[$r * 40 + $c] -band 0x7F | |||||
| # Do not mask bit 7: a set bit is reverse video, which means the | |||||
| # program left the editor's RVS flag ($F3) on (D157). Such cells | |||||
| # decode to '?' so the literal search fails. | |||||
| $code = $ram[$r * 40 + $c] | |||||
| if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' } | if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' } | ||||
| } | } | ||||
| -join $chars | -join $chars | ||||
| @@ -11,6 +11,8 @@ FORMATS_INC = 1 | |||||
| PRG_LOAD_ADDR = $1C01 | PRG_LOAD_ADDR = $1C01 | ||||
| PRG_ENTRY_ADDR = $1C0D | PRG_ENTRY_ADDR = $1C0D | ||||
| PRG_STUB_SIZE = 12 | PRG_STUB_SIZE = 12 | ||||
| PRG_ZP_PAGE = $1A ; P0 page of generated programs (D157) | |||||
| PRG_CR = $0E ; D129 generated-program mapping | |||||
| OUT_MAX_SECTIONS = 8 | OUT_MAX_SECTIONS = 8 | ||||
| OUT_SECTION_NAME_MAX = 12 | OUT_SECTION_NAME_MAX = 12 | ||||
| @@ -29,7 +29,7 @@ | |||||
| .include "../common/source.inc" | .include "../common/source.inc" | ||||
| NAME_MAX = 15 | NAME_MAX = 15 | ||||
| ENTRY_SIZE = 65 | |||||
| ENTRY_SIZE = 49 ; prefix + two JSRs + suffix (asserted) | |||||
| .import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline | .import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline | ||||
| .import src_load | .import src_load | ||||
| @@ -39,7 +39,7 @@ ENTRY_SIZE = 65 | |||||
| .import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr | .import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr | ||||
| .import gen_text, gen_text_len | .import gen_text, gen_text_len | ||||
| .import cg_code, cg_len | .import cg_code, cg_len | ||||
| .import sz_plan_code, sz_code_base | |||||
| .import sz_code_base | |||||
| .import out_begin, out_section, out_end_section, out_bytes, out_byte | .import out_begin, out_section, out_end_section, out_bytes, out_byte | ||||
| .import out_finish, out_abort, out_err | .import out_finish, out_abort, out_err | ||||
| .import out_load, out_entry, out_start | .import out_load, out_entry, out_start | ||||
| @@ -162,12 +162,10 @@ driver_entry: | |||||
| sta diag_screen | sta diag_screen | ||||
| sta em_on | sta em_on | ||||
| lda #ENTRY_SIZE ; code follows entry, startup, runtime | |||||
| clc | |||||
| adc #<gen_startup_size | |||||
| clc | |||||
| adc #<pp_size | |||||
| jsr sz_plan_code | |||||
| lda drv_code ; code follows entry, startup, runtime; | |||||
| sta sz_code_base ; their sum exceeds one byte, so use the | |||||
| lda drv_code+1 ; 16-bit address computed above | |||||
| sta sz_code_base+1 | |||||
| lda sz_code_base | lda sz_code_base | ||||
| ldx sz_code_base+1 | ldx sz_code_base+1 | ||||
| jsr parse_file_base | jsr parse_file_base | ||||
| @@ -376,13 +374,14 @@ add_rt: | |||||
| inc drv_rt+1 | inc drv_rt+1 | ||||
| @done: rts | @done: rts | ||||
| ; Emit the generated entry: preserve BASIC's FP, set the D129 frame base, | |||||
| ; call the program body, run the startup mailbox, restore FP, return. | |||||
| ; Emit the generated entry (D157): mask IRQs, install CR $0E and the private | |||||
| ; zero page, set the frame base, call the program body, run the startup | |||||
| ; mailbox, restore P0/CR/flags, return. | |||||
| ; C=1 on an output error. | ; C=1 on an output error. | ||||
| emit_entry: | emit_entry: | ||||
| lda #<entry_prefix | lda #<entry_prefix | ||||
| ldx #>entry_prefix | ldx #>entry_prefix | ||||
| ldy #34 | |||||
| ldy #<(entry_prefix_end - entry_prefix) | |||||
| jsr out_bytes | jsr out_bytes | ||||
| bcs @fail | bcs @fail | ||||
| lda drv_code | lda drv_code | ||||
| @@ -395,7 +394,7 @@ emit_entry: | |||||
| bcs @fail | bcs @fail | ||||
| lda #<entry_suffix | lda #<entry_suffix | ||||
| ldx #>entry_suffix | ldx #>entry_suffix | ||||
| ldy #25 | |||||
| ldy #<(entry_suffix_end - entry_suffix) | |||||
| jmp out_bytes | jmp out_bytes | ||||
| @fail: rts | @fail: rts | ||||
| @@ -532,16 +531,24 @@ sec_entry: .byte "ENTRY" | |||||
| sec_start: .byte "START" | sec_start: .byte "START" | ||||
| sec_print: .byte "PRINT" | sec_print: .byte "PRINT" | ||||
| sec_data: .byte "DATA" | sec_data: .byte "DATA" | ||||
| ; Save the working zero page the print runtime uses, then set the D129 | |||||
| ; frame base ($BF80). Push order: $C0, $F2, $F3, $F4, $FD, $FE. | |||||
| entry_prefix: .byte $AD, $C0, $00, $48, $AD, $F2, $00, $48 | |||||
| .byte $AD, $F3, $00, $48, $AD, $F4, $00, $48 | |||||
| .byte $AD, $FD, $00, $48, $AD, $FE, $00, $48 | |||||
| .byte $A9, $80, $8D, $FD, $00, $A9, $BF, $8D, $FE, $00 | |||||
| ; Restore in reverse order, then return to BASIC. | |||||
| entry_suffix: .byte $68, $8D, $FE, $00, $68, $8D, $FD, $00 | |||||
| .byte $68, $8D, $F4, $00, $68, $8D, $F3, $00 | |||||
| .byte $68, $8D, $F2, $00, $68, $8D, $C0, $00, $60 | |||||
| ; D157 generated entry. Mask IRQs, install the D129 mapping (CR $0E), give | |||||
| ; the program its own zero page by pointing P0 at page PRG_ZP_PAGE, and set the | |||||
| ; frame base ($BF80). The program never touches system zero page, so BASIC's | |||||
| ; and the screen editor's bytes need no save; prgprint switches P0 back to | |||||
| ; $0000 only around each CHROUT. Then CODE, START (emit_entry adds the JSRs). | |||||
| entry_prefix: .byte $08, $78 ; PHP, SEI | |||||
| .byte $AD, <MMU_CR, >MMU_CR, $48 ; LDA CR, PHA | |||||
| .byte $A9, PRG_CR, $8D, <MMU_CR, >MMU_CR | |||||
| .byte $A9, $00, $8D, <MMU_P0H, >MMU_P0H ; P0H first | |||||
| .byte $A9, PRG_ZP_PAGE, $8D, <MMU_P0L, >MMU_P0L | |||||
| .byte $A9, $80, $85, $FD, $A9, $BF, $85, $FE | |||||
| entry_prefix_end: | |||||
| ; P0 back to $0000, restore the entry CR and flags, return to BASIC. | |||||
| entry_suffix: .byte $A9, $00, $8D, <MMU_P0H, >MMU_P0H | |||||
| .byte $8D, <MMU_P0L, >MMU_P0L | |||||
| .byte $68, $8D, <MMU_CR, >MMU_CR ; PLA, STA CR | |||||
| .byte $28, $60 ; PLP, RTS | |||||
| entry_suffix_end: | |||||
| banner: .byte "NOVA128 COMPILER", $0D, "SOURCE: " | banner: .byte "NOVA128 COMPILER", $0D, "SOURCE: " | ||||
| banner_end: | banner_end: | ||||
| src_label: .byte "SOURCE " | src_label: .byte "SOURCE " | ||||
| @@ -589,3 +596,5 @@ drv_code: .res 2 | |||||
| drv_text: .res 2 | drv_text: .res 2 | ||||
| drv_tmp_lo: .res 1 | drv_tmp_lo: .res 1 | ||||
| drv_tmp_hi: .res 1 | drv_tmp_hi: .res 1 | ||||
| .assert ENTRY_SIZE = (entry_prefix_end - entry_prefix) + 6 + (entry_suffix_end - entry_suffix), error, "ENTRY_SIZE does not match the entry stub" | |||||
| @@ -2,14 +2,19 @@ | |||||
| ; | ; | ||||
| ; output.s writes these bytes into the generated image ahead of CODE, so they | ; output.s writes these bytes into the generated image ahead of CODE, so they | ||||
| ; must be position independent: no references to compiler data or to their own | ; must be position independent: no references to compiler data or to their own | ||||
| ; addresses, only relative branches and the fixed KERNAL CHROUT address. | |||||
| ; addresses, only relative branches, the fixed KERNAL CHROUT address and the | |||||
| ; MMU P0 registers. | |||||
| ; | ; | ||||
| ; Working storage is four zero-page bytes that the generated entry stub saves | |||||
| ; and restores around the program body: | |||||
| ; Working storage is four bytes of the program's private zero page (D157: the | |||||
| ; entry stub points P0 at page PRG_ZP_PAGE with IRQs masked): | |||||
| ; $F2-$F3 text pointer | ; $F2-$F3 text pointer | ||||
| ; $F4 text length | ; $F4 text length | ||||
| ; $C0 text index / byte value scratch | ; $C0 text index / byte value scratch | ||||
| ; None of them belongs to the D129 frame ABI ($FB-$FE) or the emitter cells. | |||||
| ; None of them belongs to the D129 frame ABI ($FB-$FE). CHROUT needs the system | |||||
| ; zero page (on the C128 $F2-$F4 are the editor's TCOLOR/RVS/QTSW, and the IRQ | |||||
| ; clears $C0), so every call goes through SYS_CHROUT, which switches P0 back | |||||
| ; to $0000 for the call only. Each use is inlined: a JSR to a shared helper | |||||
| ; would not be position independent. | |||||
| ; | ; | ||||
| ; Contract: | ; Contract: | ||||
| ; pp_text A/X = address, Y = length (0..255); prints the bytes, clobbers A. | ; pp_text A/X = address, Y = length (0..255); prints the bytes, clobbers A. | ||||
| @@ -19,11 +24,28 @@ | |||||
| .setcpu "6502" | .setcpu "6502" | ||||
| .include "../common/c128.inc" | .include "../common/c128.inc" | ||||
| .include "../common/formats.inc" | |||||
| PP_SRC = $F2 | PP_SRC = $F2 | ||||
| PP_LEN = $F4 | PP_LEN = $F4 | ||||
| PP_IDX = $C0 | PP_IDX = $C0 | ||||
| ; A = character. P0 -> $0000, CHROUT, re-mask IRQs, P0 -> PRG_ZP_PAGE. | |||||
| ; Clobbers A; X and Y are whatever CHROUT leaves. | |||||
| .macro SYS_CHROUT | |||||
| pha | |||||
| lda #$00 | |||||
| sta MMU_P0H ; high first, low commits | |||||
| sta MMU_P0L | |||||
| pla | |||||
| jsr CHROUT | |||||
| sei | |||||
| lda #$00 | |||||
| sta MMU_P0H | |||||
| lda #PRG_ZP_PAGE | |||||
| sta MMU_P0L | |||||
| .endmacro | |||||
| .export pp_start, pp_size | .export pp_start, pp_size | ||||
| .export pp_off_text, pp_off_byte, pp_off_cr | .export pp_off_text, pp_off_byte, pp_off_cr | ||||
| @@ -40,7 +62,7 @@ pp_text: | |||||
| sta PP_IDX | sta PP_IDX | ||||
| @loop: ldy PP_IDX | @loop: ldy PP_IDX | ||||
| lda (PP_SRC),y | lda (PP_SRC),y | ||||
| jsr CHROUT | |||||
| SYS_CHROUT | |||||
| inc PP_IDX | inc PP_IDX | ||||
| lda PP_IDX | lda PP_IDX | ||||
| cmp PP_LEN | cmp PP_LEN | ||||
| @@ -63,7 +85,7 @@ pp_byte: | |||||
| cpx #'0' | cpx #'0' | ||||
| beq @tens | beq @tens | ||||
| txa | txa | ||||
| jsr CHROUT | |||||
| SYS_CHROUT | |||||
| @tens: ldx #'0' | @tens: ldx #'0' | ||||
| @ten_loop: | @ten_loop: | ||||
| lda PP_SRC | lda PP_SRC | ||||
| @@ -77,14 +99,16 @@ pp_byte: | |||||
| cpx #'0' | cpx #'0' | ||||
| beq @units | beq @units | ||||
| txa | txa | ||||
| jsr CHROUT | |||||
| SYS_CHROUT | |||||
| @units: lda PP_SRC | @units: lda PP_SRC | ||||
| clc | clc | ||||
| adc #'0' | adc #'0' | ||||
| jmp CHROUT | |||||
| SYS_CHROUT | |||||
| rts | |||||
| pp_cr: lda #$0D | pp_cr: lda #$0D | ||||
| jmp CHROUT | |||||
| SYS_CHROUT | |||||
| rts | |||||
| pp_end: | pp_end: | ||||
| pp_size = pp_end - pp_start | pp_size = pp_end - pp_start | ||||
Powered by TurnKey Linux.