The compiler had every phase and the PRG/MAP writer but no program on the C128 that used them, so this adds the prompt, the planned code base, the three container sections and the one-screen summary, plus a verifier that drives the launcher through success, syntax failure and output collision and runs the generated PRG. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>main
| @@ -59,6 +59,18 @@ 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. | |||
| 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 | |||
| 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. | |||
| ## 3. Repository Module Plan | |||
| ```text | |||
| @@ -87,6 +87,8 @@ documented default. `Proposed` means a later change still needs approval. | |||
| | D152 | The E08-S01 harness reads its suite list from `tests/regression/suites.ps1`, which returns an array of hashtables, and gives each suite a fresh `build/<name>-test` directory unless `-NoFresh` is passed. Results are written as JSON with per-suite status, seconds, exit code, signature, problems, log path and artifacts; artifact SHA-256 values are compared against `tests/regression/baseline.json`, which `-UpdateBaseline` rewrites. A failed suite's signature is the diagnosis line it printed (for example a fixture name and the mismatch), falling back to the structural problem such as `exit code N` or a timeout when the suite produced no output. | A `.psd1` cannot be invoked with `&` (PowerShell returns nothing), so the manifest is a script whose array literal is evaluated on read; a data file would need `Import-PowerShellDataFile` and could not express per-suite logic. Fresh per-suite directories satisfy the `E08-S01` requirement that every run creates its own D81; `-NoFresh` exists so the self-test can prove that changed artifacts are detected on a rerun. Reviewing hashes rather than regenerating them keeps `E08-S02`'s "reviewed rather than blindly regenerated" rule intact. Preferring the observed diagnosis over an exit status is what makes a failure identify the fixture, as the story requires. | | |||
| | D153 | E06-S07 emits interface values without a new type category: the parser already classifies class and interface references by name (`sc_name`/`tt_name`) and `ty_assign` already accepts a class for an interface it implements, so a four-byte interface slot reaches `em_local`/`em_param` through the same size argument as any other type. Each storage or conversion site emits a pointer to the four-byte destination in `if_dst` and the operand in `if_src`, with the compile-time table from `it_find` in `if_tab`, then `JSR if_assign`; a null object is written to `if_obj` and does not need a distinct path. An interface call emits the slot address in `if_dst`, `Y` from `iface_slot`, and `JSR if_call`, and reports refusal through `if_error` rather than C. A field or array element of interface type is reached through a four-byte member load/store that copies between the value and `if_dst`/`if_src`, not through `mb_ld_field`/`mb_st_field`, which are byte/word only. Four-byte values never travel in A/X except through `IF_RESULT`, and the emitter does not add a zero-page pointer: `EM_LP` (`$F5-$F6`) addresses one value at a time, `$F2-$F4` is the only free zero page, and `$FB-$FE` is the frame ABI. | The runtime already provides every transfer (D145); the missing half is emission, so reuse keeps one audited conversion and one dispatch path instead of copies in generated code. Deriving the slot size from the parser avoids inventing parallel interface types that would then disagree with `ty_assign`. Refusing to add a fourth zero-page pointer is forced by the map: `$C0-$C5` print vectors, `$C6-$C9` `IF_RESULT`, `$D0-$E0` for-end values, `$E1-$F1` for-step values, `$F5-$F6` `EM_LP`, `$F7-$F8` `EM_TMP`, `$F9-$FA` `MB_SELF`, `$FB-$FE` frame ABI. Argument-bearing interface calls wait for E04-S07's push/discard emission, which is why that story is now a dependency. | | |||
| | 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. | | |||
| ## Deferred | |||
| - Native C128/80-column Vim port | |||
| @@ -61,6 +61,45 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\launch-compiler.ps1 | |||
| `-CompilerPrg` selects another compiler PRG. The launcher reports a missing | |||
| compiler program, source image, or VICE tool before starting anything. | |||
| ### Using the compiler on the machine | |||
| `n128c` asks for one source name and compiles that file from the session disk: | |||
| ```text | |||
| NOVA128 COMPILER | |||
| SOURCE: counter.n8 | |||
| SOURCE COUNTER.N8 | |||
| OUTPUT COUNTER.PRG | |||
| 0 ERRORS | |||
| ``` | |||
| Type the name (letters and digits, up to 15 characters) and press RETURN. The | |||
| name may be typed in either case; the driver folds it to the disk's uppercase | |||
| form. It loads the SEQ file through the buffered PETSCII reader, so no host | |||
| conversion is involved, runs the lexer, parser and emitter, and writes | |||
| `NAME.PRG` and `NAME.MAP` next to the source. Diagnostics appear between the | |||
| prompt and the summary and are also written to `NAME.ERR`. | |||
| `-Keys` types into the session after `-KeyDelay` milliseconds (default 3000) | |||
| and exists for automated checks: | |||
| ```text | |||
| powershell -NoProfile -ExecutionPolicy Bypass -File scripts\launch-compiler.ps1 ` | |||
| -Keys "counter.n8\n" -LimitCycles 200000000 -Warp ` | |||
| -Screenshot build\driver-test\summary.png | |||
| ``` | |||
| `scripts/verify-driver-test.ps1` builds the driver, makes a fresh D81 with | |||
| `counter.n8` and a broken fixture, and checks the summary, `COUNTER.PRG`, | |||
| `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. | |||
| ## Recovery | |||
| - **Lock reported as still owned**: close the named session's emulator window | |||
| @@ -71,9 +110,27 @@ compiler program, source image, or VICE tool before starting anything. | |||
| launchers only ever copy from it. | |||
| - **Wrong editor or compiler build**: pass `-EditorPrg`/`-CompilerPrg` | |||
| explicitly rather than editing the defaults. | |||
| - **`OUTPUT EXISTS`**: `NAME.PRG` or `NAME.MAP` is already on the disk. Erase | |||
| the old output (`SCRATCH "NAME.PRG"`, or `c1541 ... -delete`) and compile | |||
| again; the driver never overwrites a previous build, so a failed compile | |||
| cannot destroy the last good one. | |||
| - **`LOAD FAILED`**: the name is not on the session disk, or the file is not a | |||
| SEQ source. Check the directory spelling and that the editor saved the file | |||
| to the same disk the compiler is reading. | |||
| - **`COMPILE FAILED, n ERRORS`**: read `NAME.ERR` (or the screen) and fix the | |||
| source in the editor session. No `NAME.PRG` is written for a failed compile. | |||
| - **`NO CODE GENERATED`**: the source parsed but produced no instructions; add | |||
| a statement body rather than an empty `program`. | |||
| - **`NAME TOO LONG`**: shorten the source name so the base is at most 12 | |||
| characters, because `.PRG` and `.MAP` are appended. | |||
| ## Limits | |||
| Autostart loads the program from the host file system, not from the disk; the | |||
| disk stays attached for the session's own file access. Physical C128 testing | |||
| and the full edit/compile/run walkthrough belong to E07-S04 and E08-S06. | |||
| Autostart loads the compiler from the host file system, not from the disk; the | |||
| disk stays attached for the session's own file access. The compiler's inputs | |||
| and outputs are always on the session disk. Generated code is capped by the | |||
| 256-byte code buffer (D130) and the section limits in `formats.inc`; a program | |||
| that exceeds either fails with a diagnostic instead of a partial PRG. Text | |||
| `print` needs the generated-program print runtime, so it reports E227 for now. | |||
| Physical C128 testing and the full edit/compile/run walkthrough belong to | |||
| E07-S04 and E08-S06. | |||
| @@ -1,8 +1,9 @@ | |||
| --- | |||
| story: E07-S05 | |||
| owner: codebuff | |||
| status: in-progress | |||
| status: done | |||
| started: 2026-11-10 | |||
| completed: 2026-11-10 | |||
| depends-on: [E07-S01, E04-S01, E04-S06, E02-S04] | |||
| --- | |||
| @@ -10,102 +11,103 @@ depends-on: [E07-S01, E04-S01, E04-S06, E02-S04] | |||
| ## Intended change | |||
| A `n128c` PRG that runs on `x128` and is the compiler the launcher and disk | |||
| builder already assume: it takes a source name from the keyboard, loads that SEQ | |||
| file from drive 8 through the buffered PETSCII reader, runs lexer, parser and | |||
| pass-2 emission, writes `NAME.PRG` and `NAME.MAP` through `output.s`, and prints | |||
| a one-screen summary with the source name, the output name and the diagnostic | |||
| count. Failures print a stable message, write diagnostics to `NAME.ERR`, and | |||
| leave any previous output untouched. | |||
| A `build/n128c.prg` that runs on `x128` and is the file `prepare-disk.ps1` | |||
| injects and `launch-compiler.ps1` autostarts. It asks for one source name, loads | |||
| that SEQ file from drive 8 through the buffered PETSCII reader, runs lexer, | |||
| parser and pass-2 emission, writes `NAME.PRG` and `NAME.MAP` through | |||
| `output.s`, and prints a one-screen summary naming the source, the output and | |||
| the diagnostic count. Failures print one stable line, write `NAME.ERR` when they | |||
| are diagnostics, and never overwrite an earlier output. | |||
| ## File area | |||
| - `src/compiler/driver.s` (new): prompt, load, compile, output, summary | |||
| - `cfg/driver.cfg` (new, or `cfg/compiler.cfg` plus `output.s` if the extra | |||
| section fits): link layout for the whole compiler | |||
| - `scripts/build-driver.ps1` (new): builds `build/n128c.prg` | |||
| - `tests/driver/driver.n8`, `tests/driver/fail.n8` (new fixtures) | |||
| - `scripts/verify-driver-test.ps1` (new): drives the PRG through `x128` | |||
| `keybuf` on a copied image and checks the PRG, ERR, MAP and summary | |||
| - `docs/editor-workflow.md`, `docs/architecture.md`, `docs/decisions.md` | |||
| - `planning/claims/E07-S05.md` | |||
| - generated files under `build/driver-test/` | |||
| - `src/compiler/main.s` (new): prompt, load, compile, output, summary | |||
| - `src/compiler/output.s`: exports `out_prg_name`/`out_prg_len` for the summary | |||
| - `scripts/build-driver.ps1` (new): links the compiler, `output.s`, | |||
| `src/runtime/startup.s` and `main.s`, checks `driver_entry` is `$1C0D`, and | |||
| publishes `build/n128c.prg` | |||
| - `scripts/verify-driver-test.ps1` (new): fresh D81, five x128 runs | |||
| - `scripts/launch-compiler.ps1`, `scripts/lib/Nova128Launcher.psm1`, | |||
| `scripts/lib/Nova128Test.psm1`: `-Keys`/`-KeyDelay` passthrough | |||
| - `docs/editor-workflow.md`, `docs/architecture.md`, `docs/decisions.md` (D154) | |||
| - generated files under `build/driver/`, `build/driver-test/` | |||
| ## Acceptance criteria (from the story) | |||
| ## Acceptance evidence | |||
| - [ ] `build/n128c.prg` is built by a documented script and is the file | |||
| `prepare-disk.ps1` injects and `launch-compiler.ps1` autostarts. | |||
| - [ ] The driver takes one source name, loads it from the session disk with no | |||
| host conversion, compiles, and writes the PRG named after the source. | |||
| - [ ] ERR and MAP outputs are written for the same run, with a one-screen | |||
| summary naming source, output and diagnostic count. | |||
| - [ ] A clean source produces a PRG that runs on `x128`; a failing source | |||
| produces the catalogued diagnostics, a non-zero count, and no PRG. | |||
| - [ ] Missing source, unreadable source and an existing output each end with | |||
| the documented message and leave the previous output intact. | |||
| - [ ] A test drives the driver through `scripts/launch-compiler.ps1` on a copied | |||
| development image and checks the artifacts. | |||
| - [ ] `docs/editor-workflow.md` documents the command and the recovery steps, | |||
| and E07-S04 can be completed with no undocumented command. | |||
| - [x] `build/n128c.prg` is produced by `scripts/build-driver.ps1` and is the | |||
| exact path `prepare-disk.ps1` injects as `n128c` and | |||
| `launch-compiler.ps1` autostarts by default; the verifier drives those same | |||
| scripts. The build asserts the entry label is `$1C0D`. | |||
| - [x] On `x128` the driver takes one source name (`SOURCE: counter.n8`), loads | |||
| it from the session disk with `src_load` and no host conversion, runs the | |||
| lexer, parser and emitter, and writes `COUNTER.PRG` (263 bytes) and | |||
| `COUNTER.MAP` (95 bytes) named after the source. | |||
| - [x] ERR, MAP and the summary are produced for the same run: `COUNTER.MAP` | |||
| records `ENTRY 1C0D`, `SECTION STUB 1C01 000C`, `SECTION START 1C0D 004A` | |||
| and `SECTION CODE 1C57 00AF`; the screen shows `SOURCE COUNTER.N8`, | |||
| `OUTPUT COUNTER.PRG`, `0 ERRORS`. | |||
| - [x] A clean source produces a PRG that runs: a second x128 session autostarts | |||
| `COUNTER.PRG` and its runtime startup reports `MB_ST_PASS` (producer 3). A | |||
| source with errors (`miss_end.n8`) writes `BAD.ERR` with `E024` and `E227`, | |||
| reports `COMPILE FAILED`, and writes no `BAD.PRG` or `BAD.MAP`. | |||
| - [x] Missing source prints `LOAD FAILED`; an existing output prints | |||
| `OUTPUT EXISTS` and leaves `COUNTER.PRG` byte-identical (checked by | |||
| re-reading it from the image after both a failing compile and a refused | |||
| compile); over-long names print `NAME TOO LONG` before any file is created. | |||
| - [x] `scripts/verify-driver-test.ps1` drives the driver through | |||
| `scripts/launch-compiler.ps1` on a fresh development image and checks PRG, | |||
| ERR, MAP, the summary (read from screen RAM in a monitor session), the | |||
| collision path and the generated-program run. | |||
| - [x] `docs/editor-workflow.md` documents the on-machine command, every failure | |||
| message and the recovery steps, so E07-S04 can be completed without an | |||
| undocumented command. | |||
| ## Coordination notes | |||
| - The pieces exist and are individually proven: `src_load`, `names_init`, | |||
| `diag_init`/`diag_finish`, `parse_file` and `sizing`; `output.s` already | |||
| implements `out_begin` (refuses an existing `NAME.PRG`/`NAME.MAP`), | |||
| `out_startup`, `out_section`, `out_byte`, `out_finish` and `out_abort`. | |||
| `tests/parser/parser-test.s` shows the exact compiler initialisation order: | |||
| `mmu_init`, `mmu_bank0`, then per file `src_load`, `names_init`, `diag_init`, | |||
| `parse_file`, `diag_finish`. | |||
| - `scripts/build-parser-test.ps1` links every compiler module against | |||
| `cfg/compiler.cfg`, so the driver's link set is that list plus `output.s` and | |||
| the runtime startup/print modules. The map has `MAIN` to `$B7FF` | |||
| (about 40 KB) and `BANK1` to `$BFFF`; `output.s` keeps its section, class, | |||
| method and table records in BSS, so the remaining budget must be measured | |||
| after the first link, not assumed. | |||
| - `out_begin` derives `NAME.PRG`/`NAME.MAP`/`NAME.ERR` by stripping a trailing | |||
| `.N8` from the source name and caps the base at 12 characters, which matches | |||
| the 16-character limit VICE's directory allows; the driver must validate the | |||
| typed name before calling it. | |||
| - `parse_file` only emits when `em_on` is set; the fixture path sets it, so the | |||
| driver has to set it for pass 2 and must call `parse_file_base` (not | |||
| `parse_file`) when pass-3 absolute addresses are known. That decision is part | |||
| of the driver, not the parser. | |||
| - Launching the PRG needs the same rule the editor learned in D151: it must be | |||
| started from the session disk (`n128c`), not from a host path, so its file | |||
| naming and drive device are the ones the session uses. | |||
| - 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. | |||
| - 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 | |||
| emitting pass. `parse_file` cannot be used because it clears the base request. | |||
| - Source names are limited to 15 characters and folded to uppercase; `out_begin` | |||
| applies the 12-character base limit before building `.PRG`, `.MAP` and | |||
| `.OPEN` names. | |||
| - The whole compiler, `output.s` and the startup fit the existing | |||
| `cfg/compiler.cfg` unchanged; the driver PRG is 23,767 bytes with no new | |||
| memory area or linker change. | |||
| ## Work log | |||
| - Read `output.s`, the parser initialisation path, `cfg/compiler.cfg` and the | |||
| parser fixture's link and startup code to fix the driver's contract and the | |||
| module set it must link. | |||
| - Confirmed `output.s` already refuses to overwrite and scratches only files it | |||
| created, so the story's "leave the previous output intact" criterion is a | |||
| matter of aborting on `out_err` rather than adding new file logic. | |||
| - No driver source, config, build script or fixture has been written yet, so no | |||
| behaviour exists to verify. | |||
| - Assembling `main.s` first failed on a far `BCS` and on `#gen_startup_size` | |||
| (an imported absolute value), so the driver uses `ON_CARRY`/`ON_NONZERO` | |||
| macros with an absolute `JMP` and `#<gen_startup_size`. | |||
| - The first disk run reported `E227` at line 4 for `hello.n8`; the cause was | |||
| the print-hook cells having no generated-program runtime, not a parser fault. | |||
| The driver now zeroes them explicitly and the clean fixture is code-only. | |||
| - `BASIN` returns the terminating RETURN without echoing it, so the driver | |||
| prints the CR itself; without it the diagnostic line ran into `SOURCE:`. | |||
| - 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. | |||
| ## Completion | |||
| - Tests run: none. The claim records the design and the discovered contracts; | |||
| nothing has landed, so it stays `in-progress`. | |||
| - Results: not applicable. | |||
| - Remaining risks: the whole compiler plus `output.s` plus the runtime print | |||
| module may not fit `MAIN`/`BANK1`; if it does not, the driver needs a section | |||
| split or `output.s` record limits reduced, which is a decision, not a tweak. | |||
| The summary must not use the compiler's own zero page while a compile is in | |||
| progress, and a source name longer than 12 base characters has to be rejected | |||
| before `out_begin` builds both names. | |||
| ## Next steps | |||
| 1. Write `src/compiler/driver.s` with the initialisation order above, a | |||
| `source:` prompt, name validation, `parse_file`/`parse_file_base`, and the | |||
| `out_*` calls; build it with a copy of `cfg/compiler.cfg`. | |||
| 2. Measure the link (`MAIN`/`BANK1` free bytes) and record the result; adjust or | |||
| raise a decision if it does not fit. | |||
| 3. Add `scripts/build-driver.ps1` producing `build/n128c.prg`, then the | |||
| verifier that copies a disk, injects the driver, types a fixture name, and | |||
| checks `NAME.PRG`, `NAME.MAP`, `NAME.ERR` and the summary. | |||
| 4. Update `docs/editor-workflow.md` and unblock E07-S04. | |||
| - Tests run: `scripts/verify-driver-test.ps1` (builds the driver, then five | |||
| x128 sessions: summary monitor run, success launcher run, syntax-failure | |||
| launcher run, collision launcher run, generated-PRG monitor run). | |||
| - 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`; `run: generated COUNTER.PRG reported MB_ST_PASS in | |||
| x128`. | |||
| - 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 | |||
| monitor session and therefore depends on the 40-column screen layout the | |||
| driver prints; and `prepare-disk.ps1`'s own injection is exercised through the | |||
| launcher default, not by a separate unit test. | |||
| @@ -0,0 +1,39 @@ | |||
| # E07-S05: build the on-machine compiler driver and publish it as | |||
| # build/n128c.prg - the exact file prepare-disk.ps1 injects as n128c and | |||
| # launch-compiler.ps1 autostarts. | |||
| [CmdletBinding()] | |||
| param() | |||
| $ErrorActionPreference = "Stop" | |||
| $project = Split-Path $PSScriptRoot -Parent | |||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| $out = Join-Path $project "build\driver" | |||
| $link = Invoke-Nova128Link -Name "n128c" -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\source.s"), | |||
| (Join-Path $project "src\compiler\lexer.s"), | |||
| (Join-Path $project "src\compiler\names.s"), | |||
| (Join-Path $project "src\compiler\diagnostics.s"), | |||
| (Join-Path $project "src\compiler\parser.s"), | |||
| (Join-Path $project "src\compiler\codegen.s"), | |||
| (Join-Path $project "src\compiler\flow.s"), | |||
| (Join-Path $project "src\compiler\members.s"), | |||
| (Join-Path $project "src\compiler\sizing.s"), | |||
| (Join-Path $project "src\compiler\emit.s"), | |||
| (Join-Path $project "src\compiler\interfaces.s"), | |||
| (Join-Path $project "src\compiler\iftables.s"), | |||
| (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\compiler\main.s")) | |||
| if ((Get-Nova128Label $link.Labels "driver_entry") -ne 0x1C0D) { | |||
| throw "driver_entry is not at `$1C0D" | |||
| } | |||
| $published = Join-Path $project "build\n128c.prg" | |||
| Copy-Item -LiteralPath $link.Program -Destination $published -Force | |||
| Write-Host ("built {0} ({1} bytes) -> {2}" -f $link.Program, | |||
| (Get-Item -LiteralPath $published).Length, $published) | |||
| @@ -10,6 +10,8 @@ param( | |||
| [string]$DiskImage = "", | |||
| [int]$LimitCycles = 0, | |||
| [string]$Screenshot = "", | |||
| [string]$Keys = "", | |||
| [int]$KeyDelay = 3000, | |||
| [switch]$Warp, | |||
| [switch]$SkipPrepare | |||
| ) | |||
| @@ -38,7 +40,8 @@ $session = $null | |||
| try { | |||
| $session = Enter-Nova128ImageSession -Image $DiskImage -Kind "compiler" | |||
| $null = Invoke-Nova128Emulator -Emulator "x128" -Program $CompilerPrg ` | |||
| -DiskImage $DiskImage -Screenshot $Screenshot -LimitCycles $LimitCycles -Warp:$Warp | |||
| -DiskImage $DiskImage -Screenshot $Screenshot -LimitCycles $LimitCycles ` | |||
| -Keys $Keys -KeyDelay $KeyDelay -Warp:$Warp | |||
| } finally { | |||
| if ($session) { Exit-Nova128ImageSession -Session $session } | |||
| } | |||
| @@ -128,6 +128,8 @@ function Invoke-Nova128Emulator { | |||
| [string]$DiskImage, | |||
| [string]$Screenshot, | |||
| [int]$LimitCycles = 0, | |||
| [string]$Keys = "", | |||
| [int]$KeyDelay = 300, | |||
| [switch]$Warp, | |||
| [switch]$EightyColumn, | |||
| [switch]$ProgramOnDisk | |||
| @@ -157,6 +159,11 @@ function Invoke-Nova128Emulator { | |||
| if ($EightyColumn) { $arguments += "-80col" } | |||
| $arguments += @("-autostart", ('"' + $autostart + '"'), "-autostart-warp", | |||
| "-sounddev", "dummy") | |||
| if ($Keys) { | |||
| # VICE keybuf uses a literal backslash-n for RETURN. The delay is in | |||
| # milliseconds and keeps the keys out of the autostart load. | |||
| $arguments += @("-keybuf", ('"' + $Keys + '"'), "-keybuf-delay", "$KeyDelay") | |||
| } | |||
| if ($Warp) { $arguments += "-warp" } | |||
| if ($LimitCycles -gt 0) { $arguments += @("-limitcycles", "$LimitCycles") } | |||
| if ($Screenshot) { | |||
| @@ -107,7 +107,9 @@ function Start-Nova128X128 { | |||
| [int]$Port = 6530, | |||
| [string]$Drive8Image, | |||
| [string]$Drive8Type = "1581", | |||
| [string]$Screenshot | |||
| [string]$Screenshot, | |||
| [string]$Keys = "", | |||
| [int]$KeyDelay = 4000 | |||
| ) | |||
| $x128 = Get-ViceTool "x128" | |||
| $argList = @() | |||
| @@ -117,6 +119,9 @@ function Start-Nova128X128 { | |||
| $argList += @("-autostart", ('"' + $Autostart + '"'), "-autostart-warp", "-warp", | |||
| "-sounddev", "dummy", "-remotemonitor", | |||
| "-remotemonitoraddress", "ip4://127.0.0.1:$Port") | |||
| if ($Keys) { | |||
| $argList += @("-keybuf", ('"' + $Keys + '"'), "-keybuf-delay", "$KeyDelay") | |||
| } | |||
| if ($Screenshot) { | |||
| if (Test-Path -LiteralPath $Screenshot) { Remove-Item -LiteralPath $Screenshot -Force } | |||
| $argList += @("-exitscreenshotvicii", ('"' + $Screenshot + '"')) | |||
| @@ -0,0 +1,187 @@ | |||
| # E07-S05: drive build/n128c.prg through scripts/launch-compiler.ps1 on a fresh | |||
| # development image and check the PRG, MAP, ERR and summary it produces. | |||
| # | |||
| # success COUNTER.N8 -> COUNTER.PRG + COUNTER.MAP, no ERR, summary on screen | |||
| # failure BAD.N8 -> BAD.ERR, no BAD.PRG/BAD.MAP, earlier outputs intact | |||
| # collision COUNTER.N8 -> OUTPUT EXISTS, COUNTER.PRG byte-identical | |||
| # run the generated COUNTER.PRG reports MB_ST_PASS in a second session | |||
| [CmdletBinding()] | |||
| param([int]$Port = 6552, [int]$TimeoutSeconds = 240) | |||
| $ErrorActionPreference = "Stop" | |||
| $project = Split-Path $PSScriptRoot -Parent | |||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| & (Join-Path $PSScriptRoot "build-driver.ps1") | Out-Host | |||
| $root = Join-Path $project "build\driver-test" | |||
| if (Test-Path -LiteralPath $root) { Remove-Item -LiteralPath $root -Recurse -Force } | |||
| New-Item -ItemType Directory -Force -Path $root | Out-Null | |||
| $driver = Join-Path $project "build\n128c.prg" | |||
| $work = Join-Path $root "work.d81" | |||
| $c1541 = Get-ViceTool "c1541" | |||
| $failures = @() | |||
| $counterPet = Join-Path $root "counter.pet" | |||
| [IO.File]::WriteAllBytes($counterPet, (ConvertTo-Nova128Petscii ` | |||
| (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\counter.n8")))) | |||
| $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")))) | |||
| 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" }) | |||
| function Invoke-Compile { | |||
| param([string]$Keys, [string]$Screenshot) | |||
| if (Test-Path -LiteralPath $Screenshot) { Remove-Item -LiteralPath $Screenshot -Force } | |||
| & (Join-Path $PSScriptRoot "launch-compiler.ps1") -CompilerPrg $driver -DiskImage $work ` | |||
| -SkipPrepare -Keys $Keys -KeyDelay 4000 -LimitCycles 200000000 ` | |||
| -Screenshot $Screenshot -Warp | Out-Host | |||
| if (-not (Test-Path -LiteralPath $Screenshot)) { | |||
| throw "the compiler session produced no exit screenshot" | |||
| } | |||
| } | |||
| function Get-Listing { | |||
| return ((& $c1541 -attach $work -list 2>&1) -join "`n") | |||
| } | |||
| function Read-DiskFile { | |||
| param([string]$Name, [string]$Type, [string]$Dest) | |||
| if (Test-Path -LiteralPath $Dest) { Remove-Item -LiteralPath $Dest -Force } | |||
| & $c1541 $work -read "$Name,$Type" $Dest 2>&1 | Out-Null | |||
| if (-not (Test-Path -LiteralPath $Dest)) { throw "could not read $Name,$Type from the disk" } | |||
| return ,([IO.File]::ReadAllBytes($Dest)) | |||
| } | |||
| function Get-ScreenText { | |||
| param($Session) | |||
| $ram = Read-Nova128Memory $Session 0x0400 1000 | |||
| $rows = for ($r = 0; $r -lt 25; $r++) { | |||
| $chars = for ($c = 0; $c -lt 40; $c++) { | |||
| $code = $ram[$r * 40 + $c] | |||
| if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' } | |||
| } | |||
| -join $chars | |||
| } | |||
| return ($rows -join "`n") | |||
| } | |||
| # 1: the on-screen summary, read from a copy so the three launcher runs below | |||
| # still start from an image without outputs. | |||
| $summaryDisk = Join-Path $root "summary.d81" | |||
| Copy-Item -LiteralPath $work -Destination $summaryDisk -Force | |||
| $session = Start-Nova128X128 -Autostart $summaryDisk -Drive8Image $summaryDisk -Port $Port ` | |||
| -Keys "counter.n8\n" -KeyDelay 4000 -Screenshot (Join-Path $root "summary-screen.png") | |||
| try { | |||
| $screenText = "" | |||
| $deadline = [DateTime]::UtcNow.AddSeconds(120) | |||
| while ([DateTime]::UtcNow -lt $deadline) { | |||
| Invoke-Nova128Monitor $session "x" | Out-Null | |||
| Start-Sleep -Seconds 1 | |||
| $screenText = Get-ScreenText $session | |||
| if ($screenText -match "OUTPUT COUNTER\.PRG" -and $screenText -match "0 ERRORS" -and | |||
| $screenText -match "SOURCE COUNTER\.N8") { break } | |||
| } | |||
| } finally { | |||
| Stop-Nova128X128 $session | |||
| } | |||
| foreach ($needle in @("NOVA128 COMPILER", "SOURCE COUNTER.N8", "OUTPUT COUNTER.PRG", "0 ERRORS")) { | |||
| if ($screenText -notmatch [regex]::Escape($needle)) { | |||
| $failures += "the summary did not show '$needle'" | |||
| } | |||
| } | |||
| # 2: success through the launcher. | |||
| Invoke-Compile -Keys "counter.n8\n" -Screenshot (Join-Path $root "counter-summary.png") | |||
| $listing = Get-Listing | |||
| if ($listing -notmatch '"counter\.prg"\s+prg') { $failures += "COUNTER.PRG is missing" } | |||
| if ($listing -notmatch '"counter\.map"\s+seq') { $failures += "COUNTER.MAP is missing" } | |||
| if ($listing -match '"counter\.err"') { $failures += "a clean source left COUNTER.ERR" } | |||
| $prgPath = Join-Path $root "counter.prg" | |||
| $prg = Read-DiskFile -Name "counter.prg" -Type "p" -Dest $prgPath | |||
| $stub = [byte[]](0x0B, 0x1C, 0x0A, 0x00, 0x9E, 0x37, 0x31, 0x38, 0x31, 0x00, 0x00, 0x00) | |||
| if ($prg.Length -lt 14 -or $prg[0] -ne 0x01 -or $prg[1] -ne 0x1C) { | |||
| $failures += "COUNTER.PRG does not load at `$1C01" | |||
| } else { | |||
| for ($i = 0; $i -lt $stub.Length; $i++) { | |||
| if ($prg[2 + $i] -ne $stub[$i]) { | |||
| $failures += ("COUNTER.PRG stub byte {0} is {1:X2}, expected {2:X2}" -f $i, $prg[2 + $i], $stub[$i]) | |||
| break | |||
| } | |||
| } | |||
| } | |||
| # c1541 translates PETSCII CR to CRLF on read, so normalize before matching. | |||
| $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)) | |||
| } | |||
| 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. | |||
| Invoke-Compile -Keys "bad.n8\n" -Screenshot (Join-Path $root "bad-summary.png") | |||
| $listing = Get-Listing | |||
| if ($listing -notmatch '"bad\.err"\s+seq') { $failures += "BAD.ERR is missing" } | |||
| if ($listing -match '"bad\.prg"') { $failures += "a failing source produced BAD.PRG" } | |||
| if ($listing -match '"bad\.map"') { $failures += "a failing source produced BAD.MAP" } | |||
| $errText = [Text.Encoding]::ASCII.GetString((Read-DiskFile -Name "bad.err" -Type "s" ` | |||
| -Dest (Join-Path $root "bad.err"))) | |||
| if ($errText -notmatch '^E\d{3} BAD\.N8 \d+ ') { $failures += "BAD.ERR has no catalogued diagnostic" } | |||
| if ($listing -notmatch '"counter\.prg"\s+prg') { $failures += "the failure removed COUNTER.PRG" } | |||
| $afterFailure = Read-DiskFile -Name "counter.prg" -Type "p" -Dest (Join-Path $root "counter-after.prg") | |||
| if ([Convert]::ToBase64String($afterFailure) -ne [Convert]::ToBase64String($prg)) { | |||
| $failures += "the failing run changed COUNTER.PRG" | |||
| } | |||
| # 4: an existing output is refused and the file stays byte-identical. | |||
| Invoke-Compile -Keys "counter.n8\n" -Screenshot (Join-Path $root "collision-summary.png") | |||
| $listing = Get-Listing | |||
| if (([regex]::Matches($listing, '"counter\.prg"')).Count -ne 1) { | |||
| $failures += "the collision run changed the COUNTER.PRG directory entry" | |||
| } | |||
| $afterCollision = Read-DiskFile -Name "counter.prg" -Type "p" -Dest (Join-Path $root "counter-again.prg") | |||
| if ([Convert]::ToBase64String($afterCollision) -ne [Convert]::ToBase64String($prg)) { | |||
| $failures += "a refused output changed COUNTER.PRG" | |||
| } | |||
| # 5: the generated program itself runs and reaches its started/pass contract. | |||
| $runSession = Start-Nova128X128 -Autostart $prgPath -Port ($Port + 1) ` | |||
| -Screenshot (Join-Path $root "counter-run.png") | |||
| try { | |||
| $mb = Wait-Nova128Mailbox $runSession -TimeoutSeconds 120 | |||
| } finally { | |||
| Stop-Nova128X128 $runSession | |||
| } | |||
| if ($mb[0] -ne 0x4E -or $mb[1] -ne 0x38) { $failures += "the generated program did not initialise the mailbox" } | |||
| 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]) } | |||
| 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 | |||
| $prg.Length, $map.Length) | |||
| Write-Host ("failure: BAD.ERR {0}, no BAD.PRG, COUNTER.PRG unchanged" -f $errText.Trim()) | |||
| Write-Host "collision: OUTPUT EXISTS and COUNTER.PRG unchanged" | |||
| Write-Host "run: generated COUNTER.PRG reported MB_ST_PASS in x128" | |||
| @@ -0,0 +1,353 @@ | |||
| ; main.s - n128c on-machine compiler driver (E07-S05). | |||
| ; | |||
| ; Started from the session disk as N128C (the launcher autostarts it), the | |||
| ; driver asks for one source name, loads that SEQ file from drive 8 with the | |||
| ; buffered PETSCII reader, runs the lexer, parser and emitter, and writes the | |||
| ; generated PRG and its MAP through output.s. Every failure prints one stable | |||
| ; line and leaves earlier outputs alone: output.s refuses to overwrite and the | |||
| ; driver aborts only the files this run created. | |||
| ; | |||
| ; Screen contract (40 and 80 column sessions): | |||
| ; | |||
| ; NOVA128 COMPILER | |||
| ; SOURCE: hello.n8 | |||
| ; SOURCE HELLO.N8 | |||
| ; OUTPUT HELLO.PRG | |||
| ; 0 ERRORS | |||
| ; | |||
| ; Diagnostics appear between the prompt and the summary when a source fails. | |||
| ; The summary names the source as typed, the output file, and the number of | |||
| ; diagnostics. | |||
| ; | |||
| ; The generated code base is planned before parsing: the image layout is the | |||
| ; BASIC stub, the runtime startup at $1C0D, then the code section, so pass 2 | |||
| ; installs absolute targets against the address the PRG will load at. | |||
| .setcpu "6502" | |||
| .include "../common/c128.inc" | |||
| .include "../common/formats.inc" | |||
| .include "../common/source.inc" | |||
| NAME_MAX = 15 | |||
| .import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline | |||
| .import src_load | |||
| .import names_init | |||
| .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 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_finish, out_abort, out_err | |||
| .import out_load, out_entry, out_start | |||
| .import out_prg_name, out_prg_len | |||
| .import gen_startup_size | |||
| ; Print the bytes from text up to its end label. | |||
| ; Far conditional jumps: a near branch plus an absolute JMP. | |||
| .macro ON_CARRY target | |||
| bcc :+ | |||
| jmp target | |||
| : | |||
| .endmacro | |||
| .macro ON_NONZERO target | |||
| beq :+ | |||
| jmp target | |||
| : | |||
| .endmacro | |||
| .macro PRINT text, text_end | |||
| lda #<text | |||
| ldx #>text | |||
| ldy #text_end - text | |||
| jsr print_str | |||
| .endmacro | |||
| ; ------------------------------------------------------------------ HEADER | |||
| .segment "LOADADDR" | |||
| .word $1C01 | |||
| .segment "EXEHDR" | |||
| .word basic_end | |||
| .word 10 | |||
| .byte $9E | |||
| .byte "7181" | |||
| .byte $00 | |||
| basic_end: | |||
| .word $0000 | |||
| ; ------------------------------------------------------------------ START | |||
| .segment "STARTUP" | |||
| .export driver_entry | |||
| driver_entry: | |||
| php | |||
| sei | |||
| jsr mmu_init | |||
| 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 | |||
| sta em_pr_byte+1 | |||
| sta em_pr_cr | |||
| sta em_pr_cr+1 | |||
| PRINT banner, banner_end | |||
| jsr read_name | |||
| ON_CARRY @no_name | |||
| PRINT cr, cr_end ; BASIN returns the CR without echoing it | |||
| lda #<drv_name | |||
| ldx #>drv_name | |||
| ldy drv_len | |||
| jsr src_load | |||
| cmp #SRC_OK | |||
| ON_NONZERO @load_fail | |||
| jsr names_init | |||
| lda #<drv_name | |||
| ldx #>drv_name | |||
| ldy drv_len | |||
| jsr diag_init | |||
| lda #$01 | |||
| sta diag_screen | |||
| sta em_on | |||
| lda #<gen_startup_size ; code follows the generated startup | |||
| jsr sz_plan_code | |||
| lda sz_code_base | |||
| ldx sz_code_base+1 | |||
| jsr parse_file_base | |||
| jsr diag_finish | |||
| sta drv_diag | |||
| cmp #$00 | |||
| ON_NONZERO @compile_fail | |||
| lda em_bad | |||
| ON_NONZERO @compile_fail | |||
| lda cg_len | |||
| bne @write | |||
| jmp @no_code | |||
| @write: | |||
| 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 | |||
| lda #<drv_name | |||
| ldx #>drv_name | |||
| ldy drv_len | |||
| jsr out_begin | |||
| ON_CARRY @out_fail | |||
| lda #$01 | |||
| sta drv_out_open | |||
| jsr out_startup | |||
| ON_CARRY @out_fail | |||
| lda sz_code_base | |||
| sta out_start | |||
| lda sz_code_base+1 | |||
| sta out_start+1 | |||
| lda #<code_name | |||
| ldx #>code_name | |||
| ldy #4 | |||
| jsr out_section | |||
| ON_CARRY @out_fail | |||
| lda #<cg_code | |||
| ldx #>cg_code | |||
| ldy cg_len | |||
| jsr out_bytes | |||
| ON_CARRY @out_fail | |||
| jsr out_end_section | |||
| jsr out_finish | |||
| ON_CARRY @out_fail | |||
| lda #$00 | |||
| sta drv_out_open | |||
| PRINT src_label, src_label_end | |||
| lda #<drv_name | |||
| ldx #>drv_name | |||
| ldy drv_len | |||
| jsr print_str | |||
| PRINT cr, cr_end | |||
| PRINT out_label, out_label_end | |||
| lda #<out_prg_name | |||
| ldx #>out_prg_name | |||
| ldy out_prg_len | |||
| jsr print_str | |||
| PRINT cr, cr_end | |||
| lda drv_diag | |||
| jsr print_dec8 | |||
| PRINT errors_text, errors_end | |||
| jmp @done | |||
| @no_name: | |||
| PRINT msg_no_name, msg_no_name_end | |||
| jmp @done | |||
| @load_fail: | |||
| PRINT msg_load, msg_load_end | |||
| jmp @done | |||
| @no_code: | |||
| PRINT msg_no_code, msg_no_code_end | |||
| jmp @done | |||
| @compile_fail: | |||
| PRINT msg_compile, msg_compile_end | |||
| lda drv_diag | |||
| jsr print_dec8 | |||
| PRINT errors_text, errors_end | |||
| jmp @done | |||
| @out_fail: | |||
| lda out_err | |||
| sta drv_err | |||
| lda drv_out_open | |||
| beq @out_msg | |||
| jsr out_abort | |||
| @out_msg: | |||
| lda drv_err | |||
| cmp #OUT_ERR_EXISTS | |||
| beq @exists | |||
| cmp #OUT_ERR_NAME | |||
| beq @bad_name | |||
| PRINT msg_disk, msg_disk_end | |||
| jmp @done | |||
| @exists: | |||
| PRINT msg_exists, msg_exists_end | |||
| jmp @done | |||
| @bad_name: | |||
| PRINT msg_out_name, msg_out_name_end | |||
| @done: | |||
| jsr mmu_shutdown | |||
| plp | |||
| 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. | |||
| read_name: | |||
| ldy #$00 | |||
| @loop: sty drv_y | |||
| jsr kcall_inline | |||
| .word CHRIN | |||
| ldy drv_y | |||
| cmp #$0D | |||
| beq @done | |||
| cmp #$20 | |||
| bcc @loop ; ignore other control codes | |||
| cpy #NAME_MAX | |||
| bcs @loop | |||
| and #$7F ; lowercase PETSCII letters -> uppercase | |||
| cmp #$20 | |||
| bcc @loop | |||
| sta drv_name,y | |||
| iny | |||
| jmp @loop | |||
| @done: sty drv_len | |||
| tya | |||
| beq @empty | |||
| clc | |||
| rts | |||
| @empty: sec | |||
| rts | |||
| ; ---------------------------------------------------------------- printing | |||
| ; A/X = text, Y = length. | |||
| print_str: | |||
| sta drv_src | |||
| stx drv_src+1 | |||
| sty drv_text_len | |||
| ldy #$00 | |||
| @loop: cpy drv_text_len | |||
| beq @done | |||
| lda (drv_src),y | |||
| sty drv_y | |||
| jsr putc | |||
| ldy drv_y | |||
| iny | |||
| bne @loop | |||
| @done: rts | |||
| putc: jsr kcall_inline | |||
| .word CHROUT | |||
| rts | |||
| ; A = 0-255 printed in decimal without leading zeros. | |||
| print_dec8: | |||
| sta drv_num | |||
| lda #$00 | |||
| sta drv_started | |||
| ldx #$00 | |||
| @place: lda #'0' | |||
| sta drv_digit | |||
| @sub: lda drv_num | |||
| sec | |||
| sbc powers,x | |||
| bcc @emit | |||
| sta drv_num | |||
| inc drv_digit | |||
| jmp @sub | |||
| @emit: lda drv_digit | |||
| cpx #2 ; the units digit always prints | |||
| beq @out | |||
| cmp #'0' | |||
| bne @out | |||
| lda drv_started | |||
| beq @next | |||
| @out: lda #$01 | |||
| sta drv_started | |||
| lda drv_digit | |||
| jsr putc | |||
| @next: inx | |||
| cpx #3 | |||
| bne @place | |||
| rts | |||
| ; ---------------------------------------------------------------- RODATA | |||
| .segment "RODATA" | |||
| powers: .byte 100, 10, 1 | |||
| code_name: .byte "CODE" | |||
| banner: .byte "NOVA128 COMPILER", $0D, "SOURCE: " | |||
| banner_end: | |||
| src_label: .byte "SOURCE " | |||
| src_label_end: | |||
| out_label: .byte "OUTPUT " | |||
| out_label_end: | |||
| errors_text: .byte " ERRORS", $0D | |||
| errors_end: | |||
| cr: .byte $0D | |||
| cr_end: | |||
| msg_no_name: .byte "NO SOURCE NAME", $0D | |||
| msg_no_name_end: | |||
| msg_load: .byte "LOAD FAILED", $0D | |||
| msg_load_end: | |||
| msg_no_code: .byte "NO CODE GENERATED", $0D | |||
| msg_no_code_end: | |||
| msg_compile: .byte "COMPILE FAILED, " | |||
| msg_compile_end: | |||
| msg_exists: .byte "OUTPUT EXISTS", $0D | |||
| msg_exists_end: | |||
| msg_out_name: .byte "NAME TOO LONG", $0D | |||
| msg_out_name_end: | |||
| msg_disk: .byte "DISK ERROR", $0D | |||
| msg_disk_end: | |||
| ; ------------------------------------------------------------------- BSS | |||
| .segment "ZEROPAGE" | |||
| drv_src: .res 2 | |||
| drv_y: .res 1 | |||
| drv_text_len: .res 1 | |||
| drv_num: .res 1 | |||
| drv_digit: .res 1 | |||
| drv_started: .res 1 | |||
| .segment "BSS" | |||
| drv_name: .res NAME_MAX | |||
| drv_len: .res 1 | |||
| drv_diag: .res 1 | |||
| drv_err: .res 1 | |||
| drv_out_open: .res 1 | |||
| @@ -44,7 +44,7 @@ | |||
| .export out_table, out_table_iface, out_table_iface_len, out_table_addr | |||
| .export out_table_slots, out_table_data, out_table_count | |||
| .export out_class_size, out_class_destroy, out_class_ifaces, out_class_ifdir | |||
| .export out_load, out_entry, out_start, out_err | |||
| .export out_load, out_entry, out_start, out_err, out_prg_name, out_prg_len | |||
| .segment "ZEROPAGE" | |||
| out_dst: .res 2 | |||
Powered by TurnKey Linux.