Преглед изворни кода

Add the Nova128 runtime and codegen layer through E05-S03.

Generated programs now have control-flow emission, text and fault output, a
free-list heap, class descriptors, and object construction/deletion. Each
story lands with an x128 fixture and verifier so the runtime stays proven
before the parser drives code generation.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
main
Daniel Covington пре 5 дана
родитељ
комит
b6425bd57b
43 измењених фајлова са 6063 додато и 26 уклоњено
  1. +186
    -8
      newLang/docs/architecture.md
  2. +5
    -0
      newLang/docs/decisions.md
  3. +57
    -3
      newLang/docs/memory-map.md
  4. +98
    -0
      newLang/planning/claims/E04-S04.md
  5. +120
    -0
      newLang/planning/claims/E04-S05.md
  6. +87
    -0
      newLang/planning/claims/E05-S01.md
  7. +91
    -0
      newLang/planning/claims/E05-S02.md
  8. +102
    -0
      newLang/planning/claims/E05-S03.md
  9. +23
    -0
      newLang/scripts/build-class-test.ps1
  10. +17
    -0
      newLang/scripts/build-control-test.ps1
  11. +25
    -0
      newLang/scripts/build-heap-test.ps1
  12. +32
    -0
      newLang/scripts/build-hello-test.ps1
  13. +27
    -0
      newLang/scripts/build-object-test.ps1
  14. +24
    -0
      newLang/scripts/build-print-test.ps1
  15. +76
    -0
      newLang/scripts/verify-class-test.ps1
  16. +42
    -0
      newLang/scripts/verify-control-test.ps1
  17. +55
    -0
      newLang/scripts/verify-heap-test.ps1
  18. +78
    -0
      newLang/scripts/verify-hello-test.ps1
  19. +46
    -0
      newLang/scripts/verify-object-test.ps1
  20. +67
    -0
      newLang/scripts/verify-print-test.ps1
  21. +49
    -0
      newLang/src/common/class.inc
  22. +17
    -0
      newLang/src/common/faults.inc
  23. +22
    -0
      newLang/src/common/flow.inc
  24. +5
    -1
      newLang/src/common/formats.inc
  25. +37
    -0
      newLang/src/common/heap.inc
  26. +14
    -0
      newLang/src/common/print.inc
  27. +20
    -1
      newLang/src/compiler/codegen.s
  28. +604
    -0
      newLang/src/compiler/flow.s
  29. +199
    -0
      newLang/src/compiler/output.s
  30. +74
    -9
      newLang/src/compiler/parser.s
  31. +207
    -0
      newLang/src/runtime/construct.s
  32. +87
    -0
      newLang/src/runtime/faults.s
  33. +11
    -4
      newLang/src/runtime/frames.s
  34. +742
    -0
      newLang/src/runtime/heap.s
  35. +127
    -0
      newLang/src/runtime/objects.s
  36. +152
    -0
      newLang/src/runtime/print.s
  37. +633
    -0
      newLang/tests/codegen/control-test.s
  38. +265
    -0
      newLang/tests/compiler/hello-test.s
  39. +4
    -0
      newLang/tests/compiler/hello.n8
  40. +340
    -0
      newLang/tests/runtime/class-test.s
  41. +584
    -0
      newLang/tests/runtime/heap-test.s
  42. +419
    -0
      newLang/tests/runtime/object-test.s
  43. +193
    -0
      newLang/tests/runtime/print-test.s

+ 186
- 8
newLang/docs/architecture.md Прегледај датотеку

@@ -9,8 +9,9 @@ version: 0.1-draft

Status: approved version 1 baseline. The compiler memory map is frozen by
E01-S05 (section 4.4, `docs/memory-map.md`). E04-S01 defines the generated
PRG/MAP container and low-memory startup; the higher generated-program map
(banking, heap, software stack) remains open until E04-S02.
PRG/MAP container and low-memory startup; E04-S02 adds the frame runtime; E05-S01
supplies an explicit-region heap; E05-S02 freezes class descriptors and MAP
class records. Generated-program heap placement and banking remain open.

## 1. System Boundary

@@ -72,12 +73,14 @@ src/
interfaces.s conformance and interface slot assignment
sizing.s dry-run code sizing and address assignment
codegen.s documented 8502 instruction emission
flow.s labels, branches, calls, and frame prologue/epilogue
output.s PRG, ERR, and MAP writers
diagnostics.s stable error catalog and recovery
runtime/
startup.s generated-program entry and initialization
frames.s software argument/local-frame support
objects.s object construction and destruction
objects.s object and descriptor accessors
construct.s object construction, deletion, and indirect call
interfaces.s interface conversion and indirect call trampoline
heap.s deterministic allocator and free list
arithmetic.s non-inline arithmetic helpers
@@ -368,9 +371,21 @@ fields in declaration order with no hidden alignment padding:
... remaining fields
```

The class descriptor contains the instance size, optional destructor address,
and a directory of implemented interfaces. Its final packed format is frozen
by E05 and E06 stories.
E05-S02 freezes the class descriptor as an 8-byte packed record:

```text
+0..1 instance size (includes the 2-byte object header)
+2..3 destructor address; 0 = none
+4 implemented-interface count
+5..6 interface-directory pointer; 0 when the count is 0
+7 reserved flags; must be 0
```

The interface directory is a count-long array of 4-byte entries: a pointer to
the interface descriptor and a pointer to its method table in slot order.
There is no parent-class pointer or inheritance slot. `src/runtime/objects.s`
provides read-only `obj_class`, `obj_size`, `obj_destroy`, and
`obj_iface_dir` accessors.

### 7.5 Interface representation and dispatch

@@ -421,8 +436,84 @@ remainder in `ar_rem`. Division by zero sets provisional `ar_fault` to
`AR_FAULT_DIV0`, returns A=X=0 with C=1, and is mapped to the final runtime
fault namespace by E04-S05. The scalar fixture executes the emitted shapes,
checks short-circuit evaluation, and compares a reviewed golden byte stream
and da65 disassembly. Parser integration into statements and frames is
E04-S04's responsibility.
and da65 disassembly. Parser integration into statements and frames remains
the next backend step.

### 7.7 Control flow, calls, and returns (E04-S04)

`src/compiler/flow.s` supplies the statement-level control transfer above the
scalar emitter. It keeps 16 labels and 32 fixups per routine. A label records
the byte offset where it was defined; `fl_jmp` emits an absolute `JMP` and is
also the back edge of `while` and `for` loops. `fl_branch` takes a branch
opcode and a label and always occupies five bytes:

```text
short Bxx distance ; target within +0..+127
NOP NOP NOP ; unused JMP slot
long Bxx'inv +3 ; inverse of the requested condition
JMP target
```

The form is chosen when fixups are patched. A forward branch whose target is
behind it is `FL_ERR_RANGE`; loops use `JMP`. Patching the long form inverts
the opcode, so a true condition falls through to the `JMP` and a false one
skips it. Because every conditional branch is five bytes, `cg_put`'s counting
mode produces exactly the same label offsets and routine length as emission
without writing memory, which is the sizing half of the two-pass contract.

Calls follow section 7.3: `fl_call` emits `JSR` and then `frame_discard` for
the argument bytes. `fl_call_byte` and `fl_call_word` save the result on the
hardware stack across the discard, and `frame_discard` preserves A/X. A
routine returns through `fl_ret` (no result), `fl_ret_byte` (A), or
`fl_ret_word` (A/X); the result variants save and restore the result around
`frame_leave`. `fl_prologue` emits `frame_enter` for the local byte count and
branches to the caller's error label on carry.

The x128 fixture builds a nested routine (prologue, `while`, `if`/`else`,
`for`, call with argument discard, value return), checks sizing against a
counting pass, verifies both branch encodings in memory, and executes it.
Parser-to-emitter wiring, method address assignment, and compiler pass 3 are
not part of this layer.

### 7.8 Print and runtime faults (E04-S05)

`src/runtime/print.s` writes PETSCII through `CHROUT` under the D129
generated-program mapping. It prints a byte, an unsigned word, a signed
integer, a boolean (`TRUE`/`FALSE`), a length-address text block, and a
carriage return. Decimal output has no leading zeros; only `pr_cr` ends a
line. The routines append nothing on their own and do not change CR, RCR, or
the P0/P1 pointers, so a runtime message cannot relocate the program.
`pr_text` uses a zero-page pointer, so it depends on the system zero page
being mapped as in D129.

`src/runtime/faults.s` maps the provisional arithmetic fault onto a stable
R-code namespace: R001 null, R002 bounds, R003 divide by zero, R004 software
stack overflow, and R005 heap exhaustion. `flt_report` writes the code and
fault address into the mailbox, sets `MB_ST_FAIL`, and prints
`RUNTIME FAULT R00n`. The code and address are written before the status so a
monitor never observes a failure with stale fields. The routine returns;
unwinding to the runtime entry remains with the frame/exception work.

### 7.9 First source-to-code proof (E04-S05)

The `hello.n8` compile fixture closes one source-to-code path without claiming
the general backend. Parser pass 2 records a print that consists of one string
literal in the bounded `gen_text` buffer, sets `gen_print_count`, and sets
`gen_bad` for any other print shape. The fixture then emits the D128 container
by hand: a 12-byte BASIC stub section at `$1C01` and a code section at
`$1C0D`. The fixture must initialize `out_load` and `out_entry` itself;
`output.s` reads them but does not assign them.

The emitted body saves the entry CR, selects `$0E`, writes each captured
PETSCII byte with `LDA #byte` / `JSR $FFD2`, restores CR, and returns to BASIC.
It does not call `startup.s` or the scalar/control-flow emitters. A successful
compile produces a 118-byte `HELLO.PRG`; a second x128 session autostarts the
extracted PRG and the host checks for `HELLO FROM NOVA128` on the screen.

This is deliberately narrower than compiler pass 3: expressions, calls,
multiple statements per program, and address assignment for generated code
remain open. Pass-2 code growth also made some parser branches exceed ca65's
+/-128 range, so the literal path uses a near branch and `JMP` trampoline.

## 8. Heap and Ownership

@@ -437,6 +528,93 @@ E04-S04's responsibility.
- Aliases can outlive a deletion; that is a program error. Debug poisoning may
help diagnose it but is not a correctness mechanism.

### 8.1 Free-list heap (E05-S01)

`src/runtime/heap.s` supplies the allocator; `src/common/heap.inc` fixes the
block/API contract. The caller supplies the region, so this does not freeze the
still-open generated-program map. In the E05-S01 fixture the region is bank-0
`$4000-$4FFF` under CR `$0E`.

Each block has a four-byte header:

```text
+0..1 total block size; bit 15 set while allocated
+2..3 next free block while free; $A55A while allocated
+4.. payload
```

Blocks are two-byte aligned and the minimum block is six bytes. Free blocks
are kept in ascending address order. `heap_alloc` takes a payload byte count in
A/X, walks first-fit, splits a remainder of at least six bytes, zeroes the
payload, and returns its pointer in A/X. `heap_free` takes a payload pointer
and inserts/coalesces with adjacent free blocks. A zero pointer is a no-op.

Range, size, used-magic, and double-free checks are always on. The allocator
returns `HEAP_ERR_*` rather than printing; generated `new` maps allocation
failure to R005 when E05-S03 wires construction. The stress fixture frees in
different orders and requires the initial free capacity to return.

Heap placement for generated programs and banked heaps remain open. The
fixture keeps code below `$4000` and uses only its explicit 4K region.

### 8.2 Class descriptors and objects (E05-S02)

`src/common/class.inc` fixes the object/descriptor ABI and `objects.s` reads it
without allocating or changing object state. The class fixture covers a class
with no fields and no destructor, and a class with an optional destructor and
one interface-directory entry.

`output.s` accepts class records through `out_class` and appends deterministic
MAP lines after the section records:

```text
CLASS name size destroy interface-count interface-directory
```

All numeric fields are four hex digits except the interface count, which is
two. Empty classes and no destructor are written as `0000`; records keep the
caller's order. The class test reads the emitted MAP back and compares it with
the descriptor's actual destructor and directory addresses.

### 8.3 Construction and deletion (E05-S03)

`src/runtime/construct.s` implements `new` and `delete` on top of the heap and
descriptor layers, so both can be tested before parser-to-codegen exists.

`obj_new` takes A/X = class descriptor plus `obj_init_ptr` chosen by the caller
(0 = no constructor). It reads the descriptor's instance size and refuses zero
with C=1/`OBJ_ERR_BAD`, allocates exactly that many bytes, and maps
`HEAP_ERR_OOM` to C=1/`OBJ_ERR_HEAP` with A/X=0. Only after a successful
allocation does it write the descriptor pointer at object+0 and call the init
entry once with A/X = self; the payload is already zeroed by the heap, so
fields start at zero. Constructor arguments are not copied: they stay on the
caller's software frame stack, init enters its normal frame and reads them from
`FP+2` right-to-left, and the caller discards them with `frame_discard` after
`obj_new` returns. A failed allocation never calls init.

`obj_delete` takes A/X = object. Null returns C=0 without touching the heap or
the destructor. Otherwise it reads the descriptor's optional destructor, calls
it once with A/X = self and no arguments, then frees the complete block with
`heap_free`, so an instance returns to the coalescing free list in one piece.

Both calls use `obj_call`, the synthetic-return trampoline. The entry address
travels in zero-page `co_vec` rather than A/X, because A/X already carry the
callee's self pointer; `obj_call` saves them, pushes a two-byte return address,
restores them, and ends with `JMP (co_vec)`. The callee's `RTS` lands on the
trampoline's own `RTS`, which returns to the original `JSR`. `co_vec` must not
straddle a page (link-time assertion) because of the 6502 indirect-JMP bug, and
every such call costs two extra hardware-stack bytes.

`construct.s` reports through `obj_error` and the carry flag rather than
printing, so generated code still owns the R005 fault report for `new`.

The fixture constructs a five-byte class whose `init(value as word, tag as
byte)` stores both fields beside a destructor, then checks the stored header,
zeroed fields, the constructor's arguments and self, that `delete null` calls
nothing, that deletion calls the destructor once with the fields intact and
restores the full free capacity, that a `$FFFF` instance fails without calling
init, and that a class without a destructor still frees completely.

## 9. KERNAL and Disk I/O

The compiler uses documented KERNAL entry points through `diskio.s` wrappers.


+ 5
- 0
newLang/docs/decisions.md Прегледај датотеку

@@ -57,6 +57,11 @@ documented default. `Proposed` means a later change still needs approval.
| D128 | The generated PRG container loads at `$1C01` with the same 12-byte BASIC 7 stub as the compiler and enters at `$1C0D`. Sections follow in ascending address order; gaps are zero-filled. `NAME.MAP` is CR-terminated text: `NOVA128 MAP 1`, `ENTRY hhhh`, then `SECTION name start size` in hex. Existing `NAME.PRG`/`NAME.MAP` is refused with E202 instead of overwritten; outputs created by the run are scratched on abort or failure. The higher generated-program map (banking, heap, software stack) remains E04-S02. | Gives E04-S01 a deterministic, testable container while preserving a previously successful build. Refusing an existing output is explicit; the user must erase it before recompiling. Implemented by `output.s`, `startup.s`, and `formats.inc`. |
| D129 | Generated code executes with CR `$0E` and IRQs masked, then restores the entry CR. The software stack is bank 0 `$B800-$BFFF`, growing down from `$C000`; `SP`/`FP` use `$FB-$FE`. A frame stores the previous FP at `FP`, arguments from `FP+2`, and locals below `FP`; callers push arguments right-to-left and discard them after return. Results are A or A/X. `frame_enter` reserves the old FP and locals transactionally, refuses when the 6502 stack pointer is below `$40`, and returns C=1/`FRAME_ERR_STACK` with `SP`/`FP` restored on overflow. | Makes recursion and nested calls testable without consuming BASIC's zero page or the compiler's private stack. The $40 hardware-stack guard prevents a JAM before the software bound, and the transactional failure lets callers unwind cleanly. The 2 KB software stack, guard threshold, and mapping install are provisional until codegen and later runtime stories. Implemented by `frames.s` and `frames.inc`. |
| D130 | Scalar codegen emits documented 6502 instructions into a bounded 256-byte buffer. Values are A (one byte) or A/X low/high (two bytes); assignments use absolute stores, comparisons produce a 0/1 byte, and forward branches use checked fixups. Multiplication and signed/unsigned division call runtime helpers with operands in `ar_a`/`ar_b`; results are A/X, division remainder is `ar_rem`, and division by zero sets provisional `ar_fault=AR_FAULT_DIV0`, A=X=0, C=1. E04-S04 owns parser-to-emitter integration, control flow, and long-branch expansion. | Gives the backend a small, directly testable instruction layer and a deterministic golden stream before the control-flow shell exists. The 256-byte buffer and forward-only branches are deliberate limits; E04-S05 maps the provisional arithmetic fault to the final R-code namespace. Implemented by `codegen.s`, `codegen.inc`, `arithmetic.s`, and `arithmetic.inc`. |
| D131 | Control-flow emission (E04-S04) uses `src/compiler/flow.s`: label ids 0-15, absolute `JMP` for back edges, and a fixed five-byte conditional branch. A target within +0..+127 is patched as the direct branch plus three `NOP`s; a farther target is patched as the inverted opcode, `+3`, and `JMP target`. `cg_put` has a counting mode so a sizing pass and an emitting pass make identical label offsets and lengths. Calls emit `JSR` then `frame_discard`; `fl_call_byte`/`fl_call_word` save A (or A/X) across the discard, and `frame_discard` itself now preserves A/X. `fl_ret_byte`/`fl_ret_word` preserve results across `frame_leave`. | Gives the backend a bounded, directly testable control-flow layer before parser and pass-2 address assignment exist. The fixed five-byte branch avoids size drift between passes by construction. Parser/statement integration, `sizing.s`, and compiler pass 3 remain open. Implemented by `flow.s`, `flow.inc`, and `codegen.s`. |
| D132 | Generated-program text output calls KERNAL `CHROUT` directly under the D129 mapping (CR `$0E`, system zero page, code below `$4000`). `pr_byte`/`pr_word`/`pr_int` print minimal decimal (no leading zeros, `-` for negatives), `pr_bool` prints `TRUE`/`FALSE`, and only `pr_cr` ends a line. Runtime faults use stable R001 null, R002 bounds, R003 divide, R004 stack, and R005 heap; `flt_report` writes code and address, then `MB_ST_FAIL`, prints `RUNTIME FAULT R00n`, and returns. The print and fault routines never change CR, RCR, or P0/P1. | Implements the output and fault half of E04-S05 without a second MMU wrapper: generated code already runs in the D129 mapping, so direct KERNAL calls are safe and the fixture proves CR/RCR survive every message. The `hello.n8` path is now proven by a fixture-only pass-2 capture (architecture 7.9); fault unwinding, general parser-to-codegen integration, and compiler pass 3 remain open. Output is verified in x128's VIC-II display only. Implemented by `print.s`, `faults.s`, and the two `.inc` files. |
| D133 | The E05-S01 heap uses four-byte headers: a two-byte total size with bit 15 set while allocated, and a two-byte next pointer while free or `$A55A` while allocated. Payload follows at header+4, blocks are two-byte aligned, and the minimum block is six bytes. `heap_init` takes A/X = page-aligned base and Y = pages 1..127; `heap_alloc` takes payload bytes, zeroes the payload, returns it or `HEAP_ERR_OOM`, and splits first-fit free blocks; `heap_free` validates range/size/magic and coalesces in address order. Validation is always on. The E05-S01 fixture uses bank-0 `$4000-$4FFF` under CR `$0E`; generated-program placement and banked heaps remain open. | Makes allocation deterministic and testable before class layout chooses a production heap base. The allocator does not print or report faults itself; E05-S03 maps `HEAP_ERR_OOM` to R005 when `new` is wired. The 15-bit size limit and page-granular region avoid a separate size argument while keeping the block header small. Implemented by `heap.s`, `heap.inc`, and the heap fixture. |
| D135 | E05-S03 construction and deletion live in `src/runtime/construct.s` above the heap and descriptor layers. `obj_new` takes A/X = descriptor, reads the instance size (zero is `OBJ_ERR_BAD`), allocates it, writes the descriptor pointer at object+0, then calls the caller-selected `obj_init_ptr` entry once with A/X = self and the constructor arguments still on the software frame stack at `FP+2`; the caller discards them with `frame_discard`. Allocation failure returns C=1 with `OBJ_ERR_HEAP` and never calls `init`. `obj_delete` takes A/X = object: null is a successful no-op, otherwise the descriptor's optional destructor runs once with A/X = self and the complete block goes back through `heap_free`. Both calls use `obj_call`, which takes its entry from zero-page `co_vec`, preserves A/X for `self`, pushes one synthetic return address, and ends with `JMP (co_vec)`; `co_vec` must not straddle a page. | Gives generated `new`/`delete` a testable runtime contract without freezing heap placement or the E06 dispatch. Carrying the entry in `co_vec` instead of A/X is what lets the callee still receive `self` in A/X, and the same shape serves method-table dispatch later. `construct.s` reports `OBJ_ERR_HEAP` rather than printing; codegen still maps that to R005. Implemented by `construct.s`, the `OBJ_*` codes in `class.inc`, and the object fixture. |
| D134 | E05-S02 class descriptors are 8-byte packed records: 2-byte instance size (including the object header), 2-byte destructor address (0 = none), 1-byte interface count, 2-byte interface-directory pointer (0 when empty), and a reserved flags byte that must be 0. There is no parent-class slot. Objects begin with a 2-byte descriptor pointer and fields follow at +2. Interface-directory entries are 4 bytes: interface-descriptor pointer and method-table pointer. `objects.s` provides read-only accessors; `output.s` writes `CLASS name size destroy count ifdir` records after sections. | Makes object identity, size/deletion, and the future interface path deterministic before construction/dispatch. Flags are reserved for non-inheritance metadata; interface descriptor contents and generated `new` remain E05-S03/E06. Implemented by `class.inc`, `objects.s`, and the class fixture. |

## Deferred



+ 57
- 3
newLang/docs/memory-map.md Прегледај датотеку

@@ -6,8 +6,10 @@ x128 3.10
Authoritative source: `cfg/compiler.cfg`. This document explains it.

E04-S01 defines the generated PRG/MAP container below; E04-S02 adds the
provisional frame runtime. Heap placement and any future banking remain later
stories.
provisional frame runtime. E05-S01 supplies an explicit-region heap; E05-S02
freezes class descriptors and MAP class records; E05-S03 adds construction and
deletion above both. Production heap placement and any future banking remain
later stories.

## Generated Program Container (E04-S01)

