Emit a BASIC-stub PRG with ordered sections and a MAP entry/section table, refuse existing output names, and scratch partial files on failure. Include a runnable startup that reports through the test mailbox. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>main
| @@ -8,8 +8,9 @@ version: 0.1-draft | |||
| # Nova128 Compiler and Runtime Architecture | |||
| Status: approved version 1 baseline. The compiler memory map is frozen by | |||
| E01-S05 (section 4.4, `docs/memory-map.md`). Generated-program addresses are | |||
| still deliberately absent until the E04 stories validate them. | |||
| E01-S05 (section 4.4, `docs/memory-map.md`). E04-S01 defines the generated | |||
| PRG/MAP container and low-memory startup; the higher generated-program map | |||
| (banking, heap, software stack) remains open until E04-S02. | |||
| ## 1. System Boundary | |||
| @@ -47,7 +48,10 @@ C64 mode or the Z80. | |||
| | `NAME.MAP` | Symbols, addresses, class layouts, interface slots, and section sizes | | |||
| Output replacement must be explicit. The compiler must not silently overwrite | |||
| an existing PRG after a failed compilation. | |||
| an existing PRG after a failed compilation. E04-S01 therefore refuses an | |||
| existing `NAME.PRG` or `NAME.MAP` with E202; the user erases the old output | |||
| before rebuilding. Files created by the current run are scratched on abort or | |||
| failure. | |||
| ## 3. Repository Module Plan | |||
| @@ -162,6 +166,26 @@ speed bit is cleared before returning to BASIC, and any uncertain mode falls | |||
| back to 1 MHz. Direct VDC screen memory is display-only and is not compiler | |||
| semantic storage. See `docs/platform-display-clock.md` for evidence. | |||
| ### 2.3 Generated program container (E04-S01) | |||
| The generated PRG begins with load address `$1C01`, the 12-byte BASIC 7 stub | |||
| `10 SYS 7181`, and machine entry `$1C0D`. Sections are emitted in ascending | |||
| address order; a gap before a section is filled with zero bytes. The entry | |||
| point and each section's start/size are recorded in `NAME.MAP` as | |||
| CR-terminated text: | |||
| ```text | |||
| NOVA128 MAP 1 | |||
| ENTRY 1C0D | |||
| SECTION STUB 1C01 000C | |||
| SECTION START 1C0D 004A | |||
| ``` | |||
| `src/runtime/startup.s` supplies the fixed stub and startup bytes. The startup | |||
| initializes the test mailbox, reports producer `$03` and PASS, and returns to | |||
| BASIC. The test fixture keeps its sections below `$4000`, where BASIC's mapping | |||
| still exposes RAM; banking, heap, and software-stack placement are E04-S02. | |||
| ## 5. Compiler Passes | |||
| The compiler reparses a buffered/streamed source rather than retaining a large | |||
| @@ -277,6 +301,14 @@ memory. | |||
| ## 7. Generated Program ABI | |||
| ### 7.0 Container and entry (E04-S01) | |||
| - Load address `$1C01`; BASIC 7 stub `10 SYS 7181`; entry `$1C0D`. | |||
| - Sections are contiguous and ascending; gaps are zero-filled. | |||
| - `NAME.MAP` records `ENTRY` and every `SECTION name start size` in hex. | |||
| - The startup writes the mailbox with producer `$03`, then PASS, and returns | |||
| to BASIC. It does not yet install a runtime mapping or frames. | |||
| ### 7.1 Scalar representation | |||
| - Multi-byte values are little-endian. | |||
| @@ -54,6 +54,7 @@ documented default. `Proposed` means a later change still needs approval. | |||
| | D125 | An array index may be an integer or a value assignable to word; a constant index is also checked against the declared length. | Spec 3.3 gives arrays but not the index type. Byte indexes widen; an out-of-range constant is E089 at compile time. Implemented by `check_index` in `parser.s`. | | |||
| | D126 | Arrays are fields or locals only: never parameters, results, or whole values. A bare array is E088, and a parameter or result declaring one is E094. | Keeps array storage and copying out of the ABI (D108); indexing is the only way to read or write one. Implemented by `no_array_sig` and `ty_assign`. | | |||
| | D127 | `return` with a value in a program is E151. A method that declares a result must end every path in `return`; an `if` satisfies this only when both branches do, a `while` never does, and a missing return is reported at `end` as E150. | Spec 7 permits `return` but does not say what a program return means; a program has no caller or result. Deterministic and observable, and lets E04 emit a single exit path. Implemented by `close_block` and `set_ret`. | | |||
| | D128 | The generated PRG container loads at `$1C01` with the same 12-byte BASIC 7 stub as the compiler and enters at `$1C0D`. Sections follow in ascending address order; gaps are zero-filled. `NAME.MAP` is CR-terminated text: `NOVA128 MAP 1`, `ENTRY hhhh`, then `SECTION name start size` in hex. Existing `NAME.PRG`/`NAME.MAP` is refused with E202 instead of overwritten; outputs created by the run are scratched on abort or failure. The higher generated-program map (banking, heap, software stack) remains E04-S02. | Gives E04-S01 a deterministic, testable container while preserving a previously successful build. Refusing an existing output is explicit; the user must erase it before recompiling. Implemented by `output.s`, `startup.s`, and `formats.inc`. | | |||
| ## Deferred | |||
| @@ -117,6 +117,7 @@ After 20 errors the next one is replaced by E221 and compilation stops. | |||
| |---|---|---| | |||
| | E200 | `DSK_SOURCE_UNREADABLE` | CANNOT READ SOURCE; DOS % | | |||
| | E201 | `DSK_ERR_FILE_UNWRITABLE` | CANNOT WRITE ERROR FILE; DOS % | | |||
| | E202 | `DSK_OUTPUT_EXISTS` | OUTPUT FILE ALREADY EXISTS | | |||
| ## Resource limits (E220-E239) | |||
| @@ -5,8 +5,24 @@ Status: frozen for the compiler (D117, source cache D118); verified in VICE | |||
| x128 3.10 | |||
| Authoritative source: `cfg/compiler.cfg`. This document explains it. | |||
| The generated-program memory map is **not** frozen here. It belongs to E04-S01 | |||
| and E04-S02, which must reuse the test mailbox below. | |||
| E04-S01 defines the generated PRG/MAP container below; the higher | |||
| generated-program map (banking, heap, software stack) still belongs to E04-S02. | |||
| ## Generated Program Container (E04-S01) | |||
| | Element | Value | | |||
| |---|---| | |||
| | Load address | `$1C01` | | |||
| | BASIC 7 stub | 12 bytes, `10 SYS 7181` | | |||
| | Entry | `$1C0D` | | |||
| | Sections | ascending addresses, zero-filled gaps | | |||
| | MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size` | | |||
| | Startup | clears mailbox, producer `$03`, status PASS, then `RTS` | | |||
| 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. | |||
| Test sections stay below `$4000` until E04-S02 freezes the runtime mapping. | |||
| The generated program still uses the test mailbox below. | |||
| ## Mappings | |||
| @@ -0,0 +1,117 @@ | |||
| --- | |||
| story: E04-S01 | |||
| owner: e04_output | |||
| status: done | |||
| started: 2026-11-09 | |||
| depends-on: [E01-S05, E02-S04] | |||
| --- | |||
| # E04-S01 Claim | |||
| ## Intended change | |||
| Add the generated-program container and writer. `src/compiler/output.s` accepts | |||
| a source base name, writes a PRG with a valid load-address header and ordered | |||
| sections, records each section's start/size plus the entry point in a MAP file, | |||
| and emits the fixed BASIC 7 stub and startup path from | |||
| `src/runtime/startup.s`. The generated program initializes the test mailbox, | |||
| reports pass, and returns cleanly to BASIC. | |||
| An existing `NAME.PRG` or `NAME.MAP` is refused with the new disk diagnostic | |||
| E202 instead of being overwritten. Outputs created by the current run are | |||
| scratched if emission fails or the caller aborts, so no partial final file is | |||
| left behind. | |||
| ## File area | |||
| - `src/compiler/output.s` (new), `src/runtime/startup.s` (new) | |||
| - `src/common/formats.inc` (new), `src/common/diagcat.inc`, `docs/diagnostics.md` | |||
| - `tests/codegen/output-test.s` | |||
| - `scripts/build-output-test.ps1`, `scripts/verify-output-test.ps1` | |||
| - `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md` | |||
| - `planning/claims/E04-S01.md` | |||
| - generated files under `build/` | |||
| ## Acceptance evidence | |||
| - [x] Writes a valid load-address header and deterministic sections: the | |||
| generated `generate.prg` is 133 bytes, starts with `01 1C`, contains the | |||
| verified 12-byte `10 SYS 7181` stub at `$1C01`, 74 startup bytes at `$1C0D`, | |||
| zero padding through `$1C7F`, and `DE AD BE EF` at `$1C80`. | |||
| - [x] Emits a runnable startup path and clean termination: the extracted PRG | |||
| autostarts in x128, writes producer `$03`, status `$A5`, code 0, empty data, | |||
| and returns to BASIC. | |||
| - [x] Refuses/handles output-name collision according to the approved policy: | |||
| the pre-existing `collide.prg`/`collide.map` fixtures cause | |||
| `OUT_ERR_EXISTS`; both files remain byte-for-byte unchanged because nothing | |||
| is opened before the check. | |||
| - [x] Removes or marks partial output after any failure: the `order.prg` | |||
| section-order failure and the `abort.prg` partial-section run both call | |||
| `out_abort`; neither PRG nor MAP exists afterwards. | |||
| - [x] MAP records section start, size, and entry point: `generate.map` is the | |||
| exact CR-terminated sequence `NOVA128 MAP 1`, `ENTRY 1C0D`, | |||
| `SECTION STUB 1C01 000C`, `SECTION START 1C0D 004A`, | |||
| `SECTION PAD 1C80 0004`. | |||
| ## Coordination notes | |||
| - E01-S05 and E02-S04 are `done`; no other claim overlapped this area. | |||
| - D128 records the provisional container and refusal policy. E04-S02 still | |||
| owns the higher generated-program map (banking, heap, software stack); | |||
| this story's fixture keeps sections below `$4000` where BASIC's mapping | |||
| exposes RAM. | |||
| - `output.s` returns `OUT_ERR_EXISTS`; E202 is in the stable catalog for the | |||
| compiler shell (a later story) to report. No shell integration was invented | |||
| here. | |||
| - Production and abort paths use the existing `diskio.s` wrappers; no module | |||
| except `mmu.s` touches the MMU. | |||
| ## Work log | |||
| - Built the writer around a compact section-record table (name pointer, length, | |||
| start, size), a load-address header, and a MAP written after the PRG closes. | |||
| - First build failed because `out_rec_ptr` was in BSS; 6502 `(ptr),y` requires | |||
| zero page. Moved it to `ZEROPAGE`. | |||
| - Long branches in `out_begin` and `out_write_map` needed short-branch/`jmp` | |||
| trampolines. | |||
| - First x128 result was `OUT_ERR_ORDER`: the load-address header had advanced | |||
| `out_current` to `$1C03`, so the `$1C01` STUB section looked backward. The | |||
| writer now rewinds to `out_load` after the header. | |||
| - The next fixture exposed every PRG byte as `01`: `out_byte` loaded the section | |||
| flag into A before calling `out_put`. It now saves the byte first. | |||
| - Collision fixtures originally used PETSCII uppercase names while the writer | |||
| preserved the source's PETSCII lowercase names, so the writer legitimately | |||
| did not see them. Fixtures and host reads now use the same lowercase names. | |||
| - The abort fixture initially emitted a byte after `out_startup` had ended its | |||
| section, producing `OUT_ERR_STATE`; it now opens an explicit `X` section and | |||
| writes `$AA` before aborting. | |||
| - A stale parser-test x128 held port 6530 and caused empty/dropped monitor | |||
| replies. Verification identifies test-image processes and stops only those | |||
| before running. | |||
| ## Completion | |||
| - Tests run: | |||
| - `scripts/build-output-test.ps1` | |||
| - `scripts/verify-output-test.ps1 -Port 6530 -RunPort 6531` | |||
| - `scripts/build-diag-test.ps1 -UpdateDocs` | |||
| - `scripts/verify-diag-test.ps1 -Port 6530` | |||
| - `scripts/verify-parser-test.ps1 -Port 6530` | |||
| - `git diff --check` | |||
| - Results: writer fixture PASS with results finish=0, collision=1, order=2, | |||
| abort=0; PRG and MAP bytes match; generated PRG autostarts with producer 3 | |||
| and PASS; collision fixtures unchanged; abort/order outputs absent. | |||
| Diagnostics: catalog 71 entries, docs in sync, 19 screen rows and ERR files | |||
| exact. Parser: all 41 fixtures pass, lowest expression SP `$A7`. | |||
| - Files changed: `src/compiler/output.s`, `src/runtime/startup.s`, | |||
| `src/common/formats.inc`, `src/common/diagcat.inc`, `docs/diagnostics.md`, | |||
| `tests/codegen/output-test.s`, `scripts/build-output-test.ps1`, | |||
| `scripts/verify-output-test.ps1`, `docs/decisions.md`, | |||
| `docs/architecture.md`, `docs/memory-map.md`, this claim. | |||
| - Remaining risks: an existing output must be erased before recompiling | |||
| (refusal policy, D128); a power loss during direct PRG writing can leave a | |||
| partial file because there is no atomic rename; at most 8 sections and | |||
| 12-character section names; the MAP has no symbols/class/interface entries | |||
| yet; the startup is only the mailbox/termination path and does not install | |||
| a runtime mapping; sections above `$3FFF` are E04-S02 work; `out_load` is | |||
| caller-controlled rather than validated against the fixed container. | |||
| @@ -0,0 +1,29 @@ | |||
| # E04-S01: build the PRG/MAP writer test and a disk with collision fixtures. | |||
| [CmdletBinding()] | |||
| param() | |||
| $ErrorActionPreference = "Stop" | |||
| $project = Split-Path $PSScriptRoot -Parent | |||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| $out = Join-Path $project "build\output-test" | |||
| $link = Invoke-Nova128Link -Name "output-test" -BuildDir $out ` | |||
| -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( | |||
| (Join-Path $project "src\compiler\mmu.s"), | |||
| (Join-Path $project "src\compiler\diskio.s"), | |||
| (Join-Path $project "src\compiler\output.s"), | |||
| (Join-Path $project "src\runtime\startup.s"), | |||
| (Join-Path $project "tests\codegen\output-test.s")) | |||
| $oldPrg = Join-Path $out "old-collide.prg" | |||
| $oldMap = Join-Path $out "old-collide.map" | |||
| [IO.File]::WriteAllBytes($oldPrg, [byte[]](0x4B, 0x45, 0x45, 0x50)) # "KEEP" | |||
| [IO.File]::WriteAllBytes($oldMap, [byte[]](0x4B, 0x45, 0x45, 0x50, 0x4D, 0x41, 0x50)) # "KEEPMAP" | |||
| New-Nova128Disk -Path (Join-Path $out "output-test.d81") -Files @( | |||
| @{ Name = "OUTPUTTEST"; Path = $link.Program; Type = "prg" }, | |||
| @{ Name = "collide.prg"; Path = $oldPrg; Type = "prg" }, | |||
| @{ Name = "collide.map"; Path = $oldMap; Type = "seq" }) | |||
| Write-Host ("built {0}" -f $link.Program) | |||
| Write-Host ("disk {0}" -f (Join-Path $out "output-test.d81")) | |||
| @@ -0,0 +1,124 @@ | |||
| # E04-S01: run the PRG/MAP writer test in x128, inspect the disk artifacts, | |||
| # and autostart the generated PRG to prove the startup path and termination. | |||
| [CmdletBinding()] | |||
| param( | |||
| [int]$Port = 6530, | |||
| [int]$RunPort = 6531, | |||
| [int]$TimeoutSeconds = 90 | |||
| ) | |||
| $ErrorActionPreference = "Stop" | |||
| $project = Split-Path $PSScriptRoot -Parent | |||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| & (Join-Path $PSScriptRoot "build-output-test.ps1") | |||
| $out = Join-Path $project "build\output-test" | |||
| $disk = Join-Path $out "output-test.d81" | |||
| $c1541 = Get-ViceTool "c1541" | |||
| $session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port ` | |||
| -Screenshot (Join-Path $out "output-test-vicii.png") | |||
| try { | |||
| $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds | |||
| } finally { | |||
| Stop-Nova128X128 $session | |||
| } | |||
| Write-Host ('mailbox: ' + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' ')) | |||
| Write-Host ('results: finish={0} collision={1} order={2} abort={3}' -f $mb[8], $mb[9], $mb[10], $mb[11]) | |||
| $failures = @() | |||
| if ($mb[2] -ne 0x01) { $failures += "producer $($mb[2]) is not the platform test" } | |||
| if ($mb[3] -ne 0xA5) { $failures += "fixture status is not PASS" } | |||
| $want = @(0, 1, 2, 0) | |||
| for ($i = 0; $i -lt $want.Count; $i++) { | |||
| if ($mb[8 + $i] -ne $want[$i]) { | |||
| $failures += "result $i is $($mb[8 + $i]), expected $($want[$i])" | |||
| } | |||
| } | |||
| if ($mb[4] -ne 0 -or $mb[5] -ne 0) { | |||
| $failures += "code is $($mb[4] + 256 * $mb[5])" | |||
| } | |||
| function Read-DiskFile([string]$Name, [string]$Dest, [bool]$Seq) { | |||
| if (Test-Path -LiteralPath $Dest) { Remove-Item -LiteralPath $Dest -Force } | |||
| $spec = if ($Seq) { "$Name,s" } else { $Name } | |||
| & $c1541 -attach $disk -read $spec $Dest 2>&1 | Out-Null | |||
| if (-not (Test-Path -LiteralPath $Dest)) { throw "could not read $Name from the test disk" } | |||
| } | |||
| $prgPath = Join-Path $out "generate.prg" | |||
| $mapPath = Join-Path $out "generate.map" | |||
| $collidePrg = Join-Path $out "collide-check.prg" | |||
| $collideMap = Join-Path $out "collide-check.map" | |||
| Read-DiskFile "generate.prg" $prgPath $false | |||
| Read-DiskFile "generate.map" $mapPath $true | |||
| Read-DiskFile "collide.prg" $collidePrg $false | |||
| Read-DiskFile "collide.map" $collideMap $true | |||
| $bytes = [IO.File]::ReadAllBytes($prgPath) | |||
| if ($bytes.Length -ne 133) { $failures += "GENERATE.PRG is $($bytes.Length) bytes, expected 133" } | |||
| if ($bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) { | |||
| $failures += ("load address is ${0:X2}{1:X2}, expected 011C" -f $bytes[1], $bytes[0]) | |||
| } | |||
| $stub = [byte[]](0x0B, 0x1C, 0x0A, 0x00, 0x9E, 0x37, 0x31, 0x38, 0x31, 0x00, 0x00, 0x00) | |||
| for ($i = 0; $i -lt $stub.Length; $i++) { | |||
| if ($bytes[2 + $i] -ne $stub[$i]) { | |||
| $failures += ("BASIC stub byte {0} is {1:X2}, expected {2:X2}" -f $i, $bytes[2 + $i], $stub[$i]) | |||
| } | |||
| } | |||
| $padOffset = 2 + (0x1C80 - 0x1C01) | |||
| $pad = @(0xDE, 0xAD, 0xBE, 0xEF) | |||
| for ($i = 0; $i -lt $pad.Count; $i++) { | |||
| if ($bytes[$padOffset + $i] -ne $pad[$i]) { | |||
| $failures += ("PAD byte {0} is {1:X2}, expected {2:X2}" -f $i, $bytes[$padOffset + $i], $pad[$i]) | |||
| } | |||
| } | |||
| for ($i = 2 + 12 + 80; $i -lt $padOffset; $i++) { | |||
| if ($bytes[$i] -ne 0) { $failures += "gap byte at $i is not zero"; break } | |||
| } | |||
| $mapLines = ([IO.File]::ReadAllText($mapPath)).TrimEnd("`r", "`n") -split "`r`n|`r|`n" | |||
| $expectedMap = @( | |||
| "NOVA128 MAP 1", | |||
| "ENTRY 1C0D", | |||
| "SECTION STUB 1C01 000C", | |||
| "SECTION START 1C0D 004A", | |||
| "SECTION PAD 1C80 0004") | |||
| if (($mapLines -join "`n") -ne ($expectedMap -join "`n")) { | |||
| $failures += ("GENERATE.MAP differs:`n got: " + ($mapLines -join "`n ") + | |||
| "`n want: " + ($expectedMap -join "`n ")) | |||
| } | |||
| $keep = [Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($collidePrg)) | |||
| if ($keep -cne "KEEP") { $failures += "COLLIDE.PRG changed to '$keep'" } | |||
| $keepMap = [Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($collideMap)).TrimEnd("`r", "`n") | |||
| if ($keepMap -cne "KEEPMAP") { $failures += "COLLIDE.MAP changed to '$keepMap'" } | |||
| $listing = (& $c1541 -attach $disk -list 2>&1) -join "`n" | |||
| foreach ($absent in @("abort.prg", "abort.map", "order.prg", "order.map")) { | |||
| if ($listing -match ('"' + [regex]::Escape($absent) + '"')) { | |||
| $failures += "$absent exists after abort/failure" | |||
| } | |||
| } | |||
| if ($failures) { throw ("output verification failed:`n " + ($failures -join "`n ")) } | |||
| # ---------------------------------------------------------- generated run | |||
| $run = Start-Nova128X128 -Autostart $prgPath -Port $RunPort | |||
| try { | |||
| $gmb = Wait-Nova128Mailbox $run -TimeoutSeconds $TimeoutSeconds | |||
| } finally { | |||
| Stop-Nova128X128 $run | |||
| } | |||
| if ($gmb[2] -ne 0x03) { throw "generated producer is $($gmb[2]), expected 3" } | |||
| if ($gmb[3] -ne 0xA5) { throw "generated program did not report PASS" } | |||
| if ($gmb[4] -ne 0 -or $gmb[5] -ne 0) { throw "generated code is $($gmb[4] + 256 * $gmb[5])" } | |||
| for ($i = 8; $i -lt 16; $i++) { | |||
| if ($gmb[$i] -ne 0) { throw "generated data byte $i is not zero" } | |||
| } | |||
| Write-Host "verified PRG header, BASIC stub, PAD section, and MAP:" | |||
| $expectedMap | ForEach-Object { Write-Host " |$_" } | |||
| Write-Host "COLLIDE.PRG/MAP unchanged; ABORT/ORDER left no output" | |||
| Write-Host "generated PRG autostarted: producer 3, PASS, code 0" | |||
| Write-Host "PRG bytes: $($bytes.Length); screenshots not requested" | |||
| @@ -94,6 +94,7 @@ | |||
| ; ------------------------------------------------------------------- disk | |||
| DIAGNOSTIC DSK_SOURCE_UNREADABLE, 200, "CANNOT READ SOURCE; DOS %" | |||
| DIAGNOSTIC DSK_ERR_FILE_UNWRITABLE, 201, "CANNOT WRITE ERROR FILE; DOS %" | |||
| DIAGNOSTIC DSK_OUTPUT_EXISTS, 202, "OUTPUT FILE ALREADY EXISTS" | |||
| ; ------------------------------------------------------- resource limits | |||
| DIAGNOSTIC RES_TOO_MANY_NAMES, 220, "MORE THAN 255 NAMES" | |||
| @@ -0,0 +1,27 @@ | |||
| ; formats.inc - generated-program container constants (E04-S01). | |||
| ; | |||
| ; The container is deliberately small: a C128 BASIC 7 stub at $1C01, the | |||
| ; machine entry at $1C0D, and contiguous sections loaded in ascending order. | |||
| ; The higher generated-program map (banking, heap, software stack) is E04-S02. | |||
| .ifndef FORMATS_INC | |||
| FORMATS_INC = 1 | |||
| PRG_LOAD_ADDR = $1C01 | |||
| PRG_ENTRY_ADDR = $1C0D | |||
| PRG_STUB_SIZE = 12 | |||
| OUT_MAX_SECTIONS = 8 | |||
| OUT_SECTION_NAME_MAX = 12 | |||
| OUT_RECORD_SIZE = 7 ; name ptr(2), name len(1), start(2), size(2) | |||
| ; out_begin/out_finish/out_abort result codes (out_err). | |||
| OUT_OK = 0 | |||
| OUT_ERR_EXISTS = 1 ; NAME.PRG or NAME.MAP already exists | |||
| OUT_ERR_ORDER = 2 ; section starts below the current address | |||
| OUT_ERR_SECTIONS = 3 ; more than OUT_MAX_SECTIONS | |||
| OUT_ERR_NAME = 4 ; base or section name too long | |||
| OUT_ERR_DISK = 5 ; KERNAL or DOS failure | |||
| OUT_ERR_STATE = 6 ; byte emitted outside a section | |||
| .endif | |||
| @@ -0,0 +1,855 @@ | |||
| ; output.s - generated PRG and MAP writer (E04-S01). | |||
| ; | |||
| ; Container (D128): | |||
| ; PRG starts with the load address PRG_LOAD_ADDR, then ordered sections. | |||
| ; A gap before a section is filled with zero bytes. The MAP is PETSCII text: | |||
| ; | |||
| ; NOVA128 MAP 1 | |||
| ; ENTRY hhhh | |||
| ; SECTION name start size | |||
| ; | |||
| ; API (all calls in the bank-0 compiler state): | |||
| ; out_begin A/X = source name (bank 0), Y = length. Sets out_load and | |||
| ; out_entry first. Refuses an existing NAME.PRG or NAME.MAP | |||
| ; with OUT_ERR_EXISTS; no existing output is overwritten. | |||
| ; out_startup Emits the BASIC 7 stub at $1C01 and the startup code at | |||
| ; $1C0D from src/runtime/startup.s. | |||
| ; out_section A/X = section name, Y = length; out_start = start address. | |||
| ; out_byte A = byte; requires an open section. | |||
| ; out_bytes A/X = data, Y = count (0-255). | |||
| ; out_end_section | |||
| ; out_finish Writes the MAP, closes both files, and returns C=0 on success. | |||
| ; out_abort Closes and scratches only files created by this run. | |||
| ; | |||
| ; Every failure sets out_err and returns C=1; the caller must call out_abort. | |||
| ; DOS/KERNAL errors are OUT_ERR_DISK; the detailed DOS code is not retained. | |||
| .setcpu "6502" | |||
| .include "../common/c128.inc" | |||
| .include "../common/diskio.inc" | |||
| .include "../common/formats.inc" | |||
| .import dio_open_cmd, dio_open_in, dio_open_out, dio_read_status | |||
| .import dio_chkout, dio_putc, dio_clrchn, dio_close, dio_command, dio_dos_code | |||
| .import gen_stub, gen_startup, gen_startup_size | |||
| .export out_begin, out_startup, out_section, out_end_section | |||
| .export out_byte, out_bytes, out_finish, out_abort | |||
| .export out_load, out_entry, out_start, out_err | |||
| .segment "ZEROPAGE" | |||
| out_dst: .res 2 | |||
| out_srcp: .res 2 | |||
| out_rec_ptr: .res 2 | |||
| .segment "CODE" | |||
| ; Build one name from out_base plus the suffix at out_srcp (out_suffix_len). | |||
| ; out_dst is advanced to the end; out_name_len is the total length. | |||
| build_name: | |||
| ldy #$00 | |||
| @base: cpy out_base_len | |||
| beq @suffix | |||
| lda out_base,y | |||
| sta (out_dst),y | |||
| iny | |||
| bne @base | |||
| @suffix: | |||
| sty out_tmp | |||
| clc | |||
| lda out_dst | |||
| adc out_tmp | |||
| sta out_dst | |||
| bcc :+ | |||
| inc out_dst+1 | |||
| : ldy #$00 | |||
| @sl: cpy out_suffix_len | |||
| beq @done | |||
| lda (out_srcp),y | |||
| sta (out_dst),y | |||
| iny | |||
| bne @sl | |||
| @done: lda out_base_len | |||
| clc | |||
| adc out_suffix_len | |||
| sta out_name_len | |||
| rts | |||
| .macro MAKE_NAME dst, suffix, slen, lenvar | |||
| lda #<dst | |||
| sta out_dst | |||
| lda #>dst | |||
| sta out_dst+1 | |||
| lda #<suffix | |||
| sta out_srcp | |||
| lda #>suffix | |||
| sta out_srcp+1 | |||
| lda #slen | |||
| sta out_suffix_len | |||
| jsr build_name | |||
| lda out_name_len | |||
| sta lenvar | |||
| .endmacro | |||
| ; Append out_append_len bytes from out_srcp to out_dst. | |||
| append_bytes: | |||
| ldy #$00 | |||
| @loop: cpy out_append_len | |||
| beq @done | |||
| lda (out_srcp),y | |||
| sta (out_dst),y | |||
| iny | |||
| bne @loop | |||
| @done: tya | |||
| clc | |||
| adc out_dst | |||
| sta out_dst | |||
| bcc :+ | |||
| inc out_dst+1 | |||
| : rts | |||
| ; out_rec_ptr = out_sections + out_index * OUT_RECORD_SIZE. | |||
| set_rec_ptr: | |||
| lda #<out_sections | |||
| sta out_rec_ptr | |||
| lda #>out_sections | |||
| sta out_rec_ptr+1 | |||
| ldx out_index | |||
| beq @done | |||
| @add: clc | |||
| lda out_rec_ptr | |||
| adc #OUT_RECORD_SIZE | |||
| sta out_rec_ptr | |||
| bcc :+ | |||
| inc out_rec_ptr+1 | |||
| : dex | |||
| bne @add | |||
| @done: rts | |||
| ; Build and send "S0:name" on the command channel. | |||
| out_scratch: | |||
| sta out_scratch_ptr | |||
| stx out_scratch_ptr+1 | |||
| sty out_scratch_len | |||
| lda #<out_cmd | |||
| sta out_dst | |||
| lda #>out_cmd | |||
| sta out_dst+1 | |||
| lda #<scratch_prefix | |||
| sta out_srcp | |||
| lda #>scratch_prefix | |||
| sta out_srcp+1 | |||
| lda #3 | |||
| sta out_append_len | |||
| jsr append_bytes | |||
| lda out_scratch_ptr | |||
| sta out_srcp | |||
| lda out_scratch_ptr+1 | |||
| sta out_srcp+1 | |||
| lda out_scratch_len | |||
| sta out_append_len | |||
| jsr append_bytes | |||
| lda out_scratch_len | |||
| clc | |||
| adc #3 | |||
| sta out_cmd_len | |||
| lda #<out_cmd | |||
| ldx #>out_cmd | |||
| ldy out_cmd_len | |||
| jsr dio_command | |||
| bcs @disk | |||
| lda dio_dos_code | |||
| cmp #20 | |||
| bcc @ok | |||
| cmp #62 ; already absent is harmless | |||
| beq @ok | |||
| @disk: lda #OUT_ERR_DISK | |||
| sta out_err | |||
| sec | |||
| rts | |||
| @ok: clc | |||
| rts | |||
| ; Test A/X name, length Y. C=1 if it exists (out_err=0) or the drive failed | |||
| ; (out_err=OUT_ERR_DISK); C=0 if it does not exist. | |||
| out_exists: | |||
| jsr dio_open_in | |||
| bcs @disk | |||
| jsr dio_read_status | |||
| bcs @disk | |||
| lda dio_dos_code | |||
| cmp #62 | |||
| beq @absent | |||
| cmp #20 | |||
| bcs @disk | |||
| lda #DIO_LFN_IN | |||
| jsr dio_close | |||
| lda #OUT_OK | |||
| sta out_err | |||
| sec | |||
| rts | |||
| @absent: | |||
| lda #DIO_LFN_IN | |||
| jsr dio_close | |||
| lda #OUT_OK | |||
| sta out_err | |||
| clc | |||
| rts | |||
| @disk: | |||
| lda #DIO_LFN_IN | |||
| jsr dio_close | |||
| lda #OUT_ERR_DISK | |||
| sta out_err | |||
| sec | |||
| rts | |||
| out_begin: | |||
| sta out_srcp | |||
| stx out_srcp+1 | |||
| sty out_name_len | |||
| lda #$00 | |||
| sta out_count | |||
| sta out_in_section | |||
| sta out_prg_created | |||
| sta out_map_created | |||
| sta out_cmd_open | |||
| sta out_prg_is_open | |||
| sta out_map_is_open | |||
| sta out_err | |||
| lda out_load | |||
| sta out_current | |||
| lda out_load+1 | |||
| sta out_current+1 | |||
| lda out_name_len | |||
| sta out_base_len | |||
| cmp #4 | |||
| bcc @base_ready | |||
| ldy out_name_len | |||
| dey | |||
| lda (out_srcp),y | |||
| cmp #'8' | |||
| bne @base_ready | |||
| dey | |||
| lda (out_srcp),y | |||
| and #$7F | |||
| cmp #'N' | |||
| bne @base_ready | |||
| dey | |||
| lda (out_srcp),y | |||
| cmp #'.' | |||
| bne @base_ready | |||
| lda out_name_len | |||
| sec | |||
| sbc #3 | |||
| sta out_base_len | |||
| @base_ready: | |||
| lda out_base_len | |||
| bne @have_base | |||
| jmp @bad_name | |||
| @have_base: | |||
| cmp #13 | |||
| bcc @base_ok | |||
| jmp @bad_name | |||
| @base_ok: | |||
| ldy #$00 | |||
| @copy: cpy out_base_len | |||
| beq @copied | |||
| lda (out_srcp),y | |||
| sta out_base,y | |||
| iny | |||
| bne @copy | |||
| @copied: | |||
| MAKE_NAME out_prg_name, sfx_prg, 4, out_prg_len | |||
| MAKE_NAME out_map_name, sfx_map, 4, out_map_len | |||
| MAKE_NAME out_prg_open, sfx_prg_open, 8, out_prg_open_len | |||
| MAKE_NAME out_map_open, sfx_map_open, 8, out_map_open_len | |||
| MAKE_NAME out_prg_read, sfx_prg_read, 8, out_prg_read_len | |||
| MAKE_NAME out_map_read, sfx_map_read, 8, out_map_read_len | |||
| jsr dio_open_cmd | |||
| bcc :+ | |||
| jmp @disk_fail | |||
| : | |||
| lda #1 | |||
| sta out_cmd_open | |||
| lda #<out_prg_read | |||
| ldx #>out_prg_read | |||
| ldy out_prg_read_len | |||
| jsr out_exists | |||
| bcc @check_map | |||
| lda out_err | |||
| bne @close_fail | |||
| jmp @exists | |||
| @check_map: | |||
| lda #<out_map_read | |||
| ldx #>out_map_read | |||
| ldy out_map_read_len | |||
| jsr out_exists | |||
| bcc @open_prg | |||
| lda out_err | |||
| bne @close_fail | |||
| jmp @exists | |||
| @exists: | |||
| lda #OUT_ERR_EXISTS | |||
| sta out_err | |||
| jmp @close_fail | |||
| @open_prg: | |||
| lda #1 | |||
| sta out_prg_is_open | |||
| sta out_prg_created | |||
| lda #<out_prg_open | |||
| ldx #>out_prg_open | |||
| ldy out_prg_open_len | |||
| jsr dio_open_out | |||
| bcs @fail_created | |||
| jsr dio_read_status | |||
| bcs @fail_created | |||
| lda dio_dos_code | |||
| cmp #20 | |||
| bcs @fail_created | |||
| ldx #DIO_LFN_ERR | |||
| jsr dio_chkout | |||
| bcs @fail_created | |||
| lda out_load | |||
| jsr out_put | |||
| bcs @fail_created | |||
| lda out_load+1 | |||
| jsr out_put | |||
| bcs @fail_created | |||
| lda out_load ; the header is not part of section 0 | |||
| sta out_current | |||
| lda out_load+1 | |||
| sta out_current+1 | |||
| clc | |||
| rts | |||
| @bad_name: | |||
| lda #OUT_ERR_NAME | |||
| sta out_err | |||
| sec | |||
| rts | |||
| @disk_fail: | |||
| lda #OUT_ERR_DISK | |||
| sta out_err | |||
| sec | |||
| rts | |||
| @close_fail: | |||
| jsr out_close_cmd | |||
| sec | |||
| rts | |||
| @fail_created: | |||
| lda #OUT_ERR_DISK | |||
| sta out_err | |||
| jsr out_cleanup | |||
| sec | |||
| rts | |||
| out_startup: | |||
| lda #<PRG_LOAD_ADDR | |||
| sta out_start | |||
| lda #>PRG_LOAD_ADDR | |||
| sta out_start+1 | |||
| lda #<stub_name | |||
| ldx #>stub_name | |||
| ldy #4 | |||
| jsr out_section | |||
| bcs @out | |||
| lda #<gen_stub | |||
| ldx #>gen_stub | |||
| ldy #PRG_STUB_SIZE | |||
| jsr out_bytes | |||
| bcs @out | |||
| jsr out_end_section | |||
| lda #<PRG_ENTRY_ADDR | |||
| sta out_start | |||
| lda #>PRG_ENTRY_ADDR | |||
| sta out_start+1 | |||
| lda #<start_name | |||
| ldx #>start_name | |||
| ldy #5 | |||
| jsr out_section | |||
| bcs @out | |||
| lda #<gen_startup | |||
| ldx #>gen_startup | |||
| ldy #<gen_startup_size | |||
| jsr out_bytes | |||
| bcs @out | |||
| jmp out_end_section | |||
| @out: rts | |||
| out_section: | |||
| sta out_tmp | |||
| stx out_tmp+1 | |||
| sty out_tmp_len | |||
| lda out_in_section | |||
| beq @no_prev | |||
| jsr out_end_section | |||
| @no_prev: | |||
| lda out_count | |||
| cmp #OUT_MAX_SECTIONS | |||
| bcs @too_many | |||
| lda out_tmp_len | |||
| cmp #OUT_SECTION_NAME_MAX + 1 | |||
| bcs @bad_name | |||
| lda out_count | |||
| sta out_index | |||
| jsr set_rec_ptr | |||
| ldy #0 | |||
| lda out_tmp | |||
| sta (out_rec_ptr),y | |||
| iny | |||
| lda out_tmp+1 | |||
| sta (out_rec_ptr),y | |||
| iny | |||
| lda out_tmp_len | |||
| sta (out_rec_ptr),y | |||
| iny | |||
| lda out_start | |||
| sta (out_rec_ptr),y | |||
| iny | |||
| lda out_start+1 | |||
| sta (out_rec_ptr),y | |||
| iny | |||
| lda #$00 | |||
| sta (out_rec_ptr),y | |||
| iny | |||
| sta (out_rec_ptr),y | |||
| lda out_current+1 | |||
| cmp out_start+1 | |||
| bcc @pad | |||
| bne @order | |||
| lda out_current | |||
| cmp out_start | |||
| bcc @pad | |||
| bne @order | |||
| beq @at_start | |||
| @pad: lda out_current+1 | |||
| cmp out_start+1 | |||
| bcc @pad_byte | |||
| bne @at_start | |||
| lda out_current | |||
| cmp out_start | |||
| bcs @at_start | |||
| @pad_byte: | |||
| lda #$00 | |||
| jsr out_put | |||
| bcs @out | |||
| jmp @pad | |||
| @at_start: | |||
| lda #1 | |||
| sta out_in_section | |||
| inc out_count | |||
| clc | |||
| rts | |||
| @order: | |||
| lda #OUT_ERR_ORDER | |||
| sta out_err | |||
| sec | |||
| rts | |||
| @too_many: | |||
| lda #OUT_ERR_SECTIONS | |||
| sta out_err | |||
| sec | |||
| rts | |||
| @bad_name: | |||
| lda #OUT_ERR_NAME | |||
| sta out_err | |||
| sec | |||
| @out: rts | |||
| out_end_section: | |||
| lda out_in_section | |||
| beq @ok | |||
| ldy #5 | |||
| lda out_current | |||
| sec | |||
| sbc out_start | |||
| sta (out_rec_ptr),y | |||
| iny | |||
| lda out_current+1 | |||
| sbc out_start+1 | |||
| sta (out_rec_ptr),y | |||
| lda #$00 | |||
| sta out_in_section | |||
| @ok: clc | |||
| rts | |||
| out_byte: | |||
| sta out_byte_value | |||
| lda out_in_section | |||
| bne @write | |||
| lda #OUT_ERR_STATE | |||
| sta out_err | |||
| sec | |||
| rts | |||
| @write: lda out_byte_value | |||
| jmp out_put | |||
| ; A = byte, no section check. Preserves Y; C=1 on write failure. | |||
| out_put: | |||
| sty out_y | |||
| jsr dio_putc | |||
| bcs @fail | |||
| inc out_current | |||
| bne @ok | |||
| inc out_current+1 | |||
| @ok: ldy out_y | |||
| clc | |||
| rts | |||
| @fail: | |||
| lda #OUT_ERR_DISK | |||
| sta out_err | |||
| ldy out_y | |||
| sec | |||
| rts | |||
| out_bytes: | |||
| sta out_srcp | |||
| stx out_srcp+1 | |||
| sty out_append_len | |||
| ldy #$00 | |||
| @loop: cpy out_append_len | |||
| beq @done | |||
| lda (out_srcp),y | |||
| sty out_y | |||
| jsr out_byte | |||
| bcs @out | |||
| ldy out_y | |||
| iny | |||
| bne @loop | |||
| @done: clc | |||
| @out: rts | |||
| out_finish: | |||
| jsr out_end_section | |||
| jsr out_close_prg | |||
| lda #1 | |||
| sta out_map_is_open | |||
| sta out_map_created | |||
| lda #<out_map_open | |||
| ldx #>out_map_open | |||
| ldy out_map_open_len | |||
| jsr dio_open_out | |||
| bcs @fail | |||
| jsr dio_read_status | |||
| bcs @fail | |||
| lda dio_dos_code | |||
| cmp #20 | |||
| bcs @fail | |||
| ldx #DIO_LFN_ERR | |||
| jsr dio_chkout | |||
| bcs @fail | |||
| jsr out_write_map | |||
| bcs @fail | |||
| jsr out_close_map | |||
| jsr out_close_cmd | |||
| lda #OUT_OK | |||
| sta out_err | |||
| lda #$00 | |||
| sta out_prg_created | |||
| sta out_map_created | |||
| clc | |||
| rts | |||
| @fail: | |||
| lda out_err | |||
| bne :+ | |||
| lda #OUT_ERR_DISK | |||
| sta out_err | |||
| : jsr out_cleanup | |||
| sec | |||
| rts | |||
| out_abort: | |||
| lda #OUT_OK | |||
| sta out_err | |||
| jsr out_cleanup | |||
| lda out_err | |||
| beq @ok | |||
| sec | |||
| rts | |||
| @ok: clc | |||
| rts | |||
| ; Close created outputs and scratch only files this run created. | |||
| out_cleanup: | |||
| jsr out_close_prg | |||
| jsr out_close_map | |||
| lda out_cmd_open | |||
| bne @have_cmd | |||
| jsr dio_open_cmd | |||
| bcs @done | |||
| lda #1 | |||
| sta out_cmd_open | |||
| @have_cmd: | |||
| lda out_prg_created | |||
| beq @map | |||
| lda #<out_prg_name | |||
| ldx #>out_prg_name | |||
| ldy out_prg_len | |||
| jsr out_scratch | |||
| @map: lda out_map_created | |||
| beq @close | |||
| lda #<out_map_name | |||
| ldx #>out_map_name | |||
| ldy out_map_len | |||
| jsr out_scratch | |||
| @close: | |||
| jsr out_close_cmd | |||
| @done: | |||
| lda #$00 | |||
| sta out_prg_created | |||
| sta out_map_created | |||
| rts | |||
| out_close_prg: | |||
| lda out_prg_is_open | |||
| beq @ok | |||
| jsr dio_clrchn | |||
| lda #DIO_LFN_ERR | |||
| jsr dio_close | |||
| lda #$00 | |||
| sta out_prg_is_open | |||
| @ok: clc | |||
| rts | |||
| out_close_map: | |||
| lda out_map_is_open | |||
| beq @ok | |||
| jsr dio_clrchn | |||
| lda #DIO_LFN_ERR | |||
| jsr dio_close | |||
| lda #$00 | |||
| sta out_map_is_open | |||
| @ok: clc | |||
| rts | |||
| out_close_cmd: | |||
| lda out_cmd_open | |||
| beq @ok | |||
| lda #DIO_LFN_CMD | |||
| jsr dio_close | |||
| lda #$00 | |||
| sta out_cmd_open | |||
| @ok: clc | |||
| rts | |||
| ; ---------------------------------------------------------------- MAP text | |||
| out_write_map: | |||
| lda #<map_header | |||
| ldx #>map_header | |||
| ldy #map_header_len | |||
| jsr out_text | |||
| bcc :+ | |||
| jmp @out | |||
| : | |||
| lda #<map_entry | |||
| ldx #>map_entry | |||
| ldy #map_entry_len | |||
| jsr out_text | |||
| bcc :+ | |||
| jmp @out | |||
| : | |||
| lda out_entry | |||
| sta out_num | |||
| lda out_entry+1 | |||
| sta out_num+1 | |||
| jsr out_hex4 | |||
| bcc :+ | |||
| jmp @out | |||
| : | |||
| jsr out_cr | |||
| bcc :+ | |||
| jmp @out | |||
| : | |||
| lda #$00 | |||
| sta out_index | |||
| @section: | |||
| lda out_index | |||
| cmp out_count | |||
| beq @done | |||
| jsr set_rec_ptr | |||
| lda #<section_prefix | |||
| ldx #>section_prefix | |||
| ldy #section_prefix_len | |||
| jsr out_text | |||
| bcs @out | |||
| ldy #0 | |||
| lda (out_rec_ptr),y | |||
| sta out_srcp | |||
| iny | |||
| lda (out_rec_ptr),y | |||
| sta out_srcp+1 | |||
| iny | |||
| lda (out_rec_ptr),y | |||
| sta out_text_len | |||
| ldy #0 | |||
| @name: cpy out_text_len | |||
| beq @named | |||
| lda (out_srcp),y | |||
| sty out_y | |||
| jsr out_char | |||
| bcs @out | |||
| ldy out_y | |||
| iny | |||
| bne @name | |||
| @named: | |||
| lda #' ' | |||
| jsr out_char | |||
| bcs @out | |||
| ldy #3 | |||
| lda (out_rec_ptr),y | |||
| sta out_num | |||
| iny | |||
| lda (out_rec_ptr),y | |||
| sta out_num+1 | |||
| jsr out_hex4 | |||
| bcs @out | |||
| lda #' ' | |||
| jsr out_char | |||
| bcs @out | |||
| ldy #5 | |||
| lda (out_rec_ptr),y | |||
| sta out_num | |||
| iny | |||
| lda (out_rec_ptr),y | |||
| sta out_num+1 | |||
| jsr out_hex4 | |||
| bcs @out | |||
| jsr out_cr | |||
| bcs @out | |||
| inc out_index | |||
| jmp @section | |||
| @done: clc | |||
| @out: rts | |||
| ; A/X = text, Y = length. | |||
| out_text: | |||
| sta out_srcp | |||
| stx out_srcp+1 | |||
| sty out_text_len | |||
| ldy #$00 | |||
| @loop: cpy out_text_len | |||
| beq @done | |||
| lda (out_srcp),y | |||
| sty out_y | |||
| jsr out_char | |||
| bcs @out | |||
| ldy out_y | |||
| iny | |||
| bne @loop | |||
| @done: clc | |||
| @out: rts | |||
| out_cr: | |||
| lda #$0D | |||
| jmp out_char | |||
| out_char: | |||
| sty out_y | |||
| jsr dio_putc | |||
| bcs @fail | |||
| ldy out_y | |||
| clc | |||
| rts | |||
| @fail: | |||
| lda #OUT_ERR_DISK | |||
| sta out_err | |||
| ldy out_y | |||
| sec | |||
| rts | |||
| out_hex4: | |||
| lda out_num+1 | |||
| jsr out_hex_byte | |||
| bcs @out | |||
| lda out_num | |||
| jmp out_hex_byte | |||
| @out: rts | |||
| out_hex_byte: | |||
| pha | |||
| lsr a | |||
| lsr a | |||
| lsr a | |||
| lsr a | |||
| tax | |||
| lda hex_digits,x | |||
| jsr out_char | |||
| bcs @fail | |||
| pla | |||
| and #$0F | |||
| tax | |||
| lda hex_digits,x | |||
| jmp out_char | |||
| @fail: pla | |||
| sec | |||
| rts | |||
| .segment "RODATA" | |||
| sfx_prg: .byte ".PRG" | |||
| sfx_map: .byte ".MAP" | |||
| sfx_prg_open: .byte ".PRG,P,W" | |||
| sfx_map_open: .byte ".MAP,S,W" | |||
| sfx_prg_read: .byte ".PRG,P,R" | |||
| sfx_map_read: .byte ".MAP,S,R" | |||
| stub_name: .byte "STUB" | |||
| start_name: .byte "START" | |||
| scratch_prefix: .byte "S0:" | |||
| map_header: .byte "NOVA128 MAP 1", $0D | |||
| map_header_len = * - map_header | |||
| map_entry: .byte "ENTRY " | |||
| map_entry_len = * - map_entry | |||
| section_prefix: .byte "SECTION " | |||
| section_prefix_len = * - section_prefix | |||
| hex_digits: .byte "0123456789ABCDEF" | |||
| .segment "BSS" | |||
| out_base: .res 12 | |||
| out_base_len: .res 1 | |||
| out_name_len: .res 1 | |||
| out_suffix_len: .res 1 | |||
| out_prg_name: .res 16 | |||
| out_prg_len: .res 1 | |||
| out_map_name: .res 16 | |||
| out_map_len: .res 1 | |||
| out_prg_open: .res 20 | |||
| out_prg_open_len: .res 1 | |||
| out_map_open: .res 20 | |||
| out_map_open_len: .res 1 | |||
| out_prg_read: .res 20 | |||
| out_prg_read_len: .res 1 | |||
| out_map_read: .res 20 | |||
| out_map_read_len: .res 1 | |||
| out_cmd: .res 48 | |||
| out_cmd_len: .res 1 | |||
| out_load: .res 2 | |||
| out_entry: .res 2 | |||
| out_start: .res 2 | |||
| out_current: .res 2 | |||
| out_count: .res 1 | |||
| out_index: .res 1 | |||
| out_in_section: .res 1 | |||
| out_prg_created: .res 1 | |||
| out_map_created: .res 1 | |||
| out_cmd_open: .res 1 | |||
| out_prg_is_open: .res 1 | |||
| out_map_is_open: .res 1 | |||
| out_err: .res 1 | |||
| out_sections: .res OUT_MAX_SECTIONS * OUT_RECORD_SIZE | |||
| out_tmp: .res 2 | |||
| out_tmp_len: .res 1 | |||
| out_scratch_ptr: .res 2 | |||
| out_scratch_len: .res 1 | |||
| out_append_len: .res 1 | |||
| out_byte_value: .res 1 | |||
| out_text_len: .res 1 | |||
| out_y: .res 1 | |||
| out_num: .res 2 | |||
| @@ -0,0 +1,71 @@ | |||
| ; startup.s - fixed generated-program entry (E04-S01). | |||
| ; | |||
| ; output.s copies these bytes into the generated PRG at PRG_LOAD_ADDR and | |||
| ; PRG_ENTRY_ADDR. They are position-independent except for the fixed mailbox | |||
| ; addresses, which are part of the platform contract. The stub is the same | |||
| ; 12-byte BASIC 7 form used by the compiler itself: | |||
| ; | |||
| ; $1C01 0B 1C link to the end marker at $1C0B | |||
| ; $1C03 0A 00 line 10 | |||
| ; $1C05 9E SYS | |||
| ; $1C06 "7181" $1C0D | |||
| ; $1C0A 00 end of line | |||
| ; $1C0B 00 00 end of program | |||
| ; $1C0D startup code | |||
| ; | |||
| ; The startup has no caller-visible result and returns to BASIC. Later E04 | |||
| ; stories replace the body with runtime initialization once frames and the | |||
| ; generated-program memory map exist. | |||
| .setcpu "6502" | |||
| .include "../common/mailbox.inc" | |||
| .include "../common/formats.inc" | |||
| .segment "RODATA" | |||
| .export gen_stub, gen_stub_size, gen_startup, gen_startup_size | |||
| gen_stub: | |||
| .byte $0B, $1C ; next BASIC line at $1C0B | |||
| .word 10 | |||
| .byte $9E ; SYS | |||
| .byte "7181" ; $1C0D | |||
| .byte $00 | |||
| .word $0000 ; end of BASIC program | |||
| gen_stub_end: | |||
| gen_startup: | |||
| ldx #MB_SIZE - 1 | |||
| lda #$00 | |||
| @clr: sta MB_BASE,x | |||
| dex | |||
| bpl @clr | |||
| lda #MB_MAGIC0_VALUE | |||
| sta MB_MAGIC0 | |||
| lda #MB_MAGIC1_VALUE | |||
| sta MB_MAGIC1 | |||
| lda #MB_PROD_PROGRAM | |||
| sta MB_PRODUCER | |||
| lda #MB_ST_RUNNING | |||
| sta MB_STATUS | |||
| lda #$00 | |||
| sta MB_CODE | |||
| sta MB_CODE+1 | |||
| sta MB_ADDR | |||
| sta MB_ADDR+1 | |||
| sta MB_DATA | |||
| sta MB_DATA+1 | |||
| sta MB_DATA+2 | |||
| sta MB_DATA+3 | |||
| sta MB_DATA+4 | |||
| sta MB_DATA+5 | |||
| sta MB_DATA+6 | |||
| sta MB_DATA+7 | |||
| lda #MB_ST_PASS | |||
| sta MB_STATUS | |||
| rts | |||
| gen_startup_end: | |||
| gen_stub_size = gen_stub_end - gen_stub | |||
| gen_startup_size = gen_startup_end - gen_startup | |||
| .assert gen_stub_size = PRG_STUB_SIZE, error, "generated BASIC stub size changed" | |||
| .assert gen_startup_size <= 255, error, "startup must fit the byte-count API" | |||
| @@ -0,0 +1,248 @@ | |||
| ; E04-S01: PRG/MAP writer test in x128. | |||
| ; | |||
| ; The fixture runs in the compiler mapping and writes GENERATE.PRG/MAP with | |||
| ; the fixed startup, a PAD section at $1C80, then checks: | |||
| ; +0 out_finish result (0) | |||
| ; +1 collision result (OUT_ERR_EXISTS) | |||
| ; +2 section-order result (OUT_ERR_ORDER) | |||
| ; +3 abort result (0) | |||
| ; COLLIDE.PRG/MAP already exist on the test disk; the host verifies they are | |||
| ; unchanged. ABORT and ORDER must leave no output behind. | |||
| .setcpu "6502" | |||
| .include "../../src/common/c128.inc" | |||
| .include "../../src/common/mailbox.inc" | |||
| .include "../../src/common/formats.inc" | |||
| .import mmu_init, mmu_bank0, mmu_shutdown | |||
| .import out_begin, out_startup, out_section, out_end_section | |||
| .import out_byte, out_bytes, out_finish, out_abort | |||
| .import out_load, out_entry, out_start, out_err | |||
| .segment "LOADADDR" | |||
| .word $1C01 | |||
| .segment "EXEHDR" | |||
| .word basic_end | |||
| .word 10 | |||
| .byte $9E | |||
| .byte "7181" | |||
| .byte $00 | |||
| basic_end: | |||
| .word $0000 | |||
| .segment "MAILBOX" | |||
| .res MB_SIZE | |||
| .segment "STARTUP" | |||
| .export test_entry | |||
| test_entry: | |||
| php | |||
| sei | |||
| ldx #MB_SIZE - 1 | |||
| lda #$00 | |||
| @clr: sta MB_BASE,x | |||
| dex | |||
| bpl @clr | |||
| lda #MB_MAGIC0_VALUE | |||
| sta MB_MAGIC0 | |||
| lda #MB_MAGIC1_VALUE | |||
| sta MB_MAGIC1 | |||
| lda #MB_PROD_PLATFORM | |||
| sta MB_PRODUCER | |||
| lda #MB_ST_RUNNING | |||
| sta MB_STATUS | |||
| lda #$00 | |||
| sta fail_mask | |||
| sta MB_DATA+0 | |||
| sta MB_DATA+1 | |||
| sta MB_DATA+2 | |||
| sta MB_DATA+3 | |||
| jsr mmu_init | |||
| jsr mmu_bank0 | |||
| lda #<PRG_LOAD_ADDR | |||
| sta out_load | |||
| lda #>PRG_LOAD_ADDR | |||
| sta out_load+1 | |||
| lda #<PRG_ENTRY_ADDR | |||
| sta out_entry | |||
| lda #>PRG_ENTRY_ADDR | |||
| sta out_entry+1 | |||
| ; ------------------------------------------------ successful output | |||
| lda #<name_generate | |||
| ldx #>name_generate | |||
| ldy #name_generate_len | |||
| jsr out_begin | |||
| bcs @gen_fail | |||
| jsr out_startup | |||
| bcs @gen_fail | |||
| lda #$80 | |||
| sta out_start | |||
| lda #$1C | |||
| sta out_start+1 | |||
| lda #<name_pad | |||
| ldx #>name_pad | |||
| ldy #3 | |||
| jsr out_section | |||
| bcs @gen_fail | |||
| lda #<pad_bytes | |||
| ldx #>pad_bytes | |||
| ldy #4 | |||
| jsr out_bytes | |||
| bcs @gen_fail | |||
| jsr out_end_section | |||
| jsr out_finish | |||
| bcs @gen_fail | |||
| lda out_err | |||
| sta MB_DATA+0 | |||
| cmp #OUT_OK | |||
| beq @gen_done | |||
| lda #$01 | |||
| jsr fail | |||
| jmp @gen_done | |||
| @gen_fail: | |||
| lda out_err | |||
| sta MB_DATA+0 | |||
| lda #$01 | |||
| jsr fail | |||
| jsr out_abort | |||
| @gen_done: | |||
| ; ---------------------------------------------------- collision | |||
| lda #<name_collide | |||
| ldx #>name_collide | |||
| ldy #name_collide_len | |||
| jsr out_begin | |||
| bcc @coll_unexpected | |||
| lda out_err | |||
| sta MB_DATA+1 | |||
| cmp #OUT_ERR_EXISTS | |||
| beq @coll_ok | |||
| lda #$02 | |||
| jsr fail | |||
| jmp @coll_abort | |||
| @coll_unexpected: | |||
| lda #$02 | |||
| jsr fail | |||
| jmp @coll_abort | |||
| @coll_ok: | |||
| @coll_abort: | |||
| jsr out_abort | |||
| @coll_done: | |||
| ; ------------------------------------------------ section order | |||
| lda #<name_order | |||
| ldx #>name_order | |||
| ldy #name_order_len | |||
| jsr out_begin | |||
| bcs @order_begin_fail | |||
| lda #$00 | |||
| sta out_start | |||
| lda #$1C | |||
| sta out_start+1 | |||
| lda #<name_bad | |||
| ldx #>name_bad | |||
| ldy #3 | |||
| jsr out_section | |||
| bcc @order_unexpected | |||
| lda out_err | |||
| sta MB_DATA+2 | |||
| cmp #OUT_ERR_ORDER | |||
| beq @order_abort | |||
| lda #$04 | |||
| jsr fail | |||
| jmp @order_abort | |||
| @order_begin_fail: | |||
| lda out_err | |||
| sta MB_DATA+2 | |||
| lda #$04 | |||
| jsr fail | |||
| jmp @order_done | |||
| @order_unexpected: | |||
| lda #$04 | |||
| jsr fail | |||
| jmp @order_abort | |||
| @order_abort: | |||
| jsr out_abort | |||
| @order_done: | |||
| ; -------------------------------------------------------- abort | |||
| lda #<name_abort | |||
| ldx #>name_abort | |||
| ldy #name_abort_len | |||
| jsr out_begin | |||
| bcs @abort_fail | |||
| lda #$01 | |||
| sta out_start | |||
| lda #$1C | |||
| sta out_start+1 | |||
| lda #<name_x | |||
| ldx #>name_x | |||
| ldy #1 | |||
| jsr out_section | |||
| bcs @abort_fail | |||
| lda #$AA | |||
| jsr out_byte | |||
| bcs @abort_fail | |||
| jsr out_abort | |||
| bcs @abort_fail | |||
| lda out_err | |||
| sta MB_DATA+3 | |||
| cmp #OUT_OK | |||
| beq @abort_done | |||
| @abort_fail: | |||
| lda out_err | |||
| sta MB_DATA+3 | |||
| lda #$08 | |||
| jsr fail | |||
| jsr out_abort | |||
| @abort_done: | |||
| jsr mmu_shutdown | |||
| lda fail_mask | |||
| sta MB_CODE | |||
| lda #$00 | |||
| sta MB_CODE+1 | |||
| lda #MB_ST_FAIL | |||
| ldx fail_mask | |||
| bne @status | |||
| lda #MB_ST_PASS | |||
| @status: | |||
| sta MB_STATUS | |||
| ldy #$00 | |||
| @print: lda message,y | |||
| beq @end | |||
| sty print_y | |||
| jsr CHROUT | |||
| ldy print_y | |||
| iny | |||
| bne @print | |||
| @end: rts | |||
| .segment "CODE" | |||
| fail: | |||
| ora fail_mask | |||
| sta fail_mask | |||
| rts | |||
| .segment "RODATA" | |||
| name_generate: .byte "GENERATE.N8" | |||
| name_generate_len = * - name_generate | |||
| name_collide: .byte "COLLIDE.N8" | |||
| name_collide_len = * - name_collide | |||
| name_order: .byte "ORDER.N8" | |||
| name_order_len = * - name_order | |||
| name_abort: .byte "ABORT.N8" | |||
| name_abort_len = * - name_abort | |||
| name_pad: .byte "PAD" | |||
| name_bad: .byte "BAD" | |||
| name_x: .byte "X" | |||
| pad_bytes: .byte $DE, $AD, $BE, $EF | |||
| message: .byte "NOVA128 OUTPUT TEST DONE", $0D, $00 | |||
| .segment "BSS" | |||
| fail_mask: .res 1 | |||
| print_y: .res 1 | |||
Powered by TurnKey Linux.