Procházet zdrojové kódy

newLang: add the diagnostic catalog and ERR writer (E02-S04)

diagcat.inc is the single catalog: one line per diagnostic with a stable
number in a per-category range. Included by errors.inc it gives E_*
constants; included by diagnostics.s it gives the message table.
docs/diagnostics.md is generated from it. The build rejects out-of-range
or duplicate numbers and any grammar .expect symbol missing from it.

diagnostics.s prints a two-line, 40-column-safe screen form and appends
full lines to NAME.ERR. NAME.ERR is scratched at the start and created
only when needed. After 20 errors, E221 replaces the next one and the
caller must stop. diskio.s gains output-file and DOS-command wrappers.

Verified in x128: screen RAM rows, diag.err and limit.err read back from
the D81, stale-file scratching, an unwritable ERR name (DOS 33 -> E201
once), and no open files after any phase. A counting mutation is caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
Daniel Covington před 6 dny
rodič
revize
c7fdf9aa3a
13 změnil soubory, kde provedl 1297 přidání a 4 odebrání
  1. +11
    -2
      newLang/docs/architecture.md
  2. +94
    -0
      newLang/docs/diagnostics.md
  3. +77
    -0
      newLang/planning/claims/E02-S04.md
  4. +145
    -0
      newLang/scripts/build-diag-test.ps1
  5. +85
    -0
      newLang/scripts/verify-diag-test.ps1
  6. +1
    -0
      newLang/src/common/c128.inc
  7. +68
    -0
      newLang/src/common/diagcat.inc
  8. +2
    -0
      newLang/src/common/diskio.inc
  9. +16
    -0
      newLang/src/common/errors.inc
  10. +539
    -0
      newLang/src/compiler/diagnostics.s
  11. +56
    -0
      newLang/src/compiler/diskio.s
  12. +199
    -0
      newLang/tests/diag/diag-test.s
  13. +4
    -2
      newLang/tests/grammar/README.md

+ 11
- 2
newLang/docs/architecture.md Zobrazit soubor

@@ -344,8 +344,17 @@ fresh image per run.
## 11. Diagnostics and Faults

Compile-time diagnostics use stable IDs such as `E014`. Runtime faults use a
separate stable namespace such as `R003`. Numeric assignments live in
`src/common/errors.inc` once story E02-S03 freezes the catalog.
separate stable namespace such as `R003`. The compile-time catalog is
`src/common/diagcat.inc` (one line per diagnostic, numbered by category
range); `errors.inc` turns it into `E_*` constants and `diagnostics.s` into
messages. `docs/diagnostics.md` is generated from it (E02-S04).

`diagnostics.s` prints a two-line screen form (header, message clipped to 39
characters) and appends one full line per diagnostic to `NAME.ERR`. The ERR
file is scratched when compilation starts and created only on the first
diagnostic. After 20 errors the next is replaced by E221 and the caller must
stop. If `NAME.ERR` cannot be written, E201 is shown once and reporting
continues on screen.

Compiler errors include source name, line, column, and compact message. Runtime
faults always include a stable fault code and the best available program address


+ 94
- 0
newLang/docs/diagnostics.md Zobrazit soubor

@@ -0,0 +1,94 @@
# Nova128 Diagnostics

Generated from `src/common/diagcat.inc` by `scripts/build-diag-test.ps1 -UpdateDocs`.
Do not edit by hand. Numbers are stable: never renumber or reuse one.

Each `%` is replaced by an argument such as a name or type. On screen a
diagnostic is two lines, the header and the message clipped to 39 characters:

```text
E010 TEST.N8 3 11
CHARACTER LITERAL NEEDS ONE CHARACTER
```

`NAME.ERR` holds one full line per diagnostic: `E010 TEST.N8 3 11 CHARACTER ...`.
It is scratched when compilation starts and created only if a diagnostic occurs.
After 20 errors the next one is replaced by E221 and compilation stops.

## Source and lexical (E001-E019)

| Code | Symbol | Message |
|---|---|---|
| E001 | `SRC_LINE_TOO_LONG` | LINE LONGER THAN 80 CHARACTERS |
| E002 | `SRC_FILE_TOO_LARGE` | SOURCE LARGER THAN 18999 BYTES |
| E003 | `SRC_TOO_MANY_LINES` | MORE THAN 599 LINES |
| E004 | `LEX_INVALID_CHAR` | INVALID CHARACTER |
| E005 | `LEX_IDENT_TOO_LONG` | NAME LONGER THAN 16 CHARACTERS |
| E006 | `LEX_NUMBER_RANGE` | NUMBER LARGER THAN 65535 |
| E007 | `LEX_BAD_NUMBER` | MALFORMED NUMBER |
| E008 | `LEX_UNTERMINATED_STRING` | STRING NOT CLOSED ON THIS LINE |
| E009 | `LEX_BAD_ESCAPE` | UNKNOWN ESCAPE SEQUENCE |
| E010 | `LEX_BAD_CHAR_LITERAL` | CHARACTER LITERAL NEEDS ONE CHARACTER |

## Syntax (E020-E059)

| Code | Symbol | Message |
|---|---|---|
| E020 | `SYN_EXPECTED_IDENTIFIER` | NAME EXPECTED |
| E021 | `SYN_EXPECTED_EOL` | END OF LINE EXPECTED |
| E022 | `SYN_UNEXPECTED_EOL` | LINE ENDS TOO EARLY |
| E023 | `SYN_EXPECTED_RPAREN` | ')' EXPECTED |
| E024 | `SYN_EXPECTED_END_PROGRAM` | END PROGRAM EXPECTED |
| E025 | `SYN_EXPECTED_END_METHOD` | END METHOD EXPECTED |
| E026 | `SYN_EXPECTED_END_IF` | END IF EXPECTED |
| E027 | `SYN_EXPECTED_END_CLASS` | END CLASS EXPECTED |
| 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 |

## Names and declarations (E060-E079)

| Code | Symbol | Message |
|---|---|---|
| E060 | `SEM_DUPLICATE_NAME` | % IS ALREADY DECLARED |
| E061 | `SEM_UNKNOWN_NAME` | % IS NOT DECLARED |

## Types (E080-E119)

| Code | Symbol | Message |
|---|---|---|
| E080 | `TYP_MISMATCH` | % EXPECTED; % FOUND |
| E081 | `TYP_CONDITION_NOT_BOOLEAN` | CONDITION MUST BE BOOLEAN |

## Access (E120-E129)

| Code | Symbol | Message |
|---|---|---|
| E120 | `ACC_PRIVATE_MEMBER` | % IS PRIVATE |

