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:
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.
|
||||
Reference in New Issue
Block a user