Pass 1 now records every field and method signature (access, type, parameter types) in bank-1 member and parameter tables, rejects duplicate fields or methods per type, invalid init/destroy shapes (E151/E152, D122), and `var` after a method statement or inside a block. Each signature and the program open a typed local scope. Pass 2 scans statement lines: unqualified names must be locals, parameters, or fields of the enclosing class (D123), and member chains through locals, fields, self, new, calls, and indexing report unknown members (E061) and private access (E120) at the member. The parser test PRG grew past $4000 and crashed: mmu_init copies the COMMON load image in the system mapping, where BASIC ROM hides $4000+. COMMON now loads right after STARTUP and mmu.s asserts both stay below $4000. Verified in x128: 28 fixtures, member tables against a source-derived oracle, privacy and scope mutations caught; map, source, lexer, names, and diagnostics suites pass on the new layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
| @@ -39,10 +39,12 @@ SEGMENTS { | |||||
| LOADADDR: load = LOADADDR, type = ro; | LOADADDR: load = LOADADDR, type = ro; | ||||
| EXEHDR: load = HEADER, type = ro; | EXEHDR: load = HEADER, type = ro; | ||||
| STARTUP: load = MAIN, type = ro; # first: SYS 7181 enters at $1C0D | STARTUP: load = MAIN, type = ro; # first: SYS 7181 enters at $1C0D | ||||
| # COMMON's load image comes next so mmu_init, which copies it in the | |||||
| # system mapping (BASIC ROM over $4000-$7FFF), reads it below $4000. | |||||
| COMMON: load = MAIN, run = COMMONRAM, type = rw, define = yes; | |||||
| CODE: load = MAIN, type = ro; | CODE: load = MAIN, type = ro; | ||||
| RODATA: load = MAIN, type = ro, optional = yes; | RODATA: load = MAIN, type = ro, optional = yes; | ||||
| DATA: load = MAIN, type = rw, optional = yes; | DATA: load = MAIN, type = rw, optional = yes; | ||||
| COMMON: load = MAIN, run = COMMONRAM, type = rw, define = yes; | |||||
| BSS: load = MAIN, type = bss, optional = yes, define = yes; | BSS: load = MAIN, type = bss, optional = yes, define = yes; | ||||
| DISKBUF: load = DISKBUF, type = bss, optional = yes; | DISKBUF: load = DISKBUF, type = bss, optional = yes; | ||||
| SCRATCH: load = SCRATCH, type = bss, optional = yes; | SCRATCH: load = SCRATCH, type = bss, optional = yes; | ||||
| @@ -174,6 +174,18 @@ duplicate errors; pass 2 replays the source and reports type references that | |||||
| name no declared interface or class. Statement bodies are skimmed until | name no declared interface or class. Statement bodies are skimmed until | ||||
| E03-S03. | E03-S03. | ||||
| E03-S02 adds members and scopes. Pass 1 records every field and method | |||||
| signature (access, type, parameter types) in bank-1 member and parameter | |||||
| tables, rejects duplicate fields or methods per type (separate namespaces), | |||||
| `init`/`destroy` shapes (D122), and `var` after a method statement or inside | |||||
| a block. Each method signature and the program open a fresh local scope whose | |||||
| locals carry their declared types. Pass 2 scans statement lines token by | |||||
| token: an unqualified name must be a local, parameter, or (in a method) a field | |||||
| of the enclosing class (D123); `expr.member` follows the left side's type | |||||
| through locals, fields, `self`, `new`, call results, and indexing, and reports | |||||
| unknown members and private members used outside their class. E03-S03 is | |||||
| expected to replace that scan with the expression parser and keep the lookups. | |||||
| ### Pass 1 — declarations and layout | ### Pass 1 — declarations and layout | ||||
| - tokenize the complete source; | - tokenize the complete source; | ||||
| @@ -48,6 +48,8 @@ documented default. `Proposed` means a later change still needs approval. | |||||
| | D119 | The string and character escapes `\n` and `\r` both produce PETSCII `$0D`. | Commodore output has one line-end character, so both escapes start a new line. Implemented as `ESC_N_VALUE`/`ESC_R_VALUE` in `src/common/tokens.inc`; changing them later affects only those constants and the lexer golden files. | | | D119 | The string and character escapes `\n` and `\r` both produce PETSCII `$0D`. | Commodore output has one line-end character, so both escapes start a new line. Implemented as `ESC_N_VALUE`/`ESC_R_VALUE` in `src/common/tokens.inc`; changing them later affects only those constants and the lexer golden files. | | ||||
| | D120 | A program may use at most 255 distinct identifiers (names are 1-byte IDs). | Covers well beyond a 599-line program's typical vocabulary at 5,164 bytes of bank 1. Exceeding it is a resource diagnostic; raising it means 2-byte name IDs in every later record. | | | D120 | A program may use at most 255 distinct identifiers (names are 1-byte IDs). | Covers well beyond a 599-line program's typical vocabulary at 5,164 bytes of bank 1. Exceeding it is a resource diagnostic; raising it means 2-byte name IDs in every later record. | | ||||
| | D121 | A method name may be any identifier or reserved word, as in spec 5.1's `method print()`. | Spec 5.1 and the accepted fixtures declare `method print()` although `print` is a statement keyword. The parser accepts a keyword after `method`; E03-S03 must accept the same member names after `.`. | | | D121 | A method name may be any identifier or reserved word, as in spec 5.1's `method print()`. | Spec 5.1 and the accepted fixtures declare `method print()` although `print` is a statement keyword. The parser accepts a keyword after `method`; E03-S03 must accept the same member names after `.`. | | ||||
| | D122 | `init` returns no value (`as type` is E151), like `destroy`. | Spec 6.3 says `new Class(args)` yields the object, so an `init` result could never be observed. Spec 6.3 already gives `destroy` no parameters and no result (E152, E151). Implemented by E03-S02. | | |||||
| | D123 | Inside a method an unqualified name is a parameter or local, else a field of the enclosing class; methods are always called through an expression such as `self.tick()`. | Spec 9 lets a local "shadow a field", which implies fields are in scope unqualified, but no fixture used one. Spec 7 defines calls only as `expression.method(arguments)`, so a bare `tick()` is an unknown name. Implemented by E03-S02. | | |||||
| ## Deferred | ## Deferred | ||||
| @@ -85,6 +85,8 @@ After 20 errors the next one is replaced by E221 and compilation stops. | |||||
| | Code | Symbol | Message | | | Code | Symbol | Message | | ||||
| |---|---|---| | |---|---|---| | ||||
| | E150 | `SEM_MISSING_RETURN` | MISSING RETURN VALUE | | | E150 | `SEM_MISSING_RETURN` | MISSING RETURN VALUE | | ||||
| | E151 | `SEM_LIFECYCLE_RETURN` | % CANNOT RETURN A VALUE | | |||||
| | E152 | `SEM_LIFECYCLE_PARAMS` | % TAKES NO PARAMETERS | | |||||
| ## Disk (E200-E219) | ## Disk (E200-E219) | ||||
| @@ -100,3 +102,5 @@ After 20 errors the next one is replaced by E221 and compilation stops. | |||||
| | E220 | `RES_TOO_MANY_NAMES` | MORE THAN 255 NAMES | | | E220 | `RES_TOO_MANY_NAMES` | MORE THAN 255 NAMES | | ||||
| | E221 | `RES_ERROR_LIMIT` | TOO MANY ERRORS; COMPILATION STOPPED | | | E221 | `RES_ERROR_LIMIT` | TOO MANY ERRORS; COMPILATION STOPPED | | ||||
| | E222 | `RES_NESTING_TOO_DEEP` | BLOCKS NESTED TOO DEEPLY | | | E222 | `RES_NESTING_TOO_DEEP` | BLOCKS NESTED TOO DEEPLY | | ||||
| | E223 | `RES_TOO_MANY_MEMBERS` | MORE THAN 255 MEMBERS | | |||||
| | E224 | `RES_TOO_MANY_PARAMS` | MORE THAN 255 PARAMETERS | | |||||
| @@ -51,10 +51,11 @@ COMMON, DISKBUF, private ZP, SCRATCH, and MAILBOX are visible from bank 1. | |||||
| | Owner | Bytes | Contents | | | Owner | Bytes | Contents | | ||||
| |---|---|---| | |---|---|---| | ||||
| | `names.s` | 5,164 | 64 hash-chain heads + 255 name records of 20 bytes (D120) | | | `names.s` | 5,164 | 64 hash-chain heads + 255 name records of 20 bytes (D120) | | ||||
| | `parser.s` | 768 | Top-level type table by name ID: kind, declaration line (E03-S01) | | |||||
| | `parser.s` | 4,608 | 18 columns of 256 bytes (`src/common/parser.inc`): type table by name ID (kind, declaration line, member range; E03-S01/E03-S02), 255 member rows (owner, name, flags, type, parameter range) and 255 parameter-type rows (E03-S02) | | |||||
| BSS is not zeroed at load: every table must be cleared by its owner before | BSS is not zeroed at load: every table must be cleared by its owner before | ||||
| use (`names_init`, `parser.s` `tt_clear`). | |||||
| use (`names_init`, `parser.s` `tt_clear`). The member and parameter rows are | |||||
| bounded by their counts instead, so only the type columns are cleared. | |||||
| | `$C000-$FFFF` | | unmapped | System ROM is visible here instead; BASIC string space at the top of bank 1 is untouched | | | `$C000-$FFFF` | | unmapped | System ROM is visible here instead; BASIC string space at the top of bank 1 is untouched | | ||||
| Bank-1 data is reached only through `mmu.s` helpers (`mmu_peek1`/`mmu_poke1` | Bank-1 data is reached only through `mmu.s` helpers (`mmu_peek1`/`mmu_poke1` | ||||
| @@ -77,6 +78,15 @@ System zero page stays intact. KERNAL calls switch P0 back to `$0000` for the | |||||
| call. While P0 is relocated, the system zero page is still readable at | call. While P0 is relocated, the system zero page is still readable at | ||||
| `$1A00-$1AFF` (the MMU swaps the two pages), e.g. `ST` at `$1A90`. | `$1A00-$1AFF` (the MMU swaps the two pages), e.g. `ST` at `$1A90`. | ||||
| `mmu_init` runs in the system mapping too, before the first `mmu_bank0`, and | |||||
| copies the COMMON load image with ordinary loads. `cfg/compiler.cfg` therefore | |||||
| places that image directly after STARTUP, and `mmu.s` asserts at link time that | |||||
| both `mmu_init` and the image end at or below `$4000`. E03-S02 found this when | |||||
| the parser test's load image first crossed `$4000`: COMMON was copied from | |||||
| BASIC ROM and the test crashed into the monitor. KERNAL file names are not | |||||
| affected: `diskio.s` selects bank 0 with SETBNK, and the KERNAL reads names | |||||
| through an all-RAM mapping. | |||||
| After `mmu_shutdown` the CPU is back in BASIC's mapping, where bank-0 RAM at | After `mmu_shutdown` the CPU is back in BASIC's mapping, where bank-0 RAM at | ||||
| `$4000-$BFFF` is hidden by BASIC ROM. A program must finish reading its MAIN | `$4000-$BFFF` is hidden by BASIC ROM. A program must finish reading its MAIN | ||||
| variables and buffers (and anything else above `$3FFF`) before it calls | variables and buffers (and anything else above `$3FFF`) before it calls | ||||
| @@ -0,0 +1,117 @@ | |||||
| --- | |||||
| story: E03-S02 | |||||
| owner: e03_member_parser | |||||
| status: done | |||||
| started: 2026-11-07 | |||||
| depends-on: [E03-S01] | |||||
| --- | |||||
| # E03-S02 Claim | |||||
| ## Intended change | |||||
| Extend `src/compiler/parser.s` so pass 1 records every interface and class | |||||
| member (fields and method signatures, with access, type, and parameter | |||||
| types) in bank-1 tables, enforces unique field and method names per type and | |||||
| the `init`/`destroy` shapes, and keeps locals leading in method bodies. Each | |||||
| method and the program get their own local scope with recorded local types. | |||||
| Pass 2 scans statement lines for name references: bare names must resolve to | |||||
| a local/parameter or (inside a method) a field of the enclosing class, and | |||||
| `expr.member` chains through locals, fields, `self`, `new`, calls, and | |||||
| indexing to report unknown and private members at the member's position. | |||||
| E03-S03 replaces the scan with the real expression parser and reuses the | |||||
| lookups. | |||||
| ## File area | |||||
| - `src/compiler/parser.s`, `src/common/parser.inc` | |||||
| - `src/compiler/names.s` (adds lookup-only `name_find`) | |||||
| - `src/common/diagcat.inc` (appended entries), `docs/diagnostics.md` | |||||
| - `tests/parser/` | |||||
| - `scripts/build-parser-test.ps1`, `scripts/verify-parser-test.ps1` | |||||
| - `docs/architecture.md`, `docs/memory-map.md`, `docs/decisions.md` | |||||
| - `planning/claims/E03-S02.md` | |||||
| - generated files under `build/` | |||||
| ## Acceptance evidence | |||||
| - [x] Parses access, signatures, parameters, fields, and leading local | |||||
| declarations: the member dump (owner, name, field/method, access, type, | |||||
| parameter types) matches an independent source-derived oracle for all 7 | |||||
| accepted fixtures and `members`/`lifecycle`/`scopes`/`private` (41 member | |||||
| rows, default field-private/method-public included). `scopes.n8` rejects | |||||
| `var` after a method statement (22:5) and inside a block (40:5); the | |||||
| program may still interleave `var` at its top level (35). | |||||
| - [x] Enforces unique method names and valid `init`/`destroy` shapes: | |||||
| `members.n8` rejects a second method `area` in an interface (3:10) and a | |||||
| class (17:18) and a second field `size` (9:15), while one field and one | |||||
| method may share a name. `lifecycle.n8` rejects init/destroy in an | |||||
| interface (E030), `init ... as word` (E151, D122), a second `init` | |||||
| (E060), and destroy with parameters (E152, first one) and a result | |||||
| (E151); `class Fine` shows parameterized init and private destroy pass. | |||||
| - [x] Assigns lexical scopes without leaking names across methods/classes: | |||||
| `scopes.n8` reports `one()`'s local and parameter in `two()`, class A's | |||||
| field in class B, `three()`'s parameter and local in `four()`, method | |||||
| locals and fields in the program, `self` in the program, and an unknown | |||||
| `new Ghost()` (8 pass-2 diagnostics, exact positions). Mutation: removing | |||||
| the per-signature scope reset fails 5 fixtures. | |||||
| - [x] Reports private-member violations with source positions: | |||||
| `private.n8` reports 6 E120s at the member's column, through a parameter, | |||||
| local, call result (`v.make().hidden`, `p.look().secret`), and index | |||||
| (`list[0].secret`), with Thief resolving Vault's members before Vault is | |||||
| declared; access from Vault's own methods (through `self`, a bare field, | |||||
| or another Vault) passes; unknown members are E061. Mutation: disabling | |||||
| the privacy test drops the 6 E120s and fails the fixture. | |||||
| ## Coordination notes | |||||
| - No other claim is active. E03-S03 will own statement/expression grammar; | |||||
| the pass-2 scan here only resolves names and members token by token and | |||||
| reports nothing about expression shape or types. | |||||
| ## Work log | |||||
| - Baseline: `scripts/verify-parser-test.ps1` passes 24 fixtures before any | |||||
| change. | |||||
| - Added `name_find` (lookup without interning) so pass 2 never grows the | |||||
| name table: a name first seen in a statement was never declared. | |||||
| - Appended E151 `SEM_LIFECYCLE_RETURN`, E152 `SEM_LIFECYCLE_PARAMS`, E223 | |||||
| `RES_TOO_MANY_MEMBERS`, E224 `RES_TOO_MANY_PARAMS`; docs regenerated. | |||||
| - First x128 run hung with the C128 in its ML monitor. Cause: the test PRG | |||||
| now extends past `$4000`, and `mmu_init` copies the COMMON load image | |||||
| (then placed after DATA) in the system mapping, where BASIC ROM covers | |||||
| `$4000-$7FFF`. Fix: `cfg/compiler.cfg` loads COMMON directly after | |||||
| STARTUP, and `mmu.s` asserts at link time that `mmu_init` and the COMMON | |||||
| image stay at or below `$4000`. Region addresses (D117) are unchanged. | |||||
| Map skeleton, source, lexer, names, and diagnostics suites were rerun on | |||||
| the new layout. | |||||
| - Verifier: members are compared against an independent regex oracle over | |||||
| the fixture source (declaration order, access defaults, first | |||||
| declaration wins) for the 7 accepted fixtures and the 4 new ones. | |||||
| ## Completion | |||||
| - Tests run: `scripts/verify-parser-test.ps1` (final run, plus the two | |||||
| mutations above); `scripts/build-diag-test.ps1` catalog check; because | |||||
| `cfg/compiler.cfg` changed, `verify-map-skeleton.ps1` (including its | |||||
| overflow variants), `verify-source-test.ps1`, `verify-lexer-test.ps1`, | |||||
| `verify-names-test.ps1`, and `verify-diag-test.ps1`. | |||||
| - Results: 28 parser fixtures as expected (2 still pending E03-S03, with no | |||||
| false diagnostics); both mutations caught; all five other suites pass. | |||||
| - Files changed: `src/compiler/parser.s`, `src/common/parser.inc`, | |||||
| `src/compiler/names.s`, `src/compiler/mmu.s`, `cfg/compiler.cfg`, | |||||
| `src/common/diagcat.inc`, `docs/diagnostics.md`, `docs/decisions.md` | |||||
| (D122, D123 proposed), `docs/architecture.md`, `docs/memory-map.md`, | |||||
| `tests/parser/` (4 fixtures + expectations, harness dump), | |||||
| `scripts/build-parser-test.ps1`, `scripts/verify-parser-test.ps1`, this | |||||
| claim. | |||||
| - Remaining risks: D121-D123 await approval. The pass-2 scan is token based: | |||||
| where it cannot know a left side's type (expression results, grouping, | |||||
| primitives, array-valued calls) it checks nothing, and a field used as | |||||
| `x.f(...)` reports "not declared" rather than a type error; E03-S03/S04 | |||||
| should replace it. Access to `init` from `new` outside the class is not | |||||
| checked (spec silent). The type descriptor (base, name, 16-bit length) is | |||||
| provisional until E03-S04. Member and parameter capacities (255 each) are | |||||
| untested at the limit. CODE/DATA may now exceed `$4000`; only `mmu_init` | |||||
| and code run before `mmu_bank0` must stay below it (asserted for | |||||
| `mmu_init` and COMMON; test STARTUP code is first in MAIN). | |||||
| @@ -1,4 +1,4 @@ | |||||
| # E03-S01: build the parser test PRG and a fresh D81 holding every grammar and | |||||
| # E03-S01/E03-S02: build the parser test PRG and a fresh D81 holding every grammar and | |||||
| # parser fixture. The fixture order must match tests/parser/parser-test.s. | # parser fixture. The fixture order must match tests/parser/parser-test.s. | ||||
| [CmdletBinding()] | [CmdletBinding()] | ||||
| param() | param() | ||||
| @@ -37,6 +37,10 @@ $script:ParserFixtures = [ordered]@{ | |||||
| "recover.n8" = @("$p\recover.n8", "exact", "$p\recover.expect") | "recover.n8" = @("$p\recover.n8", "exact", "$p\recover.expect") | ||||
| "noprog.n8" = @("$p\noprog.n8", "exact", "$p\noprog.expect") | "noprog.n8" = @("$p\noprog.n8", "exact", "$p\noprog.expect") | ||||
| "eofclass.n8" = @("$p\eofclass.n8", "exact", "$p\eofclass.expect") | "eofclass.n8" = @("$p\eofclass.n8", "exact", "$p\eofclass.expect") | ||||
| "members.n8" = @("$p\members.n8", "exact", "$p\members.expect") | |||||
| "lifecycle.n8" = @("$p\lifecycle.n8", "exact", "$p\lifecycle.expect") | |||||
| "scopes.n8" = @("$p\scopes.n8", "exact", "$p\scopes.expect") | |||||
| "private.n8" = @("$p\private.n8", "exact", "$p\private.expect") | |||||
| } | } | ||||
| $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` | $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` | ||||
| @@ -28,22 +28,97 @@ try { | |||||
| if ($mb[3] -ne 0xA5) { throw ("C128 reported FAIL (code `${0:X2})" -f $mb[4]) } | if ($mb[3] -ne 0xA5) { throw ("C128 reported FAIL (code `${0:X2})" -f $mb[4]) } | ||||
| # ------------------------------------------------------------ decode dump | # ------------------------------------------------------------ decode dump | ||||
| $records = @(); $p = 0 | |||||
| function ConvertFrom-Spelling([byte[]]$bytes) { | |||||
| return -join ($bytes | ForEach-Object { | |||||
| if ($_ -ge 0x41 -and $_ -le 0x5A) { [char]($_ + 0x20) } elseif ($_ -ge 0xC1 -and $_ -le 0xDA) { [char]($_ - 0x80) } else { [char]$_ } }) | |||||
| } | |||||
| # A name is a length and its spelling; a type is base, name, length lo/hi. | |||||
| # Both are rendered lowercase, as the oracle below writes them. | |||||
| function Read-DumpName { | |||||
| $len = $dump[$script:p]; $script:p++ | |||||
| if ($len -eq 0) { return "" } | |||||
| $s = ConvertFrom-Spelling $dump[$script:p..($script:p + $len - 1)] | |||||
| $script:p += $len | |||||
| return $s.ToLower() | |||||
| } | |||||
| function Read-DumpType { | |||||
| $base = $dump[$script:p]; $script:p++ | |||||
| $name = Read-DumpName | |||||
| $n = $dump[$script:p] + 256 * $dump[$script:p + 1]; $script:p += 2 | |||||
| $t = @("none", "boolean", "byte", "char", "integer", "word", $name)[$base] | |||||
| if ($n) { $t += "[$n]" } | |||||
| return $t | |||||
| } | |||||
| $records = @(); $script:p = 0 | |||||
| while ($dump[$p] -ne 0xFE) { | while ($dump[$p] -ne 0xFE) { | ||||
| if ($dump[$p] -ne 0xFF) { throw "dump out of sync at $p" } | 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]; Types = @() } | |||||
| $r = [pscustomobject]@{ Index = $dump[$p + 1]; Load = $dump[$p + 2]; Count = $dump[$p + 3]; Types = @(); Members = @() } | |||||
| $p += 4 | $p += 4 | ||||
| while ($dump[$p] -ne 0xFD) { | |||||
| while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFC) { | |||||
| $len = $dump[$p + 3] | $len = $dump[$p + 3] | ||||
| $spell = -join ($dump[($p + 4)..($p + 3 + $len)] | ForEach-Object { | |||||
| if ($_ -ge 0x41 -and $_ -le 0x5A) { [char]($_ + 0x20) } elseif ($_ -ge 0xC1 -and $_ -le 0xDA) { [char]($_ - 0x80) } else { [char]$_ } }) | |||||
| $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])) | $r.Types += ("{0} {1} {2}" -f @("", "interface", "class", "program")[$dump[$p]], $spell, ($dump[$p + 1] + 256 * $dump[$p + 2])) | ||||
| $p += 4 + $len | $p += 4 + $len | ||||
| } | } | ||||
| if ($dump[$p] -eq 0xFC) { | |||||
| $p++ | |||||
| while ($dump[$p] -ne 0xFD) { | |||||
| $owner = Read-DumpName | |||||
| $name = Read-DumpName | |||||
| $flags = $dump[$p]; $p++ | |||||
| $type = Read-DumpType | |||||
| $count = $dump[$p]; $p++ | |||||
| $params = @(for ($k = 0; $k -lt $count; $k++) { Read-DumpType }) | |||||
| $r.Members += ("{0}.{1} {2} {3} {4} ({5})" -f $owner, $name, | |||||
| $(if ($flags -band 1) { "method" } else { "field" }), | |||||
| $(if ($flags -band 2) { "private" } else { "public" }), $type, ($params -join ",")) | |||||
| } | |||||
| } | |||||
| $p++ | $p++ | ||||
| $records += $r | $records += $r | ||||
| } | } | ||||
| # Members the source declares, derived independently by regex: per class or | |||||
| # interface in order; access defaults (fields private, methods public); | |||||
| # a second declaration of a name in the same namespace is not recorded. | |||||
| $typeRx = '(boolean|byte|char|integer|word|\w+)(\[(\d+)\])?' | |||||
| function Format-SourceType([string]$base, [string]$n) { | |||||
| $t = $base.ToLower(); if ($n) { $t += "[$n]" }; return $t | |||||
| } | |||||
| function Get-SourceMembers([string]$path) { | |||||
| $members = @(); $seen = @{}; $owner = $null; $inBody = $false | |||||
| foreach ($line in Get-Content -LiteralPath $path) { | |||||
| if ($line -match '^\s*(interface|class)\s+(\w+)') { $ownerKind = $Matches[1]; $owner = $Matches[2].ToLower(); continue } | |||||
| if ($line -match '^\s*end\s+(interface|class)\b') { $owner = $null; continue } | |||||
| if ($line -match '^\s*end\s+method\b') { $inBody = $false; continue } | |||||
| if (-not $owner -or $inBody) { continue } | |||||
| $m = [regex]::Match($line, "^\s*(public|private)?\s*method\s+(\w+)\s*\((.*)\)\s*(as\s+$typeRx)?\s*$") | |||||
| if ($m.Success) { | |||||
| $kind = "method"; $name = $m.Groups[2].Value.ToLower() | |||||
| $access = if ($m.Groups[1].Value -eq "private") { "private" } else { "public" } | |||||
| $type = if ($m.Groups[4].Success) { Format-SourceType $m.Groups[5].Value $m.Groups[7].Value } else { "none" } | |||||
| $params = @(foreach ($part in ($m.Groups[3].Value -split ',' | Where-Object { $_.Trim() })) { | |||||
| $pm = [regex]::Match($part.Trim(), "^\w+\s+as\s+$typeRx$") | |||||
| Format-SourceType $pm.Groups[1].Value $pm.Groups[3].Value }) | |||||
| $inBody = ($ownerKind -eq "class") # interface methods have no body | |||||
| } else { | |||||
| $m = [regex]::Match($line, "^\s*(public|private)?\s*$typeRx\s+(\w+)\s*$") | |||||
| if (-not $m.Success) { continue } | |||||
| $kind = "field"; $name = $m.Groups[5].Value.ToLower() | |||||
| $access = if ($m.Groups[1].Value -eq "public") { "public" } else { "private" } | |||||
| $type = Format-SourceType $m.Groups[2].Value $m.Groups[4].Value | |||||
| $params = @() | |||||
| } | |||||
| $key = "$owner.$kind.$name" | |||||
| if ($seen[$key]) { continue } | |||||
| $seen[$key] = $true | |||||
| $members += ("{0}.{1} {2} {3} {4} ({5})" -f $owner, $name, $kind, $access, $type, ($params -join ",")) | |||||
| } | |||||
| return ,$members | |||||
| } | |||||
| # Fixtures whose member tables are compared (headers all well formed). | |||||
| $memberChecked = @("members.n8", "lifecycle.n8", "scopes.n8", "private.n8") | |||||
| $c1541 = Get-ViceTool "c1541" | $c1541 = Get-ViceTool "c1541" | ||||
| $listing = (& $c1541 -attach $disk -list 2>&1) | Out-String | $listing = (& $c1541 -attach $disk -list 2>&1) | Out-String | ||||
| function Get-ErrLines([string]$base) { | function Get-ErrLines([string]$base) { | ||||
| @@ -114,6 +189,13 @@ foreach ($name in $ParserFixtures.Keys) { | |||||
| $detail = "{0} diagnostics" -f $got.Count | $detail = "{0} diagnostics" -f $got.Count | ||||
| } | } | ||||
| } | } | ||||
| if ($group -eq "accepted" -or $memberChecked -contains $name) { | |||||
| $wantMembers = Get-SourceMembers $src | |||||
| if (($r.Members -join ';') -ne ($wantMembers -join ';')) { | |||||
| $problems += "members [$($r.Members -join '; ')] expected [$($wantMembers -join '; ')]" | |||||
| } | |||||
| $detail += ", {0} members" -f $r.Members.Count | |||||
| } | |||||
| $status = if ($problems) { "FAIL" } else { "ok" } | $status = if ($problems) { "FAIL" } else { "ok" } | ||||
| Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail) | Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail) | ||||
| if ($problems) { $failures += "${name}: " + ($problems -join "; ") } | if ($problems) { $failures += "${name}: " + ($problems -join "; ") } | ||||
| @@ -65,6 +65,8 @@ | |||||
| ; ---------------------------------------------------- methods and returns | ; ---------------------------------------------------- methods and returns | ||||
| DIAGNOSTIC SEM_MISSING_RETURN, 150, "MISSING RETURN VALUE" | DIAGNOSTIC SEM_MISSING_RETURN, 150, "MISSING RETURN VALUE" | ||||
| DIAGNOSTIC SEM_LIFECYCLE_RETURN, 151, "% CANNOT RETURN A VALUE" | |||||
| DIAGNOSTIC SEM_LIFECYCLE_PARAMS, 152, "% TAKES NO PARAMETERS" | |||||
| ; ------------------------------------------------------------------- disk | ; ------------------------------------------------------------------- disk | ||||
| DIAGNOSTIC DSK_SOURCE_UNREADABLE, 200, "CANNOT READ SOURCE; DOS %" | DIAGNOSTIC DSK_SOURCE_UNREADABLE, 200, "CANNOT READ SOURCE; DOS %" | ||||
| @@ -74,3 +76,5 @@ | |||||
| DIAGNOSTIC RES_TOO_MANY_NAMES, 220, "MORE THAN 255 NAMES" | DIAGNOSTIC RES_TOO_MANY_NAMES, 220, "MORE THAN 255 NAMES" | ||||
| DIAGNOSTIC RES_ERROR_LIMIT, 221, "TOO MANY ERRORS; COMPILATION STOPPED" | DIAGNOSTIC RES_ERROR_LIMIT, 221, "TOO MANY ERRORS; COMPILATION STOPPED" | ||||
| DIAGNOSTIC RES_NESTING_TOO_DEEP, 222, "BLOCKS NESTED TOO DEEPLY" | DIAGNOSTIC RES_NESTING_TOO_DEEP, 222, "BLOCKS NESTED TOO DEEPLY" | ||||
| DIAGNOSTIC RES_TOO_MANY_MEMBERS, 223, "MORE THAN 255 MEMBERS" | |||||
| DIAGNOSTIC RES_TOO_MANY_PARAMS, 224, "MORE THAN 255 PARAMETERS" | |||||
| @@ -1,13 +1,56 @@ | |||||
| ; parser.inc - top-level type table and parser limits (E03-S01). | |||||
| ; parser.inc - parser tables and limits (E03-S01, E03-S02). | |||||
| .ifndef PARSER_INC | .ifndef PARSER_INC | ||||
| PARSER_INC = 1 | PARSER_INC = 1 | ||||
| ; Top-level type kinds (type table column C_TYPE_KIND) | |||||
| TYPE_NONE = 0 | TYPE_NONE = 0 | ||||
| TYPE_INTERFACE = 1 | TYPE_INTERFACE = 1 | ||||
| TYPE_CLASS = 2 | TYPE_CLASS = 2 | ||||
| TYPE_PROGRAM = 3 | TYPE_PROGRAM = 3 | ||||
| PARSE_MAX_DEPTH = 16 ; nested method/program/if/while/for blocks | PARSE_MAX_DEPTH = 16 ; nested method/program/if/while/for blocks | ||||
| SCAN_MAX_DEPTH = 16 ; nested ( and [ tracked by the pass-2 scan | |||||
| ; Declared-type descriptor (provisional until E03-S04): base, name, length. | |||||
| ; base TY_*; TY_NONE for a method without a result | |||||
| ; name class/interface name ID when base = TY_REF, else 0 | |||||
| ; length array length (2 bytes); 0 = not an array | |||||
| TY_NONE = 0 | |||||
| TY_BOOLEAN = 1 | |||||
| TY_BYTE = 2 | |||||
| TY_CHAR = 3 | |||||
| TY_INTEGER = 4 | |||||
| TY_WORD = 5 | |||||
| TY_REF = 6 | |||||
| ; Member flags | |||||
| MF_METHOD = $01 ; clear = field | |||||
| MF_PRIVATE = $02 ; clear = public | |||||
| MEMBERS_MAX = 255 ; rows 0..254, all classes and interfaces | |||||
| PARAMS_MAX = 255 ; rows 0..254, all method signatures | |||||
| ; Bank-1 parser tables: 256-byte columns, consecutive from type_kind. | |||||
| ; tb_get/tb_set take A = column and Y = row (a name ID for type columns). | |||||
| C_TYPE_KIND = 0 ; TYPE_* | |||||
| C_TYPE_LINE_LO = 1 ; declaration line | |||||
| C_TYPE_LINE_HI = 2 | |||||
| C_TYPE_MFIRST = 3 ; first member row | |||||
| C_TYPE_MCOUNT = 4 ; member rows (contiguous, declaration order) | |||||
| C_MEM_OWNER = 5 ; owning class/interface name ID | |||||
| C_MEM_NAME = 6 ; member name ID | |||||
| C_MEM_FLAGS = 7 ; MF_* | |||||
| C_MEM_TBASE = 8 ; field type / method result type | |||||
| C_MEM_TNAME = 9 | |||||
| C_MEM_TLEN_LO = 10 | |||||
| C_MEM_TLEN_HI = 11 | |||||
| C_MEM_PFIRST = 12 ; first parameter row | |||||
| C_MEM_PCOUNT = 13 ; parameter rows (declaration order) | |||||
| C_PAR_TBASE = 14 ; parameter type | |||||
| C_PAR_TNAME = 15 | |||||
| C_PAR_TLEN_LO = 16 | |||||
| C_PAR_TLEN_HI = 17 | |||||
| TABLE_COLUMNS = 18 | |||||
| .endif | .endif | ||||
| @@ -109,6 +109,10 @@ mmu_init: | |||||
| lda #>mmu_nmi_guard | lda #>mmu_nmi_guard | ||||
| sta NMINV+1 | sta NMINV+1 | ||||
| rts | rts | ||||
| ; mmu_init runs, and reads the COMMON load image, in the system mapping, where | |||||
| ; BASIC ROM hides bank-0 RAM from $4000. Both must stay below it. | |||||
| .assert * <= $4000, error, "mmu_init must lie below $4000" | |||||
| .assert __COMMON_LOAD__ + __COMMON_SIZE__ <= $4000, error, "COMMON load image must lie below $4000" | |||||
| ; ---------------------------------------------------------------- COMMON | ; ---------------------------------------------------------------- COMMON | ||||
| .segment "COMMON" | .segment "COMMON" | ||||
| @@ -20,7 +20,7 @@ | |||||
| .importzp mmu_ptr, mmu_src | .importzp mmu_ptr, mmu_src | ||||
| .import src_line | .import src_line | ||||
| .export names_init, name_intern, name_declare, name_get, names_count | |||||
| .export names_init, name_intern, name_find, name_declare, name_get, names_count | |||||
| .export name_out, name_out_len, name_out_flags | .export name_out, name_out_len, name_out_flags | ||||
| .export name_heads, name_recs | .export name_heads, name_recs | ||||
| @@ -49,6 +49,22 @@ name_intern: | |||||
| @found: clc | @found: clc | ||||
| @out: rts | @out: rts | ||||
| ; Look up the identifier at src_line[A-1], length X, without adding it. | |||||
| ; out: C=0, A = ID; C=1, A = 0 when the name was never interned | |||||
| name_find: | |||||
| jsr set_key | |||||
| bcs @no | |||||
| lda #$00 | |||||
| sta nk_add | |||||
| jsr nt_find | |||||
| lda nk_result | |||||
| beq @no | |||||
| clc | |||||
| rts | |||||
| @no: lda #$00 | |||||
| sec | |||||
| rts | |||||
| ; Record the declaration spelling at src_line[Y-1], length X, for ID A, | ; Record the declaration spelling at src_line[Y-1], length X, for ID A, | ||||
| ; unless an earlier declaration already did. The spelling must normalize to | ; unless an earlier declaration already did. The spelling must normalize to | ||||
| ; the same name (the caller interned it from the same token). | ; the same name (the caller interned it from the same token). | ||||
| @@ -0,0 +1,9 @@ | |||||
| // interfaces declare no init/destroy; init and destroy return nothing; | |||||
| // destroy takes no parameters (first one reported); one init per class. | |||||
| // class Fine: init may take parameters and destroy may be private | |||||
| SYN_UNEXPECTED_TOKEN 2 10 | |||||
| SYN_UNEXPECTED_TOKEN 3 10 | |||||
| SEM_LIFECYCLE_RETURN 7 33 | |||||
| SEM_DUPLICATE_NAME 10 17 | |||||
| SEM_LIFECYCLE_PARAMS 12 25 | |||||
| SEM_LIFECYCLE_RETURN 12 56 | |||||
| @@ -0,0 +1,25 @@ | |||||
| interface Life | |||||
| method init() | |||||
| method destroy() | |||||
| end interface | |||||
| class Res | |||||
| public method init(n as word) as word | |||||
| return n | |||||
| end method | |||||
| public method init() | |||||
| end method | |||||
| public method destroy(flag as boolean, more as byte) as boolean | |||||
| return flag | |||||
| end method | |||||
| end class | |||||
| class Fine | |||||
| method init(a as word, b as Res) | |||||
| end method | |||||
| private method destroy() | |||||
| end method | |||||
| end class | |||||
| program Main | |||||
| end program | |||||
| @@ -0,0 +1,5 @@ | |||||
| // fields and methods are separate namespaces per type: `size` and `area` | |||||
| // may each be a field and a method once, never twice in the same namespace | |||||
| SEM_DUPLICATE_NAME 3 10 | |||||
| SEM_DUPLICATE_NAME 9 15 | |||||
| SEM_DUPLICATE_NAME 17 18 | |||||
| @@ -0,0 +1,24 @@ | |||||
| interface Shape | |||||
| method area() as word | |||||
| method area() as word | |||||
| method name() | |||||
| end interface | |||||
| class Box implements Shape | |||||
| word size | |||||
| public byte size | |||||
| public word area | |||||
| public method size() as word | |||||
| return 0 | |||||
| end method | |||||
| public method area() as word | |||||
| return 0 | |||||
| end method | |||||
| private method area() | |||||
| end method | |||||
| method name() | |||||
| end method | |||||
| end class | |||||
| program Main | |||||
| end program | |||||
| @@ -1,4 +1,4 @@ | |||||
| ; E03-S01: parser test in x128. | |||||
| ; E03-S01/E03-S02: parser test in x128. | |||||
| ; | ; | ||||
| ; For each fixture: src_load, names_init, diag_init (screen off), parse_file, | ; For each fixture: src_load, names_init, diag_init (screen off), parse_file, | ||||
| ; diag_finish. Rejected fixtures leave NAME.ERR on the disk for the host to | ; diag_finish. Rejected fixtures leave NAME.ERR on the disk for the host to | ||||
| @@ -6,6 +6,10 @@ | |||||
| ; $FF, fixture index, src_load result, diagnostic count | ; $FF, fixture index, src_load result, diagnostic count | ||||
| ; per declared top-level type: kind, line lo, line hi, spelling length, | ; per declared top-level type: kind, line lo, line hi, spelling length, | ||||
| ; spelling bytes | ; spelling bytes | ||||
| ; $FC | |||||
| ; 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. | |||||
| ; $FD | ; $FD | ||||
| ; then $FE at the end. Mailbox: MB_DATA+0/1 dump address, +2/3 length. | ; then $FE at the end. Mailbox: MB_DATA+0/1 dump address, +2/3 length. | ||||
| @@ -20,6 +24,7 @@ | |||||
| .import names_init, name_get, names_count, name_out, name_out_len | .import names_init, name_get, names_count, name_out, name_out_len | ||||
| .import diag_init, diag_finish, diag_screen | .import diag_init, diag_finish, diag_screen | ||||
| .import parse_file, type_kind, type_line_lo, type_line_hi | .import parse_file, type_kind, type_line_lo, type_line_hi | ||||
| .import tb_get, mem_count | |||||
| DUMP_SIZE = $2000 | DUMP_SIZE = $2000 | ||||
| @@ -112,6 +117,9 @@ test_entry: | |||||
| jsr diag_finish | jsr diag_finish | ||||
| jsr emit | jsr emit | ||||
| jsr dump_types | jsr dump_types | ||||
| lda #$FC | |||||
| jsr emit | |||||
| jsr dump_members | |||||
| @advance: | @advance: | ||||
| lda #$FD | lda #$FD | ||||
| jsr emit | jsr emit | ||||
| @@ -211,6 +219,90 @@ dump_types: | |||||
| bne @next | bne @next | ||||
| @done: rts | @done: rts | ||||
| ; Emit every member row with its parameter types. | |||||
| dump_members: | |||||
| lda #$00 | |||||
| sta row | |||||
| @next: lda row | |||||
| cmp mem_count | |||||
| bcs @done | |||||
| ldy row | |||||
| lda #C_MEM_OWNER | |||||
| jsr tb_get | |||||
| jsr emit_name | |||||
| ldy row | |||||
| lda #C_MEM_NAME | |||||
| jsr tb_get | |||||
| jsr emit_name | |||||
| ldy row | |||||
| lda #C_MEM_FLAGS | |||||
| jsr tb_get | |||||
| jsr emit | |||||
| ldy row | |||||
| lda #C_MEM_TBASE | |||||
| jsr emit_type | |||||
| ldy row | |||||
| lda #C_MEM_PCOUNT | |||||
| jsr tb_get | |||||
| sta pleft | |||||
| jsr emit | |||||
| ldy row | |||||
| lda #C_MEM_PFIRST | |||||
| jsr tb_get | |||||
| sta prow | |||||
| @par: lda pleft | |||||
| beq @adv | |||||
| ldy prow | |||||
| lda #C_PAR_TBASE | |||||
| jsr emit_type | |||||
| inc prow | |||||
| dec pleft | |||||
| jmp @par | |||||
| @adv: inc row | |||||
| jmp @next | |||||
| @done: rts | |||||
| ; Emit the four type columns starting at column A for row Y. | |||||
| emit_type: | |||||
| sta col | |||||
| sty trow | |||||
| jsr tb_get ; base | |||||
| jsr emit | |||||
| inc col | |||||
| lda col | |||||
| ldy trow | |||||
| jsr tb_get ; name ID | |||||
| jsr emit_name | |||||
| inc col | |||||
| lda col | |||||
| ldy trow | |||||
| jsr tb_get ; length lo | |||||
| jsr emit | |||||
| inc col | |||||
| lda col | |||||
| ldy trow | |||||
| jsr tb_get ; length hi | |||||
| jmp emit | |||||
| ; Emit name ID A as length + spelling; 0 emits a zero length. | |||||
| emit_name: | |||||
| cmp #$00 | |||||
| bne :+ | |||||
| jmp emit | |||||
| : jsr name_get | |||||
| lda name_out_len | |||||
| jsr emit | |||||
| ldx #$00 | |||||
| @byte: cpx name_out_len | |||||
| beq @done | |||||
| lda name_out,x | |||||
| stx emit_x | |||||
| jsr emit | |||||
| ldx emit_x | |||||
| inx | |||||
| bne @byte | |||||
| @done: rts | |||||
| emit: ldy dump_ptr+1 | emit: ldy dump_ptr+1 | ||||
| cpy #>(dump + DUMP_SIZE) | cpy #>(dump + DUMP_SIZE) | ||||
| bcc @room | bcc @room | ||||
| @@ -262,7 +354,11 @@ fixtures: | |||||
| FIXTURE "RECOVER.N8" | FIXTURE "RECOVER.N8" | ||||
| FIXTURE "NOPROG.N8" | FIXTURE "NOPROG.N8" | ||||
| FIXTURE "EOFCLASS.N8" | FIXTURE "EOFCLASS.N8" | ||||
| FIXTURE_COUNT = 24 | |||||
| FIXTURE "MEMBERS.N8" | |||||
| FIXTURE "LIFECYCLE.N8" | |||||
| FIXTURE "SCOPES.N8" | |||||
| FIXTURE "PRIVATE.N8" | |||||
| FIXTURE_COUNT = 28 | |||||
| messages: | messages: | ||||
| ok_msg: .byte "NOVA128 PARSER TEST OK", $0D, $00 | ok_msg: .byte "NOVA128 PARSER TEST OK", $0D, $00 | ||||
| @@ -279,4 +375,9 @@ id: .res 1 | |||||
| emit_x: .res 1 | emit_x: .res 1 | ||||
| emit_y: .res 1 | emit_y: .res 1 | ||||
| print_y: .res 1 | print_y: .res 1 | ||||
| row: .res 1 | |||||
| prow: .res 1 | |||||
| pleft: .res 1 | |||||
| col: .res 1 | |||||
| trow: .res 1 | |||||
| dump: .res DUMP_SIZE | dump: .res DUMP_SIZE | ||||
| @@ -0,0 +1,14 @@ | |||||
| // Thief uses Vault before Vault is declared (members resolve in pass 2). | |||||
| // Private members (explicit, or a field by default) are visible only in | |||||
| // Vault's own methods, through self or another Vault; chains through calls | |||||
| // (v.make().hidden, p.look().secret) and indexing (list[0].secret) are | |||||
| // checked; unknown members on a class or interface are reported. | |||||
| ACC_PRIVATE_MEMBER 3 7 | |||||
| ACC_PRIVATE_MEMBER 5 7 | |||||
| ACC_PRIVATE_MEMBER 7 14 | |||||
| SEM_UNKNOWN_NAME 8 7 | |||||
| SEM_UNKNOWN_NAME 9 7 | |||||
| ACC_PRIVATE_MEMBER 44 5 | |||||
| ACC_PRIVATE_MEMBER 45 11 | |||||
| ACC_PRIVATE_MEMBER 48 12 | |||||
| SEM_UNKNOWN_NAME 49 5 | |||||
| @@ -0,0 +1,51 @@ | |||||
| class Thief | |||||
| public method steal(v as Vault) | |||||
| v.secret = 1 | |||||
| v.shown = 2 | |||||
| v.lock() | |||||
| v.open() | |||||
| v.make().hidden = 3 | |||||
| v.missing = 4 | |||||
| v.shown(1) | |||||
| end method | |||||
| end class | |||||
| interface Peek | |||||
| method look() as Vault | |||||
| end interface | |||||
| class Vault implements Peek | |||||
| private word secret | |||||
| public word shown | |||||
| word hidden | |||||
| private method lock() | |||||
| end method | |||||
| method open() | |||||
| self.lock() | |||||
| self.secret = 1 | |||||
| hidden = 2 | |||||
| end method | |||||
| public method peer(other as Vault) | |||||
| other.secret = 2 | |||||
| end method | |||||
| public method make() as Vault | |||||
| return self | |||||
| end method | |||||
| public method look() as Vault | |||||
| return self | |||||
| end method | |||||
| end class | |||||
| program Main | |||||
| var v as Vault | |||||
| var list as Vault[2] | |||||
| var p as Peek | |||||
| v = new Vault() | |||||
| v.hidden = 1 | |||||
| list[0].secret = 5 | |||||
| print v.shown | |||||
| p = v | |||||
| p.look().secret = 6 | |||||
| p.touch() | |||||
| v.open() | |||||
| end program | |||||
| @@ -0,0 +1,14 @@ | |||||
| // pass 1: `var` after a method statement or inside a block | |||||
| SYN_UNEXPECTED_TOKEN 22 5 | |||||
| SYN_UNEXPECTED_TOKEN 40 5 | |||||
| // pass 2: locals/params of one() are not visible in two(); A's field is | |||||
| // not visible in B; three()'s param and local are gone in four(); the | |||||
| // program sees only its own locals and has no self; Ghost is no type | |||||
| SEM_UNKNOWN_NAME 11 5 | |||||
| SEM_UNKNOWN_NAME 12 5 | |||||
| SEM_UNKNOWN_NAME 27 5 | |||||
| SEM_UNKNOWN_NAME 28 5 | |||||
| SEM_UNKNOWN_NAME 36 11 | |||||
| SEM_UNKNOWN_NAME 37 3 | |||||
| SEM_UNKNOWN_NAME 38 3 | |||||
| SYN_UNEXPECTED_TOKEN 42 3 | |||||
| @@ -0,0 +1,43 @@ | |||||
| class A | |||||
| private word count | |||||
| public method one(p as word) | |||||
| var t as word | |||||
| t = p | |||||
| count = t | |||||
| end method | |||||
| public method two() | |||||
| t = 1 | |||||
| p = 2 | |||||
| count = 3 | |||||
| end method | |||||
| end class | |||||
| class B | |||||
| public method three(count as byte) | |||||
| var mine as word | |||||
| count = 1 | |||||
| mine = 2 | |||||
| var late as word | |||||
| late = mine | |||||
| end method | |||||
| public method four() | |||||
| count = 4 | |||||
| mine = 5 | |||||
| end method | |||||
| end class | |||||
| program Main | |||||
| var a as A | |||||
| a = new A() | |||||
| var b as B | |||||
| b = new Ghost() | |||||
| t = 0 | |||||
| count = 0 | |||||
| if a = null | |||||
| var inner as word | |||||
| end if | |||||
| self.count = 1 | |||||
| end program | |||||
Powered by TurnKey Linux.