diff --git a/newLang/docs/architecture.md b/newLang/docs/architecture.md index 7043dd5..2351590 100644 --- a/newLang/docs/architecture.md +++ b/newLang/docs/architecture.md @@ -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 top-level types, tracks block structure, and reports syntax, ordering, and duplicate errors; pass 2 replays the source and reports type references that -name no declared interface or class. Statement bodies are skimmed until -E03-S03. +name no declared interface or class. E03-S02 adds members and scopes. Pass 1 records every field and method 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 of the enclosing class (D123); `expr.member` follows the left side's type through locals, fields, `self`, `new`, call results, and indexing, and reports -unknown members and private members used outside their class. E03-S03 is -expected to replace that scan with the expression parser and keep the lookups. +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 diff --git a/newLang/docs/diagnostics.md b/newLang/docs/diagnostics.md index e908b7c..c6d2ecc 100644 --- a/newLang/docs/diagnostics.md +++ b/newLang/docs/diagnostics.md @@ -52,6 +52,11 @@ After 20 errors the next one is replaced by E221 and compilation stops. | E035 | `SYN_EXPECTED_TYPE` | TYPE EXPECTED | | E036 | `SYN_EXPECTED_END_WHILE` | END WHILE 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) @@ -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 | | E223 | `RES_TOO_MANY_MEMBERS` | MORE THAN 255 MEMBERS | | E224 | `RES_TOO_MANY_PARAMS` | MORE THAN 255 PARAMETERS | +| E225 | `RES_EXPR_TOO_DEEP` | EXPRESSION NESTED TOO DEEPLY | diff --git a/newLang/planning/claims/E03-S03.md b/newLang/planning/claims/E03-S03.md new file mode 100644 index 0000000..ab40ff1 --- /dev/null +++ b/newLang/planning/claims/E03-S03.md @@ -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. diff --git a/newLang/scripts/build-parser-test.ps1 b/newLang/scripts/build-parser-test.ps1 index 60a86d3..4bb4e8d 100644 --- a/newLang/scripts/build-parser-test.ps1 +++ b/newLang/scripts/build-parser-test.ps1 @@ -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. [CmdletBinding()] param() @@ -26,12 +26,12 @@ $script:ParserFixtures = [ordered]@{ "badmethod.n8" = @("$g\rejected\bad_method.n8", "rejected", "$g\rejected\bad_method.expect") "dupdecl.n8" = @("$g\rejected\dup_decl.n8", "rejected", "$g\rejected\dup_decl.expect") "ifacebody.n8" = @("$g\rejected\iface_body.n8", "rejected", "$g\rejected\iface_body.expect") - "linecont.n8" = @("$g\rejected\line_cont.n8", "pending", "$g\rejected\line_cont.expect") + "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") "missend.n8" = @("$g\rejected\miss_end.n8", "rejected", "$g\rejected\miss_end.expect") "missif.n8" = @("$g\rejected\miss_if.n8", "rejected", "$g\rejected\miss_if.expect") "missmeth.n8" = @("$g\rejected\miss_meth.n8", "rejected", "$g\rejected\miss_meth.expect") - "multistmt.n8" = @("$g\rejected\multi_stmt.n8", "pending", "$g\rejected\multi_stmt.expect") + "multistmt.n8" = @("$g\rejected\multi_stmt.n8", "rejected", "$g\rejected\multi_stmt.expect") "order.n8" = @("$p\order.n8", "exact", "$p\order.expect") "unknown.n8" = @("$p\unknown.n8", "exact", "$p\unknown.expect") "recover.n8" = @("$p\recover.n8", "exact", "$p\recover.expect") @@ -41,6 +41,9 @@ $script:ParserFixtures = [ordered]@{ "lifecycle.n8" = @("$p\lifecycle.n8", "exact", "$p\lifecycle.expect") "scopes.n8" = @("$p\scopes.n8", "exact", "$p\scopes.expect") "private.n8" = @("$p\private.n8", "exact", "$p\private.expect") + "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 ` diff --git a/newLang/scripts/demo-write-check.ps1 b/newLang/scripts/demo-write-check.ps1 index c1a54e1..fa13640 100644 --- a/newLang/scripts/demo-write-check.ps1 +++ b/newLang/scripts/demo-write-check.ps1 @@ -15,7 +15,9 @@ # 4. The screen and NAME.ERR, if any, are shown. # Screenshots go to build/demo/. No code is generated yet (epic E04). [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" $project = Split-Path $PSScriptRoot -Parent diff --git a/newLang/scripts/verify-parser-test.ps1 b/newLang/scripts/verify-parser-test.ps1 index 145af58..83dc60d 100644 --- a/newLang/scripts/verify-parser-test.ps1 +++ b/newLang/scripts/verify-parser-test.ps1 @@ -52,8 +52,9 @@ function Read-DumpType { $records = @(); $script:p = 0 while ($dump[$p] -ne 0xFE) { if ($dump[$p] -ne 0xFF) { throw "dump out of sync at $p" } - $r = [pscustomobject]@{ Index = $dump[$p + 1]; Load = $dump[$p + 2]; Count = $dump[$p + 3]; Types = @(); 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) { $len = $dump[$p + 3] $spell = ConvertFrom-Spelling $dump[($p + 4)..($p + 3 + $len)] @@ -62,7 +63,7 @@ while ($dump[$p] -ne 0xFE) { } if ($dump[$p] -eq 0xFC) { $p++ - while ($dump[$p] -ne 0xFD) { + while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFB) { $owner = Read-DumpName $name = Read-DumpName $flags = $dump[$p]; $p++ @@ -73,11 +74,42 @@ while ($dump[$p] -ne 0xFE) { $(if ($flags -band 1) { "method" } else { "field" }), $(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++ $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 # interface in order; access defaults (fields private, methods public); # a second declaration of a name in the same namespace is not recorded. @@ -135,7 +167,7 @@ function Parse-Err([string]$line) { } # ---------------------------------------------------------------- check -$failures = @(); $i = 0; $pending = @() +$failures = @(); $i = 0; $minSp = 0xFF foreach ($name in $ParserFixtures.Keys) { $src, $group, $expect = $ParserFixtures[$name] $base = $name -replace '\.n8$', '' @@ -174,11 +206,6 @@ foreach ($name in $ParserFixtures.Keys) { } $detail = if ($errLines) { $errLines[0] } else { "" } } - "pending" { - if ($r.Count -ne 0) { $problems += "false diagnostic(s) before E03-S03: $($errLines -join ' | ')" } - $pending += $name - $detail = "pending E03-S03 (statement grammar); no false errors" - } "exact" { $want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") } | ForEach-Object { $f = $_ -split ' '; "{0} {1} {2}" -f $codes[$f[0]], $f[1], $f[2] }) @@ -196,9 +223,24 @@ foreach ($name in $ParserFixtures.Keys) { } $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" } Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail) 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 ")) } -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) diff --git a/newLang/src/common/diagcat.inc b/newLang/src/common/diagcat.inc index bf2f876..2235ed5 100644 --- a/newLang/src/common/diagcat.inc +++ b/newLang/src/common/diagcat.inc @@ -47,6 +47,11 @@ DIAGNOSTIC SYN_EXPECTED_TYPE, 35, "TYPE EXPECTED" DIAGNOSTIC SYN_EXPECTED_END_WHILE, 36, "END WHILE EXPECTED" DIAGNOSTIC SYN_EXPECTED_END_FOR, 37, "END FOR EXPECTED" + 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 DIAGNOSTIC SEM_DUPLICATE_NAME, 60, "% IS ALREADY DECLARED" @@ -78,3 +83,4 @@ DIAGNOSTIC RES_NESTING_TOO_DEEP, 222, "BLOCKS NESTED TOO DEEPLY" DIAGNOSTIC RES_TOO_MANY_MEMBERS, 223, "MORE THAN 255 MEMBERS" DIAGNOSTIC RES_TOO_MANY_PARAMS, 224, "MORE THAN 255 PARAMETERS" + DIAGNOSTIC RES_EXPR_TOO_DEEP, 225, "EXPRESSION NESTED TOO DEEPLY" diff --git a/newLang/src/common/parser.inc b/newLang/src/common/parser.inc index 7994ba6..1f6c6ee 100644 --- a/newLang/src/common/parser.inc +++ b/newLang/src/common/parser.inc @@ -10,7 +10,18 @@ TYPE_CLASS = 2 TYPE_PROGRAM = 3 PARSE_MAX_DEPTH = 16 ; nested method/program/if/while/for blocks -SCAN_MAX_DEPTH = 16 ; nested ( and [ tracked by the pass-2 scan +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. ; base TY_*; TY_NONE for a method without a result diff --git a/newLang/src/compiler/parser.s b/newLang/src/compiler/parser.s index 436765b..a796e37 100644 --- a/newLang/src/compiler/parser.s +++ b/newLang/src/compiler/parser.s @@ -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, ; diag_init, and names_init): ; pass 1 parses interfaces, classes (implements lists, fields, method ; headers), and the one program; records each top-level type in ; the bank-1 type table 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 ; (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` ; reports the innermost missing end and closes blocks up to X. A top-level @@ -41,6 +42,8 @@ .importzp mmu_ptr .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 ACC_DEFAULT = 0 @@ -49,6 +52,9 @@ ACC_PRIVATE = 2 LIFE_INIT = 1 LIFE_DESTROY = 2 +.segment "ZEROPAGE" +ex_tp: .res 2 ; test trace pointer (ex_trace_on) + .macro IFTOK kind, target lda tok_kind cmp #kind @@ -67,6 +73,8 @@ parse_file: sta par_count sta mem_full sta par_full + lda #$FF + sta ex_min_sp lda #$01 sta pass jsr parse_pass @@ -89,6 +97,8 @@ parse_pass: sta eof_reported sta cur_owner sta cur_class + sta lex_err_line ; lines are 1-based: 0 matches none + sta lex_err_line+1 jsr next top: jsr skip_blank 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_body: sta block_stack + lda #$00 + sta block_else + sta stmt_seen lda #$01 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 - sta stmt_seen + sta refs_on @loop: jsr skip_blank lda tok_kind cmp #TOK_EOF @@ -589,31 +608,14 @@ parse_body: bne :+ jmp @interrupted : IFTOK KW_VAR, @var - IFTOK KW_IF, @open - IFTOK KW_WHILE, @open - IFTOK KW_FOR, @open IFTOK KW_END, @end lda #$01 ; any other line is a statement sta stmt_seen - jsr stmt_line + jsr parse_statement jmp @loop @var: jsr check_var_place jsr parse_var ; declared anyway, to avoid cascades 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 lda tok_kind @@ -679,20 +681,6 @@ check_var_place: jmp err1 @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 ; foreign `end class` / `end interface` reports the innermost block only. closer_in_stack: @@ -744,89 +732,439 @@ parse_var: : jsr set_local_type 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 - 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 - beq @end + beq :+ 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 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 - 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 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 + 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 - 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 :+ - 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 - 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. pend_member: - lda #$01 - sta pend lda tok_col sta pend_col lda tok_len @@ -845,8 +1183,8 @@ pend_member: ; Resolve the pending member against its left side's type. The member is a ; method when the current token is `(`, else a field. resolve_member: - lda #$00 - sta pend + lda refs_on + beq @none lda pend_base cmp #TY_REF bne @none @@ -907,7 +1245,10 @@ resolve_member: ; A bare name: a local or parameter, else a field of the enclosing class. resolve_name: - jsr find_tok + lda refs_on + bne :+ + jmp chain_none +: jsr find_tok beq @unknown sta rn_id jsr scope_test @@ -939,7 +1280,10 @@ resolve_name: jmp err2 self_ref: - lda cur_class + lda refs_on + bne :+ + jmp chain_none +: lda cur_class beq @bad sta sc_name lda #TY_REF @@ -956,7 +1300,10 @@ self_ref: ; `new Name`: Name must be a declared class or interface. new_type: jsr chain_none - jsr find_tok + lda refs_on + bne :+ + rts +: jsr find_tok beq @unknown sta sc_name tay @@ -1012,39 +1359,6 @@ element_type: tax 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). err_pend: pha @@ -1367,6 +1681,10 @@ next: jsr lex_next lda tok_kind cmp #TOK_ERROR 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 clc 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 bit_masks: .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 .byte 4, "INIT" .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_tlen_lo: .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_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_name: .res 1 -pend: .res 1 pend_id: .res 1 pend_col: .res 1 pend_len: .res 1 diff --git a/newLang/tests/parser/forms.n8 b/newLang/tests/parser/forms.n8 new file mode 100644 index 0000000..f833fa7 --- /dev/null +++ b/newLang/tests/parser/forms.n8 @@ -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 diff --git a/newLang/tests/parser/parser-test.s b/newLang/tests/parser/parser-test.s index 6f36e8f..4dc913b 100644 --- a/newLang/tests/parser/parser-test.s +++ b/newLang/tests/parser/parser-test.s @@ -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, ; diag_finish. Rejected fixtures leave NAME.ERR on the disk for the host to ; compare; accepted fixtures must leave none. The dump records: -; $FF, fixture index, src_load result, diagnostic count +; $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, ; spelling bytes ; $FC ; per member row: owner name, member name, flags, type, parameter count, ; then each parameter's type. A name is a length and its spelling (length ; 0 = none); a type is base, name, length lo, length hi. +; for the TRACE_INDEX fixture only: $FB, length lo/hi, the expression +; trace (parser.inc TR_*) ; $FD ; 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 diag_init, diag_finish, diag_screen .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 @@ -46,6 +50,7 @@ basic_end: .segment "ZEROPAGE" dump_ptr: .res 2 fx_ptr: .res 2 +fx_tmp: .res 2 .segment "STARTUP" .export test_entry, dump @@ -105,6 +110,8 @@ test_entry: inc failures lda #$00 jsr emit + lda #$FF + jsr emit jmp @advance @parse: jsr names_init lda name_lo @@ -113,13 +120,31 @@ test_entry: jsr diag_init lda #$00 ; ERR files are the evidence; keep it fast sta diag_screen + 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 + lda #$00 + sta ex_trace_on jsr diag_finish jsr emit + lda ex_min_sp + jsr emit jsr dump_types lda #$FC jsr emit jsr dump_members + lda fx_index + cmp #TRACE_INDEX + bne @advance + jsr dump_trace @advance: lda #$FD jsr emit @@ -132,9 +157,10 @@ test_entry: : inc fx_index lda fx_index cmp #FIXTURE_COUNT - bne @fixture + beq :+ + jmp @fixture - lda #$FE +: lda #$FE jsr emit lda #trace_buf + sta trace_len+1 + jsr emit + 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_type: sta col @@ -358,7 +416,11 @@ fixtures: FIXTURE "LIFECYCLE.N8" FIXTURE "SCOPES.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: ok_msg: .byte "NOVA128 PARSER TEST OK", $0D, $00 @@ -380,4 +442,6 @@ prow: .res 1 pleft: .res 1 col: .res 1 trow: .res 1 +trace_len: .res 2 +trace_buf: .res 1024 dump: .res DUMP_SIZE diff --git a/newLang/tests/parser/prec.expect b/newLang/tests/parser/prec.expect new file mode 100644 index 0000000..d3f3b7b --- /dev/null +++ b/newLang/tests/parser/prec.expect @@ -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 - - diff --git a/newLang/tests/parser/prec.n8 b/newLang/tests/parser/prec.n8 new file mode 100644 index 0000000..49441cc --- /dev/null +++ b/newLang/tests/parser/prec.n8 @@ -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 diff --git a/newLang/tests/parser/syntax.expect b/newLang/tests/parser/syntax.expect new file mode 100644 index 0000000..8505d28 --- /dev/null +++ b/newLang/tests/parser/syntax.expect @@ -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 diff --git a/newLang/tests/parser/syntax.n8 b/newLang/tests/parser/syntax.n8 new file mode 100644 index 0000000..eaa551a --- /dev/null +++ b/newLang/tests/parser/syntax.n8 @@ -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