Przeglądaj źródła

newLang: add the top-level declaration parser (E03-S01)

parser.s runs two passes over the cached source. Pass 1 parses interfaces,
classes (implements lists, fields, method headers), and the one program,
records top-level types in a bank-1 table, tracks method/program/if/while/
for blocks, checks duplicate locals, and reports syntax, ordering, and
duplicate errors with line-level recovery. Pass 2 reports type references
that name no declared type, so forward references are legal.

Verified in x128 against 24 fixtures through the real ERR files: 7/7
accepted, 10/12 rejected grammar fixtures at their sidecar positions
(line_cont and multi_stmt are statement-level, pending E03-S03), and five
parser fixtures matching their complete diagnostic lists. A pass-order
mutation is caught. D121 (proposed): method names may be keywords, as
spec 5.1's `method print()` requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
Daniel Covington 6 dni temu
rodzic
commit
e478321f42
21 zmienionych plików z 1470 dodań i 0 usunięć
  1. +7
    -0
      newLang/docs/architecture.md
  2. +1
    -0
      newLang/docs/decisions.md
  3. +8
    -0
      newLang/docs/diagnostics.md
  4. +10
    -0
      newLang/docs/memory-map.md
  5. +88
    -0
      newLang/planning/claims/E03-S01.md
  6. +61
    -0
      newLang/scripts/build-parser-test.ps1
  7. +122
    -0
      newLang/scripts/verify-parser-test.ps1
  8. +8
    -0
      newLang/src/common/diagcat.inc
  9. +13
    -0
      newLang/src/common/parser.inc
  10. +808
    -0
      newLang/src/compiler/parser.s
  11. +2
    -0
      newLang/tests/parser/eofclass.expect
  12. +2
    -0
      newLang/tests/parser/eofclass.n8
  13. +2
    -0
      newLang/tests/parser/noprog.expect
  14. +2
    -0
      newLang/tests/parser/noprog.n8
  15. +4
    -0
      newLang/tests/parser/order.expect
  16. +8
    -0
      newLang/tests/parser/order.n8
  17. +282
    -0
      newLang/tests/parser/parser-test.s
  18. +6
    -0
      newLang/tests/parser/recover.expect
  19. +17
    -0
      newLang/tests/parser/recover.n8
  20. +5
    -0
      newLang/tests/parser/unknown.expect
  21. +14
    -0
      newLang/tests/parser/unknown.n8

+ 7
- 0
newLang/docs/architecture.md Wyświetl plik

@@ -167,6 +167,13 @@ semantic storage. See `docs/platform-display-clock.md` for evidence.
The compiler reparses a buffered/streamed source rather than retaining a large
pointer-rich abstract syntax tree.

E03-S01 implements the declaration-level part of this as `parse_file`
(`parser.s`): pass 1 parses declarations and method headers, records
top-level types, tracks block structure, and reports syntax, ordering, and
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.

### Pass 1 — declarations and layout

- tokenize the complete source;


+ 1
- 0
newLang/docs/decisions.md Wyświetl plik

@@ -47,6 +47,7 @@ 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 `.`. |

## Deferred



+ 8
- 0
newLang/docs/diagnostics.md Wyświetl plik

@@ -45,6 +45,13 @@ After 20 errors the next one is replaced by E221 and compilation stops.
| E028 | `SYN_EXPECTED_INTERFACE_NAME` | INTERFACE NAME EXPECTED |
| E029 | `SYN_INTERFACE_METHOD_BODY` | INTERFACE METHODS HAVE NO BODY |
| E030 | `SYN_UNEXPECTED_TOKEN` | % IS NOT ALLOWED HERE |
| E031 | `SYN_EXPECTED_PROGRAM` | PROGRAM DECLARATION EXPECTED |
| E032 | `SYN_EXPECTED_END_INTERFACE` | END INTERFACE EXPECTED |
| E033 | `SYN_EXPECTED_LPAREN` | '(' EXPECTED |
| E034 | `SYN_EXPECTED_AS` | AS EXPECTED |
| E035 | `SYN_EXPECTED_TYPE` | TYPE EXPECTED |
| E036 | `SYN_EXPECTED_END_WHILE` | END WHILE EXPECTED |
| E037 | `SYN_EXPECTED_END_FOR` | END FOR EXPECTED |

## Names and declarations (E060-E079)

@@ -92,3 +99,4 @@ 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 |

+ 10
- 0
newLang/docs/memory-map.md Wyświetl plik

@@ -51,6 +51,10 @@ 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) |

