Emitted print calls pointed at a compiler trampoline and the literal at a compiler buffer, so a built PRG could compile a print source but never show it. Calls now use the em_pr_* cell values, a position-independent runtime is copied into a PRINT section, an ENTRY stub runs the body, and the literal is relocated into DATA. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>main
| @@ -63,13 +63,16 @@ The on-machine driver (`src/compiler/main.s`, E07-S05) turns those pieces into | |||
| `N128C.PRG`: it prompts for one source name, loads it from drive 8 with | |||
| `src_load`, runs pass 1 and pass 2 with `em_on`, plans the code section with | |||
| `sz_plan_code` so absolute targets match the loaded image, and writes STUB, | |||
| START and CODE through `output.s`. The image is `$1C01` stub, `$1C0D` runtime | |||
| startup, then CODE at `$1C0D + gen_startup_size`; the MAP records the three | |||
| ENTRY, START, PRINT and CODE through `output.s`. The image is `$1C01` stub, | |||
| `$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 | |||
| sections. The driver prints `SOURCE`, `OUTPUT` and the diagnostic count, keeps | |||
| 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 | |||
| generated image. Text `print` is such a gap until `src/runtime/print.s` is | |||
| emitted into the container. | |||
| generated image. Text `print` is emitted against a position-independent | |||
| runtime copied into the PRG as the `PRINT` section between `START` and `CODE` | |||
| (D155), so the loaded program never calls back into the compiler. | |||
| ## 3. Repository Module Plan | |||
| @@ -89,6 +89,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. | | |||
| | 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. | | |||
| ## Deferred | |||
| - Native C128/80-column Vim port | |||
| @@ -94,11 +94,11 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\launch-compiler.ps1 | |||
| `COUNTER.MAP`, `BAD.ERR`, the refusal of a second compile, and that the | |||
| generated `COUNTER.PRG` reaches `MB_ST_PASS` in its own session. | |||
| `print` statements are not yet supported by the driver: the generated image | |||
| does not contain the print runtime, so such a source ends with | |||
| `COMPILE FAILED, 1 ERRORS` and E227 `CANNOT EMIT THIS CODE YET`. Programs that | |||
| only compute and store values, like `tests/compiler/counter.n8`, compile and | |||
| run. | |||
| `print` works in generated programs: the image carries a small | |||
| position-independent print runtime in a `PRINT` section ahead of `CODE`, so | |||
| text, byte and newline output call back into the PRG itself rather than into | |||
| the compiler. `hello.n8` prints its string from the built PRG; programs that | |||
| only compute, like `tests/compiler/counter.n8`, compile and run as before. | |||
| ## Recovery | |||
| @@ -66,11 +66,13 @@ are diagnostics, and never overwrite an earlier output. | |||
| ## Coordination notes | |||
| - The print gap is deliberate and documented: the generated image has no print | |||
| runtime yet, so `main.s` zeroes `em_pr_text`/`em_pr_byte`/`em_pr_cr` and a | |||
| `print` source fails with E227 instead of emitting a call into the compiler's | |||
| own copy of `print.s`. Emitting that runtime into the container is follow-up | |||
| work on E04-S05, not a driver change. | |||
| - Generated `print` is closed as D155: `emit.s` calls the address held in the | |||
| `em_pr_*` cells, `src/runtime/prgprint.s` is copied into a `PRINT` section, | |||
| and an `ENTRY` stub calls the body, runs the startup mailbox and restores the | |||
| zero page it borrows. `hello.n8` compiles and displays its string from the | |||
| standalone PRG. E04-S05's captured-literal convention is unchanged: the | |||
| driver copies the literal into a `DATA` section after CODE and rewrites the | |||
| emitted LDA/LDX operand pair from `gen_text` to that copy. | |||
| - The code base is planned before parsing, because pass 2 emits absolute | |||
| targets: `PRG_ENTRY_ADDR + gen_startup_size` (`$1C57` with the current | |||
| startup) is installed with `sz_plan_code` and then `parse_file_base` runs one | |||
| @@ -95,6 +97,16 @@ are diagnostics, and never overwrite an earlier output. | |||
| - The verifier's MAP checks first failed on case and then on `$` anchors, | |||
| because `c1541` reads the CR-terminated MAP with CR gaps and no LF. The test | |||
| normalises CR to LF before matching. | |||
| - The first runtime draft patched its own operands, but those stores carried | |||
| compiler-image addresses (`STA $6F04`), so the copied PRG printed nothing. | |||
| It now keeps its state in `$C0`/`$F2-$F4`, which the ENTRY stub saves and | |||
| restores. | |||
| - The literal still pointed at the compiler's `gen_text`; the driver now moves | |||
| it into `DATA` and relocates the emitted address. The PRG then printed the | |||
| string (screenshot `hello-run.png`). | |||
| - Screen-RAM readback through the monitor did not match the visible VIC-II | |||
| screen, so the automated print check asserts the PRINT call, the DATA bytes | |||
| and the program's return instead of parsing screen memory. | |||
| ## Completion | |||
| @@ -104,13 +116,15 @@ are diagnostics, and never overwrite an earlier output. | |||
| generated-PRG monitor run), and the same script through the regression | |||
| harness: `run-regression.ps1 -Filter driver`. | |||
| - Results: `summary: SOURCE COUNTER.N8 / OUTPUT COUNTER.PRG / 0 ERRORS`; | |||
| `success: COUNTER.PRG 263 bytes, COUNTER.MAP 95 bytes, no COUNTER.ERR`; | |||
| `failure: BAD.ERR E024 BAD.N8 3 1 END PROGRAM EXPECTED, E227 ... CANNOT EMIT | |||
| THIS CODE YET, no BAD.PRG, COUNTER.PRG unchanged`; `collision: OUTPUT EXISTS | |||
| and COUNTER.PRG unchanged`; `injection: prepare-disk.ps1 wrote build/n128c.prg | |||
| `success: COUNTER.PRG 419 bytes, COUNTER.MAP 143 bytes, no COUNTER.ERR` (MAP: | |||
| STUB, ENTRY, START, PRINT, CODE); `failure: BAD.ERR E024 ... END PROGRAM | |||
| EXPECTED, no BAD.PRG, COUNTER.PRG unchanged`; `collision: OUTPUT EXISTS and | |||
| COUNTER.PRG unchanged`; `injection: prepare-disk.ps1 wrote build/n128c.prg | |||
| byte-identically as n128c`; `run: generated COUNTER.PRG reported MB_ST_PASS in | |||
| x128`. The harness run reports `driver pass 82.5s` and `1 passed, 0 failed`, | |||
| with `driver` now listed in `tests/regression/suites.ps1`. | |||
| x128`; `print: HELLO.PRG carries PRINT+DATA, calls the runtime, and returns`, | |||
| with `hello-run.png` showing `HELLO FROM NOVA128` on the VIC-II screen. | |||
| `tests/regression/harness.tests.ps1` still passes; `driver` remains listed in | |||
| `tests/regression/suites.ps1`. | |||
| - Remaining risks: text `print` needs the generated-program print runtime; the | |||
| code buffer stays at 255 bytes, so larger programs fail with a diagnostic | |||
| rather than a partial file; the summary check reads screen RAM through a | |||
| @@ -28,6 +28,7 @@ $link = Invoke-Nova128Link -Name "n128c" -BuildDir $out ` | |||
| (Join-Path $project "src\compiler\types.s"), | |||
| (Join-Path $project "src\compiler\output.s"), | |||
| (Join-Path $project "src\runtime\startup.s"), | |||
| (Join-Path $project "src\runtime\prgprint.s"), | |||
| (Join-Path $project "src\compiler\main.s")) | |||
| if ((Get-Nova128Label $link.Labels "driver_entry") -ne 0x1C0D) { | |||
| @@ -40,10 +40,14 @@ $counterPet = Join-Path $root "counter.pet" | |||
| $badPet = Join-Path $root "bad.pet" | |||
| [IO.File]::WriteAllBytes($badPet, (ConvertTo-Nova128Petscii ` | |||
| (Get-Content -Raw -LiteralPath (Join-Path $project "tests\grammar\rejected\miss_end.n8")))) | |||
| $helloPet = Join-Path $root "hello.pet" | |||
| [IO.File]::WriteAllBytes($helloPet, (ConvertTo-Nova128Petscii ` | |||
| (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\hello.n8")))) | |||
| New-Nova128Disk -Path $work -Files @( | |||
| @{ Name = "n128c"; Path = $driver; Type = "prg" }, | |||
| @{ Name = "counter.n8"; Path = $counterPet; Type = "seq" }, | |||
| @{ Name = "bad.n8"; Path = $badPet; Type = "seq" }) | |||
| @{ Name = "bad.n8"; Path = $badPet; Type = "seq" }, | |||
| @{ Name = "hello.n8"; Path = $helloPet; Type = "seq" }) | |||
| function Invoke-Compile { | |||
| param([string]$Keys, [string]$Screenshot) | |||
| @@ -131,26 +135,39 @@ if ($prg.Length -lt 14 -or $prg[0] -ne 0x01 -or $prg[1] -ne 0x1C) { | |||
| $map = ([Text.Encoding]::ASCII.GetString((Read-DiskFile -Name "counter.map" -Type "s" ` | |||
| -Dest (Join-Path $root "counter.map"))) -replace "`r", "`n") | |||
| $mapLines = $map -split "`n" | |||
| $codeMatch = [regex]::Match($map, '(?im)^SECTION CODE ([0-9a-f]{4}) ([0-9a-f]{4})') | |||
| $startMatch = [regex]::Match($map, '(?im)^SECTION START ([0-9a-f]{4}) ([0-9a-f]{4})') | |||
| if ($mapLines[0] -ne "NOVA128 MAP 1") { $failures += "MAP header is '$($mapLines[0])'" } | |||
| if ($map -notmatch '(?im)^ENTRY 1c0d') { $failures += "MAP ENTRY is not 1C0D" } | |||
| if ($map -notmatch '(?im)^SECTION STUB 1c01 000c') { $failures += "MAP STUB section is wrong" } | |||
| if (-not $startMatch.Success) { | |||
| $failures += "MAP has no START section" | |||
| } elseif ([Convert]::ToInt32($startMatch.Groups[1].Value, 16) -ne 0x1C0D) { | |||
| $failures += "MAP START does not begin at 1C0D" | |||
| } | |||
| if (-not $codeMatch.Success) { | |||
| $failures += "MAP has no CODE section" | |||
| } else { | |||
| $codeStart = [Convert]::ToInt32($codeMatch.Groups[1].Value, 16) | |||
| $codeSize = [Convert]::ToInt32($codeMatch.Groups[2].Value, 16) | |||
| if ($codeStart -ne 0x1C57) { $failures += ("CODE starts at {0:X4}, expected 1C57" -f $codeStart) } | |||
| if ($prg.Length -ne (2 + 12 + 0x4A + $codeSize)) { | |||
| $failures += ("PRG is {0} bytes, MAP accounts for {1}" -f $prg.Length, (2 + 12 + 0x4A + $codeSize)) | |||
| $sections = @{} | |||
| foreach ($m in [regex]::Matches($map, '(?im)^SECTION (\w+) ([0-9a-f]{4}) ([0-9a-f]{4})')) { | |||
| $sections[$m.Groups[1].Value.ToUpper()] = @{ | |||
| Start = [Convert]::ToInt32($m.Groups[2].Value, 16) | |||
| Size = [Convert]::ToInt32($m.Groups[3].Value, 16) | |||
| } | |||
| } | |||
| foreach ($name in @("STUB", "ENTRY", "START", "PRINT", "CODE")) { | |||
| if (-not $sections.ContainsKey($name)) { $failures += "MAP has no $name section" } | |||
| } | |||
| $ordered = @("ENTRY", "START", "PRINT", "CODE") | |||
| if ($sections.ContainsKey("DATA")) { $ordered += "DATA" } | |||
| if ($sections.ContainsKey("STUB") -and | |||
| ($sections.STUB.Start -ne 0x1C01 -or $sections.STUB.Size -ne 0x0C)) { | |||
| $failures += "MAP STUB section is wrong" | |||
| } | |||
| $expectedStart = 0x1C0D | |||
| foreach ($name in $ordered) { | |||
| if (-not $sections.ContainsKey($name)) { continue } | |||
| if ($sections[$name].Start -ne $expectedStart) { | |||
| $failures += ("{0} starts at {1:X4}, expected {2:X4}" -f $name, $sections[$name].Start, $expectedStart) | |||
| } | |||
| if ($sections[$name].Size -le 0) { $failures += "$name section is empty" } | |||
| $expectedStart += $sections[$name].Size | |||
| } | |||
| if ($sections.ContainsKey("CODE")) { | |||
| $expectedLen = 2 + $sections.STUB.Size | |||
| foreach ($name in $ordered) { $expectedLen += $sections[$name].Size } | |||
| if ($prg.Length -ne $expectedLen) { | |||
| $failures += ("PRG is {0} bytes, MAP accounts for {1}" -f $prg.Length, $expectedLen) | |||
| } | |||
| if ($codeSize -eq 0) { $failures += "CODE section is empty" } | |||
| } | |||
| # 3: a syntax error writes the ERR file and no PRG, and leaves COUNTER.PRG alone. | |||
| @@ -191,6 +208,63 @@ if ($mb[0] -ne 0x4E -or $mb[1] -ne 0x38) { $failures += "the generated program d | |||
| if ($mb[2] -ne 0x03) { $failures += ("producer {0:X2}, expected 03 (generated program)" -f $mb[2]) } | |||
| if ($mb[3] -ne 0xA5) { $failures += ("generated program status {0:X2}, expected A5" -f $mb[3]) } | |||
| # 6: a print source compiles with the copied runtime and prints on screen. | |||
| Invoke-Compile -Keys "hello.n8\n" -Screenshot (Join-Path $root "hello-summary.png") | |||
| $listing = Get-Listing | |||
| if ($listing -notmatch '"hello\.prg"\s+prg') { $failures += "HELLO.PRG is missing" } | |||
| if ($listing -match '"hello\.err"') { $failures += "HELLO.N8 left HELLO.ERR" } | |||
| $helloMap = ([Text.Encoding]::ASCII.GetString((Read-DiskFile -Name "hello.map" -Type "s" ` | |||
| -Dest (Join-Path $root "hello.map"))) -replace "`r", "`n") | |||
| if ($helloMap -notmatch '(?im)^SECTION PRINT ') { $failures += "HELLO.MAP has no PRINT section" } | |||
| $helloPrgPath = Join-Path $root "hello.prg" | |||
| $helloBytes = Read-DiskFile -Name "hello.prg" -Type "p" -Dest $helloPrgPath | |||
| $helloSections = @{} | |||
| foreach ($m in [regex]::Matches($helloMap, '(?im)^SECTION (\w+) ([0-9a-f]{4}) ([0-9a-f]{4})')) { | |||
| $helloSections[$m.Groups[1].Value.ToUpper()] = @{ | |||
| Start = [Convert]::ToInt32($m.Groups[2].Value, 16) | |||
| Size = [Convert]::ToInt32($m.Groups[3].Value, 16) | |||
| } | |||
| } | |||
| if (-not $helloSections.ContainsKey("DATA")) { | |||
| $failures += "HELLO.MAP has no DATA section" | |||
| } else { | |||
| $literal = [Text.Encoding]::ASCII.GetBytes("HELLO FROM NOVA128") | |||
| $dataOffset = 2 + $helloSections.STUB.Size + $helloSections.ENTRY.Size + | |||
| $helloSections.START.Size + $helloSections.PRINT.Size + $helloSections.CODE.Size | |||
| if ($helloBytes.Length -lt $dataOffset + $literal.Length) { | |||
| $failures += "HELLO.PRG is shorter than its DATA section" | |||
| } else { | |||
| for ($i = 0; $i -lt $literal.Length; $i++) { | |||
| if ($helloBytes[$dataOffset + $i] -ne $literal[$i]) { | |||
| $failures += "HELLO.PRG DATA does not hold the print literal" | |||
| break | |||
| } | |||
| } | |||
| } | |||
| $codeOffset = 2 + $helloSections.STUB.Size + $helloSections.ENTRY.Size + | |||
| $helloSections.START.Size + $helloSections.PRINT.Size | |||
| $calledPrint = $false | |||
| for ($i = 0; $i -lt $helloSections.CODE.Size - 2; $i++) { | |||
| if ($helloBytes[$codeOffset + $i] -eq 0x20) { | |||
| $target = $helloBytes[$codeOffset + $i + 1] + 256 * $helloBytes[$codeOffset + $i + 2] | |||
| if ($target -ge $helloSections.PRINT.Start -and | |||
| $target -lt ($helloSections.PRINT.Start + $helloSections.PRINT.Size)) { | |||
| $calledPrint = $true | |||
| break | |||
| } | |||
| } | |||
| } | |||
| if (-not $calledPrint) { $failures += "HELLO.PRG CODE never calls the PRINT runtime" } | |||
| } | |||
| $helloSession = Start-Nova128X128 -Autostart $helloPrgPath -Port ($Port + 2) ` | |||
| -Screenshot (Join-Path $root "hello-run.png") | |||
| try { | |||
| $helloMb = Wait-Nova128Mailbox $helloSession -TimeoutSeconds 60 | |||
| } finally { | |||
| Stop-Nova128X128 $helloSession | |||
| } | |||
| if ($helloMb[3] -ne 0xA5) { $failures += "HELLO.PRG did not return from its body" } | |||
| if ($failures) { throw ("n128c verification failed:`n " + ($failures -join "`n ")) } | |||
| Write-Host ("summary: SOURCE COUNTER.N8 / OUTPUT COUNTER.PRG / 0 ERRORS") | |||
| Write-Host ("success: COUNTER.PRG {0} bytes, COUNTER.MAP {1} bytes, no COUNTER.ERR" -f | |||
| @@ -199,3 +273,4 @@ Write-Host ("failure: BAD.ERR {0}, no BAD.PRG, COUNTER.PRG unchanged" -f $errTex | |||
| Write-Host "injection: prepare-disk.ps1 wrote build/n128c.prg byte-identically as n128c" | |||
| Write-Host "collision: OUTPUT EXISTS and COUNTER.PRG unchanged" | |||
| Write-Host "run: generated COUNTER.PRG reported MB_ST_PASS in x128" | |||
| Write-Host "print: HELLO.PRG carries PRINT+DATA, calls the runtime, and returns (screenshot hello-run.png)" | |||
| @@ -526,23 +526,23 @@ em_print: | |||
| lda gen_text_len | |||
| jsr cg_ldy_imm | |||
| bcs @fail | |||
| lda #<em_tramp_text | |||
| ldx #>em_tramp_text | |||
| lda em_pr_text ; the caller's final routine address, so a | |||
| ldx em_pr_text+1 ; generated PRG calls into its own image | |||
| jsr cg_jsr_abs | |||
| bcs @fail | |||
| jmp @cr | |||
| @byte: lda em_pr_byte | |||
| ora em_pr_byte+1 | |||
| beq @state | |||
| lda #<em_tramp_byte | |||
| ldx #>em_tramp_byte | |||
| lda em_pr_byte | |||
| ldx em_pr_byte+1 | |||
| jsr cg_jsr_abs | |||
| bcs @fail | |||
| @cr: lda em_pr_cr | |||
| ora em_pr_cr+1 | |||
| beq @state | |||
| lda #<em_tramp_cr | |||
| ldx #>em_tramp_cr | |||
| lda em_pr_cr | |||
| ldx em_pr_cr+1 | |||
| jsr cg_jsr_abs | |||
| bcs @fail | |||
| clc | |||
| @@ -29,6 +29,7 @@ | |||
| .include "../common/source.inc" | |||
| NAME_MAX = 15 | |||
| ENTRY_SIZE = 65 | |||
| .import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline | |||
| .import src_load | |||
| @@ -36,13 +37,15 @@ NAME_MAX = 15 | |||
| .import diag_init, diag_finish, diag_screen | |||
| .import parse_file_base | |||
| .import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr | |||
| .import gen_text, gen_text_len | |||
| .import cg_code, cg_len | |||
| .import sz_plan_code, sz_code_base | |||
| .import out_begin, out_startup, out_section, out_end_section, out_bytes | |||
| .import out_begin, out_section, out_end_section, out_bytes, out_byte | |||
| .import out_finish, out_abort, out_err | |||
| .import out_load, out_entry, out_start | |||
| .import out_prg_name, out_prg_len | |||
| .import gen_startup_size | |||
| .import gen_startup_size, gen_stub, gen_startup | |||
| .import pp_start, pp_size, pp_off_text, pp_off_byte, pp_off_cr | |||
| ; Print the bytes from text up to its end label. | |||
| ; Far conditional jumps: a near branch plus an absolute JMP. | |||
| @@ -88,11 +91,54 @@ driver_entry: | |||
| jsr mmu_bank0 | |||
| lda #$00 | |||
| sta drv_out_open | |||
| sta em_pr_text ; the generated image carries no print | |||
| sta em_pr_text+1 ; runtime yet: print must report E227, not | |||
| sta em_pr_byte ; a call into the compiler's own copies | |||
| lda #<PRG_ENTRY_ADDR ; ENTRY, START, PRINT, then CODE | |||
| ldx #>PRG_ENTRY_ADDR | |||
| ldy #ENTRY_SIZE | |||
| jsr add_rt | |||
| lda drv_rt | |||
| sta drv_start | |||
| lda drv_rt+1 | |||
| sta drv_start+1 | |||
| lda drv_rt | |||
| ldx drv_rt+1 | |||
| ldy #<gen_startup_size | |||
| jsr add_rt | |||
| lda drv_rt | |||
| sta drv_print | |||
| lda drv_rt+1 | |||
| sta drv_print+1 | |||
| lda drv_rt | |||
| ldx drv_rt+1 | |||
| ldy #<pp_size | |||
| jsr add_rt | |||
| lda drv_rt | |||
| sta drv_code | |||
| lda drv_rt+1 | |||
| sta drv_code+1 | |||
| lda drv_print ; print hooks point into the loaded image | |||
| ldx drv_print+1 | |||
| ldy #<pp_off_text | |||
| jsr add_rt | |||
| lda drv_rt | |||
| sta em_pr_text | |||
| lda drv_rt+1 | |||
| sta em_pr_text+1 | |||
| lda drv_print | |||
| ldx drv_print+1 | |||
| ldy #<pp_off_byte | |||
| jsr add_rt | |||
| lda drv_rt | |||
| sta em_pr_byte | |||
| lda drv_rt+1 | |||
| sta em_pr_byte+1 | |||
| lda drv_print | |||
| ldx drv_print+1 | |||
| ldy #<pp_off_cr | |||
| jsr add_rt | |||
| lda drv_rt | |||
| sta em_pr_cr | |||
| lda drv_rt+1 | |||
| sta em_pr_cr+1 | |||
| PRINT banner, banner_end | |||
| @@ -116,7 +162,11 @@ driver_entry: | |||
| sta diag_screen | |||
| sta em_on | |||
| lda #<gen_startup_size ; code follows the generated startup | |||
| lda #ENTRY_SIZE ; code follows entry, startup, runtime | |||
| clc | |||
| adc #<gen_startup_size | |||
| clc | |||
| adc #<pp_size | |||
| jsr sz_plan_code | |||
| lda sz_code_base | |||
| ldx sz_code_base+1 | |||
| @@ -132,6 +182,16 @@ driver_entry: | |||
| jmp @no_code | |||
| @write: | |||
| lda drv_code ; the literal follows the code | |||
| ldx drv_code+1 | |||
| ldy cg_len | |||
| jsr add_rt | |||
| lda drv_rt | |||
| sta drv_text | |||
| lda drv_rt+1 | |||
| sta drv_text+1 | |||
| jsr relocate_text | |||
| lda #<PRG_LOAD_ADDR | |||
| sta out_load | |||
| lda #>PRG_LOAD_ADDR | |||
| @@ -148,8 +208,67 @@ driver_entry: | |||
| lda #$01 | |||
| sta drv_out_open | |||
| jsr out_startup | |||
| lda #<PRG_LOAD_ADDR | |||
| sta out_start | |||
| lda #>PRG_LOAD_ADDR | |||
| sta out_start+1 | |||
| lda #<sec_stub | |||
| ldx #>sec_stub | |||
| ldy #4 | |||
| jsr out_section | |||
| ON_CARRY @out_fail | |||
| lda #<gen_stub | |||
| ldx #>gen_stub | |||
| ldy #PRG_STUB_SIZE | |||
| jsr out_bytes | |||
| ON_CARRY @out_fail | |||
| jsr out_end_section | |||
| lda #<PRG_ENTRY_ADDR | |||
| sta out_start | |||
| lda #>PRG_ENTRY_ADDR | |||
| sta out_start+1 | |||
| lda #<sec_entry | |||
| ldx #>sec_entry | |||
| ldy #5 | |||
| jsr out_section | |||
| ON_CARRY @out_fail | |||
| jsr emit_entry | |||
| ON_CARRY @out_fail | |||
| jsr out_end_section | |||
| lda drv_start | |||
| sta out_start | |||
| lda drv_start+1 | |||
| sta out_start+1 | |||
| lda #<sec_start | |||
| ldx #>sec_start | |||
| ldy #5 | |||
| jsr out_section | |||
| ON_CARRY @out_fail | |||
| lda #<gen_startup | |||
| ldx #>gen_startup | |||
| ldy #<gen_startup_size | |||
| jsr out_bytes | |||
| ON_CARRY @out_fail | |||
| jsr out_end_section | |||
| lda drv_print | |||
| sta out_start | |||
| lda drv_print+1 | |||
| sta out_start+1 | |||
| lda #<sec_print | |||
| ldx #>sec_print | |||
| ldy #5 | |||
| jsr out_section | |||
| ON_CARRY @out_fail | |||
| lda #<pp_start | |||
| ldx #>pp_start | |||
| ldy #<pp_size | |||
| jsr out_bytes | |||
| ON_CARRY @out_fail | |||
| jsr out_end_section | |||
| lda sz_code_base | |||
| sta out_start | |||
| lda sz_code_base+1 | |||
| @@ -165,6 +284,25 @@ driver_entry: | |||
| jsr out_bytes | |||
| ON_CARRY @out_fail | |||
| jsr out_end_section | |||
| lda gen_text_len | |||
| beq @no_text | |||
| lda drv_text | |||
| sta out_start | |||
| lda drv_text+1 | |||
| sta out_start+1 | |||
| lda #<sec_data | |||
| ldx #>sec_data | |||
| ldy #4 | |||
| jsr out_section | |||
| ON_CARRY @out_fail | |||
| lda #<gen_text | |||
| ldx #>gen_text | |||
| ldy gen_text_len | |||
| jsr out_bytes | |||
| ON_CARRY @out_fail | |||
| jsr out_end_section | |||
| @no_text: | |||
| jsr out_finish | |||
| ON_CARRY @out_fail | |||
| lda #$00 | |||
| @@ -226,6 +364,84 @@ driver_entry: | |||
| plp | |||
| rts | |||
| ; A/X = base, Y = 8-bit addend; drv_rt = the sum. | |||
| add_rt: | |||
| sta drv_rt | |||
| stx drv_rt+1 | |||
| tya | |||
| clc | |||
| adc drv_rt | |||
| sta drv_rt | |||
| bcc @done | |||
| inc drv_rt+1 | |||
| @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. | |||
| ; C=1 on an output error. | |||
| emit_entry: | |||
| lda #<entry_prefix | |||
| ldx #>entry_prefix | |||
| ldy #34 | |||
| jsr out_bytes | |||
| bcs @fail | |||
| lda drv_code | |||
| ldx drv_code+1 | |||
| jsr emit_jsr | |||
| bcs @fail | |||
| lda drv_start | |||
| ldx drv_start+1 | |||
| jsr emit_jsr | |||
| bcs @fail | |||
| lda #<entry_suffix | |||
| ldx #>entry_suffix | |||
| ldy #25 | |||
| jmp out_bytes | |||
| @fail: rts | |||
| ; A/X = JSR target. | |||
| emit_jsr: | |||
| sta drv_tmp_lo | |||
| stx drv_tmp_hi | |||
| lda #$20 | |||
| jsr out_byte | |||
| bcs @fail | |||
| lda drv_tmp_lo | |||
| jsr out_byte | |||
| bcs @fail | |||
| lda drv_tmp_hi | |||
| jmp out_byte | |||
| @fail: rts | |||
| ; Point the captured literal at its copy in the DATA section. The emitter | |||
| ; emits exactly LDA #<gen_text / LDX #>gen_text once per captured string. | |||
| relocate_text: | |||
| ldy #$00 | |||
| @scan: tya | |||
| clc | |||
| adc #3 | |||
| cmp cg_len | |||
| bcs @done | |||
| lda cg_code,y | |||
| cmp #$A9 | |||
| bne @next | |||
| lda cg_code+1,y | |||
| cmp #<gen_text | |||
| bne @next | |||
| lda cg_code+2,y | |||
| cmp #$A2 | |||
| bne @next | |||
| lda cg_code+3,y | |||
| cmp #>gen_text | |||
| bne @next | |||
| lda drv_text | |||
| sta cg_code+1,y | |||
| lda drv_text+1 | |||
| sta cg_code+3,y | |||
| @next: iny | |||
| bne @scan | |||
| @done: rts | |||
| ; --------------------------------------------------------------- keyboard | |||
| ; Read one line into drv_name. BASIN echoes and edits the line, so the | |||
| ; backspace key never reaches this loop. C=1 and drv_len=0 on an empty line. | |||
| @@ -311,6 +527,21 @@ print_dec8: | |||
| .segment "RODATA" | |||
| powers: .byte 100, 10, 1 | |||
| code_name: .byte "CODE" | |||
| sec_stub: .byte "STUB" | |||
| sec_entry: .byte "ENTRY" | |||
| sec_start: .byte "START" | |||
| sec_print: .byte "PRINT" | |||
| 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 | |||
| banner: .byte "NOVA128 COMPILER", $0D, "SOURCE: " | |||
| banner_end: | |||
| src_label: .byte "SOURCE " | |||
| @@ -351,3 +582,10 @@ drv_len: .res 1 | |||
| drv_diag: .res 1 | |||
| drv_err: .res 1 | |||
| drv_out_open: .res 1 | |||
| drv_rt: .res 2 | |||
| drv_start: .res 2 | |||
| drv_print: .res 2 | |||
| drv_code: .res 2 | |||
| drv_text: .res 2 | |||
| drv_tmp_lo: .res 1 | |||
| drv_tmp_hi: .res 1 | |||
| @@ -0,0 +1,98 @@ | |||
| ; prgprint.s - print runtime copied into generated programs (E07-S05). | |||
| ; | |||
| ; 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 | |||
| ; addresses, only relative branches and the fixed KERNAL CHROUT address. | |||
| ; | |||
| ; Working storage is four zero-page bytes that the generated entry stub saves | |||
| ; and restores around the program body: | |||
| ; $F2-$F3 text pointer | |||
| ; $F4 text length | |||
| ; $C0 text index / byte value scratch | |||
| ; None of them belongs to the D129 frame ABI ($FB-$FE) or the emitter cells. | |||
| ; | |||
| ; Contract: | |||
| ; pp_text A/X = address, Y = length (0..255); prints the bytes, clobbers A. | |||
| ; pp_byte A = 0..255; prints minimal decimal without leading zeros. | |||
| ; pp_cr prints $0D. | |||
| ; None of them append a newline; the emitter calls pp_cr for that. | |||
| .setcpu "6502" | |||
| .include "../common/c128.inc" | |||
| PP_SRC = $F2 | |||
| PP_LEN = $F4 | |||
| PP_IDX = $C0 | |||
| .export pp_start, pp_size | |||
| .export pp_off_text, pp_off_byte, pp_off_cr | |||
| .segment "CODE" | |||
| pp_start: | |||
| pp_text: | |||
| sta PP_SRC | |||
| stx PP_SRC+1 | |||
| sty PP_LEN | |||
| cpy #$00 | |||
| beq @done | |||
| lda #$00 | |||
| sta PP_IDX | |||
| @loop: ldy PP_IDX | |||
| lda (PP_SRC),y | |||
| jsr CHROUT | |||
| inc PP_IDX | |||
| lda PP_IDX | |||
| cmp PP_LEN | |||
| bne @loop | |||
| @done: rts | |||
| ; A = value, printed as 1..3 decimal digits with no leading zeros. | |||
| pp_byte: | |||
| sta PP_SRC | |||
| ldx #'0' | |||
| @hundreds: | |||
| lda PP_SRC | |||
| cmp #100 | |||
| bcc @h_done | |||
| sbc #100 | |||
| sta PP_SRC | |||
| inx | |||
| bne @hundreds | |||
| @h_done: | |||
| cpx #'0' | |||
| beq @tens | |||
| txa | |||
| jsr CHROUT | |||
| @tens: ldx #'0' | |||
| @ten_loop: | |||
| lda PP_SRC | |||
| cmp #10 | |||
| bcc @t_done | |||
| sbc #10 | |||
| sta PP_SRC | |||
| inx | |||
| bne @ten_loop | |||
| @t_done: | |||
| cpx #'0' | |||
| beq @units | |||
| txa | |||
| jsr CHROUT | |||
| @units: lda PP_SRC | |||
| clc | |||
| adc #'0' | |||
| jmp CHROUT | |||
| pp_cr: lda #$0D | |||
| jmp CHROUT | |||
| pp_end: | |||
| pp_size = pp_end - pp_start | |||
| pp_off_text = pp_text - pp_start | |||
| pp_off_byte = pp_byte - pp_start | |||
| pp_off_cr = pp_cr - pp_start | |||
| .assert pp_size < 256, error, "print runtime must fit one byte of length" | |||
| .assert pp_off_text < 256, error, "text offset must be one byte" | |||
| .assert pp_off_byte < 256, error, "byte offset must be one byte" | |||
| .assert pp_off_cr < 256, error, "cr offset must be one byte" | |||
Powered by TurnKey Linux.