Bläddra i källkod

newLang: add the expression and statement parser (E03-S03)

parse_statement parses every spec 7 form (assignment, call, if/else,
while, for/to/step, return, print, delete) and requires the line to end
after it. Expressions use precedence climbing over spec 8's levels: binary
operators left associative, unary not and - nesting rightwards, postfix
member access, calls, and indexing tightest. A syntax error is reported in
pass 1, unwinds to the statement, and skips the line; E03-S02's name,
member, and access checks now run from the parser in pass 2, replacing the
token scan. Expression nesting is bounded at 16 levels (E225).

Verified in x128: 31 fixtures, including the two statement fixtures that
were pending since E03-S01, a postfix trace golden for precedence and
associativity (both mutations caught), every statement form, and 15
independent syntax errors with recovery. Lowest stack pointer $A7. New
diagnostics E038-E042 and E225. The demo passes; its Vim typing is slowed
to 200 ms per key after one doubled keystroke.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
Daniel Covington 6 dagar sedan
förälder
incheckning
aa60ee11e0
15 ändrade filer med 908 tillägg och 185 borttagningar
  1. +16
    -4
      newLang/docs/architecture.md
  2. +6
    -0
      newLang/docs/diagnostics.md
  3. +97
    -0
      newLang/planning/claims/E03-S03.md
  4. +6
    -3
      newLang/scripts/build-parser-test.ps1
  5. +3
    -1
      newLang/scripts/demo-write-check.ps1
  6. +52
    -10
      newLang/scripts/verify-parser-test.ps1
  7. +6
    -0
      newLang/src/common/diagcat.inc
  8. +12
    -1
      newLang/src/common/parser.inc
  9. +496
    -160
      newLang/src/compiler/parser.s
  10. +58
    -0
      newLang/tests/parser/forms.n8
  11. +70
    -6
      newLang/tests/parser/parser-test.s
  12. +17
    -0
      newLang/tests/parser/prec.expect
  13. +21
    -0
      newLang/tests/parser/prec.n8
  14. +20
    -0
      newLang/tests/parser/syntax.expect
  15. +28
    -0
      newLang/tests/parser/syntax.n8

+ 16
- 4
newLang/docs/architecture.md Visa fil

@@ -171,8 +171,7 @@ E03-S01 implements the declaration-level part of this as `parse_file`
(`parser.s`): pass 1 parses declarations and method headers, records (`parser.s`): pass 1 parses declarations and method headers, records
top-level types, tracks block structure, and reports syntax, ordering, and top-level types, tracks block structure, and reports syntax, ordering, and
duplicate errors; pass 2 replays the source and reports type references that 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.
name no declared interface or class.


E03-S02 adds members and scopes. Pass 1 records every field and method E03-S02 adds members and scopes. Pass 1 records every field and method
signature (access, type, parameter types) in bank-1 member and parameter signature (access, type, parameter types) in bank-1 member and parameter
@@ -183,8 +182,21 @@ 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 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 of the enclosing class (D123); `expr.member` follows the left side's type
through locals, fields, `self`, `new`, call results, and indexing, and reports 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.
unknown members and private members used outside their class.

E03-S03 replaced that scan with a real statement and expression parser that
runs in both passes. `parse_statement` handles every spec 7 form (assignment,
call, if/else, while, for/to/step, return, print, delete) and requires the
line to end after it; a syntax error is reported in pass 1, unwinds to the
statement through a saved stack pointer, and skips the rest of the line, so
later lines still parse. Expressions use precedence climbing over spec 8's
levels with left-associative binary operators and right-nesting unary `not`
and `-`; postfix member access, calls, and indexing bind tightest. Name,
member, and access lookups from E03-S02 run from the parser in pass 2. Each
expression level is counted, and more than 16 is E225, which bounds hardware
stack use (the deepest fixture leaves SP at `$A7`). For tests, pass 1 can
record every expression in postfix order (`ex_trace_on`). Operator and
conversion types remain E03-S04.


### Pass 1 — declarations and layout ### Pass 1 — declarations and layout




+ 6
- 0
newLang/docs/diagnostics.md Visa fil

@@ -52,6 +52,11 @@ After 20 errors the next one is replaced by E221 and compilation stops.
| E035 | `SYN_EXPECTED_TYPE` | TYPE EXPECTED | | E035 | `SYN_EXPECTED_TYPE` | TYPE EXPECTED |
| E036 | `SYN_EXPECTED_END_WHILE` | END WHILE EXPECTED | | E036 | `SYN_EXPECTED_END_WHILE` | END WHILE EXPECTED |
| E037 | `SYN_EXPECTED_END_FOR` | END FOR EXPECTED | | E037 | `SYN_EXPECTED_END_FOR` | END FOR EXPECTED |
| E038 | `SYN_NOT_A_STATEMENT` | NOT A STATEMENT |
| E039 | `SYN_NOT_ASSIGNABLE` | CANNOT ASSIGN TO THIS |
| E040 | `SYN_EXPECTED_RBRACKET` | ']' EXPECTED |
| E041 | `SYN_EXPECTED_TO` | TO EXPECTED |
| E042 | `SYN_EXPECTED_EQUALS` | '=' EXPECTED |


## Names and declarations (E060-E079) ## Names and declarations (E060-E079)


@@ -104,3 +109,4 @@ After 20 errors the next one is replaced by E221 and compilation stops.
| E222 | `RES_NESTING_TOO_DEEP` | BLOCKS NESTED TOO DEEPLY | | E222 | `RES_NESTING_TOO_DEEP` | BLOCKS NESTED TOO DEEPLY |
| E223 | `RES_TOO_MANY_MEMBERS` | MORE THAN 255 MEMBERS | | E223 | `RES_TOO_MANY_MEMBERS` | MORE THAN 255 MEMBERS |
| E224 | `RES_TOO_MANY_PARAMS` | MORE THAN 255 PARAMETERS | | E224 | `RES_TOO_MANY_PARAMS` | MORE THAN 255 PARAMETERS |
| E225 | `RES_EXPR_TOO_DEEP` | EXPRESSION NESTED TOO DEEPLY |

+ 97
- 0
newLang/planning/claims/E03-S03.md Visa fil

@@ -0,0 +1,97 @@
---
story: E03-S03
owner: e03_expr_stmt_parser
status: done
started: 2026-11-07
depends-on: [E03-S02]
---

# E03-S03 Claim

## Intended change

Replace the pass-2 token scan in `src/compiler/parser.s` with a recursive
descent statement parser and a precedence-climbing expression parser
(spec 7 and 8). Every statement line is parsed in both passes: syntax
errors are reported in pass 1, name/member/access errors (the E03-S02
lookups, now driven by the parser) in pass 2. A syntax error unwinds to
the statement and skips its line. Expression nesting is bounded so the
hardware stack cannot overflow. For tests, pass 1 can record each
expression in postfix order (operands by column, then operators), which
shows precedence and associativity directly.

## 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/language-spec.md` (only if a decision is
needed), `docs/decisions.md`
- `planning/claims/E03-S03.md`
- generated files under `build/`

## Acceptance evidence

