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.

ConstantNameMeaning
err.failedfaileda failure in general; a false assert
err.overflowoverflowan integer overflowed
err.div_zerodiv_zerodivision by zero
err.rangerangean index out of range; taking from an empty queue or list
err.fullfulla capacity is used up
err.depthdepththe call depth limit
err.stalestalea row or handle used after it was given back
err.cancelledcancelledthe task was cancelled (clause 14)
err.interruptedinterruptedthe host cut off what was waited for (clause 19)
err.deadlockdeadlockevery task left waits for a queue that nothing can change (14.3)
err.formatformattext or bytes of the wrong form
err.not_foundnot_foundwhat 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:

GuardWhen the call fails
or_failthis word fails too, with the same code; its signature shall have or fail
or_returnthis word ends; the stack there shall match its results
or_else … endthe 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:

(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
ConditionMessage
No guard after a word that can faila word that can fail is followed by or_fail, or_return or or_else
or_fail in a word without or failor_fail passes the failure on, so this word needs `or fail` in its signature
fail in a word without or failfail needs `or fail` in the signature of this word
The body of or_else leaves other valuesthe body of or_else must leave what the word would have given, or leave the word or the loop