diff --git a/CLAUDE.md b/CLAUDE.md index 0447fb0..61846ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ scripting engine for game authors. | [`MAINTENANCE.md`](MAINTENANCE.md) | Everything above. Start here | | [`TODO.md`](TODO.md) | Outstanding defects, with file, line and consequence. §0.1 first — it retires the byte-for-byte fidelity constraint several later sections were written on | | [`README.md`](README.md) | What the project is and why, for somebody who has not seen it | -| [`docs/`](docs/README.md) | The language itself: fourteen chapters, verb and function reference. [Chapter 14](docs/14-architecture.md) is the interpreter's architecture — the step loop, the pools, the two kinds of error, and how to debug it | +| [`docs/`](docs/README.md) | The language itself: fourteen chapters, verb and function reference. [Chapter 14](docs/14-architecture.md) is the interpreter's architecture — the step loop, the pools, the two kinds of error, and how to debug it. [Chapter 15](docs/15-error-codes.md) is the error-code appendix | | `deps/libakerror/AGENTS.md` | The `ATTEMPT`/`CLEANUP`/`PROCESS`/`HANDLE`/`FINISH` protocol, authoritatively | | `deps/libakerror/UPGRADING.md` | 1.0.0's status registry. Required before writing an error code | | `deps//AGENTS.md` | Per-repo rules. Read the relevant one **before editing a submodule** | diff --git a/README.md b/README.md index eb7238e..4c24967 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ version are catalogued in [`TODO.md`](TODO.md) and summarised for a BASIC progra | | | |---|---| -| [`docs/`](docs/README.md) | The guide: fourteen chapters, the language then each hardware area then a reference section for every verb and function, and [Chapter 14](docs/14-architecture.md) on the interpreter's own architecture | +| [`docs/`](docs/README.md) | The guide: fourteen chapters, the language then each hardware area then a reference section for every verb and function, [Chapter 14](docs/14-architecture.md) on the interpreter's own architecture, and [Chapter 15](docs/15-error-codes.md) listing every error code | | [`MAINTENANCE.md`](MAINTENANCE.md) | For contributors and maintainers: the documentation-example harness, the three test lists, mutation testing, error-code allocation, style | | [`TODO.md`](TODO.md) | Outstanding defects, with file, line and consequence | | [`tests/reference/README.md`](tests/reference/README.md) | Where the golden corpus came from, and the rule for changing it | diff --git a/TODO.md b/TODO.md index 1a4ecba..37ec7e6 100644 --- a/TODO.md +++ b/TODO.md @@ -1502,6 +1502,27 @@ the reference's semantics reproduced faithfully; the third is the port's own. listings read it there. Same known-failing test as item 19; the fix is a scoping decision about where a loop counter is created, not an ordering one. +### Found while writing the error-code appendix + +21. **A dependency's status code reaches `ER#` where the interpreter has one of its own.** + `VAL("XYZ")` reports `AKERR_VALUE` — 144 on this machine — rather than + `AKBASIC_ERR_VALUE` (517), because `akbasic_fn_val()` (`src/runtime_functions.c:261`) + hands the string to `aksl_atof()` and returns whatever comes back. Both register the + name `Value Error`, so `ERR(ER#)` cannot tell them apart and only the number can. + + **The consequence is that the number is not portable.** `libakerror`'s codes are offsets + from the platform's largest `errno`, so a program testing `IF ER# = 144` is right on one + machine and wrong on the next, where 517 — which is absolute — is what it should have + been able to test. `docs/15-error-codes.md` documents the behaviour as it stands and + tells a program to compare the code rather than the text. + + Not fixed here because it is a decision about a boundary rather than a patch: every + place the interpreter calls into `libakstdlib` on a *program's* behalf could translate + the failure into the akbasic band, and the list of those places wants collecting before + the first one is changed. The disk verbs are the interesting counter-case — `ENOENT` out + of `DOPEN` is genuinely more useful to a program than a flat 519 — so this is not + "translate everything", which is exactly why it needs deciding rather than doing. + ## 7. Filing gaps against `libakgl` When phase 7 or phase 8 needs something `libakgl` does not have, **stop and file it** in @@ -1824,9 +1845,16 @@ What remains, in priority order: read the size. 320x200 is now the fallback for a backend that will not answer. Deviation 18 in §5 has the whole of it, including the off-by-one it fixed on the way. -11. Error codes are undefined for the user +11. ~~**Error codes are undefined for the user.**~~ **Done** — `docs/15-error-codes.md`, an + appendix covering the four error classes the interpreter prints, the eight codes it owns + with what raises each, and the codes that reach `ER#` from `errno`, `libakerror` and + `libakgl` underneath it. The table is not asserted: a program in the chapter trips seven + of the eight and prints what it got, and `docs_examples` byte-compares the result. -In the documentation, include a complete table of all possible values of ER# and EL# in the documentation in an appendix. + **It found three things rather than documenting eight.** A parse error under an armed + `TRAP` vanished entirely and a trapped parse error reported `ER# 0` — both fixed, both + recorded above. And `VAL` reports a dependency's status code where the interpreter has + one of its own, which is §6 item 21 and is filed rather than fixed. 12. Screenshots for graphics operations diff --git a/docs/04-control-flow.md b/docs/04-control-flow.md index a21c6c3..dd127e2 100644 --- a/docs/04-control-flow.md +++ b/docs/04-control-flow.md @@ -231,6 +231,11 @@ Two variables are set when the trap fires: **`ER#`** is the error code and **`EL the line it happened on. `ERR(ER#)` gives the message text. On a C128 these are called `ER` and `EL` with no suffix; this dialect has no bare variable names. +**[Chapter 15](15-error-codes.md) is the complete list** of what `ER#` can hold and +what each code means. The short version is that the interpreter's own codes are 512 to +519 and are the only numbers worth comparing against; two codes render as the same +`ERR()` text, so compare the number rather than the text. + `RESUME` comes in three forms: | Form | Where it goes | diff --git a/docs/12-function-reference.md b/docs/12-function-reference.md index 075aee7..9e34937 100644 --- a/docs/12-function-reference.md +++ b/docs/12-function-reference.md @@ -54,4 +54,5 @@ this dialect has no variable name without a type suffix: `ER#` carries this interpreter's error code, not a Commodore error number. There is no correspondence to reproduce — the errors raised here are not the errors a 1985 ROM -raised — so print `ERR(ER#)` rather than the number. +raised — so print `ERR(ER#)` rather than the number. **[Chapter 15](15-error-codes.md) +lists every value it can hold.** diff --git a/docs/13-differences.md b/docs/13-differences.md index 1e10af9..0e4cdd3 100644 --- a/docs/13-differences.md +++ b/docs/13-differences.md @@ -85,7 +85,7 @@ program text. `ER` and `EL` are **`ER#` and `EL#`**, ordinary global variables. `ER#` holds this interpreter's error code, which bears no relation to a Commodore error number. Print -`ERR(ER#)` for the text. +`ERR(ER#)` for the text, and see [Chapter 15](15-error-codes.md) for the whole list. ## Graphics diff --git a/docs/15-error-codes.md b/docs/15-error-codes.md new file mode 100644 index 0000000..57d3339 --- /dev/null +++ b/docs/15-error-codes.md @@ -0,0 +1,169 @@ +# 15. Appendix: error codes + +Two different things get called "the error" in this interpreter, and a program author +meets both. **The report** is the line printed when something goes wrong and nothing +catches it. **The code** is the number `ER#` holds when a `TRAP` handler catches it. +This appendix is the complete list of each. + +Chapter 4 is where error trapping is explained; this is the reference you come back to +once you already know what a `TRAP` is. + +## The report + +An error nothing traps is printed as one line and stops the run: + +```basic +10 PRINT LEFT(1, 2) +20 PRINT "NEVER REACHED" +``` + +```output +? 10 : RUNTIME ERROR LEFT expected a string + +``` + +The shape is `? line : CLASS message`. There are exactly four classes, and which one +you get says *when* the interpreter gave up rather than what was wrong: + +| Class | Raised when | +|---|---| +| `PARSE ERROR` | The line could not be turned into a statement. Nothing on it ran. | +| `SYNTAX ERROR` | A statement was well-formed but not a legal one here. | +| `RUNTIME ERROR` | A statement ran and failed. The commonest by a wide margin. | +| `IO ERROR` | Reading the program itself failed — not a file a program opened, which is a `RUNTIME ERROR` like any other. | + +The line number is where the interpreter was, which for a `PARSE ERROR` is the line +that would not parse and for a `RUNTIME ERROR` is the line that failed. + +## The code + +`ER#` is the code, `EL#` is the line, and `ERR(n)` is the message text for a code. +They are ordinary global variables here rather than the bare `ER` and `EL` a C128 +exposes; Chapter 13 says why. + +Every one of them is set for a trapped error, and **a parse error is trappable too**: + +```basic +10 TRAP 200 +20 DIM Q#(2) +30 SCALE +40 PRINT LEFT(1, 2) +50 GOTO NOWHERE +60 PRINT Q#(9) +70 PRINT 1 / 0 +80 RESUME +90 FILTER 1, 1 +100 END +200 PRINT "LINE " + EL# + ": " + ER# + " " + ERR(ER#) +210 RESUME NEXT +``` + +```output +LINE 30: 512 Syntax Error +LINE 40: 513 Type Error +LINE 50: 514 Undefined Reference +LINE 60: 515 Out Of Bounds +LINE 70: 517 Value Error +LINE 80: 518 State Error +LINE 90: 519 Device Error +``` + +That program is run by the test suite, so the numbers in the table below are checked +rather than claimed. Line 90 is `FILTER` rather than a drawing verb because `FILTER` +refuses in *every* build — a `DRAW` would only give 519 in a build with no window, and +the point here is the code rather than the verb. + +## The interpreter's own codes + +These eight are akbasic's, they are absolute, and they do not move. The interpreter +owns the block 512 to 767 and uses the first eight of it. + +| Code | `ERR()` text | What raises it | +|---|---|---| +| 512 | `Syntax Error` | A line that will not parse, a verb given the wrong shape of arguments, a keyword where a value belongs. The commonest code and the only one that can arrive before a statement runs. | +| 513 | `Type Error` | A string where a number belongs or the reverse — `LEFT(1, 2)`, `A$ = 1 + "X"` used as a number, a non-integer array subscript. | +| 514 | `Undefined Reference` | A branch to a label or line that does not exist, or a call to a function that was never defined. | +| 515 | `Out Of Bounds` | An array subscript past the end, a colour index outside 1 to 16, a sprite number outside 1 to 8, a `RGR` or `RSPRITE` field number that is not a field. | +| 516 | `Environment Error` | The scope pool is exhausted — 32 nested `GOSUB`, `FOR` or `DEF` bodies. **See below: this is the one code you cannot usefully trap.** | +| 517 | `Value Error` | A number that is the wrong number: division by zero, a negative `SCALE` maximum, a string longer than 255 characters, a `GSHAPE` handed a string that did not come from `SSHAPE`. | +| 518 | `State Error` | A verb run outside the structure it needs — `RESUME` with no handler running, `NEXT` with no `FOR`, `LOOP` with no `DO`. | +| 519 | `Device Error` | A verb needs a graphics, audio, input or sprite device the runtime has not been given, or one that cannot do what was asked. Everything in Chapters 6, 7 and 8 refuses with this in a build with no window. `FILTER` and `SYS` refuse with it always. | + +**516 is not usefully trappable and that is inherent, not a defect.** Entering a +handler takes a scope, and the pool being empty is exactly what raised the error — so +a program that exhausts it gets the report rather than the handler. Runaway recursion +is the way to reach it, and a handler that could not run is the right answer to it. + +## Codes from underneath + +`ER#` carries whatever code the failure was raised with, and not every failure comes +from the interpreter. Two other kinds reach a program. + +**Errors from the C library, as `errno`.** The disk verbs open, read and delete real +files, and a real file refuses for the reasons files refuse: + +```basic +10 TRAP 100 +20 DOPEN 1, "nosuchfile.bas" +30 END +100 PRINT "" + ER# + " " + ERR(ER#) +110 END +``` + +```output +2 No such file or directory +``` + +2 is `ENOENT`. Any `errno` your platform defines can arrive this way, with the text +your C library gives it — so this is the one part of the table that is your machine's +rather than this document's. + +**Errors from the libraries akbasic is built on.** `libakerror` names its own codes +just above the `errno` range, and they surface when something fails below the language +rather than in it — `SCRATCH` on a file that will not delete gives its +`Input Output Error`, and `VAL` of text that is not a number gives its `Value Error` +rather than the interpreter's 517, because the conversion happens in `libakstdlib` and +the code comes back up with it. In the SDL build, `libakgl` owns 256 to 260 and an +`SDL Error` can reach `ER#` if the renderer itself refuses. + +**Do not hardcode those numbers.** `libakerror`'s codes are defined as offsets from +the largest `errno` your platform has, so the same code is a different integer on a +different machine — on the machine this was written on the range begins at 134, and +that is not a promise. The interpreter's own 512 to 519 are absolute and are the only +numbers worth writing into a program. + +## Two codes render as the same text + +`ERR()` gives the *registered name* for a code, and two pairs of them collide: + +| Text | Given by | +|---|---| +| `Type Error` | 513, and `libakerror`'s own type error | +| `Value Error` | 517, and `libakerror`'s own value error | + +The `Value Error` pair is not hypothetical — it is the `VAL` case above: + +```basic +10 TRAP 100 +20 PRINT VAL("XYZ") +30 END +100 PRINT ERR(ER#) +110 IF ER# = 517 THEN PRINT "INTERPRETER" +120 IF ER# <> 517 THEN PRINT "FROM UNDERNEATH" +130 END +``` + +```output +Value Error +FROM UNDERNEATH +``` + +So `IF ERR(ER#) = "Value Error" THEN` is not a reliable test and `IF ER# = 517 THEN` +is. **Compare the code; print the text.** + +## What is not in `ER#` + +An error the interpreter reports *about the host* rather than about your program never +reaches `ER#`, because it is not the program's error to catch — a font that will not +open, a window that will not create. Those come out of the embedding API instead; +Chapter 14 draws that line, and Chapter 10 is what a host does with it. diff --git a/docs/README.md b/docs/README.md index 16bb9b1..7a5c03a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,7 @@ embedding it, debugging it or changing it. | **[12. Function reference](12-function-reference.md)** | Every function, alphabetically | | **[13. Differences from BASIC 7.0](13-differences.md)** | What a C128 programmer needs to know | | **[14. Architecture](14-architecture.md)** | How the interpreter is put together, how to debug it, how to change it | +| **[15. Error codes](15-error-codes.md)** | Appendix: every value `ER#` can hold and every error line the interpreter prints | ## The shortest possible start