@@ -17,7 +19,7 @@ stories.
| BASIC 7 stub | 12 bytes, `10 SYS 7181` |
| Entry | `$1C0D` |
| Sections | ascending addresses, zero-filled gaps |
| MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size` |
| MAP | `NOVA128 MAP 1`, `ENTRY hhhh`, `SECTION name start size`, `CLASS name size destroy count ifdir` |
| Startup | clears mailbox, producer `$03`, status PASS, then `RTS` |

The fixed startup is copied from `src/runtime/startup.s`; `output.s` refuses
@@ -40,6 +42,58 @@ the entry CR before printing and returning to BASIC. Code generation will
install this mapping and call `frame_init` when it starts emitting user code
(E04-S03). The 2 KB stack and `$40` hardware-stack guard are provisional.

## Heap Fixture (E05-S01)

| Element | Value |
|---|---|
| Region supplied by caller | page-aligned base and 1..127 pages |
| Block header | 2-byte total size (bit 15 allocated), 2-byte next/magic |
| Payload | header+4; two-byte aligned; minimum block 6 bytes |
| Fixture region | bank 0 `$4000-$4FFF` under CR `$0E` |
| Fixture code | below `$4000` |
| Exhaustion | `HEAP_ERR_OOM`, mapped to R005 by the fixture |

`heap_init` accepts the region explicitly, so this does not freeze the
generated-program heap location. The block/API contract is in
`src/common/heap.inc`; D133 records the provisional decision. The allocator
does not print faults itself; E05-S03 will map `HEAP_ERR_OOM` when `new` is
wired.

## Class Descriptors (E05-S02)

| Element | Value |
|---|---|
| Object header | descriptor pointer at +0; fields from +2 |
| Descriptor | 8 bytes: size, destructor, interface count, directory, flags |
| Instance size | includes the two-byte object header |
| Destructor | `$0000` means none |
| Interface directory | 4-byte entries: interface descriptor, method table |
| Flags | reserved, must be 0; no parent-class slot |
| MAP | `CLASS name size destroy count ifdir` after sections |

The ABI is fixed in `src/common/class.inc`; D134 records the provisional
decision. `src/runtime/objects.s` reads descriptors and objects without
allocating. `output.s` writes class MAP records in `out_class` call order; the
E05-S02 fixture checks a no-destructor/no-interface class and a destructor plus
one interface entry. Interface dispatch remains E06 work.

## Construction and Deletion (E05-S03)

| Element | Value |
|---|---|
| New fixed addresses | none; `obj_new` uses the caller-initialized heap |
| Fixture region | bank 0 `$4000-$4FFF` under CR `$0E`, code below `$4000` |
| Module zero page | `co_vec`, `co_desc`, `co_ptr`, `co_self` (link-allocated) |
| Module BSS | `obj_init_ptr`, `obj_error`, saved object/size/destructor |
| `obj_call` stack cost | one synthetic return address, two hardware-stack bytes |
| Constructor arguments | stay on the software frame stack at `FP+2`; caller discards |

`src/runtime/construct.s` adds no fixed addresses, so the generated-program
heap location stays open. `co_vec` carries the indirect target and the module
asserts it does not end at `$xxFF`, because `JMP ($xxFF)` reads its high byte
from the wrong page. Constructor arguments are not copied again: `init` enters
its normal frame and reads them where the caller pushed them.

## Mappings

| State | CR | RCR (low bits) | P0 | P1 | IRQs |


+ 98
- 0
newLang/planning/claims/E04-S04.md Прегледај датотеку

@@ -0,0 +1,98 @@
---
story: E04-S04
owner: e04_control
status: done
started: 2026-11-09
depends-on: [E04-S03, E03-S03]
---

# E04-S04 Claim

## Intended change

Add `src/compiler/flow.s`, the control-flow layer above the scalar emitter:
labels, absolute jumps for loop back edges, conditional branches with a fixed
five-byte short/long encoding, calls that discard frame arguments, and
prologue/epilogue helpers for the software-frame ABI. Add a counting mode to
`codegen.s` so one pass measures a routine without writing the buffer and the
next pass emits it; both passes must produce identical label offsets and
lengths. The x128 fixture generates a nested routine (while, if/else, for,
call, return), sizes it twice, verifies both branch encodings in memory, and
executes it.

The story is limited to the emitter: parser-to-emitter wiring, method address
assignment (`sizing.s`), and compiler pass 3 remain open and are recorded as
remaining risk.

## File area

- `src/compiler/flow.s`, `src/common/flow.inc` (new)
- `src/compiler/codegen.s` (counting mode), `src/runtime/frames.s`
(`frame_discard` preserves A/X)
- `tests/codegen/control-test.s` (new)
- `scripts/build-control-test.ps1`, `scripts/verify-control-test.ps1` (new)
- `docs/decisions.md`, `docs/architecture.md`
- `planning/claims/E04-S04.md`
- generated files under `build/`

## Acceptance evidence

- [x] Emits `if`, `else`, `while`, `for`, calls, and `return`: the generated
routine runs with the expected mailbox signature `A5 11 05 0A 04 5A 00 00 03`
(status PASS, return 17, a 5, if/else path 10, i 4, long-branch result 5A,
wrong path 0, zero sizing mismatches, both branch forms verified).
- [x] Pass-2 sizing matches emission: the fixture runs the same generator with
`FL_MODE_COUNT` (label offsets recorded, no stores) and `FL_MODE_EMIT`, then
compares every main-routine label offset and the total length; mismatches 0.
- [x] Long branches are correct: a conditional branch over 125 bytes of filler
is patched as the inverted `BEQ +3` / `JMP`, jumps over the wrong path
(`v_bad` stays 0), and reaches the target (`v_long` is `$5A`).
- [x] Nested-control fixture executes expected mailbox signatures: while exits
at 5, if/else takes the then path, for reaches 4 and sums 16, `add(sum)`
returns 17 through the frame ABI.

## Coordination notes

- E04-S03 is done; this story uses its scalar emitter and adds the control
transfer it deferred.
- D131 records the emitter contract, including the long-form opcode inversion
and the result-preserving call/return helpers.
- `frame_discard` now preserves A/X; the earlier implementation left its count
in A, which destroyed a callee's result during argument cleanup. The frame
regression still passes.
- Compiler pass 2/3 integration is not attempted here: `sizing.s`, method
addresses, and parser statement emission remain the next backend story.

## Work log

- Added count mode to `cg_put`; `cg_init` clears it and `fl_init` sets it.
- Added label/fixup tables (16 labels, 32 fixups), absolute JMP patching, and
five-byte conditional branches. The long form inverts the branch opcode at
patch time so a true condition falls through to the JMP.
- Added `fl_call`/`fl_call_byte`/`fl_call_word`: JSR, save the result on the
hardware stack, discard argument bytes, restore. Added `fl_ret`,
`fl_ret_byte`, and `fl_ret_word`, preserving results across `frame_leave`.
- Fixed an infinite filler loop in the fixture: the emit macros clobber X, so
the filler counter moved to BSS.
- Fixed the fixture's branch-offset capture: `cg_here` is a routine, so it must
be called (`jsr cg_here`), not read as a variable. This was why the in-memory
branch-form checks inspected the wrong byte.

## Completion

- Tests run:
- `scripts/verify-control-test.ps1`
- `scripts/verify-frame-test.ps1 -Port 6541`
- `scripts/verify-scalar-test.ps1 -Port 6542`
- Results: control PASS (`4E 38 03 A5 00 00 00 00 11 05 0A 04 5A 00 00 03`);
frame PASS; scalar PASS with its unchanged 231-byte golden stream.
- Files changed: `src/compiler/flow.s`, `src/common/flow.inc`,
`src/compiler/codegen.s`, `src/runtime/frames.s`, `tests/codegen/control-test.s`,
`scripts/build-control-test.ps1`, `scripts/verify-control-test.ps1`,
`docs/decisions.md`, `docs/architecture.md`, this claim.
- Remaining risks: parser statement emission and pass-2 address assignment are
not wired yet; labels (16), fixups (32), and the 255-byte code buffer bound
each routine; `fl_branch` targets must be forward (loops use `fl_jmp`);
interface dispatch calls and void calls through the control layer are not
separately exercised; the long-branch expansion adds three NOPs to short
branches.

+ 120
- 0
newLang/planning/claims/E04-S05.md Прегледај датотеку

@@ -0,0 +1,120 @@
---
story: E04-S05
owner: e04_runtime
status: done
started: 2026-11-09
depends-on: [E04-S03, E01-S04]
---

# E04-S05 Claim

## Intended change

Deliver the runtime half of print and runtime faults: PETSCII text and
scalar output, stable R-codes, and a mailbox/screen fault report, with an x128
fixture that checks the rendered screen and the mailbox. The increment also
proves one source-to-code path: `hello.n8` compiles to a PRG through a pass-2
string capture and a fixture-only emitter, then runs in x128. General
parser-to-codegen integration, `sizing.s`, and compiler pass 3 remain separate
backend work.

## File area

- `src/runtime/print.s`, `src/common/print.inc` (new)
- `src/runtime/faults.s`, `src/common/faults.inc` (new)
- `src/compiler/parser.s` (pass-2 print capture)
- `tests/runtime/print-test.s` (new)
- `tests/compiler/hello.n8`, `tests/compiler/hello-test.s` (new)
- `scripts/build-print-test.ps1`, `scripts/verify-print-test.ps1` (new)
- `scripts/build-hello-test.ps1`, `scripts/verify-hello-test.ps1` (new)
- `docs/decisions.md`, `docs/architecture.md`
- `planning/claims/E04-S05.md`
- generated files under `build/print-test/` and `build/hello-test/`

## Acceptance evidence

- [x] Prints literals and every supported scalar on tested displays: the
fixture renders `TEXT OK` (string literal), `BYTE 0 7 255`,
`WORD 0 12345 65535`, `INT -1 -32768 32767`, `BOOL FALSE TRUE`, and
`CHAR A` in x128's VIC-II display; the host compares all twelve screen rows.
- [x] Null, bounds, divide, stack, and heap faults have stable codes: R001,
R002, R003, R004, and R005 are printed as `RUNTIME FAULT R00n`, and the
mailbox ends with code 5 and address `$1234`.
- [x] Runtime messages do not corrupt the program's memory mapping: the
fixture compares CR to `$0E` and RCR to its saved value after all print and
fault calls, and records 1 in `MB_DATA` only when both match.
- [x] `hello.n8` compiles and runs end to end in x128: the compile fixture
loads `HELLO.N8` from a D81, parses it, captures the single string literal,
and emits a 118-byte `HELLO.PRG` (`$1C01` load, `$1C0D` entry). A second
x128 session autostarts the extracted PRG and displays `HELLO FROM NOVA128`
on row 13. The emitter is fixture-only: it handles one print of one string
literal and does not call `out_startup` or the general code generator.

## Coordination notes

- D132 records the output formats, the R-code namespace, and the direct
`CHROUT` contract. It builds on D129 (generated code runs at CR `$0E`) and
changes no memory map.
- The runtime must not be called from a compiler state with relocated zero
page: `print.s` calls the KERNAL without a wrapper and uses `(pr_ptr),y`.
- Fault reporting returns to its caller. Unwinding a fault through frames is
deferred to the frame/exception work.
- E04-S03's `AR_FAULT_DIV0` now has a stable external code (`FLT_DIV0` = R003);
arithmetic itself is unchanged.
- The hello path uses the D128 container (stub at `$1C01`, body at `$1C0D`)
but bypasses `startup.s`; it is a bounded proof, not the general emitter.
The fixture must set `out_load` and `out_entry` before `out_begin`, because
`output.s` does not initialize them.
- `HELLO.N8` is stored with unshifted PETSCII name bytes (`$48...`), so the
build script writes the host file with a lowercase name; c1541 maps it to
those bytes. The true-drive D81 must be allowed to settle before exit or the
PRG's data sectors are not flushed to the image.

## Work log

- Added `print.s`: `pr_char` (preserves X/Y), `pr_cr`, `pr_text`,
minimal-decimal `pr_byte`/`pr_word`/`pr_int`, and `pr_bool`.
- Moved `pr_ptr` to the zero-page segment: indirect indexed addressing needs a
zero-page operand, which ca65 rejected from BSS.
- Added `faults.s`: mailbox code/address then `MB_ST_FAIL`, a fixed
`RUNTIME FAULT R` prefix, three-digit code, and CR.
- Added the fixture with a screen clear, all scalar formats, all five faults,
and a CR/RCR preservation check.
- Fixed the fixture's mapping constant to `CR_RAM0_KERNAL` (the c128.inc
spelling) and checked that the linked image ends below `$4000` in the build
script.
- Added D132 and architecture section 7.8.
- Added pass-2 print capture to `parser.s`: `gen_text`/`gen_text_len`,
`gen_print_count`, and `gen_bad`; a print accepts one string-literal item and
marks unsupported shapes. Parser code growth required near-branch
trampolines around the literal path for ca65's +/-128 range.
- Added `hello-test.s` and its build/verify scripts. The compile fixture sets
`out_load`/`out_entry`, emits the D128 stub and a body that saves CR, selects
`$0E`, calls `CHROUT` per captured byte, restores CR, and returns to BASIC.
- Added architecture section 7.9 and updated D132.

## Completion

- Tests run:
- `scripts/verify-print-test.ps1` (build plus x128 run)
- `scripts/verify-hello-test.ps1` (compile session, D81 extraction, x128 run)
- `scripts/verify-parser-test.ps1 -Port 6551` (41 fixtures)
- `git diff --check`
- Results: all twelve print/fault screen rows match; the print mailbox has
producer 3, status FA, code 5, address `$1234`, and mapping preserved. The
hello mailbox has stage 2, status A5, one 18-byte print, and no output error;
`HELLO.PRG` is 118 bytes and displays on row 13. All 41 parser fixtures pass
(lowest expression stack pointer `$A7`).
- Files changed: `src/runtime/print.s`, `src/common/print.inc`,
`src/runtime/faults.s`, `src/common/faults.inc`, `src/compiler/parser.s`,
`tests/runtime/print-test.s`, `tests/compiler/hello.n8`,
`tests/compiler/hello-test.s`, the four build/verify scripts,
`docs/decisions.md`, `docs/architecture.md`, this claim.
- Remaining risks: the hello emitter handles only one literal string print,
with no expressions, calls, or startup section; general parser-to-codegen
integration and compiler pass 3 remain open. Only the 40-column VIC-II
display is tested. The capture buffer is bounded; longer or unsupported
print shapes stop at the unsupported stage rather than truncating silently.
Print/fault routines are not reentrant and assume the system zero page;
fault unwinding and best-address selection are not implemented; generated
code and BSS must stay below `$4000` under CR `$0E`.

+ 87
- 0
newLang/planning/claims/E05-S01.md Прегледај датотеку

@@ -0,0 +1,87 @@
---
story: E05-S01
owner: e05_heap
status: done
started: 2026-11-09
depends-on: [E04-S05, E01-S05]
---

# E05-S01 Claim

## Intended change

Add a deterministic free-list heap for generated programs. `heap_init` accepts
an explicit page-aligned base and page count, so heap placement remains
independent of the still-open generated-program map. Blocks use a four-byte
header (total size with bit 15 allocated, and a next pointer that is a magic
value while allocated), two-byte payload alignment, first-fit allocation with
splitting, payload zeroing, address-ordered free insertion, and coalescing.
`heap_free` validates range, size, the used magic, and double-free; validation
is always on rather than a separately compiled debug build. A bounded x128
fixture exercises allocation, zeroing, splitting, coalescing, a multi-block
stress pass, double-free and corrupt-header detection, and exhaustion without
damaging live allocations. The fixture maps exhaustion to stable R005 through
`flt_report`; wiring `new` to the heap is E05-S03.

## File area

- `src/runtime/heap.s`, `src/common/heap.inc` (new)
- `tests/runtime/heap-test.s` (new)
- `scripts/build-heap-test.ps1`, `scripts/verify-heap-test.ps1` (new)
- `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md`
- `planning/claims/E05-S01.md`
- generated files under `build/heap-test/`

## Acceptance evidence

- [x] Allocates, zeroes, frees, splits, and coalesces blocks deterministically.
- [x] Heap exhaustion faults without corrupting allocated blocks.
- [x] Debug mode detects representative double-free/corrupt-header cases.
- [x] Stress test returns the heap to its initial free capacity.

## Coordination notes

- E04-S05 and E01-S05 are complete; E04-S04/S05 remain uncommitted and are not
discarded or modified.
- The generated-program heap location is still open. This story supplies the
allocator and a test instance at bank-0 `$4000-$4FFF` under CR `$0E`, not a
final generated-program map.
- D133 records the provisional block/API contract. The allocator returns
`HEAP_ERR_*` on exhaustion; the test calls `flt_report` to demonstrate the
stable R005 path, while E05-S03 owns `new` integration.

## Work log

- Selected E05-S01 as the next dependency-complete story after E04-S05.
- Added `heap.s`/`heap.inc` with an explicit page-granular region, first-fit
splitting, payload zeroing, sorted free insertion, coalescing, and always-on
validation.
- Added the 4K x128 fixture at `$4000-$4FFF`; code ends at `$28C4`.
- First build exposed ca65 range errors on the far error paths; converted them
to inverted short branches plus `JMP`.
- The first run failed phase 2 because `heap_zero` branched to its tail path
before loading A=0, so payload bytes were not zeroed. Fixed by loading A=0
at the tail entry, then reran successfully.

## Completion

- Tests run:
- `scripts/build-heap-test.ps1` (link ends at `$28C4`)
- `scripts/verify-heap-test.ps1` (x128 3.10)
- `git diff --check`
- Results: the fixture reports producer 3, status `$FA`, code 5, address
`$4000`, and all eight phase flags `01`; the screen shows
`NOVA128 HEAP TEST`, `HEAP OK`, `RUNTIME FAULT R005`. It verified allocation,
zeroing, first-fit splitting, sorted insertion, repeated coalescing, the
16-block stress/free cycle, double-free rejection, corrupt-magic rejection,
exhaustion without damaging live patterns, and return to 4096 free bytes.
- Files changed: `src/runtime/heap.s`, `src/common/heap.inc`,
`tests/runtime/heap-test.s`, `scripts/build-heap-test.ps1`,
`scripts/verify-heap-test.ps1`, `docs/decisions.md`, `docs/architecture.md`,
`docs/memory-map.md`, this claim.
- Remaining risks: production generated-program heap placement is still open;
the canonical region is 4K and uses 15-bit block sizes; validation is always
on (no release/debug toggle); fragmentation is solved by first-fit plus
coalescing but no worst-case performance bound is measured. `HEAP_ERR_OOM`
is returned to the caller, not automatically reported; E05-S03 owns the
`new`/R005 integration. Banked heaps remain deferred.

+ 91
- 0
newLang/planning/claims/E05-S02.md Прегледај датотеку

@@ -0,0 +1,91 @@
---
story: E05-S02
owner: e05_objects
status: done
started: 2026-11-09
depends-on: [E03-S05, E05-S01]
---

# E05-S02 Claim

## Intended change

Freeze the object/class descriptor ABI and make it visible in generated MAP
output. A class descriptor is an 8-byte packed record containing the instance
size, an optional destructor address, an interface count, a pointer to an
interface-directory array, and reserved flags that must be zero. Objects start
with a two-byte pointer to their descriptor; fields follow at offset 2.

Add `src/runtime/objects.s` helpers to read an object's descriptor, size,
destructor, and interface directory. Extend `output.s` with `out_class` records
and deterministic `CLASS name size destroy count ifdir` MAP lines. There is no
parent-class slot and no inheritance path.

## File area

- `src/common/class.inc`, `src/runtime/objects.s` (new)
- `src/common/formats.inc`, `src/compiler/output.s`
- `tests/runtime/class-test.s`
- `scripts/build-class-test.ps1`, `scripts/verify-class-test.ps1`
- `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md`
- `planning/claims/E05-S02.md`
- generated files under `build/`

## Acceptance evidence

- [x] Descriptor identifies instance size and optional destructor: the
`EMPTY` descriptor reports size 7 and destructor 0; `BOX` reports size 35 and
a nonzero destructor. `obj_size`/`obj_destroy` read both values from the
descriptor.
- [x] Every object begins with a valid descriptor pointer: both fixture objects
store their descriptor at +0; `obj_class` rejects a null object/pointer and
returns the descriptor for both real objects.
- [x] Descriptor/MAP output is deterministic: the fixture emits
`CLASS EMPTY 0007 0000 00 0000` and
`CLASS BOX 0023 <destroy> 01 <dir>` after the section records, with actual
label addresses supplied by the linker. The verifier compares the complete
MAP and the host reruns the fixture on a freshly built disk.
- [x] Format reserves a documented path to interface-directory data without
introducing class inheritance: descriptor offset +4 is the interface count,
+5..6 points to 4-byte interface/method-table entries, and +7 flags must be
zero. There is no parent-class slot; `class.inc` records this ABI.

## Work log

- Selected E05-S02 after E05-S01; no other claim was active.
- Added the 8-byte descriptor and object header constants, plus read-only
descriptor accessors for size, destructor, and interface directory.
- Extended `output.s` with `out_class`, initialized class-record storage, and
appended deterministic `CLASS` MAP lines after sections. Converted the new
writer's long error branches to short branches plus `JMP` trampolines.
- Added the x128 fixture with a static EMPTY object and a BOX object whose
descriptor has a destructor and one interface-directory entry. It writes
`CLASSTEST.PRG/MAP` through the real writer.
- Corrected the fixture's mailbox producer to `MB_PROD_PROGRAM`; corrected the
verifier to use the drive's lowercase `classtest.map` name.

## Completion

- Tests run:
- `scripts/build-class-test.ps1`
- `scripts/verify-class-test.ps1 -Port 6552` (repeated on fresh disks)
- `scripts/verify-output-test.ps1 -Port 6530 -RunPort 6531`
- `git diff --check`
- Results: the class fixture reports producer 3, status `$A5`, code 0, and all
eight descriptor/object/MAP flags set. The screen shows
`NOVA128 CLASS TEST OK`. The complete MAP is identical to the expected
six-line record, including `CLASS EMPTY 0007 0000 00 0000` and
`CLASS BOX 0023 <destroy_b> 01 <iface_dir>`. The existing output test still
passes with its 133-byte PRG and unchanged section MAP.
- Files changed: `src/common/class.inc`, `src/runtime/objects.s`,
`src/common/formats.inc`, `src/compiler/output.s`,
`tests/runtime/class-test.s`, `scripts/build-class-test.ps1`,
`scripts/verify-class-test.ps1`, `docs/decisions.md`,
`docs/architecture.md`, `docs/memory-map.md`, this claim.
- Remaining risks: production parser/codegen does not yet call `out_class` or
emit class descriptors; the MAP exposes descriptor records but not
field-by-field offset lines, which remain the parser layout record until
codegen pass 3. The descriptor flags, interface descriptor pointer, and
method-table pointer are reserved for later interface work. The fixture uses
static synthetic objects, so construction/deletion remains E05-S03. Tests
run against a fresh true-drive D81.

+ 102
- 0
newLang/planning/claims/E05-S03.md Прегледај датотеку

@@ -0,0 +1,102 @@
---
story: E05-S03
owner: e05_objects
status: done
started: 2026-11-09
depends-on: [E05-S02, E04-S02]
---

# E05-S03 Claim

## Intended change

Add the runtime construction/deletion layer above the E05-S01 heap and E05-S02
descriptor. `obj_new` allocates the descriptor's instance size, stores the
descriptor pointer in the object header, and calls a caller-supplied `init`
entry once after a successful allocation. Constructor arguments stay on the
software frame stack, so `init` can enter its normal frame and read them.
Allocation failure returns `OBJ_ERR_HEAP` without calling `init`.

`obj_delete` treats null as a successful no-op, reads the optional destructor
from the descriptor, calls it with the object as `self`, then returns the whole
object to the heap. A small synthetic-return indirect-call helper invokes the
runtime-selected destructor without relying on `JSR (address)`, which the 8502
does not have.

## File area

- `src/runtime/construct.s` (new)
- `src/common/class.inc` (construction error codes)
- `tests/runtime/object-test.s`
- `scripts/build-object-test.ps1`, `scripts/verify-object-test.ps1`
- `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md`
- `planning/claims/E05-S03.md`
- generated files under `build/`

## Acceptance evidence

- [x] `new` allocates, initializes the header, then calls `init` once: the
fixture's five-byte `Res` object gets a nonzero heap pointer, `obj_class`
reads back `desc_res` from object+0, and `init_count` is 1. Both fields are
zero at init entry, so the heap's zeroing happens before the header is
written and before init runs.
- [x] Failed allocation does not call `init`: `desc_huge` (`$FFFF` bytes) makes
`obj_new` return C=1 with `obj_error = OBJ_ERR_HEAP` and leaves
`init_count` at 1.
- [x] `delete null` is a no-op: `obj_delete(0)` returns C=0 with
`obj_error = OBJ_OK` and `destroy_count` still 0.
- [x] `delete` calls `destroy` once and frees the complete object: the
destructor runs exactly once with A/X = the object, reads back tag `$2A` and
word `$1234` written by init, and `heap_free_total` returns to the initial
`$1000` once the instance is freed. A class with no destructor also returns
the full capacity.
- [x] Constructor arguments and destructor side effects have end-to-end tests:
`init(value as word, tag as byte)` reads its two arguments from `FP+2` in the
fixture and writes them into the object; the destructor's recorded self and
field values are checked after deletion.

## Work log

- Selected E05-S03 after E05-S02; no other claim was active.
- Added `obj_new`, `obj_delete`, and `obj_call`, plus `obj_init_ptr` and
`obj_error`, and the `OBJ_OK`/`OBJ_ERR_HEAP`/`OBJ_ERR_BAD` codes in
`class.inc`. Construction lives in its own module so the E05-S02 class test
still links `objects.s` without the heap.
- First x128 run hung: `obj_new` handed the object pointer to `obj_call`
instead of the init entry, so it jumped into the zeroed heap and executed a
BRK. `obj_call` now takes its entry from zero-page `co_vec` and preserves
A/X, which also leaves A/X free for the callee's `self`.
- Second run failed phase 3: after the null test, `obj_delete` passed `low|high`
to `obj_class`, reading the descriptor from the wrong address. Reloading the
object pointer before the accessor fixed it.
- Replaced the branches that outgrew the +/-128-byte range with the project's
short-branch plus `JMP` trampoline pattern.
- Removed the temporary mailbox breadcrumbs used to locate the two defects
before recording the final runs.

## Completion

- Tests run:
- `scripts/build-object-test.ps1` (PRG ends at `$28F7`, below the `$4000`
heap base)
- `scripts/verify-object-test.ps1`
- `scripts/verify-class-test.ps1`
- `scripts/verify-output-test.ps1`
- `scripts/verify-heap-test.ps1`
- `git diff --check`
- Results: the object fixture reports producer 3, status `$A5`, code 0, all
five construction/deletion flags set, and shows `OBJECT OK`. The class test
still emits `CLASS EMPTY 0007 0000 00 0000` and
`CLASS BOX 0023 2935 01 2A47`; the output test still passes with its 133-byte
PRG; the heap test still passes with its R005 exhaustion fault.
- Files changed: `src/runtime/construct.s`, `src/common/class.inc`,
`tests/runtime/object-test.s`, `scripts/build-object-test.ps1`,
`scripts/verify-object-test.ps1`, `docs/decisions.md` (D135),
`docs/architecture.md` (8.3), `docs/memory-map.md`, this claim.
- Remaining risks: no parser-to-codegen path emits `new`/`delete` yet, so the
R005 mapping for a failed `new` still belongs to codegen (D133). The heap
region stays caller-chosen. `obj_call` relies on `co_vec` not ending at
`$xxFF` (link-time assertion) and spends two hardware-stack bytes per call.
Constructor argument count is bounded only by the frame stack. The fixture
runs against a fresh true-drive D81 in x128; physical C128 testing remains
P2 validation.

+ 23
- 0
newLang/scripts/build-class-test.ps1 Прегледај датотеку

@@ -0,0 +1,23 @@
# E05-S02: build the class descriptor/object fixture and its x128 disk.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force

$out = Join-Path $project "build\class-test"
$link = Invoke-Nova128Link -Name "class-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\output.s"),
(Join-Path $project "src\runtime\startup.s"),
(Join-Path $project "src\runtime\objects.s"),
(Join-Path $project "tests\runtime\class-test.s"))

New-Nova128Disk -Path (Join-Path $out "class-test.d81") -Files @(
@{ Name = "CLASSTEST"; Path = $link.Program; Type = "prg" })

Write-Host ("built {0}" -f $link.Program)
Write-Host ("disk {0}" -f (Join-Path $out "class-test.d81"))

+ 17
- 0
newLang/scripts/build-control-test.ps1 Прегледај датотеку

@@ -0,0 +1,17 @@
# E04-S04: build the control-flow emitter fixture.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force

$out = Join-Path $project "build\control-test"
$link = Invoke-Nova128Link -Name "control-test" -BuildDir $out `
-Config (Join-Path $project "cfg\compiler.cfg") -Sources @(
(Join-Path $project "src\compiler\codegen.s"),
(Join-Path $project "src\compiler\flow.s"),
(Join-Path $project "src\runtime\frames.s"),
(Join-Path $project "tests\codegen\control-test.s"))

