Adds control flow, field access, forward self calls, method/program entry tracking, and E227 emission-failure reporting. Print vectors now live in safe zero-page cells to avoid the 6502 indirect-JMP page bug.main
| @@ -549,13 +549,15 @@ rejected before code generation: the parser reports `ACC_PRIVATE_MEMBER` | |||||
| (E120) for a private field or method reached outside its class, so this layer | (E120) for a private field or method reached outside its class, so this layer | ||||
| never sees one. | never sees one. | ||||
| ### 7.11 Statement emission (E04-S06, in progress) | |||||
| ### 7.11 Statement emission (E04-S06) | |||||
| `src/compiler/emit.s` is the first emitter driven by the parser itself. Pass 2 | |||||
| `src/compiler/emit.s` is the emitter driven by the parser itself. Pass 2 | |||||
| parses statements for type checking; when `em_on` is set, the same parse also | parses statements for type checking; when `em_on` is set, the same parse also | ||||
| emits bytes into the shared `codegen.s` buffer. `parse_file` resets the | emits bytes into the shared `codegen.s` buffer. `parse_file` resets the | ||||
| emitter and the buffer at the start of pass 2, and pass 1 assigns the same | emitter and the buffer at the start of pass 2, and pass 1 assigns the same | ||||
| frame slots without emitting, so the two passes agree by construction. | |||||
| frame slots without emitting, so the two passes agree by construction. Parser | |||||
| hooks are gated by both `em_on` and pass 2, so parser fixtures that only | |||||
| type-check leave the emitter idle. | |||||
| Parameters take `FP+2` upward in declaration order and locals `FP-1` downward, | Parameters take `FP+2` upward in declaration order and locals `FP-1` downward, | ||||
| with one byte for boolean/byte/char and two for integer/word. A slot is not | with one byte for boolean/byte/char and two for integer/word. A slot is not | ||||
| @@ -567,18 +569,43 @@ high byte plus the offset's sign byte), leaves Y = 0, and the load or store uses | |||||
| sequence. | sequence. | ||||
| Byte `+` and `-` push the left operand, evaluate the right, then combine | Byte `+` and `-` push the left operand, evaluate the right, then combine | ||||
| through `EM_TMP`. `print` emits through `em_pr_text`, `em_pr_byte`, and | |||||
| `em_pr_cr`, which the caller fills; each call goes through a `JMP (cell)` | |||||
| trampoline, so the test records what the program asked to print instead of | |||||
| depending on CHROUT's mapping. A `print` that captured a string literal passes | |||||
| `gen_text` and its length; otherwise it passes the byte in A. | |||||
| The `statements.n8` fixture parses two locals, three assignments, an | |||||
| addition, and two prints, then JSRs the 138 emitted bytes in place. It checks | |||||
| `a = 10`, `b = 7`, and the recorded stream `SUM`, CR, `$FF`, `$0A`, CR. Control | |||||
| flow, calls, field stores, word values, arrays, the frame prologue, and | |||||
| reporting `em_bad` as a diagnostic remain open; E04-S07 owns sizing and pass-3 | |||||
| addresses. | |||||
| through `EM_TMP`. Comparisons emit `CMP` and record the operator in | |||||
| `em_cmp_op`; `em_cond_false` turns those flags into the appropriate branch, | |||||
| including the two-branch forms for `>` and `<=`. | |||||
| Control flow uses `flow.s` labels and fixups: `if`/`else`, `while`, a | |||||
| positive-step-1 `for` with a constant byte end, and `return`. Methods get | |||||
| stable entry labels 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 can run the program after class methods. `em_finish` | |||||
| checks every flow fixup and converts `em_bad` to | |||||
| `E_RES_EMIT_FAILED` (E227) after pass 2. A failed emit leaves only the bounded | |||||
| code buffer to inspect; it writes no output file. | |||||
| `self.field` loads and stores through `members.s`. A concrete call loads | |||||
| `MB_SELF`, emits `JSR` to the method label, and preserves a byte result. The | |||||
| current call path accepts only zero-argument self calls; the existing | |||||
| `members.s` discard path is exercised with a zero-byte argument list. | |||||
| `print` emits through `em_pr_text`, `em_pr_byte`, and `em_pr_cr` at `$C0-$C5`, | |||||
| which the caller fills; each call goes through a `JMP (cell)` trampoline, so the | |||||
| test records what the program asked to print instead of depending on CHROUT's | |||||
| mapping. Keeping the cells in zero page also keeps their low byte away from | |||||
| `$FF`, avoiding the 6502 indirect-JMP page bug without run-time alignment. A | |||||
| `print` that captured a string literal passes `gen_text` and its length; | |||||
| otherwise it passes the byte in A. | |||||
| The `statements.n8` fixture parses a `for`, an `if`/`else`, and a `while`, then | |||||
| JSRs the 244 emitted bytes in place. It checks `a = 2` (the loop variable one | |||||
| past the end value) and `b = 3`, with no recorded print output. The | |||||
| `calls.n8` fixture parses a method that increments `self.n`, calls a forward | |||||
| method that increments it again, and explicitly returns; the 53 emitted bytes | |||||
| leave `n = 2`. | |||||
| Word values, call arguments, arrays, method result forwarding, the frame | |||||
| 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 | |||||
| the end value. E04-S07 owns those limits and the 255-byte code buffer. | |||||
| ## 8. Heap and Ownership | ## 8. Heap and Ownership | ||||
| @@ -64,6 +64,7 @@ documented default. `Proposed` means a later change still needs approval. | |||||
| | D134 | E05-S02 class descriptors are 8-byte packed records: 2-byte instance size (including the object header), 2-byte destructor address (0 = none), 1-byte interface count, 2-byte interface-directory pointer (0 when empty), and a reserved flags byte that must be 0. There is no parent-class slot. Objects begin with a 2-byte descriptor pointer and fields follow at +2. Interface-directory entries are 4 bytes: interface-descriptor pointer and method-table pointer. `objects.s` provides read-only accessors; `output.s` writes `CLASS name size destroy count ifdir` records after sections. | Makes object identity, size/deletion, and the future interface path deterministic before construction/dispatch. Flags are reserved for non-inheritance metadata; interface descriptor contents and generated `new` remain E05-S03/E06. Implemented by `class.inc`, `objects.s`, and the class fixture. | | | D134 | E05-S02 class descriptors are 8-byte packed records: 2-byte instance size (including the object header), 2-byte destructor address (0 = none), 1-byte interface count, 2-byte interface-directory pointer (0 when empty), and a reserved flags byte that must be 0. There is no parent-class slot. Objects begin with a 2-byte descriptor pointer and fields follow at +2. Interface-directory entries are 4 bytes: interface-descriptor pointer and method-table pointer. `objects.s` provides read-only accessors; `output.s` writes `CLASS name size destroy count ifdir` records after sections. | Makes object identity, size/deletion, and the future interface path deterministic before construction/dispatch. Flags are reserved for non-inheritance metadata; interface descriptor contents and generated `new` remain E05-S03/E06. Implemented by `class.inc`, `objects.s`, and the class fixture. | | ||||
| | D136 | E05-S04 member emission lives in `src/compiler/members.s`. A generated method body starts with `mb_self_store`, recording the incoming self in zero-page `MB_SELF` (`$F9-$FA`). `self.field` emits `LDY #offset` plus `LDA (MB_SELF),Y` or `STA (MB_SELF),Y`, with a second byte for word fields; the word store moves the high byte with `TXA` because the 6502 has no `STX (zp),Y`. `mb_point` loads any two-byte object reference into `MB_SELF` for a field or call on another receiver. A concrete call emits `mb_self_load`, `JSR target`, then `frame_discard` for the argument bytes, preserving a byte or word result. `codegen.s` gains `cg_ldy_imm`, the zero-page forms, and the indexed-indirect forms. | Gives generated methods the member half of the D129 ABI without freezing parser integration or interface dispatch. One receiver pointer keeps field access at four bytes per byte field and stays clear of `$FB-$FE` (link-time assertion). Private access never reaches this layer: the parser reports `ACC_PRIVATE_MEMBER` (E120) first. Implemented by `members.s`, `members.inc`, `codegen.s`, and the member fixture. | | | D136 | E05-S04 member emission lives in `src/compiler/members.s`. A generated method body starts with `mb_self_store`, recording the incoming self in zero-page `MB_SELF` (`$F9-$FA`). `self.field` emits `LDY #offset` plus `LDA (MB_SELF),Y` or `STA (MB_SELF),Y`, with a second byte for word fields; the word store moves the high byte with `TXA` because the 6502 has no `STX (zp),Y`. `mb_point` loads any two-byte object reference into `MB_SELF` for a field or call on another receiver. A concrete call emits `mb_self_load`, `JSR target`, then `frame_discard` for the argument bytes, preserving a byte or word result. `codegen.s` gains `cg_ldy_imm`, the zero-page forms, and the indexed-indirect forms. | Gives generated methods the member half of the D129 ABI without freezing parser integration or interface dispatch. One receiver pointer keeps field access at four bytes per byte field and stays clear of `$FB-$FE` (link-time assertion). Private access never reaches this layer: the parser reports `ACC_PRIVATE_MEMBER` (E120) first. Implemented by `members.s`, `members.inc`, `codegen.s`, and the member 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. | | | 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. | | |||||
| ## Deferred | ## Deferred | ||||
| @@ -130,3 +130,4 @@ After 20 errors the next one is replaced by E221 and compilation stops. | |||||
| | E224 | `RES_TOO_MANY_PARAMS` | MORE THAN 255 PARAMETERS | | | E224 | `RES_TOO_MANY_PARAMS` | MORE THAN 255 PARAMETERS | | ||||
| | 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 | | |||||
| @@ -65,8 +65,12 @@ the first field at +2, so generated code needs no runtime translation. | |||||
| | Combine temp | `EM_TMP` = `$F7` | | | Combine temp | `EM_TMP` = `$F7` | | ||||
| | Parameters | `FP+2` upward, declaration order | | | Parameters | `FP+2` upward, declaration order | | ||||
| | Locals | `FP-1` downward, declaration order | | | Locals | `FP-1` downward, declaration order | | ||||
| | For end values | `EM_FOR_END` = `$D0-$E0`, one byte per block depth | | |||||
| | Flow labels / fixups | 64 labels, 128 fixups (BSS, no fixed address) | | |||||
| | Method labels | 256 bytes, one entry label by member row | | |||||
| | 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) | | ||||
| | Print entries | `em_pr_text`/`em_pr_byte`/`em_pr_cr` cells 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 | ||||
| ABI at `$FB-$FE`; `emit.s` asserts at link time that `EM_TMP` does not overlap | ABI at `$FB-$FE`; `emit.s` asserts at link time that `EM_TMP` does not overlap | ||||
| @@ -74,6 +78,12 @@ ABI at `$FB-$FE`; `emit.s` asserts at link time that `EM_TMP` does not overlap | |||||
| and parameters together must stay under 127 bytes until pass-3 sizing assigns | and parameters together must stay under 127 bytes until pass-3 sizing assigns | ||||
| frame sizes. | frame sizes. | ||||
| 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 | |||||
| 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` bound the current emitted | |||||
| program; E04-S07 replaces both with the sizing and pass-3 address pass. | |||||
| ## Heap Fixture (E05-S01) | ## Heap Fixture (E05-S01) | ||||
| | Element | Value | | | Element | Value | | ||||
| @@ -1,8 +1,9 @@ | |||||
| --- | --- | ||||
| story: E04-S06 | story: E04-S06 | ||||
| owner: codebuff | owner: codebuff | ||||
| status: in-progress | |||||
| status: done | |||||
| started: 2026-11-09 | started: 2026-11-09 | ||||
| completed: 2026-11-09 | |||||
| depends-on: [E04-S04, E04-S05, E05-S04, E03-S04] | depends-on: [E04-S04, E04-S05, E05-S04, E03-S04] | ||||
| --- | --- | ||||
| @@ -10,105 +11,104 @@ depends-on: [E04-S04, E04-S05, E05-S04, E03-S04] | |||||
| ## Intended change | ## Intended change | ||||
| Give the parser a first real emission path: while pass 2 type-checks a body, | |||||
| emit code for byte locals and parameters, number and char constants, `+`/`-`, | |||||
| assignment, and `print`. The emitted bytes run in place in x128, so a fixture | |||||
| can parse a source file and check both the resulting locals and the bytes the | |||||
| program asked to print. | |||||
| Complete the parser's pass-2 emission path for byte procedural code: numeric | |||||
| and char expressions, assignments, comparisons, `if`/`else`, `while`, `for`, | |||||
| `return`, `print`, `self.field`, and concrete self calls. Each method stores | |||||
| its incoming self before any statement, and a program body's offset is | |||||
| recorded so a fixture can execute it after class methods. Emitter failures | |||||
| become a stable diagnostic. | |||||
| ## File area | ## File area | ||||
| - `src/compiler/emit.s`, `src/common/emit.inc` (new) | |||||
| - `src/compiler/parser.s` (pass-2 hooks), `src/compiler/codegen.s` (forms) | |||||
| - `src/compiler/emit.s`, `src/common/emit.inc` | |||||
| - `src/compiler/flow.s`, `src/common/flow.inc` | |||||
| - `src/compiler/parser.s` (pass-2 hooks and long-branch dispatch) | |||||
| - `src/compiler/codegen.s` (`cg_cmp_zp`) | |||||
| - `src/common/diagcat.inc` (E227) | |||||
| - `tests/compiler/statements.n8`, `tests/compiler/statements-test.s` | - `tests/compiler/statements.n8`, `tests/compiler/statements-test.s` | ||||
| - `tests/compiler/calls.n8`, `tests/compiler/calls-test.s` | |||||
| - `scripts/build-statements-test.ps1`, `scripts/verify-statements-test.ps1` | - `scripts/build-statements-test.ps1`, `scripts/verify-statements-test.ps1` | ||||
| - parser-linking build scripts (link `codegen.s` and `emit.s`) | |||||
| - `scripts/build-calls-test.ps1`, `scripts/verify-calls-test.ps1` | |||||
| - parser/hello/statements build scripts (link `flow.s`, `members.s`, and | |||||
| `emit.s` where needed) | |||||
| - `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md` | - `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md` | ||||
| ## Acceptance evidence | ## Acceptance evidence | ||||
| - [x] Partly: statements emit through `codegen.s` — assignment, `+`/`-`, and | |||||
| `print`. `if`/`else`, `while`, `for`, and `return` do not emit yet. | |||||
| - [x] Partly: temporaries follow D129 for byte values (left operand on the | |||||
| hardware stack, combine through `EM_TMP`); results are in A. Parameters | |||||
| get `FP+2` slots in declaration order; argument pushing and discard are | |||||
| not emitted yet. | |||||
| - [ ] `self.field` and concrete calls through `members.s` — not emitted yet. | |||||
| `members.s` (E05-S04) and the codegen forms it needs are in place. | |||||
| - [x] Partly: emitter failures set `em_bad` (`EM_ERR_SIZE`, `EM_ERR_TARGET`, | |||||
| `EM_ERR_OP`, `EM_ERR_STATE`) and `cg_err` keeps the buffer detail; a | |||||
| failed emit writes nothing outside `cg_code`. No diagnostic is reported | |||||
| through `diag_report` yet. | |||||
| - [x] Partly: a source program with statements and expressions runs in x128 | |||||
| with an expected signature (see below). The concrete call in the | |||||
| criterion is not part of it yet. | |||||
| - [x] Statements and expressions emit through `codegen.s`/`flow.s`: | |||||
| assignment, byte `+`/`-`, comparisons, `if`/`else`, `while`, `for`, | |||||
| `return`, and `print`. Byte control-flow fixture executes in x128. | |||||
| - [x] Byte temporaries follow D129: the left operand is on the hardware stack, | |||||
| `EM_TMP` combines it, parameters use `FP+2`, and locals use `FP-1`. | |||||
| Concrete calls use the existing zero-byte discard path; argument pushing | |||||
| is not emitted in this claim. | |||||
| - [x] `self.field` loads/stores the parser's field offset through `members.s`. | |||||
| Forward zero-argument calls through self emit a stable method label and | |||||
| direct `JSR`; `self` is stored at method entry. | |||||
| - [x] Emitter failures set `em_bad` and become `E_RES_EMIT_FAILED` (E227) | |||||
| after pass 2. A failed emit writes no output file. | |||||
| - [x] A source program with statements and one concrete call executes in x128: | |||||
| `calls.n8` leaves `Counter.n = 2` after `both()` and a forward `tick()`. | |||||
| - [x] `return` is emitted, including an explicit `return` in the called | |||||
| method. | |||||
| - [ ] Full D129 argument emission and word results are not implemented; they | |||||
| remain E04-S07 work, along with arrays and the emitted frame prologue. | |||||
| ## Work log | ## Work log | ||||
| - `pwsh -File scripts/verify-statements-test.ps1` (final): PASS — 138 bytes | |||||
| emitted, `a=10`, `b=7`, `cg_err=0`, no diagnostics, recorded output | |||||
| - Baseline statements fixture: PASS — 138 bytes, `a=10`, `b=7`, recorded | |||||
| `SUM 0D FF 0A 0D`. | `SUM 0D FF 0A 0D`. | ||||
| - `verify-member-test`, `verify-control-test`, `verify-scalar-test`: PASS | |||||
| unchanged. | |||||
| - Bugs found while bringing the path up: `(FRAME_FP),Y` cannot address `FP-1` | |||||
| (it adds 255), so slots are reached through `EM_LP = FP + signed offset`; | |||||
| `em_stmt_begin` must not clear the target `em_target` captured; the store | |||||
| must keep A across the pointer setup (`PHA`/`PLA`); `cg_len` was | |||||
| uninitialized and pass 1 also emitted, so `em_reset` now calls `cg_init` and | |||||
| assignment emission is gated on pass 2. | |||||
| - Parser pass-2 growth pushed `beq @group` out of range; it now uses the | |||||
| branch-over-`JMP` form. | |||||
| - `build-hello-test.ps1` and `build-parser-test.ps1` were rewritten with the | |||||
| `codegen.s`/`emit.s` entries (a sed pass had corrupted their path strings). | |||||
| - `verify-hello-test`: PASS — diagnostics 0, one 18-byte print, HELLO.PRG 118 | |||||
| bytes, `HELLO FROM NOVA128` on row 13. | |||||
| - `verify-parser-test` failed at first on the expression-trace fixture | |||||
| (`prec.n8`, record 28): its `$FB` trace length came back as `$FF92`, so the | |||||
| host walker ran off the end of the dump. The trace itself was fine — a | |||||
| captured dump shows the payload starting `FF 06 00 03 07 0B 0F 9A 98`. | |||||
| - Root cause in the fixture: `dump_trace` emitted the low length byte between | |||||
| the two `sbc`s, and `emit` clears carry, so the high byte lost its borrow | |||||
| ($97-$05=$92 then $6A-$6A-0 = `$FF` instead of `$00`). Both length bytes | |||||
| are now computed before either is emitted. | |||||
| - `verify-parser-test`: PASS — 41 fixtures, `prec.n8` reports 15 traced | |||||
| statements. | |||||
| - Added control-flow emission through `flow.s`: labels grew to 64 and fixups | |||||
| to 128. `fl_release` is intentionally a no-op because releasing a label can | |||||
| strand unresolved fixups; closed-block labels therefore remain allocated | |||||
| until the pass ends. | |||||
| - Added comparison-flag lowering, `if`/`else`, `while`, positive-step `for` | |||||
| with a constant byte end, and `return`. The fixture's `for a = 1 to 1` | |||||
| leaves `a = 2`, which is the documented one-past-end behavior. | |||||
| - Added method entry labels allocated by member row, `mb_self_store` at method | |||||
| entry, `self.field` loads/stores, and zero-argument concrete self calls. | |||||
| `em_entry` records the program body's offset so the fixture does not run a | |||||
| class method by accident. | |||||
| - Added `E_RES_EMIT_FAILED` (227) and the post-pass-2 `em_bad` check. | |||||
| - `calls-test` initially failed because its `fl_err` success check jumped on a | |||||
| nonzero value; corrected to jump on zero. It then passed with 53 bytes. | |||||
| - Parser regression initially reported E227 for type-only fixtures because | |||||
| the expression helpers emitted whenever `em_ctx` was set, even when `em_on` | |||||
| was off. `em_expr_name`, `em_expr_const`, `em_push_left`, and `em_bin` now | |||||
| return immediately when `em_on` is zero, and the parser fixture disables | |||||
| emission explicitly. The 41-fixture parser suite passed afterward. | |||||
| - `verify-hello-test` initially missed the PRG output because it polled screen | |||||
| memory without resuming the emulator. The verifier now sends `x` each poll; | |||||
| hello passes with the text on row 13. | |||||
| - Removed temporary stage markers from `statements-test.s`; `diag_screen` is | |||||
| off again. | |||||
| - The expanded code shifted the BSS print vectors; `statements-test` then hit | |||||
| the linker's indirect-JMP page assertion. Moved the three vectors to | |||||
| absolute zero-page cells `$C0-$C5` and reserved six BSS bytes so the existing | |||||
| emitter-state layout is unchanged. The assertion and address-size warning | |||||
| are gone; statements and calls both pass. | |||||
| ## Completion | ## Completion | ||||
| - Tests run: statements, member, control, scalar, hello, and parser fixtures | |||||
| all pass. | |||||
| - Results: statement emission runs and is deterministic; 138 bytes for the | |||||
| fixture source. | |||||
| - Tests run: statements, calls, member, control, scalar, hello, and parser | |||||
| fixtures in x128. All pass with the current code. | |||||
| - Results: | |||||
| - statements: 244 bytes, `a=2`, `b=3`, no diagnostics, no recorded print. | |||||
| - calls: 53 bytes, diagnostics 0, `em_bad=0`, `cg_err=0`, `fl_err=0`, | |||||
| `Counter.n=2`. | |||||
| - hello: 118-byte PRG, `HELLO FROM NOVA128` displayed on row 13. | |||||
| - parser: 41 fixtures, lowest expression stack pointer `$A7`. | |||||
| - Files changed: as listed above. | - Files changed: as listed above. | ||||
| - Remaining risks: the code buffer is still 255 bytes; the print path reads | |||||
| the single `gen_text` capture, so two string literals in one program do not | |||||
| work; the frame prologue is the fixture's, not emitted; nested expressions | |||||
| mix one `EM_TMP`, so deeper shapes need a spill rule; no field stores, no | |||||
| calls, no word values, no arrays. | |||||
| - Remaining risks: only byte expressions and assignments emit; word values, | |||||
| call arguments, arrays, and method result forwarding are deferred. `for` | |||||
| requires a constant byte end and step 1. `return` is a bare `RTS` until the | |||||
| emitted frame prologue exists. The code buffer is still 255 bytes, and E227 | |||||
| is reported at the end of the parse rather than at the failing source line. | |||||
| The print vectors occupy `$C0-$C5`; anything added there must preserve their | |||||
| non-`$FF` low bytes and the reserved six-byte BSS gap. | |||||
| ## Next stepControl flow through `flow.s`, then calls and `self.field` through | |||||
| `members.s`, then report `em_bad` as a diagnostic. | |||||
| ## Next step | |||||
| Design notes for the control-flow step, worked out while reading `flow.s`: | |||||
| - `flow.s` already writes into the shared code buffer, so `em_reset` can call | |||||
| `fl_init(FL_MODE_EMIT)` instead of `cg_init` and reuse its labels, fixed | |||||
| five-byte branches, and `JMP` fixups for `if`/`while`. `em_end_func` then | |||||
| calls `fl_finish` before the final `RTS`. | |||||
| - Label ids cannot be indexed by block depth alone: a second `if` at the same | |||||
| depth would redefine the label and `fl_label` rejects that. `flow.s` needs | |||||
| `fl_release` (resolve and patch the pending fixups that name a label, then | |||||
| clear its definition) so each closed block hands its two ids back. With two | |||||
| ids per block and `PARSE_MAX_DEPTH = 16`, `FL_LABELS` has to grow from 16 to | |||||
| at least 33. | |||||
| - Conditions should not be materialised as 0/1 in A. `em_bin` can emit `CMP` | |||||
| and remember the comparison in `em_flags`; `em_cond_end` then emits the | |||||
| branch straight from those flags (`>` needs two false branches, `BCC` then | |||||
| `BEQ`), which is also what `while` needs at the top of the loop. | |||||
| - `return` is a plain `RTS` while no frame prologue is emitted (E04-S07 owns | |||||
| `frame_leave`); with no prologue an inline `RTS` is the correct exit. | |||||
| - `for` is not covered by the above: the spec evaluates the end and step | |||||
| expressions once before the first iteration, and the comparison direction | |||||
| depends on the step's runtime sign, so it needs two per-depth temporaries | |||||
| plus a runtime sign test before the loop condition. E04-S07 then owns | |||||
| `sizing.s`, pass-3 addresses, and the emitted `frame_enter`. | |||||
| E04-S07 owns sizing, pass-3 section addresses, emitted `frame_enter`, word | |||||
| results, and argument push/discard, followed by the complete `counter.n8` | |||||
| end-to-end fixture. | |||||
| @@ -0,0 +1,35 @@ | |||||
| # E04-S06: build the self-field/self-call fixture and a D81 holding CALLS.N8. | |||||
| [CmdletBinding()] | |||||
| param() | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| $out = Join-Path $project "build\calls-test" | |||||
| New-Item -ItemType Directory -Force -Path $out | Out-Null | |||||
| $link = Invoke-Nova128Link -Name "calls-test" -BuildDir $out ` | |||||
| -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( | |||||
| (Join-Path $project "src\compiler\mmu.s"), | |||||
| (Join-Path $project "src\compiler\diskio.s"), | |||||
| (Join-Path $project "src\compiler\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\interfaces.s"), | |||||
| (Join-Path $project "src\compiler\types.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\emit.s"), | |||||
| (Join-Path $project "src\runtime\startup.s"), | |||||
| (Join-Path $project "tests\compiler\calls-test.s")) | |||||
| if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" } | |||||
| $pet = Join-Path $out "calls.pet" | |||||
| [IO.File]::WriteAllBytes($pet, (ConvertTo-Nova128Petscii (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\calls.n8")))) | |||||
| New-Nova128Disk -Path (Join-Path $out "calls-test.d81") -Files @( | |||||
| @{ Name = "calls-test"; Path = $link.Program; Type = "prg" }, | |||||
| @{ Name = "calls.n8"; Path = $pet; Type = "seq" }) | |||||
| Write-Host ("built {0}" -f $link.Program) | |||||
| @@ -18,6 +18,8 @@ $link = Invoke-Nova128Link -Name "hello-test" -BuildDir $out ` | |||||
| (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\codegen.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\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"), | ||||
| @@ -67,6 +67,8 @@ $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` | |||||
| (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\codegen.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\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"), | ||||
| @@ -21,6 +21,7 @@ $link = Invoke-Nova128Link -Name "statements-test" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\types.s"), | (Join-Path $project "src\compiler\types.s"), | ||||
| (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\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")) | ||||
| @@ -0,0 +1,33 @@ | |||||
| # E04-S06: parse CALLS.N8, execute the emitted Counter.both, and check n=2. | |||||
| [CmdletBinding()] | |||||
| param([int]$Port = 6561, [int]$TimeoutSeconds = 180) | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| & (Join-Path $PSScriptRoot "build-calls-test.ps1") | |||||
| $out = Join-Path $project "build\calls-test" | |||||
| $disk = Join-Path $out "calls-test.d81" | |||||
| $failures = @() | |||||
| $session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port ` | |||||
| -Screenshot (Join-Path $out "calls-vicii.png") | |||||
| try { | |||||
| $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds | |||||
| } finally { | |||||
| Stop-Nova128X128 $session | |||||
| } | |||||
| Write-Host ("mailbox: " + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' ')) | |||||
| if ($mb[2] -ne 0x02) { $failures += "producer $($mb[2]) expected 2 (compiler)" } | |||||
| if ($mb[3] -ne 0xA5) { $failures += ("fixture status {0:X2} failed at stage {1}" -f $mb[3], $mb[4]) } | |||||
| if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8])" } | |||||
| if ($mb[9] -ne 0) { $failures += "emitter error $($mb[9])" } | |||||
| if ($mb[10] -ne 2) { $failures += "n = $($mb[10]), expected 2" } | |||||
| if ($mb[12] -ne 0) { $failures += "codegen error $($mb[12])" } | |||||
| if ($mb[13] -ne 0) { $failures += "flow error $($mb[13])" } | |||||
| if ($failures) { throw ("calls verification failed:`n " + ($failures -join "`n ")) } | |||||
| Write-Host ("emitted {0} bytes; self.n = 2 after self.tick()" -f $mb[11]) | |||||
| Write-Host "verified self field emission and a forward concrete self-call in x128" | |||||
| @@ -55,6 +55,7 @@ if (-not $failures) { | |||||
| try { | try { | ||||
| $deadline = [DateTime]::UtcNow.AddSeconds(60) | $deadline = [DateTime]::UtcNow.AddSeconds(60) | ||||
| while ([DateTime]::UtcNow -lt $deadline) { | while ([DateTime]::UtcNow -lt $deadline) { | ||||
| Invoke-Nova128Monitor $session "x" | Out-Null | |||||
| Start-Sleep -Seconds 1 | Start-Sleep -Seconds 1 | ||||
| try { $screenRam = Read-Nova128Memory $session 0x0400 1000 } catch { continue } | try { $screenRam = Read-Nova128Memory $session 0x0400 1000 } catch { continue } | ||||
| for ($r = 0; $r -lt 25; $r++) { | for ($r = 0; $r -lt 25; $r++) { | ||||
| @@ -33,23 +33,13 @@ if ($mb[3] -ne 0xA5) { | |||||
| } | } | ||||
| if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8])" } | if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8])" } | ||||
| if ($mb[9] -ne 0) { $failures += "emitter error $($mb[9])" } | if ($mb[9] -ne 0) { $failures += "emitter error $($mb[9])" } | ||||
| if ($mb[10] -ne 10) { $failures += ("a = {0}, expected 10" -f $mb[10]) } | |||||
| if ($mb[11] -ne 7) { $failures += ("b = {0}, expected 7" -f $mb[11]) } | |||||
| if ($mb[10] -ne 2) { $failures += ("a = {0}, expected 2 (loop variable past end)" -f $mb[10]) } | |||||
| if ($mb[11] -ne 3) { $failures += ("b = {0}, expected 3" -f $mb[11]) } | |||||
| if ($mb[12] -lt 60) { $failures += "emitted only $($mb[12]) bytes" } | if ($mb[12] -lt 60) { $failures += "emitted only $($mb[12]) bytes" } | ||||
| if ($mb[13] -ne 0) { $failures += "codegen error $($mb[13])" } | if ($mb[13] -ne 0) { $failures += "codegen error $($mb[13])" } | ||||
| # print "sum" records SUM + CR; print a records $FF, $0A, CR. | |||||
| $expect = @(0x53, 0x55, 0x4D, 0x0D, 0xFF, 0x0A, 0x0D) | |||||
| if ($mb[14] -ne $expect.Count) { | |||||
| $failures += ("recorded {0} bytes, expected {1}" -f $mb[14], $expect.Count) | |||||
| } else { | |||||
| for ($i = 0; $i -lt $expect.Count; $i++) { | |||||
| if ($log[$i] -ne $expect[$i]) { | |||||
| $failures += ("recorded byte {0} is {1:X2}, expected {2:X2}" -f $i, $log[$i], $expect[$i]) | |||||
| } | |||||
| } | |||||
| } | |||||
| if ($mb[14] -ne 0) { $failures += ("recorded {0} bytes, expected none" -f $mb[14]) } | |||||
| if ($failures) { throw ("statements verification failed:`n " + ($failures -join "`n ")) } | if ($failures) { throw ("statements verification failed:`n " + ($failures -join "`n ")) } | ||||
| Write-Host ("emitted {0} bytes; a=10, b=7; print recorded SUM, 10" -f $mb[12]) | |||||
| Write-Host ("emitted {0} bytes; a=2, b=3" -f $mb[12]) | |||||
| Write-Host "verified parser pass-2 statement emission executes in x128" | Write-Host "verified parser pass-2 statement emission executes in x128" | ||||
| @@ -104,3 +104,4 @@ | |||||
| DIAGNOSTIC RES_TOO_MANY_PARAMS, 224, "MORE THAN 255 PARAMETERS" | DIAGNOSTIC RES_TOO_MANY_PARAMS, 224, "MORE THAN 255 PARAMETERS" | ||||
| 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" | |||||
| @@ -9,13 +9,25 @@ EMIT_INC = 1 | |||||
| EM_TMP = $F7 ; two-byte combine temp; below MB_SELF ($F9) | EM_TMP = $F7 ; two-byte combine temp; below MB_SELF ($F9) | ||||
| EM_LP = $F5 ; two-byte slot pointer; below MB_SELF | EM_LP = $F5 ; two-byte slot pointer; below MB_SELF | ||||
| EM_FOR_END = $D0 ; 17 end values, indexed by block depth | |||||
| EM_FOR_STEP = $E1 ; 17 step values, indexed by block depth | |||||
| EM_ERR_SIZE = 1 ; a value or slot is not one byte | EM_ERR_SIZE = 1 ; a value or slot is not one byte | ||||
| EM_ERR_TARGET = 2 ; the assignment target is not a scalar local | EM_ERR_TARGET = 2 ; the assignment target is not a scalar local | ||||
| EM_ERR_OP = 3 ; operator the emitter does not handle yet | EM_ERR_OP = 3 ; operator the emitter does not handle yet | ||||
| EM_ERR_STATE = 4 ; missing runtime entry or bad emitter state | EM_ERR_STATE = 4 ; missing runtime entry or bad emitter state | ||||
| EM_ERR_FLOW = 5 ; flow.s rejected a label or fixup | |||||
| EM_ERR_CALL = 6 ; call shape not emitted yet | |||||
| 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_LAST_LOCAL = 1 | |||||
| EM_LAST_SELF = 2 | |||||
| EM_LAST_FIELD = 3 | |||||
| EM_TGT_LOCAL = 1 | |||||
| EM_TGT_FIELD = 2 | |||||
| .endif | .endif | ||||
| @@ -3,8 +3,8 @@ | |||||
| .ifndef FLOW_INC | .ifndef FLOW_INC | ||||
| FLOW_INC = 1 | FLOW_INC = 1 | ||||
| FL_LABELS = 16 ; label ids 0..15 per generated routine | |||||
| FL_FIX_MAX = 32 ; jumps and conditional branches per routine | |||||
| FL_LABELS = 64 ; label ids 0..63 per generated routine | |||||
| FL_FIX_MAX = 128 ; jumps, calls, and conditional branches | |||||
| FL_FIX_SIZE = 4 ; kind, label, operand position, spare | FL_FIX_SIZE = 4 ; kind, label, operand position, spare | ||||
| FL_BRANCH_SIZE = 5 ; opcode + distance + 3-byte JMP slot | FL_BRANCH_SIZE = 5 ; opcode + distance + 3-byte JMP slot | ||||
| @@ -18,5 +18,6 @@ FL_ERR_FULL = 2 ; fixup table full | |||||
| FL_ERR_STATE = 3 ; double-defined label, missing runtime address | FL_ERR_STATE = 3 ; double-defined label, missing runtime address | ||||
| FL_ERR_ID = 4 ; label id out of range | FL_ERR_ID = 4 ; label id out of range | ||||
| FL_ERR_RANGE = 5 ; branch target is behind the branch | FL_ERR_RANGE = 5 ; branch target is behind the branch | ||||
| FL_ERR_NOFREE = 6 ; no label id left in the pool | |||||
| .endif | .endif | ||||
| @@ -28,6 +28,7 @@ | |||||
| ; cg_adc_abs A/X = address | ; cg_adc_abs A/X = address | ||||
| ; cg_sbc_abs A/X = address | ; cg_sbc_abs A/X = address | ||||
| ; cg_cmp_abs A/X = address | ; cg_cmp_abs A/X = address | ||||
| ; cg_cmp_zp A = zero-page address (CMP zp) | |||||
| ; cg_jsr_abs A/X = address | ; cg_jsr_abs A/X = address | ||||
| ; cg_clc, cg_sec, cg_rts | ; cg_clc, cg_sec, cg_rts | ||||
| ; cg_branch_fwd A = branch opcode; emits opcode + placeholder, returns | ; cg_branch_fwd A = branch opcode; emits opcode + placeholder, returns | ||||
| @@ -52,7 +53,7 @@ | |||||
| .export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_ldy_imm, cg_lda_abs, cg_ldx_abs | .export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_ldy_imm, cg_lda_abs, cg_ldx_abs | ||||
| .export cg_lda_zp, cg_ldx_zp, cg_sta_zp, cg_stx_zp, cg_lda_zpy, cg_sta_zpy | .export cg_lda_zp, cg_ldx_zp, cg_sta_zp, cg_stx_zp, cg_lda_zpy, cg_sta_zpy | ||||
| .export cg_adc_zp, cg_sbc_zp, cg_adc_imm | .export cg_adc_zp, cg_sbc_zp, cg_adc_imm | ||||
| .export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs | |||||
| .export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs, cg_cmp_zp | |||||
| .export cg_jsr_abs, cg_clc, cg_sec, cg_rts | .export cg_jsr_abs, cg_clc, cg_sec, cg_rts | ||||
| .export cg_code, cg_len, cg_err | .export cg_code, cg_len, cg_err | ||||
| @@ -161,6 +162,9 @@ cg_adc_zp: | |||||
| cg_sbc_zp: | cg_sbc_zp: | ||||
| ldy #$E5 | ldy #$E5 | ||||
| jmp cg_emit_zp | jmp cg_emit_zp | ||||
| cg_cmp_zp: | |||||
| ldy #$C5 | |||||
| jmp cg_emit_zp | |||||
| cg_lda_zpy: | cg_lda_zpy: | ||||
| ldy #$B1 | ldy #$B1 | ||||
| jmp cg_emit_zp | jmp cg_emit_zp | ||||
| @@ -6,9 +6,11 @@ | |||||
| ; pass 2. | ; pass 2. | ||||
| ; | ; | ||||
| ; Covered here: byte locals and parameters, number and char constants, name | ; Covered here: byte locals and parameters, number and char constants, name | ||||
| ; loads, byte `+` and `-`, assignment to a scalar local, print of one captured | |||||
| ; string or a byte, and the end of a function. Frame prologue sizing, control | |||||
| ; flow, calls, field access, word values, and arrays remain. | |||||
| ; loads, byte `+` and `-`, comparisons, assignment to a scalar local or a | |||||
| ; `self.field`, if/else, while, a positive-step for, return, zero-argument | |||||
| ; calls to self methods, print of one captured string or a byte, and the end | |||||
| ; of a function. Frame prologue sizing, word values, arguments, and arrays | |||||
| ; remain. | |||||
| ; | ; | ||||
| ; Locals live at FP-1 downward and parameters at FP+2 upward (frames.inc), so | ; Locals live at FP-1 downward and parameters at FP+2 upward (frames.inc), so | ||||
| ; a slot is addressed with LDY #offset / LDA (FRAME_FP),Y. Offsets are | ; a slot is addressed with LDY #offset / LDA (FRAME_FP),Y. Offsets are | ||||
| @@ -26,34 +28,60 @@ | |||||
| .include "../common/members.inc" | .include "../common/members.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_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 | |||||
| .import gen_text, gen_text_len | .import gen_text, gen_text_len | ||||
| .import cg_len | |||||
| .import fl_init, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_release, fl_finish, fl_err | |||||
| .import mb_init, mb_self_store, mb_self_load, mb_ld_field, mb_st_field | |||||
| .import tb_get | |||||
| .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_begin_func, em_begin_params, em_local, em_param | .export em_reset, em_begin_func, em_begin_params, em_local, em_param | ||||
| .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 | ||||
| .export em_pr_text, em_pr_byte, em_pr_cr | |||||
| .export em_finish, em_self, em_member_receiver, em_field_ref, em_call | |||||
| .export em_open_block, em_cond_false, em_else, em_close_block | |||||
| .export em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup | |||||
| .export em_return, em_method_begin, em_program_begin, em_entry | |||||
| .export em_pr_text: abs, em_pr_byte: abs, em_pr_cr: abs | |||||
| .assert EM_TMP + 1 < MB_SELF, error, "EM_TMP overlaps MB_SELF" | .assert EM_TMP + 1 < MB_SELF, error, "EM_TMP overlaps MB_SELF" | ||||
| .segment "CODE" | .segment "CODE" | ||||
| ; A long conditional exit for the far error labels in the block emitters. | |||||
| .macro FLOW_FAIL | |||||
| bcc :+ | |||||
| jmp @flow | |||||
| : | |||||
| .endmacro | |||||
| ; 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: | ||||
| jsr cg_init | |||||
| lda #$00 | |||||
| jsr fl_init | |||||
| jsr mb_init | |||||
| lda #$00 | lda #$00 | ||||
| sta em_bad | sta em_bad | ||||
| sta em_ctx | sta em_ctx | ||||
| sta em_cmp | |||||
| sta em_recv_self | |||||
| sta em_last_kind | sta em_last_kind | ||||
| sta em_tgt | sta em_tgt | ||||
| sta em_tgt_kind | |||||
| sta em_size | sta em_size | ||||
| sta em_left_size | sta em_left_size | ||||
| sta em_locals | sta em_locals | ||||
| sta em_par_bytes | sta em_par_bytes | ||||
| sta em_entry | |||||
| lda #$FF | lda #$FF | ||||
| sta em_loc_next | sta em_loc_next | ||||
| ldx #$FF | |||||
| @meth: sta em_meth_label,x | |||||
| dex | |||||
| bpl @meth | |||||
| lda #$02 | lda #$02 | ||||
| sta em_par_next | sta em_par_next | ||||
| rts | rts | ||||
| @@ -66,8 +94,18 @@ em_begin_func: | |||||
| lda #$00 | lda #$00 | ||||
| sta em_locals | sta em_locals | ||||
| sta em_ctx | sta em_ctx | ||||
| sta em_cmp | |||||
| sta em_recv_self | |||||
| sta em_last_kind | sta em_last_kind | ||||
| sta em_tgt | sta em_tgt | ||||
| sta em_tgt_kind | |||||
| rts | |||||
| ; The program body starts here. Its offset lets a fixture run the program | |||||
| ; after any class methods that were emitted before it. | |||||
| em_program_begin: | |||||
| lda cg_len | |||||
| sta em_entry | |||||
| rts | rts | ||||
| ; A signature starts: parameters begin at FP+2. | ; A signature starts: parameters begin at FP+2. | ||||
| @@ -126,6 +164,9 @@ em_stmt_begin: | |||||
| lda #$01 | lda #$01 | ||||
| sta em_ctx | sta em_ctx | ||||
| sta em_last_kind | sta em_last_kind | ||||
| lda #$00 | |||||
| sta em_cmp | |||||
| sta em_recv_self | |||||
| rts | rts | ||||
| ; The statement is complete: no more operand emission. The target stays set | ; The statement is complete: no more operand emission. The target stays set | ||||
| @@ -150,8 +191,15 @@ em_begin_print: | |||||
| ; X = name ID. Record it as the last primary, and load it when emitting. | ; X = name ID. Record it as the last primary, and load it when emitting. | ||||
| em_expr_name: | em_expr_name: | ||||
| stx em_last_nid | stx em_last_nid | ||||
| lda #$01 | |||||
| lda #EM_LAST_LOCAL | |||||
| sta em_last_kind | sta em_last_kind | ||||
| lda #$00 | |||||
| sta em_recv_self | |||||
| sta em_cmp | |||||
| lda em_on | |||||
| bne :+ | |||||
| jmp @done | |||||
| : | |||||
| lda em_ctx | lda em_ctx | ||||
| beq @done | beq @done | ||||
| lda em_sz,x | lda em_sz,x | ||||
| @@ -178,6 +226,14 @@ em_expr_name: | |||||
| em_expr_const: | em_expr_const: | ||||
| sta em_low | sta em_low | ||||
| stx em_high | stx em_high | ||||
| lda #$00 | |||||
| sta em_last_kind | |||||
| sta em_recv_self | |||||
| sta em_cmp | |||||
| lda em_on | |||||
| bne :+ | |||||
| jmp @done | |||||
| : | |||||
| lda em_ctx | lda em_ctx | ||||
| beq @done | beq @done | ||||
| lda em_high | lda em_high | ||||
| @@ -197,8 +253,15 @@ em_expr_const: | |||||
| ; The left operand is complete (in A at run time): keep it on the stack while | ; The left operand is complete (in A at run time): keep it on the stack while | ||||
| ; the right operand is emitted. | ; the right operand is emitted. | ||||
| em_push_left: | em_push_left: | ||||
| lda em_on | |||||
| bne :+ | |||||
| jmp @done | |||||
| : | |||||
| lda em_ctx | lda em_ctx | ||||
| beq @done | beq @done | ||||
| lda #$00 | |||||
| sta em_cmp | |||||
| sta em_recv_self | |||||
| lda em_size | lda em_size | ||||
| beq @size | beq @size | ||||
| cmp #$01 | cmp #$01 | ||||
| @@ -221,12 +284,22 @@ em_push_left: | |||||
| ; the hardware stack; combine byte values only. | ; the hardware stack; combine byte values only. | ||||
| em_bin: | em_bin: | ||||
| sta em_op | sta em_op | ||||
| lda em_on | |||||
| bne :+ | |||||
| jmp @done | |||||
| : | |||||
| lda em_ctx | lda em_ctx | ||||
| beq @done | |||||
| bne :+ | |||||
| jmp @done | |||||
| : | |||||
| lda em_left_size | lda em_left_size | ||||
| beq @size | |||||
| bne :+ | |||||
| jmp @size | |||||
| : | |||||
| lda em_size | lda em_size | ||||
| beq @size | |||||
| bne :+ | |||||
| jmp @size | |||||
| : | |||||
| lda #EM_TMP | lda #EM_TMP | ||||
| jsr cg_sta_zp | jsr cg_sta_zp | ||||
| bcs @fail | bcs @fail | ||||
| @@ -238,18 +311,46 @@ em_bin: | |||||
| beq @plus | beq @plus | ||||
| cmp #TOK_MINUS | cmp #TOK_MINUS | ||||
| beq @minus | beq @minus | ||||
| cmp #TOK_EQ | |||||
| beq @compare | |||||
| cmp #TOK_NE | |||||
| beq @compare | |||||
| cmp #TOK_LT | |||||
| beq @compare | |||||
| cmp #TOK_LE | |||||
| beq @compare | |||||
| cmp #TOK_GT | |||||
| beq @compare | |||||
| cmp #TOK_GE | |||||
| beq @compare | |||||
| lda #EM_ERR_OP | lda #EM_ERR_OP | ||||
| sta em_bad | sta em_bad | ||||
| clc | clc | ||||
| rts | rts | ||||
| @compare: | |||||
| lda #EM_TMP | |||||
| jsr cg_cmp_zp | |||||
| bcs @fail | |||||
| lda #$01 | |||||
| sta em_cmp | |||||
| lda em_op | |||||
| sta em_cmp_op | |||||
| lda #$00 | |||||
| sta em_size | |||||
| clc | |||||
| rts | |||||
| @plus: jsr cg_clc | @plus: jsr cg_clc | ||||
| bcs @fail | bcs @fail | ||||
| lda #$00 | |||||
| sta em_cmp | |||||
| lda #EM_TMP | lda #EM_TMP | ||||
| jsr cg_adc_zp | jsr cg_adc_zp | ||||
| bcs @fail | bcs @fail | ||||
| jmp @ok | jmp @ok | ||||
| @minus: jsr cg_sec | @minus: jsr cg_sec | ||||
| bcs @fail | bcs @fail | ||||
| lda #$00 | |||||
| sta em_cmp | |||||
| lda #EM_TMP | lda #EM_TMP | ||||
| jsr cg_sbc_zp | jsr cg_sbc_zp | ||||
| bcs @fail | bcs @fail | ||||
| @@ -299,14 +400,18 @@ em_point_slot: | |||||
| @out: sec | @out: sec | ||||
| rts | rts | ||||
| ; The assignment target is the statement's first primary (em_last_kind / | |||||
| ; em_last_nid). Accept a one-byte local only. | |||||
| ; The assignment target is the statement's first primary. Accept a one-byte | |||||
| ; local or a one-byte self field. | |||||
| em_target: | em_target: | ||||
| lda em_on | lda em_on | ||||
| beq @done | beq @done | ||||
| lda em_last_kind | lda em_last_kind | ||||
| cmp #$01 | |||||
| bne @bad | |||||
| cmp #EM_LAST_LOCAL | |||||
| beq @local | |||||
| cmp #EM_LAST_FIELD | |||||
| beq @field | |||||
| jmp @bad | |||||
| @local: | |||||
| ldx em_last_nid | ldx em_last_nid | ||||
| beq @bad | beq @bad | ||||
| lda em_sz,x | lda em_sz,x | ||||
| @@ -314,6 +419,21 @@ em_target: | |||||
| cmp #$01 | cmp #$01 | ||||
| bne @bad | bne @bad | ||||
| stx em_tgt | stx em_tgt | ||||
| lda #EM_TGT_LOCAL | |||||
| sta em_tgt_kind | |||||
| clc | |||||
| rts | |||||
| @field: lda em_field_size | |||||
| cmp #$01 | |||||
| bne @bad | |||||
| lda #$00 | |||||
| sta em_tgt | |||||
| lda #EM_TGT_FIELD | |||||
| sta em_tgt_kind | |||||
| lda em_field_off | |||||
| sta em_tgt_off | |||||
| lda em_field_size | |||||
| sta em_tgt_size | |||||
| clc | clc | ||||
| rts | rts | ||||
| @bad: lda #EM_ERR_TARGET | @bad: lda #EM_ERR_TARGET | ||||
| @@ -325,6 +445,10 @@ em_target: | |||||
| em_store_target: | em_store_target: | ||||
| lda em_on | lda em_on | ||||
| beq @done | beq @done | ||||
| lda em_tgt_kind | |||||
| beq @done | |||||
| cmp #EM_TGT_FIELD | |||||
| beq @field | |||||
| lda em_tgt | lda em_tgt | ||||
| beq @done | beq @done | ||||
| lda #$48 ; PHA: em_point_slot clobbers A | lda #$48 ; PHA: em_point_slot clobbers A | ||||
| @@ -343,8 +467,17 @@ em_store_target: | |||||
| bcs @fail | bcs @fail | ||||
| lda #$00 | lda #$00 | ||||
| sta em_tgt | sta em_tgt | ||||
| sta em_tgt_kind | |||||
| @done: clc | @done: clc | ||||
| rts | rts | ||||
| @field: lda em_tgt_off | |||||
| ldx em_tgt_size | |||||
| jsr mb_st_field | |||||
| bcs @fail | |||||
| lda #$00 | |||||
| sta em_tgt_kind | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | @fail: jmp em_fail | ||||
| ; Print the value just parsed: the captured string literal when this statement | ; Print the value just parsed: the captured string literal when this statement | ||||
| @@ -398,6 +531,8 @@ em_print: | |||||
| ; The runtime print entry points live in cells the caller fills, so a test | ; The runtime print entry points live in cells the caller fills, so a test | ||||
| ; can record what the generated code asked to print. | ; can record what the generated code asked to print. | ||||
| ; The cells must start on an even address: an indirect JMP must not straddle | |||||
| ; a page. emit.inc asserts the offsets and the link map is checked in tests. | |||||
| em_tramp_text: jmp (em_pr_text) | em_tramp_text: jmp (em_pr_text) | ||||
| em_tramp_byte: jmp (em_pr_byte) | em_tramp_byte: jmp (em_pr_byte) | ||||
| em_tramp_cr: jmp (em_pr_cr) | em_tramp_cr: jmp (em_pr_cr) | ||||
| @@ -412,6 +547,532 @@ em_end_func: | |||||
| rts | rts | ||||
| @fail: jmp em_fail | @fail: jmp em_fail | ||||
| ; Explicit `return`: no frame prologue is emitted yet, so this is the exit. | |||||
| em_return: | |||||
| lda em_on | |||||
| beq @done | |||||
| jmp cg_rts | |||||
| @done: clc | |||||
| rts | |||||
| ; A = member row. Emit the method entry label and save incoming self. | |||||
| em_method_begin: | |||||
| jsr em_method_label | |||||
| bcs @bad | |||||
| txa | |||||
| jsr fl_label | |||||
| bcs @bad | |||||
| jmp mb_self_store | |||||
| @bad: lda #EM_ERR_LABEL | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; A = member row -> X = its label id, allocated on first use (forward calls). | |||||
| em_method_label: | |||||
| sta em_nid | |||||
| tax | |||||
| lda em_meth_label,x | |||||
| cmp #$FF | |||||
| bne @have | |||||
| lda em_nid | |||||
| jsr fl_alloc | |||||
| bcs @bad | |||||
| ldy em_nid | |||||
| sta em_meth_label,y | |||||
| tax | |||||
| clc | |||||
| rts | |||||
| @have: tax | |||||
| clc | |||||
| rts | |||||
| @bad: lda #EM_ERR_LABEL | |||||
| sta em_bad | |||||
| sec | |||||
| rts | |||||
| ; A = member row. Emit a zero-argument call through self. | |||||
| em_call: | |||||
| sta em_nid | |||||
| jsr em_method_label | |||||
| bcs @bad | |||||
| stx em_lab | |||||
| ldy em_nid | |||||
| lda #C_MEM_PCOUNT | |||||
| jsr tb_get | |||||
| bne @call_bad | |||||
| lda em_recv_self | |||||
| beq @call_bad | |||||
| jsr mb_self_load | |||||
| bcs @fail | |||||
| lda em_lab | |||||
| jsr fl_jsr | |||||
| bcs @fail | |||||
| ldy em_nid | |||||
| lda #C_MEM_TBASE | |||||
| jsr tb_get | |||||
| cmp #TY_NONE | |||||
| beq @void | |||||
| cmp #TY_VOID | |||||
| beq @void | |||||
| cmp #TY_BOOLEAN | |||||
| beq @byte | |||||
| cmp #TY_BYTE | |||||
| beq @byte | |||||
| cmp #TY_CHAR | |||||
| beq @byte | |||||
| lda #EM_ERR_CALL | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @void: lda #$00 | |||||
| sta em_size | |||||
| clc | |||||
| rts | |||||
| @byte: lda #$01 | |||||
| sta em_size | |||||
| clc | |||||
| rts | |||||
| @call_bad: | |||||
| lda #EM_ERR_CALL | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @bad: lda #EM_ERR_LABEL | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; The next member is applied to self (a field or a call). | |||||
| em_member_receiver: | |||||
| lda #$00 | |||||
| sta em_recv_self | |||||
| lda em_last_kind | |||||
| cmp #EM_LAST_SELF | |||||
| bne @done | |||||
| lda #$01 | |||||
| sta em_recv_self | |||||
| @done: rts | |||||
| ; The parser saw `self`; remember it as the receiver, not as a value. | |||||
| em_self: | |||||
| lda #$00 | |||||
| sta em_last_nid | |||||
| sta em_cmp | |||||
| lda #EM_LAST_SELF | |||||
| sta em_last_kind | |||||
| lda #$01 | |||||
| sta em_recv_self | |||||
| rts | |||||
| ; A = field member row. Record it; load it when the expression wants a value. | |||||
| em_field_ref: | |||||
| sta em_nid | |||||
| lda em_recv_self | |||||
| beq @bad | |||||
| ldy em_nid | |||||
| lda #C_MEM_OFFSET_HI | |||||
| jsr tb_get | |||||
| bne @bad | |||||
| lda #C_MEM_OFFSET_LO | |||||
| jsr tb_get | |||||
| sta em_field_off | |||||
| ldy em_nid | |||||
| lda #C_MEM_TBASE | |||||
| jsr tb_get | |||||
| cmp #TY_BOOLEAN | |||||
| beq @one | |||||
| cmp #TY_BYTE | |||||
| beq @one | |||||
| cmp #TY_CHAR | |||||
| beq @one | |||||
| cmp #TY_INTEGER | |||||
| beq @two | |||||
| cmp #TY_WORD | |||||
| beq @two | |||||
| jmp @bad | |||||
| @one: lda #$01 | |||||
| jmp @size | |||||
| @two: lda #$02 | |||||
| @size: sta em_field_size | |||||
| lda #EM_LAST_FIELD | |||||
| sta em_last_kind | |||||
| lda em_ctx | |||||
| beq @done | |||||
| lda em_field_off | |||||
| ldx em_field_size | |||||
| jsr mb_ld_field | |||||
| bcs @fail | |||||
| lda em_field_size | |||||
| sta em_size | |||||
| @done: clc | |||||
| rts | |||||
| @bad: lda #EM_ERR_SIZE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; A = block kind, X = block depth index. Reserve what the block needs. | |||||
| em_open_block: | |||||
| sta em_kind_b | |||||
| stx em_blk_idx | |||||
| lda #$00 | |||||
| sta em_blk_else_done,x | |||||
| lda em_kind_b | |||||
| cmp #KW_IF | |||||
| beq @if | |||||
| cmp #KW_WHILE | |||||
| beq @while | |||||
| cmp #KW_FOR | |||||
| beq @for | |||||
| clc | |||||
| rts | |||||
| @if: jsr fl_alloc | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| sta em_blk_end,x | |||||
| jsr fl_alloc | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| sta em_blk_else,x | |||||
| clc | |||||
| rts | |||||
| @while: jsr fl_alloc | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| sta em_blk_top,x | |||||
| jsr fl_label | |||||
| FLOW_FAIL | |||||
| jsr fl_alloc | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| sta em_blk_end,x | |||||
| clc | |||||
| rts | |||||
| @for: jsr fl_alloc | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| sta em_blk_top,x | |||||
| jsr fl_alloc | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| sta em_blk_end,x | |||||
| clc | |||||
| rts | |||||
| @flow: lda #EM_ERR_FLOW | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; X = block depth index. Branch to the block's end when the condition is | |||||
| ; false; the comparison flags are already set (or A is tested against zero). | |||||
| em_cond_false: | |||||
| stx em_blk_idx | |||||
| lda em_cmp | |||||
| bne @flags | |||||
| lda #$C9 ; CMP #0 | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| lda #$00 | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| lda #TOK_EQ | |||||
| sta em_cmp_op | |||||
| @flags: ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| sta em_lab | |||||
| lda em_cmp_op | |||||
| cmp #TOK_EQ | |||||
| beq @eq | |||||
| cmp #TOK_NE | |||||
| beq @ne | |||||
| cmp #TOK_LT | |||||
| beq @lt | |||||
| cmp #TOK_GE | |||||
| beq @ge | |||||
| cmp #TOK_GT | |||||
| beq @gt | |||||
| cmp #TOK_LE | |||||
| beq @le | |||||
| jmp @flow | |||||
| @eq: lda #$F0 ; BEQ | |||||
| jmp @branch | |||||
| @ne: lda #$D0 ; BNE | |||||
| jmp @branch | |||||
| @lt: lda #$B0 ; left >= right is false | |||||
| jmp @branch | |||||
| @ge: lda #$90 ; left < right is false | |||||
| jmp @branch | |||||
| @gt: lda #$F0 ; equal is false | |||||
| ldx em_lab | |||||
| jsr fl_branch | |||||
| FLOW_FAIL | |||||
| lda #$90 ; left < right is false | |||||
| jmp @branch | |||||
| @le: lda #$F0 ; equal is true: skip the BCS | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| lda #$05 ; the fixed BCS is five bytes | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| lda #$B0 ; left > right is false | |||||
| @branch: | |||||
| ldx em_lab | |||||
| jmp fl_branch | |||||
| @flow: lda #EM_ERR_FLOW | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; X = block depth index. `else` jumps over the else arm and defines its start. | |||||
| em_else: | |||||
| stx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| jsr fl_jmp | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_else,x | |||||
| jsr fl_label | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_else,x | |||||
| jsr fl_release | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda #$01 | |||||
| sta em_blk_else_done,x | |||||
| clc | |||||
| rts | |||||
| @flow: lda #EM_ERR_FLOW | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; A = block kind, X = block depth index. Close labels and add the loop tail. | |||||
| em_close_block: | |||||
| sta em_kind_b | |||||
| stx em_blk_idx | |||||
| cmp #KW_IF | |||||
| beq @if | |||||
| cmp #KW_WHILE | |||||
| beq @while | |||||
| cmp #KW_FOR | |||||
| beq @for | |||||
| clc | |||||
| rts | |||||
| @if: ldx em_blk_idx | |||||
| lda em_blk_else_done,x | |||||
| bne @if_end | |||||
| lda em_blk_else,x | |||||
| jsr fl_label | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_else,x | |||||
| jsr fl_release | |||||
| FLOW_FAIL | |||||
| @if_end: | |||||
| ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| jsr fl_label | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| jsr fl_release | |||||
| FLOW_FAIL | |||||
| clc | |||||
| rts | |||||
| @while: ldx em_blk_idx | |||||
| lda em_blk_top,x | |||||
| jsr fl_jmp | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| jsr fl_label | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| jsr fl_release | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_top,x | |||||
| jsr fl_release | |||||
| FLOW_FAIL | |||||
| clc | |||||
| rts | |||||
| @for: ldx em_blk_idx | |||||
| lda em_for_var_tab,x | |||||
| beq @for_end | |||||
| jsr em_load_slot | |||||
| FLOW_FAIL | |||||
| jsr cg_clc | |||||
| FLOW_FAIL | |||||
| lda #$01 | |||||
| jsr cg_adc_imm | |||||
| FLOW_FAIL | |||||
| lda #$48 | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_for_var_tab,x | |||||
| jsr em_point_name | |||||
| FLOW_FAIL | |||||
| lda #$68 | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| lda #EM_LP | |||||
| jsr cg_sta_zpy | |||||
| FLOW_FAIL | |||||
| @for_end: | |||||
| ldx em_blk_idx | |||||
| lda em_blk_top,x | |||||
| jsr fl_jmp | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| jsr fl_label | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| jsr fl_release | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_top,x | |||||
| jsr fl_release | |||||
| FLOW_FAIL | |||||
| clc | |||||
| rts | |||||
| @flow: lda #EM_ERR_FLOW | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; A = for variable name ID, X = block depth index. | |||||
| em_for_var: | |||||
| sta em_for_var_tab,x | |||||
| rts | |||||
| ; X = block depth index. The end expression must be a byte constant. | |||||
| em_for_save_end: | |||||
| stx em_blk_idx | |||||
| lda sc_len | |||||
| ora sc_len+1 | |||||
| bne @bad | |||||
| lda sc_base | |||||
| cmp #TY_CONST | |||||
| bne @bad | |||||
| lda sc_val+1 | |||||
| bne @bad | |||||
| lda sc_val | |||||
| jsr cg_lda_imm | |||||
| bcs @bad | |||||
| lda em_blk_idx | |||||
| clc | |||||
| adc #EM_FOR_END | |||||
| jmp cg_sta_zp | |||||
| @bad: lda #EM_ERR_SIZE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; X = block depth index. Explicit step is implemented for the positive value 1. | |||||
| em_for_save_step: | |||||
| stx em_blk_idx | |||||
| lda sc_len | |||||
| ora sc_len+1 | |||||
| bne @bad | |||||
| lda sc_base | |||||
| cmp #TY_CONST | |||||
| bne @bad | |||||
| lda sc_val | |||||
| cmp #$01 | |||||
| bne @bad | |||||
| lda sc_val+1 | |||||
| bne @bad | |||||
| clc | |||||
| rts | |||||
| @bad: lda #EM_ERR_OP | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; X = block depth index. No `step` clause means one. | |||||
| em_for_default_step: | |||||
| clc | |||||
| rts | |||||
| ; X = block depth index. Define the loop top and test `var > end`. | |||||
| em_for_setup: | |||||
| stx em_blk_idx | |||||
| lda em_blk_top,x | |||||
| jsr fl_label | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_for_var_tab,x | |||||
| ora #$00 | |||||
| beq @flow | |||||
| jsr em_load_slot | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| txa | |||||
| clc | |||||
| adc #EM_FOR_END | |||||
| jsr cg_cmp_zp | |||||
| FLOW_FAIL | |||||
| lda #$F0 ; equal: enter the body | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| lda #$05 ; skip the five-byte BCS | |||||
| jsr cg_put | |||||
| FLOW_FAIL | |||||
| ldx em_blk_idx | |||||
| lda em_blk_end,x | |||||
| sta em_lab | |||||
| lda #$B0 ; var > end: leave the loop | |||||
| ldx em_lab | |||||
| jmp fl_branch | |||||
| @flow: lda #EM_ERR_FLOW | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| ; A = name ID. Emit a load of a one-byte slot into A. | |||||
| em_point_name: | |||||
| tax | |||||
| lda em_off,x | |||||
| jmp em_point_slot | |||||
| ; A = name ID. Emit a load of a one-byte slot into A. | |||||
| em_load_slot: | |||||
| sta em_nid | |||||
| ldx em_nid | |||||
| lda em_sz,x | |||||
| beq @size | |||||
| lda em_off,x | |||||
| jsr em_point_slot | |||||
| bcs @fail | |||||
| lda #EM_LP | |||||
| jsr cg_lda_zpy | |||||
| bcs @fail | |||||
| clc | |||||
| rts | |||||
| @size: lda #EM_ERR_SIZE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; Check flow fixups after every body has been parsed and every method label | |||||
| ; has been defined. | |||||
| em_finish: | |||||
| jsr fl_finish | |||||
| bcc @ok | |||||
| lda #EM_ERR_FLOW | |||||
| sta em_bad | |||||
| @ok: clc | |||||
| rts | |||||
| ; An emitter failure is the caller's signal; cg_err holds the detail. | ; An emitter failure is the caller's signal; cg_err holds the detail. | ||||
| em_fail: | em_fail: | ||||
| lda #EM_ERR_STATE | lda #EM_ERR_STATE | ||||
| @@ -419,19 +1080,24 @@ em_fail: | |||||
| clc | clc | ||||
| rts | rts | ||||
| em_pr_text = $C0 ; print vectors; low byte never $FF | |||||
| em_pr_byte = $C2 | |||||
| em_pr_cr = $C4 | |||||
| .segment "BSS" | .segment "BSS" | ||||
| .align 2 ; jmp (cell) must not straddle a page | |||||
| em_pr_text: .res 2 | |||||
| em_pr_byte: .res 2 | |||||
| em_pr_cr: .res 2 | |||||
| .res 6 ; preserve the BSS layout of the vectors | |||||
| em_on: .res 1 | em_on: .res 1 | ||||
| em_ctx: .res 1 | em_ctx: .res 1 | ||||
| em_bad: .res 1 | em_bad: .res 1 | ||||
| em_last_kind: .res 1 | em_last_kind: .res 1 | ||||
| em_last_nid: .res 1 | em_last_nid: .res 1 | ||||
| em_tgt: .res 1 | em_tgt: .res 1 | ||||
| em_tgt_kind: .res 1 | |||||
| em_tgt_off: .res 1 | |||||
| em_tgt_size: .res 1 | |||||
| em_nid: .res 1 | em_nid: .res 1 | ||||
| em_kind: .res 1 | em_kind: .res 1 | ||||
| em_kind_b: .res 1 | |||||
| em_size: .res 1 | em_size: .res 1 | ||||
| em_left_size: .res 1 | em_left_size: .res 1 | ||||
| em_loc_next: .res 1 | em_loc_next: .res 1 | ||||
| @@ -441,7 +1107,21 @@ em_par_bytes: .res 1 | |||||
| em_low: .res 1 | em_low: .res 1 | ||||
| em_high: .res 1 | em_high: .res 1 | ||||
| em_op: .res 1 | em_op: .res 1 | ||||
| em_cmp: .res 1 | |||||
| em_cmp_op: .res 1 | |||||
| em_recv_self: .res 1 | |||||
| em_field_off: .res 1 | |||||
| em_field_size: .res 1 | |||||
| em_blk_idx: .res 1 | |||||
| em_lab: .res 1 | |||||
| em_for_var_tab: .res PARSE_MAX_DEPTH + 1 | |||||
| em_blk_else: .res PARSE_MAX_DEPTH + 1 | |||||
| em_blk_end: .res PARSE_MAX_DEPTH + 1 | |||||
| em_blk_top: .res PARSE_MAX_DEPTH + 1 | |||||
| em_blk_else_done: .res PARSE_MAX_DEPTH + 1 | |||||
| em_txt_before: .res 1 | em_txt_before: .res 1 | ||||
| em_off_b: .res 1 | em_off_b: .res 1 | ||||
| em_off: .res 256 | em_off: .res 256 | ||||
| em_sz: .res 256 | em_sz: .res 256 | ||||
| em_meth_label: .res 256 | |||||
| em_entry: .res 1 | |||||
| @@ -4,9 +4,12 @@ | |||||
| ; transfer that whole statements need: | ; transfer that whole statements need: | ||||
| ; | ; | ||||
| ; fl_init A = FL_MODE_EMIT or FL_MODE_COUNT | ; fl_init A = FL_MODE_EMIT or FL_MODE_COUNT | ||||
| ; fl_alloc reserve a label id -> A = id, C=1 if the pool is full | |||||
| ; fl_label A = label id: define it at the current position | ; fl_label A = label id: define it at the current position | ||||
| ; fl_jmp A = label id: absolute JMP (also the back edge of a loop) | ; fl_jmp A = label id: absolute JMP (also the back edge of a loop) | ||||
| ; fl_jsr A = label id: absolute JSR for a forward method entry | |||||
| ; fl_branch A = opcode, X = label id: conditional branch | ; fl_branch A = opcode, X = label id: conditional branch | ||||
| ; fl_release A = label id: patch pending uses now, then free the id | |||||
| ; fl_branch_true / fl_branch_false A = label id (BNE / BEQ) | ; fl_branch_true / fl_branch_false A = label id (BNE / BEQ) | ||||
| ; fl_finish resolve every fixup; in emit mode patch the bytes | ; fl_finish resolve every fixup; in emit mode patch the bytes | ||||
| ; fl_get_label A = label id -> X = offset, C=0; C=1 if undefined | ; fl_get_label A = label id -> X = offset, C=0; C=1 if undefined | ||||
| @@ -59,7 +62,8 @@ | |||||
| : | : | ||||
| .endmacro | .endmacro | ||||
| .export fl_init, fl_label, fl_jmp, fl_branch, fl_finish, fl_get_label | |||||
| .export fl_init, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_finish, fl_get_label | |||||
| .export fl_release | |||||
| .export fl_branch_true, fl_branch_false | .export fl_branch_true, fl_branch_false | ||||
| .export fl_call, fl_call_byte, fl_call_word | .export fl_call, fl_call_byte, fl_call_word | ||||
| .export fl_prologue, fl_ret, fl_ret_byte, fl_ret_word | .export fl_prologue, fl_ret, fl_ret_byte, fl_ret_word | ||||
| @@ -75,7 +79,8 @@ fl_init: | |||||
| sta fl_fix_count | sta fl_fix_count | ||||
| sta fl_err | sta fl_err | ||||
| ldx #FL_LABELS - 1 | ldx #FL_LABELS - 1 | ||||
| @clr: sta fl_defined,x | |||||
| @clr: sta fl_used,x | |||||
| sta fl_defined,x | |||||
| dex | dex | ||||
| bpl @clr | bpl @clr | ||||
| lda fl_mode | lda fl_mode | ||||
| @@ -83,6 +88,25 @@ fl_init: | |||||
| clc | clc | ||||
| rts | rts | ||||
| ; Reserve a label id. A label stays reserved until fl_release, so a block's | |||||
| ; forward targets can be defined in any order. | |||||
| fl_alloc: | |||||
| ldx #$00 | |||||
| @scan: lda fl_used,x | |||||
| beq @take | |||||
| inx | |||||
| cpx #FL_LABELS | |||||
| bcc @scan | |||||
| lda #FL_ERR_NOFREE | |||||
| sta fl_err | |||||
| sec | |||||
| rts | |||||
| @take: lda #$01 | |||||
| sta fl_used,x | |||||
| txa | |||||
| clc | |||||
| rts | |||||
| ; A = label id. C=1 on a bad id or a redefinition. | ; A = label id. C=1 on a bad id or a redefinition. | ||||
| fl_label: | fl_label: | ||||
| cmp #FL_LABELS | cmp #FL_LABELS | ||||
| @@ -91,6 +115,8 @@ fl_label: | |||||
| lda fl_defined,x | lda fl_defined,x | ||||
| bne @dup | bne @dup | ||||
| lda #$01 | lda #$01 | ||||
| sta fl_used,x | |||||
| lda #$01 | |||||
| sta fl_defined,x | sta fl_defined,x | ||||
| lda cg_len | lda cg_len | ||||
| sta fl_labels,x | sta fl_labels,x | ||||
| @@ -130,6 +156,110 @@ fl_jmp: | |||||
| @out: sec | @out: sec | ||||
| rts | rts | ||||
| ; A = label id. Emit JSR abs with a placeholder operand. | |||||
| fl_jsr: | |||||
| cmp #FL_LABELS | |||||
| bcs @bad | |||||
| sta fl_lab | |||||
| lda #$20 | |||||
| jsr cg_put | |||||
| CHECK_OUT | |||||
| lda cg_len | |||||
| sta fl_pos | |||||
| lda #$03 ; fixup kind: JSR | |||||
| jsr fl_add_fix | |||||
| CHECK_OUT | |||||
| lda #$FF | |||||
| jsr cg_put | |||||
| CHECK_OUT | |||||
| lda #$FF | |||||
| jmp cg_put | |||||
| @bad: lda #FL_ERR_ID | |||||
| sta fl_err | |||||
| sec | |||||
| rts | |||||
| @out: sec | |||||
| rts | |||||
| ; A = label id. Kept as a compatibility entry point: labels stay defined until | |||||
| ; fl_finish, so a block cannot free an id that a later fixup still names. | |||||
| fl_release: | |||||
| clc | |||||
| rts | |||||
| ; Old release body retained below for the future label-reuse pass. | |||||
| fl_release_unused: | |||||
| cmp #FL_LABELS | |||||
| bcc :+ | |||||
| jmp @bad | |||||
| : | |||||
| sta fl_rel | |||||
| tax | |||||
| lda fl_defined,x | |||||
| bne :+ | |||||
| jmp @bad | |||||
| : | |||||
| lda #$00 | |||||
| sta fl_index | |||||
| @scan: lda fl_index | |||||
| cmp fl_fix_count | |||||
| bne :+ | |||||
| jmp @clear | |||||
| : | |||||
| asl a | |||||
| asl a | |||||
| sta fl_recp | |||||
| tay | |||||
| lda fl_fix+1,y | |||||
| cmp fl_rel | |||||
| bne @next | |||||
| lda fl_fix,y | |||||
| beq @next | |||||
| cmp #$02 | |||||
| beq @branch | |||||
| lda fl_rel ; JMP/JSR operand | |||||
| jsr fl_resolve | |||||
| bcs @out | |||||
| ldy fl_recp | |||||
| lda fl_fix+2,y | |||||
| tay | |||||
| lda fl_tlo | |||||
| sta cg_code,y | |||||
| iny | |||||
| lda fl_thi | |||||
| sta cg_code,y | |||||
| ldy fl_recp | |||||
| lda #$00 | |||||
| sta fl_fix,y | |||||
| jmp @next | |||||
| @branch: | |||||
| lda fl_rel | |||||
| jsr fl_resolve | |||||
| bcs @out | |||||
| ldy fl_recp | |||||
| lda fl_fix+1,y | |||||
| tax | |||||
| lda fl_labels,x | |||||
| jsr fl_patch_branch | |||||
| bcs @out | |||||
| ldy fl_recp | |||||
| lda #$00 | |||||
| sta fl_fix,y | |||||
| @next: inc fl_index | |||||
| jmp @scan | |||||
| @clear: ldx fl_rel | |||||
| lda #$00 | |||||
| sta fl_defined,x | |||||
| sta fl_used,x | |||||
| clc | |||||
| rts | |||||
| @bad: lda #FL_ERR_ID | |||||
| sta fl_err | |||||
| sec | |||||
| rts | |||||
| @out: sec | |||||
| rts | |||||
| ; A = branch opcode, X = label id. Emit the fixed five-byte form. | ; A = branch opcode, X = label id. Emit the fixed five-byte form. | ||||
| fl_branch: | fl_branch: | ||||
| sta fl_op | sta fl_op | ||||
| @@ -217,8 +347,18 @@ fl_finish: | |||||
| bne @valid ; counting: no bytes to patch | bne @valid ; counting: no bytes to patch | ||||
| ldy fl_recp | ldy fl_recp | ||||
| lda fl_fix,y | lda fl_fix,y | ||||
| beq @valid | |||||
| cmp #$02 | |||||
| beq @patch_branch | |||||
| cmp #$01 | cmp #$01 | ||||
| beq @patch_jmp | beq @patch_jmp | ||||
| cmp #$03 | |||||
| beq @patch_jmp | |||||
| lda #FL_ERR_STATE | |||||
| sta fl_err | |||||
| sec | |||||
| rts | |||||
| @patch_branch: | |||||
| lda fl_fix+1,y | lda fl_fix+1,y | ||||
| tax | tax | ||||
| lda fl_labels,x | lda fl_labels,x | ||||
| @@ -596,8 +736,10 @@ fl_locals: .res 2 | |||||
| fl_laboff: .res 1 | fl_laboff: .res 1 | ||||
| fl_tlo: .res 1 | fl_tlo: .res 1 | ||||
| fl_thi: .res 1 | fl_thi: .res 1 | ||||
| fl_rel: .res 1 | |||||
| fl_labels: .res FL_LABELS | fl_labels: .res FL_LABELS | ||||
| fl_defined: .res FL_LABELS | fl_defined: .res FL_LABELS | ||||
| fl_used: .res FL_LABELS | |||||
| fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE | fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE | ||||
| fl_enter: .res 2 | fl_enter: .res 2 | ||||
| fl_leave: .res 2 | fl_leave: .res 2 | ||||
| @@ -56,11 +56,15 @@ | |||||
| .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_on, em_ctx, em_last_kind | |||||
| .import em_reset, 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 | ||||
| .import em_target, em_store_target, em_print, em_end_func | .import em_target, em_store_target, em_print, em_end_func | ||||
| .import em_finish, em_self, em_member_receiver, em_field_ref, em_call | |||||
| .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_return, em_method_begin, em_program_begin | |||||
| .export parse_file, tb_get, tb_set, mem_count, par_count | .export parse_file, 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 | ||||
| @@ -119,6 +123,12 @@ parse_file: | |||||
| sta pass | sta pass | ||||
| jsr em_reset | jsr em_reset | ||||
| jsr parse_pass | jsr parse_pass | ||||
| jsr em_finish | |||||
| lda em_bad | |||||
| beq :+ | |||||
| lda #E_RES_EMIT_FAILED | |||||
| jsr err2 | |||||
| : | |||||
| rts | rts | ||||
| ; Error limit reached: return straight to parse_file's caller. | ; Error limit reached: return straight to parse_file's caller. | ||||
| @@ -932,6 +942,32 @@ parse_body: | |||||
| sta depth | sta depth | ||||
| sta refs_on ; names resolve, except in the methods of | sta refs_on ; names resolve, except in the methods of | ||||
| jsr em_begin_func | jsr em_begin_func | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| lda block_stack | |||||
| cmp #KW_PROGRAM | |||||
| bne :+ | |||||
| jsr em_program_begin | |||||
| : | |||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda block_stack | |||||
| cmp #KW_METHOD | |||||
| bne :+ | |||||
| lda cur_mem | |||||
| cmp #$FF | |||||
| beq :+ | |||||
| lda cur_class | |||||
| beq :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| lda cur_mem | |||||
| jsr em_method_begin | |||||
| : | |||||
| lda block_stack ; a class whose name failed to parse | lda block_stack ; a class whose name failed to parse | ||||
| cmp #KW_METHOD | cmp #KW_METHOD | ||||
| bne @loop | bne @loop | ||||
| @@ -1013,6 +1049,15 @@ parse_body: | |||||
| ; Block block_stack[depth] has just closed: update term. At the end of a | ; Block block_stack[depth] has just closed: update term. At the end of a | ||||
| ; method body (depth 0) a method with a result must end in return. | ; method body (depth 0) a method with a result must end in return. | ||||
| close_block: | close_block: | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| ldx depth | |||||
| lda block_stack,x | |||||
| jsr em_close_block | |||||
| : | |||||
| ldx depth | ldx depth | ||||
| bne @inner | bne @inner | ||||
| lda ret_kind | lda ret_kind | ||||
| @@ -1122,9 +1167,13 @@ parse_statement: | |||||
| jsr trace_line | jsr trace_line | ||||
| lda tok_kind | lda tok_kind | ||||
| cmp #KW_IF | cmp #KW_IF | ||||
| beq @cond | |||||
| bne :+ | |||||
| jmp @cond | |||||
| : | |||||
| cmp #KW_WHILE | cmp #KW_WHILE | ||||
| beq @cond | |||||
| bne :+ | |||||
| jmp @cond | |||||
| : | |||||
| cmp #KW_FOR | cmp #KW_FOR | ||||
| bne :+ | bne :+ | ||||
| jmp @for | jmp @for | ||||
| @@ -1132,15 +1181,51 @@ parse_statement: | |||||
| bne :+ | bne :+ | ||||
| jmp @else | jmp @else | ||||
| : cmp #KW_RETURN | : cmp #KW_RETURN | ||||
| beq @return | |||||
| bne :+ | |||||
| jmp @return | |||||
| : | |||||
| cmp #KW_PRINT | cmp #KW_PRINT | ||||
| beq @print | |||||
| bne :+ | |||||
| jmp @print | |||||
| : | |||||
| cmp #KW_DELETE | cmp #KW_DELETE | ||||
| beq @delete | |||||
| bne :+ | |||||
| jmp @delete | |||||
| : | |||||
| jmp stmt_expr | jmp stmt_expr | ||||
| @cond: jsr open_block ; if / while condition | @cond: jsr open_block ; if / while condition | ||||
| lda pass | |||||
| cmp #$02 | |||||
| beq :+ | |||||
| jmp @cond_value | |||||
| : | |||||
| lda em_on | |||||
| bne :+ | |||||
| jmp @cond_value | |||||
| : | |||||
| ldx depth | |||||
| dex | |||||
| lda tok_kind | |||||
| jsr em_open_block | |||||
| @cond_value: | |||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @cond_parse | |||||
| lda em_on | |||||
| beq @cond_parse | |||||
| jsr em_stmt_begin | |||||
| @cond_parse: | |||||
| jsr value | jsr value | ||||
| jsr check_cond | jsr check_cond | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @cond_done | |||||
| lda em_on | |||||
| beq @cond_done | |||||
| ldx depth | |||||
| dex | |||||
| jsr em_cond_false | |||||
| @cond_done: | |||||
| jmp end_stmt | jmp end_stmt | ||||
| @print: lda pass ; pass 2 captures a string literal in print | @print: lda pass ; pass 2 captures a string literal in print | ||||
| cmp #$02 | cmp #$02 | ||||
| @@ -1192,6 +1277,7 @@ parse_statement: | |||||
| beq @bare | beq @bare | ||||
| jsr parse_full | jsr parse_full | ||||
| jsr check_return | jsr check_return | ||||
| jsr em_return | |||||
| jmp end_stmt | jmp end_stmt | ||||
| @bare: lda ret_kind | @bare: lda ret_kind | ||||
| cmp #RET_VALUE | cmp #RET_VALUE | ||||
| @@ -1199,7 +1285,8 @@ parse_statement: | |||||
| lda #E_SEM_MISSING_RETURN | lda #E_SEM_MISSING_RETURN | ||||
| ldx stmt_col | ldx stmt_col | ||||
| jsr err2_col | jsr err2_col | ||||
| : jmp end_stmt | |||||
| : jsr em_return | |||||
| jmp end_stmt | |||||
| @else: ldx depth ; only once, directly inside an if | @else: ldx depth ; only once, directly inside an if | ||||
| lda block_stack-1,x | lda block_stack-1,x | ||||
| cmp #KW_IF | cmp #KW_IF | ||||
| @@ -1212,6 +1299,14 @@ parse_statement: | |||||
| sta blk_then-1,x | sta blk_then-1,x | ||||
| lda #$00 | lda #$00 | ||||
| sta term | sta term | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| dex | |||||
| jsr em_else | |||||
| : | |||||
| jsr next | jsr next | ||||
| jmp end_stmt | jmp end_stmt | ||||
| @bad_else: | @bad_else: | ||||
| @@ -1220,6 +1315,16 @@ parse_statement: | |||||
| jmp syn_error | jmp syn_error | ||||
| ; for name = expr to expr [step expr] | ; for name = expr to expr [step expr] | ||||
| @for: jsr open_block | @for: jsr open_block | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @for_parse | |||||
| lda em_on | |||||
| beq @for_parse | |||||
| ldx depth | |||||
| dex | |||||
| lda #KW_FOR | |||||
| jsr em_open_block | |||||
| @for_parse: | |||||
| jsr next | jsr next | ||||
| lda tok_kind | lda tok_kind | ||||
| cmp #TOK_IDENT | cmp #TOK_IDENT | ||||
| @@ -1230,6 +1335,20 @@ parse_statement: | |||||
| sta val_col | sta val_col | ||||
| jsr resolve_name | jsr resolve_name | ||||
| jsr check_for_var | jsr check_for_var | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @for_slot | |||||
| lda em_on | |||||
| beq @for_slot | |||||
| ldx rn_id | |||||
| jsr em_expr_name | |||||
| ldx depth | |||||
| dex | |||||
| lda rn_id | |||||
| jsr em_for_var | |||||
| jsr em_target | |||||
| jsr em_stmt_begin | |||||
| @for_slot: | |||||
| ldx #$00 ; slot 0: the variable's type | ldx #$00 ; slot 0: the variable's type | ||||
| jsr save_slot | jsr save_slot | ||||
| jsr next | jsr next | ||||
| @@ -1240,6 +1359,14 @@ parse_statement: | |||||
| jmp syn_error | jmp syn_error | ||||
| : jsr value | : jsr value | ||||
| jsr check_slot0 | jsr check_slot0 | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @for_init_done | |||||
| lda em_on | |||||
| beq @for_init_done | |||||
| jsr em_stmt_end | |||||
| jsr em_store_target | |||||
| @for_init_done: | |||||
| lda tok_kind | lda tok_kind | ||||
| cmp #KW_TO | cmp #KW_TO | ||||
| beq :+ | beq :+ | ||||
| @@ -1247,11 +1374,44 @@ parse_statement: | |||||
| jmp syn_error | jmp syn_error | ||||
| : jsr value | : jsr value | ||||
| jsr check_slot0 | jsr check_slot0 | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @for_end_done | |||||
| lda em_on | |||||
| beq @for_end_done | |||||
| ldx depth | |||||
| dex | |||||
| jsr em_for_save_end | |||||
| @for_end_done: | |||||
| lda tok_kind | lda tok_kind | ||||
| cmp #KW_STEP | cmp #KW_STEP | ||||
| bne end_stmt | |||||
| bne @for_no_step | |||||
| jsr value | jsr value | ||||
| jsr check_slot0 | jsr check_slot0 | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @for_step_done | |||||
| lda em_on | |||||
| beq @for_step_done | |||||
| ldx depth | |||||
| dex | |||||
| jsr em_for_save_step | |||||
| jmp @for_setup | |||||
| @for_step_done: | |||||
| jmp end_stmt | |||||
| @for_no_step: | |||||
| lda pass | |||||
| cmp #$02 | |||||
| bne end_stmt | |||||
| lda em_on | |||||
| beq end_stmt | |||||
| ldx depth | |||||
| dex | |||||
| jsr em_for_default_step | |||||
| @for_setup: | |||||
| ldx depth | |||||
| dex | |||||
| jsr em_for_setup | |||||
| ; fall through | ; fall through | ||||
| ; The statement must end here: one statement per line (spec 2.2). | ; The statement must end here: one statement per line (spec 2.2). | ||||
| @@ -1673,6 +1833,13 @@ parse_postfix: | |||||
| @self: lda #$00 | @self: lda #$00 | ||||
| sta em_last_kind | sta em_last_kind | ||||
| jsr self_ref | jsr self_ref | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| jsr em_self | |||||
| : | |||||
| jsr next | jsr next | ||||
| jmp @post | jmp @post | ||||
| @group: lda #$00 | @group: lda #$00 | ||||
| @@ -1712,11 +1879,22 @@ parse_postfix: | |||||
| sta ex_last | sta ex_last | ||||
| @post: lda tok_kind | @post: lda tok_kind | ||||
| cmp #TOK_DOT | cmp #TOK_DOT | ||||
| beq @member | |||||
| bne :+ | |||||
| jmp @member | |||||
| : | |||||
| cmp #TOK_LBRACKET | cmp #TOK_LBRACKET | ||||
| beq @index | |||||
| bne :+ | |||||
| jmp @index | |||||
| : | |||||
| rts | rts | ||||
| @member: | @member: | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| jsr em_member_receiver | |||||
| : | |||||
| lda #$00 | lda #$00 | ||||
| sta em_last_kind | sta em_last_kind | ||||
| jsr next | jsr next | ||||
| @@ -1733,6 +1911,14 @@ parse_postfix: | |||||
| lda tok_kind | lda tok_kind | ||||
| cmp #TOK_LPAREN | cmp #TOK_LPAREN | ||||
| beq @call | beq @call | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| lda fm_row | |||||
| jsr em_field_ref | |||||
| : | |||||
| lda #EX_PLACE | lda #EX_PLACE | ||||
| sta ex_last | sta ex_last | ||||
| jmp @post | jmp @post | ||||
| @@ -1740,6 +1926,14 @@ parse_postfix: | |||||
| sta em_last_kind | sta em_last_kind | ||||
| ldx ex_depth ; sc = the result, kept over the arguments | ldx ex_depth ; sc = the result, kept over the arguments | ||||
| jsr save_slot | jsr save_slot | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne :+ | |||||
| lda em_on | |||||
| beq :+ | |||||
| lda fm_row | |||||
| jsr em_call | |||||
| : | |||||
| jsr parse_args | jsr parse_args | ||||
| ldx ex_depth | ldx ex_depth | ||||
| jsr load_sc | jsr load_sc | ||||
| @@ -0,0 +1,154 @@ | |||||
| ; E04-S06: parse calls.n8, run the emitted first method, and check that | |||||
| ; `self.n` and `self.tick()` both went through members.s. | |||||
| ; | |||||
| ; The first emitted body is Counter.both at cg_code+0. It receives self in | |||||
| ; A/X, increments self.n, then JSRs the later tick method. The fixture passes | |||||
| ; an object whose field n begins at +2. | |||||
| ; | |||||
| ; Mailbox data: +0 diagnostics, +1 em_bad, +2 n, +3 cg_len, +4 cg_err, | |||||
| ; +5 fl_err. FAIL stage: 1 load, 2 diagnostics, 3 emitter, 4 result. | |||||
| .setcpu "6502" | |||||
| .include "../../src/common/c128.inc" | |||||
| .include "../../src/common/mailbox.inc" | |||||
| .include "../../src/common/formats.inc" | |||||
| .import mmu_init, mmu_bank0, mmu_shutdown | |||||
| .import src_load, src_dos_code, src_kernal_err | |||||
| .import names_init | |||||
| .import diag_init, diag_finish, diag_screen | |||||
| .import parse_file | |||||
| .import em_on, em_bad | |||||
| .import cg_code, cg_len, cg_err | |||||
| .import fl_err | |||||
| .macro ON_ZERO target | |||||
| beq :+ | |||||
| jmp target | |||||
| : | |||||
| .endmacro | |||||
| .segment "LOADADDR" | |||||
| .word $1C01 | |||||
| .segment "EXEHDR" | |||||
| .word basic_end | |||||
| .word 10 | |||||
| .byte $9E | |||||
| .byte "7181" | |||||
| .byte $00 | |||||
| basic_end: | |||||
| .word $0000 | |||||
| .segment "MAILBOX" | |||||
| .res MB_SIZE | |||||
| .segment "STARTUP" | |||||
| .export test_entry | |||||
| test_entry: | |||||
| php | |||||
| sei | |||||
| ldx #MB_SIZE - 1 | |||||
| lda #$00 | |||||
| @clr: sta MB_BASE,x | |||||
| dex | |||||
| bpl @clr | |||||
| lda #MB_MAGIC0_VALUE | |||||
| sta MB_MAGIC0 | |||||
| lda #MB_MAGIC1_VALUE | |||||
| sta MB_MAGIC1 | |||||
| lda #MB_PROD_COMPILER | |||||
| sta MB_PRODUCER | |||||
| lda #MB_ST_RUNNING | |||||
| sta MB_STATUS | |||||
| jsr mmu_init | |||||
| jsr mmu_bank0 | |||||
| lda #<name_src | |||||
| ldx #>name_src | |||||
| ldy #name_src_len | |||||
| jsr src_load | |||||
| cmp #$00 | |||||
| ON_ZERO @fail_load | |||||
| jsr names_init | |||||
| lda #<name_src | |||||
| ldx #>name_src | |||||
| ldy #name_src_len | |||||
| jsr diag_init | |||||
| lda #$00 | |||||
| sta diag_screen | |||||
| lda #$01 | |||||
| sta em_on | |||||
| jsr parse_file | |||||
| jsr diag_finish | |||||
| sta MB_DATA+0 | |||||
| lda em_bad | |||||
| sta MB_DATA+1 | |||||
| lda cg_len | |||||
| sta MB_DATA+3 | |||||
| lda cg_err | |||||
| sta MB_DATA+4 | |||||
| lda fl_err | |||||
| sta MB_DATA+5 | |||||
| lda MB_DATA+0 | |||||
| ON_ZERO @fail_diag | |||||
| lda em_bad | |||||
| ON_ZERO @fail_emit | |||||
| lda cg_err | |||||
| ON_ZERO @fail_cg | |||||
| lda fl_err | |||||
| ON_ZERO @fail_cg | |||||
| jmp @run | |||||
| @fail_cg: | |||||
| lda #3 | |||||
| jmp @fail | |||||
| @run: ldx #3 ; descriptor + zeroed byte field | |||||
| lda #$00 | |||||
| @obj: sta object,x | |||||
| dex | |||||
| bpl @obj | |||||
| lda #<object | |||||
| ldx #>object | |||||
| jsr cg_code | |||||
| lda object+2 | |||||
| sta MB_DATA+2 | |||||
| cmp #$02 | |||||
| beq @pass | |||||
| lda #4 | |||||
| jmp @fail | |||||
| @fail_load: | |||||
| sta MB_DATA+4 | |||||
| lda src_dos_code | |||||
| sta MB_DATA+5 | |||||
| lda src_kernal_err | |||||
| sta MB_DATA+6 | |||||
| lda #1 | |||||
| jmp @fail | |||||
| @fail_diag: | |||||
| lda #2 | |||||
| jmp @fail | |||||
| @fail_emit: | |||||
| lda #3 | |||||
| @fail: sta MB_CODE | |||||
| lda #$00 | |||||
| sta MB_CODE+1 | |||||
| lda #MB_ST_FAIL | |||||
| sta MB_STATUS | |||||
| jmp @done | |||||
| @pass: lda #MB_ST_PASS | |||||
| sta MB_STATUS | |||||
| @done: jsr mmu_shutdown | |||||
| plp | |||||
| rts | |||||
| .segment "RODATA" | |||||
| name_src: .byte "CALLS.N8" | |||||
| name_src_len = * - name_src | |||||
| .segment "BSS" | |||||
| object: .res 4 | |||||
| @@ -0,0 +1,16 @@ | |||||
| class Counter | |||||
| byte n | |||||
| public method both() | |||||
| self.n = self.n + 1 | |||||
| self.tick() | |||||
| end method | |||||
| public method tick() | |||||
| self.n = self.n + 1 | |||||
| return | |||||
| end method | |||||
| end class | |||||
| program calls | |||||
| end program | |||||
| @@ -27,6 +27,7 @@ | |||||
| .import diag_init, diag_finish, diag_screen | .import diag_init, diag_finish, diag_screen | ||||
| .import parse_file | .import parse_file | ||||
| .import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr | .import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr | ||||
| .import em_entry | |||||
| .import cg_code, cg_len, cg_err | .import cg_code, cg_len, cg_err | ||||
| ; A conditional branch to a far error path: near branch over an absolute JMP. | ; A conditional branch to a far error path: near branch over an absolute JMP. | ||||
| @@ -131,7 +132,14 @@ test_entry: | |||||
| lda #>em_fp | lda #>em_fp | ||||
| sta FRAME_FP+1 | sta FRAME_FP+1 | ||||
| jsr cg_code ; run the emitted body | |||||
| lda #<cg_code ; run the program body, not a class method | |||||
| clc | |||||
| adc em_entry | |||||
| sta run_ptr | |||||
| lda #>cg_code | |||||
| adc #$00 | |||||
| sta run_ptr+1 | |||||
| jsr run_entry | |||||
| lda em_fp - 2 ; a: the first local (FP-2) | lda em_fp - 2 ; a: the first local (FP-2) | ||||
| sta MB_DATA+2 | sta MB_DATA+2 | ||||
| @@ -167,6 +175,15 @@ test_entry: | |||||
| plp | plp | ||||
| rts | rts | ||||
| ; JSR to the address in run_ptr (self-modifying, so a normal RTS returns here). | |||||
| run_entry: | |||||
| lda run_ptr | |||||
| sta @call+1 | |||||
| lda run_ptr+1 | |||||
| sta @call+2 | |||||
| @call: jsr $FFFF | |||||
| rts | |||||
| ; ------------------------------------------------------------- recorders | ; ------------------------------------------------------------- recorders | ||||
| ; A/X = address, Y = length: record the literal bytes. | ; A/X = address, Y = length: record the literal bytes. | ||||
| rec_text: | rec_text: | ||||
| @@ -209,6 +226,7 @@ rec_log: | |||||
| .segment "ZEROPAGE" | .segment "ZEROPAGE" | ||||
| rec_ptr: .res 2 | rec_ptr: .res 2 | ||||
| run_ptr: .res 2 | |||||
| .segment "RODATA" | .segment "RODATA" | ||||
| name_src: .byte "STMTS.N8" | name_src: .byte "STMTS.N8" | ||||
| @@ -1,10 +1,15 @@ | |||||
| // E04-S06 emission fixture: byte locals, assignment, arithmetic, print. | |||||
| program stmts | program stmts | ||||
| var a as byte | var a as byte | ||||
| var b as byte | var b as byte | ||||
| a = 7 | |||||
| b = a | |||||
| a = a + 3 | |||||
| print "sum" | |||||
| print a | |||||
| for a = 1 to 1 | |||||
| b = 1 | |||||
| end for | |||||
| if b = 1 | |||||
| b = 2 | |||||
| else | |||||
| b = 3 | |||||
| end if | |||||
| while b < 3 | |||||
| b = 3 | |||||
| end while | |||||
| end program | end program | ||||
| @@ -31,6 +31,7 @@ | |||||
| .import names_init, name_get, names_count, name_out, name_out_len | .import names_init, name_get, names_count, name_out, name_out_len | ||||
| .import diag_init, diag_finish, diag_screen | .import diag_init, diag_finish, diag_screen | ||||
| .import parse_file, type_kind, type_line_lo, type_line_hi | .import parse_file, type_kind, type_line_lo, type_line_hi | ||||
| .import em_on | |||||
| .import tb_get, mem_count, ex_trace_on, ex_min_sp | .import tb_get, mem_count, ex_trace_on, ex_min_sp | ||||
| .importzp ex_tp | .importzp ex_tp | ||||
| @@ -124,6 +125,8 @@ test_entry: | |||||
| jsr diag_init | jsr diag_init | ||||
| lda #$00 ; ERR files are the evidence; keep it fast | lda #$00 ; ERR files are the evidence; keep it fast | ||||
| sta diag_screen | sta diag_screen | ||||
| lda #$00 ; parser fixtures only type-check | |||||
| sta em_on | |||||
| lda #<trace_buf | lda #<trace_buf | ||||
| sta ex_tp | sta ex_tp | ||||
| lda #>trace_buf | lda #>trace_buf | ||||
Powered by TurnKey Linux.