11 실패와 결함

(1) 일이 잘못되는 길은 둘이다. 실패는 일어날 수 있다고 본 일이다: 수로 읽히지 않는 글, 가득 찬 목록, 없는 키. 부른 쪽이 그 자리에서 다룬다. 결함은 프로그램이 틀렸다는 뜻이다: 넘침, 0 으로 나눔, 범위 밖의 번호. 실행을 끝내고 호스트에게 알린다. 스크립트 안에서 결함을 잡는 길은 없다.

11.1 오류 번호

(1) 실패와 결함에는 오류 번호(error code)가 있다. 타입은 err 이다. 이름은 err. 에 이어 적는 상수다(use 없이 쓴다). 낱말 err_text 는 번호의 이름을 str 로 준다.

상수이름뜻
err.failedfailed일반적인 실패, assert 가 거짓
err.overflowoverflow정수가 넘침
err.div_zerodiv_zero0 으로 나눔
err.rangerange번호가 범위 밖, 빈 큐·목록에서 꺼냄
err.fullfull용량이 다 참
err.depthdepth호출 깊이의 한계
err.stalestale돌려준 줄이나 핸들을 씀
err.cancelledcancelled작업이 취소됨(14절)
err.interruptedinterrupted호스트가 기다리던 일을 끊음(19절)
err.deadlockdeadlock남은 작업이 모두 아무것도 바꾸지 못할 큐를 기다림(14.3)
err.formatformat글이나 바이트의 모양이 틀림
err.not_foundnot_found찾는 것이 없음

값은 호스트 인터페이스의 pinax_err 와 같다(19.1). 프로그램은 err 이름 으로 제 코드를 선언한다(8.14). err_text 는 그 이름도 준다. 호스트 모듈은 자기 번호를 더할 수 있다.

11-errors.pnx

use io
err.full err_text io.println
err.not_found err_text io.println
err.format err_text io.println

출력

$ pnx 11-errors.pnx
full
not_found
format

11.2 실패

구문
guard = "or_fail" | "or_return" | "or_else" [ "(" NAME ")" ] { body } "end" ;

(1) 서명이 or fail 로 끝나는 낱말은 실패할 수 있다. 실패하면 결과는 없고 오류 번호만 있다.

(2) 실패할 수 있는 낱말을 부른 바로 다음 토큰은 지킴 낱말(guard)이어야 한다.

지킴실패했을 때
or_fail같은 오류로 이 낱말도 실패한다. 이 낱말의 서명에 or fail 이 있어야 한다
or_return이 낱말을 끝낸다. 그 자리의 스택이 결과와 맞아야 한다
or_else … end몸이 대신할 결과를 만든다. or_else (e) … end 는 오류 번호를 지역 변수 e 로 받는다. 몸은 return fail break 로 떠나도 된다

성공하면 지킴 낱말은 아무 일도 하지 않고 결과가 스택에 남는다.

(2a) str(N) 자리에 넣는 일 뒤에도 지킴 낱말이 올 수 있다. 그러면 글이 너무 길 때 range 로 실패한다 (7.3 (4a)). 다른 넣기 뒤의 지킴 낱말은 거절된다.

(3) 번호 fail 은 이 낱말을 그 오류로 실패시킨다. 이 낱말의 서명에 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

출력

$ 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

출력

$ pnx 11-guards.pnx
try_three failed: full
0

(4) 넘침과 0 으로 나눔을 결함 대신 실패로 받으려면 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

출력

$ pnx 11-chk.pnx
chk_add failed: overflow
42
chk_div failed: div_zero
제약

(5) 실패할 수 있는 낱말 뒤에 지킴 낱말이 없으면 거절된다. 실패를 모르고 지나칠 수 없다.

11-guard-missing.pnx

use str
word f (s str gives int) s str.to_int 1 add end

거절

$ 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 과 fail 은 서명에 or fail 이 있는 낱말 안에서만 쓴다. 정의 밖의 코드도 or fail 이 없는 것으로 친다.

11-or-fail-needs.pnx

use str
word f (s str gives int) s str.to_int or_fail end

거절

$ 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

거절

$ pnx 11-fail-needs.pnx
11-fail-needs.pnx:1:40: fail needs `or fail` in the signature of this word

(7) or_else 의 몸은 낱말이 성공했다면 남겼을 값을 남기거나, 떠나야 한다.

11-or-else-stack.pnx

use str
word f (s str gives int) s str.to_int or_else (e) "zero" end end

거절

$ 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) 실패의 경로에서도 owned 값은 넘겨줘야 한다(12.4). 처리기는 or_fail or_return 자리마다 검사한다.

11.3 결함

(1) 결함은 그 자리에서 실행을 끝낸다. 결함은 호스트가 부른 함수(pinax_run, pinax_load 등)의 반환값이 된다. 작업 안의 결함도 그 실행 전체와 모든 작업을 끝낸다. 작업의 묶음이 apart 로 선언되었으면 그렇지 않다(14.4). 상태는 남아 다음 실행을 받는다.

(2) 결함은 다음에서 난다.

(3) 결함으로 끝난 실행이 구역에 만든 것은 되감긴다. 실행이 연 핸들은 처리기가 닫는다. 그래서 결함의 경로에서 owned 값을 손으로 닫을 일은 없다. 전역에 이미 넣은 값과 호스트 낱말이 이미 한 일은 그대로 남는다.

(4) 적합한 처리기는 결함의 갈래와 그것이 난 줄을 알린다. pnx 는 그 아래에 결함이 난 낱말의 지역 변수를 함께 보인다.

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

결함으로 멈춤

$ 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

결함으로 멈춤

$ pnx 11-assert.pnx
10 is even
11-assert.pnx:2: failed
  n int = 7
진단
조건메시지
실패할 수 있는 낱말 뒤에 지킴이 없음a word that can fail is followed by or_fail, or_return or or_else
or fail 없는 낱말의 or_failor_fail passes the failure on, so this word needs `or fail` in its signature
or fail 없는 낱말의 failfail needs `or fail` in the signature of this word
or_else 의 몸이 남기는 값이 다름the body of or_else must leave what the word would have given, or leave the word or the loop