From b790ef268334ec14db0a1fbf4698e3a39d216dcd Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Sun, 8 Nov 2026 21:27:58 -0500 Subject: [PATCH] Add interface conformance and stable slot assignment (E03-S06). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Number interface methods in source order and check each class against every implemented interface, reporting missing, private, mismatched, conflicting, and non-interface implementations at the class declaration. Generated with Codebuff 🤖 Co-Authored-By: Codebuff --- newLang/docs/architecture.md | 12 + newLang/docs/diagnostics.md | 2 + newLang/docs/memory-map.md | 2 +- newLang/planning/claims/E03-S06.md | 122 ++++++ newLang/scripts/build-parser-test.ps1 | 3 + newLang/scripts/demo-write-check.ps1 | 1 + newLang/scripts/verify-parser-test.ps1 | 29 +- newLang/src/common/diagcat.inc | 2 + newLang/src/common/parser.inc | 5 +- newLang/src/compiler/interfaces.s | 452 +++++++++++++++++++++++ newLang/src/compiler/parser.s | 14 +- newLang/tests/parser/parser-test.s | 55 ++- newLang/tests/parser/unknown.expect | 4 +- newLang/tests/semantic/iface_bad.expect | 10 + newLang/tests/semantic/iface_bad.n8 | 58 +++ newLang/tests/semantic/interfaces.expect | 9 + newLang/tests/semantic/interfaces.n8 | 34 ++ 17 files changed, 800 insertions(+), 14 deletions(-) create mode 100644 newLang/planning/claims/E03-S06.md create mode 100644 newLang/src/compiler/interfaces.s create mode 100644 newLang/tests/semantic/iface_bad.expect create mode 100644 newLang/tests/semantic/iface_bad.n8 create mode 100644 newLang/tests/semantic/interfaces.expect create mode 100644 newLang/tests/semantic/interfaces.n8 diff --git a/newLang/docs/architecture.md b/newLang/docs/architecture.md index 7456b50..1eaf7ef 100644 --- a/newLang/docs/architecture.md +++ b/newLang/docs/architecture.md @@ -211,6 +211,18 @@ sizes are 1 for boolean/byte/char, 2 for integer/word/class reference, 4 for an interface reference, and element size times length for an array. A class whose instance would exceed 65535 bytes reports E096 at its declaration line. +E03-S06 adds `interfaces.s`. After the declaration pass it numbers every +interface method in source order (`C_MEM_SLOT`), giving later dispatch tables +a stable slot order, and checks each class against every interface in its +implements list. A satisfying method is public and has the same parameter +count, parameter types, and return type; one method may satisfy identical +requirements from several interfaces. Failures report at the class declaration +line, column 1: E130 for a missing or private method, E131 for a signature +mismatch, E132 when a same-named requirement of another implemented interface +conflicts with the class method's signature, and E133 when an implements entry +names a class or program rather than an interface. An implements entry that +names no declared type is left to pass 2's unknown-name diagnostic. + ### Pass 1 — declarations and layout - tokenize the complete source; diff --git a/newLang/docs/diagnostics.md b/newLang/docs/diagnostics.md index 538ac9b..7535bcc 100644 --- a/newLang/docs/diagnostics.md +++ b/newLang/docs/diagnostics.md @@ -99,6 +99,8 @@ After 20 errors the next one is replaced by E221 and compilation stops. |---|---|---| | E130 | `IFC_METHOD_MISSING` | % IS NOT IMPLEMENTED | | E131 | `IFC_SIGNATURE_MISMATCH` | % DIFFERS FROM INTERFACE | +| E132 | `IFC_SIGNATURE_CONFLICT` | % HAS CONFLICTING SIGNATURES | +| E133 | `IFC_NOT_INTERFACE` | % IS NOT AN INTERFACE | ## Methods and returns (E150-E169) diff --git a/newLang/docs/memory-map.md b/newLang/docs/memory-map.md index a848405..0a0555e 100644 --- a/newLang/docs/memory-map.md +++ b/newLang/docs/memory-map.md @@ -51,7 +51,7 @@ COMMON, DISKBUF, private ZP, SCRATCH, and MAILBOX are visible from bank 1. | Owner | Bytes | Contents | |---|---|---| | `names.s` | 5,164 | 64 hash-chain heads + 255 name records of 20 bytes (D120) | -| `parser.s` | 6,656 | 26 columns of 256 bytes (`src/common/parser.inc`): type table by name ID (kind, declaration line, member and implements ranges, instance size; E03-S01, E03-S05), 255 member rows (owner, name, flags, type, parameter range, field offset) and 255 parameter-type rows (E03-S02, E03-S05) | +| `parser.s` | 6,912 | 27 columns of 256 bytes (`src/common/parser.inc`): type table by name ID (kind, declaration line, member and implements ranges, instance size; E03-S01, E03-S05), 255 member rows (owner, name, flags, type, parameter range, field offset, interface slot; E03-S02, E03-S05, E03-S06) and 255 parameter-type rows | BSS is not zeroed at load: every table must be cleared by its owner before use (`names_init`, `parser.s` `tt_clear`). The member and parameter rows are diff --git a/newLang/planning/claims/E03-S06.md b/newLang/planning/claims/E03-S06.md new file mode 100644 index 0000000..33e854b --- /dev/null +++ b/newLang/planning/claims/E03-S06.md @@ -0,0 +1,122 @@ +--- +story: E03-S06 +owner: e03_iface +status: done +started: 2026-11-09 +depends-on: [E03-S02, E03-S04] +--- + +# E03-S06 Claim + +## Intended change + +Add `src/compiler/interfaces.s`, which runs once after the declaration pass. +It numbers each interface method in source order in a new `C_MEM_SLOT` member +column, then checks every class against every interface in its implements +list. A satisfying method is public and has the same parameter count, +parameter types, and return type; one method may satisfy identical +requirements from several interfaces. Missing and private methods report +E130, signature mismatches E131, a class method that matches a same-named +requirement of another implemented interface E132, and an implements entry +that names a class or program E133. All are reported at the class declaration +line, column 1. The parser harness emits an `$F9` slot record so a fixture can +verify source order. + +## File area + +- `src/compiler/interfaces.s` (new), `src/compiler/parser.s` +- `src/common/parser.inc`, `src/common/diagcat.inc`, `docs/diagnostics.md` +- `tests/parser/parser-test.s`, `scripts/build-parser-test.ps1`, + `scripts/verify-parser-test.ps1`, `scripts/demo-write-check.ps1` +- `tests/semantic/interfaces.n8`, `interfaces.expect`, `iface_bad.n8`, + `iface_bad.expect`, `tests/parser/unknown.expect` +- `docs/architecture.md`, `docs/memory-map.md` +- `planning/claims/E03-S06.md` +- generated files under `build/` + +## Acceptance evidence + +- [x] Interface method slots follow interface source order: + `interfaces.n8` declares Shape (`area`, `scale`, `name`), Named (`label`, + `show`), and Display (`show`, declared after the class); the harness's + `$F9` record and `interfaces.expect` list them as shape 0/1/2, named 0/1, + display 0. `assign_slots` writes that order into `C_MEM_SLOT`. +- [x] A public exact-signature method satisfies a requirement: Box's five + public methods match all five interface signatures exactly, and + `interfaces.n8` is accepted with no ERR file in the parser run. +- [x] One method may satisfy identical requirements from multiple interfaces: + Box's single public `show()` satisfies both `Named.show()` and + `Display.show()`; no duplicate or conflict diagnostic is produced. +- [x] Same-name incompatible requirements are rejected: `iface_bad.n8` has + Left (`show()`) and Right (`show(x as byte)`) implemented by one class whose + `show()` matches Left; Right reports E132 at 49:1, + `SHOW HAS CONFLICTING SIGNATURES`. +- [x] Missing, private, parameter-mismatched, and return-mismatched methods + each have fixture coverage: `iface_bad.n8` expects E130 at 28:1 (`AREA`), + E130 at 31:1 (`LABEL`, private), E131 at 37:1 (`SCALE`, word vs byte), and + E131 at 43:1 (`OK`, word vs boolean). E133 at 54:1 covers a class used as an + interface. All six exact diagnostics match their messages. + +## Coordination notes + +- No other claim is active. E03-S05 is complete (`42a26c9`). +- Conformance legitimately adds E130 to `tests/parser/unknown.n8` (Worker + implements Printable without `print`); its exact expectation gained that + first pass-1 line and still passes with six diagnostics. +- E132 and E133 extend the approved E130-E149 interface category; they do not + change source syntax or the interface ABI. Slot order is spec 5.2's source + order, and C_MEM_SLOT is parser metadata for later dispatch/MAP work. + +## Work log + +- `interfaces.s`: `assign_slots` numbers interface methods by declaration; + `check_interfaces` walks classes and implements lists in source order and + reports at the class line. `find_method` ignores fields; a private method is + treated as not implemented. `same_sig` compares result type, parameter count, + and all parameter types. `conflicting` decides E132 by looking for a + same-named requirement in another implemented interface whose signature the + class method does satisfy. `iface_err` spells the argument from `name_out` + and reports through `diag_report`, returning carry when the error limit + stops compilation. +- `parser.s` exports `tb_set`, calls `assign_slots` then `check_interfaces` + after `layout_classes`, and returns at once if the diagnostic limit is hit. +- `parser.inc`: `C_MEM_SLOT = 26`, `TABLE_COLUMNS = 27`; the parser tables are + now 6,912 bytes in `docs/memory-map.md`. +- `parser-test.s`: `dump_ifaces` emits `$F9`, each interface name, member + count, and each slot/name pair. Fixtures 40-41 added; `FIXTURE_COUNT = 41`. +- `verify-parser-test.ps1`: decodes `$F9` before `$FA`, stops the type and + member loops at it, treats the new `iface` group like an accepted fixture + for diagnostics/member checks, and compares slot lines to + `interfaces.expect`. +- `diagcat.inc`: E132 `% HAS CONFLICTING SIGNATURES`, E133 + `% IS NOT AN INTERFACE`; catalog is 70 entries and docs are regenerated. +- First build failed on seven long `bcs @stop`/`beq @inext` branches in the + 6502 code; each became a short branch plus `jmp` trampoline. + +## Completion + +- Tests run: `scripts/build-parser-test.ps1`, + `scripts/verify-parser-test.ps1 -Port 6530`, + `scripts/build-diag-test.ps1 -UpdateDocs`, + `scripts/verify-diag-test.ps1 -Port 6530`, + `scripts/verify-lexer-test.ps1 -Port 6530`, + `scripts/demo-write-check.ps1` (good.n8 and bad.n8), + `scripts/demo-write-check.ps1 -Programs bad.n8`. +- Results: all 41 parser fixtures pass (lowest expression SP `$A7`); + `interfaces.n8` reports 6 interface slots and 11 members that match the + source oracle, and `ifacebad.n8` matches all six exact diagnostics. The + diagnostics catalog has 70 entries and its screen/ERR expectations and docs + are in sync. The lexer still matches 10 fixtures / 6,733 bytes. The demo + rebuilt and linked `interfaces.s`; good.n8 completed its Vim save and C128 + check in the full run, and the bad.n8 rerun saved 258 PETSCII bytes + identically and reported E151, E152, E120, and E061. +- Files changed: as listed under File area. +- Remaining risks: conformance positions are class-level (line, column 1) + because member rows do not store a source column; the message names the + method. E132 is anchored on a class method that matches another requirement; + a class with no method at all still rejects each interface with E130. An + implements entry naming no declared type is left to pass 2's unknown-name + diagnostic, so E133 only covers declared classes/programs. The VICE verifier + defaults on this machine (6532/6534/6535) can produce an empty monitor reply; + all runs used `-Port 6530`. Interface dispatch tables and their MAP output + remain E04/E05 work; C_MEM_SLOT is the stable input for them. diff --git a/newLang/scripts/build-parser-test.ps1 b/newLang/scripts/build-parser-test.ps1 index fa38a27..6f095ec 100644 --- a/newLang/scripts/build-parser-test.ps1 +++ b/newLang/scripts/build-parser-test.ps1 @@ -53,6 +53,8 @@ $script:ParserFixtures = [ordered]@{ "calls.n8" = @("$t\calls.n8", "exact", "$t\calls.expect") "layout.n8" = @("$t\layout.n8", "layout", "$t\layout.expect") "toobig.n8" = @("$t\too_big.n8", "rejected", "$t\too_big.expect") + "interfaces.n8" = @("$t\interfaces.n8", "iface", "$t\interfaces.expect") + "ifacebad.n8" = @("$t\iface_bad.n8", "exact", "$t\iface_bad.expect") } $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` @@ -64,6 +66,7 @@ $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` (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 "tests\parser\parser-test.s")) if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" } diff --git a/newLang/scripts/demo-write-check.ps1 b/newLang/scripts/demo-write-check.ps1 index c4bac39..53ec2ee 100644 --- a/newLang/scripts/demo-write-check.ps1 +++ b/newLang/scripts/demo-write-check.ps1 @@ -39,6 +39,7 @@ $link = Invoke-Nova128Link -Name "n8check" -BuildDir $out ` (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 "demo\n8check.s")) diff --git a/newLang/scripts/verify-parser-test.ps1 b/newLang/scripts/verify-parser-test.ps1 index da2a43c..f4e45cf 100644 --- a/newLang/scripts/verify-parser-test.ps1 +++ b/newLang/scripts/verify-parser-test.ps1 @@ -53,9 +53,9 @@ $records = @(); $script:p = 0 while ($dump[$p] -ne 0xFE) { if ($dump[$p] -ne 0xFF) { throw "dump out of sync at $p" } $r = [pscustomobject]@{ Index = $dump[$p + 1]; Load = $dump[$p + 2]; Count = $dump[$p + 3] - MinSp = $dump[$p + 4]; Types = @(); Members = @(); Layout = @(); Trace = $null } + MinSp = $dump[$p + 4]; Types = @(); Members = @(); Ifaces = @(); Layout = @(); Trace = $null } $p += 5 - while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFC -and $dump[$p] -ne 0xFB -and $dump[$p] -ne 0xFA) { + while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFC -and $dump[$p] -ne 0xFB -and $dump[$p] -ne 0xFA -and $dump[$p] -ne 0xF9) { $len = $dump[$p + 3] $spell = ConvertFrom-Spelling $dump[($p + 4)..($p + 3 + $len)] $r.Types += ("{0} {1} {2}" -f @("", "interface", "class", "program")[$dump[$p]], $spell, ($dump[$p + 1] + 256 * $dump[$p + 2])) @@ -63,7 +63,7 @@ while ($dump[$p] -ne 0xFE) { } if ($dump[$p] -eq 0xFC) { $p++ - while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFB -and $dump[$p] -ne 0xFA) { + while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFB -and $dump[$p] -ne 0xFA -and $dump[$p] -ne 0xF9) { $owner = Read-DumpName $name = Read-DumpName $flags = $dump[$p]; $p++ @@ -74,6 +74,18 @@ while ($dump[$p] -ne 0xFE) { $(if ($flags -band 1) { "method" } else { "field" }), $(if ($flags -band 2) { "private" } else { "public" }), $type, ($params -join ",")) } + if ($dump[$p] -eq 0xF9) { + $p++ + while ($dump[$p] -ne 0xFA -and $dump[$p] -ne 0xFB -and $dump[$p] -ne 0xFD) { + $owner = Read-DumpName + $count = $dump[$p]; $p++ + for ($k = 0; $k -lt $count; $k++) { + $slot = $dump[$p]; $p++ + $name = Read-DumpName + $r.Ifaces += ("{0} slot {1} {2}" -f $owner, $slot, $name) + } + } + } if ($dump[$p] -eq 0xFA) { $p++ while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFB) { @@ -195,7 +207,7 @@ foreach ($name in $ParserFixtures.Keys) { $problems = @() if ($r.Load -ne 0) { $problems += "src_load returned $($r.Load)" } switch ($group) { - { $_ -eq "accepted" -or $_ -eq "layout" } { + { $_ -eq "accepted" -or $_ -eq "layout" -or $_ -eq "iface" } { if ($r.Count -ne 0) { $problems += "$($r.Count) diagnostic(s): $($errLines -join ' | ')" } if ($null -ne $errLines) { $problems += "an ERR file exists" } $want = @() @@ -245,7 +257,7 @@ foreach ($name in $ParserFixtures.Keys) { $detail = "{0} diagnostics" -f $got.Count } } - if ($group -in @("accepted", "layout") -or $memberChecked -contains $name) { + if ($group -in @("accepted", "layout", "iface") -or $memberChecked -contains $name) { $wantMembers = Get-SourceMembers $src if (($r.Members -join ';') -ne ($wantMembers -join ';')) { $problems += "members [$($r.Members -join '; ')] expected [$($wantMembers -join '; ')]" @@ -270,6 +282,13 @@ foreach ($name in $ParserFixtures.Keys) { } $detail += ", {0} layout lines" -f $r.Layout.Count } + if ($group -eq "iface") { + $want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") }) + if (($r.Ifaces -join ';') -ne ($want -join ';')) { + $problems += "slots [$($r.Ifaces -join '; ')] expected [$($want -join '; ')]" + } + $detail += ", {0} interface slots" -f $r.Ifaces.Count + } if ($r.MinSp -ne 0xFF -and $r.MinSp -lt $minSp) { $minSp = $r.MinSp } $status = if ($problems) { "FAIL" } else { "ok" } Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail) diff --git a/newLang/src/common/diagcat.inc b/newLang/src/common/diagcat.inc index 1bbc50f..a1e940c 100644 --- a/newLang/src/common/diagcat.inc +++ b/newLang/src/common/diagcat.inc @@ -82,6 +82,8 @@ ; ------------------------------------------------------------- interfaces DIAGNOSTIC IFC_METHOD_MISSING, 130, "% IS NOT IMPLEMENTED" DIAGNOSTIC IFC_SIGNATURE_MISMATCH, 131, "% DIFFERS FROM INTERFACE" + DIAGNOSTIC IFC_SIGNATURE_CONFLICT, 132, "% HAS CONFLICTING SIGNATURES" + DIAGNOSTIC IFC_NOT_INTERFACE, 133, "% IS NOT AN INTERFACE" ; ---------------------------------------------------- methods and returns DIAGNOSTIC SEM_MISSING_RETURN, 150, "MISSING RETURN VALUE" diff --git a/newLang/src/common/parser.inc b/newLang/src/common/parser.inc index acf23bf..e4e893d 100644 --- a/newLang/src/common/parser.inc +++ b/newLang/src/common/parser.inc @@ -1,4 +1,4 @@ -; parser.inc - parser tables and limits (E03-S01..E03-S04). +; parser.inc - parser tables and limits (E03-S01..E03-S06). .ifndef PARSER_INC PARSER_INC = 1 @@ -79,7 +79,8 @@ C_TYPE_SIZE_LO = 22 ; class instance size (descriptor + fields) C_TYPE_SIZE_HI = 23 C_MEM_OFFSET_LO = 24 ; field byte offset C_MEM_OFFSET_HI = 25 -TABLE_COLUMNS = 26 +C_MEM_SLOT = 26 ; interface method slot (source order) +TABLE_COLUMNS = 27 C_TYPE_LAST = C_TYPE_INIT ; type columns 0..this are cleared per run .endif diff --git a/newLang/src/compiler/interfaces.s b/newLang/src/compiler/interfaces.s new file mode 100644 index 0000000..21eb0e3 --- /dev/null +++ b/newLang/src/compiler/interfaces.s @@ -0,0 +1,452 @@ +; interfaces.s - interface slot order and class conformance (E03-S06). +; +; Runs once after pass 1, when every interface, class, member, parameter, and +; implements row is recorded: +; assign_slots numbers each interface method in declaration order and +; stores it in C_MEM_SLOT, so later method tables and the MAP +; writer can use a stable slot without re-reading the source; +; check_interfaces +; verifies each class against every interface it implements. +; A satisfying method is public and has the same parameter +; count, parameter types, and return type. One method may +; satisfy identical requirements from several interfaces. +; +; Diagnostics are reported at the class declaration line, column 1: +; E130 no public method with a required name (missing, field, or private) +; E131 a public method with that name differs in its signature +; E132 the differing method matches the same-named requirement of another +; implemented interface, so the interfaces conflict +; E133 an implements entry names a class or program, not an interface +; An implements entry that names no declared type is left to pass 2, which +; already reports the unknown name at the source position. +; +; Everything is read through tb_get/tb_set, so this module runs in the normal +; bank-0 compiler state. + +.setcpu "6502" +.include "../common/errors.inc" +.include "../common/parser.inc" + +.import tb_get, tb_set, names_count +.import diag_report, diag_line, diag_col, diag_arg1, diag_arg1_len +.import name_get, name_out, name_out_len + +.export assign_slots, check_interfaces + +.segment "CODE" + +; ------------------------------------------------------------- slot order +; Every interface method gets its declaration index: 0, 1, ... in source +; order. Interfaces cannot declare fields, so member rows are signatures. +assign_slots: + lda #$01 + sta as_id +@next: lda as_id + cmp names_count + bcc :+ + rts +: ldy as_id + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_INTERFACE + beq :+ + jmp @skip +: ldy as_id + lda #C_TYPE_MFIRST + jsr tb_get + sta as_row + ldy as_id + lda #C_TYPE_MCOUNT + jsr tb_get + sta as_n + lda #$00 + sta as_slot +@slot: lda as_n + beq @skip + ldy as_row + ldx as_slot + lda #C_MEM_SLOT + jsr tb_set + inc as_row + inc as_slot + dec as_n + bne @slot +@skip: inc as_id + bne @next + rts + +; ----------------------------------------------------------- conformance +; For each class in declaration order, walk its implements list in source +; order and each interface's signatures in slot order. +check_interfaces: + lda #$01 + sta cf_class +@class: lda cf_class + cmp names_count + bcc :+ + jmp @done +: ldy cf_class + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_CLASS + beq :+ + jmp @cnext +: ldy cf_class + lda #C_TYPE_ICOUNT + jsr tb_get + bne :+ + jmp @cnext ; no implements list +: sta cf_count + ldy cf_class + lda #C_TYPE_IFIRST + jsr tb_get + sta cf_row + lda #$00 + sta cf_i +@impl: lda cf_i + cmp cf_count + bcc :+ + jmp @cnext +: ldy cf_row + lda #C_IMPL_IFACE + jsr tb_get + sta cf_iface + ldy cf_iface + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_INTERFACE + beq @conform + cmp #$00 + bne :+ ; unknown: pass 2 reports it + jmp @inext +: + ldx cf_iface + lda #E_IFC_NOT_INTERFACE + jsr iface_err + bcc :+ + jmp @stop +: + jmp @inext +@conform: + ldy cf_iface + lda #C_TYPE_MFIRST + jsr tb_get + sta cf_req + ldy cf_iface + lda #C_TYPE_MCOUNT + jsr tb_get + sta cf_n +@req: lda cf_n + bne :+ + jmp @inext +: ldy cf_req + lda #C_MEM_NAME + jsr tb_get + sta cf_name + lda cf_class + ldx cf_name + jsr find_method + bcc @missing + sty cf_method + lda #C_MEM_FLAGS + jsr tb_get + and #MF_PRIVATE + beq @sig + jmp @missing ; private methods do not implement +@sig: lda cf_req + ldx cf_method + jsr same_sig + bcs @satisfied + lda cf_method + ldx cf_name + jsr conflicting + bcs @conflict + ldx cf_name + lda #E_IFC_SIGNATURE_MISMATCH + jsr iface_err + bcc :+ + jmp @stop +: + jmp @satisfied +@conflict: + ldx cf_name + lda #E_IFC_SIGNATURE_CONFLICT + jsr iface_err + bcc :+ + jmp @stop +: + jmp @satisfied +@missing: + ldx cf_name + lda #E_IFC_METHOD_MISSING + jsr iface_err + bcc :+ + jmp @stop +: +@satisfied: + inc cf_req + dec cf_n + jmp @req +@inext: inc cf_row + inc cf_i + jmp @impl +@cnext: inc cf_class + beq @done + jmp @class +@stop: sec + rts +@done: clc + rts + +; A = class ID, X = method name ID. C=1 and Y=row when the class declares a +; method with that name; C=0 otherwise. A field with the name does not count. +find_method: + sta fmc_class + stx fmc_name + ldy fmc_class + lda #C_TYPE_MFIRST + jsr tb_get + sta fmc_row + ldy fmc_class + lda #C_TYPE_MCOUNT + jsr tb_get + sta fmc_n +@row: lda fmc_n + beq @no + ldy fmc_row + lda #C_MEM_FLAGS + jsr tb_get + and #MF_METHOD + beq @next + ldy fmc_row + lda #C_MEM_NAME + jsr tb_get + cmp fmc_name + beq @yes +@next: inc fmc_row + dec fmc_n + bne @row +@no: clc + rts +@yes: ldy fmc_row + sec + rts + +; C=1 when the class method A matches a same-named requirement (X) of an +; implemented interface other than the one being checked. That means the +; interfaces themselves disagree, not that the class method is wrong. +conflicting: + sta cn_method + stx cn_name + ldy cf_class + lda #C_TYPE_IFIRST + jsr tb_get + sta cn_row + ldy cf_class + lda #C_TYPE_ICOUNT + jsr tb_get + sta cn_n +@iface: lda cn_n + beq @no + ldy cn_row + lda #C_IMPL_IFACE + jsr tb_get + sta cn_iface + cmp cf_iface + beq @next_iface + ldy cn_iface + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_INTERFACE + bne @next_iface + ldy cn_iface + lda #C_TYPE_MFIRST + jsr tb_get + sta cn_req + ldy cn_iface + lda #C_TYPE_MCOUNT + jsr tb_get + sta cn_req_n +@req: lda cn_req_n + beq @next_iface + ldy cn_req + lda #C_MEM_NAME + jsr tb_get + cmp cn_name + bne @next_req + lda cn_method + ldx cn_req + jsr same_sig + bcs @yes +@next_req: + inc cn_req + dec cn_req_n + bne @req +@next_iface: + inc cn_row + dec cn_n + bne @iface +@no: clc + rts +@yes: sec + rts + +; C=1 when member rows A and X have equal signatures: result type, parameter +; count, and each parameter type. +same_sig: + sta sg_a + stx sg_b + ldy sg_a + lda #C_MEM_PCOUNT + jsr tb_get + sta sg_n + ldy sg_b + lda #C_MEM_PCOUNT + jsr tb_get + cmp sg_n + bne @no + lda sg_a + ldx sg_b + jsr same_result + bcc @no + ldy sg_a + lda #C_MEM_PFIRST + jsr tb_get + sta sg_pa + ldy sg_b + lda #C_MEM_PFIRST + jsr tb_get + sta sg_pb + lda #$00 + sta sg_i +@param: lda sg_i + cmp sg_n + beq @yes + lda sg_pa + clc + adc sg_i + sta sg_ra + lda sg_pb + clc + adc sg_i + sta sg_rb + lda sg_ra + ldx sg_rb + jsr same_param + bcc @no + inc sg_i + bne @param +@yes: sec + rts +@no: clc + rts + +same_result: + ldy #C_MEM_TBASE + jmp same_cells + +same_param: + ldy #C_PAR_TBASE + jmp same_cells + +; C=1 when four consecutive type columns starting at Y are equal for rows A +; and X (base, name, length lo, length hi). +same_cells: + sta sg_a + stx sg_b + sty sg_col + lda #$00 + sta sg_ci +@col: ldy sg_a + lda sg_col + clc + adc sg_ci + jsr tb_get + sta sg_v + ldy sg_b + lda sg_col + clc + adc sg_ci + jsr tb_get + cmp sg_v + bne @no + inc sg_ci + lda sg_ci + cmp #$04 + bne @col + sec + rts +@no: clc + rts + +; Report A at class cf_class's declaration line, column 1, with name X as +; argument 1. C=1 when the diagnostic limit stopped compilation. +iface_err: + sta ie_code + stx ie_name + ldy cf_class + lda #C_TYPE_LINE_LO + jsr tb_get + sta diag_line + ldy cf_class + lda #C_TYPE_LINE_HI + jsr tb_get + sta diag_line+1 + lda #$01 + sta diag_col + lda ie_name + jsr name_get + lda #name_out + sta diag_arg1+1 + lda name_out_len + sta diag_arg1_len + lda ie_code + jsr diag_report + bcc @ok + sec + rts +@ok: clc + rts + +.segment "BSS" +; slot assignment +as_id: .res 1 +as_row: .res 1 +as_n: .res 1 +as_slot: .res 1 +; conformance +cf_class: .res 1 +cf_iface: .res 1 +cf_count: .res 1 +cf_row: .res 1 +cf_i: .res 1 +cf_req: .res 1 +cf_n: .res 1 +cf_name: .res 1 +cf_method: .res 1 +fmc_class: .res 1 +fmc_name: .res 1 +fmc_row: .res 1 +fmc_n: .res 1 +cn_method: .res 1 +cn_name: .res 1 +cn_row: .res 1 +cn_n: .res 1 +cn_iface: .res 1 +cn_req: .res 1 +cn_req_n: .res 1 +sg_a: .res 1 +sg_b: .res 1 +sg_n: .res 1 +sg_i: .res 1 +sg_pa: .res 1 +sg_pb: .res 1 +sg_col: .res 1 +sg_ci: .res 1 +sg_v: .res 1 +sg_ra: .res 1 +sg_rb: .res 1 +ie_code: .res 1 +ie_name: .res 1 diff --git a/newLang/src/compiler/parser.s b/newLang/src/compiler/parser.s index 91796d1..7f574b2 100644 --- a/newLang/src/compiler/parser.s +++ b/newLang/src/compiler/parser.s @@ -1,5 +1,5 @@ ; parser.s - declarations, members, scopes, statements, expressions, and -; type checking (E03-S01..E03-S05). +; type checking (E03-S01..E03-S06). ; ; parse_file runs two passes over the loaded source (call after src_load, ; diag_init, and names_init): @@ -9,7 +9,8 @@ ; member and parameter tables; parses every statement; reports ; syntax, ordering, duplicate, init/destroy shape, and ; local-placement errors; records each class's implements list, -; init row, instance size, and field offsets (E03-S05). +; init row, instance size, and field offsets (E03-S05); assigns +; interface slots and checks class conformance (E03-S06). ; pass 2 replays the source, parsing everything again, and reports type ; references that do not name a declared interface or class ; (forward references are therefore legal), name, member, and @@ -54,8 +55,9 @@ .import tt_base, tt_name, tt_len .import ty_assign, ty_binary, ty_unary .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 +.import assign_slots, check_interfaces -.export parse_file, tb_get, mem_count, par_count +.export parse_file, tb_get, tb_set, mem_count, par_count .export ex_trace_on, ex_min_sp .exportzp ex_tp .export type_kind, type_line_lo, type_line_hi @@ -99,7 +101,11 @@ parse_file: sta pass jsr parse_pass jsr layout_classes - lda #$02 + jsr assign_slots + jsr check_interfaces + bcc :+ + rts +: lda #$02 sta pass jsr parse_pass rts diff --git a/newLang/tests/parser/parser-test.s b/newLang/tests/parser/parser-test.s index 8dce1d7..3a4a50e 100644 --- a/newLang/tests/parser/parser-test.s +++ b/newLang/tests/parser/parser-test.s @@ -11,6 +11,10 @@ ; per member row: owner name, member name, flags, type, parameter count, ; then each parameter's type. A name is a length and its spelling (length ; 0 = none); a type is base, name, length lo, length hi. +; $F9 +; per interface: name, slot count (member count), then each slot's number +; and method name in source order (E03-S06) +; $FA, per class: name, instance size, field count, fields and offsets ; for the TRACE_INDEX fixture only: $FB, length lo/hi, the expression ; trace (parser.inc TR_*) ; $FD @@ -141,6 +145,7 @@ test_entry: lda #$FC jsr emit jsr dump_members + jsr dump_ifaces jsr dump_layout lda fx_index cmp #TRACE_INDEX @@ -289,6 +294,52 @@ dump_members: jmp @next @done: rts +; Emit $F9, then each interface: name, member count, and each member's slot +; number and method name in declaration order. +dump_ifaces: + lda #$F9 + jsr emit + lda #$01 + sta id +@next: lda id + cmp names_count + bcc :+ + jmp @done +: ldy id + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_INTERFACE + beq :+ + jmp @adv +: lda id + jsr emit_name + ldy id + lda #C_TYPE_MCOUNT + jsr tb_get + sta lk_n + jsr emit + ldy id + lda #C_TYPE_MFIRST + jsr tb_get + sta lk_row +@slot: lda lk_n + beq @adv + ldy lk_row + lda #C_MEM_SLOT + jsr tb_get + jsr emit + ldy lk_row + lda #C_MEM_NAME + jsr tb_get + jsr emit_name + inc lk_row + dec lk_n + jmp @slot +@adv: inc id + beq @done + jmp @next +@done: rts + ; Emit $FA, then each class: name, instance size lo/hi, field count, and ; every field's name and offset lo/hi in declaration order. dump_layout: @@ -519,7 +570,9 @@ fixtures: FIXTURE "CALLS.N8" FIXTURE "LAYOUT.N8" FIXTURE "TOOBIG.N8" -FIXTURE_COUNT = 39 + FIXTURE "INTERFACES.N8" + FIXTURE "IFACEBAD.N8" +FIXTURE_COUNT = 41 TRACE_INDEX = 28 ; PREC.N8 messages: diff --git a/newLang/tests/parser/unknown.expect b/newLang/tests/parser/unknown.expect index 0e03837..99e0c09 100644 --- a/newLang/tests/parser/unknown.expect +++ b/newLang/tests/parser/unknown.expect @@ -1,5 +1,7 @@ // pass 2: Printable and Worker are forward references; the rest are unknown. -// run() declares a result but its body has no return (E150). +// run() declares a result but its body has no return (E150). Worker also does +// not implement Printable.print (E130, pass 1). +IFC_METHOD_MISSING 1 1 PRINT IS NOT IMPLEMENTED SEM_UNKNOWN_NAME 1 25 SEM_UNKNOWN_NAME 2 11 SEM_UNKNOWN_NAME 3 28 diff --git a/newLang/tests/semantic/iface_bad.expect b/newLang/tests/semantic/iface_bad.expect new file mode 100644 index 0000000..2d1b257 --- /dev/null +++ b/newLang/tests/semantic/iface_bad.expect @@ -0,0 +1,10 @@ +// E03-S06: conformance failures are reported at the class declaration line, +// column 1. Missing, private, parameter-mismatched, return-mismatched, +// conflicting same-name requirements, and a class used as an interface. +// SYMBOL line column message (message compared case-insensitively) +IFC_METHOD_MISSING 28 1 AREA IS NOT IMPLEMENTED +IFC_METHOD_MISSING 31 1 LABEL IS NOT IMPLEMENTED +IFC_SIGNATURE_MISMATCH 37 1 SCALE DIFFERS FROM INTERFACE +IFC_SIGNATURE_MISMATCH 43 1 OK DIFFERS FROM INTERFACE +IFC_SIGNATURE_CONFLICT 49 1 SHOW HAS CONFLICTING SIGNATURES +IFC_NOT_INTERFACE 54 1 NOTIFACE IS NOT AN INTERFACE diff --git a/newLang/tests/semantic/iface_bad.n8 b/newLang/tests/semantic/iface_bad.n8 new file mode 100644 index 0000000..994a27a --- /dev/null +++ b/newLang/tests/semantic/iface_bad.n8 @@ -0,0 +1,58 @@ +interface Shape + method area() as word +end interface + +interface Named + method label() as char +end interface + +interface Scaled + method scale(f as byte) as word +end interface + +interface Boolish + method ok() as boolean +end interface + +interface Left + method show() +end interface + +interface Right + method show(x as byte) +end interface + +class NotIface +end class + +class Missing implements Shape +end class + +class Hider implements Named + private method label() as char + return 'h' + end method +end class + +class WrongParam implements Scaled + public method scale(f as word) as word + return f + end method +end class + +class WrongReturn implements Boolish + public method ok() as word + return 0 + end method +end class + +class Conflict implements Left, Right + public method show() + end method +end class + +class BadIface implements NotIface +end class + +program Main +end program diff --git a/newLang/tests/semantic/interfaces.expect b/newLang/tests/semantic/interfaces.expect new file mode 100644 index 0000000..52c2e6a --- /dev/null +++ b/newLang/tests/semantic/interfaces.expect @@ -0,0 +1,9 @@ +// E03-S06: interface slots follow the interface's source declaration order, +// not alphabetical or class order. Box satisfies Named.show and Display.show +// with its single public show() method. +shape slot 0 area +shape slot 1 scale +shape slot 2 name +named slot 0 label +named slot 1 show +display slot 0 show diff --git a/newLang/tests/semantic/interfaces.n8 b/newLang/tests/semantic/interfaces.n8 new file mode 100644 index 0000000..5c768a3 --- /dev/null +++ b/newLang/tests/semantic/interfaces.n8 @@ -0,0 +1,34 @@ +interface Shape + method area() as word + method scale(f as byte) as word + method name() as char +end interface + +interface Named + method label() as char + method show() +end interface + +class Box implements Shape, Named, Display + public method area() as word + return 1 + end method + public method scale(f as byte) as word + return f + end method + public method name() as char + return 'b' + end method + public method label() as char + return 'l' + end method + public method show() + end method +end class + +interface Display + method show() +end interface + +program Main +end program