BSS is not zeroed at load: every table must be cleared by its owner before
use (`names_init`, `parser.s` `tt_clear`).
| `$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`
@@ -73,6 +77,12 @@ 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`.

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
`mmu_shutdown`. Two test programs read wrong values this way before the rule
was written down.

## Interrupts

- **IRQ:** masked in both compiler states. The ROM IRQ path (`$FF17`) jumps via


+ 88
- 0
newLang/planning/claims/E03-S01.md Wyświetl plik

@@ -0,0 +1,88 @@
---
story: E03-S01
owner: e03_toplevel_parser
status: done
started: 2026-11-07
depends-on: [E02-S02, E02-S03, E02-S04]
---

# E03-S01 Claim

## Intended change

Add `src/compiler/parser.s`: a two-pass declaration-level parser. Pass 1
parses interfaces, classes (with implements lists, fields, and method
headers), and exactly one program, records top-level types in a bank-1
table, tracks block structure inside method and program bodies, and reports
syntax and duplicate-name errors with recovery. Pass 2 replays the source
and reports type references that name no declared interface or class, so
forward references work.

## File area

- `src/compiler/parser.s`, `src/common/parser.inc`
- `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`
- `planning/claims/E03-S01.md`
- generated files under `build/`

## Acceptance evidence

- [x] Parses interfaces, classes, implements lists, and exactly one program:
all 7 accepted fixtures give no diagnostics and the type table matches
every interface/class/program declaration and line; `noprog.n8` reports
PROGRAM EXPECTED; a second or late declaration is an ordering error.
- [x] Supports forward top-level type references: `forward.n8` and
`decls.n8` use types before declaring them and pass; `unknown.n8` reports
exactly its four truly unknown names. Swapping the pass order (checking
references first) fails 6 accepted fixtures, so pass 2 is what makes
forward references work.
- [x] Rejects duplicate names and invalid ordering with recovery to later
lines: `order.n8` (late class, late duplicate interface) and `recover.n8`
(five independent errors) match their full expected diagnostic lists
exactly; `eofclass.n8` shows end-of-file errors are not cascaded.
- [x] Accepted/rejected fixtures from E00-S02 pass: 7/7 accepted; 10/12
rejected report their sidecar diagnostic at the sidecar line (and column
where given). `line_cont.n8` and `multi_stmt.n8` need expression and
statement parsing, which E03-S03's own criteria cover; the test lists them
as pending and confirms they raise no false diagnostics meanwhile.

## Coordination notes

- Statement and expression bodies are skimmed line by line here (block
keywords tracked). `line_cont.n8` and `multi_stmt.n8` need the statement
and expression grammar that E03-S03 owns ("Rejects multiple statements per
line and cross-line expressions"); the test lists them as pending E03-S03
and checks that they raise no false diagnostics meanwhile.
- Member-name uniqueness and access rules are E03-S02. Parameters and `var`
locals already share one duplicate-checked scope here because
`dup_decl.n8` needs it.

## Work log

- Appended diagnostics E031-E037 and E222 to the catalog; docs regenerated.
- First x128 runs found: the bank-1 type table was never cleared (power-on
bytes looked like declarations); the test's PASS branch fell through when
`ok_msg - messages` was 0; and `method print()` was rejected because
`print` is a keyword. The last is a spec conflict, resolved by D121
(proposed).
- Verifier fix: PowerShell unrolls a one-element array returned from a
function, so single-line ERR files read as one string.

## Completion

- Tests run: `scripts/verify-parser-test.ps1` (pass plus pass-order mutation),
`scripts/build-diag-test.ps1` catalog check.
- Results: 24 fixtures as expected (2 pending E03-S03); mutation caught.
- Files changed: `src/compiler/parser.s`, `src/common/parser.inc`,
`src/common/diagcat.inc`, `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` (D121),
this claim.
- Remaining risks: D121 awaits approval; pass-2 diagnostics come after all
pass-1 diagnostics in NAME.ERR (not sorted by line); `implements` accepts a
class name until interface conformance (E03-S06); statement lines are only
skimmed, so a block keyword inside a skimmed expression is not possible
today but must stay so when E03-S03 lands.

+ 61
- 0
newLang/scripts/build-parser-test.ps1 Wyświetl plik

@@ -0,0 +1,61 @@
# E03-S01: 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()

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

$g = Join-Path $project "tests\grammar"
$p = Join-Path $project "tests\parser"
# disk name -> @(source, group, expect file)
$script:ParserFixtures = [ordered]@{
"minimal.n8" = @("$g\accepted\minimal.n8", "accepted", $null)
"decls.n8" = @("$g\accepted\decls.n8", "accepted", $null)
"arrays.n8" = @("$g\accepted\arrays.n8", "accepted", $null)
"exprs.n8" = @("$g\accepted\exprs.n8", "accepted", $null)
"forward.n8" = @("$g\accepted\forward.n8", "accepted", $null)
"nulls.n8" = @("$g\accepted\nulls.n8", "accepted", $null)
"stmts.n8" = @("$g\accepted\stmts.n8", "accepted", $null)
"badchar.n8" = @("$g\rejected\bad_char.n8", "rejected", "$g\rejected\bad_char.expect")
"badclass.n8" = @("$g\rejected\bad_class.n8", "rejected", "$g\rejected\bad_class.expect")
"badiface.n8" = @("$g\rejected\bad_iface.n8", "rejected", "$g\rejected\bad_iface.expect")
"badmethod.n8" = @("$g\rejected\bad_method.n8", "rejected", "$g\rejected\bad_method.expect")
"dupdecl.n8" = @("$g\rejected\dup_decl.n8", "rejected", "$g\rejected\dup_decl.expect")
"ifacebody.n8" = @("$g\rejected\iface_body.n8", "rejected", "$g\rejected\iface_body.expect")
"linecont.n8" = @("$g\rejected\line_cont.n8", "pending", "$g\rejected\line_cont.expect")
"missclass.n8" = @("$g\rejected\miss_class.n8", "rejected", "$g\rejected\miss_class.expect")
"missend.n8" = @("$g\rejected\miss_end.n8", "rejected", "$g\rejected\miss_end.expect")
"missif.n8" = @("$g\rejected\miss_if.n8", "rejected", "$g\rejected\miss_if.expect")
"missmeth.n8" = @("$g\rejected\miss_meth.n8", "rejected", "$g\rejected\miss_meth.expect")
"multistmt.n8" = @("$g\rejected\multi_stmt.n8", "pending", "$g\rejected\multi_stmt.expect")
"order.n8" = @("$p\order.n8", "exact", "$p\order.expect")
"unknown.n8" = @("$p\unknown.n8", "exact", "$p\unknown.expect")
"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")
}

$link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out `
-Config (Join-Path $project "cfg\compiler.cfg") -Sources @(
(Join-Path $project "src\compiler\mmu.s"),
(Join-Path $project "src\compiler\diskio.s"),
(Join-Path $project "src\compiler\source.s"),
(Join-Path $project "src\compiler\lexer.s"),
(Join-Path $project "src\compiler\names.s"),
(Join-Path $project "src\compiler\diagnostics.s"),
(Join-Path $project "src\compiler\parser.s"),
(Join-Path $project "tests\parser\parser-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }

$files = @(@{ Name = "parser-test"; Path = $link.Program; Type = "prg" })
foreach ($name in $ParserFixtures.Keys) {
$pet = Join-Path $out ($name -replace '\.n8$', '.pet')
[IO.File]::WriteAllBytes($pet, (ConvertTo-Nova128Petscii (Get-Content -Raw -LiteralPath $ParserFixtures[$name][0])))
$files += @{ Name = $name; Path = $pet; Type = "seq" }
}
New-Nova128Disk -Path (Join-Path $out "parser-test.d81") -Files $files
Write-Host ("built {0}; {1} fixtures on parser-test.d81" -f $link.Program, $ParserFixtures.Count)

+ 122
- 0
newLang/scripts/verify-parser-test.ps1 Wyświetl plik

@@ -0,0 +1,122 @@
# E03-S01: run the parser over every fixture in x128 and check the ERR files
# and the recorded top-level types against the fixtures' expectations.
[CmdletBinding()]
param([int]$Port = 6535, [int]$TimeoutSeconds = 300)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
. (Join-Path $PSScriptRoot "build-parser-test.ps1") # defines $ParserFixtures
$out = Join-Path $project "build\parser-test"
$disk = Join-Path $out "parser-test.d81"

# Catalog: symbol -> number.
$codes = @{}
foreach ($line in Get-Content (Join-Path $project "src\common\diagcat.inc")) {
$m = [regex]::Match($line, '^\s*DIAGNOSTIC\s+(\w+),\s*(\d+),')
if ($m.Success) { $codes[$m.Groups[1].Value] = [int]$m.Groups[2].Value }
}

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "parser-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
$dump = Read-Nova128Memory $session ($mb[8] + 256 * $mb[9]) ($mb[10] + 256 * $mb[11])
} finally {
Stop-Nova128X128 $session
}
if ($mb[3] -ne 0xA5) { throw ("C128 reported FAIL (code `${0:X2})" -f $mb[4]) }

# ------------------------------------------------------------ decode dump
$records = @(); $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 = @() }
$p += 4
while ($dump[$p] -ne 0xFD) {
$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]$_ } })
$r.Types += ("{0} {1} {2}" -f @("", "interface", "class", "program")[$dump[$p]], $spell, ($dump[$p + 1] + 256 * $dump[$p + 2]))
$p += 4 + $len
}
$p++
$records += $r
}