Write-Host ("built {0}" -f $link.Program)

+ 25
- 0
newLang/scripts/build-heap-test.ps1 Прегледај датотеку

@@ -0,0 +1,25 @@
# E05-S01: build the free-list heap fixture and its D81.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
$out = Join-Path $project "build\heap-test"
New-Item -ItemType Directory -Force -Path $out | Out-Null

$link = Invoke-Nova128Link -Name "heap-test" -BuildDir $out `
-Config (Join-Path $project "cfg\compiler.cfg") -Sources @(
(Join-Path $project "src\runtime\print.s"),
(Join-Path $project "src\runtime\faults.s"),
(Join-Path $project "src\runtime\heap.s"),
(Join-Path $project "tests\runtime\heap-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }
$end = Get-Nova128Label $link.Labels "test_end"
if ($end -ge 0x4000) {
throw ("heap-test ends at `$" + $end.ToString("X4") + "; it must stay below `$4000")
}

New-Nova128Disk -Path (Join-Path $out "heap-test.d81") -Files @(
@{ Name = "heap-test"; Path = $link.Program; Type = "prg" })
Write-Host ("built {0} (end {1:X4})" -f $link.Program, $end)

+ 32
- 0
newLang/scripts/build-hello-test.ps1 Прегледај датотеку

@@ -0,0 +1,32 @@
# E04-S05: build the hello compiler fixture and a D81 holding HELLO.N8.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
$out = Join-Path $project "build\hello-test"
New-Item -ItemType Directory -Force -Path $out | Out-Null

$link = Invoke-Nova128Link -Name "hello-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\output.s"),
(Join-Path $project "src\runtime\startup.s"),
(Join-Path $project "tests\compiler\hello-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }

$pet = Join-Path $out "hello.pet"
[IO.File]::WriteAllBytes($pet, (ConvertTo-Nova128Petscii (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\hello.n8"))))
New-Nova128Disk -Path (Join-Path $out "hello-test.d81") -Files @(
@{ Name = "hello-test"; Path = $link.Program; Type = "prg" },
@{ Name = "hello.n8"; Path = $pet; Type = "seq" }) # lowercase -> unshifted PETSCII
Write-Host ("built {0}" -f $link.Program)

+ 27
- 0
newLang/scripts/build-object-test.ps1 Прегледај датотеку

@@ -0,0 +1,27 @@
# E05-S03: build the object construction/deletion fixture and its D81.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
$out = Join-Path $project "build\object-test"
New-Item -ItemType Directory -Force -Path $out | Out-Null

$link = Invoke-Nova128Link -Name "object-test" -BuildDir $out `
-Config (Join-Path $project "cfg\compiler.cfg") -Sources @(
(Join-Path $project "src\runtime\print.s"),
(Join-Path $project "src\runtime\heap.s"),
(Join-Path $project "src\runtime\frames.s"),
(Join-Path $project "src\runtime\objects.s"),
(Join-Path $project "src\runtime\construct.s"),
(Join-Path $project "tests\runtime\object-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }
$end = Get-Nova128Label $link.Labels "test_end"
if ($end -ge 0x4000) {
throw ("object-test ends at `$" + $end.ToString("X4") + "; it must stay below `$4000")
}

New-Nova128Disk -Path (Join-Path $out "object-test.d81") -Files @(
@{ Name = "object-test"; Path = $link.Program; Type = "prg" })
Write-Host ("built {0} (end {1:X4})" -f $link.Program, $end)

+ 24
- 0
newLang/scripts/build-print-test.ps1 Прегледај датотеку

@@ -0,0 +1,24 @@
# E04-S05: build the print/fault runtime test and its D81.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
$out = Join-Path $project "build\print-test"
New-Item -ItemType Directory -Force -Path $out | Out-Null

$link = Invoke-Nova128Link -Name "print-test" -BuildDir $out `
-Config (Join-Path $project "cfg\compiler.cfg") -Sources @(
(Join-Path $project "src\runtime\print.s"),
(Join-Path $project "src\runtime\faults.s"),
(Join-Path $project "tests\runtime\print-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }
$end = Get-Nova128Label $link.Labels "test_end"
if ($end -ge 0x4000) {
throw ("print-test ends at `$" + $end.ToString("X4") + "; it must stay below `$4000 for the CR=`$00 mapping")
}

New-Nova128Disk -Path (Join-Path $out "print-test.d81") -Files @(
@{ Name = "print-test"; Path = $link.Program; Type = "prg" })
Write-Host ("built {0} (end {1:X4})" -f $link.Program, $end)

+ 76
- 0
newLang/scripts/verify-class-test.ps1 Прегледај датотеку

@@ -0,0 +1,76 @@
# E05-S02: run the class descriptor/object fixture in x128 and verify the
# descriptor ABI, object headers, and deterministic CLASS MAP records.
[CmdletBinding()]
param([int]$Port = 6552, [int]$TimeoutSeconds = 90)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-class-test.ps1")

$out = Join-Path $project "build\class-test"
$disk = Join-Path $out "class-test.d81"
$labels = Join-Path $out "class-test.lbl"
$c1541 = Get-ViceTool "c1541"
$failures = @()

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "class-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
Start-Sleep -Milliseconds 500
$screenRam = Read-Nova128Memory $session 0x0400 1000
} finally {
Stop-Nova128X128 $session -SettleSeconds 0
}

if ($mb[2] -ne 0x03) { $failures += "producer $($mb[2]) expected 3" }
if ($mb[3] -ne 0xA5) { $failures += ("status {0:X2} expected A5" -f $mb[3]) }
if ($mb[4] -ne 0 -or $mb[5] -ne 0) { $failures += "code $($mb[4] + 256 * $mb[5]) expected 0" }
for ($i = 0; $i -lt 8; $i++) {
if ($mb[8 + $i] -ne 1) { $failures += "phase $i flag is $($mb[8 + $i]), expected 1" }
}

$rows = for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
$code = $screenRam[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
(-join $chars).TrimEnd()
}
if (-not ($rows -contains "NOVA128 CLASS TEST OK")) {
$failures += "screen does not contain 'NOVA128 CLASS TEST OK'"
}

function Read-DiskFile([string]$Name, [string]$Dest, [bool]$Seq) {
if (Test-Path -LiteralPath $Dest) { Remove-Item -LiteralPath $Dest -Force }
$spec = if ($Seq) { "$Name,s" } else { $Name }
& $c1541 -attach $disk -read $spec $Dest 2>&1 | Out-Null
if (-not (Test-Path -LiteralPath $Dest)) { throw "could not read $Name from the test disk" }
}

$mapPath = Join-Path $out "CLASSTEST.MAP"
Read-DiskFile "classtest.map" $mapPath $true
$mapLines = ([IO.File]::ReadAllText($mapPath)).TrimEnd("`r", "`n") -split "`r`n|`r|`n"
$destroy = Get-Nova128Label $labels "destroy_b"
$ifdir = Get-Nova128Label $labels "iface_dir"
$expectedMap = @(
"NOVA128 MAP 1",
"ENTRY 1C0D",
"SECTION STUB 1C01 000C",
"SECTION START 1C0D 004A",
("CLASS EMPTY 0007 0000 00 0000"),
("CLASS BOX 0023 {0:X4} 01 {1:X4}" -f $destroy, $ifdir))
if (($mapLines -join "`n") -ne ($expectedMap -join "`n")) {
$failures += ("CLASSTEST.MAP differs:`n got: " + ($mapLines -join "`n ") +
"`n want: " + ($expectedMap -join "`n "))
}

if ($failures) {
throw ("class verification failed:`n " + ($failures -join "`n "))
}
Write-Host "screen:"
$rows | Where-Object { $_ } | ForEach-Object { Write-Host " |$_" }
Write-Host ("mailbox: producer 3, PASS, all 8 descriptor/MAP flags set; destroy {0:X4}, ifdir {1:X4}" -f $destroy, $ifdir)
Write-Host "MAP:"
$expectedMap | ForEach-Object { Write-Host " |$_" }

+ 42
- 0
newLang/scripts/verify-control-test.ps1 Прегледај датотеку

@@ -0,0 +1,42 @@
# E04-S04: run the control-flow fixture and check sizing, branch forms, and
# the executed if/else/while/for/call/return results.
[CmdletBinding()]
param(
[int]$Port = 6530,
[int]$TimeoutSeconds = 90
)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-control-test.ps1")

$out = Join-Path $project "build\control-test"
$prg = Join-Path $out "control-test.prg"
$session = Start-Nova128X128 -Autostart $prg -Port $Port `
-Screenshot (Join-Path $out "control-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
} finally {
Stop-Nova128X128 $session
}

Write-Host ('mailbox: ' + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' '))
Write-Host (("results: return={0} a={1} path={2} i={3} long={4:X2} bad={5} mismatches={6} forms={7:X2}" -f `
$mb[8], $mb[9], $mb[10], $mb[11], $mb[12], $mb[13], $mb[14], $mb[15]))

$failures = @()
if ($mb[2] -ne 0x03) { $failures += "producer $($mb[2]) is not a generated program" }
if ($mb[3] -ne 0xA5) { $failures += "fixture status is not PASS" }
if ($mb[4] -ne 0 -or $mb[5] -ne 0) { $failures += "code is $($mb[4] + 256 * $mb[5])" }
if ($mb[8] -ne 17) { $failures += "program result is $($mb[8]), expected 17" }
if ($mb[9] -ne 5) { $failures += "while/a is $($mb[9]), expected 5" }
if ($mb[10] -ne 10) { $failures += "if/else path is $($mb[10]), expected 10" }
if ($mb[11] -ne 4) { $failures += "for/i is $($mb[11]), expected 4" }
if ($mb[12] -ne 0x5A) { $failures += ('long branch result is {0:X2}, expected 5A' -f $mb[12]) }
if ($mb[13] -ne 0) { $failures += "long branch ran the wrong path" }
if ($mb[14] -ne 0) { $failures += "$($mb[14]) sizing label/length mismatches" }
if ($mb[15] -ne 3) { $failures += ('branch forms are {0:X2}, expected 03 (short and long verified)' -f $mb[15]) }

if ($failures) { throw ("control verification failed:`n " + ($failures -join "`n ")) }
Write-Host "verified sizing pass, short/long branches, if/else, while, for, call, and return"

+ 55
- 0
newLang/scripts/verify-heap-test.ps1 Прегледај датотеку

@@ -0,0 +1,55 @@
# E05-S01: run the free-list heap fixture and check its phase flags, R005
# exhaustion fault, and screen output.
[CmdletBinding()]
param([int]$Port = 6547, [int]$TimeoutSeconds = 180)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-heap-test.ps1")

$out = Join-Path $project "build\heap-test"
$disk = Join-Path $out "heap-test.d81"
$expected = @(
"NOVA128 HEAP TEST",
"HEAP OK",
"RUNTIME FAULT R005")
$failures = @()

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "heap-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
Start-Sleep -Seconds 1
$screenRam = Read-Nova128Memory $session 0x0400 1000
} finally {
Stop-Nova128X128 $session -SettleSeconds 0
}

if ($mb[2] -ne 0x03) { $failures += ("producer $($mb[2]) expected 3 (generated program)") }
if ($mb[3] -ne 0xFA) { $failures += ("status ${0:X2} expected FA (failure)" -f $mb[3]) }
if ($mb[4] -ne 5 -or $mb[5] -ne 0) { $failures += ("code {0} expected 5 (heap)" -f ($mb[4] + 256 * $mb[5])) }
$addr = $mb[6] + 256 * $mb[7]
if ($addr -ne 0x4000) { $failures += ("address ${0:X4} expected 4000" -f $addr) }
for ($i = 0; $i -lt 8; $i++) {
if ($mb[8 + $i] -ne 1) { $failures += ("heap phase {0} flag is {1}, expected 1" -f ($i + 1), $mb[8 + $i]) }
}

$rows = for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
$code = $screenRam[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
(-join $chars).TrimEnd()
}
for ($r = 0; $r -lt $expected.Count; $r++) {
if ($rows[$r] -cne $expected[$r]) {
$failures += "screen row ${r}: '$($rows[$r])' expected '$($expected[$r])'"
}
}

if ($failures) { throw ("heap verification failed:`n " + ($failures -join "`n ")) }
Write-Host "screen:"
$rows[0..($expected.Count - 1)] | ForEach-Object { Write-Host " |$_" }
Write-Host ("mailbox: producer 3, status FA, code 5, address {0:X4}, flags 01 01 01 01 01 01 01 01" -f $addr)
Write-Host "verified allocation, zeroing, splitting, coalescing, stress, double-free, corrupt-header detection, and R005 exhaustion"

+ 78
- 0
newLang/scripts/verify-hello-test.ps1 Прегледај датотеку

@@ -0,0 +1,78 @@
# E04-S05: compile HELLO.N8 in x128, extract the generated PRG, and run it in
# a second x128 session; the screen must show the source's string.
[CmdletBinding()]
param([int]$Port = 6548, [int]$TimeoutSeconds = 180)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-hello-test.ps1")

$out = Join-Path $project "build\hello-test"
$disk = Join-Path $out "hello-test.d81"
$text = "HELLO FROM NOVA128"
$failures = @()

# ------------------------------------------------------- compile session
$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "hello-compile-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 += ("compiler status {0:X2} failed at stage {1}" -f $mb[3], $mb[4])
}
if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8]) (see HELLO.ERR)" }
if ($mb[9] -ne 1) { $failures += "captured $($mb[9]) string prints, expected 1" }
if ($mb[10] -ne 0) { $failures += "unsupported print flag set" }
if ($mb[11] -ne 18) { $failures += "captured $($mb[11]) string bytes, expected 18" }
if ($mb[12] -ne 0) { $failures += "output error $($mb[12])" }

# ------------------------------------------------- extract the generated PRG
$c1541 = Get-ViceTool "c1541"
$prg = Join-Path $out "hello.prg"
if (Test-Path -LiteralPath $prg) { Remove-Item -LiteralPath $prg -Force }
& $c1541 -attach $disk -read "hello.prg" $prg 2>&1 | Out-Null
if (-not (Test-Path -LiteralPath $prg)) {
$failures += "HELLO.PRG was not written to the compile disk"
} else {
$bytes = [IO.File]::ReadAllBytes($prg)
if ($bytes.Length -lt 14 -or $bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) {
$failures += "HELLO.PRG does not load at `$1C01"
}
}