## Interfaces (E130-E149)

| Code | Symbol | Message |
|---|---|---|
| E130 | `IFC_METHOD_MISSING` | % IS NOT IMPLEMENTED |
| E131 | `IFC_SIGNATURE_MISMATCH` | % DIFFERS FROM INTERFACE |

## Methods and returns (E150-E169)

| Code | Symbol | Message |
|---|---|---|
| E150 | `SEM_MISSING_RETURN` | MISSING RETURN VALUE |

## Disk (E200-E219)

| Code | Symbol | Message |
|---|---|---|
| E200 | `DSK_SOURCE_UNREADABLE` | CANNOT READ SOURCE; DOS % |
| E201 | `DSK_ERR_FILE_UNWRITABLE` | CANNOT WRITE ERROR FILE; DOS % |

## Resource limits (E220-E239)

| Code | Symbol | Message |
|---|---|---|
| E220 | `RES_TOO_MANY_NAMES` | MORE THAN 255 NAMES |
| E221 | `RES_ERROR_LIMIT` | TOO MANY ERRORS; COMPILATION STOPPED |

+ 77
- 0
newLang/planning/claims/E02-S04.md Zobrazit soubor

@@ -0,0 +1,77 @@
---
story: E02-S04
owner: e02_diagnostics
status: done
started: 2026-11-07
depends-on: [E01-S03, E00-S02]
---

# E02-S04 Claim

## Intended change

Add the stable diagnostic catalog (`src/common/diagcat.inc`, one line per
diagnostic, included as constants and as messages), `src/common/errors.inc`,
and `src/compiler/diagnostics.s`, which prints the 40-column screen form and
writes `NAME.ERR`, with a default limit of 20 errors. Extend `diskio.s` with
output-file and DOS-command wrappers.

## File area

- `src/common/diagcat.inc`, `src/common/errors.inc`, `src/common/diskio.inc`
- `src/compiler/diagnostics.s`, `src/compiler/diskio.s`
- `tests/diag/`
- `scripts/build-diag-test.ps1`, `scripts/verify-diag-test.ps1`,
`scripts/lib/Nova128Test.psm1`
- `docs/diagnostics.md` (generated from the catalog), `docs/architecture.md`
- `planning/claims/E02-S04.md`
- generated files under `build/`

## Acceptance evidence

- [x] Lexical, syntax, type, access, interface, disk, and resource categories
have stable IDs: 33 diagnostics in `src/common/diagcat.inc` across nine
category ranges; the build rejects out-of-range, duplicate, or reused
numbers and any grammar `.expect` symbol missing from the catalog.
- [x] Screen form fits 40 columns for representative diagnostics: x128 screen
RAM shows each diagnostic as a header row and a message row, all at most
39 characters, including the longest message, a clipped two-argument
message, and the widest header (`E023 DIAG.N8 65535 255`).
- [x] ERR output contains source, line, column, code, and message:
`diag.err` read back from the D81 equals the expected six lines exactly;
a stale `diag.err` was replaced and a stale `clean.err` was scratched when
no diagnostics occurred.
- [x] Error limiting defaults to 20 and terminates cleanly: 25 reports give
carry 0 x20 then 1 x5; `limit.err` holds 20 errors plus E221; no files are
left open after any phase. An unwritable ERR name (DOS 33) shows E201 once
and leaves no file. A mutation that stops counting is caught.

## Coordination notes

The grammar fixtures' symbolic `.expect` names become catalog symbols
unchanged. Later stories append diagnostics within their category range.

## Work log

- The first x128 run showed garbage before every message: `find_message`
clobbered X, the formatter's buffer index. Fixed; found by dumping screen
RAM and the format buffer.
- `c1541 -read` needs `name,s` for SEQ files.
- The ERR width check uses spec limits (15-character source name, line <= 599,
column <= 81), so the longest ERR line is 77 characters.

## Completion

- Tests run: `scripts/verify-diag-test.ps1` (pass plus counting mutation).
- Results: result block, 19 screen rows, `diag.err`, `limit.err`, and the
directory all match expectations.
- Files changed: `src/common/diagcat.inc`, `src/common/errors.inc`,
`src/common/diskio.inc`, `src/common/c128.inc` (CHKOUT),
`src/compiler/diagnostics.s`, `src/compiler/diskio.s`, `tests/diag/`,
`scripts/build-diag-test.ps1`, `scripts/verify-diag-test.ps1`,
`docs/diagnostics.md`, `docs/architecture.md`,
`tests/grammar/README.md`, this claim.
- Remaining risks: later stories will append diagnostics, and their wording
should be reviewed by the user; the 80-column display path was not
exercised here (E01-S04 covers KERNAL output in both modes); a disk that
fills up while NAME.ERR is being written is not tested.

+ 145
- 0
newLang/scripts/build-diag-test.ps1 Zobrazit soubor

@@ -0,0 +1,145 @@
# E02-S04: check the diagnostic catalog, keep docs/diagnostics.md in sync with
# it, build the diagnostics test PRG, and prepare a D81 with stale ERR files.
# -UpdateDocs rewrites docs/diagnostics.md from src/common/diagcat.inc.
[CmdletBinding()]
param([switch]$UpdateDocs)

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

