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 <noreply@codebuff.com>
main
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | | |||
| @@ -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. | |||
| @@ -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" } | |||
| @@ -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")) { | |||
| @@ -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 | |||
| } | |||
| @@ -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" | |||
| @@ -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 | |||
| @@ -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 <op> 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 | |||
| lda #>arg1_buf | |||
| sta diag_arg1+1 | |||
| lda sp_n | |||
| sta diag_arg1_len | |||
| rts | |||
| @two: lda #<arg2_buf | |||
| sta diag_arg2 | |||
| 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 | |||
| @@ -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() + '"' | |||
| } | |||
| @@ -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 | |||
| @@ -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: | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
Powered by TurnKey Linux.