- [x] Implements documented precedence and left/right associativity:
`prec.n8` records each statement's expression in postfix order in x128
and all 15 match `prec.expect` (`a - b - c` -> `a b - c -`, `a / b / c`,
`- - a`, `not d and d` -> `d not d and`, `d or d and d`, mixed levels,
comparisons below arithmetic, groups). Mutations: making binary operators
right associative, or swapping the `+`/`*` levels, each fail the golden.
- [x] Parses every version 1 statement form: `forms.n8` (assignment to a
local, field, bare field, and element; call statements including chained
calls and calls as arguments; if/else nested; while; for with and without
`step`, negative step; return with and without a value; print of string,
char, boolean, and word; delete; `new` with an expression argument) and
all 7 accepted grammar fixtures parse with no diagnostics.
- [x] Rejects multiple statements per line and cross-line expressions:
`multi_stmt.n8` gives E021 at the second `print` (2:11) and
`line_cont.n8` gives E022 at the end of `total = 1 +` (3:14); both were
pending since E03-S01 and now pass as ordinary rejected fixtures.
`syntax.n8` adds `a = 1 a = 2` (E021) and `print` with no operand (E022).
- [x] Recovers sufficiently to report a later independent error:
`syntax.n8` reports 15 independent syntax errors on consecutive lines
(missing `)`/`]`, not-a-statement, unassignable target, `=`/`to`
missing in `for`, stray `)`/`*`, `else` outside an if and twice in one,
an expression nested too deeply) and then a pass-2 unknown name; a
lexer error is not followed by a cascaded E022.

## Coordination notes

- No other claim is active. Types of operators and conversions remain
E03-S04; the parser only carries the member-chain type that E03-S02's
access checks need.

## Work log

- Appended E038 NOT A STATEMENT, E039 CANNOT ASSIGN TO THIS, E040 ']'
EXPECTED, E041 TO EXPECTED, E042 '=' EXPECTED, and E225 EXPRESSION
NESTED TOO DEEPLY; docs regenerated.
- The pass-2 token scan (`scan_refs`) is gone; its name and member lookups
are called by the parser, so all E03-S02 fixtures pass unchanged.
- Expression depth is bounded at 16 levels; the harness records the
lowest hardware SP seen in `ex_enter` and fails below `$60` (seen: `$A7`).
- The write-and-check demo (which links the parser) passes; its Vim typing
once doubled a key at 120 ms per key, which the demo's read-back caught,
so its default is now 200 ms.

## Completion

- Tests run: `scripts/verify-parser-test.ps1` (final run and two
mutations), `scripts/build-diag-test.ps1` catalog check,
`scripts/demo-write-check.ps1`.
- Results: 31 parser fixtures pass (no pending ones remain); both mutations
caught; demo passes.
- Files changed: `src/compiler/parser.s`, `src/common/parser.inc`,
`src/common/diagcat.inc`, `docs/diagnostics.md`, `docs/architecture.md`,
`tests/parser/` (`prec`, `forms`, `syntax` fixtures, harness trace and SP
dump), `scripts/build-parser-test.ps1`, `scripts/verify-parser-test.ps1`,
`scripts/demo-write-check.ps1`, this claim.
- Remaining risks: `return` is accepted in the program body (spec 7 does
not restrict it; E03-S04/E04 must define what it does there).
Parenthesized groups do not carry a member-chain type, so `(x).f` is not
access-checked. Literals accept postfix syntax (`5.x`) that E03-S04 must
reject as a type error. A bare call `f()` reports the name or E021, not a
dedicated message (D123). The trace hook writes without a bound; only the
test enables it.

+ 6
- 3
newLang/scripts/build-parser-test.ps1 Visa fil

@@ -1,4 +1,4 @@
# E03-S01/E03-S02: build the parser test PRG and a fresh D81 holding every grammar and
# E03-S01..E03-S03: build the parser test PRG and a fresh D81 holding every grammar and
# parser fixture. The fixture order must match tests/parser/parser-test.s. # parser fixture. The fixture order must match tests/parser/parser-test.s.
[CmdletBinding()] [CmdletBinding()]
param() param()
@@ -26,12 +26,12 @@ $script:ParserFixtures = [ordered]@{
"badmethod.n8" = @("$g\rejected\bad_method.n8", "rejected", "$g\rejected\bad_method.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") "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") "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")
"linecont.n8" = @("$g\rejected\line_cont.n8", "rejected", "$g\rejected\line_cont.expect")
"missclass.n8" = @("$g\rejected\miss_class.n8", "rejected", "$g\rejected\miss_class.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") "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") "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") "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")
"multistmt.n8" = @("$g\rejected\multi_stmt.n8", "rejected", "$g\rejected\multi_stmt.expect")
"order.n8" = @("$p\order.n8", "exact", "$p\order.expect") "order.n8" = @("$p\order.n8", "exact", "$p\order.expect")
"unknown.n8" = @("$p\unknown.n8", "exact", "$p\unknown.expect") "unknown.n8" = @("$p\unknown.n8", "exact", "$p\unknown.expect")
"recover.n8" = @("$p\recover.n8", "exact", "$p\recover.expect") "recover.n8" = @("$p\recover.n8", "exact", "$p\recover.expect")
@@ -41,6 +41,9 @@ $script:ParserFixtures = [ordered]@{
"lifecycle.n8" = @("$p\lifecycle.n8", "exact", "$p\lifecycle.expect") "lifecycle.n8" = @("$p\lifecycle.n8", "exact", "$p\lifecycle.expect")
"scopes.n8" = @("$p\scopes.n8", "exact", "$p\scopes.expect") "scopes.n8" = @("$p\scopes.n8", "exact", "$p\scopes.expect")
"private.n8" = @("$p\private.n8", "exact", "$p\private.expect") "private.n8" = @("$p\private.n8", "exact", "$p\private.expect")
"prec.n8" = @("$p\prec.n8", "accepted", "$p\prec.expect")
"forms.n8" = @("$p\forms.n8", "accepted", $null)
"syntax.n8" = @("$p\syntax.n8", "exact", "$p\syntax.expect")
} }