# ----------------------------------------------------------- run the PRG
$hitRow = -1
if (-not $failures) {
$session = Start-Nova128X128 -Autostart $prg -Port ($Port + 1) `
-Screenshot (Join-Path $out "hello-run-vicii.png")
try {
$deadline = [DateTime]::UtcNow.AddSeconds(60)
while ([DateTime]::UtcNow -lt $deadline) {
Start-Sleep -Seconds 1
try { $screenRam = Read-Nova128Memory $session 0x0400 1000 } catch { continue }
for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
$code = $screenRam[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
if ((-join $chars) -match [regex]::Escape($text)) { $hitRow = $r; break }
}
if ($hitRow -ge 0) { break }
}
} finally {
Stop-Nova128X128 $session
}
if ($hitRow -lt 0) { $failures += "the generated PRG never displayed '$text'" }
}

if ($failures) { throw ("hello verification failed:`n " + ($failures -join "`n ")) }
Write-Host ("compile: diagnostics 0, one 18-byte print, out_err 0; HELLO.PRG {0} bytes" -f (Get-Item $prg).Length)
Write-Host ("run: '$text' displayed on screen row $hitRow")
Write-Host "verified hello.n8 compiles to a PRG and the PRG runs in x128"

+ 46
- 0
newLang/scripts/verify-object-test.ps1 Прегледај датотеку

@@ -0,0 +1,46 @@
# E05-S03: run the object construction/deletion fixture and check its mailbox.
[CmdletBinding()]
param([int]$Port = 6555, [int]$TimeoutSeconds = 180)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-object-test.ps1")

$out = Join-Path $project "build\object-test"
$disk = Join-Path $out "object-test.d81"
$failures = @()

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "object-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
Start-Sleep -Milliseconds 500
$screenRam = Read-Nova128Memory $session 0x0400 1000
} finally {
Stop-Nova128X128 $session -SettleSeconds 0
}

if ($mb[2] -ne 0x03) { $failures += "producer $($mb[2]) expected 3" }
if ($mb[3] -ne 0xA5) { $failures += ("status {0:X2} expected A5" -f $mb[3]) }
if ($mb[4] -ne 0 -or $mb[5] -ne 0) { $failures += "code $($mb[4] + 256 * $mb[5]) expected 0" }
for ($i = 0; $i -lt 5; $i++) {
if ($mb[8 + $i] -ne 1) { $failures += "phase $i flag is $($mb[8 + $i]), expected 1" }
}

$rows = for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
$code = $screenRam[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
(-join $chars).TrimEnd()
}
if (-not ($rows -contains "OBJECT OK")) {
$failures += "screen does not contain 'OBJECT OK'"
}

if ($failures) { throw ("object verification failed:`n " + ($failures -join "`n ")) }
Write-Host "screen:"
$rows | Where-Object { $_ } | ForEach-Object { Write-Host " |$_" }
Write-Host "mailbox: producer 3, PASS, code 0, all 5 construction/deletion flags set"
Write-Host "verified init arguments, zeroed fields, header, null deletion, destructor side effects, allocation-failure no-init, and complete free"

+ 67
- 0
newLang/scripts/verify-print-test.ps1 Прегледај датотеку

@@ -0,0 +1,67 @@
# E04-S05: run the print/fault runtime test in x128, then check the screen
# text and the mailbox failure fields.
[CmdletBinding()]
param([int]$Port = 6546, [int]$TimeoutSeconds = 180)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-print-test.ps1")

$out = Join-Path $project "build\print-test"
$disk = Join-Path $out "print-test.d81"
$expected = @(
"NOVA128 PRINT TEST",
"TEXT OK",
"BYTE 0 7 255",
"WORD 0 12345 65535",
"INT -1 -32768 32767",
"BOOL FALSE TRUE",
"CHAR A",
"RUNTIME FAULT R001",
"RUNTIME FAULT R002",
"RUNTIME FAULT R003",
"RUNTIME FAULT R004",
"RUNTIME FAULT R005")
$failures = @()

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "print-test-vicii.png")
try {
$null = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
Start-Sleep -Seconds 2 # all five fault lines are printed
$mb = Read-Nova128Memory $session 0x1BF0 16
$screenRam = Read-Nova128Memory $session 0x0400 1000
} finally {
Stop-Nova128X128 $session -SettleSeconds 0
}

# ------------------------------------------------------------- mailbox
if ($mb[2] -ne 0x03) { $failures += ("producer $($mb[2]) expected 3 (generated program)") }
if ($mb[3] -ne 0xFA) { $failures += ("status ${0:X2} expected FA (failure)" -f $mb[3]) }
if ($mb[4] -ne 5 -or $mb[5] -ne 0) { $failures += ("code {0} expected 5 (heap)" -f ($mb[4] + 256 * $mb[5])) }
$addr = $mb[6] + 256 * $mb[7]
if ($addr -ne 0x1234) { $failures += ("address ${0:X4} expected 1234" -f $addr) }
if ($mb[8] -ne 1) { $failures += "CR/RCR changed across the print and fault calls" }

# -------------------------------------------------------------- screen
$rows = for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
$code = $screenRam[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
(-join $chars).TrimEnd()
}
for ($r = 0; $r -lt $expected.Count; $r++) {
if ($rows[$r] -cne $expected[$r]) {
$failures += "screen row ${r}: '$($rows[$r])' expected '$($expected[$r])'"
}
}
$wide = @($rows[0..($expected.Count - 1)] | Where-Object { $_.Length -gt 39 })
if ($wide) { $failures += "screen rows longer than 39: $($wide -join ' | ')" }

if ($failures) { throw ("print runtime verification failed:`n " + ($failures -join "`n ")) }
Write-Host "screen:"
$rows[0..($expected.Count - 1)] | ForEach-Object { Write-Host " |$_" }
Write-Host ("mailbox: producer 3, status FA, code 5, address {0:X4}, mapping preserved" -f $addr)
Write-Host "verified print formatting, fault reporting, and mapping stability"

+ 49
- 0
newLang/src/common/class.inc Прегледај датотеку

@@ -0,0 +1,49 @@
; class.inc - object header and class descriptor ABI (E05-S02).
;
; Every object starts with a two-byte pointer to its class descriptor. Fields
; follow at offset 2 in declaration order, with no hidden padding. The
; descriptor is packed little-endian:
;
; +0 instance size in bytes (2), including the object header
; +2 destructor address (2), 0 = no destructor
; +4 implemented-interface count (1)
; +5 interface-directory pointer (2), 0 when the count is 0
; +7 flags (1), must be 0; no parent-class slot exists
;
; The interface directory is an array of four-byte entries:
;
; +0 interface descriptor pointer (2), defined by the interface ABI
; +2 method-table pointer (2), in interface slot order
;
; The directory reserves the path for interface dispatch and runtime identity
; without introducing class inheritance. Flag bits are reserved for future
; non-inheritance metadata and must be zero in this ABI.

.ifndef CLASS_INC
CLASS_INC = 1

CLASS_DESC_SIZE = 8
CD_INSTANCE_SIZE = 0
CD_DESTRUCTOR = 2
CD_IFACE_COUNT = 4
CD_IFACE_DIR = 5
CD_FLAGS = 7

CLASS_DESC_FLAG_NONE = 0
CLASS_DESC_FLAG_MASK = 0 ; no defined flag bits yet

OBJ_DESCRIPTOR = 0
OBJ_FIELDS = 2
OBJ_HEADER_SIZE = 2
OBJ_MIN_SIZE = 2

CLASS_IFACE_ENTRY_SIZE = 4
CI_INTERFACE = 0
CI_TABLE = 2

; construct.s result codes (obj_error)
OBJ_OK = 0
OBJ_ERR_HEAP = 1
OBJ_ERR_BAD = 2

.endif

+ 17
- 0
newLang/src/common/faults.inc Прегледај датотеку

@@ -0,0 +1,17 @@
; faults.inc - stable runtime fault codes (E04-S05).
;
; The codes are the R-code suffix printed by flt_report and stored in the
; mailbox MB_CODE. They are stable: never renumber or reuse one.

.ifndef FAULTS_INC
FAULTS_INC = 1

FLT_NULL = 1 ; R001 null object/interface reference
FLT_BOUNDS = 2 ; R002 array index outside the bounds
FLT_DIV0 = 3 ; R003 division by zero
FLT_STACK = 4 ; R004 software-frame stack overflow
FLT_HEAP = 5 ; R005 heap exhausted or corrupt

FLT_CODE_MAX = 5

.endif

+ 22
- 0
newLang/src/common/flow.inc Прегледај датотеку

@@ -0,0 +1,22 @@
; flow.inc - control-flow emitter limits (E04-S04).

.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_FIX_SIZE = 4 ; kind, label, operand position, spare

FL_BRANCH_SIZE = 5 ; opcode + distance + 3-byte JMP slot

FL_MODE_EMIT = 0 ; write bytes and patch fixups
FL_MODE_COUNT = 1 ; count bytes only (sizing pass)

FL_OK = 0
FL_ERR_LABEL = 1 ; fixup names a label that was never defined
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

.endif

+ 5
- 1
newLang/src/common/formats.inc Прегледај датотеку

@@ -2,7 +2,8 @@
;
; The container is deliberately small: a C128 BASIC 7 stub at $1C01, the
; machine entry at $1C0D, and contiguous sections loaded in ascending order.
; The higher generated-program map (banking, heap, software stack) is E04-S02.
; Frames (E04-S02), an explicit-region heap (E05-S01), and class descriptors
; (E05-S02) extend the container without changing its load address.

.ifndef FORMATS_INC
FORMATS_INC = 1
@@ -14,6 +15,8 @@ PRG_STUB_SIZE = 12
OUT_MAX_SECTIONS = 8
OUT_SECTION_NAME_MAX = 12
OUT_RECORD_SIZE = 7 ; name ptr(2), name len(1), start(2), size(2)
OUT_MAX_CLASSES = 8
OUT_CLASS_RECORD_SIZE = 10 ; name ptr(2), len(1), size(2), destroy(2), count(1), dir(2)

; out_begin/out_finish/out_abort result codes (out_err).
OUT_OK = 0
@@ -23,5 +26,6 @@ OUT_ERR_SECTIONS = 3 ; more than OUT_MAX_SECTIONS
OUT_ERR_NAME = 4 ; base or section name too long
OUT_ERR_DISK = 5 ; KERNAL or DOS failure
OUT_ERR_STATE = 6 ; byte emitted outside a section
OUT_ERR_CLASSES = 7 ; more than OUT_MAX_CLASSES

.endif

+ 37
- 0
newLang/src/common/heap.inc Прегледај датотеку

@@ -0,0 +1,37 @@
; heap.inc - generated-program free-list heap contract (E05-S01).
;
; Block header, four bytes:
; +0/+1 total block size; bit 15 set while allocated
; +2/+3 next free block pointer while free; HEAP_USED_MAGIC while allocated
;
; Blocks are two-byte aligned. Payload starts at header+4. The minimum block is
; six bytes: one four-byte header plus a two-byte payload. A heap block must be
; smaller than 32K because the used bit lives in the size word.
;
; heap_init: A/X = page-aligned base (A must be 0), Y = page count (1..127).
; heap_alloc: A/X = payload bytes; returns A/X = payload pointer, C=0, or
; A/X = 0, C=1 with heap_error.
; heap_free: A/X = payload pointer; A/X=0 is a no-op; C=1 with heap_error on
; an invalid, corrupt, or already-free block.
;
; The heap is not banked. The caller supplies a region in the generated
; program's current RAM bank; heap placement remains open until later stories.

.ifndef HEAP_INC
HEAP_INC = 1

HEAP_HEADER_SIZE = 4
HEAP_MIN_BLOCK = 6
HEAP_SIZE_MASK = $7FFF
HEAP_USED_FLAG = $8000
HEAP_USED_MAGIC = $A55A
HEAP_MAX_PAGES = 127

HEAP_OK = 0
HEAP_ERR_OOM = 1
HEAP_ERR_ARG = 2
HEAP_ERR_RANGE = 3
HEAP_ERR_DOUBLE = 4
HEAP_ERR_CORRUPT = 5

.endif

+ 14
- 0
newLang/src/common/print.inc Прегледај датотеку

@@ -0,0 +1,14 @@
; print.inc - PETSCII output constants for generated Nova128 programs (E04-S05).
;
; The runtime routines in src/runtime/print.s assume the D129 generated-program
; mapping (CR=$0E): KERNAL and I/O visible, standard zero page, code below
; $4000. They do not change the mapping (D132).

.ifndef PRINT_INC
PRINT_INC = 1

PR_CR = $0D ; PETSCII carriage return
PR_SPACE = $20
PR_CLEAR = $93 ; PETSCII clear screen

.endif

+ 20
- 1
newLang/src/compiler/codegen.s Прегледај датотеку

@@ -23,15 +23,21 @@
; C=0 and A = fixup index
; cg_patch_here A = fixup index; target is the current end of buffer
; cg_here returns A = current length
; cg_set_count A != 0: count bytes only (no stores); 0: emit bytes
;
; Only forward relative branches within +0..+127 bytes are accepted. This is
; enough for the scalar shapes and gives E04-S04 the checked primitive it
; needs for a later long-branch expansion.
;
; Count mode (E04-S04) lets a sizing pass measure a routine without writing
; the code buffer. cg_len advances exactly as in emit mode, so label offsets
; and lengths match by construction.

.setcpu "6502"
.include "../common/codegen.inc"

.export cg_init, cg_put, cg_here, cg_branch_fwd, cg_patch_here
.export cg_set_count, cg_count
.export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_lda_abs, cg_ldx_abs
.export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs
.export cg_jsr_abs, cg_clc, cg_sec, cg_rts
@@ -44,6 +50,7 @@ cg_init:
sta cg_len
sta cg_err
sta cg_fix_count
sta cg_count
clc
rts

@@ -51,13 +58,24 @@ cg_here:
lda cg_len
rts

; A != 0: count bytes only. A = 0: emit them (the default).
cg_set_count:
sta cg_count
rts

; A = byte to append. C=1 on overflow.
cg_put:
ldy cg_len
cpy #CG_MAX
bcs @full
pha
lda cg_count
bne @count
pla
sta cg_code,y
inc cg_len
jmp @bump
@count: pla
@bump: inc cg_len
clc
rts
@full: lda #CG_ERR_FULL
@@ -208,6 +226,7 @@ cg_patch_here:
cg_code: .res 256
cg_len: .res 1
cg_err: .res 1
cg_count: .res 1
cg_fix_count: .res 1
cg_fixpos: .res CG_FIX_MAX
cg_addr: .res 2


+ 604
- 0
newLang/src/compiler/flow.s Прегледај датотеку

@@ -0,0 +1,604 @@
; flow.s - labels, branches, calls, and frame prologue/epilogue (E04-S04).
;
; flow.s builds on the scalar emitter (codegen.s). It supplies the control
; transfer that whole statements need:
;
; fl_init A = FL_MODE_EMIT or FL_MODE_COUNT
; 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_branch A = opcode, X = label id: conditional branch
; 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
; fl_call A/X = target, Y = argument bytes: JSR then discard
; fl_call_byte same, with a one-byte result preserved in A
; fl_call_word same, with a two-byte result preserved in A/X
; fl_prologue A/X = local bytes, Y = error label: frame_enter + BCS
; fl_ret no result: JSR frame_leave, then RTS
; fl_ret_byte one-byte result in A, preserved across frame_leave
; fl_ret_word two-byte result in A/X, preserved across frame_leave
; fl_err FL_OK or the first FL_ERR_* code
; fl_enter, fl_leave, fl_discard runtime addresses the caller supplies
;
; Conditional branches are always five bytes:
;
; short form (target within +0..+127)
; Bxx target ; distance byte
; NOP NOP NOP ; unused JMP slot; executed only when not taken
;
; long form (target further away, or out of range of a byte)
; Bxx'inv +3 ; the inverse of the requested condition
; JMP target
;
; The length never changes, so a counting sizing pass and an emitting pass
; make identical label offsets and identical total lengths. The long form
; inverts the opcode when it patches, so a true condition falls through to the
; JMP and a false one skips it. Short branches keep the direct opcode and NOP
; out the unused JMP slot.
;
; Backward control transfer always uses fl_jmp (JMP abs). A relative branch
; whose target is not ahead is FL_ERR_RANGE rather than a wrapped distance.
;
; Calls follow the software-frame ABI (frames.inc): arguments are already on
; the software stack, fl_call emits JSR and then frame_discard for Y bytes.
; fl_prologue emits frame_enter and branches to the caller's error label with
; BCS, so a refused frame takes the same fault path as later runtime checks.

.setcpu "6502"
.include "../common/codegen.inc"
.include "../common/flow.inc"

.import cg_init, cg_put, cg_len, cg_code, cg_lda_imm, cg_ldx_imm
.import cg_jsr_abs, cg_rts
.import cg_set_count

; Branch to this routine's @out label even when it is out of relative range.
.macro CHECK_OUT
bcc :+
jmp @out
:
.endmacro

.export fl_init, fl_label, fl_jmp, fl_branch, fl_finish, fl_get_label
.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
.export fl_err, fl_enter, fl_leave, fl_discard

.segment "CODE"

; A = mode. Reset the buffer, labels, and fixups, then set count mode.
fl_init:
sta fl_mode
jsr cg_init
lda #$00
sta fl_fix_count
sta fl_err
ldx #FL_LABELS - 1
@clr: sta fl_defined,x
dex
bpl @clr
lda fl_mode
jsr cg_set_count
clc
rts

; A = label id. C=1 on a bad id or a redefinition.
fl_label:
cmp #FL_LABELS
bcs @bad
tax
lda fl_defined,x
bne @dup
lda #$01
sta fl_defined,x
lda cg_len
sta fl_labels,x
clc
rts
@dup: lda #FL_ERR_STATE
sta fl_err
sec
rts
@bad: lda #FL_ERR_ID
sta fl_err
sec
rts

; A = label id. Emit JMP abs with a placeholder operand.
fl_jmp:
cmp #FL_LABELS
bcs @bad
sta fl_lab
lda #$4C
jsr cg_put
CHECK_OUT
lda cg_len
sta fl_pos
lda #$01 ; fixup kind: JMP
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 = branch opcode, X = label id. Emit the fixed five-byte form.
fl_branch:
sta fl_op
stx fl_lab
cpx #FL_LABELS
bcs @bad
lda fl_op
jsr cg_put
CHECK_OUT
lda cg_len ; the distance byte
sta fl_pos
lda #$02 ; fixup kind: branch
jsr fl_add_fix
CHECK_OUT
lda #$00
jsr cg_put
CHECK_OUT
lda #$4C
jsr cg_put
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

fl_branch_true:
sta fl_lab
lda #$D0 ; BNE
ldx fl_lab
jmp fl_branch

fl_branch_false:
sta fl_lab
lda #$F0 ; BEQ
ldx fl_lab
jmp fl_branch

; A = kind, fl_lab = label, fl_pos = operand position. Append a fixup.
fl_add_fix:
sta fl_kind
lda fl_fix_count
cmp #FL_FIX_MAX
bcs @full
asl a
asl a ; * FL_FIX_SIZE
tax
lda fl_kind
sta fl_fix,x
inx
lda fl_lab
sta fl_fix,x
inx
lda fl_pos
sta fl_fix,x
inc fl_fix_count
clc
rts
@full: lda #FL_ERR_FULL
sta fl_err
sec
rts

; Resolve every fixup. Counting mode only checks that each label exists.
fl_finish:
lda #$00
sta fl_index
@next: lda fl_index
cmp fl_fix_count
beq @ok
asl a
asl a
sta fl_recp
tay
lda fl_fix+1,y ; label id
jsr fl_resolve
CHECK_OUT
lda fl_mode
bne @valid ; counting: no bytes to patch
ldy fl_recp
lda fl_fix,y
cmp #$01
beq @patch_jmp
lda fl_fix+1,y
tax
lda fl_labels,x
jsr fl_patch_branch
CHECK_OUT
jmp @valid
@patch_jmp:
ldy fl_recp
lda fl_fix+2,y
tay
lda fl_tlo
sta cg_code,y
iny
lda fl_thi
sta cg_code,y
@valid: inc fl_index
jmp @next
@ok: clc
rts
@out: sec
rts

; A = label id -> fl_tlo/fl_thi = the label's absolute code address.
fl_resolve:
cmp #FL_LABELS
bcs @bad
tax
lda fl_defined,x
beq @bad
lda fl_labels,x
clc
adc #<cg_code
sta fl_tlo
lda #>cg_code
adc #$00
sta fl_thi
clc
rts
@bad: lda #FL_ERR_LABEL
sta fl_err
sec
rts

; A = target offset. fl_recp = record, fl_pos = distance-byte position.
; Patch the five-byte form: short (distance + three NOPs) or long (JMP).
fl_patch_branch:
sta fl_laboff
ldy fl_recp
lda fl_fix+2,y
sta fl_pos
lda fl_laboff
cmp fl_pos
bcc @range ; target before the branch
beq @range
sec
sbc fl_pos
sec
sbc #$01 ; distance from the end of the branch
cmp #128
bcs @long
ldy fl_pos
sta cg_code,y
iny
lda #$EA ; NOP out the unused JMP slot
sta cg_code,y
iny
sta cg_code,y
iny
sta cg_code,y
clc
rts
@long: ldy fl_pos
dey
lda cg_code,y ; invert the requested condition
jsr fl_invert_opcode
CHECK_OUT
ldy fl_pos
dey
sta cg_code,y
iny
lda #$03 ; skip the three-byte JMP
sta cg_code,y
iny
lda #$4C
sta cg_code,y
iny
lda fl_tlo
sta cg_code,y
iny
lda fl_thi
sta cg_code,y
clc
rts
@out: sec
rts
@range: lda #FL_ERR_RANGE
sta fl_err
sec
rts

; A = branch opcode -> A = its inverse. Only the conditions flow.s emits.
fl_invert_opcode:
cmp #$F0 ; BEQ <-> BNE
beq @beq
cmp #$D0
beq @bne
cmp #$90 ; BCC <-> BCS
beq @bcc
cmp #$B0
beq @bcs
cmp #$10 ; BPL <-> BMI
beq @bpl
cmp #$30
beq @bmi
cmp #$50 ; BVC <-> BVS
beq @bvc
cmp #$70
beq @bvs
lda #FL_ERR_STATE
sta fl_err
sec
rts
@beq: lda #$D0
clc
rts
@bne: lda #$F0
clc
rts
@bcc: lda #$B0
clc
rts
@bcs: lda #$90
clc
rts
@bpl: lda #$10
clc
rts
@bmi: lda #$30
clc
rts
@bvc: lda #$50
clc
rts
@bvs: lda #$70
clc
rts

; A = label id -> X = offset, C=0; C=1 if the label was never defined.
fl_get_label:
cmp #FL_LABELS
bcs @bad
tax
lda fl_defined,x
beq @bad
lda fl_labels,x
tax
clc
rts
@bad: lda #FL_ERR_LABEL
sta fl_err
sec
rts

; A/X = target, Y = argument bytes. fl_call is for a callee without a
; result; fl_call_byte and fl_call_word save the result on the hardware
; stack across the argument discard and restore it afterward.
fl_call:
sta fl_addr
stx fl_addr+1
sty fl_count
lda #$00
sta fl_resmode
jmp fl_call_emit

fl_call_byte:
sta fl_addr
stx fl_addr+1
sty fl_count
lda #$01
sta fl_resmode
jmp fl_call_emit

fl_call_word:
sta fl_addr
stx fl_addr+1
sty fl_count
lda #$02
sta fl_resmode
jmp fl_call_emit

fl_call_emit:
lda fl_addr
ldx fl_addr+1
jsr cg_jsr_abs
CHECK_OUT
lda fl_resmode
beq @discard
cmp #$01
beq @save_byte
lda #$48 ; PHA (word low)
jsr cg_put
CHECK_OUT
lda #$8A ; TXA
jsr cg_put
CHECK_OUT
lda #$48 ; PHA (word high)
jsr cg_put
CHECK_OUT
jmp @discard
@save_byte:
lda #$48 ; PHA (byte result)
jsr cg_put
CHECK_OUT
@discard:
lda fl_count
beq @restore
lda fl_discard
ora fl_discard+1
beq @state
lda fl_count
jsr cg_lda_imm
CHECK_OUT
lda #$00
jsr cg_ldx_imm
CHECK_OUT
lda fl_discard
ldx fl_discard+1
jsr cg_jsr_abs
CHECK_OUT
@restore:
lda fl_resmode
beq @ok
cmp #$01
beq @restore_byte
lda #$68 ; PLA -> high
jsr cg_put
CHECK_OUT
lda #$AA ; TAX
jsr cg_put
CHECK_OUT
lda #$68 ; PLA -> low
jsr cg_put
CHECK_OUT
jmp @ok
@restore_byte:
lda #$68 ; PLA
jsr cg_put
CHECK_OUT
@ok: clc
rts
@state: lda #FL_ERR_STATE
sta fl_err
sec
rts
@out: sec
rts

; A/X = local bytes, Y = error label. frame_enter, then BCS error.
fl_prologue:
sta fl_locals
stx fl_locals+1
sty fl_lab
lda fl_enter
ora fl_enter+1
beq @state
lda fl_locals
jsr cg_lda_imm
CHECK_OUT
lda fl_locals+1
jsr cg_ldx_imm
CHECK_OUT
lda fl_enter
ldx fl_enter+1
jsr cg_jsr_abs
CHECK_OUT
lda #$B0 ; BCS: frame refused
ldx fl_lab
jmp fl_branch
@state: lda #FL_ERR_STATE
sta fl_err
sec
rts
@out: sec
rts

fl_ret:
lda fl_leave
ora fl_leave+1
beq @state
lda fl_leave
ldx fl_leave+1
jsr cg_jsr_abs
CHECK_OUT
jmp cg_rts
@state: lda #FL_ERR_STATE
sta fl_err
sec
rts
@out: sec
rts

; One-byte result in A: PHA, JSR frame_leave, PLA, RTS.
fl_ret_byte:
lda fl_leave
ora fl_leave+1
beq @state
lda #$48 ; PHA
jsr cg_put
CHECK_OUT
lda fl_leave
ldx fl_leave+1
jsr cg_jsr_abs
CHECK_OUT
lda #$68 ; PLA
jsr cg_put
CHECK_OUT
jmp cg_rts
@state: lda #FL_ERR_STATE
sta fl_err
sec
rts
@out: sec
rts

; Two-byte result in A (low) and X (high): PHA, TXA, PHA, JSR frame_leave,
; PLA, TAX, PLA, RTS.
fl_ret_word:
lda fl_leave
ora fl_leave+1
beq @state
lda #$48 ; PHA (low)
jsr cg_put
CHECK_OUT
lda #$8A ; TXA
jsr cg_put
CHECK_OUT
lda #$48 ; PHA (high)
jsr cg_put
CHECK_OUT
lda fl_leave
ldx fl_leave+1
jsr cg_jsr_abs
CHECK_OUT
lda #$68 ; PLA -> high
jsr cg_put
CHECK_OUT
lda #$AA ; TAX
jsr cg_put
CHECK_OUT
lda #$68 ; PLA -> low
jsr cg_put
CHECK_OUT
jmp cg_rts
@state: lda #FL_ERR_STATE
sta fl_err
sec
rts
@out: sec
rts

.segment "BSS"
fl_mode: .res 1
fl_err: .res 1
fl_fix_count: .res 1
fl_index: .res 1
fl_recp: .res 1
fl_lab: .res 1
fl_pos: .res 1
fl_op: .res 1
fl_kind: .res 1
fl_count: .res 1
fl_resmode: .res 1
fl_addr: .res 2
fl_locals: .res 2
fl_laboff: .res 1
fl_tlo: .res 1
fl_thi: .res 1
fl_labels: .res FL_LABELS
fl_defined: .res FL_LABELS
fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE
fl_enter: .res 2
fl_leave: .res 2
fl_discard: .res 2

+ 199
- 0
newLang/src/compiler/output.s Прегледај датотеку

@@ -7,6 +7,7 @@
; NOVA128 MAP 1
; ENTRY hhhh
; SECTION name start size
; CLASS name size destroy interface-count interface-directory
;
; API (all calls in the bank-0 compiler state):
; out_begin A/X = source name (bank 0), Y = length. Sets out_load and
@@ -19,6 +20,9 @@
; out_bytes A/X = data, Y = count (0-255).
; out_end_section
; out_finish Writes the MAP, closes both files, and returns C=0 on success.
; out_class A/X = class name, Y = length. Adds a deterministic CLASS
; record from out_class_size/destroy/ifaces/ifdir. Class
; records follow the SECTION lines in call order.
; out_abort Closes and scratches only files created by this run.
;
; Every failure sets out_err and returns C=1; the caller must call out_abort.
@@ -35,6 +39,8 @@

.export out_begin, out_startup, out_section, out_end_section
.export out_byte, out_bytes, out_finish, out_abort
.export out_class
.export out_class_size, out_class_destroy, out_class_ifaces, out_class_ifdir
.export out_load, out_entry, out_start, out_err

.segment "ZEROPAGE"
@@ -126,6 +132,24 @@ set_rec_ptr:
bne @add
@done: rts

; out_rec_ptr = out_classes + out_index * OUT_CLASS_RECORD_SIZE.
set_class_ptr:
lda #<out_classes
sta out_rec_ptr
lda #>out_classes
sta out_rec_ptr+1
ldx out_index
beq @done
@add: clc
lda out_rec_ptr
adc #OUT_CLASS_RECORD_SIZE
sta out_rec_ptr
bcc :+
inc out_rec_ptr+1
: dex
bne @add
@done: rts

; Build and send "S0:name" on the command channel.
out_scratch:
sta out_scratch_ptr
@@ -210,6 +234,7 @@ out_begin:

lda #$00
sta out_count
sta out_class_count
sta out_in_section
sta out_prg_created
sta out_map_created
@@ -527,6 +552,65 @@ out_bytes:
@done: clc
@out: rts

; A/X = class name, Y = length. The descriptor values are supplied in the
; exported out_class_* variables. C=1 sets out_err and adds no record.
out_class:
sta out_tmp
stx out_tmp+1
sty out_tmp_len
lda out_class_count
cmp #OUT_MAX_CLASSES
bcs @too_many
lda out_tmp_len
cmp #OUT_SECTION_NAME_MAX + 1
bcs @bad_name
lda out_class_count
sta out_index
jsr set_class_ptr
ldy #0
lda out_tmp
sta (out_rec_ptr),y
iny
lda out_tmp+1
sta (out_rec_ptr),y
iny
lda out_tmp_len
sta (out_rec_ptr),y
iny
lda out_class_size
sta (out_rec_ptr),y
iny
lda out_class_size+1
sta (out_rec_ptr),y
iny
lda out_class_destroy
sta (out_rec_ptr),y
iny
lda out_class_destroy+1
sta (out_rec_ptr),y
iny
lda out_class_ifaces
sta (out_rec_ptr),y
iny
lda out_class_ifdir
sta (out_rec_ptr),y
iny
lda out_class_ifdir+1
sta (out_rec_ptr),y
inc out_class_count
clc
rts
@too_many:
lda #OUT_ERR_CLASSES
sta out_err
sec
rts
@bad_name:
lda #OUT_ERR_NAME
sta out_err
sec
rts

out_finish:
jsr out_end_section
jsr out_close_prg
@@ -727,6 +811,113 @@ out_write_map:
bcs @out
inc out_index
jmp @section
@done: jsr out_write_classes
bcs @out
clc
@out: rts

; Write CLASS records after the SECTION records, in out_class call order.
out_write_classes:
lda #$00
sta out_index
@class:
lda out_index
cmp out_class_count
bne @body
jmp @done
@body:
jsr set_class_ptr
lda #<class_prefix
ldx #>class_prefix
ldy #class_prefix_len
jsr out_text
bcc @prefix_ok
jmp @out
@prefix_ok:
ldy #0
lda (out_rec_ptr),y
sta out_srcp
iny
lda (out_rec_ptr),y
sta out_srcp+1
iny
lda (out_rec_ptr),y
sta out_text_len
ldy #0
@name: cpy out_text_len
beq @named
lda (out_srcp),y
sty out_y
jsr out_char
bcc @name_ok
jmp @out
@name_ok:
ldy out_y
iny
bne @name
@named:
lda #' '
jsr out_char
bcc @space1
jmp @out
@space1:
ldy #3
lda (out_rec_ptr),y
sta out_num
iny
lda (out_rec_ptr),y
sta out_num+1
jsr out_hex4
bcc @size_ok
jmp @out
@size_ok:
lda #' '
jsr out_char
bcc @space2
jmp @out
@space2:
ldy #5
lda (out_rec_ptr),y
sta out_num
iny
lda (out_rec_ptr),y
sta out_num+1
jsr out_hex4
bcc @destroy_ok
jmp @out
@destroy_ok:
lda #' '
jsr out_char
bcc @space3
jmp @out
@space3:
ldy #7
lda (out_rec_ptr),y
jsr out_hex_byte
bcc @count_ok
jmp @out
@count_ok:
lda #' '
jsr out_char
bcc @space4
jmp @out
@space4:
ldy #8
lda (out_rec_ptr),y
sta out_num
iny
lda (out_rec_ptr),y
sta out_num+1
jsr out_hex4
bcc @dir_ok
jmp @out
@dir_ok:
jsr out_cr
bcc @line_ok
jmp @out
@line_ok:
inc out_index
jmp @class
@done: clc
@out: rts

@@ -809,6 +1000,8 @@ map_entry: .byte "ENTRY "
map_entry_len = * - map_entry
section_prefix: .byte "SECTION "
section_prefix_len = * - section_prefix
class_prefix: .byte "CLASS "
class_prefix_len = * - class_prefix
hex_digits: .byte "0123456789ABCDEF"

.segment "BSS"
@@ -835,6 +1028,7 @@ out_entry: .res 2
out_start: .res 2
out_current: .res 2
out_count: .res 1
out_class_count: .res 1
out_index: .res 1
out_in_section: .res 1
out_prg_created: .res 1
@@ -844,6 +1038,11 @@ out_prg_is_open: .res 1
out_map_is_open: .res 1
out_err: .res 1
out_sections: .res OUT_MAX_SECTIONS * OUT_RECORD_SIZE
out_classes: .res OUT_MAX_CLASSES * OUT_CLASS_RECORD_SIZE
out_class_size: .res 2
out_class_destroy: .res 2
out_class_ifaces: .res 1
out_class_ifdir: .res 2
out_tmp: .res 2
out_tmp_len: .res 1
out_scratch_ptr: .res 2


+ 74
- 9
newLang/src/compiler/parser.s Прегледај датотеку

@@ -58,6 +58,7 @@
.import assign_slots, check_interfaces

.export parse_file, tb_get, tb_set, mem_count, par_count
.export gen_text, gen_text_len, gen_print_count, gen_bad
.export ex_trace_on, ex_min_sp
.exportzp ex_tp
.export type_kind, type_line_lo, type_line_hi
@@ -89,6 +90,10 @@ parse_file:
stx parse_sp
jsr tt_clear ; bank-1 BSS is not zeroed at load
lda #$00
sta gen_text_len ; first source-to-code path (E04-S05)
sta gen_print_count
sta gen_bad
sta gen_in_print
sta mem_count
sta par_count
sta mem_full
@@ -1095,6 +1100,7 @@ parse_statement:
stx stmt_sp
lda #$00
sta ex_depth
sta gen_in_print
lda tok_col
sta stmt_col
jsr trace_line
@@ -1107,8 +1113,9 @@ parse_statement:
bne :+
jmp @for
: cmp #KW_ELSE
beq @else
cmp #KW_RETURN
bne :+
jmp @else
: cmp #KW_RETURN
beq @return
cmp #KW_PRINT
beq @print
@@ -1119,8 +1126,27 @@ parse_statement:
jsr value
jsr check_cond
jmp end_stmt
@print: jsr value
@print: lda pass ; pass 2 captures a string literal in print
cmp #$02
bne @print_value
lda #$01
sta gen_in_print
lda gen_text_len
sta gen_len_before
@print_value:
jsr value
jsr check_print
lda #$00
sta gen_in_print
lda pass
cmp #$02
bne @print_done
lda gen_text_len
cmp gen_len_before
bne @print_done
lda #$01
sta gen_bad
@print_done:
jmp end_stmt
@delete:
jsr value
@@ -1528,11 +1554,15 @@ parse_postfix:
jsr trace_byte
lda tok_kind
cmp #TOK_IDENT
beq @name
cmp #KW_NEW
beq @new
cmp #KW_SELF
beq @self
bne :+
jmp @name
: cmp #KW_NEW
bne :+
jmp @new
: cmp #KW_SELF
bne :+
jmp @self
:
ldx #lit_count - 1
@lit: cmp lit_kinds,x
beq @literal
@@ -1548,7 +1578,17 @@ parse_postfix:
@early: lda #E_SYN_UNEXPECTED_EOL
jmp syn_error
@literal:
lda lit_types,x
lda gen_in_print
beq :+
lda tok_kind
cmp #TOK_STRING
bne :+
txa
pha
jsr gen_capture
pla
tax
: lda lit_types,x
sta sc_base
lda tok_value ; a number's value (others ignore it)
sta sc_val
@@ -1876,6 +1916,24 @@ trace_byte:
inc ex_tp+1
@off: rts

; Copy the current STRING token into gen_text. Called from parse_postfix
; only inside a pass-2 print expression (E04-S05). Clobbers A/X/Y.
gen_capture:
inc gen_print_count
ldx tok_text_len
cpx #81
bcc :+
ldx #80
: stx gen_text_len
ldy #$00
@copy: cpy gen_text_len
beq @done
lda tok_text,y
sta gen_text,y
iny
bne @copy
@done: rts

; Remember the member name token; the next token picks field or method.
pend_member:
lda tok_col
@@ -2719,6 +2777,13 @@ op_code: .res 1
rt_col: .res 1
val_col: .res 1 ; first column of the statement's value
ty_col: .res 1 ; first column of a signature type
; pass-2 capture used by the hello compile fixture (E04-S05)
gen_text: .res 80
gen_text_len: .res 1
gen_print_count: .res 1
gen_bad: .res 1
gen_in_print: .res 1
gen_len_before: .res 1
mem_seq: .res 1 ; pass 2: next member row of cur_owner
impl_count: .res 1
impl_full: .res 1


+ 207
- 0
newLang/src/runtime/construct.s Прегледај датотеку

@@ -0,0 +1,207 @@
; construct.s - runtime object construction and deletion (E05-S03).
;
; This layer sits above the E05-S01 heap and the E05-S02 descriptor ABI. It
; deliberately does not depend on the compiler: generated code will call it
; after evaluating constructor arguments.
;
; API:
; obj_new A/X = class descriptor pointer. The caller has already
; pushed constructor arguments right-to-left on the software
; frame stack and set obj_init_ptr to the init entry (or 0).
; Allocates the descriptor's instance size, zeroes it (heap
; contract), stores the descriptor at object+0, then calls
; init with A/X = self. Returns A/X = object, C=0. On failure
; A/X=0, C=1 and obj_error is OBJ_ERR_HEAP or OBJ_ERR_BAD.
; init is never called on failure.
; obj_delete A/X = object. Null is a no-op. Reads the optional
; destructor from the descriptor, calls it once with A/X =
; self, then frees the complete object. Returns C=0 and
; A/X=0; C=1 with obj_error on a bad object or heap error.
; obj_init_ptr init entry selected by the caller; 0 = no init.
; obj_error OBJ_OK, OBJ_ERR_HEAP, or OBJ_ERR_BAD.
;
; Constructor arguments remain on the caller's frame stack. init uses the
; normal frame_enter/frame_leave contract and sees them at FP+2. The caller
; discards the argument bytes after obj_new returns. obj_delete and obj_new
; use obj_call, a synthetic-return indirect call, because the 8502 has no
; JSR (address). obj_call takes its entry from co_vec because A/X already
; carry the callee's self pointer; it preserves A/X and only needs one level
; of synthetic return, so E06-S02 can reuse it for interface dispatch.

.setcpu "6502"
.include "../common/class.inc"

.import heap_alloc, heap_free, obj_class
.export obj_new, obj_delete, obj_init_ptr, obj_error

.segment "ZEROPAGE"
co_vec: .res 2 ; indirect target for obj_call
co_desc: .res 2 ; descriptor pointer
co_ptr: .res 2 ; object header writer
co_self: .res 2 ; A/X passed through obj_call

.segment "CODE"

; A/X = descriptor. Allocate and construct; see the API above.
obj_new:
sta co_desc
stx co_desc+1
lda #OBJ_OK
sta obj_error

ldy #CD_INSTANCE_SIZE
lda (co_desc),y
sta co_size
iny
lda (co_desc),y
sta co_size+1
ora co_size
beq @bad

lda co_size
ldx co_size+1
jsr heap_alloc
bcs @heap_fail
sta co_obj
stx co_obj+1

lda co_obj ; object+0 = descriptor
sta co_ptr
lda co_obj+1
sta co_ptr+1
ldy #OBJ_DESCRIPTOR
lda co_desc
sta (co_ptr),y
iny
lda co_desc+1
sta (co_ptr),y

lda obj_init_ptr
ora obj_init_ptr+1
beq @ok
lda obj_init_ptr
sta co_vec
lda obj_init_ptr+1
sta co_vec+1
lda co_obj
ldx co_obj+1
jsr obj_call
@ok:
lda co_obj
ldx co_obj+1
clc
rts
@heap_fail:
lda #OBJ_ERR_HEAP
sta obj_error
lda #$00
tax
sec
rts
@bad:
lda #OBJ_ERR_BAD
sta obj_error
lda #$00
tax
sec
rts

; A/X = object. Optional destructor, then free. Null is successful.
obj_delete:
sta co_obj
stx co_obj+1
lda #OBJ_OK
sta obj_error
lda co_obj
ora co_obj+1
bne :+
jmp @null
:
lda co_obj ; reload: the null test left A = low|high
ldx co_obj+1
jsr obj_class
bcc :+
jmp @bad
:
sta co_desc
stx co_desc+1

ldy #CD_INSTANCE_SIZE
lda (co_desc),y
sta co_size
iny
lda (co_desc),y
sta co_size+1
ora co_size
bne :+
jmp @bad
:

ldy #CD_DESTRUCTOR
lda (co_desc),y
sta co_destroy
iny
lda (co_desc),y
sta co_destroy+1
lda co_destroy
ora co_destroy+1
bne :+
jmp @free
:

lda co_destroy
sta co_vec
lda co_destroy+1
sta co_vec+1
lda co_obj
ldx co_obj+1
jsr obj_call

@free:
lda co_obj
ldx co_obj+1
jsr heap_free
bcs @heap_fail
@null:
lda #$00
tax
clc
rts
@heap_fail:
lda #OBJ_ERR_HEAP
sta obj_error
lda #$00
tax
sec
rts
@bad:
lda #OBJ_ERR_BAD
sta obj_error
lda #$00
tax
sec
rts

; Entry in co_vec. A/X pass through to the callee, so the caller passes self
; there. Push a synthetic return address, then JMP (co_vec): the callee's RTS
; returns to @ret, whose RTS returns to the original caller. co_vec must not
; straddle a page because of the 6502 JMP-indirect bug.
obj_call:
sta co_self
stx co_self+1
lda #>(@ret - 1)
pha
lda #<(@ret - 1)
pha
lda co_self
ldx co_self+1
jmp (co_vec)
@ret: rts
.assert <co_vec <> $FF, error, "co_vec must not straddle a page"

.segment "BSS"
co_obj: .res 2
co_size: .res 2
co_destroy: .res 2
obj_init_ptr: .res 2
obj_error: .res 1

+ 87
- 0
newLang/src/runtime/faults.s Прегледај датотеку

@@ -0,0 +1,87 @@
; faults.s - runtime fault reporting for generated programs (E04-S05).
;
; flt_report: A = fault code (FLT_*), X/Y = fault address (0 if unknown).
; Writes the mailbox as a generated program failure and prints
;
; RUNTIME FAULT R00n
;
; followed by a carriage return. The code and address are written before
; MB_STATUS, so a monitor polling the mailbox never sees FAIL with stale
; fields. The routine returns; unwinding to the runtime entry is deferred to
; the frame/exception work in E05. It requires the same mapping and zero page
; as src/runtime/print.s (D132).

.setcpu "6502"
.include "../common/mailbox.inc"
.include "../common/faults.inc"

.import pr_text, pr_char, pr_cr
.export flt_report, flt_code, flt_addr

.segment "CODE"
flt_report:
sta flt_code
stx flt_addr
sty flt_addr+1

lda #MB_PROD_PROGRAM
sta MB_PRODUCER
lda flt_code
sta MB_CODE
lda #$00
sta MB_CODE+1
lda flt_addr
sta MB_ADDR
lda flt_addr+1
sta MB_ADDR+1
lda #MB_ST_FAIL
sta MB_STATUS

lda #<fault_prefix
ldx #>fault_prefix
ldy #fault_prefix_len
jsr pr_text

lda flt_code ; three digits, leading zeros kept
ldx #$00
@hundreds:
cmp #100
bcc @h_done
sbc #100
inx
bne @hundreds
@h_done:
pha
txa
clc
adc #$30
jsr pr_char
pla
ldx #$00
@tens: cmp #10
bcc @t_done
sbc #10
inx
bne @tens
@t_done:
pha
txa
clc
adc #$30
jsr pr_char
pla
clc
adc #$30
jsr pr_char
jsr pr_cr
rts

.segment "RODATA"
fault_prefix: .byte "RUNTIME FAULT R"
fault_prefix_end:
fault_prefix_len = fault_prefix_end - fault_prefix
.assert fault_prefix_len = 15, error, "fault prefix changed"

.segment "BSS"
flt_code: .res 1
flt_addr: .res 2

+ 11
- 4
newLang/src/runtime/frames.s Прегледај датотеку

@@ -159,14 +159,20 @@ frame_leave:
inc FRAME_SP+1
@done: rts

; Pop argument bytes after a return. A/X = byte count (low/high).
; Pop argument bytes after a return. A/X = byte count (low/high). A and X
; are preserved so the caller can still store the callee's result.
frame_discard:
sta frame_discard_n
stx frame_discard_n+1
clc
adc FRAME_SP
lda FRAME_SP
adc frame_discard_n
sta FRAME_SP
txa
adc FRAME_SP+1
lda FRAME_SP+1
adc frame_discard_n+1
sta FRAME_SP+1
lda frame_discard_n
ldx frame_discard_n+1
rts

.segment "BSS"
@@ -177,4 +183,5 @@ frame_word: .res 2
frame_locals: .res 2
frame_saved_sp: .res 2
frame_saved_fp: .res 2
frame_discard_n: .res 2
frame_error: .res 1

+ 742
- 0
newLang/src/runtime/heap.s Прегледај датотеку

@@ -0,0 +1,742 @@
; heap.s - deterministic generated-program free-list heap (E05-S01).
;
; The caller supplies the region; this module does not choose a generated-
; program heap address. All routines run in the generated program's current
; RAM bank. See src/common/heap.inc for the block layout and API.
;
; Allocation is first-fit in address order. A free block is split when the
; remainder is at least HEAP_MIN_BLOCK. Freeing inserts in address order and
; coalesces with the following free block(s) and then with the preceding one.
; Every boundary, size, magic, and double-free check is always on.

.setcpu "6502"
.include "../common/heap.inc"

.export heap_init, heap_alloc, heap_free
.export heap_base, heap_end, heap_free_head, heap_free_total, heap_error

.segment "ZEROPAGE"
heap_ptr: .res 2
heap_ptr2: .res 2

.segment "CODE"

; A/X = base (A must be 0), Y = pages (1..127). C=1 with HEAP_ERR_ARG on a
; malformed region. On success the heap is one free block.
heap_init:
sta heap_arg
stx heap_arg+1
sty heap_arg+2
lda #HEAP_OK
sta heap_error

lda heap_arg+2
beq @bad
cmp #HEAP_MAX_PAGES+1
bcs @bad
lda heap_arg
bne @bad

lda heap_arg+1
clc
adc heap_arg+2
bcs @bad
cmp #$C1 ; heap must end at or below $C000
bcs @bad
sta heap_end+1
lda #$00
sta heap_end

lda heap_arg+1
sta heap_base+1
lda #$00
sta heap_base

lda heap_base
sta heap_free_head
lda heap_base+1
sta heap_free_head+1

lda #$00
sta heap_free_total
lda heap_arg+2
sta heap_free_total+1

lda heap_base
sta heap_ptr
lda heap_base+1
sta heap_ptr+1
ldy #0
lda #$00
sta (heap_ptr),y ; size low
iny
lda heap_arg+2
sta (heap_ptr),y ; size high = pages
iny
lda #$00
sta (heap_ptr),y ; next low
iny
sta (heap_ptr),y ; next high
clc
rts
@bad:
lda #HEAP_ERR_ARG
sta heap_error
sec
rts

; A/X = requested payload bytes. Returns A/X = payload, C=0; or A/X=0, C=1
; with heap_error. The payload is zeroed.
heap_alloc:
sta heap_req
stx heap_req+1
lda #HEAP_OK
sta heap_error

lda heap_req
clc
adc #HEAP_HEADER_SIZE
sta heap_need
lda heap_req+1
adc #0
sta heap_need+1
bcc :+
jmp @oom
:
lda heap_need
and #1
beq @rounded
inc heap_need
bne @rounded
inc heap_need+1
bne :+
jmp @oom
:
@rounded:
lda heap_need+1
bpl :+
jmp @oom
:
bne @min_ok
lda heap_need
cmp #HEAP_MIN_BLOCK
bcs @min_ok
lda #HEAP_MIN_BLOCK
sta heap_need
lda #0
sta heap_need+1
@min_ok:
lda #0
sta heap_prev
sta heap_prev+1
lda heap_free_head
sta heap_cur
lda heap_free_head+1
sta heap_cur+1

@walk:
lda heap_cur
ora heap_cur+1
bne :+
jmp @oom
:
lda heap_cur
sta heap_ptr
lda heap_cur+1
sta heap_ptr+1
ldy #0
lda (heap_ptr),y
sta heap_bsize
iny
lda (heap_ptr),y
and #$7F
sta heap_bsize+1

lda heap_bsize+1
cmp heap_need+1
bcc @next
bne @found
lda heap_bsize
cmp heap_need
bcc @next

@found:
lda heap_bsize
sec
sbc heap_need
sta heap_rem
lda heap_bsize+1
sbc heap_need+1
sta heap_rem+1
lda heap_rem+1
beq :+
jmp @split
:
lda heap_rem
cmp #HEAP_MIN_BLOCK
bcc :+
jmp @split
:
jmp @use_whole

@next:
lda heap_cur
sta heap_prev
lda heap_cur+1
sta heap_prev+1
ldy #2
lda (heap_ptr),y
sta heap_cur
iny
lda (heap_ptr),y
sta heap_cur+1
jmp @walk

@use_whole:
lda heap_cur
sta heap_block
lda heap_cur+1
sta heap_block+1
lda heap_block
sta heap_ptr
lda heap_block+1
sta heap_ptr+1
ldy #2
lda (heap_ptr),y
sta heap_next
iny
lda (heap_ptr),y
sta heap_next+1

lda heap_prev
ora heap_prev+1
beq @uw_head
lda heap_prev
sta heap_ptr
lda heap_prev+1
sta heap_ptr+1
ldy #2
lda heap_next
sta (heap_ptr),y
iny
lda heap_next+1
sta (heap_ptr),y
jmp @uw_removed
@uw_head:
lda heap_next
sta heap_free_head
lda heap_next+1
sta heap_free_head+1
@uw_removed:
lda heap_free_total
sec
sbc heap_bsize
sta heap_free_total
lda heap_free_total+1
sbc heap_bsize+1
sta heap_free_total+1

jsr @mark_used
lda heap_bsize
sec
sbc #HEAP_HEADER_SIZE
sta heap_zero_len
lda heap_bsize+1
sbc #0
sta heap_zero_len+1
jmp @zero_return

@split:
lda heap_cur
sta heap_block
lda heap_cur+1
sta heap_block+1
lda heap_block
sta heap_ptr
lda heap_block+1
sta heap_ptr+1
ldy #2
lda (heap_ptr),y
sta heap_next
iny
lda (heap_ptr),y
sta heap_next+1

lda heap_block
clc
adc heap_need
sta heap_rem_block
lda heap_block+1
adc heap_need+1
sta heap_rem_block+1

lda heap_rem_block
sta heap_ptr2
lda heap_rem_block+1
sta heap_ptr2+1
ldy #0
lda heap_rem
sta (heap_ptr2),y
iny
lda heap_rem+1
sta (heap_ptr2),y
iny
lda heap_next
sta (heap_ptr2),y
iny
lda heap_next+1
sta (heap_ptr2),y

lda heap_prev
ora heap_prev+1
beq @sp_head
lda heap_prev
sta heap_ptr
lda heap_prev+1
sta heap_ptr+1
ldy #2
lda heap_rem_block
sta (heap_ptr),y
iny
lda heap_rem_block+1
sta (heap_ptr),y
jmp @sp_linked
@sp_head:
lda heap_rem_block
sta heap_free_head
lda heap_rem_block+1
sta heap_free_head+1
@sp_linked:
lda heap_need
sta heap_bsize
lda heap_need+1
sta heap_bsize+1
jsr @mark_used

lda heap_free_total
sec
sbc heap_need
sta heap_free_total
lda heap_free_total+1
sbc heap_need+1
sta heap_free_total+1

lda heap_need
sec
sbc #HEAP_HEADER_SIZE
sta heap_zero_len
lda heap_need+1
sbc #0
sta heap_zero_len+1
jmp @zero_return

@mark_used:
lda heap_block
sta heap_ptr
lda heap_block+1
sta heap_ptr+1
ldy #0
lda heap_bsize
sta (heap_ptr),y
iny
lda heap_bsize+1
ora #$80
sta (heap_ptr),y
iny
lda #$5A
sta (heap_ptr),y
iny
lda #$A5
sta (heap_ptr),y
rts

@zero_return:
lda heap_block
clc
adc #HEAP_HEADER_SIZE
sta heap_ptr
sta heap_ptr2
lda heap_block+1
adc #0
sta heap_ptr+1
sta heap_ptr2+1
jsr heap_zero
lda heap_ptr2
ldx heap_ptr2+1
clc
rts

@oom:
lda #HEAP_ERR_OOM
sta heap_error
lda #0
tax
sec
rts

; Zero heap_zero_len bytes at heap_ptr. A=0 and is preserved.
heap_zero:
ldx heap_zero_len+1
beq @tail
ldy #0
lda #0
@page:
sta (heap_ptr),y
iny
bne @page
inc heap_ptr+1
dex
bne @page
@tail:
ldy #0
lda #0
cpy heap_zero_len
beq @done
@byte:
sta (heap_ptr),y
iny
cpy heap_zero_len
bcc @byte
@done:
rts

; A/X = payload pointer. Null is a successful no-op. On success the block is
; free and coalesced; on failure C=1 and heap_error is set.
heap_free:
sta heap_arg
stx heap_arg+1
lda #HEAP_OK
sta heap_error
lda heap_arg
ora heap_arg+1
bne :+
jmp @ok
:
lda heap_arg
and #1
beq :+
jmp @corrupt
:

lda heap_arg
sec
sbc #HEAP_HEADER_SIZE
sta heap_block
lda heap_arg+1
sbc #0
sta heap_block+1
bcs @header_ok
jmp @corrupt
@header_ok:
lda heap_block+1
cmp heap_base+1
bcs :+
jmp @corrupt
:
bne @above_base
lda heap_block
cmp heap_base
bcs :+
jmp @corrupt
:
@above_base:
lda heap_block+1
cmp heap_end+1
bcc @in_range
beq :+
jmp @corrupt
:
lda heap_block
cmp heap_end
bcc :+
jmp @corrupt
:
@in_range:
lda heap_block
sta heap_ptr
lda heap_block+1
sta heap_ptr+1
ldy #0
lda (heap_ptr),y
sta heap_bsize
iny
lda (heap_ptr),y
sta heap_bsize+1

lda heap_bsize+1
and #$80
bne :+
jmp @double
:
lda heap_bsize+1
and #$7F
sta heap_bsize+1

lda heap_bsize+1
bne @size_hi
lda heap_bsize
cmp #HEAP_MIN_BLOCK
bcs :+
jmp @corrupt
:
@size_hi:
lda heap_bsize
and #1
beq :+
jmp @corrupt
:

lda heap_block
clc
adc heap_bsize
sta heap_tmp
lda heap_block+1
adc heap_bsize+1
sta heap_tmp+1
bcc :+
jmp @corrupt
:
lda heap_tmp+1
cmp heap_end+1
bcc @size_ok
beq :+
jmp @corrupt
:
lda heap_tmp
cmp heap_end
beq :+
jmp @corrupt
:
@size_ok:
ldy #2
lda (heap_ptr),y
cmp #$5A
beq :+
jmp @corrupt
:
iny
lda (heap_ptr),y
cmp #$A5
beq :+
jmp @corrupt
:

ldy #0
lda heap_bsize
sta (heap_ptr),y
iny
lda heap_bsize+1
sta (heap_ptr),y
iny
lda #0
sta (heap_ptr),y
iny
sta (heap_ptr),y

clc
lda heap_free_total
adc heap_bsize
sta heap_free_total
lda heap_free_total+1
adc heap_bsize+1
sta heap_free_total+1

lda #0
sta heap_prev
sta heap_prev+1
lda heap_free_head
sta heap_cur
lda heap_free_head+1
sta heap_cur+1

@find:
lda heap_cur
ora heap_cur+1
beq @place
lda heap_cur+1
cmp heap_block+1
bcc @advance
bne @place
lda heap_cur
cmp heap_block
bcc @advance
bne :+
jmp @double
:
jmp @place
@advance:
lda heap_cur
sta heap_prev
lda heap_cur+1
sta heap_prev+1
lda heap_cur
sta heap_ptr
lda heap_cur+1
sta heap_ptr+1
ldy #2
lda (heap_ptr),y
sta heap_cur
iny
lda (heap_ptr),y
sta heap_cur+1
jmp @find

@place:
lda heap_cur
ora heap_cur+1
beq @merged_next
@merge_next:
lda heap_block
clc
adc heap_bsize
sta heap_tmp
lda heap_block+1
adc heap_bsize+1
sta heap_tmp+1
lda heap_tmp+1
cmp heap_cur+1
bne @merged_next
lda heap_tmp
cmp heap_cur
bne @merged_next

lda heap_cur
sta heap_ptr
lda heap_cur+1
sta heap_ptr+1
ldy #0
lda (heap_ptr),y
clc
adc heap_bsize
sta heap_bsize
iny
lda (heap_ptr),y
adc heap_bsize+1
sta heap_bsize+1
ldy #2
lda (heap_ptr),y
sta heap_next
iny
lda (heap_ptr),y
sta heap_next+1
lda heap_next
sta heap_cur
lda heap_next+1
sta heap_cur+1
jmp @merge_next

@merged_next:
lda heap_block
sta heap_ptr
lda heap_block+1
sta heap_ptr+1
ldy #0
lda heap_bsize
sta (heap_ptr),y
iny
lda heap_bsize+1
sta (heap_ptr),y
iny
lda heap_cur
sta (heap_ptr),y
iny
lda heap_cur+1
sta (heap_ptr),y

lda heap_prev
ora heap_prev+1
beq @link_head
lda heap_prev
sta heap_ptr
lda heap_prev+1
sta heap_ptr+1
ldy #0
lda (heap_ptr),y
sta heap_tmp
iny
lda (heap_ptr),y
sta heap_tmp+1

lda heap_prev
clc
adc heap_tmp
sta heap_rem
lda heap_prev+1
adc heap_tmp+1
sta heap_rem+1
lda heap_rem+1
cmp heap_block+1
bne @link_prev_block
lda heap_rem
cmp heap_block
bne @link_prev_block

ldy #0
lda heap_tmp
clc
adc heap_bsize
sta (heap_ptr),y
iny
lda heap_tmp+1
adc heap_bsize+1
sta (heap_ptr),y
iny
lda heap_cur
sta (heap_ptr),y
iny
lda heap_cur+1
sta (heap_ptr),y
jmp @ok
@link_prev_block:
ldy #2
lda heap_block
sta (heap_ptr),y
iny
lda heap_block+1
sta (heap_ptr),y
jmp @ok
@link_head:
lda heap_block
sta heap_free_head
lda heap_block+1
sta heap_free_head+1
jmp @ok

@ok:
lda #0
tax
clc
rts
@double:
lda #HEAP_ERR_DOUBLE
sta heap_error
lda #0
tax
sec
rts
@corrupt:
lda #HEAP_ERR_CORRUPT
sta heap_error
lda #0
tax
sec
rts

.segment "BSS"
heap_base: .res 2
heap_end: .res 2
heap_free_head: .res 2
heap_free_total: .res 2
heap_error: .res 1
heap_arg: .res 3
heap_req: .res 2
heap_need: .res 2
heap_bsize: .res 2
heap_rem: .res 2
heap_rem_block: .res 2
heap_cur: .res 2
heap_prev: .res 2
heap_next: .res 2
heap_block: .res 2
heap_tmp: .res 2
heap_zero_len: .res 2

+ 127
- 0
newLang/src/runtime/objects.s Прегледај датотеку

@@ -0,0 +1,127 @@
; objects.s - object and class descriptor accessors (E05-S02).
;
; Objects use the E05-S02 layout in ../common/class.inc:
; object+0 = descriptor pointer, object+2 = first field.
; The helpers are read-only and non-reentrant; they use this module's BSS.
;
; API:
; obj_class A/X = object -> A/X = class descriptor; C=1 if null or
; the object has no descriptor pointer.
; obj_size A/X = object -> A/X = instance size; C=1 if invalid or the
; descriptor's size is zero.
; obj_destroy A/X = object -> A/X = destructor address (0 = none); C=1
; if the object/descriptor is invalid.
; obj_iface_dir A/X = object -> A/X = interface-directory pointer, Y =
; interface count; C=1 if invalid.
;
; The accessors return C=0 on success. They do not allocate, free, or call a
; destructor; construction and deletion remain E05-S03.

.setcpu "6502"
.include "../common/class.inc"

.export obj_class, obj_size, obj_destroy, obj_iface_dir

.segment "ZEROPAGE"
obj_ptr: .res 2 ; object pointer
obj_desc: .res 2 ; descriptor pointer
obj_tmp: .res 2 ; returned word
obj_count: .res 1 ; interface count

.segment "CODE"

; A/X = object. A/X = its descriptor; C=1 if the object or pointer is null.
obj_class:
sta obj_ptr
stx obj_ptr+1
ora obj_ptr+1
beq @bad
ldy #OBJ_DESCRIPTOR
lda (obj_ptr),y
sta obj_desc
iny
lda (obj_ptr),y
sta obj_desc+1
ora obj_desc
beq @bad
lda obj_desc
ldx obj_desc+1
clc
rts
@bad: lda #$00
tax
sec
rts

; A/X = object. A/X = instance size; C=1 if the object/descriptor is invalid.
obj_size:
jsr obj_class
bcs @bad
sta obj_desc
stx obj_desc+1
ldy #CD_INSTANCE_SIZE
lda (obj_desc),y
sta obj_tmp
iny
lda (obj_desc),y
sta obj_tmp+1
ora obj_tmp
beq @bad
lda obj_tmp
ldx obj_tmp+1
clc
rts
@bad: lda #$00
tax
sec
rts

; A/X = object. A/X = destructor address, zero meaning none.
obj_destroy:
jsr obj_class
bcs @bad
sta obj_desc
stx obj_desc+1
ldy #CD_DESTRUCTOR
lda (obj_desc),y
sta obj_tmp
iny
lda (obj_desc),y
sta obj_tmp+1
lda obj_tmp
ldx obj_tmp+1
clc
rts
@bad: lda #$00
tax
sec
rts

; A/X = object. A/X = interface directory, Y = count.
obj_iface_dir:
jsr obj_class
bcs @bad
sta obj_desc
stx obj_desc+1
ldy #CD_IFACE_COUNT
lda (obj_desc),y
sta obj_count
ldy #CD_IFACE_DIR
lda (obj_desc),y
sta obj_tmp
iny
lda (obj_desc),y
sta obj_tmp+1
lda obj_tmp
ldx obj_tmp+1
ldy obj_count
clc
rts
@bad: lda #$00
tax
ldy #$00
sec
rts

.segment "BSS"
obj_save: .res 0 ; keeps BSS non-empty if fields are optimized

+ 152
- 0
newLang/src/runtime/print.s Прегледај датотеку

@@ -0,0 +1,152 @@
; print.s - PETSCII text and scalar output for generated programs (E04-S05).
;
; Contract (D132):
; - The caller runs in the D129 generated-program mapping (CR=$0E): KERNAL and
; I/O visible, system zero page mapped, code below $4000. CHROUT can then be
; called directly. The routines never change CR, RCR, or the P0/P1 pointers.
; - The routines are not reentrant: module BSS holds the working values.
; - pr_char preserves X and Y and clobbers A.
; - pr_text takes A/X = address and Y = length (0..255); clobbers A.
; - pr_byte takes A = 0..255; pr_word A = low, X = high; pr_int A/X signed;
; pr_bool treats A != 0 as TRUE. These clobber A/X/Y.
; - No routine appends a newline; call pr_cr for that.

.setcpu "6502"
.include "../common/c128.inc"
.include "../common/print.inc"

.export pr_char, pr_cr, pr_text, pr_byte, pr_word, pr_int, pr_bool

.segment "ZEROPAGE"
pr_ptr: .res 2 ; indirect text pointer (must be zero page)

.segment "CODE"

; A = PETSCII character. CHROUT may clobber X/Y, so they are saved here to
; keep the formatting loops simple.
pr_char:
stx pr_sx
sty pr_sy
jsr CHROUT
ldx pr_sx
ldy pr_sy
rts

pr_cr:
lda #PR_CR
jmp pr_char

; A/X = address, Y = length. A length of 0 prints nothing.
pr_text:
sta pr_ptr
stx pr_ptr+1
sty pr_len
lda pr_len
beq @done
ldy #$00
@loop: lda (pr_ptr),y
jsr pr_char
iny
dec pr_len
bne @loop
@done: rts

; A = unsigned byte, printed as 1..3 decimal digits with no leading zeros.
pr_byte:
ldx #$00
jmp pr_word

; A/X = unsigned word, printed as 1..5 decimal digits with no leading zeros.
pr_word:
sta pr_val
stx pr_val+1
lda #$00
sta pr_started
ldx #$00 ; 10000, 1000, 100, 10, 1
@power: lda pr_pow_lo,x
sta pr_pow
lda pr_pow_hi,x
sta pr_pow+1
lda #$00
sta pr_digit
@sub: lda pr_val+1 ; while value >= power
cmp pr_pow+1
bcc @emit
bne @take
lda pr_val
cmp pr_pow
bcc @emit
@take: sec
lda pr_val
sbc pr_pow
sta pr_val
lda pr_val+1
sbc pr_pow+1
sta pr_val+1
inc pr_digit
jmp @sub
@emit: lda pr_digit
bne @show
lda pr_started
bne @show
cpx #$04 ; the ones digit is always printed
beq @show
jmp @next
@show: lda #$01
sta pr_started
lda pr_digit
clc
adc #$30
jsr pr_char
@next: inx
cpx #$05
bne @power
rts

; A/X = signed 16-bit integer, printed in minimal decimal form.
pr_int:
sta pr_val
stx pr_val+1
lda pr_val+1
bpl @positive
lda #'-'
jsr pr_char
sec
lda #$00
sbc pr_val
sta pr_val
lda #$00
sbc pr_val+1
sta pr_val+1
@positive:
lda pr_val
ldx pr_val+1
jmp pr_word

; A != 0 prints TRUE, A = 0 prints FALSE.
pr_bool:
cmp #$00
beq @false
lda #<pr_true_text
ldx #>pr_true_text
ldy #$04
jmp pr_text
@false: lda #<pr_false_text
ldx #>pr_false_text
ldy #$05
jmp pr_text

.segment "RODATA"
pr_pow_lo: .byte $10, $E8, $64, $0A, $01
pr_pow_hi: .byte $27, $03, $00, $00, $00
pr_true_text: .byte "TRUE"
pr_false_text: .byte "FALSE"

.segment "BSS"
pr_len: .res 1
pr_val: .res 2
pr_pow: .res 2
pr_digit: .res 1
pr_started: .res 1
pr_sx: .res 1
pr_sy: .res 1

+ 633
- 0
newLang/tests/codegen/control-test.s Прегледај датотеку

@@ -0,0 +1,633 @@
; E04-S04: control-flow emitter fixture in x128.
;
; The fixture generates the same routine twice with flow.s: first in counting
; mode (a sizing pass that writes no bytes), then in emitting mode. Both
; passes must produce identical label offsets and total lengths. The emitted
; routine is then executed and must compute:
;
; a = 0; while a < 5: a = a + 1 -> a = 5
; if a = 5 then path = 10 else path = 20 -> path = 10
; for i = 1 to 3: sum = sum + i -> sum = 16, i = 4
; sum = add(sum) -> sum = 17 (call + return)
; return sum -> A = 17
;
; A separate routine takes a long conditional branch (distance >= 128) over
; 125 bytes of filler and must reach its target. The fixture also checks the
; five-byte branch encoding: short form has the distance byte plus three NOPs,
; long form has +3 and a live JMP.
;
; Mailbox data:
; +0 program result (17)
; +1 a (5)
; +2 if/else path marker (10)
; +3 i after the for loop (4)
; +4 long-branch result (5A)
; +5 long-branch wrong-path marker (0)
; +6 label/length mismatches between the sizing and emission passes (0)
; +7 branch-form bits: 1 short form verified, 2 long form verified (3)
; Failure bits: 1 sizing, 2 program result, 4 if/else, 8 for loop,
; 16 call/return, 32 long branch, 64 branch bytes, 128 emitter error.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/codegen.inc"
.include "../../src/common/flow.inc"
.include "../../src/common/frames.inc"

.import cg_init, cg_len, cg_err, cg_code, cg_here
.import cg_lda_imm, cg_ldx_imm, cg_lda_abs, cg_sta_abs
.import cg_adc_abs, cg_cmp_abs, cg_jsr_abs
.import cg_clc, cg_sec, cg_rts
.import fl_init, fl_label, fl_jmp, fl_branch, fl_finish, fl_get_label
.import fl_branch_true, fl_call_byte, fl_prologue, fl_ret_byte
.import fl_err, fl_enter, fl_leave, fl_discard
.import frame_init, frame_enter, frame_leave, frame_discard, frame_push_word

; Label ids (reset by every fl_init).
L_WHILE = 0
L_ENDWHILE = 1
L_ELSE = 2
L_ENDIF = 3
L_FOR = 4
L_ENDFOR = 5
L_ERR = 6
L_LONG = 7
LABEL_IDS = 7 ; ids 0..6 belong to the main routine

.macro EMIT_LDA addr
lda #<(addr)
ldx #>(addr)
jsr cg_lda_abs
.endmacro

.macro EMIT_STA addr
lda #<(addr)
ldx #>(addr)
jsr cg_sta_abs
.endmacro

.macro EMIT_ADC addr
lda #<(addr)
ldx #>(addr)
jsr cg_adc_abs
.endmacro

.macro EMIT_CMP addr
lda #<(addr)
ldx #>(addr)
jsr cg_cmp_abs
.endmacro

.macro EMIT_LDA_IMM value
lda #value
jsr cg_lda_imm
.endmacro

.macro EMIT_LDX_IMM value
lda #value
jsr cg_ldx_imm
.endmacro

.macro EMIT_JSR addr
lda #<(addr)
ldx #>(addr)
jsr cg_jsr_abs
.endmacro

; Branch to a global error label even when it is out of relative range.
.macro ON_CARRY target
bcc :+
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_PROGRAM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS
lda #$00
sta fail_mask
sta mismatch
sta form_mask
sta MB_CODE
sta MB_CODE+1
jsr init_vars

; Runtime addresses for the emitted prologue/call/epilogue.
lda #<frame_enter
sta fl_enter
lda #>frame_enter
sta fl_enter+1
lda #<frame_leave
sta fl_leave
lda #>frame_leave
sta fl_leave+1
lda #<frame_discard
sta fl_discard
lda #>frame_discard
sta fl_discard+1

lda MMU_CR
sta saved_cr
lda #CR_RAM0_KERNAL
sta MMU_CR

; ------------------------------------------- main routine: sizing pass
lda #FL_MODE_COUNT
jsr fl_init
jsr gen_main
ON_CARRY emitter_error
jsr fl_finish
ON_CARRY emitter_error
lda cg_len
sta main_size
jsr save_labels

; ------------------------------------------ main routine: emit pass 2
jsr frame_init
lda #FL_MODE_EMIT
jsr fl_init
jsr gen_main
ON_CARRY emitter_error
jsr fl_finish
ON_CARRY emitter_error
lda cg_len
cmp main_size
beq @len_ok
inc mismatch
lda #$01
jsr fail
@len_ok:
jsr compare_labels

; short branch: distance byte plus three NOPs
ldy pos_while
lda cg_code,y
cmp #128
bcs @short_bad
iny
lda cg_code,y
cmp #$EA
bne @short_bad
iny
lda cg_code,y
cmp #$EA
bne @short_bad
iny
lda cg_code,y
cmp #$EA
bne @short_bad
lda #$01
sta form_mask
jmp @run_main
@short_bad:
lda #$40
jsr fail

; ------------------------------------------------ execute the routine
@run_main:
jsr frame_init
lda #$00
jsr run_code
sta prog_result

lda prog_result
cmp #17
beq @check_a
lda #$02
jsr fail
@check_a:
lda v_a
cmp #5
beq @check_if
lda #$04
jsr fail
@check_if:
lda v_ifpath
cmp #10
beq @check_for
lda #$04
jsr fail
@check_for:
lda v_i
cmp #4
beq @check_call
lda #$08
jsr fail
@check_call:
lda v_sum
cmp #17
beq @long_begin
lda #$10
jsr fail

; ------------------------------- long branch routine: sizing + emit
@long_begin:
lda #FL_MODE_COUNT
jsr fl_init
jsr gen_long
ON_CARRY emitter_error
jsr fl_finish
ON_CARRY emitter_error
lda cg_len
sta long_size

lda #FL_MODE_EMIT
jsr fl_init
jsr gen_long
ON_CARRY emitter_error
jsr fl_finish
ON_CARRY emitter_error
lda cg_len
cmp long_size
beq @long_len_ok
inc mismatch
lda #$01
jsr fail
@long_len_ok:
; long form: +3 then JMP, and the target is at least 128 bytes away
ldy pos_long
lda cg_code,y
cmp #$03
bne @long_bad
iny
lda cg_code,y
cmp #$4C
bne @long_bad
ldy pos_long
dey
lda cg_code,y
cmp #$F0 ; BNE was inverted to BEQ
bne @long_bad
lda #L_LONG
jsr fl_get_label
bcs @long_bad
txa
sec
sbc pos_long
sec
sbc #$01
cmp #128
bcc @long_bad
lda form_mask
ora #$02
sta form_mask
jmp @run_long
@long_bad:
lda #$40
jsr fail

@run_long:
lda #$00
jsr run_code
lda v_long
cmp #$5A
beq @check_bad
lda #$20
jsr fail
@check_bad:
lda v_bad
beq report
lda #$20
jsr fail

jmp report

; ---------------------------------------------------------- report
report:
lda saved_cr
sta MMU_CR
lda prog_result
sta MB_DATA+0
lda v_a
sta MB_DATA+1
lda v_ifpath
sta MB_DATA+2
lda v_i
sta MB_DATA+3
lda v_long
sta MB_DATA+4
lda v_bad
sta MB_DATA+5
lda mismatch
sta MB_DATA+6
lda form_mask
sta MB_DATA+7
lda fail_mask
sta MB_CODE
lda #$00
sta MB_CODE+1
lda #MB_ST_FAIL
ldx fail_mask
bne @status
lda #MB_ST_PASS
@status:
sta MB_STATUS
ldy #$00
@print: lda message,y
beq @end
sty print_y
jsr CHROUT
ldy print_y
iny
bne @print
@end: plp
rts

emitter_error:
lda #$80
jsr fail
jmp report

; ------------------------------------------------------------- generators
; if/else/while/for, one call, and a value return.
gen_main:
lda #$00 ; prologue: no locals
ldx #$00
ldy #L_ERR
jsr fl_prologue
ON_CARRY gen_out

EMIT_LDA_IMM $00 ; a = 0
EMIT_STA v_a
EMIT_LDA_IMM $00 ; sum = 0
EMIT_STA v_sum

lda #L_WHILE ; while a < 5
jsr fl_label
EMIT_LDA v_a
EMIT_CMP v_five
jsr cg_here ; the exit branch's distance byte
clc
adc #$01
sta pos_while
lda #$B0 ; BCS: a >= 5 leaves the loop
ldx #L_ENDWHILE
jsr fl_branch
ON_CARRY gen_out
EMIT_LDA v_a ; a = a + 1
jsr cg_clc
EMIT_ADC v_one
EMIT_STA v_a
lda #L_WHILE ; back edge: absolute jump
jsr fl_jmp
ON_CARRY gen_out
lda #L_ENDWHILE
jsr fl_label

EMIT_LDA v_a ; if a = 5
EMIT_CMP v_five
lda #$D0 ; BNE: else
ldx #L_ELSE
jsr fl_branch
ON_CARRY gen_out
EMIT_LDA_IMM 10
EMIT_STA v_ifpath
EMIT_STA v_sum
lda #L_ENDIF
jsr fl_jmp
ON_CARRY gen_out
lda #L_ELSE
jsr fl_label
EMIT_LDA_IMM 20
EMIT_STA v_ifpath
EMIT_STA v_sum
lda #L_ENDIF
jsr fl_label

EMIT_LDA_IMM 1 ; for i = 1 to 3
EMIT_STA v_i
lda #L_FOR
jsr fl_label
EMIT_LDA v_i
EMIT_CMP v_four
lda #$B0 ; BCS: i >= 4 leaves the loop
ldx #L_ENDFOR
jsr fl_branch
ON_CARRY gen_out
EMIT_LDA v_sum ; sum = sum + i
jsr cg_clc
EMIT_ADC v_i
EMIT_STA v_sum
EMIT_LDA v_i ; i = i + 1
jsr cg_clc
EMIT_ADC v_one
EMIT_STA v_i
lda #L_FOR
jsr fl_jmp
ON_CARRY gen_out
lda #L_ENDFOR
jsr fl_label

EMIT_LDA v_sum ; push(sum) for add()
EMIT_LDX_IMM 0
EMIT_JSR frame_push_word
ON_CARRY gen_out
lda #$B0 ; push refused a frame
ldx #L_ERR
jsr fl_branch
ON_CARRY gen_out
lda #<add ; sum = add(sum)
ldx #>add
ldy #2
jsr fl_call_byte
ON_CARRY gen_out
EMIT_STA v_sum

EMIT_LDA v_sum ; return sum
jsr fl_ret_byte
ON_CARRY gen_out

lda #L_ERR ; frame or argument-push failure
jsr fl_label
lda #$00
jsr cg_sec
jmp cg_rts

; A long taken branch over 125 bytes of filler.
gen_long:
EMIT_LDA_IMM $00
EMIT_STA v_bad
EMIT_LDA_IMM $01
jsr cg_here ; remember the branch's distance byte
clc
adc #$01
sta pos_long
lda #L_LONG
jsr fl_branch_true
ON_CARRY gen_out
EMIT_LDA_IMM $AA ; wrong path if the branch fails
EMIT_STA v_bad
lda #25 ; X is clobbered by the emit macros
sta fill_count
@fill: EMIT_LDA_IMM $11
EMIT_STA v_filler
dec fill_count
bne @fill
lda #L_LONG
jsr fl_label
EMIT_LDA_IMM $5A
EMIT_STA v_long
jmp cg_rts

; Shared early exit for both generators: C=1 from the failing call.
gen_out:
rts

; ---------------------------------------------------------------- helpers
; A = code-buffer offset. Run the snippet and return its A.
run_code:
clc
adc #<cg_code
sta @target+1
lda #>cg_code
adc #$00
sta @target+2
@target:
jsr $FFFF
rts

save_labels:
lda #$00
sta label_id
@loop: lda label_id
cmp #LABEL_IDS
beq @done
jsr fl_get_label
bcs @missing
ldy label_id
txa
sta saved_labels,y
inc label_id
jmp @loop
@missing:
inc mismatch
lda #$01
jsr fail
inc label_id
jmp @loop
@done: rts

compare_labels:
lda #$00
sta label_id
@loop: lda label_id
cmp #LABEL_IDS
beq @done
jsr fl_get_label
bcs @bad
ldy label_id
txa
cmp saved_labels,y
beq @next
@bad: inc mismatch
lda #$01
jsr fail
@next: inc label_id
jmp @loop
@done: rts

init_vars:
lda #$01
sta v_one
lda #$03
sta v_three
lda #$04
sta v_four
lda #$05
sta v_five
lda #$00
sta v_a
sta v_sum
sta v_i
sta v_ifpath
sta v_bad
sta v_long
sta v_filler
sta prog_result
rts

; add(value as word) as byte: returns value + 1.
add:
lda #$00
ldx #$00
jsr frame_enter
bcs @fault
ldy #2
lda (FRAME_FP),y
clc
adc #$01
sta add_result
jsr frame_leave
lda add_result
clc
rts
@fault: lda #$00
sec
rts

.segment "CODE"
fail:
ora fail_mask
sta fail_mask
rts

.segment "RODATA"
message: .byte "NOVA128 CONTROL TEST DONE", $0D, $00

.segment "BSS"
fail_mask: .res 1
print_y: .res 1
saved_cr: .res 1
label_id: .res 1
mismatch: .res 1
form_mask: .res 1
main_size: .res 1
long_size: .res 1
pos_while: .res 1
pos_long: .res 1
prog_result: .res 1
v_one: .res 1
v_three: .res 1
v_four: .res 1
v_five: .res 1
v_a: .res 1
v_sum: .res 1
v_i: .res 1
v_ifpath: .res 1
v_bad: .res 1
v_long: .res 1
v_filler: .res 1
add_result: .res 1
saved_labels: .res LABEL_IDS
fill_count: .res 1

+ 265
- 0
newLang/tests/compiler/hello-test.s Прегледај датотеку

@@ -0,0 +1,265 @@
; E04-S05: compile hello.n8 and emit a runnable PRG (x128).
;
; The fixture runs in the compiler mapping, loads HELLO.N8, parses and
; type-checks it normally, and reads the single string print captured during
; pass 2. It then writes HELLO.PRG with output.s: the standard BASIC stub at
; $1C01 and a body at $1C0D that saves CR, selects the D129 mapping ($0E),
; prints the captured bytes through CHROUT, restores CR, and returns to BASIC.
;
; This is a bounded source-to-code proof, not the general statement emitter:
; one string print is required and gen_bad rejects anything else.
;
; Mailbox data: +0 diagnostics, +1 print count, +2 gen_bad, +3 text length,
; +4 out_err. FAIL code is the stage: 1 load, 2 diagnostics, 3 unsupported
; print, 4 output.

.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, gen_text, gen_text_len, gen_print_count, gen_bad
.import out_begin, out_section, out_end_section
.import out_byte, out_bytes, out_finish, out_abort
.import out_load, out_entry, out_start, out_err
.import gen_stub

; A conditional branch to a far error path: near branch over an absolute JMP.
.macro ON_CARRY target
bcc :+
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
lda #$00
sta MB_DATA
sta MB_DATA+1
sta MB_DATA+2
sta MB_DATA+3
sta MB_DATA+4
sta MB_DATA+5
sta MB_DATA+6
sta MB_DATA+7

jsr mmu_init
jsr mmu_bank0

lda #<PRG_LOAD_ADDR
sta out_load
lda #>PRG_LOAD_ADDR
sta out_load+1
lda #<PRG_ENTRY_ADDR
sta out_entry
lda #>PRG_ENTRY_ADDR
sta out_entry+1

lda #<name_src
ldx #>name_src
ldy #name_src_len
jsr src_load
cmp #$00
bne @fail_load

jsr names_init
lda #<name_src
ldx #>name_src
ldy #name_src_len
jsr diag_init
lda #$00 ; ERR files only; keep the screen quiet
sta diag_screen
jsr parse_file
jsr diag_finish
sta diag_count

lda diag_count
sta MB_DATA+0
lda gen_print_count
sta MB_DATA+1
lda gen_bad
sta MB_DATA+2
lda gen_text_len
sta MB_DATA+3

lda diag_count
bne @fail_diag
lda gen_bad
bne @fail_gen
lda gen_print_count
cmp #1
bne @fail_gen
lda gen_text_len
beq @fail_gen

jsr emit_hello
bcc @ok
lda emit_err
sta MB_DATA+4
lda #4
jmp @fail
@ok: lda out_err
sta MB_DATA+4
lda #MB_ST_PASS
sta MB_STATUS
jmp @done
@fail_load:
sta MB_DATA+5 ; src_load result
lda src_dos_code
sta MB_DATA+6
lda src_kernal_err
sta MB_DATA+7
lda #1
jmp @fail
@fail_diag:
lda #2
jmp @fail
@fail_gen:
lda #3
@fail: sta MB_CODE
lda #$00
sta MB_CODE+1
lda #MB_ST_FAIL
sta MB_STATUS
@done: jsr mmu_shutdown
plp
rts

; Write HELLO.PRG through output.s. C=1 on any output error.
emit_hello:
lda #<name_hello
ldx #>name_hello
ldy #name_hello_len
jsr out_begin
ON_CARRY @abort

lda #<PRG_LOAD_ADDR ; BASIC stub section
sta out_start
lda #>PRG_LOAD_ADDR
sta out_start+1
lda #<sec_stub
ldx #>sec_stub
ldy #4
jsr out_section
ON_CARRY @abort
lda #<gen_stub
ldx #>gen_stub
ldy #PRG_STUB_SIZE
jsr out_bytes
ON_CARRY @abort
jsr out_end_section
ON_CARRY @abort

lda #<PRG_ENTRY_ADDR ; generated body
sta out_start
lda #>PRG_ENTRY_ADDR
sta out_start+1
lda #<sec_code
ldx #>sec_code
ldy #4
jsr out_section
ON_CARRY @abort
lda #<prologue
ldx #>prologue
ldy #prologue_len
jsr out_bytes
ON_CARRY @abort

ldy #$00
@chars: cpy gen_text_len
beq @epilogue
sty temp_y
lda #$A9 ; LDA #char
jsr out_byte
ON_CARRY @abort
ldy temp_y
lda gen_text,y
jsr out_byte
ON_CARRY @abort
lda #$20 ; JSR $FFD2 (CHROUT)
jsr out_byte
ON_CARRY @abort
lda #$D2
jsr out_byte
ON_CARRY @abort
lda #$FF
jsr out_byte
ON_CARRY @abort
ldy temp_y
iny
bne @chars

@epilogue:
lda #<epilogue
ldx #>epilogue
ldy #epilogue_len
jsr out_bytes
ON_CARRY @abort
jsr out_end_section
ON_CARRY @abort
jsr out_finish
ON_CARRY @abort
clc
rts
@abort: lda out_err
sta emit_err
jsr out_abort
sec
rts

.segment "RODATA"
name_src: .byte "HELLO.N8"
name_src_len = * - name_src
name_hello: .byte "HELLO"
name_hello_len = * - name_hello
sec_stub: .byte "STUB"
sec_code: .byte "CODE"

; LDA MMU_CR / PHA / LDA #$0E / STA MMU_CR
prologue:
.byte $AD, $00, $FF, $48, $A9, $0E, $8D, $00, $FF
prologue_len = * - prologue
; PLA / STA MMU_CR / RTS
epilogue:
.byte $68, $8D, $00, $FF, $60
epilogue_len = * - epilogue

.segment "BSS"
diag_count: .res 1
temp_y: .res 1
emit_err: .res 1

+ 4
- 0
newLang/tests/compiler/hello.n8 Прегледај датотеку

@@ -0,0 +1,4 @@
// E04-S05 end-to-end source: one string print.
program hello
print "hello from nova128"
end program

+ 340
- 0
newLang/tests/runtime/class-test.s Прегледај датотеку

@@ -0,0 +1,340 @@
; E05-S02: class descriptor and object-header test in x128.
;
; The fixture checks the packed descriptor ABI and its runtime accessors:
; EMPTY: size 7, no destructor, no interfaces
; BOX: size 35, destructor, one interface-directory entry
; It then writes CLASSTEST.PRG/MAP through the real output writer. MB_DATA
; flags 0..7 report descriptor, size, destructor, interface, and MAP success.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/formats.inc"
.include "../../src/common/class.inc"

.import mmu_init, mmu_bank0, mmu_shutdown
.import out_begin, out_startup, out_finish, out_abort
.import out_load, out_entry, out_err
.import out_class, out_class_size, out_class_destroy
.import out_class_ifaces, out_class_ifdir
.import obj_class, obj_size, obj_destroy, obj_iface_dir

.export destroy_b, iface_dir

.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_PROGRAM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS
lda #$00
sta fail_mask
sta MB_CODE
sta MB_CODE+1
ldx #$07
@flags: sta MB_DATA,x
dex
bpl @flags

jsr mmu_init
jsr mmu_bank0

; ------------------------------------------------ descriptor: EMPTY
lda #<obj_empty
ldx #>obj_empty
jsr obj_class
bcs @p0_fail
cmp #<desc_empty
bne @p0_fail
cpx #>desc_empty
bne @p0_fail
lda #$00 ; null object and null descriptor pointer
ldx #$00
jsr obj_class
bcc @p0_fail
lda #$01
sta MB_DATA+0
jmp @p1
@p0_fail:
lda #$01
jsr fail

@p1: ; ------------------------------------------------------ size: EMPTY
lda #<obj_empty
ldx #>obj_empty
jsr obj_size
bcs @p1_fail
cmp #7
bne @p1_fail
cpx #$00
bne @p1_fail
lda #$01
sta MB_DATA+1
jmp @p2
@p1_fail:
lda #$02
jsr fail

@p2: ; -------------------------------------------------- destructor: EMPTY
lda #<obj_empty
ldx #>obj_empty
jsr obj_destroy
bcs @p2_fail
cmp #$00
bne @p2_fail
cpx #$00
bne @p2_fail
lda #$01
sta MB_DATA+2
jmp @p3
@p2_fail:
lda #$04
jsr fail

@p3: ; ------------------------------------------------- descriptor: BOX
lda #<obj_box
ldx #>obj_box
jsr obj_class
bcs @p3_fail
cmp #<desc_box
bne @p3_fail
cpx #>desc_box
bne @p3_fail
lda #$01
sta MB_DATA+3
jmp @p4
@p3_fail:
lda #$08
jsr fail

@p4: ; ------------------------------------------------------ size: BOX
lda #<obj_box
ldx #>obj_box
jsr obj_size
bcs @p4_fail
cmp #35
bne @p4_fail
cpx #$00
bne @p4_fail
lda #$01
sta MB_DATA+4
jmp @p5
@p4_fail:
lda #$10
jsr fail

@p5: ; ------------------------------------------------- destructor: BOX
lda #<obj_box
ldx #>obj_box
jsr obj_destroy
bcs @p5_fail
cmp #<destroy_b
bne @p5_fail
cpx #>destroy_b
bne @p5_fail
lda #$01
sta MB_DATA+5
jmp @p6
@p5_fail:
lda #$20
jsr fail

@p6: ; ------------------------------------------------ interface directory
lda #<obj_box
ldx #>obj_box
jsr obj_iface_dir
bcs @p6_fail
cmp #<iface_dir
bne @p6_fail
cpx #>iface_dir
bne @p6_fail
cpy #1
bne @p6_fail
lda #$01
sta MB_DATA+6
jmp @p7
@p6_fail:
lda #$40
jsr fail

@p7: ; -------------------------------------------------------- MAP output
lda #<PRG_LOAD_ADDR
sta out_load
lda #>PRG_LOAD_ADDR
sta out_load+1
lda #<PRG_ENTRY_ADDR
sta out_entry
lda #>PRG_ENTRY_ADDR
sta out_entry+1
lda #<name_source
ldx #>name_source
ldy #name_source_len
jsr out_begin
bcs @p7_abort
jsr out_startup
bcs @p7_abort

lda #7 ; EMPTY: size 7, no destructor/interface
sta out_class_size
lda #$00
sta out_class_size+1
sta out_class_destroy
sta out_class_destroy+1
sta out_class_ifaces
sta out_class_ifdir
sta out_class_ifdir+1
lda #<name_empty
ldx #>name_empty
ldy #name_empty_len
jsr out_class
bcs @p7_abort

lda #35 ; BOX: destructor and one interface
sta out_class_size
lda #$00
sta out_class_size+1
lda #<destroy_b
sta out_class_destroy
lda #>destroy_b
sta out_class_destroy+1
lda #1
sta out_class_ifaces
lda #<iface_dir
sta out_class_ifdir
lda #>iface_dir
sta out_class_ifdir+1
lda #<name_box
ldx #>name_box
ldy #name_box_len
jsr out_class
bcs @p7_abort

jsr out_finish
bcs @p7_fail
lda #$01
sta MB_DATA+7
jmp @done
@p7_abort:
lda out_err
sta MB_ADDR
jsr out_abort
@p7_fail:
lda out_err
sta MB_ADDR
lda #$80
jsr fail

@done:
jsr mmu_shutdown
lda fail_mask
sta MB_CODE
lda #$00
sta MB_CODE+1
lda #MB_ST_FAIL
ldx fail_mask
bne @status
lda #MB_ST_PASS
@status:
sta MB_STATUS
lda fail_mask
beq @ok_message
ldy #fail_message - messages
bne @print
@ok_message:
ldy #ok_message - messages
@print: lda messages,y
beq @end
sty print_y
jsr CHROUT
ldy print_y
iny
bne @print
@end: rts

.segment "CODE"
fail:
ora fail_mask
sta fail_mask
rts

destroy_b:
rts

method_stub:
rts

.segment "DATA"
desc_empty:
.word 7
.word 0
.byte 0
.word 0
.byte 0
.assert * - desc_empty = CLASS_DESC_SIZE, error, "EMPTY descriptor size"

desc_box:
.word 35
.word destroy_b
.byte 1
.word iface_dir
.byte 0
.assert * - desc_box = CLASS_DESC_SIZE, error, "BOX descriptor size"

iface_dir:
.word iface_stub
.word iface_table
iface_stub:
.word 0
iface_table:
.word method_stub

obj_empty:
.word desc_empty
.res 5
obj_box:
.word desc_box
.res 33
.assert obj_box - obj_empty = 7, error, "EMPTY object size"
.assert * - obj_box = 35, error, "BOX object size"

.segment "RODATA"
name_source: .byte "CLASSTEST.N8"
name_source_len = * - name_source
name_empty: .byte "EMPTY"
name_empty_len = * - name_empty
name_box: .byte "BOX"
name_box_len = * - name_box
messages:
ok_message: .byte "NOVA128 CLASS TEST OK", $0D, $00
fail_message: .byte "NOVA128 CLASS TEST FAIL", $0D, $00

.segment "BSS"
fail_mask: .res 1
print_y: .res 1

+ 584
- 0
newLang/tests/runtime/heap-test.s Прегледај датотеку

@@ -0,0 +1,584 @@
; E05-S01: free-list heap fixture in x128.
;
; Runs under CR=$0E with the heap at $4000-$4FFF (16 pages). All code and BSS
; stay below $4000. The fixture checks the allocator and records one flag per
; phase in MB_DATA; the final exhaustion check invokes the stable R005 fault
; path, so the mailbox intentionally ends FAIL with code 5.
;
; Expected screen:
; NOVA128 HEAP TEST
; HEAP OK
; RUNTIME FAULT R005

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/print.inc"
.include "../../src/common/faults.inc"
.include "../../src/common/heap.inc"

.import pr_char, pr_cr, pr_text
.import flt_report
.import heap_init, heap_alloc, heap_free
.import heap_base, heap_end, heap_free_head, heap_free_total, heap_error

HEAP_TEST_BASE = $4000
HEAP_TEST_END = $5000
HEAP_TEST_PAGES = 16
HEAP_TEST_SIZE = $1000

.macro TEXT src, len
lda #<src
ldx #>src
ldy #len
jsr pr_text
.endmacro

.macro PHASE number
lda #number
sta MB_CODE
lda #$00
sta MB_CODE+1
.endmacro

.macro FAIL_CS
bcc :+
jmp @fail
:
.endmacro

.macro FAIL_CC
bcs :+
jmp @fail
:
.endmacro

.macro FAIL_NE
beq :+
jmp @fail
:
.endmacro

.macro FAIL_EQ
bne :+
jmp @fail
:
.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 "ZEROPAGE"
tptr: .res 2

.segment "STARTUP"
.export test_entry, test_end
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_PROGRAM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS

lda MMU_CR
sta saved_cr
lda #CR_RAM0_KERNAL
sta MMU_CR

lda #PR_CLEAR
jsr pr_char
TEXT title, 17
jsr pr_cr

; ------------------------------------------------------------- init
PHASE 1
lda #$00
ldx #>HEAP_TEST_BASE
ldy #HEAP_TEST_PAGES
jsr heap_init
FAIL_CS
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda heap_free_head
cmp #$00
FAIL_NE
lda heap_free_head+1
cmp #$40
FAIL_NE
lda HEAP_TEST_BASE
cmp #$00
FAIL_NE
lda HEAP_TEST_BASE+1
cmp #$10
FAIL_NE
lda HEAP_TEST_BASE+2
ora HEAP_TEST_BASE+3
FAIL_NE
lda #$01
sta MB_DATA+0

; ------------------------------------------- one allocation and free
PHASE 2
lda #32
ldx #0
jsr heap_alloc
FAIL_CS
cmp #$04
FAIL_NE
cpx #$40
FAIL_NE
lda heap_free_total
cmp #$DC
FAIL_NE
lda heap_free_total+1
cmp #$0F
FAIL_NE
lda HEAP_TEST_BASE
cmp #$24
FAIL_NE
lda HEAP_TEST_BASE+1
cmp #$80
FAIL_NE
lda HEAP_TEST_BASE+2
cmp #$5A
FAIL_NE
lda HEAP_TEST_BASE+3
cmp #$A5
FAIL_NE
ldy #0
@zero: lda HEAP_TEST_BASE+4,y
FAIL_NE
iny
cpy #32
bne @zero
lda #$A5
ldy #0
@fill: sta HEAP_TEST_BASE+4,y
iny
cpy #32
bne @fill
lda #$04
ldx #$40
jsr heap_free
FAIL_CS
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda HEAP_TEST_BASE
cmp #$00
FAIL_NE
lda HEAP_TEST_BASE+1
cmp #$10
FAIL_NE
lda HEAP_TEST_BASE+2
ora HEAP_TEST_BASE+3
FAIL_NE
lda #$01
sta MB_DATA+1

; ------------------------------------------ split and coalesce twice
PHASE 3
lda #16
ldx #0
jsr heap_alloc
FAIL_CS
cmp #$04
FAIL_NE
cpx #$40
FAIL_NE
lda heap_free_total
cmp #$EC
FAIL_NE
lda heap_free_total+1
cmp #$0F
FAIL_NE
lda HEAP_TEST_BASE
cmp #$14
FAIL_NE
lda HEAP_TEST_BASE+1
cmp #$80
FAIL_NE
lda HEAP_TEST_BASE+$14
cmp #$EC
FAIL_NE
lda HEAP_TEST_BASE+$15
cmp #$0F
FAIL_NE

lda #16
ldx #0
jsr heap_alloc
FAIL_CS
cmp #$18
FAIL_NE
cpx #$40
FAIL_NE
lda heap_free_total
cmp #$D8
FAIL_NE
lda heap_free_total+1
cmp #$0F
FAIL_NE
lda HEAP_TEST_BASE+$28
cmp #$D8
FAIL_NE
lda HEAP_TEST_BASE+$29
cmp #$0F
FAIL_NE

lda #$04
ldx #$40
jsr heap_free
FAIL_CS
lda #$18
ldx #$40
jsr heap_free
FAIL_CS
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda HEAP_TEST_BASE
cmp #$00
FAIL_NE
lda HEAP_TEST_BASE+1
cmp #$10
FAIL_NE
lda HEAP_TEST_BASE+2
ora HEAP_TEST_BASE+3
FAIL_NE
lda #$01
sta MB_DATA+2

; ---------------------------------------------------- stress pass
PHASE 4
lda #0
sta stress_i
@alloc:
lda #64
ldx #0
jsr heap_alloc
FAIL_CS
sta tptr
stx tptr+1
ldx stress_i
lda tptr
sta ptrs,x
lda tptr+1
sta ptrs+1,x
ldy #0
lda tptr
sta (tptr),y
ldy #63
lda #$A5
sta (tptr),y
lda stress_i
clc
adc #2
sta stress_i
cmp #32
bne @alloc

lda #0
sta stress_i
@verify:
ldx stress_i
lda ptrs,x
sta tptr
lda ptrs+1,x
sta tptr+1
ldy #0
lda (tptr),y
cmp tptr
FAIL_NE
ldy #63
lda (tptr),y
cmp #$A5
FAIL_NE
lda stress_i
clc
adc #2
sta stress_i
cmp #32
bne @verify

lda #0
sta stress_i
@free_even:
ldx stress_i
lda ptrs,x
sta tptr
lda ptrs+1,x
sta tptr+1
lda tptr
ldx tptr+1
jsr heap_free
FAIL_CS
lda stress_i
clc
adc #4
sta stress_i
cmp #32
bne @free_even

lda #30
sta stress_i
@free_odd:
ldx stress_i
lda ptrs,x
sta tptr
lda ptrs+1,x
sta tptr+1
lda tptr
ldx tptr+1
jsr heap_free
FAIL_CS
lda stress_i
sec
sbc #4
sta stress_i
bcs @free_odd
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda HEAP_TEST_BASE
cmp #$00
FAIL_NE
lda HEAP_TEST_BASE+1
cmp #$10
FAIL_NE
lda #$01
sta MB_DATA+3

; --------------------------------------------------- double free
PHASE 5
lda #16
ldx #0
jsr heap_alloc
FAIL_CS
lda #$04
ldx #$40
jsr heap_free
FAIL_CS
lda #$04
ldx #$40
jsr heap_free
FAIL_CC
lda heap_error
cmp #HEAP_ERR_DOUBLE
FAIL_NE
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda #$01
sta MB_DATA+4

; ------------------------------------------------ corrupt header
PHASE 6
lda #16
ldx #0
jsr heap_alloc
FAIL_CS
lda #$00
sta HEAP_TEST_BASE+2
sta HEAP_TEST_BASE+3
lda #$04
ldx #$40
jsr heap_free
FAIL_CC
lda heap_error
cmp #HEAP_ERR_CORRUPT
FAIL_NE
lda heap_free_total
cmp #$EC
FAIL_NE
lda heap_free_total+1
cmp #$0F
FAIL_NE
lda #$5A
sta HEAP_TEST_BASE+2
lda #$A5
sta HEAP_TEST_BASE+3
lda #$04
ldx #$40
jsr heap_free
FAIL_CS
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda #$01
sta MB_DATA+5

; ---------------------------------------- exhaustion and integrity
PHASE 7
lda #0
sta stress_i
@ex_alloc:
lda #128
ldx #0
jsr heap_alloc
FAIL_CS
sta tptr
stx tptr+1
ldx stress_i
lda tptr
sta ptrs,x
lda tptr+1
sta ptrs+1,x
ldy #0
lda #$C3
sta (tptr),y
ldy #127
lda #$A5
sta (tptr),y
lda stress_i
clc
adc #2
sta stress_i
cmp #6
bne @ex_alloc
lda heap_free_total
cmp #$74
FAIL_NE
lda heap_free_total+1
cmp #$0E
FAIL_NE

lda #$00
ldx #$10
jsr heap_alloc
FAIL_CC
lda heap_error
cmp #HEAP_ERR_OOM
FAIL_NE
lda heap_free_total
cmp #$74
FAIL_NE
lda heap_free_total+1
cmp #$0E
FAIL_NE

lda #0
sta stress_i
@ex_verify:
ldx stress_i
lda ptrs,x
sta tptr
lda ptrs+1,x
sta tptr+1
ldy #0
lda (tptr),y
cmp #$C3
FAIL_NE
ldy #127
lda (tptr),y
cmp #$A5
FAIL_NE
lda stress_i
clc
adc #2
sta stress_i
cmp #6
bne @ex_verify

lda #0
sta stress_i
@ex_free:
ldx stress_i
lda ptrs,x
sta tptr
lda ptrs+1,x
sta tptr+1
lda tptr
ldx tptr+1
jsr heap_free
FAIL_CS
lda stress_i
clc
adc #2
sta stress_i
cmp #6
bne @ex_free
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda HEAP_TEST_BASE
cmp #$00
FAIL_NE
lda HEAP_TEST_BASE+1
cmp #$10
FAIL_NE
lda #$01
sta MB_DATA+6
lda #$01
sta MB_DATA+7

TEXT heap_ok, 7
jsr pr_cr
lda #FLT_HEAP
ldx #$00
ldy #$40
jsr flt_report
jmp @done

@fail:
lda #MB_ST_FAIL
sta MB_STATUS
@done:
lda saved_cr
sta MMU_CR
plp
rts

.segment "RODATA"
title: .byte "NOVA128 HEAP TEST"
heap_ok: .byte "HEAP OK"

.segment "BSS"
saved_cr: .res 1
stress_i: .res 1
ptrs: .res 32
test_end:

+ 419
- 0
newLang/tests/runtime/object-test.s Прегледај датотеку

@@ -0,0 +1,419 @@
; E05-S03: construction/deletion fixture in x128.
;
; Runs under CR=$0E with a 4K heap at $4000. It checks:
; - object header and zeroed fields, then init called once with two args;
; - delete null is a no-op;
; - delete calls destroy once with self/fields intact and frees the object;
; - failed allocation does not call init;
; - a class without a destructor still frees correctly.
;
; MB_DATA flags 0..4 report the five phases.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/print.inc"
.include "../../src/common/heap.inc"
.include "../../src/common/class.inc"
.include "../../src/common/frames.inc"

.import pr_char, pr_cr, pr_text
.import heap_init, heap_alloc, heap_free, heap_free_total, heap_error
.import frame_init, frame_enter, frame_leave, frame_push_byte, frame_push_word, frame_discard
.import obj_class
.import obj_new, obj_delete, obj_init_ptr, obj_error

HEAP_BASE = $4000
HEAP_PAGES = 16

.macro TEXT src, len
lda #<src
ldx #>src
ldy #len
jsr pr_text
.endmacro

.macro PHASE n
lda #n
sta MB_CODE
lda #$00
sta MB_CODE+1
.endmacro

.macro FAIL_CS
bcc :+
jmp @fail
:
.endmacro

.macro FAIL_CC
bcs :+
jmp @fail
:
.endmacro

.macro FAIL_NE
beq :+
jmp @fail
:
.endmacro

.macro FAIL_EQ
bne :+
jmp @fail
:
.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 "ZEROPAGE"
tptr: .res 2

.segment "STARTUP"
.export test_entry, test_end
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_PROGRAM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS
lda #$00
sta MB_CODE
sta MB_CODE+1

lda MMU_CR
sta saved_cr
lda #CR_RAM0_KERNAL
sta MMU_CR
lda #PR_CLEAR
jsr pr_char
TEXT title, 18
jsr pr_cr

PHASE 1
lda #$00
ldx #>HEAP_BASE
ldy #HEAP_PAGES
jsr heap_init
FAIL_CS
jsr frame_init
lda #$00
sta init_count
sta destroy_count
sta init_error

; Construct Res(value=$1234, tag=$2A).
lda #$2A
jsr frame_push_byte ; right-to-left: tag first
lda #$34
ldx #$12
jsr frame_push_word ; then value
lda #<init_res
sta obj_init_ptr
lda #>init_res
sta obj_init_ptr+1
lda #<desc_res
ldx #>desc_res
jsr obj_new
FAIL_CS
sta new_obj
stx new_obj+1
lda #3
ldx #$00
jsr frame_discard

lda init_error
FAIL_NE
lda init_count
cmp #1
FAIL_NE
lda init_self
cmp new_obj
FAIL_NE
lda init_self+1
cmp new_obj+1
FAIL_NE
lda init_tag
cmp #$2A
FAIL_NE
lda init_word
cmp #$34
FAIL_NE
lda init_word+1
cmp #$12
FAIL_NE
lda init_tag_before
FAIL_NE
lda init_word_before
ora init_word_before+1
FAIL_NE

lda new_obj
ldx new_obj+1
jsr obj_class
FAIL_CS
cmp #<desc_res
FAIL_NE
cpx #>desc_res
FAIL_NE
lda #$01
sta MB_DATA+0

; delete null must not call destroy.
PHASE 2
lda #$00
ldx #$00
jsr obj_delete
FAIL_CS
lda obj_error
cmp #OBJ_OK
FAIL_NE
lda destroy_count
FAIL_NE
lda #$01
sta MB_DATA+1

; Deleting the object calls destroy once, with self and fields intact.
PHASE 3
lda new_obj
ldx new_obj+1
jsr obj_delete
FAIL_CS
lda obj_error
cmp #OBJ_OK
FAIL_NE
lda destroy_count
cmp #1
FAIL_NE
lda destroy_self
cmp new_obj
FAIL_NE
lda destroy_self+1
cmp new_obj+1
FAIL_NE
lda destroy_tag
cmp #$2A
FAIL_NE
lda destroy_word
cmp #$34
FAIL_NE
lda destroy_word+1
cmp #$12
FAIL_NE
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda #$01
sta MB_DATA+2

; Allocation failure must not call init.
PHASE 4
lda #<init_res
sta obj_init_ptr
lda #>init_res
sta obj_init_ptr+1
lda #<desc_huge
ldx #>desc_huge
jsr obj_new
FAIL_CC
lda obj_error
cmp #OBJ_ERR_HEAP
FAIL_NE
lda init_count
cmp #1
FAIL_NE
lda #$01
sta MB_DATA+3

; A class with no destructor still frees its complete object.
PHASE 5
lda #$00
sta obj_init_ptr
sta obj_init_ptr+1
lda #<desc_plain
ldx #>desc_plain
jsr obj_new
FAIL_CS
sta plain_obj
stx plain_obj+1
lda plain_obj
ldx plain_obj+1
jsr obj_delete
FAIL_CS
lda obj_error
cmp #OBJ_OK
FAIL_NE
lda destroy_count
cmp #1
FAIL_NE
lda heap_free_total
cmp #$00
FAIL_NE
lda heap_free_total+1
cmp #$10
FAIL_NE
lda #$01
sta MB_DATA+4

TEXT object_ok, 9
jsr pr_cr
lda #MB_ST_PASS
sta MB_STATUS
lda #$00
sta MB_CODE
sta MB_CODE+1
jmp @restore

@fail:
lda #MB_ST_FAIL
sta MB_STATUS
TEXT object_fail, 11
jsr pr_cr
@restore:
lda saved_cr
sta MMU_CR
plp
rts

; init(value as word, tag as byte), arguments at FP+2..FP+4.
init_res:
sta init_self
stx init_self+1
lda #$00
ldx #$00
jsr frame_enter
bcc @entered
lda #1
sta init_error
rts
@entered:
ldy #2
lda (FRAME_FP),y
sta init_word
iny
lda (FRAME_FP),y
sta init_word+1
iny
lda (FRAME_FP),y
sta init_tag

lda init_self
sta tptr
lda init_self+1
sta tptr+1
ldy #2
lda (tptr),y
sta init_tag_before
ldy #3
lda (tptr),y
sta init_word_before
iny
lda (tptr),y
sta init_word_before+1

ldy #2
lda init_tag
sta (tptr),y
ldy #3
lda init_word
sta (tptr),y
iny
lda init_word+1
sta (tptr),y
inc init_count
jsr frame_leave
clc
rts

; No-argument destructor; it records self and the fields left by init.
destroy_res:
sta destroy_self
stx destroy_self+1
inc destroy_count
lda destroy_self
sta tptr
lda destroy_self+1
sta tptr+1
ldy #2
lda (tptr),y
sta destroy_tag
ldy #3
lda (tptr),y
sta destroy_word
iny
lda (tptr),y
sta destroy_word+1
rts

.segment "DATA"
desc_res:
.word 5 ; header + tag + word
.word destroy_res
.byte 0
.word 0
.byte 0
.assert * - desc_res = CLASS_DESC_SIZE, error, "Res descriptor size"

desc_plain:
.word 2 ; header only
.word 0
.byte 0
.word 0
.byte 0
.assert * - desc_plain = CLASS_DESC_SIZE, error, "plain descriptor size"

desc_huge:
.word $FFFF
.word 0
.byte 0
.word 0
.byte 0
.assert * - desc_huge = CLASS_DESC_SIZE, error, "huge descriptor size"

.segment "RODATA"
title: .byte "NOVA128 OBJECT TEST"
object_ok: .byte "OBJECT OK"
object_fail: .byte "OBJECT FAIL"

.segment "BSS"
saved_cr: .res 1
new_obj: .res 2
plain_obj: .res 2
init_count: .res 1
init_error: .res 1
init_self: .res 2
init_word: .res 2
init_tag: .res 1
init_tag_before: .res 1
init_word_before: .res 2
destroy_count: .res 1
destroy_self: .res 2
destroy_tag: .res 1
destroy_word: .res 2
test_end:

+ 193
- 0
newLang/tests/runtime/print-test.s Прегледај датотеку

@@ -0,0 +1,193 @@
; E04-S05: print and fault runtime test in x128.
;
; Runs in the D129 generated-program mapping (CR=$0E), where the KERNAL and
; I/O are visible and $B800-$BFFF is RAM; all code and BSS stay below $4000.
; The screen is cleared first, then:
;
; NOVA128 PRINT TEST
; TEXT OK
; BYTE 0 7 255
; WORD 0 12345 65535
; INT -1 -32768 32767
; BOOL FALSE TRUE
; CHAR A
; RUNTIME FAULT R001 .. R005
;
; The faults run last, so the mailbox ends in MB_ST_FAIL with code 5 and
; address $1234. The host checks the screen rows and the mailbox fields.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/print.inc"
.include "../../src/common/faults.inc"

.import pr_char, pr_cr, pr_text, pr_byte, pr_word, pr_int, pr_bool
.import flt_report

.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

.macro TEXT text, len
lda #<text
ldx #>text
ldy #len
jsr pr_text
.endmacro

.macro SPACE
lda #PR_SPACE
jsr pr_char
.endmacro

.segment "STARTUP"
.export test_entry, test_end
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_PROGRAM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS

lda MMU_CR
sta saved_cr
lda MMU_RCR
sta saved_rcr
lda #CR_RAM0_KERNAL
sta MMU_CR

lda #PR_CLEAR
jsr pr_char

; ------------------------------------------------------- print tests
TEXT title, 18
jsr pr_cr
TEXT text_ok, 7
jsr pr_cr

TEXT label_byte, 5
lda #0
jsr pr_byte
SPACE
lda #7
jsr pr_byte
SPACE
lda #255
jsr pr_byte
jsr pr_cr

TEXT label_word, 5
lda #0
ldx #0
jsr pr_word
SPACE
lda #<12345
ldx #>12345
jsr pr_word
SPACE
lda #$FF
ldx #$FF
jsr pr_word
jsr pr_cr

TEXT label_int, 4
lda #$FF ; -1
ldx #$FF
jsr pr_int
SPACE
lda #$00 ; -32768
ldx #$80
jsr pr_int
SPACE
lda #$FF ; 32767
ldx #$7F
jsr pr_int
jsr pr_cr

TEXT label_bool, 5
lda #$00
jsr pr_bool
SPACE
lda #$01
jsr pr_bool
jsr pr_cr

TEXT label_char, 5
lda #$41 ; 'A'
jsr pr_char
jsr pr_cr

; ------------------------------------------------------- fault tests
lda #FLT_NULL
ldx #$11
ldy #$22
jsr flt_report
lda #FLT_BOUNDS
ldx #$22
ldy #$33
jsr flt_report
lda #FLT_DIV0
ldx #$33
ldy #$44
jsr flt_report
lda #FLT_STACK
ldx #$44
ldy #$55
jsr flt_report
lda #FLT_HEAP
ldx #$34
ldy #$12
jsr flt_report

; The print and fault calls must not disturb the generated-program map.
lda #$00
sta MB_DATA
lda MMU_CR
cmp #CR_RAM0_KERNAL
bne @map_done
lda MMU_RCR
cmp saved_rcr
bne @map_done
lda #$01
sta MB_DATA
@map_done:
lda saved_cr
sta MMU_CR
plp
rts

.segment "RODATA"
title: .byte "NOVA128 PRINT TEST"
text_ok: .byte "TEXT OK"
label_byte: .byte "BYTE "
label_word: .byte "WORD "
label_int: .byte "INT "
label_bool: .byte "BOOL "
label_char: .byte "CHAR "

.segment "BSS"
saved_cr: .res 1
saved_rcr: .res 1
test_end:

Loading…
Откажи
Сачувај

Powered by TurnKey Linux.