$c1541 = Get-ViceTool "c1541"
$listing = (& $c1541 -attach $disk -list 2>&1) | Out-String
function Get-ErrLines([string]$base) {
$dest = Join-Path $out "$base.err.bin"
if (Test-Path $dest) { Remove-Item $dest -Force }
if ($listing -notmatch ('"' + [regex]::Escape("$base.err") + '"')) { return $null }
& $c1541 -attach $disk -read "$base.err,s" $dest 2>&1 | Out-Null
# The leading comma keeps a one-line file an array (PowerShell unrolls it).
return ,@(([Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($dest)).TrimEnd("`r")) -split "`r")
}
function Parse-Err([string]$line) {
$m = [regex]::Match($line, '^E(\d{3}) \S+ (\d+) (\d+) ')
return [pscustomobject]@{ Code = [int]$m.Groups[1].Value; Line = [int]$m.Groups[2].Value; Col = [int]$m.Groups[3].Value }
}

# ---------------------------------------------------------------- check
$failures = @(); $i = 0; $pending = @()
foreach ($name in $ParserFixtures.Keys) {
$src, $group, $expect = $ParserFixtures[$name]
$base = $name -replace '\.n8$', ''
$r = $records[$i]; $i++
$errLines = Get-ErrLines $base
$problems = @()
if ($r.Load -ne 0) { $problems += "src_load returned $($r.Load)" }
switch ($group) {
"accepted" {
if ($r.Count -ne 0) { $problems += "$($r.Count) diagnostic(s): $($errLines -join ' | ')" }
if ($null -ne $errLines) { $problems += "an ERR file exists" }
$want = @()
$n = 0
foreach ($line in Get-Content -LiteralPath $src) {
$n++
$m = [regex]::Match($line, '^\s*(interface|class|program)\s+(\w+)')
if ($m.Success) { $want += ("{0} {1} {2}" -f $m.Groups[1].Value, $m.Groups[2].Value, $n) }
}
if ((($r.Types | Sort-Object) -join ';') -ne (($want | Sort-Object) -join ';')) {
$problems += "types [$($r.Types -join '; ')] expected [$($want -join '; ')]"
}
$detail = "{0} types" -f $r.Types.Count
}
"rejected" {
$text = Get-Content -Raw -LiteralPath $expect
$sym = [regex]::Match($text, '(?m)^diagnostic: (\w+)').Groups[1].Value
$wantLine = [int][regex]::Match($text, '(?m)^line: (\d+)').Groups[1].Value
$colMatch = [regex]::Match($text, '(?m)^column: (\d+)')
if ($null -eq $errLines) { $problems += "no ERR file" }
else {
$first = Parse-Err $errLines[0]
if ($first.Code -ne $codes[$sym] -or $first.Line -ne $wantLine -or
($colMatch.Success -and $first.Col -ne [int]$colMatch.Groups[1].Value)) {
$problems += "first diagnostic '$($errLines[0])' expected $sym (E$($codes[$sym])) at line $wantLine"
}
}
$detail = if ($errLines) { $errLines[0] } else { "" }
}
"pending" {
if ($r.Count -ne 0) { $problems += "false diagnostic(s) before E03-S03: $($errLines -join ' | ')" }
$pending += $name
$detail = "pending E03-S03 (statement grammar); no false errors"
}
"exact" {
$want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") } | ForEach-Object {
$f = $_ -split ' '; "{0} {1} {2}" -f $codes[$f[0]], $f[1], $f[2] })
$got = @($errLines | Where-Object { $_ } | ForEach-Object { $e = Parse-Err $_; "{0} {1} {2}" -f $e.Code, $e.Line, $e.Col })
if (($got -join ';') -ne ($want -join ';')) {
$problems += "diagnostics [$($got -join '; ')] expected [$($want -join '; ')]"
}
$detail = "{0} diagnostics" -f $got.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 "; ") }
}
if ($failures) { throw ("parser verification failed:`n " + ($failures -join "`n ")) }
Write-Host ("verified parser: {0} fixtures ({1} pending E03-S03)" -f $ParserFixtures.Count, $pending.Count)

