You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.8KB


story: E04-S06 owner: codebuff status: in-progress started: 2026-11-09 depends-on: [E04-S04, E04-S05, E05-S04, E03-S04]

E04-S06 Claim

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.

File area

  • src/compiler/emit.s, src/common/emit.inc (new)
  • src/compiler/parser.s (pass-2 hooks), src/compiler/codegen.s (forms)
  • tests/compiler/statements.n8, tests/compiler/statements-test.s
  • scripts/build-statements-test.ps1, scripts/verify-statements-test.ps1
  • parser-linking build scripts (link codegen.s and emit.s)
  • docs/decisions.md, docs/architecture.md, docs/memory-map.md

Acceptance evidence

  • Partly: statements emit through codegen.s — assignment, +/-, and print. if/else, while, for, and return do not emit yet.
  • 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.
  • 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.
  • 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.

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 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 sbcs, 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.

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

Next stepControl flow through flow.s, then calls and self.field through

members.s, then report em_bad as a diagnostic.

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.

Powered by TurnKey Linux.