# ------------------------------------------------------------ the catalog
$catalogPath = Join-Path $project "src\common\diagcat.inc"
$catalog = @()
foreach ($line in Get-Content -LiteralPath $catalogPath) {
$m = [regex]::Match($line, '^\s*DIAGNOSTIC\s+(\w+),\s*(\d+),\s*"(.*)"\s*$')
if ($m.Success) {
$catalog += [pscustomobject]@{ Symbol = $m.Groups[1].Value; Code = [int]$m.Groups[2].Value
Text = $m.Groups[3].Value }
}
}
$ranges = @{
SRC = @(, @(1, 19)); LEX = @(, @(1, 19)); SYN = @(, @(20, 59)); SEM = @(@(60, 79), @(150, 169))
TYP = @(, @(80, 119)); ACC = @(, @(120, 129)); IFC = @(, @(130, 149)); DSK = @(, @(200, 219))
RES = @(, @(220, 239))
}
$categories = [ordered]@{
"Source and lexical" = @(1, 19); "Syntax" = @(20, 59); "Names and declarations" = @(60, 79)
"Types" = @(80, 119); "Access" = @(120, 129); "Interfaces" = @(130, 149)
"Methods and returns" = @(150, 169); "Disk" = @(200, 219); "Resource limits" = @(220, 239)
}
$problems = @()
foreach ($d in $catalog) {
$prefix = $d.Symbol.Split('_')[0]
if (-not $ranges.ContainsKey($prefix)) { $problems += "$($d.Symbol): unknown category prefix" }
elseif (-not ($ranges[$prefix] | Where-Object { $d.Code -ge $_[0] -and $d.Code -le $_[1] })) {
$problems += "$($d.Symbol) = $($d.Code) is outside the $prefix range"
}
if ($d.Code -lt 1 -or $d.Code -gt 255) { $problems += "$($d.Symbol): code must be 1..255" }
$argCount = ([regex]::Matches($d.Text, '%')).Count
if ($argCount -gt 2) { $problems += "$($d.Symbol): more than two arguments" }
if ($argCount -eq 0 -and $d.Text.Length -gt 39) { $problems += "$($d.Symbol): message longer than 39" }
if ($d.Text -cmatch '[a-z]') { $problems += "$($d.Symbol): use uppercase ASCII (PETSCII unshifted)" }
# ERR line <= 80 (Vim's width). Spec limits: name <= 15 ("12345678901
# 2.N8"), line <= 599, column <= 81, so "Ennn NAME LLL CC " <= 28.
if ((28 + $d.Text.Length - $argCount + 16 * $argCount) -gt 80) { $problems += "$($d.Symbol): ERR line can exceed 80" }
}
$dupes = $catalog | Group-Object Code | Where-Object Count -gt 1
foreach ($g in $dupes) { $problems += "code $($g.Name) used by $(($g.Group.Symbol) -join ', ')" }
$dupes = $catalog | Group-Object Symbol | Where-Object Count -gt 1
foreach ($g in $dupes) { $problems += "symbol $($g.Name) declared twice" }
foreach ($e in Get-ChildItem (Join-Path $project "tests\grammar\rejected") -Filter *.expect) {
$sym = [regex]::Match((Get-Content -Raw $e.FullName), '(?m)^diagnostic: (\w+)').Groups[1].Value
if (-not ($catalog | Where-Object Symbol -eq $sym)) { $problems += "$($e.Name): $sym is not in the catalog" }
}
if ($problems) { throw ("diagnostic catalog problems:`n " + ($problems -join "`n ")) }

# ------------------------------------------------- docs/diagnostics.md
$doc = New-Object System.Text.StringBuilder
[void]$doc.AppendLine("# Nova128 Diagnostics")
[void]$doc.AppendLine("")
[void]$doc.AppendLine("Generated from ``src/common/diagcat.inc`` by ``scripts/build-diag-test.ps1 -UpdateDocs``.")
[void]$doc.AppendLine("Do not edit by hand. Numbers are stable: never renumber or reuse one.")
[void]$doc.AppendLine("")
[void]$doc.AppendLine("Each ``%`` is replaced by an argument such as a name or type. On screen a")
[void]$doc.AppendLine("diagnostic is two lines, the header and the message clipped to 39 characters:")
[void]$doc.AppendLine("")
[void]$doc.AppendLine('```text')
[void]$doc.AppendLine("E010 TEST.N8 3 11")
[void]$doc.AppendLine("CHARACTER LITERAL NEEDS ONE CHARACTER")
[void]$doc.AppendLine('```')
[void]$doc.AppendLine("")
[void]$doc.AppendLine("``NAME.ERR`` holds one full line per diagnostic: ``E010 TEST.N8 3 11 CHARACTER ...``.")
[void]$doc.AppendLine("It is scratched when compilation starts and created only if a diagnostic occurs.")
[void]$doc.AppendLine("After 20 errors the next one is replaced by E221 and compilation stops.")
foreach ($c in $categories.Keys) {
$lo = $categories[$c][0]; $hi = $categories[$c][1]
[void]$doc.AppendLine("")
[void]$doc.AppendLine(("## {0} (E{1:D3}-E{2:D3})" -f $c, $lo, $hi))
[void]$doc.AppendLine("")
[void]$doc.AppendLine("| Code | Symbol | Message |")
[void]$doc.AppendLine("|---|---|---|")
foreach ($d in ($catalog | Where-Object { $_.Code -ge $lo -and $_.Code -le $hi } | Sort-Object Code)) {
[void]$doc.AppendLine(("| E{0:D3} | ``{1}`` | {2} |" -f $d.Code, $d.Symbol, $d.Text.Replace('|', '\|')))
}
}
$docPath = Join-Path $project "docs\diagnostics.md"
$docText = $doc.ToString().Replace("`r`n", "`n")
if ($UpdateDocs) { [IO.File]::WriteAllText($docPath, $docText) }
elseif (-not (Test-Path $docPath) -or [IO.File]::ReadAllText($docPath) -ne $docText) {
throw "docs/diagnostics.md is out of date; run build-diag-test.ps1 -UpdateDocs and review"
}

# -------------------------------------------------------- expectations
function Format-Diag([string]$name, [int]$line, [int]$col, [string]$symbol, [string[]]$arguments = @()) {
$d = $catalog | Where-Object Symbol -eq $symbol
$msg = $d.Text # each % takes the next argument
foreach ($a in $arguments) { $idx = $msg.IndexOf('%'); $msg = $msg.Substring(0, $idx) + $a + $msg.Substring($idx + 1) }
$header = "E{0:D3} {1} {2} {3}" -f $d.Code, $name, $line, $col
return [pscustomobject]@{ Header = $header; Message = $msg; ErrLine = "$header $msg" }
}
$phaseA = @(
(Format-Diag "DIAG.N8" 3 11 "LEX_BAD_CHAR_LITERAL"),
(Format-Diag "DIAG.N8" 599 80 "SYN_EXPECTED_END_PROGRAM"),
(Format-Diag "DIAG.N8" 12 5 "SEM_DUPLICATE_NAME" @("COUNTER")),
(Format-Diag "DIAG.N8" 40 17 "TYP_MISMATCH" @("INTEGER", "VERYLONGCLASSNAM")),
(Format-Diag "DIAG.N8" 1 1 "DSK_SOURCE_UNREADABLE" @("62")),
(Format-Diag "DIAG.N8" 65535 255 "SYN_EXPECTED_RPAREN"))
$phaseD = @(
(Format-Diag "BAD*.N8" 7 3 "LEX_INVALID_CHAR"),
(Format-Diag "BAD*.N8" 7 3 "DSK_ERR_FILE_UNWRITABLE" @("##")), # ## = DOS code, checked by pattern
(Format-Diag "BAD*.N8" 8 4 "LEX_INVALID_CHAR"))
$limit = @(1..20 | ForEach-Object { (Format-Diag "LIMIT.N8" $_ 1 "LEX_INVALID_CHAR").ErrLine })
$limit += (Format-Diag "LIMIT.N8" 21 1 "RES_ERROR_LIMIT").ErrLine
$screen = @()
foreach ($d in ($phaseA + $phaseD)) {
$screen += $d.Header
$screen += $(if ($d.Message.Length -gt 39) { $d.Message.Substring(0, 39) } else { $d.Message })
}
$screen += "NOVA128 DIAG TEST OK"
[ordered]@{
diag = @($phaseA | ForEach-Object ErrLine)
limit = $limit
screen = $screen
} | ConvertTo-Json -Depth 3 | Set-Content (Join-Path $out "expected.json")

