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
| @@ -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 | ||||
| @@ -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 | | |||||
| @@ -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. | |||||
| @@ -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 ` | ||||
| @@ -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,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) | |||||
| @@ -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" | |||||
| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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 | |||||
| @@ -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 | ||||
| @@ -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 - - | |||||
| @@ -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 | |||||
| @@ -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 | |||||
| @@ -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 | |||||
| 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 | |||||
Powered by TurnKey Linux.