Errors, refusals & diagnostics¶
Programs go wrong, and in different ways: a misspelled name, a full disk, a record that is not there, a compiled file that is corrupt. This page explains the three moments at which a d/BASIC program can fail, how to catch an error while the program is running with ON REFUSAL, what ERR and ERL tell you, how to resume safely, and how to clean up. In d/BASIC an operation that cannot go ahead is said to be refused: it stops and tells you exactly what is missing rather than guessing. The word is part of the language's keywords, so you will see it throughout this page.
A misspelled name is not the same event as a full disk, and "record not found" is not the same event as a corrupt compiled program. d/BASIC keeps those distinctions visible so your recovery code does not have to guess what happened.
The three moments of failure¶
| Moment | Example | What happens |
|---|---|---|
| compile | unknown name, wrong type, unmatched block | no compiled program file, .dbc, is produced; fix the source |
| load (the checks a program passes before it runs) | a capability the program needs is missing, the .dbc version is not supported, a library it needs cannot be found |
the program never starts |
| runtime | an index out of bounds, a capacity exceeded, a handle that is no longer valid, a request the machine would not carry out, an explicit ERROR |
the active ON REFUSAL handler runs, or the program stops with the error |
A compile or load failure cannot be caught by the program, because the program never started. A runtime error can be handled, but only when the program has a truthful alternative to offer.
Catch a runtime refusal¶
ON REFUSAL GOTO CouldNotLoad
CALL LoadOptionalReport
PRINT "REPORT READY"
GOTO Done
CouldNotLoad:
PRINT "REPORT UNAVAILABLE. RULE "; ERR; " LINE "; ERL
RESUME Done
Done:
END
ON REFUSAL GOTO target installs the handler. The target may be a named label or a numbered line. A handler should either finish what it can of the current job or leave through one of the RESUME forms; falling out of the handler and carrying on as if the failed operation had succeeded is almost always a bug.
The handler is program-wide, not a try block around a few lines. It is also deliberately not re-entrant: if an operation inside the handler fails while the first error is still being handled, the program stops rather than entering the same handler again inside itself. Keep the handler's work small, and make its cleanup steps safe to run more than once.
Inspect ERR and ERL¶
| Value | Meaning |
|---|---|
ERR |
the error code of the error being handled: a stable nonzero number that means the same thing on every machine |
ERL |
the numbered source line it belongs to, or 0 when that line has no number |
Outside a handler, both are 0. Treat the error code as data: log it, compare it when a particular recovery genuinely depends on it, and include it in a bug report. Do not compare against the wording of the message instead; the wording may differ from one host to the next, and the code does not.
Resume deliberately¶
| Form | Meaning |
|---|---|
RESUME NEXT |
continue with the statement after the one that failed |
RESUME target |
leave the handler at a named label or numbered line |
Prefer an explicit target when the failed operation would have set up state that the next statement needs:
ON REFUSAL GOTO NoOptionalSound
CALL OpenOptionalSound
SoundReady% = -1
GOTO ContinueSetup
NoOptionalSound:
SoundReady% = 0
RESUME ContinueSetup
ContinueSetup:
PRINT "SETUP COMPLETE"
RESUME NEXT suits a self-contained optional action whose following code does not depend on its result. It is not a general "ignore errors" switch.
Raise a governed rule intentionally¶
ERROR raises an error by its code, given as a single literal number:
The operand is a numeric literal from 1 through 65535; a variable or a CONST name is not accepted there. ERR is an INTEGER, so it shows the same 16 bits of the code as a signed value: &HF001 displays as -4095. Compare a private code using the same hexadecimal literal, IF ERR = &HF001 THEN, rather than the decimal 61441. Being able to write a number does not make it yours. Every error code that can reach a shipped program must be registered in d/BASIC's list of error codes, a list that only ever grows, and the literal must name exactly the condition registered under it.
The range 61440..65534 (&HF000..&HFFFE) is set aside for private experiments. It is handy while you are working out a local rule of your own, but it must not appear in a shipped program or in a published record of a test run. Move the condition to its registered code before release. 0 means no error, and 65535 is never assigned to anything.
Use ERROR for a state that is impossible or that you have explicitly decided to reject, not for ordinary branching and not as a stand-in for a result value.
Expected outcomes are often values, not refusals¶
A well-designed operation on the outside world tells an expected result apart from a broken promise:
- a file
STAT(a request for a file's details) may report that a name does not exist, as a normal answer; - a database query may return zero rows;
- a network response may carry an HTTP status;
- a save may still be pending; and
- cancel may be an ordinary way for an operation to end.
Those belong in result records and in your program's logic. Malformed data, a handle that no longer points at anything, a required capability the host does not have, an invalid bound, and a broken integrity rule are errors. Treating every non-success as one undifferentiated error throws away information the user needs.
Cleanup is part of the control flow¶
Anything you open or hold on to should have one visible cleanup path:
DIM SessionOpen AS INTEGER
ON REFUSAL GOTO Failed
CALL OpenSession
SessionOpen = -1
CALL DoWork
GOTO Cleanup
Failed:
PRINT "WORK REFUSED. RULE "; ERR
RESUME Cleanup
Cleanup:
IF SessionOpen THEN CALL CloseSession
END
For objects that own something, put close or cancel behavior that is safe to repeat in a method, and call it both from the normal path and from the window program's CLOSE handler, the routine that runs when its window is closed. Destructors remain a final, predictable release point, not a substitute for reporting whether an external save succeeded.
Do not catch what you cannot repair¶
Good reasons to install a handler:
- an optional capability has a real fallback;
- a transaction can be cancelled and the previous document kept;
- the user can choose a different file, or retry after freeing space;
- a window program can keep showing its last known-good state; or
- a command-line tool can add context before it finishes.
Bad reasons:
- hiding a type or bounds bug;
- carrying on with output from an operation that never finished;
- turning every error into "not found"; or
- looping forever on a capability the host has already said it does not have.
Reading compiler diagnostics¶
A useful diagnostic, the compiler's report of a problem, answers four questions:
- Where? File, line and column, or the span of source involved.
- What? The name, type, shape or construct that could not be accepted.
- Why? A stable error code.
- What next? The nearest valid spelling, or the boundary you have run into.
Fix the first diagnostic first; the later ones may be knock-on effects of the same missing token. In the Playground, the editor on this site, selecting a diagnostic moves the cursor to its position. On the command line, keep the stable error code for your scripts and show the human explanation to the person running the build.
Errors in GUI applications¶
Never spin, retry or open a modal alert from inside DRAW, the routine that paints a window. Store the final outcome in your program's data, ask for a repaint, and let the next frame show it. Keep Cancel and Close reachable while an outside operation is pending. A useful status line says what failed and what the person can do about it, not merely "Error".
SUB ReportFailure (BYVAL Rule AS INTEGER)
StatusText$ = "COULD NOT SAVE. RULE " + LTRIM$(STR$(Rule))
SERVICE INVALIDATE()
END SUB
A refusal checklist¶
- Decide whether the condition is an expected result or an error.
- Catch only when a real recovery or cleanup path exists.
- Read
ERRandERLbefore leaving the handler. - Never run success-only code after an operation that failed.
- Resume at an explicit safe point when state may be incomplete.
- Keep the last known-good state the one the user sees.
- Close or release everything you own on success, on cancel and on failure.
- Keep the stable error codes in tests and reports.