+ 8
- 0
newLang/src/common/diagcat.inc Wyświetl plik

@@ -40,6 +40,13 @@
DIAGNOSTIC SYN_EXPECTED_INTERFACE_NAME, 28, "INTERFACE NAME EXPECTED"
DIAGNOSTIC SYN_INTERFACE_METHOD_BODY, 29, "INTERFACE METHODS HAVE NO BODY"
DIAGNOSTIC SYN_UNEXPECTED_TOKEN, 30, "% IS NOT ALLOWED HERE"
DIAGNOSTIC SYN_EXPECTED_PROGRAM, 31, "PROGRAM DECLARATION EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_INTERFACE, 32, "END INTERFACE EXPECTED"
DIAGNOSTIC SYN_EXPECTED_LPAREN, 33, "'(' EXPECTED"
DIAGNOSTIC SYN_EXPECTED_AS, 34, "AS EXPECTED"
DIAGNOSTIC SYN_EXPECTED_TYPE, 35, "TYPE EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_WHILE, 36, "END WHILE EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_FOR, 37, "END FOR EXPECTED"

; -------------------------------------------- names and declarations
DIAGNOSTIC SEM_DUPLICATE_NAME, 60, "% IS ALREADY DECLARED"
@@ -66,3 +73,4 @@
; ------------------------------------------------------- resource limits
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"

+ 13
- 0
newLang/src/common/parser.inc Wyświetl plik

@@ -0,0 +1,13 @@
; parser.inc - top-level type table and parser limits (E03-S01).

.ifndef PARSER_INC
PARSER_INC = 1

TYPE_NONE = 0
TYPE_INTERFACE = 1
TYPE_CLASS = 2
TYPE_PROGRAM = 3

PARSE_MAX_DEPTH = 16 ; nested method/program/if/while/for blocks

.endif

+ 808
- 0
newLang/src/compiler/parser.s Wyświetl plik

@@ -0,0 +1,808 @@
; parser.s - top-level declaration parser (E03-S01).
;
; parse_file runs two passes over the loaded source (call after src_load,
; diag_init, and names_init):
; pass 1 parses interfaces, classes (implements lists, fields, method
; headers), and the one program; records each top-level type in
; the bank-1 type table; reports syntax, ordering, and duplicate
; errors.
; pass 2 replays the source and reports only type references that do
; not name a declared interface or class (forward references are
; therefore legal).
; Method and program bodies are parsed line by line: `var` declarations are
; parsed and duplicate-checked; if/while/for/end blocks are tracked; other
; statement lines are skimmed until E03-S03 parses them.
;
; Recovery: an error skips the rest of its line. A mismatched `end X`
; reports the innermost missing end and closes blocks up to X. A top-level
; keyword or end of file inside a declaration reports the missing end once.
; Errors at end of file are reported at (last line + 1, column 1).
; If diag_report hits the error limit, parse_file returns at once.

.setcpu "6502"
.include "../common/tokens.inc"
.include "../common/errors.inc"
.include "../common/parser.inc"

.import lex_reset, lex_next
.import tok_kind, tok_line, tok_col, tok_len, tok_err
.import src_line
.import name_intern, name_declare
.import diag_report, diag_line, diag_col, diag_arg1, diag_arg1_len
.import mmu_peek1, mmu_poke1
.importzp mmu_ptr

.export parse_file, type_kind, type_line_lo, type_line_hi

.macro IFTOK kind, target
lda tok_kind
cmp #kind
beq target
.endmacro

; ------------------------------------------------------------------ CODE
.segment "CODE"

parse_file:
tsx
stx parse_sp
jsr tt_clear ; bank-1 BSS is not zeroed at load
lda #$01
sta pass
jsr parse_pass
lda #$02
sta pass
jsr parse_pass
rts

; Error limit reached: return straight to parse_file's caller.
stop_parse:
ldx parse_sp
txs
rts

parse_pass:
jsr lex_reset
lda #$00
sta program_seen
sta pending_end
sta eof_reported
jsr next
top: jsr skip_blank
lda pending_end ; stray `end X` left by a body: reported
beq :+
lda #$00
sta pending_end
jsr skip_line
jmp top
: IFTOK TOK_EOF, @eof
IFTOK KW_INTERFACE, @iface
IFTOK KW_CLASS, @class
IFTOK KW_PROGRAM, @program
jsr arg_token ; anything else at top level
lda #E_SYN_UNEXPECTED_TOKEN
jsr err1
jsr skip_line
jmp top
@eof: lda program_seen
bne @done
lda #E_SYN_EXPECTED_PROGRAM
jsr err_eof
@done: rts
@iface: jsr check_order
jsr parse_interface
jmp top
@class: jsr check_order
jsr parse_class
jmp top
@program:
jsr check_order
jsr parse_program
jmp top

; Declarations after the program are out of order (still parsed).
check_order:
lda program_seen
beq :+
jsr arg_token
lda #E_SYN_UNEXPECTED_TOKEN
jmp err1
: rts

; ----------------------------------------------------------- interface
parse_interface:
jsr next
lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jsr err1
jsr skip_line
jmp @members
: lda #TYPE_INTERFACE
jsr declare_type
jsr next
jsr expect_eol
@members:
lda #$00
sta body_err
@loop: jsr skip_blank
IFTOK TOK_EOF, @eof
jsr at_toplevel_kw
beq @missing
IFTOK KW_END, @end
lda body_err
bne @skip
IFTOK KW_METHOD, @method
lda tok_kind
cmp #KW_PUBLIC
beq @access
cmp #KW_PRIVATE
bne @body
@access:
jsr arg_token ; interfaces have no access modifiers
lda #E_SYN_UNEXPECTED_TOKEN
jsr err1
jmp @skip
@body: lda #E_SYN_INTERFACE_METHOD_BODY
jsr err1
lda #$01
sta body_err
@skip: jsr skip_line
jmp @loop
@method:
jsr parse_signature
jmp @loop
@end: jsr next
IFTOK KW_INTERFACE, @close
lda body_err ; `end method` closes a reported body
bne :+
lda #E_SYN_INTERFACE_METHOD_BODY
jsr err1
: lda #$00
sta body_err
jsr skip_line
jmp @loop
@close: jsr next
jmp expect_eol
@eof: lda #E_SYN_EXPECTED_END_INTERFACE
jmp err_eof
@missing:
lda #E_SYN_EXPECTED_END_INTERFACE
jmp err1

