| @@ -308,7 +308,8 @@ memory. | |||||
| - Load address `$1C01`; BASIC 7 stub `10 SYS 7181`; entry `$1C0D`. | - Load address `$1C01`; BASIC 7 stub `10 SYS 7181`; entry `$1C0D`. | ||||
| - Sections are contiguous and ascending; gaps are zero-filled. | - Sections are contiguous and ascending; gaps are zero-filled. | ||||
| - `NAME.MAP` records `ENTRY` and every `SECTION name start size` in hex. | |||||
| - `NAME.MAP` records `ENTRY`, every `SECTION name start size`, and optional | |||||
| `CLASS`/`METHOD` records in hex. | |||||
| - The E04-S01 startup writes the mailbox with producer `$03`, then PASS, and | - The E04-S01 startup writes the mailbox with producer `$03`, then PASS, and | ||||
| returns to BASIC. E04-S02 supplies the frame runtime separately; code | returns to BASIC. E04-S02 supplies the frame runtime separately; code | ||||
| generation will switch to CR `$0E` and initialize frames when it emits user | generation will switch to CR `$0E` and initialize frames when it emits user | ||||
| @@ -607,7 +608,7 @@ prologue, and sizing/pass-3 addresses remain unimplemented. `for` currently | |||||
| requires a constant byte end and step 1 and leaves the loop variable one past | requires a constant byte end and step 1 and leaves the loop variable one past | ||||
| the end value. E04-S07 owns those limits and the 255-byte code buffer. | the end value. E04-S07 owns those limits and the 255-byte code buffer. | ||||
| ### 7.12 Pass-3 relocation and counter (E04-S07, partial) | |||||
| ### 7.12 Pass-3 relocation and sizing (E04-S07) | |||||
| `sizing.s` computes the final CODE base from `PRG_ENTRY_ADDR` plus the bytes | `sizing.s` computes the final CODE base from `PRG_ENTRY_ADDR` plus the bytes | ||||
| before the section and installs it through `parse_file_base`. `flow.s` resolves | before the section and installs it through `parse_file_base`. `flow.s` resolves | ||||
| @@ -615,6 +616,14 @@ absolute `JMP` and `JSR` operands against that base; relative branches remain | |||||
| offset-based. Normal `parse_file` clears the base request, so the in-place | offset-based. Normal `parse_file` clears the base request, so the in-place | ||||
| statement and call fixtures keep using `cg_code`. | statement and call fixtures keep using `cg_code`. | ||||
| `parse_file_sized` selects count mode with `em_count`, captures the count | |||||
| pass with `sz_capture`, then reruns with emission enabled and diagnostics | |||||
| suppressed. `sz_verify` compares the code length, entry offset, routine sizes, | |||||
| and all 64 label offsets; any difference raises `RES_SIZE_MISMATCH` (E228). | |||||
| Routine lengths are recorded by `em_end_func` in `em_routine_sizes`. | |||||
| `output.s` appends `METHOD name address` records after `CLASS` records, so | |||||
| descriptor and method entry addresses appear in the deterministic MAP. | |||||
| `counter.n8` is a two-local program: `total = 0`, then `for i = 1 to 5` adds | `counter.n8` is a two-local program: `total = 0`, then `for i = 1 to 5` adds | ||||
| `i` to `total`. Its fixture emits `COUNTER.PRG` and `COUNTB.PRG` with final | `i` to `total`. Its fixture emits `COUNTER.PRG` and `COUNTB.PRG` with final | ||||
| `STUB`, `START`, and `CODE` addresses, checks the two files are byte-identical, | `STUB`, `START`, and `CODE` addresses, checks the two files are byte-identical, | ||||
| @@ -622,9 +631,11 @@ and runs `COUNTER.PRG` in a second x128 session. The fixture-owned wrapper | |||||
| selects CR `$0E`, points `FRAME_FP` at the software frame area, calls the code, | selects CR `$0E`, points `FRAME_FP` at the software frame area, calls the code, | ||||
| reports `total`, and restores the entry mapping. The observed result is 15. | reports `total`, and restores the entry mapping. The observed result is 15. | ||||
| This closes final code relocation and the first real source-to-PRG run, but not | |||||
| E04-S07's count-mode routine sizing, method/class MAP records, or a single | |||||
| unresolved-reference diagnostic. | |||||
| The counter fixture also verifies a zero sizing-mismatch counter. The parser's | |||||
| `unresolved.n8` fixture produces exactly one `SEM_UNKNOWN_NAME` (E61) for an | |||||
| unknown method, with no bad-operand follow-up. The class fixture checks | |||||
| `METHOD DESTROY` and `METHOD STUB` MAP records, and the output fixture | |||||
| reconfirms the E202 overwrite refusal. | |||||
| ## 8. Heap and Ownership | ## 8. Heap and Ownership | ||||
| @@ -680,10 +691,11 @@ MAP lines after the section records: | |||||
| ```text | ```text | ||||
| CLASS name size destroy interface-count interface-directory | CLASS name size destroy interface-count interface-directory | ||||
| METHOD name address | |||||
| ``` | ``` | ||||
| All numeric fields are four hex digits except the interface count, which is | All numeric fields are four hex digits except the interface count, which is | ||||
| two. Empty classes and no destructor are written as `0000`; records keep the | |||||
| two. METHOD records follow all CLASS records in `out_method` call order. Empty classes and no destructor are written as `0000`; records keep the | |||||
| caller's order. The class test reads the emitted MAP back and compares it with | caller's order. The class test reads the emitted MAP back and compares it with | ||||
| the descriptor's actual destructor and directory addresses. | the descriptor's actual destructor and directory addresses. | ||||
| @@ -66,7 +66,8 @@ documented default. `Proposed` means a later change still needs approval. | |||||
| | D137 | E04-S06 emission is driven from parser pass 2: with `em_on` set, the parser calls `emit.s` as it type-checks each statement, and `parse_file` resets the emitter (and the code buffer) at the start of pass 2; pass 1 only assigns the same slots. Parameters take `FP+2` upward in declaration order, locals `FP-1` downward, one byte for byte-sized types and two for words. A slot is reached by emitting `EM_LP = FRAME_FP + offset` with the offset's sign byte, because `(FP),Y` cannot address below `FP`; `EM_LP` is `$F5-$F6` and the combine temp `EM_TMP` is `$F7`. Byte `+`/`-` keep the left operand on the hardware stack. Print calls go through the `em_pr_text/byte/cr` cells and three `JMP (cell)` trampolines, so a fixture can record output without CHROUT. Emitter failures set `em_bad` (`EM_ERR_SIZE`, `EM_ERR_TARGET`, `EM_ERR_OP`, `EM_ERR_STATE`) and stop emitting; nothing is written outside the code buffer. | Gives the first parser-to-codegen path a testable shape without freezing pass 3: the fixture executes the emitted bytes in place and checks locals and recorded output. The 255-byte buffer, one captured string literal, and the fixture-owned frame prologue are known limits for E04-S07, which owns sizing, section addresses, and `frame_enter`. Control flow, calls, fields, word values, arrays, and diagnostic reporting remain E04-S06 work. Implemented by `emit.s`, `emit.inc`, the `parser.s` hooks, `codegen.s`, and the statements fixture. | | | D137 | E04-S06 emission is driven from parser pass 2: with `em_on` set, the parser calls `emit.s` as it type-checks each statement, and `parse_file` resets the emitter (and the code buffer) at the start of pass 2; pass 1 only assigns the same slots. Parameters take `FP+2` upward in declaration order, locals `FP-1` downward, one byte for byte-sized types and two for words. A slot is reached by emitting `EM_LP = FRAME_FP + offset` with the offset's sign byte, because `(FP),Y` cannot address below `FP`; `EM_LP` is `$F5-$F6` and the combine temp `EM_TMP` is `$F7`. Byte `+`/`-` keep the left operand on the hardware stack. Print calls go through the `em_pr_text/byte/cr` cells and three `JMP (cell)` trampolines, so a fixture can record output without CHROUT. Emitter failures set `em_bad` (`EM_ERR_SIZE`, `EM_ERR_TARGET`, `EM_ERR_OP`, `EM_ERR_STATE`) and stop emitting; nothing is written outside the code buffer. | Gives the first parser-to-codegen path a testable shape without freezing pass 3: the fixture executes the emitted bytes in place and checks locals and recorded output. The 255-byte buffer, one captured string literal, and the fixture-owned frame prologue are known limits for E04-S07, which owns sizing, section addresses, and `frame_enter`. Control flow, calls, fields, word values, arrays, and diagnostic reporting remain E04-S06 work. Implemented by `emit.s`, `emit.inc`, the `parser.s` hooks, `codegen.s`, and the statements fixture. | | ||||
| | D138 | E04-S06 completes the pass-2 emission path above D137. `flow.s` grows to 64 labels and 128 fixups; `if`/`else`, `while`, a constant-byte-end positive-step-1 `for`, and `return` emit through five-byte branches, `JMP` back edges, and `JSR` fixups. `fl_release` remains a successful no-op because released labels could still have unresolved fixups. Each method gets a stable entry label allocated by member row, so a call written before the method body resolves forward; `em_entry` records the program body's offset in `cg_code` so a fixture runs the program rather than a class method. `em_bad` is converted to `E_RES_EMIT_FAILED` (E227) after pass 2. The three print vectors are absolute zero-page cells at `$C0-$C5` (`em_pr_text`, `em_pr_byte`, `em_pr_cr`), so their low byte is never `$FF` and the `JMP (cell)` trampolines avoid the 6502 indirect-JMP page bug; six reserved BSS bytes preserve the existing layout of later emitter state. | Closes the E04-S06 byte-code path without changing the generated container. `for` ends one past its end value and currently requires a constant byte end and step 1; `return` is a bare `RTS` because the frame prologue is still fixture-owned. Word values, call arguments, arrays, method result forwarding, and the sizing/pass-3 address pass remain explicitly unimplemented and belong to E04-S07. Implemented by `emit.s`, `flow.s`, `parser.s`, `codegen.s`, `diagcat.inc`, the statements fixture, and the calls fixture. | | | D138 | E04-S06 completes the pass-2 emission path above D137. `flow.s` grows to 64 labels and 128 fixups; `if`/`else`, `while`, a constant-byte-end positive-step-1 `for`, and `return` emit through five-byte branches, `JMP` back edges, and `JSR` fixups. `fl_release` remains a successful no-op because released labels could still have unresolved fixups. Each method gets a stable entry label allocated by member row, so a call written before the method body resolves forward; `em_entry` records the program body's offset in `cg_code` so a fixture runs the program rather than a class method. `em_bad` is converted to `E_RES_EMIT_FAILED` (E227) after pass 2. The three print vectors are absolute zero-page cells at `$C0-$C5` (`em_pr_text`, `em_pr_byte`, `em_pr_cr`), so their low byte is never `$FF` and the `JMP (cell)` trampolines avoid the 6502 indirect-JMP page bug; six reserved BSS bytes preserve the existing layout of later emitter state. | Closes the E04-S06 byte-code path without changing the generated container. `for` ends one past its end value and currently requires a constant byte end and step 1; `return` is a bare `RTS` because the frame prologue is still fixture-owned. Word values, call arguments, arrays, method result forwarding, and the sizing/pass-3 address pass remain explicitly unimplemented and belong to E04-S07. Implemented by `emit.s`, `flow.s`, `parser.s`, `codegen.s`, `diagcat.inc`, the statements fixture, and the calls fixture. | | ||||
| | D139 | E04-S07 begins the pass-3 address path. `flow.s` resolves absolute `JMP`/`JSR` operands against `fl_code_base`, which defaults to `cg_code`; `sizing.s` computes the final code base as `PRG_ENTRY_ADDR + bytes before CODE` and installs it with `em_set_base` before pass 2. `counter.n8` aliases two local bytes, loops `i = 1 to 5`, and sums into `total`; its fixture writes `COUNTER.PRG`/`COUNTB.PRG` with STUB, START, and CODE sections. The generated wrapper selects CR `$0E`, sets `FRAME_FP` to `$BF80`, calls the code base, reads `total`, restores CR, and reports PASS. | Proves final section addresses and generated-code relocation in a real x128 run while keeping existing in-place emitter fixtures unchanged. Count-mode routine sizing/mismatch accounting, method and class records in the MAP, unresolved-reference diagnostics, and general pass-3 scheduling remain E04-S07 work. Implemented by `flow.s`, `emit.s`, `sizing.s`, `counter.n8`, the counter fixture, and its scripts. | | |||||
| | D139 | E04-S07 begins the pass-3 address path. `flow.s` resolves absolute `JMP`/`JSR` operands against `fl_code_base`, which defaults to `cg_code`; `sizing.s` computes the final code base as `PRG_ENTRY_ADDR + bytes before CODE` and installs it with `em_set_base` before pass 2. `counter.n8` aliases two local bytes, loops `i = 1 to 5`, and sums into `total`; its fixture writes `COUNTER.PRG`/`COUNTB.PRG` with STUB, START, and CODE sections. The generated wrapper selects CR `$0E`, sets `FRAME_FP` to `$BF80`, calls the code base, reads `total`, restores CR, and reports PASS. | Proves final section addresses and generated-code relocation in a real x128 run while keeping existing in-place emitter fixtures unchanged. D140 completes the count-mode and MAP work for E04-S07. Implemented by `flow.s`, `emit.s`, `sizing.s`, `counter.n8`, the counter fixture, and its scripts. | | |||||
| | D140 | E04-S07 completes count-mode sizing and the MAP/diagnostic evidence. `em_count` selects the count pass; `em_end_func` records each routine length in `em_routine_sizes`; `sz_capture` snapshots code length, entry, routine sizes, and all 64 label offsets; the emitting pass runs with `diag_suppress`; `sz_verify` counts every difference and raises `RES_SIZE_MISMATCH` (E228) after the pass. The counter fixture requires `sz_mismatch = 0`, 175 code bytes, `CODE 1C4F`, deterministic PRGs, and `total = 15`. `output.s` adds `METHOD name address` records after `CLASS` records, proven by `METHOD DESTROY`/`METHOD STUB` in the class MAP. `tests/parser/unresolved.n8` proves one unknown method produces exactly one `SEM_UNKNOWN_NAME` (E61), and the output suite still proves E202 in the pass-3-adjacent writer path. | Closes the E04-S07 acceptance evidence without changing ordinary in-place fixtures: `parse_file` clears `em_count`, `diag_suppress`, and the base request, while `parse_file_sized` opts into the counted path. The code buffer remains 255 bytes and `em_entry` remains an offset; general program startup and frame sizing are separate work. Implemented by `parser.s`, `emit.s`, `emit.inc`, `sizing.s`, `diagnostics.s`, `diagcat.inc`, `output.s`, `formats.inc`, the counter/class/output/parser fixtures, and their scripts. | | |||||
| ## Deferred | ## Deferred | ||||
| @@ -131,3 +131,4 @@ After 20 errors the next one is replaced by E221 and compilation stops. | |||||
| | E225 | `RES_EXPR_TOO_DEEP` | EXPRESSION NESTED TOO DEEPLY | | | E225 | `RES_EXPR_TOO_DEEP` | EXPRESSION NESTED TOO DEEPLY | | ||||
| | E226 | `RES_TOO_MANY_IMPLEMENTS` | MORE THAN 255 IMPLEMENTED INTERFACES | | | E226 | `RES_TOO_MANY_IMPLEMENTS` | MORE THAN 255 IMPLEMENTED INTERFACES | | ||||
| | E227 | `RES_EMIT_FAILED` | CANNOT EMIT THIS CODE YET | | | E227 | `RES_EMIT_FAILED` | CANNOT EMIT THIS CODE YET | | ||||
| | E228 | `RES_SIZE_MISMATCH` | CODE SIZE CHANGED BETWEEN PASSES | | |||||
| @@ -19,7 +19,7 @@ later stories. | |||||
| | BASIC 7 stub | 12 bytes, `10 SYS 7181` | | | BASIC 7 stub | 12 bytes, `10 SYS 7181` | | ||||
| | Entry | `$1C0D` | | | Entry | `$1C0D` | | ||||
| | Sections | ascending addresses, zero-filled gaps | | | Sections | ascending addresses, zero-filled gaps | | ||||
| | MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size`, `CLASS name size destroy count ifdir` | | |||||
| | MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size`, `CLASS name size destroy count ifdir`, `METHOD name address` | | |||||
| | Startup | clears mailbox, producer `$03`, status PASS, then `RTS` | | | Startup | clears mailbox, producer `$03`, status PASS, then `RTS` | | ||||
| The fixed startup is copied from `src/runtime/startup.s`; `output.s` refuses | The fixed startup is copied from `src/runtime/startup.s`; `output.s` refuses | ||||
| @@ -71,6 +71,8 @@ the first field at +2, so generated code needs no runtime translation. | |||||
| | Program entry | `em_entry` = offset of the program body in `cg_code` | | | Program entry | `em_entry` = offset of the program body in `cg_code` | | ||||
| | Code buffer | `cg_code`, 255 bytes (E04-S03) | | | Code buffer | `cg_code`, 255 bytes (E04-S03) | | ||||
| | Final code base | `fl_code_base`, default `cg_code`; pass 3 uses `PRG_ENTRY_ADDR + pre-CODE bytes` | | | Final code base | `fl_code_base`, default `cg_code`; pass 3 uses `PRG_ENTRY_ADDR + pre-CODE bytes` | | ||||
| | Routine sizes | `em_routine_sizes`, one byte per routine, recorded by `em_end_func` | | |||||
| | Sizing mismatch | `sz_mismatch`; nonzero raises E228 after the emitting pass | | |||||
| | Print entries | `em_pr_text`/`em_pr_byte`/`em_pr_cr` cells at `$C0-$C5`, with `JMP (cell)` trampolines | | | Print entries | `em_pr_text`/`em_pr_byte`/`em_pr_cr` cells at `$C0-$C5`, with `JMP (cell)` trampolines | | ||||
| `EM_LP` and `EM_TMP` sit below `MB_SELF` (`$F9-$FA`) and clear of the frame | `EM_LP` and `EM_TMP` sit below `MB_SELF` (`$F9-$FA`) and clear of the frame | ||||
| @@ -83,8 +85,9 @@ Control-flow labels use the `flow.s` pool; `if`/`while`/`for` labels are | |||||
| released only after their fixups resolve. Method entry labels are allocated by | released only after their fixups resolve. Method entry labels are allocated by | ||||
| member row, so a forward self-call can target a method body parsed later. The | member row, so a forward self-call can target a method body parsed later. The | ||||
| 255-byte code buffer and the one-byte `em_entry` still bound the current | 255-byte code buffer and the one-byte `em_entry` still bound the current | ||||
| emitted program; pass 3 currently relocates absolute targets but does not yet | |||||
| replace either limit with count-mode sizing. | |||||
| emitted program. Count-mode sizing checks that the emitting pass keeps the | |||||
| same length and label offsets; a mismatch raises E228 instead of producing a | |||||
| shifted program. | |||||
| ## Heap Fixture (E05-S01) | ## Heap Fixture (E05-S01) | ||||
| @@ -113,13 +116,14 @@ wired. | |||||
| | Destructor | `$0000` means none | | | Destructor | `$0000` means none | | ||||
| | Interface directory | 4-byte entries: interface descriptor, method table | | | Interface directory | 4-byte entries: interface descriptor, method table | | ||||
| | Flags | reserved, must be 0; no parent-class slot | | | Flags | reserved, must be 0; no parent-class slot | | ||||
| | MAP | `CLASS name size destroy count ifdir` after sections | | |||||
| | MAP | `CLASS name size destroy count ifdir` and `METHOD name address` after sections | | |||||
| The ABI is fixed in `src/common/class.inc`; D134 records the provisional | The ABI is fixed in `src/common/class.inc`; D134 records the provisional | ||||
| decision. `src/runtime/objects.s` reads descriptors and objects without | decision. `src/runtime/objects.s` reads descriptors and objects without | ||||
| allocating. `output.s` writes class MAP records in `out_class` call order; the | |||||
| E05-S02 fixture checks a no-destructor/no-interface class and a destructor plus | |||||
| one interface entry. Interface dispatch remains E06 work. | |||||
| allocating. `output.s` writes class MAP records in `out_class` call order and method MAP | |||||
| records in `out_method` call order; the E05-S02 fixture checks a | |||||
| no-destructor/no-interface class, a destructor plus one interface entry, and | |||||
| two method addresses. Interface dispatch remains E06 work. | |||||
| ## Construction and Deletion (E05-S03) | ## Construction and Deletion (E05-S03) | ||||
| @@ -1,8 +1,9 @@ | |||||
| --- | --- | ||||
| story: E04-S07 | story: E04-S07 | ||||
| owner: codebuff | owner: codebuff | ||||
| status: in-progress | |||||
| status: done | |||||
| started: 2026-11-10 | started: 2026-11-10 | ||||
| completed: 2026-11-10 | |||||
| depends-on: [E04-S06] | depends-on: [E04-S06] | ||||
| --- | --- | ||||
| @@ -10,65 +11,62 @@ depends-on: [E04-S06] | |||||
| ## Intended change | ## Intended change | ||||
| Begin the pass-3 address path: relocate absolute flow targets against the | |||||
| final CODE base, add `sizing.s` address planning, and prove a real source file | |||||
| compiles to deterministic PRGs and runs in x128. | |||||
| Finish the pass-3 address path: count-mode sizing with zero mismatch, final | |||||
| section and method addresses in the MAP, unresolved-reference diagnostics, | |||||
| deterministic PRGs, and a real source file running in x128. | |||||
| ## File area | ## File area | ||||
| - `src/compiler/flow.s`, `src/compiler/emit.s`, `src/compiler/sizing.s` | |||||
| - `src/compiler/flow.s`, `emit.s`, `sizing.s`, `parser.s`, `diagnostics.s` | |||||
| - `src/compiler/output.s`, `src/common/formats.inc`, `diagcat.inc` | |||||
| - `tests/compiler/counter.n8`, `tests/compiler/counter-test.s` | - `tests/compiler/counter.n8`, `tests/compiler/counter-test.s` | ||||
| - `scripts/build-counter-test.ps1`, `scripts/verify-counter-test.ps1` | |||||
| - `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md` | |||||
| - `tests/runtime/class-test.s`, `tests/parser/unresolved.n8` | |||||
| - `scripts/build-*.ps1`, `scripts/verify-*.ps1` | |||||
| - `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md`, | |||||
| `docs/diagnostics.md` | |||||
| ## Acceptance evidence | ## Acceptance evidence | ||||
| - [x] Pass 3 can patch absolute labels/JMP/JSR against a final CODE base: | |||||
| `counter.n8`'s 175-byte routine runs from `CODE 1C4F`. | |||||
| - [x] `counter.n8` compiles from source and runs in x128: the generated PRG | |||||
| reports `total = 15` through the mailbox. | |||||
| - [x] `sizing.s` measures each routine in count mode so pass 2 and pass 3 | |||||
| produce identical lengths and label offsets; `sz_mismatch` is 0. | |||||
| - [x] Pass 3 patches absolute `JMP`/`JSR` label fixups; relative branches stay | |||||
| offset-based. `counter.n8` runs from `CODE 1C4F` with `total = 15`. | |||||
| - [x] MAP `ENTRY` and `SECTION` records carry final addresses; class records | |||||
| and `METHOD` entry addresses are emitted and checked by the class fixture. | |||||
| - [x] Forward references resolve; `tests/parser/unresolved.n8` reports exactly | |||||
| one `SEM_UNKNOWN_NAME` (E61) and no bad-operand follow-up. | |||||
| - [x] Recompiling the same source is byte-identical: `COUNTER.PRG` and | - [x] Recompiling the same source is byte-identical: `COUNTER.PRG` and | ||||
| `COUNTB.PRG` compare equal. | `COUNTB.PRG` compare equal. | ||||
| - [x] MAP `ENTRY` and SECTION records carry final addresses for STUB, START, | |||||
| and CODE. | |||||
| - [ ] Count-mode sizing for every routine with a zero mismatch counter. | |||||
| - [ ] MAP class-descriptor and method-entry address records. | |||||
| - [ ] One diagnostic for an unresolved method/class reference. | |||||
| - [ ] Reconfirm E202 overwrite refusal in the pass-3 path. | |||||
| ## Coordination notes | |||||
| - This is intentionally a partial claim; the story is not marked done. | |||||
| - Existing statement/call fixtures still use `cg_code` as the base, so their | |||||
| in-place execution behavior is unchanged. | |||||
| - The counter fixture owns its startup wrapper and frame base, like the | |||||
| E04-S05 hello fixture. General runtime startup remains open. | |||||
| - [x] E202 overwrite refusal still holds; the output suite leaves | |||||
| `COLLIDE.PRG`/`COLLIDE.MAP` unchanged and leaves no output after abort. | |||||
| - [x] `counter.n8` compiles from source and runs in x128 with `total = 15`. | |||||
| ## Work log | ## Work log | ||||
| - Added `fl_set_base`; absolute fixups now add the selected base instead of | |||||
| hard-coding `cg_code`. | |||||
| - Added `em_set_base`/`em_base_set`, applied at `em_reset`. | |||||
| - Added `sizing.s` to compute `PRG_ENTRY_ADDR + bytes-before-CODE`. | |||||
| - Added `counter.n8` and a compiler fixture that writes two PRGs and a MAP. | |||||
| - The first output attempt had `out_load`/`out_entry` zero, producing a 7 KB | |||||
| padded file; initializing them fixed the container and MAP entry. | |||||
| - The first run used `$CF80` as a frame while BASIC ROM was mapped; switched | |||||
| to CR `$0E` with `$BF80`, matching the D129 frame ABI. | |||||
| - Added `parse_file_base`: ordinary `parse_file` clears the requested base so | |||||
| existing in-place fixtures never see uninitialized BSS, while the counter | |||||
| installs its final code base explicitly. | |||||
| - Made repeated definition of a label at the same offset harmless; this keeps | |||||
| the forward-call fixture stable across BSS layout changes. | |||||
| - Counter verification passes with 175 code bytes and `total = 15`. | |||||
| - `flow.s` now resolves absolute `JMP`/`JSR` operands against `fl_code_base`; | |||||
| ordinary fixtures keep the default `cg_code` base. | |||||
| - `sizing.s` computes `PRG_ENTRY_ADDR + bytes-before-CODE` and installs it for | |||||
| the emitting pass. `sz_capture`/`sz_verify` compare code length, entry | |||||
| offset, per-routine sizes, and all label offsets. | |||||
| - `emit.s` records routine sizes in `em_routine_sizes` and selects count mode | |||||
| through `em_count`. `parse_file` clears count mode, diagnostic suppression, | |||||
| and any base request; `parse_file_sized` opts into the counted path. | |||||
| - `diag_suppress` prevents duplicate diagnostics during the emitting pass. | |||||
| `RES_SIZE_MISMATCH` (E228) reports a count/emit disagreement. | |||||
| - `output.s`/`formats.inc` add bounded `METHOD name address` MAP records after | |||||
| class records. The class fixture checks `METHOD DESTROY` and `METHOD STUB`. | |||||
| - Added the one-reference `unresolved.n8` parser fixture and the matching | |||||
| `SEM_UNKNOWN_NAME` expectation. | |||||
| ## Completion | ## Completion | ||||
| - Tests run: statements, calls, member, control, scalar, hello, parser, and | |||||
| counter suites all PASS after the BSS-layout move. | |||||
| - Results: counter emits 175 bytes; `COUNTER.PRG` and `COUNTB.PRG` are | |||||
| byte-identical; MAP contains `ENTRY 1C0D`, `START 1C0D 0042`, and | |||||
| `CODE 1C4F 00AF`; the generated PRG reports `total = 15`. | |||||
| - Remaining risks: code buffer remains 255 bytes, `em_entry` remains an | |||||
| offset, and the MAP still lacks method/class address records. No unresolved | |||||
| reference path or count-mode mismatch ledger exists yet. | |||||
| - Tests run: statements, calls, member, control, scalar, hello, parser, | |||||
| counter, output; class test separately. All PASS. | |||||
| - Parser: 42 fixtures, including `unresolved.n8` with exactly one diagnostic. | |||||
| - Counter: 175 code bytes; `sz_mismatch = 0`; MAP `ENTRY 1C0D`, | |||||
| `START 1C0D 0042`, `CODE 1C4F 00AF`; `COUNTER.PRG` and `COUNTB.PRG` | |||||
| byte-identical; generated PRG reports `total = 15`. | |||||
| - Class MAP: `CLASS EMPTY`, `CLASS BOX`, `METHOD DESTROY 2A56`, | |||||
| `METHOD STUB 2A57`; all descriptor/MAP flags pass. | |||||
| - Remaining risks: the code buffer remains 255 bytes and `em_entry` remains an | |||||
| offset; general runtime startup and frame sizing remain separate work. | |||||
| @@ -22,6 +22,7 @@ $link = Invoke-Nova128Link -Name "calls-test" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\codegen.s"), | (Join-Path $project "src\compiler\codegen.s"), | ||||
| (Join-Path $project "src\compiler\flow.s"), | (Join-Path $project "src\compiler\flow.s"), | ||||
| (Join-Path $project "src\compiler\members.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\emit.s"), | ||||
| (Join-Path $project "src\runtime\startup.s"), | (Join-Path $project "src\runtime\startup.s"), | ||||
| (Join-Path $project "tests\compiler\calls-test.s")) | (Join-Path $project "tests\compiler\calls-test.s")) | ||||
| @@ -20,6 +20,7 @@ $link = Invoke-Nova128Link -Name "hello-test" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\codegen.s"), | (Join-Path $project "src\compiler\codegen.s"), | ||||
| (Join-Path $project "src\compiler\flow.s"), | (Join-Path $project "src\compiler\flow.s"), | ||||
| (Join-Path $project "src\compiler\members.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\emit.s"), | ||||
| (Join-Path $project "src\compiler\interfaces.s"), | (Join-Path $project "src\compiler\interfaces.s"), | ||||
| (Join-Path $project "src\compiler\types.s"), | (Join-Path $project "src\compiler\types.s"), | ||||
| @@ -55,6 +55,7 @@ $script:ParserFixtures = [ordered]@{ | |||||
| "toobig.n8" = @("$t\too_big.n8", "rejected", "$t\too_big.expect") | "toobig.n8" = @("$t\too_big.n8", "rejected", "$t\too_big.expect") | ||||
| "interfaces.n8" = @("$t\interfaces.n8", "iface", "$t\interfaces.expect") | "interfaces.n8" = @("$t\interfaces.n8", "iface", "$t\interfaces.expect") | ||||
| "ifacebad.n8" = @("$t\iface_bad.n8", "exact", "$t\iface_bad.expect") | "ifacebad.n8" = @("$t\iface_bad.n8", "exact", "$t\iface_bad.expect") | ||||
| "unresolved.n8" = @("$p\unresolved.n8", "exact", "$p\unresolved.expect") | |||||
| } | } | ||||
| $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` | $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` | ||||
| @@ -69,6 +70,7 @@ $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\codegen.s"), | (Join-Path $project "src\compiler\codegen.s"), | ||||
| (Join-Path $project "src\compiler\flow.s"), | (Join-Path $project "src\compiler\flow.s"), | ||||
| (Join-Path $project "src\compiler\members.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\emit.s"), | ||||
| (Join-Path $project "src\compiler\interfaces.s"), | (Join-Path $project "src\compiler\interfaces.s"), | ||||
| (Join-Path $project "src\compiler\types.s"), | (Join-Path $project "src\compiler\types.s"), | ||||
| @@ -22,6 +22,7 @@ $link = Invoke-Nova128Link -Name "statements-test" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\codegen.s"), | (Join-Path $project "src\compiler\codegen.s"), | ||||
| (Join-Path $project "src\compiler\flow.s"), | (Join-Path $project "src\compiler\flow.s"), | ||||
| (Join-Path $project "src\compiler\members.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\emit.s"), | ||||
| (Join-Path $project "src\runtime\startup.s"), | (Join-Path $project "src\runtime\startup.s"), | ||||
| (Join-Path $project "tests\compiler\statements-test.s")) | (Join-Path $project "tests\compiler\statements-test.s")) | ||||
| @@ -39,6 +39,7 @@ $link = Invoke-Nova128Link -Name "n8check" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\names.s"), | (Join-Path $project "src\compiler\names.s"), | ||||
| (Join-Path $project "src\compiler\diagnostics.s"), | (Join-Path $project "src\compiler\diagnostics.s"), | ||||
| (Join-Path $project "src\compiler\parser.s"), | (Join-Path $project "src\compiler\parser.s"), | ||||
| (Join-Path $project "src\compiler\sizing.s"), | |||||
| (Join-Path $project "src\compiler\interfaces.s"), | (Join-Path $project "src\compiler\interfaces.s"), | ||||
| (Join-Path $project "src\compiler\types.s"), | (Join-Path $project "src\compiler\types.s"), | ||||
| (Join-Path $project "demo\n8check.s")) | (Join-Path $project "demo\n8check.s")) | ||||
| @@ -53,6 +53,7 @@ $mapPath = Join-Path $out "CLASSTEST.MAP" | |||||
| Read-DiskFile "classtest.map" $mapPath $true | Read-DiskFile "classtest.map" $mapPath $true | ||||
| $mapLines = ([IO.File]::ReadAllText($mapPath)).TrimEnd("`r", "`n") -split "`r`n|`r|`n" | $mapLines = ([IO.File]::ReadAllText($mapPath)).TrimEnd("`r", "`n") -split "`r`n|`r|`n" | ||||
| $destroy = Get-Nova128Label $labels "destroy_b" | $destroy = Get-Nova128Label $labels "destroy_b" | ||||
| $stub = Get-Nova128Label $labels "method_stub" | |||||
| $ifdir = Get-Nova128Label $labels "iface_dir" | $ifdir = Get-Nova128Label $labels "iface_dir" | ||||
| $expectedMap = @( | $expectedMap = @( | ||||
| "NOVA128 MAP 1", | "NOVA128 MAP 1", | ||||
| @@ -60,7 +61,9 @@ $expectedMap = @( | |||||
| "SECTION STUB 1C01 000C", | "SECTION STUB 1C01 000C", | ||||
| "SECTION START 1C0D 004A", | "SECTION START 1C0D 004A", | ||||
| ("CLASS EMPTY 0007 0000 00 0000"), | ("CLASS EMPTY 0007 0000 00 0000"), | ||||
| ("CLASS BOX 0023 {0:X4} 01 {1:X4}" -f $destroy, $ifdir)) | |||||
| ("CLASS BOX 0023 {0:X4} 01 {1:X4}" -f $destroy, $ifdir), | |||||
| ("METHOD DESTROY {0:X4}" -f $destroy), | |||||
| ("METHOD STUB {0:X4}" -f $stub)) | |||||
| if (($mapLines -join "`n") -ne ($expectedMap -join "`n")) { | if (($mapLines -join "`n") -ne ($expectedMap -join "`n")) { | ||||
| $failures += ("CLASSTEST.MAP differs:`n got: " + ($mapLines -join "`n ") + | $failures += ("CLASSTEST.MAP differs:`n got: " + ($mapLines -join "`n ") + | ||||
| "`n want: " + ($expectedMap -join "`n ")) | "`n want: " + ($expectedMap -join "`n ")) | ||||
| @@ -31,6 +31,7 @@ if ($mb[10] -eq 0) { $failures += "no generated code" } | |||||
| if ($mb[11] -ne 0) { $failures += "codegen error: $($mb[11])" } | if ($mb[11] -ne 0) { $failures += "codegen error: $($mb[11])" } | ||||
| if ($mb[12] -ne 0) { $failures += "flow error: $($mb[12])" } | if ($mb[12] -ne 0) { $failures += "flow error: $($mb[12])" } | ||||
| if ($mb[13] -ne 0) { $failures += "output error: $($mb[13])" } | if ($mb[13] -ne 0) { $failures += "output error: $($mb[13])" } | ||||
| if ($mb[14] -ne 0) { $failures += "sizing mismatch count: $($mb[14])" } | |||||
| $c1541 = Get-ViceTool "c1541" | $c1541 = Get-ViceTool "c1541" | ||||
| $prg = Join-Path $out "counter.prg" | $prg = Join-Path $out "counter.prg" | ||||
| @@ -105,3 +105,4 @@ | |||||
| DIAGNOSTIC RES_EXPR_TOO_DEEP, 225, "EXPRESSION NESTED TOO DEEPLY" | DIAGNOSTIC RES_EXPR_TOO_DEEP, 225, "EXPRESSION NESTED TOO DEEPLY" | ||||
| DIAGNOSTIC RES_TOO_MANY_IMPLEMENTS, 226, "MORE THAN 255 IMPLEMENTED INTERFACES" | DIAGNOSTIC RES_TOO_MANY_IMPLEMENTS, 226, "MORE THAN 255 IMPLEMENTED INTERFACES" | ||||
| DIAGNOSTIC RES_EMIT_FAILED, 227, "CANNOT EMIT THIS CODE YET" | DIAGNOSTIC RES_EMIT_FAILED, 227, "CANNOT EMIT THIS CODE YET" | ||||
| DIAGNOSTIC RES_SIZE_MISMATCH, 228, "CODE SIZE CHANGED BETWEEN PASSES" | |||||
| @@ -22,6 +22,7 @@ EM_ERR_LABEL = 7 ; no control-flow label available | |||||
| EM_KIND_LOCAL = 0 | EM_KIND_LOCAL = 0 | ||||
| EM_KIND_PARAM = 1 | EM_KIND_PARAM = 1 | ||||
| EM_MAX_ROUTINES = 24 | |||||
| EM_LAST_LOCAL = 1 | EM_LAST_LOCAL = 1 | ||||
| EM_LAST_SELF = 2 | EM_LAST_SELF = 2 | ||||
| @@ -17,6 +17,8 @@ OUT_SECTION_NAME_MAX = 12 | |||||
| OUT_RECORD_SIZE = 7 ; name ptr(2), name len(1), start(2), size(2) | OUT_RECORD_SIZE = 7 ; name ptr(2), name len(1), start(2), size(2) | ||||
| OUT_MAX_CLASSES = 8 | OUT_MAX_CLASSES = 8 | ||||
| OUT_CLASS_RECORD_SIZE = 10 ; name ptr(2), len(1), size(2), destroy(2), count(1), dir(2) | OUT_CLASS_RECORD_SIZE = 10 ; name ptr(2), len(1), size(2), destroy(2), count(1), dir(2) | ||||
| OUT_MAX_METHODS = 16 | |||||
| OUT_METHOD_RECORD_SIZE = 5 ; name ptr(2), len(1), address(2) | |||||
| ; out_begin/out_finish/out_abort result codes (out_err). | ; out_begin/out_finish/out_abort result codes (out_err). | ||||
| OUT_OK = 0 | OUT_OK = 0 | ||||
| @@ -27,5 +29,6 @@ OUT_ERR_NAME = 4 ; base or section name too long | |||||
| OUT_ERR_DISK = 5 ; KERNAL or DOS failure | OUT_ERR_DISK = 5 ; KERNAL or DOS failure | ||||
| OUT_ERR_STATE = 6 ; byte emitted outside a section | OUT_ERR_STATE = 6 ; byte emitted outside a section | ||||
| OUT_ERR_CLASSES = 7 ; more than OUT_MAX_CLASSES | OUT_ERR_CLASSES = 7 ; more than OUT_MAX_CLASSES | ||||
| OUT_ERR_METHODS = 8 ; more than OUT_MAX_METHODS | |||||
| .endif | .endif | ||||
| @@ -27,7 +27,7 @@ | |||||
| .import dio_open_cmd, dio_open_out, dio_read_status, dio_chkout, dio_putc | .import dio_open_cmd, dio_open_out, dio_read_status, dio_chkout, dio_putc | ||||
| .import dio_clrchn, dio_close, dio_command, dio_dos_code | .import dio_clrchn, dio_close, dio_command, dio_dos_code | ||||
| .export diag_init, diag_report, diag_finish | |||||
| .export diag_init, diag_report, diag_finish, diag_suppress | |||||
| .export diag_line, diag_col, diag_arg1, diag_arg1_len, diag_arg2, diag_arg2_len | .export diag_line, diag_col, diag_arg1, diag_arg1_len, diag_arg2, diag_arg2_len | ||||
| .export diag_count, diag_limit, diag_screen, diag_file_state, diag_buf | .export diag_count, diag_limit, diag_screen, diag_file_state, diag_buf | ||||
| @@ -59,6 +59,7 @@ diag_init: | |||||
| lda #$00 | lda #$00 | ||||
| sta diag_count | sta diag_count | ||||
| sta diag_stopped | sta diag_stopped | ||||
| sta diag_suppress | |||||
| sta diag_file_state | sta diag_file_state | ||||
| sta diag_arg1_len | sta diag_arg1_len | ||||
| sta diag_arg2_len | sta diag_arg2_len | ||||
| @@ -149,6 +150,12 @@ append_base_to_open: | |||||
| rts | rts | ||||
| diag_report: | diag_report: | ||||
| sta diag_code | |||||
| lda diag_suppress | |||||
| beq :+ | |||||
| clc | |||||
| rts | |||||
| : | |||||
| ldx diag_stopped | ldx diag_stopped | ||||
| beq :+ | beq :+ | ||||
| sec ; already stopped: report nothing more | sec ; already stopped: report nothing more | ||||
| @@ -166,8 +173,7 @@ diag_report: | |||||
| jsr emit | jsr emit | ||||
| sec | sec | ||||
| rts | rts | ||||
| @count: sta diag_code | |||||
| inc diag_count | |||||
| @count: inc diag_count | |||||
| jsr emit | jsr emit | ||||
| clc | clc | ||||
| rts | rts | ||||
| @@ -537,3 +543,4 @@ hdr_len: .res 1 | |||||
| msg_start: .res 1 | msg_start: .res 1 | ||||
| buf_len: .res 1 | buf_len: .res 1 | ||||
| diag_buf: .res DIAG_BUF_MAX | diag_buf: .res DIAG_BUF_MAX | ||||
| diag_suppress: .res 1 | |||||
| @@ -26,6 +26,7 @@ | |||||
| .include "../common/frames.inc" | .include "../common/frames.inc" | ||||
| .include "../common/emit.inc" | .include "../common/emit.inc" | ||||
| .include "../common/members.inc" | .include "../common/members.inc" | ||||
| .include "../common/flow.inc" | |||||
| .import cg_init, cg_put, cg_lda_imm, cg_ldx_imm, cg_ldy_imm, cg_lda_zp, cg_lda_zpy, cg_sta_zpy | .import cg_init, cg_put, cg_lda_imm, cg_ldx_imm, cg_ldy_imm, cg_lda_zp, cg_lda_zpy, cg_sta_zpy | ||||
| .import cg_sta_zp, cg_adc_zp, cg_sbc_zp, cg_cmp_zp, cg_adc_imm, cg_clc, cg_sec, cg_jsr_abs, cg_rts | .import cg_sta_zp, cg_adc_zp, cg_sbc_zp, cg_cmp_zp, cg_adc_imm, cg_clc, cg_sec, cg_jsr_abs, cg_rts | ||||
| @@ -37,7 +38,8 @@ | |||||
| .import sc_base, sc_val, sc_len | .import sc_base, sc_val, sc_len | ||||
| .export em_on, em_ctx, em_bad, em_last_kind | .export em_on, em_ctx, em_bad, em_last_kind | ||||
| .export em_reset, em_set_base, em_base_set, em_begin_func, em_begin_params, em_local, em_param | |||||
| .export em_reset, em_set_base, em_base_set, em_count, em_begin_func, em_begin_params, em_local, em_param | |||||
| .export em_n_routines, em_routine_sizes, em_routine_start | |||||
| .export em_expr_name, em_expr_const, em_push_left, em_bin | .export em_expr_name, em_expr_const, em_push_left, em_bin | ||||
| .export em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | .export em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | ||||
| .export em_target, em_store_target, em_print, em_end_func | .export em_target, em_store_target, em_print, em_end_func | ||||
| @@ -60,7 +62,13 @@ | |||||
| ; Pass 2 start: empty code buffer, no errors, no function, no slots. | ; Pass 2 start: empty code buffer, no errors, no function, no slots. | ||||
| em_reset: | em_reset: | ||||
| lda em_count | |||||
| beq @emit_mode | |||||
| lda #FL_MODE_COUNT | |||||
| jmp @init_flow | |||||
| @emit_mode: | |||||
| lda #$00 | lda #$00 | ||||
| @init_flow: | |||||
| jsr fl_init | jsr fl_init | ||||
| lda em_base_set | lda em_base_set | ||||
| beq @base_done | beq @base_done | ||||
| @@ -82,6 +90,7 @@ em_reset: | |||||
| sta em_locals | sta em_locals | ||||
| sta em_par_bytes | sta em_par_bytes | ||||
| sta em_entry | sta em_entry | ||||
| sta em_n_routines | |||||
| lda #$FF | lda #$FF | ||||
| sta em_loc_next | sta em_loc_next | ||||
| ldx #$FF | ldx #$FF | ||||
| @@ -104,6 +113,8 @@ em_set_base: | |||||
| ; A body starts: fresh local slots. Parameter offsets were assigned while the | ; A body starts: fresh local slots. Parameter offsets were assigned while the | ||||
| ; signature was parsed and are kept. | ; signature was parsed and are kept. | ||||
| em_begin_func: | em_begin_func: | ||||
| lda cg_len | |||||
| sta em_routine_start | |||||
| lda #$FF | lda #$FF | ||||
| sta em_loc_next | sta em_loc_next | ||||
| lda #$00 | lda #$00 | ||||
| @@ -558,6 +569,14 @@ em_end_func: | |||||
| beq @done | beq @done | ||||
| jsr cg_rts | jsr cg_rts | ||||
| bcs @fail | bcs @fail | ||||
| ldx em_n_routines | |||||
| cpx #EM_MAX_ROUTINES | |||||
| bcs @done | |||||
| lda cg_len | |||||
| sec | |||||
| sbc em_routine_start | |||||
| sta em_routine_sizes,x | |||||
| inc em_n_routines | |||||
| @done: clc | @done: clc | ||||
| rts | rts | ||||
| @fail: jmp em_fail | @fail: jmp em_fail | ||||
| @@ -1142,3 +1161,7 @@ em_meth_label: .res 256 | |||||
| em_entry: .res 1 | em_entry: .res 1 | ||||
| em_code_base: .res 2 | em_code_base: .res 2 | ||||
| em_base_set: .res 1 | em_base_set: .res 1 | ||||
| em_count: .res 1 | |||||
| em_n_routines: .res 1 | |||||
| em_routine_start: .res 1 | |||||
| em_routine_sizes: .res EM_MAX_ROUTINES | |||||
| @@ -39,7 +39,7 @@ | |||||
| .export out_begin, out_startup, out_section, out_end_section | .export out_begin, out_startup, out_section, out_end_section | ||||
| .export out_byte, out_bytes, out_finish, out_abort | .export out_byte, out_bytes, out_finish, out_abort | ||||
| .export out_class | |||||
| .export out_class, out_method, out_method_addr | |||||
| .export out_class_size, out_class_destroy, out_class_ifaces, out_class_ifdir | .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 | ||||
| @@ -150,6 +150,24 @@ set_class_ptr: | |||||
| bne @add | bne @add | ||||
| @done: rts | @done: rts | ||||
| ; out_rec_ptr = out_methods + out_index * OUT_METHOD_RECORD_SIZE. | |||||
| set_method_ptr: | |||||
| lda #<out_methods | |||||
| sta out_rec_ptr | |||||
| lda #>out_methods | |||||
| sta out_rec_ptr+1 | |||||
| ldx out_index | |||||
| beq @done | |||||
| @add: clc | |||||
| lda out_rec_ptr | |||||
| adc #OUT_METHOD_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. | ; Build and send "S0:name" on the command channel. | ||||
| out_scratch: | out_scratch: | ||||
| sta out_scratch_ptr | sta out_scratch_ptr | ||||
| @@ -235,6 +253,7 @@ out_begin: | |||||
| lda #$00 | lda #$00 | ||||
| sta out_count | sta out_count | ||||
| sta out_class_count | sta out_class_count | ||||
| sta out_method_count | |||||
| sta out_in_section | sta out_in_section | ||||
| sta out_prg_created | sta out_prg_created | ||||
| sta out_map_created | sta out_map_created | ||||
| @@ -611,6 +630,49 @@ out_class: | |||||
| sec | sec | ||||
| rts | rts | ||||
| ; A/X = method name, Y = length. out_method_addr supplies the final address. | |||||
| out_method: | |||||
| sta out_tmp | |||||
| stx out_tmp+1 | |||||
| sty out_tmp_len | |||||
| lda out_method_count | |||||
| cmp #OUT_MAX_METHODS | |||||
| bcs @too_many | |||||
| lda out_tmp_len | |||||
| cmp #OUT_SECTION_NAME_MAX + 1 | |||||
| bcs @bad_name | |||||
| lda out_method_count | |||||
| sta out_index | |||||
| jsr set_method_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_method_addr | |||||
| sta (out_rec_ptr),y | |||||
| iny | |||||
| lda out_method_addr+1 | |||||
| sta (out_rec_ptr),y | |||||
| inc out_method_count | |||||
| clc | |||||
| rts | |||||
| @too_many: | |||||
| lda #OUT_ERR_METHODS | |||||
| sta out_err | |||||
| sec | |||||
| rts | |||||
| @bad_name: | |||||
| lda #OUT_ERR_NAME | |||||
| sta out_err | |||||
| sec | |||||
| rts | |||||
| out_finish: | out_finish: | ||||
| jsr out_end_section | jsr out_end_section | ||||
| jsr out_close_prg | jsr out_close_prg | ||||
| @@ -812,6 +874,8 @@ out_write_map: | |||||
| inc out_index | inc out_index | ||||
| jmp @section | jmp @section | ||||
| @done: jsr out_write_classes | @done: jsr out_write_classes | ||||
| bcs @out | |||||
| jsr out_write_methods | |||||
| bcs @out | bcs @out | ||||
| clc | clc | ||||
| @out: rts | @out: rts | ||||
| @@ -921,6 +985,70 @@ out_write_classes: | |||||
| @done: clc | @done: clc | ||||
| @out: rts | @out: rts | ||||
| ; Write METHOD records after the CLASS records, in out_method call order. | |||||
| out_write_methods: | |||||
| lda #$00 | |||||
| sta out_index | |||||
| @method: | |||||
| lda out_index | |||||
| cmp out_method_count | |||||
| bne @body | |||||
| jmp @done | |||||
| @body: | |||||
| jsr set_method_ptr | |||||
| lda #<method_prefix | |||||
| ldx #>method_prefix | |||||
| ldy #method_prefix_len | |||||
| jsr out_text | |||||
| bcc @prefix_ok | |||||
| jmp @out | |||||
| @prefix_ok: | |||||
| 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 | |||||
| bcc @name_ok | |||||
| jmp @out | |||||
| @name_ok: | |||||
| ldy out_y | |||||
| iny | |||||
| bne @name | |||||
| @named: | |||||
| lda #' ' | |||||
| jsr out_char | |||||
| bcc @space_ok | |||||
| jmp @out | |||||
| @space_ok: | |||||
| ldy #3 | |||||
| lda (out_rec_ptr),y | |||||
| sta out_num | |||||
| iny | |||||
| lda (out_rec_ptr),y | |||||
| sta out_num+1 | |||||
| jsr out_hex4 | |||||
| bcc @addr_ok | |||||
| jmp @out | |||||
| @addr_ok: | |||||
| jsr out_cr | |||||
| bcc @line_ok | |||||
| jmp @out | |||||
| @line_ok: | |||||
| inc out_index | |||||
| jmp @method | |||||
| @done: clc | |||||
| @out: rts | |||||
| ; A/X = text, Y = length. | ; A/X = text, Y = length. | ||||
| out_text: | out_text: | ||||
| sta out_srcp | sta out_srcp | ||||
| @@ -1002,6 +1130,8 @@ section_prefix: .byte "SECTION " | |||||
| section_prefix_len = * - section_prefix | section_prefix_len = * - section_prefix | ||||
| class_prefix: .byte "CLASS " | class_prefix: .byte "CLASS " | ||||
| class_prefix_len = * - class_prefix | class_prefix_len = * - class_prefix | ||||
| method_prefix: .byte "METHOD " | |||||
| method_prefix_len = * - method_prefix | |||||
| hex_digits: .byte "0123456789ABCDEF" | hex_digits: .byte "0123456789ABCDEF" | ||||
| .segment "BSS" | .segment "BSS" | ||||
| @@ -1039,6 +1169,9 @@ out_map_is_open: .res 1 | |||||
| out_err: .res 1 | out_err: .res 1 | ||||
| out_sections: .res OUT_MAX_SECTIONS * OUT_RECORD_SIZE | out_sections: .res OUT_MAX_SECTIONS * OUT_RECORD_SIZE | ||||
| out_classes: .res OUT_MAX_CLASSES * OUT_CLASS_RECORD_SIZE | out_classes: .res OUT_MAX_CLASSES * OUT_CLASS_RECORD_SIZE | ||||
| out_methods: .res OUT_MAX_METHODS * OUT_METHOD_RECORD_SIZE | |||||
| out_method_count: .res 1 | |||||
| out_method_addr: .res 2 | |||||
| out_class_size: .res 2 | out_class_size: .res 2 | ||||
| out_class_destroy: .res 2 | out_class_destroy: .res 2 | ||||
| out_class_ifaces: .res 1 | out_class_ifaces: .res 1 | ||||
| @@ -47,7 +47,7 @@ | |||||
| .import src_line | .import src_line | ||||
| .import name_intern, name_find, name_declare | .import name_intern, name_find, name_declare | ||||
| .import names_count | .import names_count | ||||
| .import diag_report, diag_line, diag_col, diag_arg1, diag_arg1_len | |||||
| .import diag_report, diag_line, diag_col, diag_arg1, diag_arg1_len, diag_suppress, diag_count | |||||
| .import mmu_peek1, mmu_poke1 | .import mmu_peek1, mmu_poke1 | ||||
| .importzp mmu_ptr | .importzp mmu_ptr | ||||
| .import diag_arg2_len | .import diag_arg2_len | ||||
| @@ -56,7 +56,7 @@ | |||||
| .import ty_assign, ty_binary, ty_unary | .import ty_assign, ty_binary, ty_unary | ||||
| .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 | .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 | ||||
| .import assign_slots, check_interfaces | .import assign_slots, check_interfaces | ||||
| .import em_reset, em_set_base, em_base_set, em_on, em_ctx, em_bad, em_last_kind | |||||
| .import em_reset, em_set_base, em_base_set, em_count, em_on, em_ctx, em_bad, em_last_kind | |||||
| .import em_begin_func, em_begin_params, em_local, em_param | .import em_begin_func, em_begin_params, em_local, em_param | ||||
| .import em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | .import em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | ||||
| .import em_expr_name, em_expr_const, em_push_left, em_bin | .import em_expr_name, em_expr_const, em_push_left, em_bin | ||||
| @@ -65,8 +65,9 @@ | |||||
| .import em_open_block, em_cond_false, em_else, em_close_block | .import em_open_block, em_cond_false, em_else, em_close_block | ||||
| .import em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup | .import em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup | ||||
| .import em_return, em_method_begin, em_program_begin | .import em_return, em_method_begin, em_program_begin | ||||
| .import sz_capture, sz_verify | |||||
| .export parse_file, parse_file_base, tb_get, tb_set, mem_count, par_count | |||||
| .export parse_file, parse_file_base, parse_file_sized, tb_get, tb_set, mem_count, par_count | |||||
| .export gen_text, gen_text_len, gen_print_count, gen_bad | .export gen_text, gen_text_len, gen_print_count, gen_bad | ||||
| .export ex_trace_on, ex_min_sp | .export ex_trace_on, ex_min_sp | ||||
| .exportzp ex_tp | .exportzp ex_tp | ||||
| @@ -97,12 +98,26 @@ ex_tp: .res 2 ; test trace pointer (ex_trace_on) | |||||
| parse_file: | parse_file: | ||||
| lda #$00 | lda #$00 | ||||
| sta em_base_set | sta em_base_set | ||||
| sta em_count | |||||
| sta diag_suppress | |||||
| jmp parse_start | jmp parse_start | ||||
| ; A/X = final code base for pass-3 absolute fixups. Clears any pending base | ; A/X = final code base for pass-3 absolute fixups. Clears any pending base | ||||
| ; before installing this one, so BSS garbage cannot affect ordinary fixtures. | ; before installing this one, so BSS garbage cannot affect ordinary fixtures. | ||||
| parse_file_base: | parse_file_base: | ||||
| jsr em_set_base | jsr em_set_base | ||||
| lda #$00 | |||||
| sta em_count | |||||
| sta diag_suppress | |||||
| jmp parse_start | |||||
| ; Count pass, snapshot, then an emitting pass. The caller installs the final | |||||
| ; code base first. Diagnostics are reported only by the count pass. | |||||
| parse_file_sized: | |||||
| lda #$01 | |||||
| sta em_count | |||||
| lda #$00 | |||||
| sta diag_suppress | |||||
| parse_start: | parse_start: | ||||
| tsx | tsx | ||||
| stx parse_sp | stx parse_sp | ||||
| @@ -137,7 +152,33 @@ parse_start: | |||||
| beq :+ | beq :+ | ||||
| lda #E_RES_EMIT_FAILED | lda #E_RES_EMIT_FAILED | ||||
| jsr err2 | jsr err2 | ||||
| rts | |||||
| : | |||||
| lda em_count | |||||
| beq @done | |||||
| lda diag_count | |||||
| bne @done | |||||
| jsr sz_capture | |||||
| lda #$00 | |||||
| sta em_count | |||||
| lda #$01 | |||||
| sta diag_suppress | |||||
| jsr em_reset | |||||
| jsr parse_pass | |||||
| jsr em_finish | |||||
| lda #$00 | |||||
| sta diag_suppress | |||||
| lda em_bad | |||||
| beq :+ | |||||
| lda #E_RES_EMIT_FAILED | |||||
| jsr err2 | |||||
| rts | |||||
| : | : | ||||
| jsr sz_verify | |||||
| bcc @done | |||||
| lda #E_RES_SIZE_MISMATCH | |||||
| jsr diag_report | |||||
| @done: | |||||
| rts | rts | ||||
| ; Error limit reached: return straight to parse_file's caller. | ; Error limit reached: return straight to parse_file's caller. | ||||
| @@ -7,10 +7,14 @@ | |||||
| .setcpu "6502" | .setcpu "6502" | ||||
| .include "../common/formats.inc" | .include "../common/formats.inc" | ||||
| .include "../common/flow.inc" | |||||
| .include "../common/emit.inc" | |||||
| .import em_set_base | |||||
| .import em_set_base, em_entry, em_n_routines, em_routine_sizes | |||||
| .import cg_len, fl_get_label, fl_err | |||||
| .export sz_plan_code, sz_code_base | .export sz_plan_code, sz_code_base | ||||
| .export sz_capture, sz_verify, sz_mismatch | |||||
| .segment "CODE" | .segment "CODE" | ||||
| @@ -27,5 +31,119 @@ sz_plan_code: | |||||
| ldx sz_code_base+1 | ldx sz_code_base+1 | ||||
| jmp em_set_base | jmp em_set_base | ||||
| ; Snapshot the count-mode pass before the emitting pass resets the buffer. | |||||
| sz_capture: | |||||
| lda #$00 | |||||
| sta fl_err | |||||
| lda cg_len | |||||
| sta sz_len | |||||
| lda em_entry | |||||
| sta sz_entry | |||||
| lda em_n_routines | |||||
| sta sz_nr | |||||
| ldx #$00 | |||||
| @routine: | |||||
| cpx sz_nr | |||||
| beq @labels | |||||
| lda em_routine_sizes,x | |||||
| sta sz_sizes,x | |||||
| inx | |||||
| bne @routine | |||||
| @labels: | |||||
| lda #$00 | |||||
| sta sz_i | |||||
| @label: | |||||
| lda sz_i | |||||
| cmp #FL_LABELS | |||||
| beq @done | |||||
| jsr fl_get_label | |||||
| bcs @undefined | |||||
| txa | |||||
| ldy sz_i | |||||
| sta sz_labels,y | |||||
| jmp @next | |||||
| @undefined: | |||||
| ldy sz_i | |||||
| lda #$FF | |||||
| sta sz_labels,y | |||||
| @next: inc sz_i | |||||
| jmp @label | |||||
| @done: rts | |||||
| ; Compare the count-mode snapshot with the emitting pass. Every difference | |||||
| ; increments sz_mismatch; C=1 when anything differs. | |||||
| sz_verify: | |||||
| lda #$00 | |||||
| sta sz_mismatch | |||||
| sta fl_err | |||||
| lda cg_len | |||||
| cmp sz_len | |||||
| beq @entry | |||||
| inc sz_mismatch | |||||
| @entry: | |||||
| lda em_entry | |||||
| cmp sz_entry | |||||
| beq @count | |||||
| inc sz_mismatch | |||||
| @count: | |||||
| lda em_n_routines | |||||
| cmp sz_nr | |||||
| beq @routines | |||||
| inc sz_mismatch | |||||
| @routines: | |||||
| ldx #$00 | |||||
| @routine: | |||||
| cpx sz_nr | |||||
| beq @labels | |||||
| lda em_routine_sizes,x | |||||
| cmp sz_sizes,x | |||||
| beq @next_routine | |||||
| inc sz_mismatch | |||||
| @next_routine: | |||||
| inx | |||||
| bne @routine | |||||
| @labels: | |||||
| lda #$00 | |||||
| sta sz_i | |||||
| @label: | |||||
| lda sz_i | |||||
| cmp #FL_LABELS | |||||
| beq @done | |||||
| jsr fl_get_label | |||||
| bcs @undefined | |||||
| txa | |||||
| sta sz_off | |||||
| ldy sz_i | |||||
| lda sz_labels,y | |||||
| cmp sz_off | |||||
| beq @next | |||||
| inc sz_mismatch | |||||
| jmp @next | |||||
| @undefined: | |||||
| ldy sz_i | |||||
| lda sz_labels,y | |||||
| cmp #$FF | |||||
| beq @next | |||||
| inc sz_mismatch | |||||
| @next: inc sz_i | |||||
| jmp @label | |||||
| @done: | |||||
| lda #$00 | |||||
| sta fl_err | |||||
| lda sz_mismatch | |||||
| beq @ok | |||||
| sec | |||||
| rts | |||||
| @ok: clc | |||||
| rts | |||||
| .segment "BSS" | .segment "BSS" | ||||
| sz_code_base: .res 2 | |||||
| sz_code_base: .res 2 | |||||
| sz_len: .res 1 | |||||
| sz_entry: .res 1 | |||||
| sz_nr: .res 1 | |||||
| sz_i: .res 1 | |||||
| sz_off: .res 1 | |||||
| sz_mismatch: .res 1 | |||||
| sz_sizes: .res EM_MAX_ROUTINES | |||||
| sz_labels: .res FL_LABELS | |||||
| @@ -13,11 +13,12 @@ | |||||
| .import src_load, src_dos_code, src_kernal_err | .import src_load, src_dos_code, src_kernal_err | ||||
| .import names_init | .import names_init | ||||
| .import diag_init, diag_finish, diag_screen | .import diag_init, diag_finish, diag_screen | ||||
| .import parse_file_base | |||||
| .import parse_file_sized | |||||
| .import em_on, em_bad | .import em_on, em_bad | ||||
| .import cg_code, cg_len, cg_err | .import cg_code, cg_len, cg_err | ||||
| .import fl_err | .import fl_err | ||||
| .import sz_plan_code, sz_code_base | .import sz_plan_code, sz_code_base | ||||
| .import sz_mismatch | |||||
| .import out_begin, out_section, out_end_section, out_byte, out_bytes | .import out_begin, out_section, out_end_section, out_byte, out_bytes | ||||
| .import out_finish, out_abort, out_load, out_entry, out_start, out_err | .import out_finish, out_abort, out_load, out_entry, out_start, out_err | ||||
| .import gen_stub | .import gen_stub | ||||
| @@ -101,9 +102,7 @@ test_entry: | |||||
| lda #WRAPPER_LEN | lda #WRAPPER_LEN | ||||
| jsr sz_plan_code ; final code base for absolute fixups | jsr sz_plan_code ; final code base for absolute fixups | ||||
| lda sz_code_base | |||||
| ldx sz_code_base+1 | |||||
| jsr parse_file_base | |||||
| jsr parse_file_sized | |||||
| jsr diag_finish | jsr diag_finish | ||||
| sta diag_count | sta diag_count | ||||
| lda diag_count | lda diag_count | ||||
| @@ -116,10 +115,8 @@ test_entry: | |||||
| sta MB_DATA+3 | sta MB_DATA+3 | ||||
| lda fl_err | lda fl_err | ||||
| sta MB_DATA+4 | sta MB_DATA+4 | ||||
| lda sz_code_base | |||||
| lda sz_mismatch | |||||
| sta MB_DATA+6 | sta MB_DATA+6 | ||||
| lda sz_code_base+1 | |||||
| sta MB_DATA+7 | |||||
| lda diag_count | lda diag_count | ||||
| ON_NONZERO fail_diag | ON_NONZERO fail_diag | ||||
| @@ -577,7 +577,8 @@ fixtures: | |||||
| FIXTURE "TOOBIG.N8" | FIXTURE "TOOBIG.N8" | ||||
| FIXTURE "INTERFACES.N8" | FIXTURE "INTERFACES.N8" | ||||
| FIXTURE "IFACEBAD.N8" | FIXTURE "IFACEBAD.N8" | ||||
| FIXTURE_COUNT = 41 | |||||
| FIXTURE "UNRESOLVED.N8" | |||||
| FIXTURE_COUNT = 42 | |||||
| TRACE_INDEX = 28 ; PREC.N8 | TRACE_INDEX = 28 ; PREC.N8 | ||||
| messages: | messages: | ||||
| @@ -0,0 +1,3 @@ | |||||
| // One unresolved method reference; it must be reported once, not emitted as a | |||||
| // bad operand or a second error. | |||||
| SEM_UNKNOWN_NAME 3 10 | |||||
| @@ -0,0 +1,9 @@ | |||||
| class Box | |||||
| public method run() | |||||
| self.missing() | |||||
| end method | |||||
| end class | |||||
| program Main | |||||
| var b as Box | |||||
| end program | |||||
| @@ -16,10 +16,11 @@ | |||||
| .import out_begin, out_startup, out_finish, out_abort | .import out_begin, out_startup, out_finish, out_abort | ||||
| .import out_load, out_entry, out_err | .import out_load, out_entry, out_err | ||||
| .import out_class, out_class_size, out_class_destroy | .import out_class, out_class_size, out_class_destroy | ||||
| .import out_method, out_method_addr | |||||
| .import out_class_ifaces, out_class_ifdir | .import out_class_ifaces, out_class_ifdir | ||||
| .import obj_class, obj_size, obj_destroy, obj_iface_dir | .import obj_class, obj_size, obj_destroy, obj_iface_dir | ||||
| .export destroy_b, iface_dir | |||||
| .export destroy_b, iface_dir, method_stub | |||||
| .segment "LOADADDR" | .segment "LOADADDR" | ||||
| .word $1C01 | .word $1C01 | ||||
| @@ -197,9 +198,12 @@ test_entry: | |||||
| ldx #>name_source | ldx #>name_source | ||||
| ldy #name_source_len | ldy #name_source_len | ||||
| jsr out_begin | jsr out_begin | ||||
| bcs @p7_abort | |||||
| jsr out_startup | |||||
| bcs @p7_abort | |||||
| bcc :+ | |||||
| jmp @p7_abort | |||||
| : jsr out_startup | |||||
| bcc :+ | |||||
| jmp @p7_abort | |||||
| : | |||||
| lda #7 ; EMPTY: size 7, no destructor/interface | lda #7 ; EMPTY: size 7, no destructor/interface | ||||
| sta out_class_size | sta out_class_size | ||||
| @@ -236,6 +240,26 @@ test_entry: | |||||
| jsr out_class | jsr out_class | ||||
| bcs @p7_abort | bcs @p7_abort | ||||
| lda #<destroy_b | |||||
| sta out_method_addr | |||||
| lda #>destroy_b | |||||
| sta out_method_addr+1 | |||||
| lda #<name_destroy | |||||
| ldx #>name_destroy | |||||
| ldy #name_destroy_len | |||||
| jsr out_method | |||||
| bcs @p7_abort | |||||
| lda #<method_stub | |||||
| sta out_method_addr | |||||
| lda #>method_stub | |||||
| sta out_method_addr+1 | |||||
| lda #<name_stub | |||||
| ldx #>name_stub | |||||
| ldy #name_stub_len | |||||
| jsr out_method | |||||
| bcs @p7_abort | |||||
| jsr out_finish | jsr out_finish | ||||
| bcs @p7_fail | bcs @p7_fail | ||||
| lda #$01 | lda #$01 | ||||
| @@ -331,6 +355,10 @@ name_empty: .byte "EMPTY" | |||||
| name_empty_len = * - name_empty | name_empty_len = * - name_empty | ||||
| name_box: .byte "BOX" | name_box: .byte "BOX" | ||||
| name_box_len = * - name_box | name_box_len = * - name_box | ||||
| name_destroy: .byte "DESTROY" | |||||
| name_destroy_len = * - name_destroy | |||||
| name_stub: .byte "STUB" | |||||
| name_stub_len = * - name_stub | |||||
| messages: | messages: | ||||
| ok_message: .byte "NOVA128 CLASS TEST OK", $0D, $00 | ok_message: .byte "NOVA128 CLASS TEST OK", $0D, $00 | ||||
| fail_message: .byte "NOVA128 CLASS TEST FAIL", $0D, $00 | fail_message: .byte "NOVA128 CLASS TEST FAIL", $0D, $00 | ||||
Powered by TurnKey Linux.