$link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out `


+ 3
- 1
newLang/scripts/demo-write-check.ps1 Visa fil

@@ -15,7 +15,9 @@
# 4. The screen and NAME.ERR, if any, are shown. # 4. The screen and NAME.ERR, if any, are shown.
# Screenshots go to build/demo/. No code is generated yet (epic E04). # Screenshots go to build/demo/. No code is generated yet (epic E04).
[CmdletBinding()] [CmdletBinding()]
param([string[]]$Programs = @("good.n8", "bad.n8"), [int]$KeyDelay = 120)
# At 120 ms per key Vim once saw a doubled key and a lost RETURN; the
# read-back comparison catches that, and 200 ms leaves more margin.
param([string[]]$Programs = @("good.n8", "bad.n8"), [int]$KeyDelay = 200)


$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent $project = Split-Path $PSScriptRoot -Parent


+ 52
- 10
newLang/scripts/verify-parser-test.ps1 Visa fil

@@ -52,8 +52,9 @@ function Read-DumpType {
$records = @(); $script:p = 0 $records = @(); $script:p = 0
while ($dump[$p] -ne 0xFE) { while ($dump[$p] -ne 0xFE) {
if ($dump[$p] -ne 0xFF) { throw "dump out of sync at $p" } if ($dump[$p] -ne 0xFF) { throw "dump out of sync at $p" }
$r = [pscustomobject]@{ Index = $dump[$p + 1]; Load = $dump[$p + 2]; Count = $dump[$p + 3]; Types = @(); Members = @() }
$p += 4
$r = [pscustomobject]@{ Index = $dump[$p + 1]; Load = $dump[$p + 2]; Count = $dump[$p + 3]
MinSp = $dump[$p + 4]; Types = @(); Members = @(); Trace = $null }
$p += 5
while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFC) { while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFC) {
$len = $dump[$p + 3] $len = $dump[$p + 3]
$spell = ConvertFrom-Spelling $dump[($p + 4)..($p + 3 + $len)] $spell = ConvertFrom-Spelling $dump[($p + 4)..($p + 3 + $len)]
@@ -62,7 +63,7 @@ while ($dump[$p] -ne 0xFE) {
} }
if ($dump[$p] -eq 0xFC) { if ($dump[$p] -eq 0xFC) {
$p++ $p++
while ($dump[$p] -ne 0xFD) {
while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFB) {
$owner = Read-DumpName $owner = Read-DumpName
$name = Read-DumpName $name = Read-DumpName
$flags = $dump[$p]; $p++ $flags = $dump[$p]; $p++
@@ -73,11 +74,42 @@ while ($dump[$p] -ne 0xFE) {
$(if ($flags -band 1) { "method" } else { "field" }), $(if ($flags -band 1) { "method" } else { "field" }),
$(if ($flags -band 2) { "private" } else { "public" }), $type, ($params -join ",")) $(if ($flags -band 2) { "private" } else { "public" }), $type, ($params -join ","))
} }
if ($dump[$p] -eq 0xFB) {
$n = $dump[$p + 1] + 256 * $dump[$p + 2]
$r.Trace = if ($n) { [byte[]]$dump[($p + 3)..($p + 2 + $n)] } else { [byte[]]@() }
$p += 3 + $n
}
} }
$p++ $p++
$records += $r $records += $r
} }


# Expression trace -> "line: postfix" strings. Operands are named by the
# source text at their column; operators by their symbol (parser.inc TR_*).
$traceOps = @{ 0x17 = "="; 0x1E = "<>"; 0x1C = "<"; 0x1D = ">"; 0x1F = "<="; 0x20 = ">="
0x18 = "+"; 0x19 = "-"; 0x1A = "*"; 0x1B = "/"; 0x40 = "and"; 0x53 = "or" }
function ConvertFrom-Trace([byte[]]$trace, [string[]]$lines) {
$out = @(); $k = 0; $cur = $null
while ($k -lt $trace.Length) {
$b = $trace[$k]
if ($b -eq 0xFF) {
if ($cur) { $out += $cur }
$line = $trace[$k + 1] + 256 * $trace[$k + 2]; $k += 3
$cur = "${line}:"; $src = $lines[$line - 1]; continue
}
if ($b -eq 0xF0) { $cur += " neg" }
elseif ($b -eq 0xF1) { $cur += " not" }
elseif ($b -ge 0x80) { $cur += " " + $traceOps[$b - 0x80] }
else {
$m = [regex]::Match($src.Substring($b - 1), '^(\w+|"[^"]*"|''.'')')
$cur += " " + $m.Value.ToLower()
}
$k++
}
if ($cur) { $out += $cur }
return ,$out
}

# Members the source declares, derived independently by regex: per class or # Members the source declares, derived independently by regex: per class or
# interface in order; access defaults (fields private, methods public); # interface in order; access defaults (fields private, methods public);
# a second declaration of a name in the same namespace is not recorded. # a second declaration of a name in the same namespace is not recorded.
@@ -135,7 +167,7 @@ function Parse-Err([string]$line) {
} }


# ---------------------------------------------------------------- check # ---------------------------------------------------------------- check
$failures = @(); $i = 0; $pending = @()
$failures = @(); $i = 0; $minSp = 0xFF
foreach ($name in $ParserFixtures.Keys) { foreach ($name in $ParserFixtures.Keys) {
$src, $group, $expect = $ParserFixtures[$name] $src, $group, $expect = $ParserFixtures[$name]
$base = $name -replace '\.n8$', '' $base = $name -replace '\.n8$', ''
@@ -174,11 +206,6 @@ foreach ($name in $ParserFixtures.Keys) {
} }
$detail = if ($errLines) { $errLines[0] } else { "" } $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" { "exact" {
$want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") } | ForEach-Object { $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] }) $f = $_ -split ' '; "{0} {1} {2}" -f $codes[$f[0]], $f[1], $f[2] })
@@ -196,9 +223,24 @@ foreach ($name in $ParserFixtures.Keys) {
} }
$detail += ", {0} members" -f $r.Members.Count $detail += ", {0} members" -f $r.Members.Count
} }
if ($group -eq "accepted" -and $expect) { # expression trace golden
$want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") })
if ($null -eq $r.Trace) { $problems += "no expression trace" }
else {
$got = ConvertFrom-Trace $r.Trace @(Get-Content -LiteralPath $src)
for ($k = 0; $k -lt [Math]::Max($got.Count, $want.Count); $k++) {
if ($got[$k] -ne $want[$k]) { $problems += "trace '$($got[$k])' expected '$($want[$k])'" }
}
$detail += ", {0} traced statements" -f $got.Count
}
}
if ($r.MinSp -ne 0xFF -and $r.MinSp -lt $minSp) { $minSp = $r.MinSp }
$status = if ($problems) { "FAIL" } else { "ok" } $status = if ($problems) { "FAIL" } else { "ok" }
Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail) Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail)
if ($problems) { $failures += "${name}: " + ($problems -join "; ") } if ($problems) { $failures += "${name}: " + ($problems -join "; ") }
} }
# The expression bound must leave hardware stack to spare (KERNAL calls and
# interrupts run below the parser).
if ($minSp -lt 0x60) { $failures += ("expression parsing took the stack down to `${0:X2}" -f $minSp) }
if ($failures) { throw ("parser verification failed:`n " + ($failures -join "`n ")) } 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)
Write-Host ("verified parser: {0} fixtures; lowest stack pointer in expressions `${1:X2}" -f $ParserFixtures.Count, $minSp)

+ 6
- 0
newLang/src/common/diagcat.inc Visa fil

@@ -47,6 +47,11 @@
DIAGNOSTIC SYN_EXPECTED_TYPE, 35, "TYPE EXPECTED" DIAGNOSTIC SYN_EXPECTED_TYPE, 35, "TYPE EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_WHILE, 36, "END WHILE EXPECTED" DIAGNOSTIC SYN_EXPECTED_END_WHILE, 36, "END WHILE EXPECTED"
DIAGNOSTIC SYN_EXPECTED_END_FOR, 37, "END FOR EXPECTED" DIAGNOSTIC SYN_EXPECTED_END_FOR, 37, "END FOR EXPECTED"
DIAGNOSTIC SYN_NOT_A_STATEMENT, 38, "NOT A STATEMENT"
DIAGNOSTIC SYN_NOT_ASSIGNABLE, 39, "CANNOT ASSIGN TO THIS"
DIAGNOSTIC SYN_EXPECTED_RBRACKET, 40, "']' EXPECTED"
DIAGNOSTIC SYN_EXPECTED_TO, 41, "TO EXPECTED"
DIAGNOSTIC SYN_EXPECTED_EQUALS, 42, "'=' EXPECTED"


; -------------------------------------------- names and declarations ; -------------------------------------------- names and declarations
DIAGNOSTIC SEM_DUPLICATE_NAME, 60, "% IS ALREADY DECLARED" DIAGNOSTIC SEM_DUPLICATE_NAME, 60, "% IS ALREADY DECLARED"
@@ -78,3 +83,4 @@
DIAGNOSTIC RES_NESTING_TOO_DEEP, 222, "BLOCKS NESTED TOO DEEPLY" DIAGNOSTIC RES_NESTING_TOO_DEEP, 222, "BLOCKS NESTED TOO DEEPLY"
DIAGNOSTIC RES_TOO_MANY_MEMBERS, 223, "MORE THAN 255 MEMBERS" DIAGNOSTIC RES_TOO_MANY_MEMBERS, 223, "MORE THAN 255 MEMBERS"
DIAGNOSTIC RES_TOO_MANY_PARAMS, 224, "MORE THAN 255 PARAMETERS" DIAGNOSTIC RES_TOO_MANY_PARAMS, 224, "MORE THAN 255 PARAMETERS"
DIAGNOSTIC RES_EXPR_TOO_DEEP, 225, "EXPRESSION NESTED TOO DEEPLY"