; --------------------------------------------------------------- class
parse_class:
jsr next
lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jsr err1
jsr skip_line
jmp @members
: lda #TYPE_CLASS
jsr declare_type
jsr next
lda tok_kind
cmp #KW_IMPLEMENTS
bne @header_end
jsr next
@impl: lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_INTERFACE_NAME
jsr err1
jsr skip_line
jmp @members
: jsr type_ref
jsr next
lda tok_kind
cmp #TOK_COMMA
bne @header_end
jsr next
jmp @impl
@header_end:
jsr expect_eol
@members:
@loop: lda pending_end ; `end X` seen inside a method body
beq @scan
lda tok_kind
cmp #KW_CLASS
bne @lost
lda #$00
sta pending_end
jsr next
jmp expect_eol
@lost: lda #E_SYN_EXPECTED_END_CLASS
jmp err1
@scan: jsr skip_blank
IFTOK TOK_EOF, @eof
jsr at_toplevel_kw
beq @missing
IFTOK KW_END, @end
lda tok_kind
cmp #KW_PUBLIC
beq @access
cmp #KW_PRIVATE
bne @member
@access:
jsr next
@member:
IFTOK KW_METHOD, @method
jsr is_type_start
bcc @stray
jsr parse_type ; field: type name
bcs @skip
lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jsr err1
jmp @skip
: jsr intern_tok
jsr next
jsr expect_eol
jmp @loop
@method:
jsr parse_signature ; a bad header still has a body
lda #KW_METHOD
jsr parse_body
jmp @loop
@stray: jsr arg_token
lda #E_SYN_UNEXPECTED_TOKEN
jsr err1
@skip: jsr skip_line
jmp @loop
@end: jsr next
IFTOK KW_CLASS, @close
jsr arg_token
lda #E_SYN_UNEXPECTED_TOKEN
jsr err1
jmp @skip
@close: jsr next
jmp expect_eol
@eof: lda #E_SYN_EXPECTED_END_CLASS
jmp err_eof
@missing:
lda #E_SYN_EXPECTED_END_CLASS
jmp err1

; ------------------------------------------------------------- program
parse_program:
jsr next
lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jsr err1
jsr skip_line
jmp @body
: lda #TYPE_PROGRAM
jsr declare_type
jsr next
jsr expect_eol
@body: jsr scope_clear
lda #KW_PROGRAM
jsr parse_body
lda #$01
sta program_seen
rts

; ------------------------------------------------------ method headers
; method name ( [name as type {, name as type}] ) [as type] EOL
; Starts a new local scope holding the parameters. C=1 if the header had
; an error (the line has been skipped).
parse_signature:
jsr scope_clear
jsr next
lda tok_kind ; member names may be keywords (D121),
cmp #TOK_IDENT ; e.g. `method print()` in spec 5.1
beq :+
cmp #TOK_KW_FIRST
bcs :+
lda #E_SYN_EXPECTED_IDENTIFIER
jmp @fail
: jsr intern_tok
jsr next
lda tok_kind
cmp #TOK_LPAREN
beq :+
lda #E_SYN_EXPECTED_LPAREN
jmp @fail
: jsr next
IFTOK TOK_RPAREN, @close
@param: lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jmp @fail
: jsr declare_local
jsr next
lda tok_kind
cmp #KW_AS
beq :+
lda #E_SYN_EXPECTED_AS
jmp @fail
: jsr next
jsr parse_type
bcs @skip
IFTOK TOK_COMMA, @more
IFTOK TOK_RPAREN, @close
lda #E_SYN_EXPECTED_RPAREN
jmp @fail
@more: jsr next
jmp @param
@close: jsr next
lda tok_kind
cmp #KW_AS
bne @eol
jsr next
jsr parse_type
bcs @skip
@eol: jsr expect_eol
clc
rts
@fail: jsr err1
@skip: jsr skip_line
sec
rts

; ---------------------------------------------------------------- types
; primitive | Name, optionally [number]. C=1 after reporting an error.
parse_type:
jsr is_type_start
bcs :+
lda #E_SYN_EXPECTED_TYPE
jsr err1
sec
rts
: lda tok_kind
cmp #TOK_IDENT
bne :+
jsr type_ref
: jsr next
lda tok_kind
cmp #TOK_LBRACKET
bne @ok
jsr next
lda tok_kind
cmp #TOK_NUMBER
bne @bad
jsr next
lda tok_kind
cmp #TOK_RBRACKET
bne @bad
jsr next
@ok: clc
rts
@bad: jsr arg_token
lda #E_SYN_UNEXPECTED_TOKEN
jsr err1
sec
rts

; C=1 when the token can start a type.
is_type_start:
lda tok_kind
ldx #prim_count ; primitives, then TOK_IDENT
@find: cmp prim_kinds,x
beq @yes
dex
bpl @find
clc
rts
@yes: sec
rts

; Pass 2: the identifier must name a declared interface or class.
type_ref:
jsr intern_tok
beq @done
ldx pass
cpx #$02
bne @done
tay
lda #0 ; type_kind table
jsr tt_get
cmp #TYPE_INTERFACE
beq @done
cmp #TYPE_CLASS
beq @done
jsr arg_token
lda #E_SEM_UNKNOWN_NAME
jsr err2
@done: rts