# ------------------------------------------------------------- program
$link = Invoke-Nova128Link -Name "diag-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\diagnostics.s"),
(Join-Path $project "tests\diag\diag-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }

$stale = Join-Path $out "stale.pet"
[IO.File]::WriteAllBytes($stale, [byte[]](0x53, 0x54, 0x41, 0x4C, 0x45, 0x0D)) # "STALE"
New-Nova128Disk -Path (Join-Path $out "diag-test.d81") -Files @(
@{ Name = "diag-test"; Path = $link.Program; Type = "prg" },
@{ Name = "diag.err"; Path = $stale; Type = "seq" },
@{ Name = "limit.err"; Path = $stale; Type = "seq" },
@{ Name = "clean.err"; Path = $stale; Type = "seq" })
Write-Host ("catalog: {0} diagnostics in range; docs/diagnostics.md in sync" -f $catalog.Count)
Write-Host ("built {0}" -f $link.Program)

+ 85
- 0
newLang/scripts/verify-diag-test.ps1 Zobrazit soubor

@@ -0,0 +1,85 @@
# E02-S04: run the diagnostics test in x128, then check the result block, the
# 40-column screen, and the ERR files left on the disk image.
[CmdletBinding()]
param([int]$Port = 6534, [int]$TimeoutSeconds = 180)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-diag-test.ps1")

$out = Join-Path $project "build\diag-test"
$disk = Join-Path $out "diag-test.d81"
$expected = Get-Content -Raw (Join-Path $out "expected.json") | ConvertFrom-Json
$failures = @()

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "diag-test-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
$results = Read-Nova128Memory $session ($mb[8] + 256 * $mb[9]) 34
Invoke-Nova128Monitor $session "x" | Out-Null
Start-Sleep -Seconds 2 # let the OK line print
$screenRam = Read-Nova128Memory $session 0x0400 1000
} finally {
Stop-Nova128X128 $session -SettleSeconds 0
}

# ------------------------------------------------------------ results
$want = [ordered]@{ "phase A count" = @(0, 6); "phase D count" = @(1, 2); "phase D file state (2=failed)" = @(2, 2)
"phase B count" = @(3, 20); "phase C count" = @(4, 0) }
foreach ($k in $want.Keys) {
if ($results[$want[$k][0]] -ne $want[$k][1]) { $failures += "${k}: $($results[$want[$k][0]]) expected $($want[$k][1])" }
}
for ($i = 5; $i -le 8; $i++) { if ($results[$i] -ne 0) { $failures += "files left open after phase $($i - 4): $($results[$i])" } }
$carries = $results[9..33]
$wantCarries = @(0) * 20 + @(1) * 5
if (($carries -join ',') -ne ($wantCarries -join ',')) {
$failures += "phase B carries $($carries -join '') expected $($wantCarries -join '')"
}

# ------------------------------------------------------------- screen
$rows = for ($r = 0; $r -lt 25; $r++) {
$chars = for ($c = 0; $c -lt 40; $c++) {
$code = $screenRam[$r * 40 + $c]
if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
}
(-join $chars).TrimEnd()
}
for ($r = 0; $r -lt $expected.screen.Count; $r++) {
$pattern = '^' + [regex]::Escape($expected.screen[$r]).Replace('\#\#', '\d\d').Replace('##', '\d\d') + '$'
if ($rows[$r] -notmatch $pattern) { $failures += "screen row ${r}: '$($rows[$r])' expected '$($expected.screen[$r])'" }
}
$wide = @($rows | Where-Object { $_.Length -gt 39 })
if ($wide) { $failures += "screen rows longer than 39: $($wide -join ' | ')" }

# ---------------------------------------------------------- ERR files
$c1541 = Get-ViceTool "c1541"
$listing = (& $c1541 -attach $disk -list 2>&1) | Out-String
function Read-SeqText([string]$name) {
$dest = Join-Path $out ($name.Replace('*', '_') + ".bin")
if (Test-Path $dest) { Remove-Item $dest -Force }
& $c1541 -attach $disk -read "$name,s" $dest 2>&1 | Out-Null # SEQ needs ,s
if (-not (Test-Path $dest)) { return $null }
$bytes = [IO.File]::ReadAllBytes($dest)
return @(([Text.Encoding]::ASCII.GetString($bytes).TrimEnd("`r")) -split "`r")
}
$diagLines = Read-SeqText "diag.err"
if ($null -eq $diagLines) { $failures += "diag.err missing" }
elseif (($diagLines -join "`n") -cne ($expected.diag -join "`n")) {
$failures += "diag.err differs:`n got: " + ($diagLines -join "`n ") + "`n want: " + ($expected.diag -join "`n ")
}
$limitLines = Read-SeqText "limit.err"
if ($null -eq $limitLines) { $failures += "limit.err missing" }
elseif (($limitLines -join "`n") -cne ($expected.limit -join "`n")) {
$failures += "limit.err differs ($($limitLines.Count) lines): first '$($limitLines[0])', last '$($limitLines[-1])'"
}
if ($listing -match '"clean\.err"') { $failures += "stale clean.err was not scratched" }
if ($listing -match '"bad') { $failures += "a bad*.err file was created" }

if ($failures) { throw ("diagnostics verification failed:`n " + ($failures -join "`n ")) }
Write-Host "screen (rows 0-$($expected.screen.Count - 1)):"
$rows[0..($expected.screen.Count - 1)] | ForEach-Object { Write-Host " |$_" }
Write-Host ("diag.err: {0} lines, limit.err: {1} lines (20 + limit), clean.err scratched, no bad*.err" -f
$diagLines.Count, $limitLines.Count)
Write-Host "verified diagnostics: results, screen, and ERR files match"

+ 1
- 0
newLang/src/common/c128.inc Zobrazit soubor

