11 Failures and faults
(1) Things go wrong in two ways. A failure is something expected to happen now and then: text that does not read as a number, a full list, a missing key. The caller handles it on the spot. A fault means the program is wrong: an overflow, a division by zero, an index out of range. It ends the run and is reported to the host. A script has no way to catch a fault.
11.1 Error codes
(1) Failures and faults have an error code, of type err. Codes are named by constants written after err. (no use needed). The word err_text gives the name of a code as a str.
| Constant | Name | Meaning |
err.failed | failed | a failure in general; a false assert |
err.overflow | overflow | an integer overflowed |
err.div_zero | div_zero | division by zero |
err.range | range | an index out of range; taking from an empty queue or list |
err.full | full | a capacity is used up |
err.depth | depth | the call depth limit |
err.stale | stale | a row or handle used after it was given back |
err.cancelled | cancelled | the task was cancelled (clause 14) |
err.interrupted | interrupted | the host cut off what was waited for (clause 19) |
err.deadlock | deadlock | every task left waits for a queue that nothing can change (14.3) |
err.format | format | text or bytes of the wrong form |
err.not_found | not_found | what was looked for is not there |
The values are those of pinax_err in the host interface (19.1). A program declares codes of its own with err NAME (8.14); err_text gives their names too. Host modules may add codes of their own.
11-errors.pnx
use io
err.full err_text io.println
err.not_found err_text io.println
err.format err_text io.println
Output
$ pnx 11-errors.pnx
full
not_found
format
11.2 Failures
Syntax
guard = "or_fail" | "or_return" | "or_else" [ "(" NAME ")" ] { body } "end" ;(1) A word whose signature ends in or fail can fail. When it fails it has no results, only an error code.
(2) The token right after a call of a word that can fail shall be a guard:
| Guard | When the call fails |
or_fail | this word fails too, with the same code; its signature shall have or fail |
or_return | this word ends; the stack there shall match its results |
or_else … end | the body makes the results in place of the call; or_else (e) … end takes the code into the local e; the body may also leave with return, fail or break |
When the call succeeds the guard does nothing and the results stay on the stack.
(2a) A store into a str(N) place may be followed by a guard as well; it then fails with range when the text is too long (7.3 (4a)). After any other store a guard is refused.
(3) code fail makes this word fail with that code. Its signature shall have or fail.
11-failure.pnx
use io
use str
word parse_port (s str gives port int or fail)
s str.to_int or_fail to n rem not a number: fail with the same error
n 1 lt n 65535 gt or if err.range fail end
n
end
word show (s str)
s parse_port or_else (e) cat ("bad port ", s, ": ", e err_text) io.println return end
to p
cat ("port ", p) io.println
end
"8080" show
"http" show
"70000" show
Output
$ pnx 11-failure.pnx
port 8080
bad port http: format
bad port 70000: range
11-guards.pnx
use io
use str
list items int 2
word try_three (or fail)
1 items push or_fail rem or_fail: fail the same way
2 items push or_fail
3 items push or_fail
end
word fill_some ()
4 items push or_return rem or_return: leave this word quietly
"not reached when the list is full" io.println
end
try_three or_else (e) cat ("try_three failed: ", e err_text) io.println end
fill_some
"x" str.to_int or_else 0 end str.to_str io.println rem or_else: a value in its place
Output
$ pnx 11-guards.pnx
try_three failed: full
0
(4) To receive overflow and division by zero as failures instead of faults, use chk_add chk_sub chk_mul chk_div (15.1).
11-chk.pnx
use io
use str
9223372036854775807 1 chk_add or_else (e) cat ("chk_add failed: ", e err_text) io.println 0 end drop
6 7 chk_mul or_else (e) 0 end str.to_str io.println
1 0 chk_div or_else (e) cat ("chk_div failed: ", e err_text) io.println 0 end drop
Output
$ pnx 11-chk.pnx
chk_add failed: overflow
42
chk_div failed: div_zero
Constraints
(5) A word that can fail with no guard after it is refused: a failure cannot be passed over unnoticed.
11-guard-missing.pnx
use str
word f (s str gives int) s str.to_int 1 add end
Refused
$ pnx 11-guard-missing.pnx
11-guard-missing.pnx:2:39: a word that can fail is followed by or_fail, or_return or or_else
(6) or_fail and fail are used only in words whose signature has or fail. Code outside definitions counts as having no or fail.
11-or-fail-needs.pnx
use str
word f (s str gives int) s str.to_int or_fail end
Refused
$ pnx 11-or-fail-needs.pnx
11-or-fail-needs.pnx:2:39: or_fail passes the failure on, so this word needs `or fail` in its signature
11-fail-needs.pnx
word check (n int) n 0 lt if err.range fail end end
Refused
$ pnx 11-fail-needs.pnx
11-fail-needs.pnx:1:40: fail needs `or fail` in the signature of this word
(7) The body of or_else shall leave what the word would have given on success, or leave.
11-or-else-stack.pnx
use str
word f (s str gives int) s str.to_int or_else (e) "zero" end end
Refused
$ pnx 11-or-else-stack.pnx
11-or-else-stack.pnx:2:58: the body of or_else must leave what the word would have given, or leave the word or the loop
(8) On the paths of failures too, owned values shall be handed over (12.4). A processor checks this at every or_fail and or_return.
11.3 Faults
(1) A fault ends the run on the spot. It is the return value of the host function that started the run (pinax_run, pinax_load and so on). A fault inside a task also ends the whole run and every task, unless the task’s group is declared apart (14.4). The state remains and takes the next run.
(2) Faults come from:
- arithmetic overflow (
overflow) and division by zero (div_zero) — 7.1, 15.1; - an index out of range of an array or list, a
casewith no matching branch, the none row, text longer than itsstr(N)stored with no guard (range) — 7.3, 8, 10.3; - a value that does not fit a narrow field (
overflow) — 8.8; - a region or task space too small (
full) — 12.2; - the call depth (
depth) — 9.6; - a row or handle given back (
stale) — 8.9, 19.6; - a false
assert(failed).
(3) What a run that ends with a fault made in the region is rewound. The handles that run opened are closed by the processor, so on a path of faults owned values need no closing by hand. Values already stored in globals, and what host words have already done, stay.
(4) A conforming processor reports the kind of a fault and its line. pnx also shows the locals of the word the fault happened in.
11-div-zero.pnx
use io
use str
word ratio (a int, b int gives int) a 100 mul b div end
3 4 ratio str.to_str io.println
3 0 ratio str.to_str io.println
Fault
$ pnx 11-div-zero.pnx
75
11-div-zero.pnx:3: division by zero
a int = 3
b int = 0
11-assert.pnx
use io
word half (n int gives int) n 2 mod 0 eq assert n 2 div end
10 half drop
"10 is even" io.println
7 half drop
Fault
$ pnx 11-assert.pnx
10 is even
11-assert.pnx:2: failed
n int = 7
Diagnostics
| Condition | Message |
|---|---|
| No guard after a word that can fail | a word that can fail is followed by or_fail, or_return or or_else |
or_fail in a word without or fail | or_fail passes the failure on, so this word needs `or fail` in its signature |
fail in a word without or fail | fail needs `or fail` in the signature of this word |
The body of or_else leaves other values | the body of or_else must leave what the word would have given, or leave the word or the loop |