Files
akbasic/docs/15-error-codes.md
Andrew Kesterson 737fdc760f 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>
2026-08-01 07:36:36 -04:00

6.9 KiB

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:

10 PRINT LEFT(1, 2)
20 PRINT "NEVER REACHED"
? 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:

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
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:

10 TRAP 100
20 DOPEN 1, "nosuchfile.bas"
30 END
100 PRINT "" + ER# + " " + ERR(ER#)
110 END
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:

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
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.