@@ -60,6 +60,7 @@ SETNAM = $FFBD
OPEN = $FFC0 ; no-name OPEN sends nothing on the bus
CLOSE = $FFC3
CHKIN = $FFC6 ; absent device: C=1, A=5, ST=$80
CHKOUT = $FFC9
CLRCHN = $FFCC
CHRIN = $FFCF
CHROUT = $FFD2


+ 68
- 0
newLang/src/common/diagcat.inc Zobrazit soubor

@@ -0,0 +1,68 @@
; diagcat.inc - THE Nova128 diagnostic catalog (E02-S04).
;
; One line per diagnostic: DIAGNOSTIC symbol, number, "message"
; The including file defines the DIAGNOSTIC macro: errors.inc turns each line
; into a constant E_<symbol>; diagnostics.s turns it into the message table.
; docs/diagnostics.md is generated from this file by build-diag-test.ps1.
;
; Rules (checked by scripts/build-diag-test.ps1):
; - Numbers are stable. Never renumber or reuse; append within the range.
; - Ranges: 1-19 source and lexical 20-59 syntax
; 60-79 names and declarations 80-119 types
; 120-129 access 130-149 interfaces
; 150-169 methods and returns 200-219 disk
; 220-239 resource limits
; - Messages are uppercase ASCII (PETSCII unshifted letters), at most 39
; characters with no arguments. Each % is replaced by an argument (at most
; two, each at most 16 characters); the screen clips the result to 39.

; ---------------------------------------------------- source and lexical
DIAGNOSTIC SRC_LINE_TOO_LONG, 1, "LINE LONGER THAN 80 CHARACTERS"
DIAGNOSTIC SRC_FILE_TOO_LARGE, 2, "SOURCE LARGER THAN 18999 BYTES"
DIAGNOSTIC SRC_TOO_MANY_LINES, 3, "MORE THAN 599 LINES"
DIAGNOSTIC LEX_INVALID_CHAR, 4, "INVALID CHARACTER"
DIAGNOSTIC LEX_IDENT_TOO_LONG, 5, "NAME LONGER THAN 16 CHARACTERS"
DIAGNOSTIC LEX_NUMBER_RANGE, 6, "NUMBER LARGER THAN 65535"
DIAGNOSTIC LEX_BAD_NUMBER, 7, "MALFORMED NUMBER"
DIAGNOSTIC LEX_UNTERMINATED_STRING, 8, "STRING NOT CLOSED ON THIS LINE"
DIAGNOSTIC LEX_BAD_ESCAPE, 9, "UNKNOWN ESCAPE SEQUENCE"
DIAGNOSTIC LEX_BAD_CHAR_LITERAL, 10, "CHARACTER LITERAL NEEDS ONE CHARACTER"

; ----------------------------------------------------------------- syntax
DIAGNOSTIC SYN_EXPECTED_IDENTIFIER, 20, "NAME EXPECTED"
DIAGNOSTIC SYN_EXPECTED_EOL, 21, "END OF LINE EXPECTED"
DIAGNOSTIC SYN_UNEXPECTED_EOL, 22, "LINE ENDS TOO EARLY"
DIAGNOSTIC SYN_EXPECTED_RPAREN, 23, "')' EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_PROGRAM, 24, "END PROGRAM EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_METHOD, 25, "END METHOD EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_IF, 26, "END IF EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_CLASS, 27, "END CLASS EXPECTED"
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"

; -------------------------------------------- names and declarations
DIAGNOSTIC SEM_DUPLICATE_NAME, 60, "% IS ALREADY DECLARED"
DIAGNOSTIC SEM_UNKNOWN_NAME, 61, "% IS NOT DECLARED"

; ------------------------------------------------------------------ types
DIAGNOSTIC TYP_MISMATCH, 80, "% EXPECTED; % FOUND"
DIAGNOSTIC TYP_CONDITION_NOT_BOOLEAN, 81, "CONDITION MUST BE BOOLEAN"

; ----------------------------------------------------------------- access
DIAGNOSTIC ACC_PRIVATE_MEMBER, 120, "% IS PRIVATE"

; ------------------------------------------------------------- interfaces
DIAGNOSTIC IFC_METHOD_MISSING, 130, "% IS NOT IMPLEMENTED"
DIAGNOSTIC IFC_SIGNATURE_MISMATCH, 131, "% DIFFERS FROM INTERFACE"

; ---------------------------------------------------- methods and returns
DIAGNOSTIC SEM_MISSING_RETURN, 150, "MISSING RETURN VALUE"

; ------------------------------------------------------------------- disk
DIAGNOSTIC DSK_SOURCE_UNREADABLE, 200, "CANNOT READ SOURCE; DOS %"
DIAGNOSTIC DSK_ERR_FILE_UNWRITABLE, 201, "CANNOT WRITE ERROR FILE; DOS %"

; ------------------------------------------------------- resource limits
DIAGNOSTIC RES_TOO_MANY_NAMES, 220, "MORE THAN 255 NAMES"
DIAGNOSTIC RES_ERROR_LIMIT, 221, "TOO MANY ERRORS; COMPILATION STOPPED"

+ 2
- 0
newLang/src/common/diskio.inc Zobrazit soubor

@@ -7,6 +7,8 @@ DIO_DEVICE = 8
DIO_LFN_CMD = 15 ; command/status channel: opened first, closed last
DIO_LFN_IN = 2 ; source input
DIO_SA_IN = 2
DIO_LFN_ERR = 4 ; NAME.ERR output
DIO_SA_ERR = 4
DIO_STATUS_MAX = 40 ; bytes kept from a drive status line

.endif

+ 16
- 0
newLang/src/common/errors.inc Zobrazit soubor

@@ -0,0 +1,16 @@
; errors.inc - diagnostic numbers as E_<symbol> constants (E02-S04).
; The catalog itself is src/common/diagcat.inc.

.ifndef ERRORS_INC
ERRORS_INC = 1

.macro DIAGNOSTIC sym, number, text
.ident(.concat("E_", .string(sym))) = number
.endmacro
.include "diagcat.inc"
.delmacro DIAGNOSTIC

DIAG_LIMIT_DEFAULT = 20 ; errors before compilation stops
DIAG_SCREEN_WIDTH = 39 ; longest screen line (no editor wrap)

.endif

+ 539
- 0
newLang/src/compiler/diagnostics.s Zobrazit soubor

