From 5b94b0121072b36e2983f66b11eeb21030650383 Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Sun, 8 Nov 2026 20:23:53 -0500 Subject: [PATCH] newLang: check expression types and returns (E03-S04) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit types.s gives every expression a type and applies the spec 3.4/8 conversions, operator table, constant folding and diagnostic spellings. parser.s records implements lists and init rows in pass 1, carries types through member chains, groups, calls and indexing in pass 2, and checks assignments, arguments, returns, conditions, for, print, delete and member access. A method that declares a result must end in return; a missing one is E150 at end, and a program return value is E151. Three older parser fixtures gain true diagnostics. The lexer's host reference formatter now spells bytes such as {0D} correctly, and the demo tolerates a missing ERR file. Verified in x128: 37 parser fixtures, 10 lexer fixtures, the 67-entry diagnostic catalog, and the Vim-to-N8CHECK demo (good.n8 clean, bad.n8's four diagnostics). Generated with Codebuff 🤖 Co-Authored-By: Codebuff --- newLang/docs/architecture.md | 8 +- newLang/docs/decisions.md | 4 + newLang/docs/diagnostics.md | 16 + newLang/planning/claims/E03-S04.md | 109 +++ newLang/scripts/build-parser-test.ps1 | 12 +- newLang/scripts/demo-write-check.ps1 | 6 +- newLang/scripts/verify-parser-test.ps1 | 27 +- newLang/src/common/diagcat.inc | 16 + newLang/src/common/parser.inc | 52 +- newLang/src/compiler/parser.s | 861 ++++++++++++++++++++---- newLang/src/compiler/types.s | 853 +++++++++++++++++++++++ newLang/tests/lexer/reference-lexer.ps1 | 2 +- newLang/tests/parser/forms.n8 | 9 +- newLang/tests/parser/parser-test.s | 10 +- newLang/tests/parser/recover.expect | 6 +- newLang/tests/parser/syntax.expect | 4 +- newLang/tests/parser/unknown.expect | 4 +- newLang/tests/semantic/calls.expect | 18 + newLang/tests/semantic/calls.n8 | 63 ++ newLang/tests/semantic/convert.expect | 21 + newLang/tests/semantic/convert.n8 | 26 + newLang/tests/semantic/ops.expect | 19 + newLang/tests/semantic/ops.n8 | 29 + newLang/tests/semantic/refs.expect | 16 + newLang/tests/semantic/refs.n8 | 45 ++ newLang/tests/semantic/types_ok.n8 | 115 ++++ newLang/tests/semantic/uses.expect | 21 + newLang/tests/semantic/uses.n8 | 37 + 28 files changed, 2229 insertions(+), 180 deletions(-) create mode 100644 newLang/planning/claims/E03-S04.md create mode 100644 newLang/src/compiler/types.s create mode 100644 newLang/tests/semantic/calls.expect create mode 100644 newLang/tests/semantic/calls.n8 create mode 100644 newLang/tests/semantic/convert.expect create mode 100644 newLang/tests/semantic/convert.n8 create mode 100644 newLang/tests/semantic/ops.expect create mode 100644 newLang/tests/semantic/ops.n8 create mode 100644 newLang/tests/semantic/refs.expect create mode 100644 newLang/tests/semantic/refs.n8 create mode 100644 newLang/tests/semantic/types_ok.n8 create mode 100644 newLang/tests/semantic/uses.expect create mode 100644 newLang/tests/semantic/uses.n8 diff --git a/newLang/docs/architecture.md b/newLang/docs/architecture.md index 2351590..564e453 100644 --- a/newLang/docs/architecture.md +++ b/newLang/docs/architecture.md @@ -196,7 +196,13 @@ 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. +E03-S04 adds `types.s`: every expression carries a type in pass 2, and +`ty_assign`, `ty_binary`, and `ty_unary` apply spec 3.4 and 8 (D124-D127). +Assignments, call arguments and results, returns, conditions, `for`, `print`, +`delete`, indexing, and member chains are checked; a method that declares a +result must end in `return` (an `if` only when both branches do), reported at +`end` as E150. Numeric literals are typed by context and folded (E084/E086); +arrays are never whole values (E088). ### Pass 1 — declarations and layout diff --git a/newLang/docs/decisions.md b/newLang/docs/decisions.md index f4d593e..5bce510 100644 --- a/newLang/docs/decisions.md +++ b/newLang/docs/decisions.md @@ -50,6 +50,10 @@ documented default. `Proposed` means a later change still needs approval. | D121 | A method name may be any identifier or reserved word, as in spec 5.1's `method print()`. | Spec 5.1 and the accepted fixtures declare `method print()` although `print` is a statement keyword. The parser accepts a keyword after `method`; E03-S03 must accept the same member names after `.`. | | D122 | `init` returns no value (`as type` is E151), like `destroy`. | Spec 6.3 says `new Class(args)` yields the object, so an `init` result could never be observed. Spec 6.3 already gives `destroy` no parameters and no result (E152, E151). Implemented by E03-S02. | | D123 | Inside a method an unqualified name is a parameter or local, else a field of the enclosing class; methods are always called through an expression such as `self.tick()`. | Spec 9 lets a local "shadow a field", which implies fields are in scope unqualified, but no fixture used one. Spec 7 defines calls only as `expression.method(arguments)`, so a bare `tick()` is an unknown name. Implemented by E03-S02. | +| D124 | Numeric literals are constants that adopt the type of the place or operand they meet when the value fits, and literal-only arithmetic folds at compile time. `and`/`or` take booleans; `+ - * /` take byte, word, or integer; `< <= > >=` take numbers or chars; `= <>` also take booleans and references (identity). `byte` widens to `word`/`integer`, but `word` and `integer` do not mix. | Spec 3.4 names the conversions but not literal types or the operator table. Out-of-range folded results are E084, division by zero E086, and a constant that does not fit E085. Implemented by `types.s`; changing it changes only that module and the semantic goldens. | +| D125 | An array index may be an integer or a value assignable to word; a constant index is also checked against the declared length. | Spec 3.3 gives arrays but not the index type. Byte indexes widen; an out-of-range constant is E089 at compile time. Implemented by `check_index` in `parser.s`. | +| D126 | Arrays are fields or locals only: never parameters, results, or whole values. A bare array is E088, and a parameter or result declaring one is E094. | Keeps array storage and copying out of the ABI (D108); indexing is the only way to read or write one. Implemented by `no_array_sig` and `ty_assign`. | +| D127 | `return` with a value in a program is E151. A method that declares a result must end every path in `return`; an `if` satisfies this only when both branches do, a `while` never does, and a missing return is reported at `end` as E150. | Spec 7 permits `return` but does not say what a program return means; a program has no caller or result. Deterministic and observable, and lets E04 emit a single exit path. Implemented by `close_block` and `set_ret`. | ## Deferred diff --git a/newLang/docs/diagnostics.md b/newLang/docs/diagnostics.md index c6d2ecc..ab274b4 100644 --- a/newLang/docs/diagnostics.md +++ b/newLang/docs/diagnostics.md @@ -71,6 +71,20 @@ After 20 errors the next one is replaced by E221 and compilation stops. |---|---|---| | E080 | `TYP_MISMATCH` | % EXPECTED; % FOUND | | E081 | `TYP_CONDITION_NOT_BOOLEAN` | CONDITION MUST BE BOOLEAN | +| E082 | `TYP_BAD_OPERAND` | % NOT ALLOWED ON % | +| E083 | `TYP_OPERANDS_DIFFER` | % AND % DO NOT MIX | +| E084 | `TYP_NUMBER_RANGE` | NUMBER OUT OF RANGE | +| E085 | `TYP_NUMBER_DOES_NOT_FIT` | % DOES NOT FIT % | +| E086 | `TYP_DIVIDE_BY_ZERO` | DIVISION BY ZERO | +| E087 | `TYP_NOT_ARRAY` | % IS NOT AN ARRAY | +| E088 | `TYP_ARRAY_NEEDS_INDEX` | % NEEDS AN INDEX | +| E089 | `TYP_INDEX_RANGE` | INDEX OUT OF RANGE | +| E090 | `TYP_NO_MEMBERS` | % HAS NO MEMBERS | +| E091 | `TYP_CANNOT_PRINT` | % CANNOT BE PRINTED | +| E092 | `TYP_CANNOT_DELETE` | % CANNOT BE DELETED | +| E093 | `TYP_NOT_CLASS` | % IS NOT A CLASS | +| E094 | `TYP_ARRAY_SIGNATURE` | ARRAYS CANNOT BE PARAMETERS OR RESULTS | +| E095 | `TYP_ARRAY_LENGTH` | ARRAY LENGTH MUST NOT BE ZERO | ## Access (E120-E129) @@ -92,6 +106,7 @@ After 20 errors the next one is replaced by E221 and compilation stops. | E150 | `SEM_MISSING_RETURN` | MISSING RETURN VALUE | | E151 | `SEM_LIFECYCLE_RETURN` | % CANNOT RETURN A VALUE | | E152 | `SEM_LIFECYCLE_PARAMS` | % TAKES NO PARAMETERS | +| E153 | `SEM_ARG_COUNT` | % TAKES % ARGUMENTS | ## Disk (E200-E219) @@ -110,3 +125,4 @@ After 20 errors the next one is replaced by E221 and compilation stops. | 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 | +| E226 | `RES_TOO_MANY_IMPLEMENTS` | MORE THAN 255 IMPLEMENTED INTERFACES | diff --git a/newLang/planning/claims/E03-S04.md b/newLang/planning/claims/E03-S04.md new file mode 100644 index 0000000..b9702a3 --- /dev/null +++ b/newLang/planning/claims/E03-S04.md @@ -0,0 +1,109 @@ +--- +story: E03-S04 +owner: e03_types +status: done +started: 2026-11-08 +depends-on: [E03-S03] +--- + +# E03-S04 Claim + +## Intended change + +Give every expression a type and check it in pass 2. A new module +`src/compiler/types.s` holds the type registers, assignability (spec 3.4), +operator typing (spec 8), numeric-literal constants with folding, and the +spelling of types in diagnostics. The parser records each class's +`implements` list and `init` row, finds a method's own member row again in +pass 2, carries full types through member chains, groups, calls, indexing, +and operators, and checks assignments, call arguments (count and type), +returns, conditions, `for`, `print`, `delete`, indexing, and member access. +Missing returns are found by tracking whether each block ends in `return` +(an `if` counts only with an `else` whose branches both do). + +## File area + +- `src/compiler/types.s` (new), `src/compiler/parser.s`, + `src/common/parser.inc` +- `src/common/diagcat.inc` (appended entries), `docs/diagnostics.md` +- `tests/semantic/` (new fixtures), `tests/parser/` (harness, expectations + that gain true type diagnostics, `forms.n8`) +- `scripts/build-parser-test.ps1`, `scripts/verify-parser-test.ps1`, + `scripts/demo-write-check.ps1` (links the parser) +- `docs/architecture.md`, `docs/decisions.md` +- `tests/lexer/reference-lexer.ps1` (host spelling fix found by the + regression run) +- `planning/claims/E03-S04.md` +- generated files under `build/` + +## Acceptance evidence + +- [x] Represents primitives, arrays, class references, interface references, + and null: `types_ok.n8` passes with five top-level types and eleven + members; its member table matches the source independently. `convert.n8` + and `refs.n8` cover every illegal conversion, arrays included. +- [x] Implements exactly the specified implicit conversions: `convert.n8` + matches all 19 unsafe primitive assignments and constant misfits; + `refs.n8` matches all 13 illegal reference and array cases, including + class-to-interface, unrelated references, and identity rules. +- [x] Checks calls, assignments, returns, conditions, indexing, and + operators: `calls.n8` (16 diagnostics), `uses.n8` (18), `ops.n8` (16), + `convert.n8` (19), and `refs.n8` (13) match their goldens; `types_ok.n8` + exercises the accepted forms without a diagnostic. +- [x] Reports incompatible interface assignments and missing returns: + `refs.n8` expects E080 for every unrelated reference; `calls.n8` expects + E150 at three `end method` sites (plain body, `if` without `else`, + `while` only) and E151 for a program `return` value. + +## Coordination notes + +- No other claim is active. D124-D127 are recorded as proposed: expression + typing and constant folding, array indexing, arrays as fields/locals only, + and `return` in a program plus the missing-return rule. + +## Work log + +- `types.s` holds the `sc_*`/`tt_*` type registers, `ty_assign`, + `ty_binary`, `ty_unary`, constant folding (16x16 multiply, restoring + divide), and the diagnostic spellings (BYTE, WORD[4], a class name, a + constant, NO VALUE). +- `parser.s` records `implements` lists and each class's `init` row in pass 1, + then in pass 2 re-finds the current member row, carries types through + member chains, groups, calls, and indexing, and checks every place a value + is used. Missing returns are reported at the `end` token (E150). +- The parser test harness dumps types, constant values, and members; the + semantic fixtures are compared against golden ERR lines by + `verify-parser-test.ps1`. +- Three older fixtures legitimately gained diagnostics: `unknown.n8` (E150 + for an empty result method), `recover.n8` (E081 for `while x`/`if x` on a + word), and `syntax.n8` (E151 for `return a` in a program). Their + expectations were updated; `recover.n8` records the conditions in the + order the blocks close. +- The lexer golden check exposed a host-only bug: `Format-PetsciiText` used + `"{{{0:X2}}}"`, which Windows PowerShell renders as `{X2}`. It now builds + the braces explicitly and matches the goldens (`{0D}`, `{22}`, `{5C}`). +- `verify-parser-test.ps1` uses port 6535; on this machine the first run's + monitor reply was empty. Port 6530 works and is the port the other suites + already use; the final runs used `-Port 6530`. +- The demo's ERR read ran under `$ErrorActionPreference = "Stop"`, so a + missing `good.err` (the success case) made `c1541`'s stderr terminate the + script. It now checks the disk listing first and redirects stderr. + +## Completion + +- Tests run: `scripts/build-parser-test.ps1`, + `scripts/verify-parser-test.ps1 -Port 6530` (37 fixtures), + `scripts/build-lexer-test.ps1`, `scripts/verify-lexer-test.ps1`, + `scripts/build-diag-test.ps1 -UpdateDocs`, + `scripts/verify-diag-test.ps1`, `scripts/demo-write-check.ps1` + (`good.n8` and `bad.n8`). +- Results: all 37 parser fixtures pass (lowest expression SP `$A7`); the + lexer matches 6,733 bytes over 10 fixtures; the 67-entry diagnostic catalog + matches its screen/ERR expectations and `docs/diagnostics.md` is in sync; + the demo saves both programs from Vim byte-for-byte, `good.n8` reports no + errors, and `bad.n8` reports E151, E152, E120, and E061. +- Files changed: as listed under File area. +- Remaining risks: D124-D127 await approval; a change to D124 means + revisiting `types.s` and the semantic goldens. The type registers are + statically allocated, so nested type checks reuse them deliberately. + `for` step typing follows the same operator table. diff --git a/newLang/scripts/build-parser-test.ps1 b/newLang/scripts/build-parser-test.ps1 index 4bb4e8d..ed8aecf 100644 --- a/newLang/scripts/build-parser-test.ps1 +++ b/newLang/scripts/build-parser-test.ps1 @@ -1,5 +1,5 @@ -# 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. +# E03-S01..E03-S04: build the parser test PRG and a fresh D81 holding every grammar, +# parser, and semantic fixture. The fixture order must match tests/parser/parser-test.s. [CmdletBinding()] param() @@ -11,6 +11,7 @@ New-Item -ItemType Directory -Force -Path $out | Out-Null $g = Join-Path $project "tests\grammar" $p = Join-Path $project "tests\parser" +$t = Join-Path $project "tests\semantic" # disk name -> @(source, group, expect file) $script:ParserFixtures = [ordered]@{ "minimal.n8" = @("$g\accepted\minimal.n8", "accepted", $null) @@ -44,6 +45,12 @@ $script:ParserFixtures = [ordered]@{ "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") + "typesok.n8" = @("$t\types_ok.n8", "accepted", $null) + "convert.n8" = @("$t\convert.n8", "exact", "$t\convert.expect") + "refs.n8" = @("$t\refs.n8", "exact", "$t\refs.expect") + "ops.n8" = @("$t\ops.n8", "exact", "$t\ops.expect") + "uses.n8" = @("$t\uses.n8", "exact", "$t\uses.expect") + "calls.n8" = @("$t\calls.n8", "exact", "$t\calls.expect") } $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` @@ -55,6 +62,7 @@ $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` (Join-Path $project "src\compiler\names.s"), (Join-Path $project "src\compiler\diagnostics.s"), (Join-Path $project "src\compiler\parser.s"), + (Join-Path $project "src\compiler\types.s"), (Join-Path $project "tests\parser\parser-test.s")) if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" } diff --git a/newLang/scripts/demo-write-check.ps1 b/newLang/scripts/demo-write-check.ps1 index fa13640..c4bac39 100644 --- a/newLang/scripts/demo-write-check.ps1 +++ b/newLang/scripts/demo-write-check.ps1 @@ -39,6 +39,7 @@ $link = Invoke-Nova128Link -Name "n8check" -BuildDir $out ` (Join-Path $project "src\compiler\names.s"), (Join-Path $project "src\compiler\diagnostics.s"), (Join-Path $project "src\compiler\parser.s"), + (Join-Path $project "src\compiler\types.s"), (Join-Path $project "demo\n8check.s")) # A fresh copy: the Vim work disk itself is never written. @@ -124,7 +125,10 @@ foreach ($name in $Programs) { # 4. results $err = Join-Path $out "$base.err.txt" if (Test-Path $err) { Remove-Item $err -Force } - & $c1541 $disk -read "$base.err,s" $err 2>&1 | Out-Null + $listing = (& $c1541 -attach $disk -list 2>$null) | Out-String + if ($listing -match ('"' + [regex]::Escape("$base.err") + '"')) { + & $c1541 $disk -read "$base.err,s" $err 2>$null | Out-Null + } if (Test-Path $err) { Write-Host " $base.err:" foreach ($line in ([Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($err)).TrimEnd("`r") -split "`r")) { diff --git a/newLang/scripts/verify-parser-test.ps1 b/newLang/scripts/verify-parser-test.ps1 index 83dc60d..79f30cc 100644 --- a/newLang/scripts/verify-parser-test.ps1 +++ b/newLang/scripts/verify-parser-test.ps1 @@ -159,11 +159,16 @@ function Get-ErrLines([string]$base) { if ($listing -notmatch ('"' + [regex]::Escape("$base.err") + '"')) { return $null } & $c1541 -attach $disk -read "$base.err,s" $dest 2>&1 | Out-Null # The leading comma keeps a one-line file an array (PowerShell unrolls it). - return ,@(([Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($dest)).TrimEnd("`r")) -split "`r") + # Names keep their source spelling: shifted PETSCII letters ($C1-$DA) are + # capitals; messages are compared case-insensitively. + $text = -join ([IO.File]::ReadAllBytes($dest) | ForEach-Object { + if ($_ -ge 0xC1 -and $_ -le 0xDA) { [char]($_ - 0x80) } else { [char]$_ } }) + return ,@(($text.TrimEnd("`r")) -split "`r") } function Parse-Err([string]$line) { - $m = [regex]::Match($line, '^E(\d{3}) \S+ (\d+) (\d+) ') - return [pscustomobject]@{ Code = [int]$m.Groups[1].Value; Line = [int]$m.Groups[2].Value; Col = [int]$m.Groups[3].Value } + $m = [regex]::Match($line, '^E(\d{3}) \S+ (\d+) (\d+) (.*)$') + return [pscustomobject]@{ Code = [int]$m.Groups[1].Value; Line = [int]$m.Groups[2].Value + Col = [int]$m.Groups[3].Value; Message = $m.Groups[4].Value } } # ---------------------------------------------------------------- check @@ -207,11 +212,21 @@ foreach ($name in $ParserFixtures.Keys) { $detail = if ($errLines) { $errLines[0] } else { "" } } "exact" { - $want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") } | ForEach-Object { - $f = $_ -split ' '; "{0} {1} {2}" -f $codes[$f[0]], $f[1], $f[2] }) - $got = @($errLines | Where-Object { $_ } | ForEach-Object { $e = Parse-Err $_; "{0} {1} {2}" -f $e.Code, $e.Line, $e.Col }) + # SYMBOL line column [message]; a message is compared when given. + $rows = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") } | ForEach-Object { + $f = $_ -split ' ', 4 + [pscustomobject]@{ Key = "{0} {1} {2}" -f $codes[$f[0]], $f[1], $f[2]; Message = $f[3] } }) + $want = @($rows | ForEach-Object { $_.Key }) + $errs = @($errLines | Where-Object { $_ } | ForEach-Object { Parse-Err $_ }) + $got = @($errs | ForEach-Object { "{0} {1} {2}" -f $_.Code, $_.Line, $_.Col }) if (($got -join ';') -ne ($want -join ';')) { $problems += "diagnostics [$($got -join '; ')] expected [$($want -join '; ')]" + } else { + for ($k = 0; $k -lt $rows.Count; $k++) { + if ($rows[$k].Message -and $rows[$k].Message.ToUpper() -ne $errs[$k].Message.ToUpper()) { + $problems += "E$($errs[$k].Code) at $($errs[$k].Line):$($errs[$k].Col) says '$($errs[$k].Message)', expected '$($rows[$k].Message)'" + } + } } $detail = "{0} diagnostics" -f $got.Count } diff --git a/newLang/src/common/diagcat.inc b/newLang/src/common/diagcat.inc index 2235ed5..59c1bb3 100644 --- a/newLang/src/common/diagcat.inc +++ b/newLang/src/common/diagcat.inc @@ -60,6 +60,20 @@ ; ------------------------------------------------------------------ types DIAGNOSTIC TYP_MISMATCH, 80, "% EXPECTED; % FOUND" DIAGNOSTIC TYP_CONDITION_NOT_BOOLEAN, 81, "CONDITION MUST BE BOOLEAN" + DIAGNOSTIC TYP_BAD_OPERAND, 82, "% NOT ALLOWED ON %" + DIAGNOSTIC TYP_OPERANDS_DIFFER, 83, "% AND % DO NOT MIX" + DIAGNOSTIC TYP_NUMBER_RANGE, 84, "NUMBER OUT OF RANGE" + DIAGNOSTIC TYP_NUMBER_DOES_NOT_FIT, 85, "% DOES NOT FIT %" + DIAGNOSTIC TYP_DIVIDE_BY_ZERO, 86, "DIVISION BY ZERO" + DIAGNOSTIC TYP_NOT_ARRAY, 87, "% IS NOT AN ARRAY" + DIAGNOSTIC TYP_ARRAY_NEEDS_INDEX, 88, "% NEEDS AN INDEX" + DIAGNOSTIC TYP_INDEX_RANGE, 89, "INDEX OUT OF RANGE" + DIAGNOSTIC TYP_NO_MEMBERS, 90, "% HAS NO MEMBERS" + DIAGNOSTIC TYP_CANNOT_PRINT, 91, "% CANNOT BE PRINTED" + DIAGNOSTIC TYP_CANNOT_DELETE, 92, "% CANNOT BE DELETED" + DIAGNOSTIC TYP_NOT_CLASS, 93, "% IS NOT A CLASS" + DIAGNOSTIC TYP_ARRAY_SIGNATURE, 94, "ARRAYS CANNOT BE PARAMETERS OR RESULTS" + DIAGNOSTIC TYP_ARRAY_LENGTH, 95, "ARRAY LENGTH MUST NOT BE ZERO" ; ----------------------------------------------------------------- access DIAGNOSTIC ACC_PRIVATE_MEMBER, 120, "% IS PRIVATE" @@ -72,6 +86,7 @@ DIAGNOSTIC SEM_MISSING_RETURN, 150, "MISSING RETURN VALUE" DIAGNOSTIC SEM_LIFECYCLE_RETURN, 151, "% CANNOT RETURN A VALUE" DIAGNOSTIC SEM_LIFECYCLE_PARAMS, 152, "% TAKES NO PARAMETERS" + DIAGNOSTIC SEM_ARG_COUNT, 153, "% TAKES % ARGUMENTS" ; ------------------------------------------------------------------- disk DIAGNOSTIC DSK_SOURCE_UNREADABLE, 200, "CANNOT READ SOURCE; DOS %" @@ -84,3 +99,4 @@ 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" + DIAGNOSTIC RES_TOO_MANY_IMPLEMENTS, 226, "MORE THAN 255 IMPLEMENTED INTERFACES" diff --git a/newLang/src/common/parser.inc b/newLang/src/common/parser.inc index 1f6c6ee..c5fbdce 100644 --- a/newLang/src/common/parser.inc +++ b/newLang/src/common/parser.inc @@ -1,4 +1,4 @@ -; parser.inc - parser tables and limits (E03-S01, E03-S02). +; parser.inc - parser tables and limits (E03-S01..E03-S04). .ifndef PARSER_INC PARSER_INC = 1 @@ -23,17 +23,25 @@ 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 +; A type is base, name, length (E03-S04, types.s). Declared types use the +; first four bytes; an expression's type adds a constant's value and sign. +; base TY_* ; name class/interface name ID when base = TY_REF, else 0 ; length array length (2 bytes); 0 = not an array -TY_NONE = 0 -TY_BOOLEAN = 1 +; value TY_CONST only: magnitude 0..65535 (2 bytes) +; neg TY_CONST only: 1 = negative (magnitude 1..32768) +TY_NONE = 0 ; declared: no result; expression: unknown +TY_BOOLEAN = 1 ; (an error was already reported: no checks) TY_BYTE = 2 TY_CHAR = 3 TY_INTEGER = 4 TY_WORD = 5 TY_REF = 6 +TY_NULL = 7 ; expressions only: the null literal +TY_STRING = 8 ; a string literal (print only) +TY_CONST = 9 ; a number literal or constant expression +TY_VOID = 10 ; the result of a method without `as type` +TV_SIZE = 7 ; bytes in a type value (sc_*, tt_*) ; Member flags MF_METHOD = $01 ; clear = field @@ -41,6 +49,7 @@ MF_PRIVATE = $02 ; clear = public MEMBERS_MAX = 255 ; rows 0..254, all classes and interfaces PARAMS_MAX = 255 ; rows 0..254, all method signatures +IMPLS_MAX = 255 ; rows 0..254, all implements lists ; Bank-1 parser tables: 256-byte columns, consecutive from type_kind. ; tb_get/tb_set take A = column and Y = row (a name ID for type columns). @@ -49,19 +58,24 @@ C_TYPE_LINE_LO = 1 ; declaration line C_TYPE_LINE_HI = 2 C_TYPE_MFIRST = 3 ; first member row C_TYPE_MCOUNT = 4 ; member rows (contiguous, declaration order) -C_MEM_OWNER = 5 ; owning class/interface name ID -C_MEM_NAME = 6 ; member name ID -C_MEM_FLAGS = 7 ; MF_* -C_MEM_TBASE = 8 ; field type / method result type -C_MEM_TNAME = 9 -C_MEM_TLEN_LO = 10 -C_MEM_TLEN_HI = 11 -C_MEM_PFIRST = 12 ; first parameter row -C_MEM_PCOUNT = 13 ; parameter rows (declaration order) -C_PAR_TBASE = 14 ; parameter type -C_PAR_TNAME = 15 -C_PAR_TLEN_LO = 16 -C_PAR_TLEN_HI = 17 -TABLE_COLUMNS = 18 +C_TYPE_IFIRST = 5 ; class: first implements row +C_TYPE_ICOUNT = 6 ; class: implements rows (source order) +C_TYPE_INIT = 7 ; class: init member row + 1, 0 = none +C_MEM_OWNER = 8 ; owning class/interface name ID +C_MEM_NAME = 9 ; member name ID +C_MEM_FLAGS = 10 ; MF_* +C_MEM_TBASE = 11 ; field type / method result type +C_MEM_TNAME = 12 +C_MEM_TLEN_LO = 13 +C_MEM_TLEN_HI = 14 +C_MEM_PFIRST = 15 ; first parameter row +C_MEM_PCOUNT = 16 ; parameter rows (declaration order) +C_PAR_TBASE = 17 ; parameter type +C_PAR_TNAME = 18 +C_PAR_TLEN_LO = 19 +C_PAR_TLEN_HI = 20 +C_IMPL_IFACE = 21 ; implemented interface name ID +TABLE_COLUMNS = 22 +C_TYPE_LAST = C_TYPE_INIT ; type columns 0..this are cleared per run .endif diff --git a/newLang/src/compiler/parser.s b/newLang/src/compiler/parser.s index a796e37..59cb0bb 100644 --- a/newLang/src/compiler/parser.s +++ b/newLang/src/compiler/parser.s @@ -1,5 +1,5 @@ -; parser.s - declarations, members, scopes, statements, and expressions -; (E03-S01, E03-S02, E03-S03). +; parser.s - declarations, members, scopes, statements, expressions, and +; type checking (E03-S01..E03-S04). ; ; parse_file runs two passes over the loaded source (call after src_load, ; diag_init, and names_init): @@ -8,14 +8,22 @@ ; the bank-1 type table and each field and method signature in the ; member and parameter tables; parses every statement; reports ; syntax, ordering, duplicate, init/destroy shape, and -; local-placement errors. +; local-placement errors; records each class's implements list +; and init row. ; 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. +; (forward references are therefore legal), name, member, and +; access errors in statements, and type errors (E03-S04). ; Statements are parsed by parse_statement and expressions by precedence ; climbing (parse_expr); see the "statements" and "expressions" sections. ; +; Types: every expression leaves its type in sc_* (types.s). Operators, +; conversions, and constants are decided by types.s; the parser saves the +; types it must keep (a left operand, an assignment target, an array or a +; call while its index or arguments are parsed) in per-depth slots. +; Returns: `term` says the statements so far end in `return`; an if counts +; only with an else whose branches both do; loops never count. +; ; 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 (D123); in the @@ -40,6 +48,11 @@ .import diag_report, diag_line, diag_col, diag_arg1, diag_arg1_len .import mmu_peek1, mmu_poke1 .importzp mmu_ptr +.import diag_arg2_len +.import sc_base, sc_name, sc_len, sc_val, sc_neg +.import tt_base, tt_name, tt_len +.import ty_assign, ty_binary, ty_unary +.import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 .export parse_file, tb_get, mem_count, par_count .export ex_trace_on, ex_min_sp @@ -51,6 +64,10 @@ ACC_PUBLIC = 1 ACC_PRIVATE = 2 LIFE_INIT = 1 LIFE_DESTROY = 2 +RET_UNKNOWN = 0 ; no checks (the header had an error) +RET_NONE = 1 ; `return` takes no value +RET_VALUE = 2 ; `return value`, on every path +SLOTS = EX_MAX_DEPTH + 1 ; per-depth type slots (slot 0: statement) .segment "ZEROPAGE" ex_tp: .res 2 ; test trace pointer (ex_trace_on) @@ -73,6 +90,8 @@ parse_file: sta par_count sta mem_full sta par_full + sta impl_count + sta impl_full lda #$FF sta ex_min_sp lda #$01 @@ -242,6 +261,7 @@ parse_class: jsr skip_line jmp @members : jsr type_ref + jsr add_impl jsr next lda tok_kind cmp #TOK_COMMA @@ -338,6 +358,7 @@ parse_program: lda #$00 sta cur_owner sta cur_class ; no self, no fields, privates hidden + sta ret_kind ; RET_UNKNOWN unless the name parses jsr next lda tok_kind cmp #TOK_IDENT @@ -348,7 +369,12 @@ parse_program: jmp @body : lda #TYPE_PROGRAM jsr declare_type - jsr next + lda dk_id ; `return` ends the program; no value + sta body_nm + beq :+ + lda #RET_NONE + sta ret_kind +: jsr next jsr expect_eol @body: jsr scope_clear lda #KW_PROGRAM @@ -366,6 +392,7 @@ parse_signature: jsr scope_clear lda #$00 sta sig_params + sta ret_kind ; RET_UNKNOWN until the header parses lda #$FF sta cur_mem jsr next @@ -392,6 +419,7 @@ parse_signature: sta life @named: jsr intern_tok sta mk_name + sta body_nm lda #MF_METHOD ; public unless declared private ldx m_acc cpx #ACC_PRIVATE @@ -400,6 +428,7 @@ parse_signature: : sta mk_flags jsr ty_clear ; no result until `as type` jsr declare_member + jsr note_init jsr next lda tok_kind cmp #TOK_LPAREN @@ -430,8 +459,11 @@ parse_signature: lda #E_SYN_EXPECTED_AS jmp @fail : jsr next + lda tok_col + sta ty_col jsr parse_type bcs @skip + jsr no_array_sig jsr set_local_type jsr add_param IFTOK TOK_COMMA, @more @@ -450,10 +482,14 @@ parse_signature: lda #E_SEM_LIFECYCLE_RETURN jsr err1 : jsr next + lda tok_col + sta ty_col jsr parse_type bcs @skip + jsr no_array_sig jsr set_mem_type @eol: jsr expect_eol + jsr set_ret clc rts @fail: jsr err1 @@ -497,6 +533,68 @@ lifecycle_kind: inc lw_kind bne @entry +; Arrays may be fields and locals only (spec 3.3, D126): report a +; parameter or result type ty_* that is an array, at column ty_col. +no_array_sig: + lda ty_len + ora ty_len+1 + beq @ok + lda #E_TYP_ARRAY_SIGNATURE + ldx ty_col + jmp err1_col +@ok: rts + +; Pass 1: a class's init records its member row (+1) for `new` checks. +note_init: + lda pass + cmp #$01 + bne @done + lda life + cmp #LIFE_INIT + bne @done + lda sig_iface + bne @done + ldx cur_mem + cpx #$FF + beq @done + inx + ldy cur_owner + lda #C_TYPE_INIT + jsr tb_set +@done: rts + +; After a header: what `return` in the body must look like (ret_kind) and +; the method's member row (ret_row). init/destroy return nothing; one that +; declared a result was already reported (E151), so it is not checked. +set_ret: + lda #RET_UNKNOWN + sta ret_kind + ldy cur_mem + cpy #$FF + beq @done + sty ret_row + lda #C_MEM_TBASE + jsr tb_get + ldx life + beq @plain + cmp #TY_NONE + beq @none + rts +@plain: cmp #TY_NONE + beq @none + lda #C_MEM_TLEN_LO ; an array result was reported (E094) + jsr tb_get + bne @done + lda #C_MEM_TLEN_HI + jsr tb_get + bne @done + lda #RET_VALUE + sta ret_kind + rts +@none: lda #RET_NONE + sta ret_kind +@done: rts + ; ---------------------------------------------------------------- types ; primitive | Name, optionally [number]. Fills ty_*. C=1 after reporting ; an error. @@ -524,7 +622,11 @@ parse_type: bne @bad lda tok_value sta ty_len - lda tok_value+1 + ora tok_value+1 + bne :+ + lda #E_TYP_ARRAY_LENGTH ; type[0] (it is then not an array) + jsr err1 +: lda tok_value+1 sta ty_len+1 jsr next lda tok_kind @@ -589,6 +691,7 @@ parse_body: lda #$00 sta block_else sta stmt_seen + sta term lda #$01 sta depth sta refs_on ; names resolve, except in the methods of @@ -617,7 +720,9 @@ parse_body: jsr parse_var ; declared anyway, to avoid cascades jmp @loop -@end: jsr next ; the closer keyword +@end: lda tok_col ; E150 is reported at `end` + sta end_col + jsr next ; the closer keyword lda tok_kind ldx depth cmp block_stack-1,x @@ -647,6 +752,7 @@ parse_body: sta depth @matched: dec depth + jsr close_block jsr next jsr expect_eol lda depth @@ -665,6 +771,32 @@ parse_body: sta depth rts +; Block block_stack[depth] has just closed: update term. At the end of a +; method body (depth 0) a method with a result must end in return. +close_block: + ldx depth + bne @inner + lda ret_kind + cmp #RET_VALUE + bne @done + lda term + bne @done + lda #E_SEM_MISSING_RETURN + ldx end_col + jmp err2_col +@inner: lda block_stack,x + cmp #KW_IF + bne @loop + lda block_else,x + beq @loop + lda blk_then,x ; if/else: both branches return + and term + jmp @merge +@loop: lda #$00 ; a loop or an if without else may not +@merge: ora blk_outer,x ; already returned before the block + sta term +@done: rts + ; `var` belongs at the top level of a body, and in a method only before the ; first statement (spec 4, 6.2). The program may mix them (spec 4 grammar). check_var_place: @@ -747,32 +879,52 @@ parse_statement: jsr trace_line lda tok_kind cmp #KW_IF - beq @block + beq @cond cmp #KW_WHILE - beq @block + beq @cond cmp #KW_FOR - beq @for - cmp #KW_ELSE + bne :+ + jmp @for +: cmp #KW_ELSE beq @else cmp #KW_RETURN beq @return cmp #KW_PRINT - beq @value + beq @print cmp #KW_DELETE - beq @value + beq @delete jmp stmt_expr -@block: jsr open_block ; if / while condition -@value: jsr next ; print / delete operand - jsr parse_full +@cond: jsr open_block ; if / while condition + jsr value + jsr check_cond + jmp end_stmt +@print: jsr value + jsr check_print + jmp end_stmt +@delete: + jsr value + jsr check_delete jmp end_stmt @return: + lda #$01 ; even if its operand has an error + sta term jsr next + lda tok_col + sta val_col lda tok_kind cmp #TOK_EOL - beq :+ + beq @bare cmp #TOK_EOF - beq :+ + beq @bare jsr parse_full + jsr check_return + jmp end_stmt +@bare: lda ret_kind + cmp #RET_VALUE + bne :+ + lda #E_SEM_MISSING_RETURN + ldx stmt_col + jsr err2_col : jmp end_stmt @else: ldx depth ; only once, directly inside an if lda block_stack-1,x @@ -782,6 +934,10 @@ parse_statement: bne @bad_else lda #$01 sta block_else-1,x + lda term ; the then branch is complete + sta blk_then-1,x + lda #$00 + sta term jsr next jmp end_stmt @bad_else: @@ -796,27 +952,32 @@ parse_statement: beq :+ lda #E_SYN_EXPECTED_IDENTIFIER jmp syn_error -: jsr resolve_name +: lda tok_col + sta val_col + jsr resolve_name + jsr check_for_var + ldx #$00 ; slot 0: the variable's type + jsr save_slot jsr next lda tok_kind cmp #TOK_EQ beq :+ lda #E_SYN_EXPECTED_EQUALS jmp syn_error -: jsr next - jsr parse_full +: jsr value + jsr check_slot0 lda tok_kind cmp #KW_TO beq :+ lda #E_SYN_EXPECTED_TO jmp syn_error -: jsr next - jsr parse_full +: jsr value + jsr check_slot0 lda tok_kind cmp #KW_STEP bne end_stmt - jsr next - jsr parse_full + jsr value + jsr check_slot0 ; fall through ; The statement must end here: one statement per line (spec 2.2). @@ -850,10 +1011,18 @@ stmt_expr: lda #E_SYN_NOT_ASSIGNABLE ldx stmt_col jmp syn_error_col -: jsr next - jsr parse_full +: ldx #$00 ; slot 0: the target's type + jsr save_slot + jsr value + jsr check_slot0 jmp end_stmt +; Skip the keyword or `=` and parse the expression after it (val_col). +value: jsr next + lda tok_col + sta val_col + jmp parse_full + ; Push the block opened by the current keyword (if/while/for). open_block: ldx depth @@ -865,9 +1034,119 @@ open_block: sta block_stack,x lda #$00 sta block_else,x + lda term ; restored when the block closes + sta blk_outer,x + lda #$00 + sta term inc depth rts +; ------------------------------------------------------ statement types +; Each check reports in pass 2 at val_col (the expression's first token). + +; sc must be assignable to the type in slot 0. +check_slot0: + ldx #$00 + jsr load_tt + jsr ty_assign + beq @ok + ldx val_col + jmp err2_col +@ok: rts + +check_cond: + lda sc_len + ora sc_len+1 + bne @bad + lda sc_base + beq @ok + cmp #TY_BOOLEAN + beq @ok +@bad: lda #E_TYP_CONDITION_NOT_BOOLEAN + ldx val_col + jmp err2_col +@ok: rts + +; print: a string literal, a number, or a char/byte/word/integer/boolean. +check_print: + lda sc_len + ora sc_len+1 + bne @bad + lda sc_base + cmp #TY_REF + beq @bad + cmp #TY_NULL + beq @bad + cmp #TY_VOID + beq @bad + rts +@bad: jsr ty_arg_sc1 + lda #E_TYP_CANNOT_PRINT + ldx val_col + jmp err2_col + +; delete: a class or interface reference, or null. +check_delete: + lda sc_len + ora sc_len+1 + bne @bad + lda sc_base + beq @ok + cmp #TY_REF + beq @ok + cmp #TY_NULL + beq @ok +@bad: jsr ty_arg_sc1 + lda #E_TYP_CANNOT_DELETE + ldx val_col + jmp err2_col +@ok: rts + +; `return value`: allowed only where a result is declared, and it must fit. +check_return: + lda ret_kind + cmp #RET_VALUE + beq @typed + cmp #RET_NONE + bne @ok + lda body_nm + jsr ty_arg_name1 + lda #E_SEM_LIFECYCLE_RETURN + ldx val_col + jmp err2_col +@typed: ldy ret_row + lda #C_MEM_TBASE + jsr row_tt + jsr ty_assign + beq @ok + ldx val_col + jmp err2_col +@ok: rts + +; The for variable must be a byte, word, or integer. A bad one is reported +; as "FOR NOT ALLOWED ON " and its type becomes unknown. +check_for_var: + lda sc_len + ora sc_len+1 + bne @bad + lda sc_base + beq @ok + cmp #TY_BYTE + beq @ok + cmp #TY_WORD + beq @ok + cmp #TY_INTEGER + beq @ok +@bad: jsr ty_arg_sc2 + ldx stmt_col + ldy #3 ; argument 1: the `for` keyword + jsr arg_at + jsr chain_none + lda #E_TYP_BAD_OPERAND + ldx val_col + jmp err2_col +@ok: 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. @@ -897,8 +1176,8 @@ syn_error_col: ; 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. +; sc_* is the expression's type (types.s); ex_last says what a postfix +; chain ends in. Operator errors are reported at the operator. parse_full: lda #$01 @@ -921,8 +1200,8 @@ parse_expr: cmp ex_min,x bcc @done ; binds looser: the caller takes it sta ex_prec,x - lda tok_kind - sta ex_op,x + jsr note_op + jsr save_slot ; the left operand jsr next ldx ex_depth lda ex_prec,x @@ -933,7 +1212,12 @@ parse_expr: lda ex_op,x ora #TR_BINARY jsr trace_byte - jsr chain_none + ldx ex_depth + jsr load_tt + ldx ex_depth + lda ex_op,x + jsr ty_binary + jsr op_error lda #EX_OTHER sta ex_last jmp @loop @@ -946,8 +1230,7 @@ parse_unary: beq @op jmp parse_postfix @op: jsr ex_enter - lda tok_kind - sta ex_op,x + jsr note_op jsr next jsr parse_unary ; right associative: - not -x ldx ex_depth @@ -957,12 +1240,45 @@ parse_unary: beq :+ lda #TR_NEG : jsr trace_byte - jsr chain_none + ldx ex_depth + lda ex_op,x + jsr ty_unary + jsr op_error lda #EX_OTHER sta ex_last dec ex_depth rts +; Remember the operator token at depth X (kind, column, length). X kept. +note_op: + lda tok_kind + sta ex_op,x + lda tok_col + sta ex_ocol,x + lda tok_len + sta ex_olen,x + rts + +; Report error A (0 = none) from types.s at the operator of this depth. +; E082 names the operator as argument 1. +op_error: + beq @ok + sta op_code + cmp #E_TYP_BAD_OPERAND + bne @report + ldx ex_depth + ldy ex_olen,x + lda ex_ocol,x + tax + jsr arg_at +@report: + ldx ex_depth + lda ex_ocol,x + tax + lda op_code + jmp err2_col +@ok: rts + ; One more nesting level; A preserved, X = the new depth. ex_enter: inc ex_depth @@ -1011,6 +1327,12 @@ parse_postfix: @early: lda #E_SYN_UNEXPECTED_EOL jmp syn_error @literal: + lda lit_types,x + sta sc_base + lda tok_value ; a number's value (others ignore it) + sta sc_val + lda tok_value+1 + sta sc_val+1 jsr next jmp @post @name: jsr resolve_name @@ -1021,9 +1343,8 @@ parse_postfix: @self: jsr self_ref jsr next jmp @post -@group: jsr next +@group: jsr next ; the group has its contents' type jsr parse_full - jsr chain_none lda #EX_OTHER sta ex_last lda tok_kind @@ -1039,16 +1360,18 @@ parse_postfix: beq :+ lda #E_SYN_EXPECTED_IDENTIFIER jmp syn_error -: jsr new_type +: jsr new_type ; sc, ca_row, ca_name jsr next lda tok_kind cmp #TOK_LPAREN beq :+ lda #E_SYN_EXPECTED_LPAREN jmp syn_error -: jsr ex_save_chain +: ldx ex_depth + jsr save_slot jsr parse_args - jsr ex_restore_chain + ldx ex_depth + jsr load_sc lda #EX_OTHER sta ex_last @post: lda tok_kind @@ -1066,11 +1389,8 @@ parse_postfix: bcs :+ lda #E_SYN_EXPECTED_IDENTIFIER jmp syn_error -: jsr pend_member ; the name and the left side's type +: jsr pend_member ; the name; sc is still the left side 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 @@ -1078,21 +1398,21 @@ parse_postfix: 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 +@call: ldx ex_depth ; sc = the result, kept over the arguments + jsr save_slot jsr parse_args - jsr ex_restore_chain + ldx ex_depth + jsr load_sc lda #EX_CALL sta ex_last jmp @post -@index: jsr element_type - sta sc_base - stx sc_name - jsr ex_save_chain +@index: jsr index_base ; sc = the array, or unknown + ldx ex_depth + jsr save_slot jsr next + lda tok_col + ldx ex_depth + sta ex_acol,x jsr parse_full lda tok_kind cmp #TOK_RBRACKET @@ -1100,37 +1420,134 @@ parse_postfix: lda #E_SYN_EXPECTED_RBRACKET jmp syn_error : jsr next - jsr ex_restore_chain + jsr check_index + ldx ex_depth ; the element: the array's base type + jsr load_sc + lda #$00 + sta sc_len + sta sc_len+1 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 +; `[` follows sc: it must be an array (else E087 and sc becomes unknown). +index_base: 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 + beq @ok + lda sc_len + ora sc_len+1 + bne @ok + jsr ty_arg_sc1 + lda #E_TYP_NOT_ARRAY + jsr err2 + jmp chain_none +@ok: rts + +; The index (sc) must be an integer or convert to word (D125); a constant +; must be below the length of the array saved in this depth's slot. +check_index: + lda sc_base + cmp #TY_INTEGER + bne :+ + lda sc_len + ora sc_len+1 + beq @ok +: lda #TY_WORD + sta tt_base lda #$00 - sta sc_len - sta sc_len+1 + sta tt_len + sta tt_len+1 + jsr ty_assign + beq @range + pha + ldx ex_depth + lda ex_acol,x + tax + pla + jmp err2_col +@range: lda sc_base + cmp #TY_CONST + bne @ok + ldx ex_depth + lda lt + 2 * SLOTS,x ; the array's length + ora lt + 3 * SLOTS,x + beq @ok + lda sc_val + cmp lt + 2 * SLOTS,x + lda sc_val+1 + sbc lt + 3 * SLOTS,x + bcc @ok + lda ex_acol,x + tax + lda #E_TYP_INDEX_RANGE + jmp err2_col +@ok: rts + +; Type slots: lt holds TV_SIZE arrays of SLOTS bytes, one type per depth. +; save_slot: slot X = sc. load_sc / load_tt: sc / tt = slot X. +save_slot: + ldy #$00 +@copy: lda sc_base,y + sta lt,x + jsr slot_next + bne @copy + rts +load_sc: + ldy #$00 +@copy: lda lt,x + sta sc_base,y + jsr slot_next + bne @copy + rts +load_tt: + ldy #$00 +@copy: lda lt,x + sta tt_base,y + jsr slot_next + bne @copy + rts +slot_next: ; X += SLOTS, Y += 1; Z=1 after the last + txa + clc + adc #SLOTS + tax + iny + cpy #TV_SIZE rts -; `(` [expr {, expr}] `)` with the current token at `(`. +; `(` [expr {, expr}] `)` with the current token at `(`. When ca_check is +; set the arguments are checked against member row ca_row ($FF: a class +; without init, which takes none); ca_name is the name E153 shows. parse_args: - jsr next + ldx ex_depth + lda ca_name + sta ex_cnm,x + lda ca_check + sta ex_cchk,x + lda #$00 + sta ex_carg,x + sta ex_cpc,x + sta ex_cpf,x + lda ca_check + beq @open + ldy ca_row + cpy #$FF + beq @open + lda #C_MEM_PCOUNT + jsr tb_get + sta ex_cpc,x + lda #C_MEM_PFIRST + jsr tb_get + sta ex_cpf,x +@open: jsr next lda tok_kind cmp #TOK_RPAREN beq @close -@arg: jsr parse_full +@arg: lda tok_col + ldx ex_depth + sta ex_acol,x + jsr parse_full + jsr check_arg lda tok_kind cmp #TOK_COMMA bne :+ @@ -1140,7 +1557,82 @@ parse_args: beq @close lda #E_SYN_EXPECTED_RPAREN jmp syn_error -@close: jmp next +@close: ldx ex_depth ; too few arguments: reported at `)` + lda ex_cchk,x + beq @done + lda ex_carg,x + cmp ex_cpc,x + bcs @done + lda tok_col + jsr count_error +@done: jmp next + +; Argument ex_carg (sc) against its parameter. The first argument beyond +; the parameters reports E153; later ones are not reported again. +check_arg: + ldx ex_depth + lda ex_cchk,x + beq @done + lda ex_carg,x + inc ex_carg,x + cmp ex_cpc,x + bcc @typed + bne @done + lda ex_acol,x + jmp count_error +@typed: clc + adc ex_cpf,x + tay + lda #C_PAR_TBASE + jsr row_tt + jsr ty_assign + beq @done + pha + ldx ex_depth + lda ex_acol,x + tax + pla + jmp err2_col +@done: rts + +; E153 " TAKES ARGUMENTS" at column A for this depth's call. +count_error: + pha + ldx ex_depth + lda ex_cnm,x + jsr ty_arg_name1 + ldx ex_depth + lda ex_cpc,x + jsr ty_arg_num2 + pla + tax + lda #E_SEM_ARG_COUNT + jmp err2_col + +; tt = the declared type in columns A..A+3 of row Y (a member's result or +; a parameter). An array type was already reported: it checks nothing. +row_tt: sta rt_col + jsr tb_get + sta tt_base + inc rt_col + lda rt_col + jsr tb_get + sta tt_name + inc rt_col + lda rt_col + jsr tb_get + sta tt_len + inc rt_col + lda rt_col + jsr tb_get + ora tt_len + beq :+ + lda #TY_NONE + sta tt_base +: lda #$00 + sta tt_len + sta tt_len+1 + rts ; Test trace (pass 1, when ex_trace_on): each statement starts with $FF and ; its line; operands are their column; operators follow their operands. @@ -1169,28 +1661,30 @@ pend_member: sta pend_col lda tok_len sta pend_len - lda sc_base - sta pend_base - lda sc_name - sta pend_name - lda sc_len - ora sc_len+1 - sta pend_array jsr find_tok sta pend_id - jmp chain_none + rts -; Resolve the pending member against its left side's type. The member is a -; method when the current token is `(`, else a field. +; Resolve the pending member against the left side's type (sc). The member +; is a method when the current token is `(`, else a field. sc becomes the +; field's type or the method's result (TY_VOID if none); for a method +; ca_row/ca_name describe the call for parse_args. resolve_member: + lda #$00 ; unchecked unless a method is found + sta ca_check + lda pend_id + sta ca_name lda refs_on - beq @none - lda pend_base - cmp #TY_REF - bne @none - lda pend_array ; an array has no members - bne @none - ldy pend_name + beq :+ + lda sc_base ; unknown left side: reported already + bne :++ +: jmp chain_none +: cmp #TY_REF + bne @nomem + lda sc_len ; an array has no members + ora sc_len+1 + bne @nomem + ldy sc_name beq @none lda #C_TYPE_KIND jsr tb_get @@ -1226,20 +1720,25 @@ resolve_member: bne @method jmp chain_member ; a field: its declared type @method: - lda #C_MEM_TLEN_LO ; an array result is not indexed further - jsr tb_get - bne @none - lda #C_MEM_TLEN_HI - jsr tb_get + sty ca_row + lda #$01 + sta ca_check + jsr chain_member ; the result, applied after the `)` + lda sc_len ; an array result was reported (E094) + ora sc_len+1 bne @none - lda #C_MEM_TBASE ; the result, applied at the `)` - jsr tb_get - sta call_base - lda #C_MEM_TNAME - jsr tb_get - sta call_name -@none: rts + lda sc_base + bne :+ + lda #TY_VOID + sta sc_base +: rts +@nomem: jsr ty_arg_sc1 ; a number, boolean, array, null... + lda #E_TYP_NO_MEMBERS + ldx pend_col + jsr err2_col +@none: jmp chain_none @unknown: + jsr chain_none lda #E_SEM_UNKNOWN_NAME jmp err_pend @@ -1297,38 +1796,55 @@ self_ref: lda #E_SYN_UNEXPECTED_TOKEN jmp err2 -; `new Name`: Name must be a declared class or interface. +; `new Name`: Name must be a declared class. sc = its type; ca_row/ca_name +; describe the init call (ca_row = $FF when the class declares no init). new_type: jsr chain_none + lda #$00 + sta ca_check lda refs_on bne :+ rts : jsr find_tok beq @unknown sta sc_name + sta ca_name tay lda #C_TYPE_KIND jsr tb_get beq @unknown - cmp #TYPE_PROGRAM - beq @program + cmp #TYPE_CLASS + bne @notclass lda #TY_REF sta sc_base + lda #C_TYPE_INIT ; init row + 1, or 0 + jsr tb_get + tax + dex + stx ca_row + lda #$01 + sta ca_check rts -@program: - jmp chain_none ; nonsense, but not a name error +@notclass: ; an interface or the program + jsr chain_none + jsr arg_token + lda #E_TYP_NOT_CLASS + jmp err2 @unknown: jsr chain_none jsr arg_token lda #E_SEM_UNKNOWN_NAME jmp err2 -chain_none: +chain_none: ; sc = unknown; X and Y kept lda #$00 sta sc_base sta sc_name sta sc_len sta sc_len+1 + sta sc_val + sta sc_val+1 + sta sc_neg rts ; Chain = the declared type of member row Y. @@ -1347,18 +1863,6 @@ chain_member: sta sc_len+1 rts -; A/X = base/name of the chain's element type, or 0/0 if not an array. -element_type: - lda sc_len - ora sc_len+1 - beq @none - lda sc_base - ldx sc_name - rts -@none: lda #$00 - tax - rts - ; Report A at the pending member's column (pass 2). err_pend: pha @@ -1421,21 +1925,25 @@ open_owner: sty cur_owner lda pass cmp #$01 - bne @done + bne @again ldx mem_count lda #C_TYPE_MFIRST jsr tb_set + rts +@again: lda #C_TYPE_MFIRST ; pass 2: rows recur in the same order + jsr tb_get + sta mem_seq @done: rts ; Pass 1: add member mk_name (flags mk_flags, type ty_*) to cur_owner, ; reporting a duplicate in the same namespace (fields and methods are ; separate). cur_mem = its row, or $FF when nothing was recorded. +; Pass 2: cur_mem = the row pass 1 recorded for this declaration. An owner's +; rows are in declaration order, so the declaration is the one whose row is +; the next expected (mem_seq); a duplicate finds an earlier row. declare_member: lda #$FF sta cur_mem - lda pass - cmp #$01 - bne @done lda cur_owner beq @done sta fm_owner @@ -1445,8 +1953,17 @@ declare_member: lda mk_flags and #MF_METHOD sta fm_kind - jsr find_member - bcc @add + jsr find_member ; C=1, Y = row when found + ldx pass + dex + beq @pass1 + bcc @done + cpy mem_seq + bne @done + sty cur_mem + inc mem_seq +@done: rts +@pass1: bcc @add jsr arg_token lda #E_SEM_DUPLICATE_NAME jmp err1 @@ -1484,7 +2001,7 @@ declare_member: inx lda #C_TYPE_MCOUNT jsr tb_set -@done: rts + rts ; Pass 1: the method's result type (ty_*) after `as type`. set_mem_type: @@ -1550,6 +2067,41 @@ add_param: jsr tb_set @done: rts +; Pass 1: append interface A (a name ID) to cur_owner's implements list. +add_impl: + ldx pass + cpx #$01 + bne @done + ldx cur_owner + beq @done + tax + beq @done + ldy impl_count + cpy #IMPLS_MAX + bcc :+ + lda impl_full ; reported once + bne @done + inc impl_full + jsr arg_token + lda #E_RES_TOO_MANY_IMPLEMENTS + jmp err1 +: lda #C_IMPL_IFACE + jsr tb_set + ldy cur_owner + lda #C_TYPE_ICOUNT ; the first one starts the class's range + jsr tb_get + bne :+ + ldx impl_count + lda #C_TYPE_IFIRST + jsr tb_set + lda #$00 +: tax + inx + lda #C_TYPE_ICOUNT + jsr tb_set + inc impl_count +@done: rts + ; Find member fm_name of kind fm_kind (MF_METHOD or 0) in fm_owner's rows. ; C=1 and Y = row when found. find_member: @@ -1648,9 +2200,9 @@ find_tok: jsr name_find rts -; Zero the type-table columns (kind, lines, member range). +; Zero the type-table columns (kind, lines, member, implements, init). tt_clear: - lda #C_TYPE_MCOUNT + lda #C_TYPE_LAST sta dk_kind ; column counter (reused scratch) @col: ldy #$00 @byte: lda dk_kind @@ -1769,8 +2321,16 @@ err2: ldx pass beq report drop: ldx #$00 stx diag_arg1_len + stx diag_arg2_len rts +; Report A at column X of the current line, pass 1 only. +err1_col: + ldy pass + cpy #$01 + beq report_col + bne drop + ; Report A at column X of the current line, pass 2 only. err2_col: ldy pass @@ -1838,6 +2398,8 @@ bin_precs: lit_kinds: .byte TOK_NUMBER, TOK_STRING, TOK_CHAR, KW_TRUE, KW_FALSE, KW_NULL lit_count = * - lit_kinds +lit_types: + .byte TY_CONST, TY_STRING, TY_CHAR, TY_BOOLEAN, TY_BOOLEAN, TY_NULL life_words: ; LIFE_INIT, LIFE_DESTROY order .byte 4, "INIT" .byte 7, "DESTROY" @@ -1911,21 +2473,38 @@ 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_ocol: .res SLOTS ; operator column / length +ex_olen: .res SLOTS +ex_acol: .res SLOTS ; column of the argument or index +ex_cchk: .res SLOTS ; call: check the arguments +ex_cnm: .res SLOTS ; call: name for E153 +ex_carg: .res SLOTS ; call: arguments so far +ex_cpc: .res SLOTS ; call: parameter count +ex_cpf: .res SLOTS ; call: first parameter row +lt: .res SLOTS * TV_SIZE ; type slots (save_slot) 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 -call_base: .res 1 -call_name: .res 1 pend_id: .res 1 pend_col: .res 1 pend_len: .res 1 -pend_base: .res 1 -pend_name: .res 1 -pend_array: .res 1 rn_id: .res 1 +; types (E03-S04) +ca_check: .res 1 ; parse_args: check against ca_row +ca_row: .res 1 +ca_name: .res 1 +op_code: .res 1 +rt_col: .res 1 +val_col: .res 1 ; first column of the statement's value +ty_col: .res 1 ; first column of a signature type +mem_seq: .res 1 ; pass 2: next member row of cur_owner +impl_count: .res 1 +impl_full: .res 1 +term: .res 1 ; the statements so far end in return +blk_outer: .res PARSE_MAX_DEPTH ; term before the block opened +blk_then: .res PARSE_MAX_DEPTH ; term at the end of an if's then part +ret_kind: .res 1 ; RET_* +ret_row: .res 1 ; member row of the method (RET_VALUE) +body_nm: .res 1 ; method or program name ID (E151) +end_col: .res 1 ; column of `end` (E150) diff --git a/newLang/src/compiler/types.s b/newLang/src/compiler/types.s new file mode 100644 index 0000000..a5df2b8 --- /dev/null +++ b/newLang/src/compiler/types.s @@ -0,0 +1,853 @@ +; types.s - expression types, conversions, operators, and constants (E03-S04). +; +; Two type registers hold a type value each (parser.inc, TV_SIZE bytes): +; sc_* the value just parsed (the source of a conversion, the right +; operand, the operand of a unary operator, the result) +; tt_* the target of a conversion or the left operand +; They are contiguous, sc first, so a routine can address either with X = 0 +; (sc) or X = TV_SIZE (tt). +; +; Rules (spec 3.4 and 8, decisions D124-D126): +; ty_assign may sc be stored in a place of type tt? byte widens to word +; and integer; a constant fits the target's range; null goes +; to any reference; a class goes to an interface it +; implements; nothing else converts. Arrays are not values. +; ty_binary sc = tt sc. and/or take booleans. + - * / take numbers +; and yield the operand type after widening (a constant adopts +; the other operand's type; two constants fold). < <= > >= +; take numbers or chars, = <> also booleans and references +; (identity: same type, null, or a class and an interface it +; implements); they yield boolean. +; ty_unary not takes a boolean; - takes integer, byte (giving integer), +; or a constant (folded). +; Each returns A = 0 (Z=1) or an E_ code (Z=0) whose arguments have been +; spelled into diag_arg1/diag_arg2. TY_NONE and references to undeclared +; types are "vague": an error was already reported, so nothing is checked. +; For E_TYP_BAD_OPERAND only argument 2 (the type) is set; the caller +; supplies argument 1 (the operator). After an operator error sc is TY_NONE. +; +; Constants: magnitude 0..65535 plus a sign; negative magnitudes are 1..32768. +; A folded result outside -32768..65535 is E_TYP_NUMBER_RANGE. + +.setcpu "6502" +.include "../common/tokens.inc" +.include "../common/errors.inc" +.include "../common/parser.inc" + +.import tb_get +.import name_get, name_out, name_out_len +.import diag_arg1, diag_arg1_len, diag_arg2, diag_arg2_len + +.export sc_base, sc_name, sc_len, sc_val, sc_neg +.export tt_base, tt_name, tt_len, tt_val, tt_neg +.export ty_assign, ty_binary, ty_unary +.export ty_arg_sc1, ty_arg_sc2, ty_arg_tt1, ty_arg_name1, ty_arg_num2 + +ARG_MAX = 16 ; spelled arguments (diagnostics rule) + +; operand classes (bit masks) +CL_BAD = $00 +CL_NUM = $01 +CL_BOOL = $02 +CL_CHAR = $04 +CL_REF = $08 + +; operator groups +G_LOGIC = 0 +G_ARITH = 1 +G_ORDER = 2 +G_EQUAL = 3 + +; offsets within a type value +V_BASE = 0 +V_NAME = 1 +V_LEN = 2 +V_VAL = 4 +V_NEG = 6 + +.segment "CODE" + +; ------------------------------------------------------------ assignment +ty_assign: + ldx #$00 + jsr vague + bcs @ok + ldx #TV_SIZE + jsr vague + bcs @ok + lda sc_len + ora sc_len+1 + beq :+ + lda #E_TYP_ARRAY_NEEDS_INDEX + jmp err_sc1 +: lda tt_len ; an array place takes no value + ora tt_len+1 + bne @bad + lda sc_base + cmp #TY_CONST + bne @typed + lda tt_base + jsr is_numeric + bcc @bad + ldx #$00 + lda tt_base + jsr fits + bcs @ok + lda #E_TYP_NUMBER_DOES_NOT_FIT + jmp err_sc1_tt2 +@typed: cmp tt_base + bne @diff + cmp #TY_REF + bne @ok + lda sc_name + cmp tt_name + beq @ok + lda sc_name ; class -> implemented interface + sta cv_class + lda tt_name + sta cv_iface + jsr converts + bcs @ok + bcc @bad +@diff: cmp #TY_BYTE ; byte widens to word and integer + bne @null + lda tt_base + cmp #TY_WORD + beq @ok + cmp #TY_INTEGER + beq @ok + bne @bad +@null: cmp #TY_NULL + bne @bad + lda tt_base + cmp #TY_REF + beq @ok +@bad: lda #E_TYP_MISMATCH + jmp err_tt1_sc2 +@ok: lda #$00 + rts + +; ------------------------------------------------------------- operators +; A = operator token; tt = left operand, sc = right operand. +ty_binary: + sta op + ldx #$00 + jsr vague + bcs @vague + ldx #TV_SIZE + jsr vague + bcc :+ +@vague: jmp to_none_ok +: ldx #G_LOGIC ; group and the operand classes it takes + lda op + cmp #KW_AND + beq @group + cmp #KW_OR + beq @group + ldx #G_EQUAL + cmp #TOK_EQ + beq @group + cmp #TOK_NE + beq @group + ldx #G_ARITH + cmp #TOK_PLUS + bcc @order + cmp #TOK_SLASH + 1 + bcc @group +@order: ldx #G_ORDER +@group: stx group + lda group_allow,x + sta allow + ldx #TV_SIZE ; the left operand first + jsr opclass + sta lcls + and allow + bne :+ + jsr ty_arg_tt2 + jmp bad_operand +: ldx #$00 + jsr opclass + sta rcls + and allow + bne :+ + jsr ty_arg_sc2 + jmp bad_operand +: lda lcls + cmp rcls + bne @mix + cmp #CL_NUM + beq @num + cmp #CL_REF + beq @refs + jmp to_boolean ; two booleans or two chars +@num: jsr unify ; sc = the operand type, or an error + bne @err + lda group + cmp #G_ARITH + bne to_boolean + lda sc_base + cmp #TY_CONST + bne @done + lda tt_base + cmp #TY_CONST + bne @done + jmp fold +@done: lda #$00 + rts +@err: jmp to_none +@refs: lda tt_base ; identity comparison + cmp #TY_NULL + beq to_boolean + lda sc_base + cmp #TY_NULL + beq to_boolean + lda tt_name + cmp sc_name + beq to_boolean + sta cv_class ; left class, right interface + lda sc_name + sta cv_iface + jsr converts + bcs to_boolean + lda sc_name ; right class, left interface + sta cv_class + lda tt_name + sta cv_iface + jsr converts + bcs to_boolean +@mix: lda #E_TYP_OPERANDS_DIFFER + jsr err_tt1_sc2 + jmp to_none + +to_boolean: + lda #TY_BOOLEAN + sta sc_base + lda #$00 + sta sc_name + sta sc_len + sta sc_len+1 + rts + +bad_operand: + lda #E_TYP_BAD_OPERAND + jmp to_none + +; A = operator token; sc = operand. +ty_unary: + sta op + ldx #$00 + jsr vague + bcs to_none_ok + lda sc_len + ora sc_len+1 + bne @bad + lda sc_base + ldx op + cpx #KW_NOT + bne @neg + cmp #TY_BOOLEAN + beq @ok + bne @bad +@neg: cmp #TY_INTEGER + beq @ok + cmp #TY_BYTE + bne :+ + lda #TY_INTEGER ; byte widens to integer for negation + sta sc_base + bne @ok +: cmp #TY_CONST + bne @bad + jsr negate + bcc @ok + lda #E_TYP_NUMBER_RANGE + jmp to_none +@ok: lda #$00 + rts +@bad: jsr ty_arg_sc2 + jmp bad_operand + +; sc = TY_NONE; A = 0 (Z=1) from to_none_ok, else A preserved. +to_none_ok: + lda #$00 +to_none: + pha + lda #TY_NONE + sta sc_base + sta sc_name + sta sc_len + sta sc_len+1 + pla + rts + +; Both operands numeric (tt, sc): sc = the common type. A = 0 or E_ code. +unify: lda tt_base + cmp #TY_CONST + bne @ltyped + lda sc_base + cmp #TY_CONST + beq @ok ; two constants: the caller folds + ldx #TV_SIZE ; the left constant must fit the right type + jsr fits + bcs @ok + lda #E_TYP_NUMBER_DOES_NOT_FIT + jmp err_tt1_sc2 +@ltyped: + lda sc_base + cmp #TY_CONST + bne @both + ldx #$00 ; the right constant must fit the left type + lda tt_base + jsr fits + bcc :+ + lda tt_base + sta sc_base + bne @ok +: lda #E_TYP_NUMBER_DOES_NOT_FIT + jmp err_sc1_tt2 +@both: cmp tt_base + beq @ok + lda tt_base ; byte widens to the other operand's type + cmp #TY_BYTE + beq @ok + lda sc_base + cmp #TY_BYTE + bne @mix + lda tt_base + sta sc_base +@ok: lda #$00 + rts +@mix: lda #E_TYP_OPERANDS_DIFFER + jmp err_tt1_sc2 + +; A = the operand class of the type at X (CL_*). +opclass: + lda tv+V_LEN,x + ora tv+V_LEN+1,x + bne @bad + ldy tv+V_BASE,x + lda base_class,y + rts +@bad: lda #CL_BAD + rts + +; C=1 when A is byte, word, or integer. +is_numeric: + cmp #TY_BYTE + beq @yes + cmp #TY_WORD + beq @yes + cmp #TY_INTEGER + beq @yes + clc + rts +@yes: sec + rts + +; C=1 when the constant at X fits primitive A (byte, word, or integer). +fits: cmp #TY_BYTE + beq @byte + cmp #TY_WORD + beq @word + lda tv+V_NEG,x ; integer: -32768..32767 + bne @yes + lda tv+V_VAL+1,x + cmp #$80 + bcc @yes + bcs @no +@byte: lda tv+V_NEG,x + bne @no + lda tv+V_VAL+1,x + bne @no + beq @yes +@word: lda tv+V_NEG,x + bne @no +@yes: sec + rts +@no: clc + rts + +; C=1 when the type at X is TY_NONE or names no declared class/interface. +vague: lda tv+V_BASE,x + beq @yes + cmp #TY_REF + bne @no + ldy tv+V_NAME,x + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_INTERFACE + beq @no + cmp #TYPE_CLASS + beq @no +@yes: sec + rts +@no: clc + rts + +; C=1 when cv_class is a class whose implements list names interface cv_iface. +converts: + ldy cv_iface + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_INTERFACE + bne @no + ldy cv_class + lda #C_TYPE_KIND + jsr tb_get + cmp #TYPE_CLASS + bne @no + lda #C_TYPE_ICOUNT + jsr tb_get + beq @no + sta cv_left + lda #C_TYPE_IFIRST + jsr tb_get + tay +@scan: lda #C_IMPL_IFACE + jsr tb_get + cmp cv_iface + beq @yes + iny + dec cv_left + bne @scan +@no: clc + rts +@yes: sec + rts + +; ------------------------------------------------------------- constants +; sc = -sc. C=1 when the result is below -32768. +negate: lda sc_val + ora sc_val+1 + beq @ok ; -0 = 0 + lda sc_neg + bne @flip + lda sc_val+1 ; 1..32768 can become negative + cmp #$80 + bcc @flip + bne @range + lda sc_val + bne @range +@flip: lda sc_neg + eor #$01 + sta sc_neg +@ok: clc + rts +@range: sec + rts + +; sc = tt op sc, both constants. A = 0 or E_ code (sc = TY_NONE). +fold: lda op + cmp #TOK_STAR + bne :+ + jmp @mul +: lda op + cmp #TOK_SLASH + bne :+ + jmp @div +: ldx #TV_SIZE ; + and - in 24-bit two's complement + ldy #0 + jsr to24 + ldx #$00 + ldy #3 + jsr to24 + lda op + cmp #TOK_MINUS + beq @sub + clc + lda w24 + adc w24+3 + sta w24 + lda w24+1 + adc w24+4 + sta w24+1 + lda w24+2 + adc w24+5 + sta w24+2 + jmp @back +@sub: sec + lda w24 + sbc w24+3 + sta w24 + lda w24+1 + sbc w24+4 + sta w24+1 + lda w24+2 + sbc w24+5 + sta w24+2 +@back: lda w24+2 ; -32768..65535 back to magnitude and sign + beq @pos + cmp #$FF + bne @range + lda w24+1 + bpl @range + sec + lda #$00 + sbc w24 + sta sc_val + lda #$00 + sbc w24+1 + sta sc_val+1 + lda #$01 + sta sc_neg + lda #$00 + rts +@pos: lda w24 + sta sc_val + lda w24+1 + sta sc_val+1 + lda #$00 + sta sc_neg + rts +@range: lda #E_TYP_NUMBER_RANGE + jmp to_none + +@mul: lda tt_val ; 16 x 16 -> 32 unsigned + sta mc + lda tt_val+1 + sta mc+1 + lda sc_val + sta mr + lda sc_val+1 + sta mr+1 + lda #$00 + sta prod+2 + ldx #16 +@mloop: lsr mr+1 + ror mr + bcc @mshift + tay + clc + lda mc + adc prod+2 + sta prod+2 + tya + adc mc+1 +@mshift: + ror a + ror prod+2 + ror prod+1 + ror prod + dex + bne @mloop + ora prod+2 ; above 65535 + bne @range + lda prod + sta sc_val + lda prod+1 + sta sc_val+1 + jmp @sign + +@div: lda sc_val + ora sc_val+1 + bne :+ + lda #E_TYP_DIVIDE_BY_ZERO + jmp to_none +: lda tt_val ; 16 / 16 unsigned, quotient in mr + sta mr + lda tt_val+1 + sta mr+1 + lda #$00 + sta prod + sta prod+1 + ldx #16 +@dloop: asl mr + rol mr+1 + rol prod + rol prod+1 + sec + lda prod + sbc sc_val + tay + lda prod+1 + sbc sc_val+1 + bcc @dnext + sta prod+1 + sty prod + inc mr +@dnext: dex + bne @dloop + lda mr + sta sc_val + lda mr+1 + sta sc_val+1 +; sign of a product or quotient; -0 = 0; a negative magnitude <= 32768 +@sign: lda tt_neg + eor sc_neg + sta sc_neg + lda sc_val + ora sc_val+1 + bne :+ + sta sc_neg +: lda sc_neg + beq @fine + lda sc_val+1 + cmp #$80 + bcc @fine + bne @big + lda sc_val + bne @big +@fine: lda #$00 + rts +@big: lda #E_TYP_NUMBER_RANGE + jmp to_none + +; w24[Y..Y+2] = the constant at X as a signed 24-bit number. +to24: lda tv+V_VAL,x + sta w24,y + lda tv+V_VAL+1,x + sta w24+1,y + lda #$00 + sta w24+2,y + lda tv+V_NEG,x + beq @done + sec + lda #$00 + sbc w24,y + sta w24,y + lda #$00 + sbc w24+1,y + sta w24+1,y + lda #$00 + sbc w24+2,y + sta w24+2,y +@done: rts + +; ------------------------------------------------------------- spelling +; Error exits: spell the operands into the arguments, return A = code. +err_sc1: + pha + jsr ty_arg_sc1 + pla + rts +err_tt1_sc2: + pha + jsr ty_arg_tt1 + jsr ty_arg_sc2 + pla + rts +err_sc1_tt2: + pha + jsr ty_arg_sc1 + jsr ty_arg_tt2 + pla + rts + +ty_arg_sc1: + ldx #$00 + ldy #$00 + beq spell_from +ty_arg_sc2: + ldx #$00 + ldy #$01 + bne spell_from +ty_arg_tt1: + ldx #TV_SIZE + ldy #$00 + beq spell_from +ty_arg_tt2: + ldx #TV_SIZE + ldy #$01 +spell_from: + sty sp_which + ldy #$00 +: lda tv,x + sta sp_base,y + inx + iny + cpy #TV_SIZE + bne :- + jmp spell + +; Argument 1 = the spelling of name ID A. +ty_arg_name1: + sta sp_name + lda #TY_REF + ldy #$00 + beq simple +; Argument 2 = the decimal number A. +ty_arg_num2: + sta sp_val + lda #TY_CONST + ldy #$01 +simple: sta sp_base + sty sp_which + lda #$00 + sta sp_len + sta sp_len+1 + sta sp_val+1 + sta sp_neg + ; fall through + +; Spell sp_* into argument buffer sp_which and point the argument at it: +; BYTE, WORD, ... the class/interface name, NULL, STRING, a constant's +; value, NO VALUE; an array adds [length]. Clipped to ARG_MAX bytes. +spell: lda #$00 + sta sp_n + lda sp_base + cmp #TY_REF + beq @ref + cmp #TY_CONST + beq @const + tax + lda word_offs,x + tax +@word: lda words,x + beq @array + jsr sp_put + inx + bne @word +@ref: lda sp_name + beq @array + jsr name_get + ldx #$00 +: cpx name_out_len + beq @array + lda name_out,x + jsr sp_put + inx + bne :- +@const: lda sp_neg + beq :+ + lda #'-' + jsr sp_put +: lda sp_val + ldy sp_val+1 + jsr sp_dec +@array: lda sp_len + ora sp_len+1 + beq @point + lda #'[' + jsr sp_put + lda sp_len + ldy sp_len+1 + jsr sp_dec + lda #']' + jsr sp_put +@point: lda sp_which + bne @two + lda #arg1_buf + sta diag_arg1+1 + lda sp_n + sta diag_arg1_len + rts +@two: lda #arg2_buf + sta diag_arg2+1 + lda sp_n + sta diag_arg2_len + rts + +; Append A to the current argument buffer. Preserves X and Y. +sp_put: stx sp_x + sty sp_y + ldy sp_n + cpy #ARG_MAX + bcs @out + ldx sp_which + bne @two + sta arg1_buf,y + jmp @inc +@two: sta arg2_buf,y +@inc: inc sp_n +@out: ldx sp_x + ldy sp_y + rts + +; Append the unsigned 16-bit number A (low) / Y (high) in decimal. +sp_dec: sta sp_num + sty sp_num+1 + lda #$00 + sta sp_started + ldx #4 ; 10000, 1000, 100, 10, 1 +@digit: lda #'0' + sta sp_digit +@sub: sec + lda sp_num + sbc pow_lo,x + tay + lda sp_num+1 + sbc pow_hi,x + bcc @emit + sta sp_num+1 + sty sp_num + inc sp_digit + bne @sub +@emit: lda sp_digit + cpx #$00 ; always the units digit + beq @out + cmp #'0' + bne @out + ldy sp_started + beq @skip +@out: sta sp_started + jsr sp_put +@skip: dex + bpl @digit + rts + +.segment "RODATA" +group_allow: ; G_LOGIC, G_ARITH, G_ORDER, G_EQUAL + .byte CL_BOOL, CL_NUM, CL_NUM | CL_CHAR + .byte CL_NUM | CL_CHAR | CL_BOOL | CL_REF +base_class: ; by TY_* + .byte CL_BAD, CL_BOOL, CL_NUM, CL_CHAR, CL_NUM, CL_NUM, CL_REF + .byte CL_REF, CL_BAD, CL_NUM, CL_BAD +word_offs: ; by TY_*: offset into words + .byte w_none - words, w_boolean - words, w_byte - words + .byte w_char - words, w_integer - words, w_word - words + .byte w_none - words, w_null - words, w_string - words + .byte w_none - words, w_void - words +words: +w_none: .byte "?", 0 +w_boolean: .byte "BOOLEAN", 0 +w_byte: .byte "BYTE", 0 +w_char: .byte "CHAR", 0 +w_integer: .byte "INTEGER", 0 +w_word: .byte "WORD", 0 +w_null: .byte "NULL", 0 +w_string: .byte "STRING", 0 +w_void: .byte "NO VALUE", 0 +pow_lo: .byte <1, <10, <100, <1000, <10000 +pow_hi: .byte >1, >10, >100, >1000, >10000 + +.segment "BSS" +tv: ; sc, then tt (TV_SIZE bytes each) +sc_base: .res 1 +sc_name: .res 1 +sc_len: .res 2 +sc_val: .res 2 +sc_neg: .res 1 +tt_base: .res 1 +tt_name: .res 1 +tt_len: .res 2 +tt_val: .res 2 +tt_neg: .res 1 +sp_base: .res 1 ; the type being spelled +sp_name: .res 1 +sp_len: .res 2 +sp_val: .res 2 +sp_neg: .res 1 +sp_which: .res 1 ; 0 = argument 1, 1 = argument 2 +sp_n: .res 1 +sp_x: .res 1 +sp_y: .res 1 +sp_num: .res 2 +sp_digit: .res 1 +sp_started: .res 1 +arg1_buf: .res ARG_MAX +arg2_buf: .res ARG_MAX +op: .res 1 +group: .res 1 +allow: .res 1 +lcls: .res 1 +rcls: .res 1 +cv_class: .res 1 +cv_iface: .res 1 +cv_left: .res 1 +w24: .res 6 +mc: .res 2 +mr: .res 2 +prod: .res 4 diff --git a/newLang/tests/lexer/reference-lexer.ps1 b/newLang/tests/lexer/reference-lexer.ps1 index 425c3d8..f869505 100644 --- a/newLang/tests/lexer/reference-lexer.ps1 +++ b/newLang/tests/lexer/reference-lexer.ps1 @@ -52,7 +52,7 @@ function Format-PetsciiText([byte[]]$bytes) { if ($b -ge 0x41 -and $b -le 0x5A) { [void]$sb.Append([char]($b + 0x20)) } # a-z elseif ($b -ge 0xC1 -and $b -le 0xDA) { [void]$sb.Append([char]($b - 0x80)) } # A-Z elseif ($b -ge 0x20 -and $b -le 0x3F -and $b -ne 0x22) { [void]$sb.Append([char]$b) } - else { [void]$sb.Append(("{{{0:X2}}}" -f $b)) } + else { [void]$sb.Append(('{' + $b.ToString("X2") + '}')) } } return '"' + $sb.ToString() + '"' } diff --git a/newLang/tests/parser/forms.n8 b/newLang/tests/parser/forms.n8 index f833fa7..e30a6b7 100644 --- a/newLang/tests/parser/forms.n8 +++ b/newLang/tests/parser/forms.n8 @@ -21,15 +21,16 @@ class Counter implements Source public method fill(n as word) var i as word + var k as integer for i = 0 to 3 step 1 seen[i] = self.next() end for - for i = 3 to 0 step -1 - if seen[i] = 0 + for k = 3 to 0 step -1 + if seen[k] = 0 return else - if i > n and not (i = 2) - seen[i] = seen[i - 1] + if k > 1 and not (k = 2) + seen[k] = seen[k - 1] end if end if end for diff --git a/newLang/tests/parser/parser-test.s b/newLang/tests/parser/parser-test.s index 4dc913b..676d04b 100644 --- a/newLang/tests/parser/parser-test.s +++ b/newLang/tests/parser/parser-test.s @@ -1,4 +1,4 @@ -; E03-S01..E03-S03: parser test in x128. +; E03-S01..E03-S04: parser and type-checker 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 @@ -419,7 +419,13 @@ fixtures: FIXTURE "PREC.N8" FIXTURE "FORMS.N8" FIXTURE "SYNTAX.N8" -FIXTURE_COUNT = 31 + FIXTURE "TYPESOK.N8" + FIXTURE "CONVERT.N8" + FIXTURE "REFS.N8" + FIXTURE "OPS.N8" + FIXTURE "USES.N8" + FIXTURE "CALLS.N8" +FIXTURE_COUNT = 37 TRACE_INDEX = 28 ; PREC.N8 messages: diff --git a/newLang/tests/parser/recover.expect b/newLang/tests/parser/recover.expect index 426c486..ffb2d6b 100644 --- a/newLang/tests/parser/recover.expect +++ b/newLang/tests/parser/recover.expect @@ -1,6 +1,10 @@ -// five independent errors; each line after an error still parses +// seven independent errors; each line after an error still parses. The +// duplicate x is a word, so both conditions are type errors too; they are +// checked when their blocks close, after the missing `end if` at line 16. SYN_EXPECTED_IDENTIFIER 1 10 SYN_EXPECTED_INTERFACE_NAME 5 21 SYN_EXPECTED_IDENTIFIER 7 22 SEM_DUPLICATE_NAME 13 7 SYN_EXPECTED_END_IF 16 7 +TYP_CONDITION_NOT_BOOLEAN 14 9 +TYP_CONDITION_NOT_BOOLEAN 15 8 diff --git a/newLang/tests/parser/syntax.expect b/newLang/tests/parser/syntax.expect index 8505d28..40631dc 100644 --- a/newLang/tests/parser/syntax.expect +++ b/newLang/tests/parser/syntax.expect @@ -16,5 +16,7 @@ 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 +// pass 2: name errors are still found after all of the above, and a program +// cannot return a value (E151) SEM_UNKNOWN_NAME 26 3 +SEM_LIFECYCLE_RETURN 27 10 diff --git a/newLang/tests/parser/unknown.expect b/newLang/tests/parser/unknown.expect index b58684b..0e03837 100644 --- a/newLang/tests/parser/unknown.expect +++ b/newLang/tests/parser/unknown.expect @@ -1,5 +1,7 @@ -// pass 2: Printable and Worker are forward references; the rest are unknown +// pass 2: Printable and Worker are forward references; the rest are unknown. +// run() declares a result but its body has no return (E150). SEM_UNKNOWN_NAME 1 25 SEM_UNKNOWN_NAME 2 11 SEM_UNKNOWN_NAME 3 28 +SEM_MISSING_RETURN 4 3 SEM_UNKNOWN_NAME 13 12 diff --git a/newLang/tests/semantic/calls.expect b/newLang/tests/semantic/calls.expect new file mode 100644 index 0000000..c06a9a0 --- /dev/null +++ b/newLang/tests/semantic/calls.expect @@ -0,0 +1,18 @@ +// calls, new, and returns. Missing returns are reported at `end method` +// SYMBOL line column message (message compared case-insensitively) +SEM_MISSING_RETURN 20 3 MISSING RETURN VALUE +SEM_MISSING_RETURN 26 3 MISSING RETURN VALUE +SEM_MISSING_RETURN 32 7 MISSING RETURN VALUE +SEM_LIFECYCLE_RETURN 37 12 NONE CANNOT RETURN A VALUE +TYP_MISMATCH 41 12 BOOLEAN EXPECTED; WORD FOUND +SEM_ARG_COUNT 52 16 BOX TAKES 2 ARGUMENTS +SEM_ARG_COUNT 53 21 BOX TAKES 2 ARGUMENTS +TYP_MISMATCH 54 18 BYTE EXPECTED; BOOLEAN FOUND +TYP_NUMBER_DOES_NOT_FIT 55 15 -1 DOES NOT FIT WORD +SEM_ARG_COUNT 56 15 SCALE TAKES 1 ARGUMENTS +SEM_ARG_COUNT 57 18 SCALE TAKES 1 ARGUMENTS +SEM_ARG_COUNT 58 14 AREA TAKES 0 ARGUMENTS +TYP_NOT_CLASS 59 11 SHAPE IS NOT A CLASS +SEM_ARG_COUNT 60 16 BARE TAKES 0 ARGUMENTS +TYP_MISMATCH 60 7 SHAPE EXPECTED; BARE FOUND +SEM_LIFECYCLE_RETURN 61 10 MAIN CANNOT RETURN A VALUE diff --git a/newLang/tests/semantic/calls.n8 b/newLang/tests/semantic/calls.n8 new file mode 100644 index 0000000..00d53c3 --- /dev/null +++ b/newLang/tests/semantic/calls.n8 @@ -0,0 +1,63 @@ +interface Shape + method area() as word +end interface + +class Box implements Shape + private word side + + public method init(s as word, t as byte) + side = s + t + end method + + public method area() as word + return side + end method + + public method scale(f as byte) as word + if f = 0 + return 0 + end if + end method + + public method pick(f as boolean) as word + while f + return 1 + end while + end method + + public method both(f as boolean) as word + if f + return 1 + else + return + end if + end method + + public method none() + return 5 + end method + + public method wrong() as boolean + return side + end method +end class + +class Bare +end class + +program Main + var b as Box + var s as Shape + var w as word + b = new Box(1) + b = new Box(1, 2, 3, 4) + b = new Box(1, true) + b = new Box(-1, 2) + w = b.scale() + w = b.scale(1, 2) + w = s.area(5) + s = new Shape() + s = new Bare(1) + return 1 + return +end program diff --git a/newLang/tests/semantic/convert.expect b/newLang/tests/semantic/convert.expect new file mode 100644 index 0000000..5d25835 --- /dev/null +++ b/newLang/tests/semantic/convert.expect @@ -0,0 +1,21 @@ +// every illegal primitive conversion and constant: one per line +// SYMBOL line column message (message compared case-insensitively) +TYP_MISMATCH 7 7 INTEGER EXPECTED; WORD FOUND +TYP_MISMATCH 8 7 WORD EXPECTED; INTEGER FOUND +TYP_MISMATCH 9 7 BYTE EXPECTED; WORD FOUND +TYP_MISMATCH 10 7 BYTE EXPECTED; INTEGER FOUND +TYP_MISMATCH 11 7 WORD EXPECTED; BOOLEAN FOUND +TYP_MISMATCH 12 8 BOOLEAN EXPECTED; BYTE FOUND +TYP_MISMATCH 13 7 BYTE EXPECTED; CHAR FOUND +TYP_MISMATCH 14 7 CHAR EXPECTED; BYTE FOUND +TYP_MISMATCH 15 7 CHAR EXPECTED; 65 FOUND +TYP_NUMBER_DOES_NOT_FIT 16 7 256 DOES NOT FIT BYTE +TYP_NUMBER_DOES_NOT_FIT 17 7 -1 DOES NOT FIT WORD +TYP_NUMBER_DOES_NOT_FIT 18 7 32768 DOES NOT FIT INTEGER +TYP_NUMBER_RANGE 19 7 NUMBER OUT OF RANGE +TYP_MISMATCH 20 7 WORD EXPECTED; STRING FOUND +TYP_NUMBER_DOES_NOT_FIT 21 7 300 DOES NOT FIT BYTE +TYP_NUMBER_RANGE 22 11 NUMBER OUT OF RANGE +TYP_DIVIDE_BY_ZERO 23 9 DIVISION BY ZERO +TYP_NUMBER_RANGE 24 9 NUMBER OUT OF RANGE +TYP_NUMBER_DOES_NOT_FIT 25 7 -1 DOES NOT FIT WORD diff --git a/newLang/tests/semantic/convert.n8 b/newLang/tests/semantic/convert.n8 new file mode 100644 index 0000000..fa214fb --- /dev/null +++ b/newLang/tests/semantic/convert.n8 @@ -0,0 +1,26 @@ +program Main + var x as byte + var w as word + var i as integer + var c as char + var ok as boolean + i = w + w = i + x = w + x = i + w = ok + ok = x + x = c + c = x + c = 65 + x = 256 + w = -1 + i = 32768 + i = -32769 + w = "text" + x = 200 + 100 + w = 300 * 300 + w = 1 / 0 + i = 0 - 32769 + w = 1 - 2 +end program diff --git a/newLang/tests/semantic/ops.expect b/newLang/tests/semantic/ops.expect new file mode 100644 index 0000000..d1603b6 --- /dev/null +++ b/newLang/tests/semantic/ops.expect @@ -0,0 +1,19 @@ +// operator operands, reported at the operator; line 27: the error in the +// group is not repeated for the outer * +// SYMBOL line column message (message compared case-insensitively) +TYP_BAD_OPERAND 13 9 + NOT ALLOWED ON BOOLEAN +TYP_BAD_OPERAND 14 11 + NOT ALLOWED ON BOOLEAN +TYP_BAD_OPERAND 15 10 AND NOT ALLOWED ON WORD +TYP_BAD_OPERAND 16 8 NOT NOT ALLOWED ON WORD +TYP_BAD_OPERAND 17 7 - NOT ALLOWED ON WORD +TYP_OPERANDS_DIFFER 18 9 WORD AND INTEGER DO NOT MIX +TYP_OPERANDS_DIFFER 19 10 CHAR AND BYTE DO NOT MIX +TYP_BAD_OPERAND 20 11 < NOT ALLOWED ON BOOLEAN +TYP_BAD_OPERAND 21 11 + NOT ALLOWED ON STRING +TYP_BAD_OPERAND 22 9 + NOT ALLOWED ON BOX +TYP_OPERANDS_DIFFER 23 10 BOX AND 5 DO NOT MIX +TYP_BAD_OPERAND 24 11 + NOT ALLOWED ON BYTE[3] +TYP_NUMBER_DOES_NOT_FIT 25 9 300 DOES NOT FIT BYTE +TYP_OPERANDS_DIFFER 26 10 5 AND CHAR DO NOT MIX +TYP_BAD_OPERAND 27 10 + NOT ALLOWED ON BOOLEAN +TYP_BAD_OPERAND 28 8 - NOT ALLOWED ON BOOLEAN diff --git a/newLang/tests/semantic/ops.n8 b/newLang/tests/semantic/ops.n8 new file mode 100644 index 0000000..8f70d72 --- /dev/null +++ b/newLang/tests/semantic/ops.n8 @@ -0,0 +1,29 @@ +class Box + public word size +end class + +program Main + var x as byte + var w as word + var i as integer + var c as char + var ok as boolean + var b as Box + var arr as byte[3] + w = w + ok + ok = ok + ok + ok = w and ok + ok = not w + w = -w + i = w + i + ok = c < x + ok = ok < ok + w = "a" + 1 + w = b + 1 + ok = b = 5 + w = arr + 1 + x = x + 300 + ok = 5 = c + w = (w + ok) * 2 + ok = - ok +end program diff --git a/newLang/tests/semantic/refs.expect b/newLang/tests/semantic/refs.expect new file mode 100644 index 0000000..d69558d --- /dev/null +++ b/newLang/tests/semantic/refs.expect @@ -0,0 +1,16 @@ +// references: only null, the same type, or a class to an interface it +// implements convert; arrays are not values; identity needs related types +// SYMBOL line column message (message compared case-insensitively) +TYP_MISMATCH 29 8 BOOLEAN EXPECTED; NULL FOUND +TYP_MISMATCH 30 7 BOX EXPECTED; SHAPE FOUND +TYP_MISMATCH 31 7 NAMED EXPECTED; SHAPE FOUND +TYP_MISMATCH 32 7 NAMED EXPECTED; BOX FOUND +TYP_MISMATCH 33 7 CAN EXPECTED; BOX FOUND +TYP_MISMATCH 34 7 SHAPE EXPECTED; CAN FOUND +TYP_MISMATCH 35 9 WORD[4] EXPECTED; 5 FOUND +TYP_ARRAY_NEEDS_INDEX 36 7 WORD[4] NEEDS AN INDEX +TYP_ARRAY_NEEDS_INDEX 37 9 WORD[4] NEEDS AN INDEX +TYP_MISMATCH 38 7 WORD EXPECTED; NO VALUE FOUND +TYP_OPERANDS_DIFFER 42 10 BOX AND CAN DO NOT MIX +TYP_OPERANDS_DIFFER 43 10 SHAPE AND NAMED DO NOT MIX +TYP_OPERANDS_DIFFER 44 10 NAMED AND BOX DO NOT MIX diff --git a/newLang/tests/semantic/refs.n8 b/newLang/tests/semantic/refs.n8 new file mode 100644 index 0000000..75b6f98 --- /dev/null +++ b/newLang/tests/semantic/refs.n8 @@ -0,0 +1,45 @@ +interface Shape + method area() as word +end interface + +interface Named + method id() as byte +end interface + +class Box implements Shape + public method area() as word + return 1 + end method + public method reset() + end method +end class + +class Can +end class + +program Main + var w as word + var ok as boolean + var b as Box + var k as Can + var s as Shape + var n as Named + var arr as word[4] + var arr2 as word[4] + ok = null + b = s + n = s + n = b + k = b + s = k + arr = 5 + w = arr + arr = arr2 + w = b.reset() + b = null + s = b + n = null + ok = b = k + ok = s = n + ok = n = b +end program diff --git a/newLang/tests/semantic/types_ok.n8 b/newLang/tests/semantic/types_ok.n8 new file mode 100644 index 0000000..cc0e014 --- /dev/null +++ b/newLang/tests/semantic/types_ok.n8 @@ -0,0 +1,115 @@ +interface Shape + method area() as word +end interface + +interface Named + method id() as byte +end interface + +class Box implements Shape, Named + private word side + private byte[3] tags + public Box[2] kids + + public method init(s as byte) + var w as word + w = s + self.side = w + s + tags[0] = 255 + tags[2] = s + 1 + side = (self).side + end method + + public method area() as word + return self.side * side + end method + + public method id() as byte + return tags[1] + end method + + public method same(other as Shape) as boolean + var me as Shape + me = self + return me = other or self = other and not (other = null) + end method + + public method sign(n as integer) as char + if n < 0 + return '-' + else + if n = 0 + return '0' + end if + end if + while n > 100 + n = n / 2 + end while + return '+' + end method + + public method kid(i as integer) as Box + return kids[i] + end method +end class + +class Plain +end class + +program Main + var b as Box + var s as Shape + var t as Named + var p as Plain + var x as byte + var w as word + var i as integer + var c as char + var ok as boolean + b = new Box(3) + p = new Plain() + s = b + t = b + s = null + s = b.kid(0) + ok = s = b or t <> null and b <> null + x = 255 + w = 65535 + i = -32768 + i = 32767 + w = $FFFF + x = %1010 + w = (1 + 2) * 3 + i = -(5 - 10) + w = 300 * 200 + w = 100 / 3 + i = -1 - 32767 + x = x + 1 + w = x + w + i = x + i + i = -x + w = w * 2 + 1000 + ok = c < 'z' and c = c and ok = true + ok = w <= 10 or i >= -1 + for x = 0 to 9 + print x + end for + for i = 3 to -3 step -1 + print i + end for + for w = x to 1000 step x + print w + end for + w = b.kids[1].area() + s.area() + x = t.id() + c = b.sign(i) + ok = b.same(b) + print "done" + print c + print ok + print -5 + print 60000 / 2 + delete b + delete t + delete null +end program diff --git a/newLang/tests/semantic/uses.expect b/newLang/tests/semantic/uses.expect new file mode 100644 index 0000000..9419eae --- /dev/null +++ b/newLang/tests/semantic/uses.expect @@ -0,0 +1,21 @@ +// conditions, members, indexing, print, delete, for, and array signatures +// (pass 1); lines 35-36 use the bad signatures without cascades +// SYMBOL line column message (message compared case-insensitively) +TYP_ARRAY_LENGTH 3 16 ARRAY LENGTH MUST NOT BE ZERO +TYP_ARRAY_SIGNATURE 4 27 ARRAYS CANNOT BE PARAMETERS OR RESULTS +TYP_ARRAY_SIGNATURE 6 26 ARRAYS CANNOT BE PARAMETERS OR RESULTS +TYP_CONDITION_NOT_BOOLEAN 16 6 CONDITION MUST BE BOOLEAN +TYP_CONDITION_NOT_BOOLEAN 18 9 CONDITION MUST BE BOOLEAN +TYP_NO_MEMBERS 20 9 5 HAS NO MEMBERS +TYP_NO_MEMBERS 21 9 BYTE HAS NO MEMBERS +TYP_NO_MEMBERS 22 11 BYTE[3] HAS NO MEMBERS +TYP_NOT_ARRAY 23 8 WORD IS NOT AN ARRAY +TYP_MISMATCH 24 11 WORD EXPECTED; BOOLEAN FOUND +TYP_INDEX_RANGE 25 11 INDEX OUT OF RANGE +TYP_NUMBER_DOES_NOT_FIT 26 11 -1 DOES NOT FIT WORD +TYP_CANNOT_PRINT 27 9 BOX CANNOT BE PRINTED +TYP_CANNOT_PRINT 28 9 BYTE[3] CANNOT BE PRINTED +TYP_CANNOT_PRINT 29 9 NULL CANNOT BE PRINTED +TYP_CANNOT_DELETE 30 10 WORD CANNOT BE DELETED +TYP_BAD_OPERAND 31 7 FOR NOT ALLOWED ON BOOLEAN +TYP_NUMBER_DOES_NOT_FIT 33 16 300 DOES NOT FIT BYTE diff --git a/newLang/tests/semantic/uses.n8 b/newLang/tests/semantic/uses.n8 new file mode 100644 index 0000000..f1f5016 --- /dev/null +++ b/newLang/tests/semantic/uses.n8 @@ -0,0 +1,37 @@ +class Box + public word size + private byte[0] none + public method fill(a as byte[4]) + end method + public method get() as word[2] + end method +end class + +program Main + var x as byte + var w as word + var ok as boolean + var b as Box + var arr as byte[3] + if w + end if + while 1 + end while + x = 5.size + x = x.size + x = arr.size + x = w[0] + x = arr[ok] + x = arr[3] + x = arr[-1] + print b + print arr + print null + delete w + for ok = 1 to 2 + end for + for x = 0 to 300 + end for + b.fill(arr) + w = b.get()[0] +end program