+ 12
- 1
newLang/src/common/parser.inc Visa fil

@@ -10,7 +10,18 @@ TYPE_CLASS = 2
TYPE_PROGRAM = 3 TYPE_PROGRAM = 3


PARSE_MAX_DEPTH = 16 ; nested method/program/if/while/for blocks PARSE_MAX_DEPTH = 16 ; nested method/program/if/while/for blocks
SCAN_MAX_DEPTH = 16 ; nested ( and [ tracked by the pass-2 scan
EX_MAX_DEPTH = 16 ; expression levels: parse_expr calls + unary ops

; What a postfix expression ends in (statement checks)
EX_OTHER = 0 ; literal, self, new, (group), operator result
EX_PLACE = 1 ; name, field, or element: assignable
EX_CALL = 2 ; method call: a statement by itself

; Test trace bytes (parser ex_trace_on): operand = its column (1-80)
TR_BINARY = $80 ; + operator token kind ($80-$DF)
TR_NEG = $F0 ; unary -
TR_NOT = $F1 ; unary not
TR_LINE = $FF ; statement start, then line lo/hi


; Declared-type descriptor (provisional until E03-S04): base, name, length. ; Declared-type descriptor (provisional until E03-S04): base, name, length.
; base TY_*; TY_NONE for a method without a result ; base TY_*; TY_NONE for a method without a result


+ 496
- 160
newLang/src/compiler/parser.s Visa fil

@@ -1,25 +1,26 @@
; parser.s - declaration, member, and scope parser (E03-S01, E03-S02).
; parser.s - declarations, members, scopes, statements, and expressions
; (E03-S01, E03-S02, E03-S03).
; ;
; parse_file runs two passes over the loaded source (call after src_load, ; parse_file runs two passes over the loaded source (call after src_load,
; diag_init, and names_init): ; diag_init, and names_init):
; pass 1 parses interfaces, classes (implements lists, fields, method ; pass 1 parses interfaces, classes (implements lists, fields, method
; headers), and the one program; records each top-level type in ; headers), and the one program; records each top-level type in
; the bank-1 type table and each field and method signature in the ; the bank-1 type table and each field and method signature in the
; member and parameter tables; reports syntax, ordering, duplicate,
; init/destroy shape, and local-placement errors.
; pass 2 replays the source and reports type references that do not name
; a declared interface or class (forward references are therefore
; legal), and scans statement lines for names: see scan_refs.
; Method and program bodies are parsed line by line: `var` declarations are
; parsed, typed, and duplicate-checked in the body's own scope; if/while/for/
; end blocks are tracked; other statement lines are skimmed in pass 1 and
; scanned for name references in pass 2 until E03-S03 parses them.
; member and parameter tables; parses every statement; reports
; syntax, ordering, duplicate, init/destroy shape, and
; local-placement errors.
; pass 2 replays the source, parsing everything again, and reports type
; references that do not name a declared interface or class
; (forward references are therefore legal) and name, member, and
; access errors in statements.
; Statements are parsed by parse_statement and expressions by precedence
; climbing (parse_expr); see the "statements" and "expressions" sections.
; ;
; Scopes: every method signature and the program start an empty local scope ; Scopes: every method signature and the program start an empty local scope
; (parameters and locals share it). Inside a method an unqualified name is a ; (parameters and locals share it). Inside a method an unqualified name is a
; local or parameter, else a field of the enclosing class; in the program it
; is a local. Members are reached through `expr.name`; a private member is
; visible only inside methods of its own class.
; local or parameter, else a field of the enclosing class (D123); in the
; program it is a local. Members are reached through `expr.name`; a private
; member is visible only inside methods of its own class.
; ;
; Recovery: an error skips the rest of its line. A mismatched `end X` ; 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 ; reports the innermost missing end and closes blocks up to X. A top-level
@@ -41,6 +42,8 @@
.importzp mmu_ptr .importzp mmu_ptr


.export parse_file, tb_get, mem_count, par_count .export parse_file, tb_get, mem_count, par_count
.export ex_trace_on, ex_min_sp
.exportzp ex_tp
.export type_kind, type_line_lo, type_line_hi .export type_kind, type_line_lo, type_line_hi


ACC_DEFAULT = 0 ACC_DEFAULT = 0
@@ -49,6 +52,9 @@ ACC_PRIVATE = 2
LIFE_INIT = 1 LIFE_INIT = 1
LIFE_DESTROY = 2 LIFE_DESTROY = 2


.segment "ZEROPAGE"
ex_tp: .res 2 ; test trace pointer (ex_trace_on)

.macro IFTOK kind, target .macro IFTOK kind, target
lda tok_kind lda tok_kind
cmp #kind cmp #kind
@@ -67,6 +73,8 @@ parse_file:
sta par_count sta par_count
sta mem_full sta mem_full
sta par_full sta par_full
lda #$FF
sta ex_min_sp
lda #$01 lda #$01
sta pass sta pass
jsr parse_pass jsr parse_pass
@@ -89,6 +97,8 @@ parse_pass:
sta eof_reported sta eof_reported
sta cur_owner sta cur_owner
sta cur_class sta cur_class
sta lex_err_line ; lines are 1-based: 0 matches none
sta lex_err_line+1
jsr next jsr next
top: jsr skip_blank top: jsr skip_blank
lda pending_end ; stray `end X` left by a body: reported lda pending_end ; stray `end X` left by a body: reported
@@ -576,10 +586,19 @@ type_ref:
; Parse a method or program body. A = KW_METHOD or KW_PROGRAM. ; Parse a method or program body. A = KW_METHOD or KW_PROGRAM.
parse_body: parse_body:
sta block_stack sta block_stack
lda #$00
sta block_else
sta stmt_seen
lda #$01 lda #$01
sta depth sta depth
sta refs_on ; names resolve, except in the methods of
lda block_stack ; a class whose name failed to parse
cmp #KW_METHOD
bne @loop
lda cur_class
bne @loop
lda #$00 lda #$00
sta stmt_seen
sta refs_on
@loop: jsr skip_blank @loop: jsr skip_blank
lda tok_kind lda tok_kind
cmp #TOK_EOF cmp #TOK_EOF
@@ -589,31 +608,14 @@ parse_body:
bne :+ bne :+
jmp @interrupted jmp @interrupted
: IFTOK KW_VAR, @var : IFTOK KW_VAR, @var
IFTOK KW_IF, @open
IFTOK KW_WHILE, @open
IFTOK KW_FOR, @open
IFTOK KW_END, @end IFTOK KW_END, @end
lda #$01 ; any other line is a statement lda #$01 ; any other line is a statement
sta stmt_seen sta stmt_seen
jsr stmt_line
jsr parse_statement
jmp @loop jmp @loop
@var: jsr check_var_place @var: jsr check_var_place
jsr parse_var ; declared anyway, to avoid cascades jsr parse_var ; declared anyway, to avoid cascades
jmp @loop jmp @loop
@open: lda #$01
sta stmt_seen
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 stmt_line ; condition / loop header
jmp @loop


@end: jsr next ; the closer keyword @end: jsr next ; the closer keyword
lda tok_kind lda tok_kind
@@ -679,20 +681,6 @@ check_var_place:
jmp err1 jmp err1
@ok: rts @ok: rts


; A statement line: skimmed in pass 1, scanned for names in pass 2. The
; methods of a class whose name failed to parse are only skimmed.
stmt_line:
lda pass
cmp #$02
bne @skim
lda block_stack
cmp #KW_METHOD
bne @scan
lda cur_class
beq @skim
@scan: jmp scan_refs
@skim: jmp skip_line

; X = 1-based depth of the innermost block opened by tok_kind, or 0. A ; 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. ; foreign `end class` / `end interface` reports the innermost block only.
closer_in_stack: closer_in_stack:
@@ -744,89 +732,439 @@ parse_var:
: jsr set_local_type : jsr set_local_type
jmp expect_eol jmp expect_eol


; ------------------------------------------------------ pass-2 name scan
; Scan one statement line (pass 2) up to and past its EOL, resolving names:
; name a local/parameter, else (in a method) a field of the class
; self the enclosing class; not allowed in the program
; new Name a declared class or interface
; expr.name a field, or a method when `(` follows, of expr's class or
; interface; private members only inside their own class
; expr's type is followed through locals, fields, self, new, call results
; (`x.get().y`), and indexing (`a[i].y`). Where it is unknown or not a class
; or interface (an expression result, a primitive, an unknown type), the
; member is not checked: type errors belong to E03-S04.
scan_refs:
; ----------------------------------------------------------- statements
; One statement line, through its EOL (spec 7). Syntax errors are reported
; in pass 1 by syn_error, which also unwinds to here and skips the line;
; name, member, and access errors are reported in pass 2 as the parser
; meets them (resolve_name, resolve_member, self_ref, new_type).
parse_statement:
tsx
stx stmt_sp
lda #$00 lda #$00
sta sc_sp
sta sc_over
sta pend
sta sc_prev ; TOK_EOF: no previous token
jsr chain_none
@loop: lda #$00
sta call_base ; no method result waiting for `(`
lda pend
beq @tok
jsr resolve_member ; the token after a member name decides
@tok: lda tok_kind
sta ex_depth
lda tok_col
sta stmt_col
jsr trace_line
lda tok_kind
cmp #KW_IF
beq @block
cmp #KW_WHILE
beq @block
cmp #KW_FOR
beq @for
cmp #KW_ELSE
beq @else
cmp #KW_RETURN
beq @return
cmp #KW_PRINT
beq @value
cmp #KW_DELETE
beq @value
jmp stmt_expr
@block: jsr open_block ; if / while condition
@value: jsr next ; print / delete operand
jsr parse_full
jmp end_stmt
@return:
jsr next
lda tok_kind
cmp #TOK_EOL cmp #TOK_EOL
beq @end
beq :+
cmp #TOK_EOF cmp #TOK_EOF
beq @end
ldx sc_prev
cpx #TOK_DOT
bne @kind
cmp #TOK_IDENT ; member name; keywords allowed (D121)
beq @member
cmp #TOK_KW_FIRST
bcc @kind
@member:
jsr pend_member
jmp @adv
@kind: cmp #TOK_IDENT
beq @ident
beq :+
jsr parse_full
: jmp end_stmt
@else: ldx depth ; only once, directly inside an if
lda block_stack-1,x
cmp #KW_IF
bne @bad_else
lda block_else-1,x
bne @bad_else
lda #$01
sta block_else-1,x
jsr next
jmp end_stmt
@bad_else:
jsr arg_token
lda #E_SYN_UNEXPECTED_TOKEN
jmp syn_error
; for name = expr to expr [step expr]
@for: jsr open_block
jsr next
lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jmp syn_error
: jsr resolve_name
jsr next
lda tok_kind
cmp #TOK_EQ
beq :+
lda #E_SYN_EXPECTED_EQUALS
jmp syn_error
: jsr next
jsr parse_full
lda tok_kind
cmp #KW_TO
beq :+
lda #E_SYN_EXPECTED_TO
jmp syn_error
: jsr next
jsr parse_full
lda tok_kind
cmp #KW_STEP
bne end_stmt
jsr next
jsr parse_full
; fall through

; The statement must end here: one statement per line (spec 2.2).
end_stmt:
lda tok_kind
cmp #TOK_EOL
beq @eol
cmp #TOK_EOF
beq @done
lda #E_SYN_EXPECTED_EOL
jmp syn_error
@eol: jmp next
@done: rts

; Assignment `target = expr` or a call `expr.method(args)`.
stmt_expr:
jsr parse_postfix
lda tok_kind
cmp #TOK_EQ
beq @assign
lda ex_last ; anything else must be a call
cmp #EX_CALL
beq end_stmt
lda #E_SYN_NOT_A_STATEMENT
ldx stmt_col
jmp syn_error_col
@assign:
lda ex_last
cmp #EX_PLACE
beq :+
lda #E_SYN_NOT_ASSIGNABLE
ldx stmt_col
jmp syn_error_col
: jsr next
jsr parse_full
jmp end_stmt

; Push the block opened by the current keyword (if/while/for).
open_block:
ldx depth
cpx #PARSE_MAX_DEPTH
bcc :+
lda #E_RES_NESTING_TOO_DEEP
jmp syn_error
: lda tok_kind
sta block_stack,x
lda #$00
sta block_else,x
inc depth
rts

; Report A at the current token (or column X) in pass 1, unless the lexer
; already reported an error on this line; then skip the rest of the line
; and return from parse_statement.
syn_error:
ldx tok_col
syn_error_col:
ldy lex_err_line
cpy tok_line
bne @report
ldy lex_err_line+1
cpy tok_line+1
beq @skip
@report:
ldy pass
cpy #$01
bne @skip
jsr report_col
@skip: ldx stmt_sp
txs
jmp skip_line

; ---------------------------------------------------------- expressions
; Precedence climbing over spec 8's binary levels (or 1, and 2, = <> 3,
; < <= > >= 4, + - 5, * / 6); every binary operator is left associative,
; so its right operand is parsed one level tighter. Unary not and - (level
; 7) nest to the right. Postfix member access, calls, and indexing bind
; tightest. Each nesting level (parse_expr or a unary operator) counts
; toward EX_MAX_DEPTH, which bounds hardware-stack use.
;
; sc_* is the member-chain type when the expression is a single postfix
; chain (none after an operator); ex_last says what that chain ends in.

parse_full:
lda #$01
; Parse an expression whose binary operators have precedence >= A.
parse_expr:
jsr ex_enter
sta ex_min,x
jsr parse_unary
@loop: ldx #bin_count - 1
lda tok_kind
@find: cmp bin_kinds,x
beq @binary
dex
bpl @find
@done: dec ex_depth
rts
@binary:
lda bin_precs,x
ldx ex_depth
cmp ex_min,x
bcc @done ; binds looser: the caller takes it
sta ex_prec,x
lda tok_kind
sta ex_op,x
jsr next
ldx ex_depth
lda ex_prec,x
clc
adc #$01 ; left associative
jsr parse_expr
ldx ex_depth
lda ex_op,x
ora #TR_BINARY
jsr trace_byte
jsr chain_none
lda #EX_OTHER
sta ex_last
jmp @loop

parse_unary:
lda tok_kind
cmp #KW_NOT
beq @op
cmp #TOK_MINUS
beq @op
jmp parse_postfix
@op: jsr ex_enter
lda tok_kind
sta ex_op,x
jsr next
jsr parse_unary ; right associative: - not -x
ldx ex_depth
lda #TR_NOT
ldy ex_op,x
cpy #KW_NOT
beq :+
lda #TR_NEG
: jsr trace_byte
jsr chain_none
lda #EX_OTHER
sta ex_last
dec ex_depth
rts

; One more nesting level; A preserved, X = the new depth.
ex_enter:
inc ex_depth
sta ex_tmp
tsx ; deepest hardware stack seen (evidence)
cpx ex_min_sp
bcs :+
stx ex_min_sp
: ldx ex_depth
cpx #EX_MAX_DEPTH + 1
bcs @deep
lda ex_tmp
rts
@deep: lda #E_RES_EXPR_TOO_DEEP
jmp syn_error

; primary { .name | .name(args) | [expr] }
parse_postfix:
lda #EX_OTHER
sta ex_last
jsr chain_none
lda tok_kind
cmp #TOK_LPAREN ; a group is traced by its contents
beq @group
lda tok_col ; trace: the operand, by its column
jsr trace_byte
lda tok_kind
cmp #TOK_IDENT
beq @name
cmp #KW_NEW
beq @new
cmp #KW_SELF cmp #KW_SELF
beq @self beq @self
cmp #TOK_DOT
beq @adv ; the chain is the member's left side
ldx #lit_count - 1
@lit: cmp lit_kinds,x
beq @literal
dex
bpl @lit
cmp #TOK_EOL ; an operand is missing at the line end:
beq @early ; expressions do not continue (spec 2.2)
cmp #TOK_EOF
beq @early
jsr arg_token
lda #E_SYN_UNEXPECTED_TOKEN
jmp syn_error
@early: lda #E_SYN_UNEXPECTED_EOL
jmp syn_error
@literal:
jsr next
jmp @post
@name: jsr resolve_name
lda #EX_PLACE
sta ex_last
jsr next
jmp @post
@self: jsr self_ref
jsr next
jmp @post
@group: jsr next
jsr parse_full
jsr chain_none
lda #EX_OTHER
sta ex_last
lda tok_kind
cmp #TOK_RPAREN
beq :+
lda #E_SYN_EXPECTED_RPAREN
jmp syn_error
: jsr next
jmp @post
@new: jsr next ; new Name(args)
lda tok_kind
cmp #TOK_IDENT
beq :+
lda #E_SYN_EXPECTED_IDENTIFIER
jmp syn_error
: jsr new_type
jsr next
lda tok_kind
cmp #TOK_LPAREN cmp #TOK_LPAREN
beq @open
beq :+
lda #E_SYN_EXPECTED_LPAREN
jmp syn_error
: jsr ex_save_chain
jsr parse_args
jsr ex_restore_chain
lda #EX_OTHER
sta ex_last
@post: lda tok_kind
cmp #TOK_DOT
beq @member
cmp #TOK_LBRACKET cmp #TOK_LBRACKET
beq @index beq @index
cmp #TOK_RPAREN
beq @close
rts
@member:
jsr next
lda tok_kind ; member names may be keywords (D121)
cmp #TOK_IDENT
beq :+
cmp #TOK_KW_FIRST
bcs :+
lda #E_SYN_EXPECTED_IDENTIFIER
jmp syn_error
: jsr pend_member ; the name and the left side's type
jsr next
lda #$00
sta call_base
sta call_name
jsr resolve_member ; `(` next: a method, else a field
lda tok_kind
cmp #TOK_LPAREN
beq @call
lda #EX_PLACE
sta ex_last
jmp @post
@call: lda call_base ; the result survives the arguments
sta sc_base
lda call_name
sta sc_name
jsr ex_save_chain
jsr parse_args
jsr ex_restore_chain
lda #EX_CALL
sta ex_last
jmp @post
@index: jsr element_type
sta sc_base
stx sc_name
jsr ex_save_chain
jsr next
jsr parse_full
lda tok_kind
cmp #TOK_RBRACKET cmp #TOK_RBRACKET
beq :+
lda #E_SYN_EXPECTED_RBRACKET
jmp syn_error
: jsr next
jsr ex_restore_chain
lda #EX_PLACE
sta ex_last
jmp @post

; Keep sc_base/sc_name (never an array here) across nested expressions.
ex_save_chain:
ldx ex_depth
lda sc_base
sta ex_cbase,x
lda sc_name
sta ex_cname,x
rts
ex_restore_chain:
ldx ex_depth
lda ex_cbase,x
sta sc_base
lda ex_cname,x
sta sc_name
lda #$00
sta sc_len
sta sc_len+1
rts

; `(` [expr {, expr}] `)` with the current token at `(`.
parse_args:
jsr next
lda tok_kind
cmp #TOK_RPAREN
beq @close beq @close
jsr chain_none ; operators, literals, other keywords
jmp @adv
@ident: lda sc_prev
cmp #KW_NEW
@arg: jsr parse_full
lda tok_kind
cmp #TOK_COMMA
bne :+ bne :+
jsr new_type
jmp @adv
: jsr resolve_name
jmp @adv
@self: jsr self_ref
jmp @adv
@open: lda call_base ; a call's result, or none (grouping)
ldx call_name
jsr push_type
jsr chain_none
jmp @adv
@index: jsr element_type
jsr push_type
jsr chain_none
jmp @adv
@close: jsr pop_type
@adv: lda tok_kind
sta sc_prev
jsr next jsr next
jmp @loop
@end: jmp skip_line ; past the EOL; EOF stays
jmp @arg
: cmp #TOK_RPAREN
beq @close
lda #E_SYN_EXPECTED_RPAREN
jmp syn_error
@close: jmp next

; Test trace (pass 1, when ex_trace_on): each statement starts with $FF and
; its line; operands are their column; operators follow their operands.
trace_line:
lda #TR_LINE
jsr trace_byte
lda tok_line
jsr trace_byte
lda tok_line+1
trace_byte:
ldy ex_trace_on
beq @off
ldy pass
cpy #$01
bne @off
ldy #$00
sta (ex_tp),y
inc ex_tp
bne @off
inc ex_tp+1
@off: rts


; Remember the member name token; the next token picks field or method. ; Remember the member name token; the next token picks field or method.
pend_member: pend_member:
lda #$01
sta pend
lda tok_col lda tok_col
sta pend_col sta pend_col
lda tok_len lda tok_len
@@ -845,8 +1183,8 @@ pend_member:
; Resolve the pending member against its left side's type. The member is a ; Resolve the pending member against its left side's type. The member is a
; method when the current token is `(`, else a field. ; method when the current token is `(`, else a field.
resolve_member: resolve_member:
lda #$00
sta pend
lda refs_on
beq @none
lda pend_base lda pend_base
cmp #TY_REF cmp #TY_REF
bne @none bne @none
@@ -907,7 +1245,10 @@ resolve_member:


; A bare name: a local or parameter, else a field of the enclosing class. ; A bare name: a local or parameter, else a field of the enclosing class.
resolve_name: resolve_name:
jsr find_tok
lda refs_on
bne :+
jmp chain_none
: jsr find_tok
beq @unknown beq @unknown
sta rn_id sta rn_id
jsr scope_test jsr scope_test
@@ -939,7 +1280,10 @@ resolve_name:
jmp err2 jmp err2


self_ref: self_ref:
lda cur_class
lda refs_on
bne :+
jmp chain_none
: lda cur_class
beq @bad beq @bad
sta sc_name sta sc_name
lda #TY_REF lda #TY_REF
@@ -956,7 +1300,10 @@ self_ref:
; `new Name`: Name must be a declared class or interface. ; `new Name`: Name must be a declared class or interface.
new_type: new_type:
jsr chain_none jsr chain_none
jsr find_tok
lda refs_on
bne :+
rts
: jsr find_tok
beq @unknown beq @unknown
sta sc_name sta sc_name
tay tay
@@ -1012,39 +1359,6 @@ element_type:
tax tax
rts rts


; Push base A / name X for the matching `)` or `]`. Beyond SCAN_MAX_DEPTH
; levels only the count is kept and the result is unknown.
push_type:
ldy sc_sp
cpy #SCAN_MAX_DEPTH
bcc :+
inc sc_over
rts
: sta sc_sbase,y
txa
sta sc_sname,y
inc sc_sp
rts

pop_type:
lda sc_over
beq :+
dec sc_over
jmp chain_none
: ldy sc_sp
bne :+
jmp chain_none ; unbalanced: E03-S03 reports it
: dey
sty sc_sp
lda sc_sbase,y
sta sc_base
lda sc_sname,y
sta sc_name
lda #$00
sta sc_len
sta sc_len+1
rts

; Report A at the pending member's column (pass 2). ; Report A at the pending member's column (pass 2).
err_pend: err_pend:
pha pha
@@ -1367,6 +1681,10 @@ next: jsr lex_next
lda tok_kind lda tok_kind
cmp #TOK_ERROR cmp #TOK_ERROR
bne :+ bne :+
lda tok_line ; statement errors on this line would
sta lex_err_line ; only be cascades
lda tok_line+1
sta lex_err_line+1
lda tok_err ; LEXERR_n -> E_LEX_INVALID_CHAR + n - 1 lda tok_err ; LEXERR_n -> E_LEX_INVALID_CHAR + n - 1
clc clc
adc #E_LEX_INVALID_CHAR - LEXERR_INVALID_CHAR adc #E_LEX_INVALID_CHAR - LEXERR_INVALID_CHAR
@@ -1511,6 +1829,15 @@ end_codes:
.byte E_SYN_EXPECTED_END_IF, E_SYN_EXPECTED_END_WHILE, E_SYN_EXPECTED_END_FOR .byte E_SYN_EXPECTED_END_IF, E_SYN_EXPECTED_END_WHILE, E_SYN_EXPECTED_END_FOR
bit_masks: bit_masks:
.byte $01, $02, $04, $08, $10, $20, $40, $80 .byte $01, $02, $04, $08, $10, $20, $40, $80
bin_kinds: ; binary operators and their precedence
.byte KW_OR, KW_AND, TOK_EQ, TOK_NE, TOK_LT, TOK_LE, TOK_GT, TOK_GE
.byte TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH
bin_count = * - bin_kinds
bin_precs:
.byte 1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6
lit_kinds:
.byte TOK_NUMBER, TOK_STRING, TOK_CHAR, KW_TRUE, KW_FALSE, KW_NULL
lit_count = * - lit_kinds
life_words: ; LIFE_INIT, LIFE_DESTROY order life_words: ; LIFE_INIT, LIFE_DESTROY order
.byte 4, "INIT" .byte 4, "INIT"
.byte 7, "DESTROY" .byte 7, "DESTROY"
@@ -1574,18 +1901,27 @@ loc_tbase: .res 256 ; by name ID; valid while its scope bit is se
loc_tname: .res 256 loc_tname: .res 256
loc_tlen_lo: .res 256 loc_tlen_lo: .res 256
loc_tlen_hi: .res 256 loc_tlen_hi: .res 256
; pass-2 scan
sc_prev: .res 1
sc_base: .res 1 ; type of the expression so far
; statements and expressions
block_else: .res PARSE_MAX_DEPTH ; the if at this depth has had its else
refs_on: .res 1 ; resolve names in this body
lex_err_line: .res 2 ; last line with a lexer error
stmt_sp: .res 1 ; hardware SP inside parse_statement
stmt_col: .res 1
ex_depth: .res 1
ex_min: .res EX_MAX_DEPTH + 1
ex_prec: .res EX_MAX_DEPTH + 1
ex_op: .res EX_MAX_DEPTH + 1
ex_cbase: .res EX_MAX_DEPTH + 1
ex_cname: .res EX_MAX_DEPTH + 1
ex_last: .res 1 ; EX_*
ex_tmp: .res 1
ex_min_sp: .res 1 ; lowest SP seen in ex_enter
ex_trace_on: .res 1 ; test hook: record pass-1 expressions
sc_base: .res 1 ; type of the member chain so far
sc_name: .res 1 sc_name: .res 1
sc_len: .res 2 sc_len: .res 2
sc_sp: .res 1
sc_over: .res 1
sc_sbase: .res SCAN_MAX_DEPTH
sc_sname: .res SCAN_MAX_DEPTH
call_base: .res 1 call_base: .res 1
call_name: .res 1 call_name: .res 1
pend: .res 1
pend_id: .res 1 pend_id: .res 1
pend_col: .res 1 pend_col: .res 1
pend_len: .res 1 pend_len: .res 1


+ 58
- 0
newLang/tests/parser/forms.n8 Visa fil

@@ -0,0 +1,58 @@
interface Source
method next() as word
end interface

class Counter implements Source
private word value
private word[4] seen

public method init(start as word)
self.value = start
end method

public method next() as word
self.value = self.value + 1
return self.value
end method

public method twice() as Counter
return self
end method

public method fill(n as word)
var i as word
for i = 0 to 3 step 1
seen[i] = self.next()
end for
for i = 3 to 0 step -1
if seen[i] = 0
return
else
if i > n and not (i = 2)
seen[i] = seen[i - 1]
end if
end if
end for
while value < n
value = value + 1
end while
end method
end class

program Main
var c as Counter
var s as Source
var w as word
c = new Counter((1 + 2) * 3)
c.fill(c.next())
c.twice().twice().fill(w)
s = c
w = s.next()
print "done"
print 'x'
print true
print w
if s <> null
delete s
end if
end program

+ 70
- 6
newLang/tests/parser/parser-test.s Visa fil

@@ -1,15 +1,18 @@
; E03-S01/E03-S02: parser test in x128.
; E03-S01..E03-S03: parser test in x128.
; ;
; For each fixture: src_load, names_init, diag_init (screen off), parse_file, ; For each fixture: src_load, names_init, diag_init (screen off), parse_file,
; diag_finish. Rejected fixtures leave NAME.ERR on the disk for the host to ; diag_finish. Rejected fixtures leave NAME.ERR on the disk for the host to
; compare; accepted fixtures must leave none. The dump records: ; compare; accepted fixtures must leave none. The dump records:
; $FF, fixture index, src_load result, diagnostic count
; $FF, fixture index, src_load result, diagnostic count, lowest SP seen
; by the expression parser ($FF if it never ran)
; per declared top-level type: kind, line lo, line hi, spelling length, ; per declared top-level type: kind, line lo, line hi, spelling length,
; spelling bytes ; spelling bytes
; $FC ; $FC
; per member row: owner name, member name, flags, type, parameter count, ; 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 ; 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. ; 0 = none); a type is base, name, length lo, length hi.
; for the TRACE_INDEX fixture only: $FB, length lo/hi, the expression
; trace (parser.inc TR_*)
; $FD ; $FD
; then $FE at the end. Mailbox: MB_DATA+0/1 dump address, +2/3 length. ; then $FE at the end. Mailbox: MB_DATA+0/1 dump address, +2/3 length.