@@ -0,0 +1,539 @@
; diagnostics.s - diagnostic reporting and the ERR writer (E02-S04).
;
; Usage (bank-0 compiler state):
; diag_init A/X = source name (bank 0), Y = length. Scratches a stale
; NAME.ERR (".N8" replaced by ".ERR"); resets the count.
; diag_line/diag_col, optional diag_arg1/diag_arg2 (address + length),
; then diag_report with A = E_* number.
; C=0: reported, keep going. C=1: the error limit was reached; the
; limit diagnostic has been reported and the caller must stop.
; diag_finish closes NAME.ERR; A = number of errors reported.
;
; Screen form (40 columns, no line ever longer than 39 characters):
; E010 TEST.N8 3 11
; CHARACTER LITERAL NEEDS ONE CHARACTER
; ERR form, one line per diagnostic, $0D terminated:
; E010 TEST.N8 3 11 CHARACTER LITERAL NEEDS ONE CHARACTER
; NAME.ERR is created only when the first diagnostic is reported. If it cannot
; be written, DSK_ERR_FILE_UNWRITABLE is shown once on screen and reporting
; continues on screen only.

.setcpu "6502"
.include "../common/c128.inc"
.include "../common/diskio.inc"
.include "../common/errors.inc"

.import kcall_inline
.import dio_open_cmd, dio_open_out, dio_read_status, dio_chkout, dio_putc
.import dio_clrchn, dio_close, dio_command, dio_dos_code

.export diag_init, diag_report, diag_finish
.export diag_line, diag_col, diag_arg1, diag_arg1_len, diag_arg2, diag_arg2_len
.export diag_count, diag_limit, diag_screen, diag_file_state, diag_buf

DIAG_BUF_MAX = 96
NAME_MAX = 16

FILE_NONE = 0 ; NAME.ERR not opened yet
FILE_OPEN = 1
FILE_FAILED = 2

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

diag_init:
sta @src+1
stx @src+2
cpy #NAME_MAX + 1
bcc :+
ldy #NAME_MAX
: sty name_len
ldy #$00
@copy: cpy name_len
beq @copied
@src: lda $FFFF,y ; patched
sta name_text,y
iny
bne @copy
@copied:
lda #$00
sta diag_count
sta diag_stopped
sta diag_file_state
sta diag_arg1_len
sta diag_arg2_len
lda #DIAG_LIMIT_DEFAULT
sta diag_limit
lda #$01
sta diag_screen

; base = name without a trailing ".N8" (either letter range)
lda name_len
sta base_len
cmp #3
bcc @names
tay
lda name_text-1,y
cmp #'8'
bne @names
lda name_text-2,y
and #$7F
cmp #'N'
bne @names
lda name_text-3,y
cmp #'.'
bne @names
dey
dey
dey
sty base_len
@names: ldx #$00 ; scratch_cmd = "S0:" base ".ERR"
ldy #$00
: lda scratch_prefix,y
sta scratch_cmd,x
inx
iny
cpy #3
bne :-
jsr append_base
stx scratch_len
ldx #$00 ; open_name = base ".ERR,S,W"
jsr append_base_to_open
stx open_len

jsr dio_open_cmd ; remove any stale NAME.ERR
bcs @done
lda #<scratch_cmd
ldx #>scratch_cmd
ldy scratch_len
jsr dio_command
lda #DIO_LFN_CMD
jsr dio_close
@done: rts

; Append base + ".ERR" to scratch_cmd at X.
append_base:
ldy #$00
: cpy base_len
beq :+
lda name_text,y
sta scratch_cmd,x
inx
iny
bne :-
: ldy #$00
: lda err_suffix,y
sta scratch_cmd,x
inx
iny
cpy #4
bne :-
rts

append_base_to_open:
ldy #$00
: cpy base_len
beq :+
lda name_text,y
sta open_name,x
inx
iny
bne :-
: ldy #$00
: lda err_suffix,y
sta open_name,x
inx
iny
cpy #8
bne :-
rts

diag_report:
ldx diag_stopped
beq :+
sec ; already stopped: report nothing more
rts
: ldx diag_count
cpx diag_limit
bcc @count
lda #E_RES_ERROR_LIMIT ; the (limit+1)th error stops compilation
sta diag_code
lda #$00
sta diag_arg1_len
sta diag_arg2_len
lda #$01
sta diag_stopped
jsr emit
sec
rts
@count: sta diag_code
inc diag_count
jsr emit
clc
rts

; Report to screen and file, then clear the arguments.
emit: jsr format
lda diag_screen
beq :+
jsr to_screen
: jsr to_file
lda #$00
sta diag_arg1_len
sta diag_arg2_len
rts

diag_finish:
lda diag_file_state
cmp #FILE_OPEN
bne @done
jsr dio_clrchn
lda #DIO_LFN_ERR
jsr dio_close
lda #DIO_LFN_CMD ; command channel last
jsr dio_close
lda #FILE_NONE
sta diag_file_state
@done: lda diag_count
rts

; --------------------------------------------------------------- format
; diag_buf = "Ennn NAME line col message"; hdr_len, msg_start, buf_len.
format: ldx #$00
lda #'E'
jsr put
lda diag_code ; three digits, zero padded
ldy #'0' - 1
: iny
sec
sbc #100
bcs :-
adc #100
pha
tya
jsr put
pla
ldy #'0' - 1
: iny
sec
sbc #10
bcs :-
adc #10 + '0'
pha
tya
jsr put
pla
jsr put
lda #' '
jsr put
ldy #$00
: cpy name_len
beq :+
lda name_text,y
jsr put
iny
bne :-
: lda #' '
jsr put
lda diag_line
ldy diag_line+1
jsr put_decimal
lda #' '
jsr put
lda diag_col
ldy #$00
jsr put_decimal
stx hdr_len
lda #' '
jsr put
stx msg_start

jsr find_message ; template address -> fmt_tpl
lda #$00
sta arg_index
ldy #$00
fmt_loop:
sty tpl_y
fmt_tpl:
lda $FFFF,y ; patched by find_message
beq fmt_done
cmp #'%'
bne fmt_plain
jsr put_arg
jmp fmt_next
fmt_plain:
jsr put
fmt_next:
ldy tpl_y
iny
bne fmt_loop
fmt_done:
stx buf_len
rts

; X = index; store A unless the buffer is full.
put: cpx #DIAG_BUF_MAX
bcs :+
sta diag_buf,x
inx
: rts

; Append argument 1 or 2 (in order of the % markers).
put_arg:
lda arg_index ; 0 = first %, otherwise the second
inc arg_index
cmp #$00
bne @two
lda diag_arg1
sta @src+1
lda diag_arg1+1
sta @src+2
lda diag_arg1_len
jmp @copy
@two: lda diag_arg2
sta @src+1
lda diag_arg2+1
sta @src+2
lda diag_arg2_len
@copy: sta arg_len
ldy #$00
: cpy arg_len
beq @done
@src: lda $FFFF,y ; patched
jsr put
iny
bne :-
@done: rts

