Bladeren bron

Complete Nova128 pass-2 statement and call emission.

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
Daniel Covington 4 dagen geleden
bovenliggende
commit
70dc25d127
24 gewijzigde bestanden met toevoegingen van 1487 en 154 verwijderingen
  1. +42
    -15
      newLang/docs/architecture.md
  2. +1
    -0
      newLang/docs/decisions.md
  3. +1
    -0
      newLang/docs/diagnostics.md
  4. +11
    -1
      newLang/docs/memory-map.md
  5. +84
    -84
      newLang/planning/claims/E04-S06.md
  6. +35
    -0
      newLang/scripts/build-calls-test.ps1
  7. +2
    -0
      newLang/scripts/build-hello-test.ps1
  8. +2
    -0
      newLang/scripts/build-parser-test.ps1
  9. +1
    -0
      newLang/scripts/build-statements-test.ps1
  10. +33
    -0
      newLang/scripts/verify-calls-test.ps1
  11. +1
    -0
      newLang/scripts/verify-hello-test.ps1
  12. +4
    -14
      newLang/scripts/verify-statements-test.ps1
  13. +1
    -0
      newLang/src/common/diagcat.inc
  14. +12
    -0
      newLang/src/common/emit.inc
  15. +3
    -2
      newLang/src/common/flow.inc
  16. +5
    -1
      newLang/src/compiler/codegen.s
  17. +698
    -18
      newLang/src/compiler/emit.s
  18. +144
    -2
      newLang/src/compiler/flow.s
  19. +204
    -10
      newLang/src/compiler/parser.s
  20. +154
    -0
      newLang/tests/compiler/calls-test.s
  21. +16
    -0
      newLang/tests/compiler/calls.n8
  22. +19
    -1
      newLang/tests/compiler/statements-test.s
  23. +11
    -6
      newLang/tests/compiler/statements.n8
  24. +3
    -0
      newLang/tests/parser/parser-test.s

+ 42
- 15
newLang/docs/architecture.md Bestand weergeven

@@ -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



+ 1
- 0
newLang/docs/decisions.md Bestand weergeven

@@ -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



+ 1
- 0
newLang/docs/diagnostics.md Bestand weergeven

@@ -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 |

+ 11
- 1
newLang/docs/memory-map.md Bestand weergeven

@@ -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 |


+ 84
- 84
newLang/planning/claims/E04-S06.md Bestand weergeven

@@ -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.

+ 35
- 0
newLang/scripts/build-calls-test.ps1 Bestand weergeven

@@ -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)

+ 2
- 0
newLang/scripts/build-hello-test.ps1 Bestand weergeven

@@ -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"),


+ 2
- 0
newLang/scripts/build-parser-test.ps1 Bestand weergeven

@@ -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"),


+ 1
- 0
newLang/scripts/build-statements-test.ps1 Bestand weergeven

@@ -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"))


+ 33
- 0
newLang/scripts/verify-calls-test.ps1 Bestand weergeven

@@ -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"

+ 1
- 0
newLang/scripts/verify-hello-test.ps1 Bestand weergeven

@@ -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++) {


+ 4
- 14
newLang/scripts/verify-statements-test.ps1 Bestand weergeven

@@ -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"

+ 1
- 0
newLang/src/common/diagcat.inc Bestand weergeven

@@ -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"

+ 12
- 0
newLang/src/common/emit.inc Bestand weergeven

@@ -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

+ 3
- 2
newLang/src/common/flow.inc Bestand weergeven

@@ -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

+ 5
- 1
newLang/src/compiler/codegen.s Bestand weergeven

@@ -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


+ 698
- 18
newLang/src/compiler/emit.s Bestand weergeven

@@ -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

+ 144
- 2
newLang/src/compiler/flow.s Bestand weergeven

@@ -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


+ 204
- 10
newLang/src/compiler/parser.s Bestand weergeven

@@ -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


+ 154
- 0
newLang/tests/compiler/calls-test.s Bestand weergeven

@@ -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

+ 16
- 0
newLang/tests/compiler/calls.n8 Bestand weergeven

@@ -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

+ 19
- 1
newLang/tests/compiler/statements-test.s Bestand weergeven

@@ -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 ; 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)
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"


+ 11
- 6
newLang/tests/compiler/statements.n8 Bestand weergeven

@@ -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

+ 3
- 0
newLang/tests/parser/parser-test.s Bestand weergeven

@@ -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
sta ex_tp
lda #>trace_buf


Laden…
Annuleren
Opslaan

Powered by TurnKey Linux.