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 <noreply@codebuff.com>main
| @@ -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; | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -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. | |||
| @@ -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" } | |||
| @@ -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")) | |||
| @@ -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) | |||
| @@ -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" | |||
| @@ -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 | |||
| @@ -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 | |||
| 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 | |||
| @@ -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 | |||
| @@ -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: | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
Powered by TurnKey Linux.