; Append the unsigned 16-bit value A (low) / Y (high) in decimal.
put_decimal:
sta num
sty num+1
lda #$00
sta num_started
ldy #4 ; 10000, 1000, 100, 10, 1
@digit: lda #'0'
sta num_digit
@sub: lda num
sec
sbc pow_lo,y
sta num_t
lda num+1
sbc pow_hi,y
bcc @emit
sta num+1
lda num_t
sta num
inc num_digit
bne @sub
@emit: lda num_digit
cpy #$00 ; always print the units digit
beq @out
cmp #'0'
bne @out
lda num_started
beq @skip
lda num_digit
@out: sta num_started
jsr put
@skip: dey
bpl @digit
rts

; Point the fmt_tpl load in format at the template for diag_code.
; Preserves X: it is format's buffer index.
find_message:
ldy #$00
@scan: lda catalog,y
beq @unknown
cmp diag_code
beq @found
iny
iny
iny
bne @scan
@unknown:
lda #<unknown_text
sta fmt_tpl+1
lda #>unknown_text
sta fmt_tpl+2
rts
@found: lda catalog+1,y
sta fmt_tpl+1
lda catalog+2,y
sta fmt_tpl+2
rts

; ------------------------------------------------------------- outputs
to_screen:
ldy #$00
: cpy hdr_len
beq :+
lda diag_buf,y
jsr screen_putc
iny
bne :-
: lda #$0D
jsr screen_putc
lda msg_start ; message clipped to DIAG_SCREEN_WIDTH
clc
adc #DIAG_SCREEN_WIDTH
sta clip_end
ldy msg_start
: cpy buf_len
beq :+
cpy clip_end
beq :+
lda diag_buf,y
jsr screen_putc
iny
bne :-
: lda #$0D
jmp screen_putc

screen_putc:
sty out_y
jsr kcall_inline
.word CHROUT
ldy out_y
rts

to_file:
lda diag_file_state
cmp #FILE_FAILED
beq @done
cmp #FILE_OPEN
beq @write
jsr open_err_file
bcs @done
@write: ldx #DIO_LFN_ERR
jsr dio_chkout
bcs @done
ldy #$00
: cpy buf_len
beq :+
lda diag_buf,y
sty out_y
jsr dio_putc
ldy out_y
iny
bne :-
: lda #$0D
jsr dio_putc
jsr dio_clrchn
@done: rts

; Open the command channel, then NAME.ERR. On failure close both, mark the
; file failed, and show DSK_ERR_FILE_UNWRITABLE once on screen. C=1 on failure.
open_err_file:
jsr dio_open_cmd
bcs @fail_cmd
lda #<open_name
ldx #>open_name
ldy open_len
jsr dio_open_out
bcs @fail
jsr dio_read_status
lda dio_dos_code
cmp #20
bcs @fail
lda #FILE_OPEN
sta diag_file_state
clc
rts
@fail: lda #DIO_LFN_ERR
jsr dio_close
@fail_cmd:
lda #DIO_LFN_CMD
jsr dio_close
lda #FILE_FAILED
sta diag_file_state
lda dio_dos_code ; screen-only notice; the report that
; triggered it is already on screen ; argument: DOS code as two digits
ldy #$00
jsr dos_digits
lda #E_DSK_ERR_FILE_UNWRITABLE
sta diag_code
lda #<dos_text
sta diag_arg1
lda #>dos_text
sta diag_arg1+1
lda #2
sta diag_arg1_len
lda #$00
sta diag_arg2_len
jsr format
jsr to_screen
sec
rts

dos_digits:
ldy #'0' - 1
: iny
sec
sbc #10
bcs :-
adc #10 + '0'
sty dos_text
sta dos_text+1
rts

.segment "RODATA"
pow_lo: .byte <1, <10, <100, <1000, <10000
pow_hi: .byte >1, >10, >100, >1000, >10000
scratch_prefix: .byte "S0:"
err_suffix: .byte ".ERR,S,W"
unknown_text: .byte "INTERNAL ERROR", 0

; code, template address; 0 ends the table.
.macro DIAGNOSTIC sym, number, text
.local t
.byte number
.word t
.pushseg
.segment "RODATA"
t: .byte text, 0
.popseg
.endmacro
.segment "DATA"
catalog:
.include "../common/diagcat.inc"
.byte 0
.delmacro DIAGNOSTIC
.assert * - catalog < 256, error, "catalog table must stay below 256 bytes"

; ------------------------------------------------------------------- BSS
.segment "BSS"
diag_line: .res 2
diag_col: .res 1
diag_arg1: .res 2
diag_arg1_len: .res 1
diag_arg2: .res 2
diag_arg2_len: .res 1
diag_count: .res 1
diag_limit: .res 1
diag_screen: .res 1
diag_file_state: .res 1
diag_stopped: .res 1
name_len: .res 1
name_text: .res NAME_MAX
base_len: .res 1
scratch_len: .res 1
scratch_cmd: .res 3 + NAME_MAX + 4
open_len: .res 1
open_name: .res NAME_MAX + 8
tpl_y: .res 1
arg_index: .res 1
arg_len: .res 1
num: .res 2
num_t: .res 1
num_digit: .res 1
num_started: .res 1
clip_end: .res 1
out_y: .res 1
dos_text: .res 2
diag_code: .res 1
hdr_len: .res 1
msg_start: .res 1
buf_len: .res 1
diag_buf: .res DIAG_BUF_MAX

+ 56
- 0
newLang/src/compiler/diskio.s Zobrazit soubor

@@ -14,6 +14,7 @@
.export dio_open_cmd, dio_open_in, dio_chkin_in, dio_read_status
.export dio_read_chunk, dio_clrchn, dio_close
.export dio_count, dio_eof, dio_st, dio_dos_code, dio_status
.export dio_open_out, dio_chkout, dio_putc, dio_command

.import __DISKBUF_START__

@@ -124,6 +125,61 @@ dio_close:
.word CLOSE
rts

; OPEN DIO_LFN_ERR,8,DIO_SA_ERR,name for writing. In: A/X = name (bank 0,
; including ",S,W"), Y = length. Check dio_read_status afterwards.
dio_open_out:
sta dio_tmp
stx dio_tmp+1
sty dio_tmp+2
jsr dio_setbank
lda dio_tmp+2
ldx dio_tmp
ldy dio_tmp+1
jsr kcall_inline
.word SETNAM
lda #DIO_LFN_ERR
ldx #DIO_DEVICE
ldy #DIO_SA_ERR
jsr kcall_inline
.word SETLFS
jsr kcall_inline
.word OPEN
rts

