瀏覽代碼

newLang: add the method, field, and local parser (E03-S02)

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
Daniel Covington 6 天之前
父節點
當前提交
f2af2f2aa5
共有 22 個檔案被更改,包括 1459 行新增 和 86 行删除
  1. +3
    -1
      newLang/cfg/compiler.cfg
  2. +12
    -0
      newLang/docs/architecture.md
  3. +2
    -0
      newLang/docs/decisions.md
  4. +4
    -0
      newLang/docs/diagnostics.md
  5. +12
    -2
      newLang/docs/memory-map.md
  6. +117
    -0
      newLang/planning/claims/E03-S02.md
  7. +5
    -1
      newLang/scripts/build-parser-test.ps1
  8. +87
    -5
      newLang/scripts/verify-parser-test.ps1
  9. +4
    -0
      newLang/src/common/diagcat.inc
  10. +44
    -1
      newLang/src/common/parser.inc
  11. +4
    -0
      newLang/src/compiler/mmu.s
  12. +17
    -1
      newLang/src/compiler/names.s
  13. +860
    -73
      newLang/src/compiler/parser.s
  14. +9
    -0
      newLang/tests/parser/lifecycle.expect
  15. +25
    -0
      newLang/tests/parser/lifecycle.n8
  16. +5
    -0
      newLang/tests/parser/members.expect
  17. +24
    -0
      newLang/tests/parser/members.n8
  18. +103
    -2
      newLang/tests/parser/parser-test.s
  19. +14
    -0
      newLang/tests/parser/private.expect
  20. +51
    -0
      newLang/tests/parser/private.n8
  21. +14
    -0
      newLang/tests/parser/scopes.expect
  22. +43
    -0
      newLang/tests/parser/scopes.n8

+ 3
- 1
newLang/cfg/compiler.cfg 查看文件

@@ -39,10 +39,12 @@ SEGMENTS {
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = HEADER, type = ro;
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;
RODATA: load = MAIN, type = ro, 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;
DISKBUF: load = DISKBUF, type = bss, optional = yes;
SCRATCH: load = SCRATCH, type = bss, optional = yes;


+ 12
- 0
newLang/docs/architecture.md 查看文件

@@ -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
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

- tokenize the complete source;


+ 2
- 0
newLang/docs/decisions.md 查看文件

@@ -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. |
| 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 `.`. |
| 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



+ 4
- 0
newLang/docs/diagnostics.md 查看文件

@@ -85,6 +85,8 @@ After 20 errors the next one is replaced by E221 and compilation stops.
| Code | Symbol | Message |
|---|---|---|
| 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)

@@ -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 |
| E221 | `RES_ERROR_LIMIT` | TOO MANY ERRORS; COMPILATION STOPPED |
| 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 |

+ 12
- 2
newLang/docs/memory-map.md 查看文件

@@ -51,10 +51,11 @@ 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` | 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
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 |

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
`$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
`$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


+ 117
- 0
newLang/planning/claims/E03-S02.md 查看文件

@@ -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).

+ 5
- 1
newLang/scripts/build-parser-test.ps1 查看文件

@@ -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.
[CmdletBinding()]
param()
@@ -37,6 +37,10 @@ $script:ParserFixtures = [ordered]@{
"recover.n8" = @("$p\recover.n8", "exact", "$p\recover.expect")
"noprog.n8" = @("$p\noprog.n8", "exact", "$p\noprog.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 `


+ 87
- 5
newLang/scripts/verify-parser-test.ps1 查看文件

@@ -28,22 +28,97 @@ try {
if ($mb[3] -ne 0xA5) { throw ("C128 reported FAIL (code `${0:X2})" -f $mb[4]) }

# ------------------------------------------------------------ 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) {
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
while ($dump[$p] -ne 0xFD) {
while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFC) {
$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]))
$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++
$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"
$listing = (& $c1541 -attach $disk -list 2>&1) | Out-String
function Get-ErrLines([string]$base) {
@@ -114,6 +189,13 @@ foreach ($name in $ParserFixtures.Keys) {
$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" }
Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail)
if ($problems) { $failures += "${name}: " + ($problems -join "; ") }


+ 4
- 0
newLang/src/common/diagcat.inc 查看文件

@@ -65,6 +65,8 @@

; ---------------------------------------------------- methods and returns
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
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_ERROR_LIMIT, 221, "TOO MANY ERRORS; COMPILATION STOPPED"
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"

+ 44
- 1
newLang/src/common/parser.inc 查看文件

@@ -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
PARSER_INC = 1

; Top-level type kinds (type table column C_TYPE_KIND)
TYPE_NONE = 0
TYPE_INTERFACE = 1
TYPE_CLASS = 2
TYPE_PROGRAM = 3

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

+ 4
- 0
newLang/src/compiler/mmu.s 查看文件

@@ -109,6 +109,10 @@ mmu_init:
lda #>mmu_nmi_guard
sta NMINV+1
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
.segment "COMMON"


+ 17
- 1
newLang/src/compiler/names.s 查看文件

@@ -20,7 +20,7 @@
.importzp mmu_ptr, mmu_src
.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_heads, name_recs

@@ -49,6 +49,22 @@ name_intern:
@found: clc
@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,
; unless an earlier declaration already did. The spelling must normalize to
; the same name (the caller interned it from the same token).


+ 860
- 73
newLang/src/compiler/parser.s
文件差異過大導致無法顯示
查看文件


+ 9
- 0
newLang/tests/parser/lifecycle.expect 查看文件

@@ -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

+ 25
- 0
newLang/tests/parser/lifecycle.n8 查看文件

@@ -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

+ 5
- 0
newLang/tests/parser/members.expect 查看文件

@@ -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

+ 24
- 0
newLang/tests/parser/members.n8 查看文件

@@ -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

+ 103
- 2
newLang/tests/parser/parser-test.s 查看文件

@@ -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,
; 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
; per declared top-level type: kind, line lo, line hi, spelling length,
; 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
; 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 diag_init, diag_finish, diag_screen
.import parse_file, type_kind, type_line_lo, type_line_hi
.import tb_get, mem_count

DUMP_SIZE = $2000

@@ -112,6 +117,9 @@ test_entry:
jsr diag_finish
jsr emit
jsr dump_types
lda #$FC
jsr emit
jsr dump_members
@advance:
lda #$FD
jsr emit
@@ -211,6 +219,90 @@ dump_types:
bne @next
@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
cpy #>(dump + DUMP_SIZE)
bcc @room
@@ -262,7 +354,11 @@ fixtures:
FIXTURE "RECOVER.N8"
FIXTURE "NOPROG.N8"
FIXTURE "EOFCLASS.N8"
FIXTURE_COUNT = 24
FIXTURE "MEMBERS.N8"
FIXTURE "LIFECYCLE.N8"
FIXTURE "SCOPES.N8"
FIXTURE "PRIVATE.N8"
FIXTURE_COUNT = 28

messages:
ok_msg: .byte "NOVA128 PARSER TEST OK", $0D, $00
@@ -279,4 +375,9 @@ id: .res 1
emit_x: .res 1
emit_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

+ 14
- 0
newLang/tests/parser/private.expect 查看文件

@@ -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

+ 51
- 0
newLang/tests/parser/private.n8 查看文件

@@ -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

+ 14
- 0
newLang/tests/parser/scopes.expect 查看文件

@@ -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

+ 43
- 0
newLang/tests/parser/scopes.n8 查看文件

@@ -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

Loading…
取消
儲存

Powered by TurnKey Linux.