@@ -24,7 +27,8 @@
.import names_init, name_get, names_count, name_out, name_out_len .import names_init, name_get, names_count, name_out, name_out_len
.import diag_init, diag_finish, diag_screen .import diag_init, diag_finish, diag_screen
.import parse_file, type_kind, type_line_lo, type_line_hi .import parse_file, type_kind, type_line_lo, type_line_hi
.import tb_get, mem_count
.import tb_get, mem_count, ex_trace_on, ex_min_sp
.importzp ex_tp


DUMP_SIZE = $2000 DUMP_SIZE = $2000


@@ -46,6 +50,7 @@ basic_end:
.segment "ZEROPAGE" .segment "ZEROPAGE"
dump_ptr: .res 2 dump_ptr: .res 2
fx_ptr: .res 2 fx_ptr: .res 2
fx_tmp: .res 2


.segment "STARTUP" .segment "STARTUP"
.export test_entry, dump .export test_entry, dump
@@ -105,6 +110,8 @@ test_entry:
inc failures inc failures
lda #$00 lda #$00
jsr emit jsr emit
lda #$FF
jsr emit
jmp @advance jmp @advance
@parse: jsr names_init @parse: jsr names_init
lda name_lo lda name_lo
@@ -113,13 +120,31 @@ test_entry:
jsr diag_init jsr diag_init
lda #$00 ; ERR files are the evidence; keep it fast lda #$00 ; ERR files are the evidence; keep it fast
sta diag_screen sta diag_screen
lda #<trace_buf
sta ex_tp
lda #>trace_buf
sta ex_tp+1
lda #$00
ldx fx_index
cpx #TRACE_INDEX
bne :+
lda #$01
: sta ex_trace_on
jsr parse_file jsr parse_file
lda #$00
sta ex_trace_on
jsr diag_finish jsr diag_finish
jsr emit jsr emit
lda ex_min_sp
jsr emit
jsr dump_types jsr dump_types
lda #$FC lda #$FC
jsr emit jsr emit
jsr dump_members jsr dump_members
lda fx_index
cmp #TRACE_INDEX
bne @advance
jsr dump_trace
@advance: @advance:
lda #$FD lda #$FD
jsr emit jsr emit
@@ -132,9 +157,10 @@ test_entry:
: inc fx_index : inc fx_index
lda fx_index lda fx_index
cmp #FIXTURE_COUNT cmp #FIXTURE_COUNT
bne @fixture
beq :+
jmp @fixture