; ---------------------------------------------------------------- bodies
; Parse a method or program body. A = KW_METHOD or KW_PROGRAM.
parse_body:
sta block_stack
lda #$01
sta depth
@loop: jsr skip_blank
lda tok_kind
cmp #TOK_EOF
bne :+
jmp @eof
: jsr at_toplevel_kw
bne :+
jmp @interrupted
: IFTOK KW_VAR, @var
IFTOK KW_IF, @open
IFTOK KW_WHILE, @open
IFTOK KW_FOR, @open
IFTOK KW_END, @end
jsr skip_line ; statement: E03-S03 parses these
jmp @loop
@var: jsr parse_var
jmp @loop
@open: ldx depth
cpx #PARSE_MAX_DEPTH
bcc :+
lda #E_RES_NESTING_TOO_DEEP
jsr err1
jsr skip_line
jmp @loop
: lda tok_kind
sta block_stack,x
inc depth
jsr skip_line ; condition / loop header: E03-S03
jmp @loop

@end: jsr next ; the closer keyword
lda tok_kind
ldx depth
cmp block_stack-1,x
beq @matched
jsr closer_in_stack ; X = depth of the matching block or 0
cpx #$00
bne @unwind
cmp #KW_CLASS ; closes an outer declaration?
beq @outer
cmp #KW_INTERFACE
beq @outer
jsr arg_token ; `end method` in a program, `end foo`...
lda #E_SYN_UNEXPECTED_TOKEN
jsr err1
jsr skip_line
jmp @loop
@outer: jsr report_missing_end
lda tok_kind
sta pending_end ; the class/interface parser closes it
lda #$00
sta depth
rts
@unwind:
stx unwind_to ; inner blocks were never closed
jsr report_missing_end
lda unwind_to
sta depth
@matched:
dec depth
jsr next
jsr expect_eol
lda depth
beq :+
jmp @loop
: rts

@eof: jsr missing_end_code
jsr err_eof
lda #$00
sta depth
rts
@interrupted:
jsr report_missing_end
lda #$00
sta depth
rts

; X = 1-based depth of the innermost block opened by tok_kind, or 0. A
; foreign `end class` / `end interface` reports the innermost block only.
closer_in_stack:
ldx depth
@scan: cmp block_stack-1,x
beq @found
dex
bne @scan
@found: rts

report_missing_end:
jsr missing_end_code
jmp err1

; A = E_ code for the innermost open block.
missing_end_code:
ldx depth
lda block_stack-1,x
ldx #end_count - 1
@find: cmp end_blocks,x
beq @found
dex
bpl @find
ldx #$00
@found: lda end_codes,x
rts

; var name as type EOL
parse_var:
jsr next
lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jsr err1
jmp skip_line
: jsr declare_local
jsr next
lda tok_kind
cmp #KW_AS
beq :+
lda #E_SYN_EXPECTED_AS
jsr err1
jmp skip_line
: jsr next
jsr parse_type
bcc :+
jmp skip_line
: jmp expect_eol

; --------------------------------------------------------- declarations
; Record the IDENT token as a top-level type of kind A (pass 1).
declare_type:
sta dk_kind
jsr intern_tok
beq @done
sta dk_id
ldx pass
cpx #$01
bne @done
lda dk_id
ldy tok_col
ldx tok_len
jsr name_declare
ldy dk_id
lda #0
jsr tt_get
beq @new
jsr arg_token
lda #E_SEM_DUPLICATE_NAME
jmp err1
@new: ldy dk_id
lda #0
ldx dk_kind
jsr tt_set
ldy dk_id
lda #1
ldx tok_line
jsr tt_set
ldy dk_id
lda #2
ldx tok_line+1
jsr tt_set
@done: rts

; Add the IDENT token to the current local scope; report a duplicate.
declare_local:
jsr intern_tok
beq @done
tax
lsr a ; byte = id / 8, bit = id & 7
lsr a
lsr a
tay
txa
and #$07
tax
lda bit_masks,x
and scope_bits,y
bne @dup
lda bit_masks,x
ora scope_bits,y
sta scope_bits,y
@done: rts
@dup: jsr arg_token
lda #E_SEM_DUPLICATE_NAME
jmp err1

scope_clear:
lda #$00
ldx #31
: sta scope_bits,x
dex
bpl :-
rts

; Intern the IDENT token. A = ID (Z=0), or A = 0 after a full-table error.
intern_tok:
lda tok_col
ldx tok_len
jsr name_intern
bcc :+
lda #E_RES_TOO_MANY_NAMES
jsr err1
lda #$00
: rts

; Zero all three type-table columns.
tt_clear:
lda #2
sta dk_kind ; column counter (reused scratch)
@col: ldy #$00
@byte: lda dk_kind
ldx #$00
jsr tt_set
iny
bne @byte
dec dk_kind
bpl @col
rts

; Type table in bank 1: A = column (0 kind, 1 line lo, 2 line hi), Y = ID.
tt_get: jsr tt_ptr
jmp mmu_peek1
tt_set: jsr tt_ptr
txa
jmp mmu_poke1
tt_ptr: clc
adc #>type_kind
sta mmu_ptr+1
lda #<type_kind
sta mmu_ptr
rts

; ---------------------------------------------------------------- tokens
; Advance, reporting lexer errors (pass 1); the lexer then yields EOL.
next: jsr lex_next
lda tok_kind
cmp #TOK_ERROR
bne :+
lda tok_err ; LEXERR_n -> E_LEX_INVALID_CHAR + n - 1
clc
adc #E_LEX_INVALID_CHAR - LEXERR_INVALID_CHAR
jsr err1
jmp next
: rts

skip_blank:
lda tok_kind
cmp #TOK_EOL
bne :+
jsr next
jmp skip_blank
: rts

; Skip to the end of the line and past its EOL (not past EOF).
skip_line:
lda tok_kind
cmp #TOK_EOF
beq @done
cmp #TOK_EOL
beq @eol
jsr next
jmp skip_line
@eol: jsr next
@done: rts

expect_eol:
lda tok_kind
cmp #TOK_EOL
beq @eol
cmp #TOK_EOF
beq @done
lda #E_SYN_EXPECTED_EOL
jsr err1
jmp skip_line
@eol: jmp next
@done: rts

; Z=1 when the token is interface/class/program (a new declaration).
at_toplevel_kw:
lda tok_kind
cmp #KW_INTERFACE
beq @yes
cmp #KW_CLASS
beq @yes
cmp #KW_PROGRAM
@yes: rts

