diff --git a/newLang/docs/architecture.md b/newLang/docs/architecture.md index e61d670..53fcdcd 100644 --- a/newLang/docs/architecture.md +++ b/newLang/docs/architecture.md @@ -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 diff --git a/newLang/docs/decisions.md b/newLang/docs/decisions.md index 4f0716f..a67bb39 100644 --- a/newLang/docs/decisions.md +++ b/newLang/docs/decisions.md @@ -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` 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 diff --git a/newLang/docs/editor-workflow.md b/newLang/docs/editor-workflow.md index e3ba785..8e17a01 100644 --- a/newLang/docs/editor-workflow.md +++ b/newLang/docs/editor-workflow.md @@ -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 diff --git a/newLang/planning/claims/E07-S05.md b/newLang/planning/claims/E07-S05.md index f8ef7c6..12f8faa 100644 --- a/newLang/planning/claims/E07-S05.md +++ b/newLang/planning/claims/E07-S05.md @@ -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 diff --git a/newLang/scripts/build-driver.ps1 b/newLang/scripts/build-driver.ps1 index c2dc48e..5ec7715 100644 --- a/newLang/scripts/build-driver.ps1 +++ b/newLang/scripts/build-driver.ps1 @@ -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) { diff --git a/newLang/scripts/verify-driver-test.ps1 b/newLang/scripts/verify-driver-test.ps1 index 25475fc..9cd24f0 100644 --- a/newLang/scripts/verify-driver-test.ps1 +++ b/newLang/scripts/verify-driver-test.ps1 @@ -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)" diff --git a/newLang/src/compiler/emit.s b/newLang/src/compiler/emit.s index a5b10fe..41734f5 100644 --- a/newLang/src/compiler/emit.s +++ b/newLang/src/compiler/emit.s @@ -526,23 +526,23 @@ em_print: lda gen_text_len jsr cg_ldy_imm bcs @fail - lda #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 + 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 + lda em_pr_cr + ldx em_pr_cr+1 jsr cg_jsr_abs bcs @fail clc diff --git a/newLang/src/compiler/main.s b/newLang/src/compiler/main.s index 13e79d9..9fc1f4c 100644 --- a/newLang/src/compiler/main.s +++ b/newLang/src/compiler/main.s @@ -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 + 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 #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+1 + lda #sec_stub + ldy #4 + jsr out_section + ON_CARRY @out_fail + lda #gen_stub + ldy #PRG_STUB_SIZE + jsr out_bytes + ON_CARRY @out_fail + jsr out_end_section + + lda #PRG_ENTRY_ADDR + sta out_start+1 + lda #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 + ldy #5 + jsr out_section + ON_CARRY @out_fail + lda #gen_startup + ldy #sec_print + ldy #5 + jsr out_section + ON_CARRY @out_fail + lda #pp_start + ldy #sec_data + ldy #4 + jsr out_section + ON_CARRY @out_fail + lda #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 + 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 + 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 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 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 diff --git a/newLang/src/runtime/prgprint.s b/newLang/src/runtime/prgprint.s new file mode 100644 index 0000000..8566bf2 --- /dev/null +++ b/newLang/src/runtime/prgprint.s @@ -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"