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