diff --git a/newLang/docs/architecture.md b/newLang/docs/architecture.md index 77d0346..382ca71 100644 --- a/newLang/docs/architecture.md +++ b/newLang/docs/architecture.md @@ -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 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 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 -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, 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. 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 diff --git a/newLang/docs/decisions.md b/newLang/docs/decisions.md index 106214d..2858982 100644 --- a/newLang/docs/decisions.md +++ b/newLang/docs/decisions.md @@ -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. | | 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. | +| 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 diff --git a/newLang/docs/diagnostics.md b/newLang/docs/diagnostics.md index 7fba465..5637b18 100644 --- a/newLang/docs/diagnostics.md +++ b/newLang/docs/diagnostics.md @@ -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 | | E225 | `RES_EXPR_TOO_DEEP` | EXPRESSION NESTED TOO DEEPLY | | E226 | `RES_TOO_MANY_IMPLEMENTS` | MORE THAN 255 IMPLEMENTED INTERFACES | +| E227 | `RES_EMIT_FAILED` | CANNOT EMIT THIS CODE YET | diff --git a/newLang/docs/memory-map.md b/newLang/docs/memory-map.md index 528de8f..d9be5a8 100644 --- a/newLang/docs/memory-map.md +++ b/newLang/docs/memory-map.md @@ -65,8 +65,12 @@ the first field at +2, so generated code needs no runtime translation. | Combine temp | `EM_TMP` = `$F7` | | Parameters | `FP+2` upward, 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) | -| 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 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 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) | Element | Value | diff --git a/newLang/planning/claims/E04-S06.md b/newLang/planning/claims/E04-S06.md index 4dd24bd..5e53c34 100644 --- a/newLang/planning/claims/E04-S06.md +++ b/newLang/planning/claims/E04-S06.md @@ -1,8 +1,9 @@ --- story: E04-S06 owner: codebuff -status: in-progress +status: done started: 2026-11-09 +completed: 2026-11-09 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 -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 -- `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/calls.n8`, `tests/compiler/calls-test.s` - `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` ## 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 -- `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`. -- `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 -- 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. -- 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. diff --git a/newLang/scripts/build-calls-test.ps1 b/newLang/scripts/build-calls-test.ps1 new file mode 100644 index 0000000..e6b362a --- /dev/null +++ b/newLang/scripts/build-calls-test.ps1 @@ -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) diff --git a/newLang/scripts/build-hello-test.ps1 b/newLang/scripts/build-hello-test.ps1 index e99ba20..d2e65a5 100644 --- a/newLang/scripts/build-hello-test.ps1 +++ b/newLang/scripts/build-hello-test.ps1 @@ -18,6 +18,8 @@ $link = Invoke-Nova128Link -Name "hello-test" -BuildDir $out ` (Join-Path $project "src\compiler\diagnostics.s"), (Join-Path $project "src\compiler\parser.s"), (Join-Path $project "src\compiler\codegen.s"), + (Join-Path $project "src\compiler\flow.s"), + (Join-Path $project "src\compiler\members.s"), (Join-Path $project "src\compiler\emit.s"), (Join-Path $project "src\compiler\interfaces.s"), (Join-Path $project "src\compiler\types.s"), diff --git a/newLang/scripts/build-parser-test.ps1 b/newLang/scripts/build-parser-test.ps1 index 1c49113..b94234b 100644 --- a/newLang/scripts/build-parser-test.ps1 +++ b/newLang/scripts/build-parser-test.ps1 @@ -67,6 +67,8 @@ $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` (Join-Path $project "src\compiler\diagnostics.s"), (Join-Path $project "src\compiler\parser.s"), (Join-Path $project "src\compiler\codegen.s"), + (Join-Path $project "src\compiler\flow.s"), + (Join-Path $project "src\compiler\members.s"), (Join-Path $project "src\compiler\emit.s"), (Join-Path $project "src\compiler\interfaces.s"), (Join-Path $project "src\compiler\types.s"), diff --git a/newLang/scripts/build-statements-test.ps1 b/newLang/scripts/build-statements-test.ps1 index fba1cee..114475c 100644 --- a/newLang/scripts/build-statements-test.ps1 +++ b/newLang/scripts/build-statements-test.ps1 @@ -21,6 +21,7 @@ $link = Invoke-Nova128Link -Name "statements-test" -BuildDir $out ` (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\statements-test.s")) diff --git a/newLang/scripts/verify-calls-test.ps1 b/newLang/scripts/verify-calls-test.ps1 new file mode 100644 index 0000000..a64086c --- /dev/null +++ b/newLang/scripts/verify-calls-test.ps1 @@ -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" diff --git a/newLang/scripts/verify-hello-test.ps1 b/newLang/scripts/verify-hello-test.ps1 index b3d6851..c927602 100644 --- a/newLang/scripts/verify-hello-test.ps1 +++ b/newLang/scripts/verify-hello-test.ps1 @@ -55,6 +55,7 @@ if (-not $failures) { try { $deadline = [DateTime]::UtcNow.AddSeconds(60) while ([DateTime]::UtcNow -lt $deadline) { + Invoke-Nova128Monitor $session "x" | Out-Null Start-Sleep -Seconds 1 try { $screenRam = Read-Nova128Memory $session 0x0400 1000 } catch { continue } for ($r = 0; $r -lt 25; $r++) { diff --git a/newLang/scripts/verify-statements-test.ps1 b/newLang/scripts/verify-statements-test.ps1 index ff082bc..f0b35ed 100644 --- a/newLang/scripts/verify-statements-test.ps1 +++ b/newLang/scripts/verify-statements-test.ps1 @@ -33,23 +33,13 @@ if ($mb[3] -ne 0xA5) { } if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8])" } 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[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 ")) } -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" diff --git a/newLang/src/common/diagcat.inc b/newLang/src/common/diagcat.inc index a38e4da..50a2f33 100644 --- a/newLang/src/common/diagcat.inc +++ b/newLang/src/common/diagcat.inc @@ -104,3 +104,4 @@ DIAGNOSTIC RES_TOO_MANY_PARAMS, 224, "MORE THAN 255 PARAMETERS" DIAGNOSTIC RES_EXPR_TOO_DEEP, 225, "EXPRESSION NESTED TOO DEEPLY" DIAGNOSTIC RES_TOO_MANY_IMPLEMENTS, 226, "MORE THAN 255 IMPLEMENTED INTERFACES" + DIAGNOSTIC RES_EMIT_FAILED, 227, "CANNOT EMIT THIS CODE YET" diff --git a/newLang/src/common/emit.inc b/newLang/src/common/emit.inc index 73351e6..c550f82 100644 --- a/newLang/src/common/emit.inc +++ b/newLang/src/common/emit.inc @@ -9,13 +9,25 @@ EMIT_INC = 1 EM_TMP = $F7 ; two-byte combine temp; below MB_SELF ($F9) 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_TARGET = 2 ; the assignment target is not a scalar local EM_ERR_OP = 3 ; operator the emitter does not handle yet 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_PARAM = 1 +EM_LAST_LOCAL = 1 +EM_LAST_SELF = 2 +EM_LAST_FIELD = 3 + +EM_TGT_LOCAL = 1 +EM_TGT_FIELD = 2 + .endif diff --git a/newLang/src/common/flow.inc b/newLang/src/common/flow.inc index f75b55a..e698bf5 100644 --- a/newLang/src/common/flow.inc +++ b/newLang/src/common/flow.inc @@ -3,8 +3,8 @@ .ifndef FLOW_INC 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_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_ID = 4 ; label id out of range FL_ERR_RANGE = 5 ; branch target is behind the branch +FL_ERR_NOFREE = 6 ; no label id left in the pool .endif diff --git a/newLang/src/compiler/codegen.s b/newLang/src/compiler/codegen.s index bef82b1..1144138 100644 --- a/newLang/src/compiler/codegen.s +++ b/newLang/src/compiler/codegen.s @@ -28,6 +28,7 @@ ; cg_adc_abs A/X = address ; cg_sbc_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_clc, cg_sec, cg_rts ; 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_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_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_code, cg_len, cg_err @@ -161,6 +162,9 @@ cg_adc_zp: cg_sbc_zp: ldy #$E5 jmp cg_emit_zp +cg_cmp_zp: + ldy #$C5 + jmp cg_emit_zp cg_lda_zpy: ldy #$B1 jmp cg_emit_zp diff --git a/newLang/src/compiler/emit.s b/newLang/src/compiler/emit.s index 3c3981e..629916b 100644 --- a/newLang/src/compiler/emit.s +++ b/newLang/src/compiler/emit.s @@ -6,9 +6,11 @@ ; pass 2. ; ; 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 ; a slot is addressed with LDY #offset / LDA (FRAME_FP),Y. Offsets are @@ -26,34 +28,60 @@ .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_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 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_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_stmt_begin, em_stmt_end, em_expr_off, em_begin_print .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" .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. em_reset: - jsr cg_init + lda #$00 + jsr fl_init + jsr mb_init lda #$00 sta em_bad sta em_ctx + sta em_cmp + sta em_recv_self sta em_last_kind sta em_tgt + sta em_tgt_kind sta em_size sta em_left_size sta em_locals sta em_par_bytes + sta em_entry lda #$FF sta em_loc_next + ldx #$FF +@meth: sta em_meth_label,x + dex + bpl @meth lda #$02 sta em_par_next rts @@ -66,8 +94,18 @@ em_begin_func: lda #$00 sta em_locals sta em_ctx + sta em_cmp + sta em_recv_self sta em_last_kind 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 ; A signature starts: parameters begin at FP+2. @@ -126,6 +164,9 @@ em_stmt_begin: lda #$01 sta em_ctx sta em_last_kind + lda #$00 + sta em_cmp + sta em_recv_self rts ; 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. em_expr_name: stx em_last_nid - lda #$01 + lda #EM_LAST_LOCAL sta em_last_kind + lda #$00 + sta em_recv_self + sta em_cmp + lda em_on + bne :+ + jmp @done +: lda em_ctx beq @done lda em_sz,x @@ -178,6 +226,14 @@ em_expr_name: em_expr_const: sta em_low 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 beq @done 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 right operand is emitted. em_push_left: + lda em_on + bne :+ + jmp @done +: lda em_ctx beq @done + lda #$00 + sta em_cmp + sta em_recv_self lda em_size beq @size cmp #$01 @@ -221,12 +284,22 @@ em_push_left: ; the hardware stack; combine byte values only. em_bin: sta em_op + lda em_on + bne :+ + jmp @done +: lda em_ctx - beq @done + bne :+ + jmp @done +: lda em_left_size - beq @size + bne :+ + jmp @size +: lda em_size - beq @size + bne :+ + jmp @size +: lda #EM_TMP jsr cg_sta_zp bcs @fail @@ -238,18 +311,46 @@ em_bin: beq @plus cmp #TOK_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 sta em_bad clc 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 bcs @fail + lda #$00 + sta em_cmp lda #EM_TMP jsr cg_adc_zp bcs @fail jmp @ok @minus: jsr cg_sec bcs @fail + lda #$00 + sta em_cmp lda #EM_TMP jsr cg_sbc_zp bcs @fail @@ -299,14 +400,18 @@ em_point_slot: @out: sec 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: lda em_on beq @done 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 beq @bad lda em_sz,x @@ -314,6 +419,21 @@ em_target: cmp #$01 bne @bad 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 rts @bad: lda #EM_ERR_TARGET @@ -325,6 +445,10 @@ em_target: em_store_target: lda em_on beq @done + lda em_tgt_kind + beq @done + cmp #EM_TGT_FIELD + beq @field lda em_tgt beq @done lda #$48 ; PHA: em_point_slot clobbers A @@ -343,8 +467,17 @@ em_store_target: bcs @fail lda #$00 sta em_tgt + sta em_tgt_kind @done: clc 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 ; 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 ; 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_byte: jmp (em_pr_byte) em_tramp_cr: jmp (em_pr_cr) @@ -412,6 +547,532 @@ em_end_func: rts @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. em_fail: lda #EM_ERR_STATE @@ -419,19 +1080,24 @@ em_fail: clc rts +em_pr_text = $C0 ; print vectors; low byte never $FF +em_pr_byte = $C2 +em_pr_cr = $C4 + .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_ctx: .res 1 em_bad: .res 1 em_last_kind: .res 1 em_last_nid: .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_kind: .res 1 +em_kind_b: .res 1 em_size: .res 1 em_left_size: .res 1 em_loc_next: .res 1 @@ -441,7 +1107,21 @@ em_par_bytes: .res 1 em_low: .res 1 em_high: .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_off_b: .res 1 em_off: .res 256 em_sz: .res 256 +em_meth_label: .res 256 +em_entry: .res 1 diff --git a/newLang/src/compiler/flow.s b/newLang/src/compiler/flow.s index faeca00..ef6ae38 100644 --- a/newLang/src/compiler/flow.s +++ b/newLang/src/compiler/flow.s @@ -4,9 +4,12 @@ ; transfer that whole statements need: ; ; 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_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_release A = label id: patch pending uses now, then free the id ; fl_branch_true / fl_branch_false A = label id (BNE / BEQ) ; 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 @@ -59,7 +62,8 @@ : .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_call, fl_call_byte, fl_call_word .export fl_prologue, fl_ret, fl_ret_byte, fl_ret_word @@ -75,7 +79,8 @@ fl_init: sta fl_fix_count sta fl_err ldx #FL_LABELS - 1 -@clr: sta fl_defined,x +@clr: sta fl_used,x + sta fl_defined,x dex bpl @clr lda fl_mode @@ -83,6 +88,25 @@ fl_init: clc 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. fl_label: cmp #FL_LABELS @@ -91,6 +115,8 @@ fl_label: lda fl_defined,x bne @dup lda #$01 + sta fl_used,x + lda #$01 sta fl_defined,x lda cg_len sta fl_labels,x @@ -130,6 +156,110 @@ fl_jmp: @out: sec 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. fl_branch: sta fl_op @@ -217,8 +347,18 @@ fl_finish: bne @valid ; counting: no bytes to patch ldy fl_recp lda fl_fix,y + beq @valid + cmp #$02 + beq @patch_branch cmp #$01 beq @patch_jmp + cmp #$03 + beq @patch_jmp + lda #FL_ERR_STATE + sta fl_err + sec + rts +@patch_branch: lda fl_fix+1,y tax lda fl_labels,x @@ -596,8 +736,10 @@ fl_locals: .res 2 fl_laboff: .res 1 fl_tlo: .res 1 fl_thi: .res 1 +fl_rel: .res 1 fl_labels: .res FL_LABELS fl_defined: .res FL_LABELS +fl_used: .res FL_LABELS fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE fl_enter: .res 2 fl_leave: .res 2 diff --git a/newLang/src/compiler/parser.s b/newLang/src/compiler/parser.s index 54a5bc3..e3b0a8e 100644 --- a/newLang/src/compiler/parser.s +++ b/newLang/src/compiler/parser.s @@ -56,11 +56,15 @@ .import ty_assign, ty_binary, ty_unary .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 .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_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_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 gen_text, gen_text_len, gen_print_count, gen_bad @@ -119,6 +123,12 @@ parse_file: sta pass jsr em_reset jsr parse_pass + jsr em_finish + lda em_bad + beq :+ + lda #E_RES_EMIT_FAILED + jsr err2 +: rts ; Error limit reached: return straight to parse_file's caller. @@ -932,6 +942,32 @@ parse_body: sta depth sta refs_on ; names resolve, except in the methods of 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 cmp #KW_METHOD bne @loop @@ -1013,6 +1049,15 @@ parse_body: ; 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. close_block: + lda pass + cmp #$02 + bne :+ + lda em_on + beq :+ + ldx depth + lda block_stack,x + jsr em_close_block +: ldx depth bne @inner lda ret_kind @@ -1122,9 +1167,13 @@ parse_statement: jsr trace_line lda tok_kind cmp #KW_IF - beq @cond + bne :+ + jmp @cond +: cmp #KW_WHILE - beq @cond + bne :+ + jmp @cond +: cmp #KW_FOR bne :+ jmp @for @@ -1132,15 +1181,51 @@ parse_statement: bne :+ jmp @else : cmp #KW_RETURN - beq @return + bne :+ + jmp @return +: cmp #KW_PRINT - beq @print + bne :+ + jmp @print +: cmp #KW_DELETE - beq @delete + bne :+ + jmp @delete +: jmp stmt_expr @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 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 @print: lda pass ; pass 2 captures a string literal in print cmp #$02 @@ -1192,6 +1277,7 @@ parse_statement: beq @bare jsr parse_full jsr check_return + jsr em_return jmp end_stmt @bare: lda ret_kind cmp #RET_VALUE @@ -1199,7 +1285,8 @@ parse_statement: lda #E_SEM_MISSING_RETURN ldx stmt_col jsr err2_col -: jmp end_stmt +: jsr em_return + jmp end_stmt @else: ldx depth ; only once, directly inside an if lda block_stack-1,x cmp #KW_IF @@ -1212,6 +1299,14 @@ parse_statement: sta blk_then-1,x lda #$00 sta term + lda pass + cmp #$02 + bne :+ + lda em_on + beq :+ + dex + jsr em_else +: jsr next jmp end_stmt @bad_else: @@ -1220,6 +1315,16 @@ parse_statement: jmp syn_error ; for name = expr to expr [step expr] @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 lda tok_kind cmp #TOK_IDENT @@ -1230,6 +1335,20 @@ parse_statement: sta val_col jsr resolve_name 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 jsr save_slot jsr next @@ -1240,6 +1359,14 @@ parse_statement: jmp syn_error : jsr value 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 cmp #KW_TO beq :+ @@ -1247,11 +1374,44 @@ parse_statement: jmp syn_error : jsr value 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 cmp #KW_STEP - bne end_stmt + bne @for_no_step jsr value 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 ; The statement must end here: one statement per line (spec 2.2). @@ -1673,6 +1833,13 @@ parse_postfix: @self: lda #$00 sta em_last_kind jsr self_ref + lda pass + cmp #$02 + bne :+ + lda em_on + beq :+ + jsr em_self +: jsr next jmp @post @group: lda #$00 @@ -1712,11 +1879,22 @@ parse_postfix: sta ex_last @post: lda tok_kind cmp #TOK_DOT - beq @member + bne :+ + jmp @member +: cmp #TOK_LBRACKET - beq @index + bne :+ + jmp @index +: rts @member: + lda pass + cmp #$02 + bne :+ + lda em_on + beq :+ + jsr em_member_receiver +: lda #$00 sta em_last_kind jsr next @@ -1733,6 +1911,14 @@ parse_postfix: lda tok_kind cmp #TOK_LPAREN beq @call + lda pass + cmp #$02 + bne :+ + lda em_on + beq :+ + lda fm_row + jsr em_field_ref +: lda #EX_PLACE sta ex_last jmp @post @@ -1740,6 +1926,14 @@ parse_postfix: sta em_last_kind ldx ex_depth ; sc = the result, kept over the arguments jsr save_slot + lda pass + cmp #$02 + bne :+ + lda em_on + beq :+ + lda fm_row + jsr em_call +: jsr parse_args ldx ex_depth jsr load_sc diff --git a/newLang/tests/compiler/calls-test.s b/newLang/tests/compiler/calls-test.s new file mode 100644 index 0000000..f200aee --- /dev/null +++ b/newLang/tests/compiler/calls-test.s @@ -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 + ldy #name_src_len + jsr src_load + cmp #$00 + ON_ZERO @fail_load + + jsr names_init + lda #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 + 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 diff --git a/newLang/tests/compiler/calls.n8 b/newLang/tests/compiler/calls.n8 new file mode 100644 index 0000000..069fb93 --- /dev/null +++ b/newLang/tests/compiler/calls.n8 @@ -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 diff --git a/newLang/tests/compiler/statements-test.s b/newLang/tests/compiler/statements-test.s index 5fea396..8677af3 100644 --- a/newLang/tests/compiler/statements-test.s +++ b/newLang/tests/compiler/statements-test.s @@ -27,6 +27,7 @@ .import diag_init, diag_finish, diag_screen .import parse_file .import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr +.import em_entry .import cg_code, cg_len, cg_err ; A conditional branch to a far error path: near branch over an absolute JMP. @@ -131,7 +132,14 @@ test_entry: lda #>em_fp sta FRAME_FP+1 - jsr cg_code ; run the emitted body + lda #cg_code + adc #$00 + sta run_ptr+1 + jsr run_entry lda em_fp - 2 ; a: the first local (FP-2) sta MB_DATA+2 @@ -167,6 +175,15 @@ test_entry: plp 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 ; A/X = address, Y = length: record the literal bytes. rec_text: @@ -209,6 +226,7 @@ rec_log: .segment "ZEROPAGE" rec_ptr: .res 2 +run_ptr: .res 2 .segment "RODATA" name_src: .byte "STMTS.N8" diff --git a/newLang/tests/compiler/statements.n8 b/newLang/tests/compiler/statements.n8 index 2101f78..4c398ca 100644 --- a/newLang/tests/compiler/statements.n8 +++ b/newLang/tests/compiler/statements.n8 @@ -1,10 +1,15 @@ -// E04-S06 emission fixture: byte locals, assignment, arithmetic, print. program stmts var a 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 diff --git a/newLang/tests/parser/parser-test.s b/newLang/tests/parser/parser-test.s index e5e81af..7e06aeb 100644 --- a/newLang/tests/parser/parser-test.s +++ b/newLang/tests/parser/parser-test.s @@ -31,6 +31,7 @@ .import names_init, name_get, names_count, name_out, name_out_len .import diag_init, diag_finish, diag_screen .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 .importzp ex_tp @@ -124,6 +125,8 @@ test_entry: jsr diag_init lda #$00 ; ERR files are the evidence; keep it fast sta diag_screen + lda #$00 ; parser fixtures only type-check + sta em_on lda #trace_buf