; Select logical file X for output. C=1 with A = KERNAL error on failure.
dio_chkout:
jsr kcall_inline
.word CHKOUT
rts

; Write A to the current output channel.
dio_putc:
jsr kcall_inline
.word CHROUT
rts

; Send a DOS command (A/X = text, Y = length) on the open command channel,
; then read the reply into dio_status / dio_dos_code.
dio_command:
sta @src+1
stx @src+2
sty dio_tmp+2
ldx #DIO_LFN_CMD
jsr dio_chkout
bcs @out
ldy #$00
@byte: cpy dio_tmp+2
beq @sent
@src: lda $FFFF,y ; patched
sty dio_tmp
jsr dio_putc
ldy dio_tmp
iny
bne @byte
@sent: jsr dio_clrchn ; UNLISTEN executes the command
jmp dio_read_status
@out: rts

dio_setbank:
lda #$00 ; data and filename in bank 0
tax


+ 199
- 0
newLang/tests/diag/diag-test.s Zobrazit soubor

@@ -0,0 +1,199 @@
; E02-S04: diagnostics and ERR-writer test in x128.
;
; Phase A DIAG.N8 six representative diagnostics on a cleared screen
; Phase D BAD*.N8 ERR name the drive refuses: report, screen-only notice,
; second report on screen only
; Phase B LIMIT.N8 25 errors, screen off: 20 reported, then the limit
; Phase C CLEAN.N8 no errors: the stale CLEAN.ERR is scratched, none made
; Results (address in MB_DATA+0/1):
; +0 A count +1 D count +2 D file state +3 B count +4 C count
; +5..+8 LDTND after A, D, B, C
; +9..+33 carry returned by each of B's 25 diag_report calls
; Mailbox PASS means the program ran to the end; the host checks the rest.

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

.import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline
.import diag_init, diag_report, diag_finish, diag_line, diag_col
.import diag_arg1, diag_arg1_len, diag_arg2, diag_arg2_len
.import diag_screen, diag_file_state
.import __COMPILER_ZP_PAGE__

.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

.macro REPORT line, col, code
lda #<(line)
sta diag_line
lda #>(line)
sta diag_line+1
lda #col
sta diag_col
lda #code
jsr diag_report
.endmacro

.macro ARG1 text, len
lda #<text
sta diag_arg1
lda #>text
sta diag_arg1+1
lda #len
sta diag_arg1_len
.endmacro

.macro ARG2 text, len
lda #<text
sta diag_arg2
lda #>text
sta diag_arg2+1
lda #len
sta diag_arg2_len
.endmacro

.macro INIT name, len
lda #<name
ldx #>name
ldy #len
jsr diag_init
.endmacro

.segment "STARTUP"
.export test_entry, results
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 #$93 ; clear screen: the host reads it back
jsr kcall_inline
.word CHROUT

; ------------------------------------------------------ phase A
INIT n_diag, 7
REPORT 3, 11, E_LEX_BAD_CHAR_LITERAL
REPORT 599, 80, E_SYN_EXPECTED_END_PROGRAM
ARG1 a_counter, 7
REPORT 12, 5, E_SEM_DUPLICATE_NAME
ARG1 a_integer, 7
ARG2 a_long, 16
REPORT 40, 17, E_TYP_MISMATCH
ARG1 a_dos, 2
REPORT 1, 1, E_DSK_SOURCE_UNREADABLE
REPORT 65535, 255, E_SYN_EXPECTED_RPAREN
jsr diag_finish
sta results+0
jsr open_files
sta results+5

; ------------------------------------------------------ phase D
INIT n_bad, 7
REPORT 7, 3, E_LEX_INVALID_CHAR
REPORT 8, 4, E_LEX_INVALID_CHAR
lda diag_file_state
sta results+2
jsr diag_finish
sta results+1
jsr open_files
sta results+6

; ------------------------------------------------------ phase B
INIT n_limit, 8
lda #$00
sta diag_screen
ldx #$00
@limit: stx b_index
inx ; line = 1..25
stx diag_line
lda #$00
sta diag_line+1
lda #1
sta diag_col
lda #E_LEX_INVALID_CHAR
jsr diag_report
lda #$00
rol a ; carry -> 0/1
ldx b_index
sta results+9,x
inx
cpx #25
bne @limit
jsr diag_finish
sta results+3
jsr open_files
sta results+7

; ------------------------------------------------------ phase C
INIT n_clean, 8
jsr diag_finish
sta results+4
jsr open_files
sta results+8

jsr mmu_shutdown
lda #<results
sta MB_DATA+0
lda #>results
sta MB_DATA+1
lda #MB_ST_PASS
sta MB_STATUS
plp
ldy #$00
@print: lda ok_msg,y
beq @end
sty b_index
jsr CHROUT
ldy b_index
iny
bne @print
@end: rts

.segment "CODE"
; A = number of open logical files (system zero page via the P0 swap).
open_files:
lda __COMPILER_ZP_PAGE__ * 256 + LDTND
rts

.segment "RODATA"
n_diag: .byte "DIAG.N8"
n_bad: .byte "BAD*.N8"
n_limit: .byte "LIMIT.N8"
n_clean: .byte "CLEAN.N8"
a_counter: .byte "COUNTER"
a_integer: .byte "INTEGER"
a_long: .byte "VERYLONGCLASSNAM"
a_dos: .byte "62"
ok_msg: .byte "NOVA128 DIAG TEST OK", $0D, $00

.segment "BSS"
results: .res 34
b_index: .res 1

+ 4
- 2
newLang/tests/grammar/README.md Zobrazit soubor

@@ -20,8 +20,10 @@ The validator enforces the Vim editor profile for every `.n8` fixture:
- one symbolic diagnostic expectation for every rejected fixture;
- no diagnostic sidecars for accepted fixtures.

`.expect` files deliberately use symbolic diagnostic names. Story E02-S04 will
assign stable numeric codes without requiring grammar fixtures to guess them.
`.expect` files deliberately use symbolic diagnostic names. E02-S04 assigned
their stable numbers in `src/common/diagcat.inc` (listed in
`docs/diagnostics.md`); `scripts/build-diag-test.ps1` fails if an `.expect`
name is missing from the catalog.

## Accepted Coverage



Načítá se…
Zrušit
Uložit

Powered by TurnKey Linux.