Document every error code a script can see, as chapter 15
ER# held numbers nothing explained. The appendix lists the four error classes the interpreter prints, the eight codes it owns and 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 -- so the numbers are checked rather than claimed. The eighth, 516, is not usefully trappable and the chapter says why: entering a handler takes a scope, and the pool being empty is what raised it. Two things worth a reader's attention came out of writing it. Two codes register the same ERR() text, so a program must compare the number and print the text. And VAL reports libakerror's Value Error rather than the interpreter's 517, which makes that number the platform's rather than ours -- filed as section 6 item 21, not fixed here, because deciding which libakstdlib failures to translate is a boundary question and ENOENT out of DOPEN is the counter-case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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/<library>/AGENTS.md` | Per-repo rules. Read the relevant one **before editing a submodule** |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
32
TODO.md
32
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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
169
docs/15-error-codes.md
Normal file
169
docs/15-error-codes.md
Normal file
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user