lda #$FE
: lda #$FE
jsr emit jsr emit
lda #<dump ; report while the private ZP is mapped lda #<dump ; report while the private ZP is mapped
sta MB_DATA+0 sta MB_DATA+0
@@ -262,6 +288,38 @@ dump_members:
jmp @next jmp @next
@done: rts @done: rts


; Emit $FB, the trace length, and the trace bytes.
dump_trace:
lda #$FB
jsr emit
sec
lda ex_tp
sbc #<trace_buf
sta trace_len
jsr emit
lda ex_tp+1
sbc #>trace_buf
sta trace_len+1
jsr emit
lda #<trace_buf
sta fx_tmp
lda #>trace_buf
sta fx_tmp+1
@byte: lda fx_tmp
cmp ex_tp
bne :+
lda fx_tmp+1
cmp ex_tp+1
beq @done
: ldy #$00
lda (fx_tmp),y
jsr emit
inc fx_tmp
bne @byte
inc fx_tmp+1
bne @byte
@done: rts

; Emit the four type columns starting at column A for row Y. ; Emit the four type columns starting at column A for row Y.
emit_type: emit_type:
sta col sta col
@@ -358,7 +416,11 @@ fixtures:
FIXTURE "LIFECYCLE.N8" FIXTURE "LIFECYCLE.N8"
FIXTURE "SCOPES.N8" FIXTURE "SCOPES.N8"
FIXTURE "PRIVATE.N8" FIXTURE "PRIVATE.N8"
FIXTURE_COUNT = 28
FIXTURE "PREC.N8"
FIXTURE "FORMS.N8"
FIXTURE "SYNTAX.N8"
FIXTURE_COUNT = 31
TRACE_INDEX = 28 ; PREC.N8


