浏览代码

Make print work in generated programs.

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
父节点
当前提交
221e0c5ac2
共有 9 个文件被更改,包括 482 次插入 和 51 次删除
  1. +7
    -4
      newLang/docs/architecture.md
  2. +2
    -0
      newLang/docs/decisions.md
  3. +5
    -5
      newLang/docs/editor-workflow.md
  4. +25
    -11
      newLang/planning/claims/E07-S05.md
  5. +1
    -0
      newLang/scripts/build-driver.ps1
  6. +93
    -18
      newLang/scripts/verify-driver-test.ps1
  7. +6
    -6
      newLang/src/compiler/emit.s
  8. +245
    -7
      newLang/src/compiler/main.s
  9. +98
    -0
      newLang/src/runtime/prgprint.s

+ 7
- 4
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



+ 2
- 0
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`/`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


+ 5
- 5
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



+ 25
- 11
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


+ 1
- 0
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) {


+ 93
- 18
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)"

+ 6
- 6
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
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


+ 245
- 7
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 ; 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

+ 98
- 0
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"

正在加载...
取消
保存

Powered by TurnKey Linux.