; ----------------------------------------------------------- reporting
; diag_arg1 = the current token's source spelling (at most 16 bytes).
arg_token:
clc
lda #<(src_line - 1)
adc tok_col
sta diag_arg1
lda #>(src_line - 1)
adc #$00
sta diag_arg1+1
lda tok_len
cmp #17
bcc :+
lda #16
: sta diag_arg1_len
rts

; Report A at the current token in pass 1 / pass 2 only.
err1: ldx pass
cpx #$01
beq report
bne drop
err2: ldx pass
cpx #$02
beq report
drop: ldx #$00
stx diag_arg1_len
rts

; Report A at end of file (pass 1): line after the last, column 1. Only
; the first end-of-file error is reported; the rest would be cascades.
err_eof:
ldx pass
cpx #$01
bne drop
ldx eof_reported
bne drop
ldx #$01
stx eof_reported
tax
clc
lda tok_line
adc #$01
sta diag_line
lda tok_line+1
adc #$00
sta diag_line+1
lda #$01
sta diag_col
txa
jmp send

report: tax
lda tok_line
sta diag_line
lda tok_line+1
sta diag_line+1
lda tok_col
sta diag_col
txa
send: jsr diag_report
bcc :+
jmp stop_parse
: rts

.segment "RODATA"
prim_kinds:
.byte TOK_IDENT, KW_BOOLEAN, KW_BYTE, KW_CHAR, KW_INTEGER, KW_WORD
prim_count = * - prim_kinds - 1
end_blocks:
.byte KW_PROGRAM, KW_METHOD, KW_IF, KW_WHILE, KW_FOR
end_count = * - end_blocks
end_codes:
.byte E_SYN_EXPECTED_END_PROGRAM, E_SYN_EXPECTED_END_METHOD
.byte E_SYN_EXPECTED_END_IF, E_SYN_EXPECTED_END_WHILE, E_SYN_EXPECTED_END_FOR
bit_masks:
.byte $01, $02, $04, $08, $10, $20, $40, $80

; ---------------------------------------------------------------- TABLES
; Top-level types by name ID: kind, then declaration line low/high.
.segment "TABLES"
type_kind: .res 256
type_line_lo: .res 256
type_line_hi: .res 256

; ------------------------------------------------------------------- BSS
.segment "BSS"
parse_sp: .res 1
pass: .res 1
program_seen: .res 1
pending_end: .res 1
eof_reported: .res 1
body_err: .res 1
depth: .res 1
block_stack: .res PARSE_MAX_DEPTH
scope_bits: .res 32
dk_kind: .res 1
dk_id: .res 1
unwind_to: .res 1

+ 2
- 0
newLang/tests/parser/eofclass.expect Wyświetl plik

@@ -0,0 +1,2 @@
// one end-of-file error only: no extra PROGRAM EXPECTED cascade
SYN_EXPECTED_END_CLASS 3 1

+ 2
- 0
newLang/tests/parser/eofclass.n8 Wyświetl plik

@@ -0,0 +1,2 @@
class Open
private word x

+ 2
- 0
newLang/tests/parser/noprog.expect Wyświetl plik

@@ -0,0 +1,2 @@
// reported at end of file: line after the last, column 1
SYN_EXPECTED_PROGRAM 3 1

+ 2
- 0
newLang/tests/parser/noprog.n8 Wyświetl plik

@@ -0,0 +1,2 @@
class Lonely
end class

+ 4
- 0
newLang/tests/parser/order.expect Wyświetl plik

@@ -0,0 +1,4 @@
// declarations after the program are out of order but still parsed
SYN_UNEXPECTED_TOKEN 4 1
SYN_UNEXPECTED_TOKEN 7 1
SEM_DUPLICATE_NAME 7 11

+ 8
- 0
newLang/tests/parser/order.n8 Wyświetl plik

@@ -0,0 +1,8 @@
program Main
end program

class Late
end class

interface Late
end interface

+ 282
- 0
newLang/tests/parser/parser-test.s Wyświetl plik

@@ -0,0 +1,282 @@
; E03-S01: 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
; compare; accepted fixtures must leave none. The dump records:
; $FF, fixture index, src_load result, diagnostic count
; per declared top-level type: kind, line lo, line hi, spelling length,
; spelling bytes
; $FD
; then $FE at the end. Mailbox: MB_DATA+0/1 dump address, +2/3 length.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/parser.inc"

.import mmu_init, mmu_bank0, mmu_shutdown, mmu_peek1, kcall_inline
.importzp mmu_ptr
.import src_load
.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

DUMP_SIZE = $2000

.segment "LOADADDR"
.word $1C01

.segment "EXEHDR"
.word basic_end
.word 10
.byte $9E
.byte "7181"
.byte $00
basic_end:
.word $0000

.segment "MAILBOX"
.res MB_SIZE

.segment "ZEROPAGE"
dump_ptr: .res 2
fx_ptr: .res 2

.segment "STARTUP"
.export test_entry, dump
test_entry:
php
sei
ldx #MB_SIZE - 1
lda #$00
@clr: sta MB_BASE,x
dex
bpl @clr
lda #MB_MAGIC0_VALUE
sta MB_MAGIC0
lda #MB_MAGIC1_VALUE
sta MB_MAGIC1
lda #MB_PROD_PLATFORM
sta MB_PRODUCER
lda #MB_ST_RUNNING
sta MB_STATUS
jsr mmu_init
jsr mmu_bank0
lda #<dump
sta dump_ptr
lda #>dump
sta dump_ptr+1
lda #<fixtures
sta fx_ptr
lda #>fixtures
sta fx_ptr+1
lda #$00
sta failures
sta overflow
sta fx_index