messages: messages:
ok_msg: .byte "NOVA128 PARSER TEST OK", $0D, $00 ok_msg: .byte "NOVA128 PARSER TEST OK", $0D, $00
@@ -380,4 +442,6 @@ prow: .res 1
pleft: .res 1 pleft: .res 1
col: .res 1 col: .res 1
trow: .res 1 trow: .res 1
trace_len: .res 2
trace_buf: .res 1024
dump: .res DUMP_SIZE dump: .res DUMP_SIZE

+ 17
- 0
newLang/tests/parser/prec.expect Visa fil

@@ -0,0 +1,17 @@
// postfix order per statement: operands by name, then operators.
// binary operators are left associative; unary operators nest rightwards
6: a a b c * +
7: a a b - c -
8: a a b / c /
9: a a b + c *
10: a a neg b *
11: a a neg neg
12: d d not d and
13: d d d d and or
14: d a b < d =
15: d a b = a c <> d not and or
16: a a b * c a * + b c / -
17: d a b + c a * <
18: d a b <= a c >= or
19: a b + c neg *
20: a a b c - -

+ 21
- 0
newLang/tests/parser/prec.n8 Visa fil

@@ -0,0 +1,21 @@
program main
var a as integer
var b as integer
var c as integer
var d as boolean
a = a + b * c
a = a - b - c
a = a / b / c
a = (a + b) * c
a = -a * b
a = - - a
d = not d and d
d = d or d and d
d = a < b = d
d = a = b or a <> c and not d
a = a * b + c * a - b / c
d = a + b < c * a
d = a <= b or a >= c
print (a + b) * -c
a = a - (b - c)
end program