@fixture:
lda #$FF
jsr emit
lda fx_index
jsr emit
ldy #0
lda (fx_ptr),y
sta name_lo
iny
lda (fx_ptr),y
sta name_hi
iny
lda (fx_ptr),y
sta name_len
lda name_lo
ldx name_hi
ldy name_len
jsr src_load
pha
jsr emit
pla
beq @parse
inc failures
lda #$00
jsr emit
jmp @advance
@parse: jsr names_init
lda name_lo
ldx name_hi
ldy name_len
jsr diag_init
lda #$00 ; ERR files are the evidence; keep it fast
sta diag_screen
jsr parse_file
jsr diag_finish
jsr emit
jsr dump_types
@advance:
lda #$FD
jsr emit
clc
lda fx_ptr
adc #3
sta fx_ptr
bcc :+
inc fx_ptr+1
: inc fx_index
lda fx_index
cmp #FIXTURE_COUNT
bne @fixture

lda #$FE
jsr emit
lda #<dump ; report while the private ZP is mapped
sta MB_DATA+0
lda #>dump
sta MB_DATA+1
sec
lda dump_ptr
sbc #<dump
sta MB_DATA+2
lda dump_ptr+1
sbc #>dump
sta MB_DATA+3
lda failures ; everything above $3FFF disappears behind
ora overflow ; BASIC ROM after mmu_shutdown: report first
sta MB_CODE
bne @fail
lda #MB_ST_PASS
ldy #ok_msg - messages ; may be 0: do not branch on it
jmp @report
@fail: lda #MB_ST_FAIL
ldy #fail_msg - messages
@report:
sta MB_STATUS
: lda messages,y
beq @end
sty print_y
jsr kcall_inline
.word CHROUT
ldy print_y
iny
bne :-
@end: jsr mmu_shutdown
plp
rts

.segment "CODE"

; Emit every name ID that the parser recorded as a top-level type.
dump_types:
lda #$01
sta id
@next: lda id
cmp names_count
beq :+
bcs @done
: lda #<type_kind
sta mmu_ptr
lda #>type_kind
sta mmu_ptr+1
ldy id
jsr mmu_peek1
beq @skip
jsr emit
lda #<type_line_lo
sta mmu_ptr
lda #>type_line_lo
sta mmu_ptr+1
ldy id
jsr mmu_peek1
jsr emit
lda #<type_line_hi
sta mmu_ptr
lda #>type_line_hi
sta mmu_ptr+1
ldy id
jsr mmu_peek1
jsr emit
lda id
jsr name_get
lda name_out_len
jsr emit
ldx #$00
: cpx name_out_len
beq @skip
lda name_out,x
stx emit_x
jsr emit
ldx emit_x
inx
bne :-
@skip: inc id
bne @next
@done: rts

emit: ldy dump_ptr+1
cpy #>(dump + DUMP_SIZE)
bcc @room
lda #$01
sta overflow
rts
@room: sty emit_y
ldy #$00
sta (dump_ptr),y
ldy emit_y
inc dump_ptr
bne :+
inc dump_ptr+1
: rts

.segment "RODATA"
.macro FIXTURE name
.local text, done
.word text
.byte done - text
.pushseg
.segment "DATA"
text: .byte name
done:
.popseg
.endmacro
fixtures:
FIXTURE "MINIMAL.N8"
FIXTURE "DECLS.N8"
FIXTURE "ARRAYS.N8"
FIXTURE "EXPRS.N8"
FIXTURE "FORWARD.N8"
FIXTURE "NULLS.N8"
FIXTURE "STMTS.N8"
FIXTURE "BADCHAR.N8"
FIXTURE "BADCLASS.N8"
FIXTURE "BADIFACE.N8"
FIXTURE "BADMETHOD.N8"
FIXTURE "DUPDECL.N8"
FIXTURE "IFACEBODY.N8"
FIXTURE "LINECONT.N8"
FIXTURE "MISSCLASS.N8"
FIXTURE "MISSEND.N8"
FIXTURE "MISSIF.N8"
FIXTURE "MISSMETH.N8"
FIXTURE "MULTISTMT.N8"
FIXTURE "ORDER.N8"
FIXTURE "UNKNOWN.N8"
FIXTURE "RECOVER.N8"
FIXTURE "NOPROG.N8"
FIXTURE "EOFCLASS.N8"
FIXTURE_COUNT = 24

messages:
ok_msg: .byte "NOVA128 PARSER TEST OK", $0D, $00
fail_msg: .byte "NOVA128 PARSER TEST FAIL", $0D, $00

.segment "BSS"
failures: .res 1
overflow: .res 1
fx_index: .res 1
name_lo: .res 1
name_hi: .res 1
name_len: .res 1
id: .res 1
emit_x: .res 1
emit_y: .res 1
print_y: .res 1
dump: .res DUMP_SIZE

+ 6
- 0
newLang/tests/parser/recover.expect Wyświetl plik

@@ -0,0 +1,6 @@
// five independent errors; each line after an error still parses
SYN_EXPECTED_IDENTIFIER 1 10
SYN_EXPECTED_INTERFACE_NAME 5 21
SYN_EXPECTED_IDENTIFIER 7 22
SEM_DUPLICATE_NAME 13 7
SYN_EXPECTED_END_IF 16 7

+ 17
- 0
newLang/tests/parser/recover.n8 Wyświetl plik

@@ -0,0 +1,17 @@
interface
method a()
end interface

class Box implements
private word size
public method get( as word
end method
end class

program Main
var x as word
var x as byte
while x
if x
end while
end program

+ 5
- 0
newLang/tests/parser/unknown.expect Wyświetl plik

@@ -0,0 +1,5 @@
// pass 2: Printable and Worker are forward references; the rest are unknown
SEM_UNKNOWN_NAME 1 25
SEM_UNKNOWN_NAME 2 11
SEM_UNKNOWN_NAME 3 28
SEM_UNKNOWN_NAME 13 12

+ 14
- 0
newLang/tests/parser/unknown.n8 Wyświetl plik

@@ -0,0 +1,14 @@
class Worker implements Missing, Printable
private Ghost helper
public method run(job as Phantom) as Printable
end method
end class

interface Printable
method print()
end interface

program Main
var w as Worker
var g as Nothing
end program

Ładowanie…
Anuluj
Zapisz

Powered by TurnKey Linux.