+ 20
- 0
newLang/tests/parser/syntax.expect Visa fil

@@ -0,0 +1,20 @@
// one independent syntax error per line; each line after an error parses.
// line 23: the lexer error is not followed by a cascaded LINE ENDS TOO EARLY
// line 24: 18 nested groups exceed the 16-level expression bound, 15 do not
SYN_UNEXPECTED_EOL 4 10
SYN_EXPECTED_RPAREN 5 13
SYN_EXPECTED_RBRACKET 6 10
SYN_NOT_A_STATEMENT 7 3
SYN_NOT_ASSIGNABLE 8 3
SYN_EXPECTED_EOL 9 9
SYN_UNEXPECTED_EOL 10 8
SYN_EXPECTED_EQUALS 11 9
SYN_EXPECTED_TO 13 13
SYN_UNEXPECTED_TOKEN 15 13
SYN_UNEXPECTED_TOKEN 17 3
SYN_UNEXPECTED_TOKEN 20 3
SYN_UNEXPECTED_TOKEN 22 7
LEX_BAD_CHAR_LITERAL 23 7
RES_EXPR_TOO_DEEP 24 23
// pass 2: name errors are still found after all of the above
SEM_UNKNOWN_NAME 26 3

+ 28
- 0
newLang/tests/parser/syntax.n8 Visa fil

@@ -0,0 +1,28 @@
program main
var a as word
var b as word[4]
a = 1 +
a = (1 + 2
a = b[1
a + 1
5 = a
a = 1 a = 2
print
for a 1 to 2
end for
for a = 1 2
end for
while a = )
end while
else
if a = 1
else
else
end if
a = * 2
a = 'xy'
a = ((((((((((((((((((1))))))))))))))))))
a = (((((((((((((((1)))))))))))))))
zz = 1
return a
end program

Laddar…
Avbryt
Spara

Powered by TurnKey Linux.