Previous: , Up: Control Structures   [Contents][Index]


6.9.8 Exception Handling

ワードが処理できないエラー状態を検出した場合、 例外を投げる(throw)ことができます。 最も単純なケースでは、 これによりプログラムが終了し、適切なエラーが報告されます。

throw ( y1 .. ym nerror – y1 .. ym / z1 .. zn error  ) exception “throw”

nerror が 0 の場合は、 それを drop して続行します。 それ以外の場合は、 動的に囲んでいる次の例外ハンドラー(next dynamically enclosing exception handler)に制御を移し、 それに応じてスタックをリセットし、 nerror をプッシュします。

fast-throw ( ... wball – ... wball ) gforth-experimental “fast-throw”

軽量の throw バリエーション: ゼロ以外のみに使用され、 バックトレースを保存したり、 欠落している catch を扱ったりしません。

throw は、スタック上のセル・サイズのエラー番号数値を消費します。 標準 Forth には事前定義されたエラー番号がいくつかあります(errors.fs 参照)。 Gforth (および他のほとんどのシステム)では、 さまざまなワードによって生成された ior をエラー番号として使用できます(たとえば、 allocate の一般的な使用法は allocate throw です)。 Gforth は、 (適切なエラー報告付きで)独自のエラー番号を定義するための Exception というワードも提供します。 このワードの標準 Forth バージョン(ただしエラー・メッセージなし) は compat/excel.fs で入手できます。 最後に、 あなた独自のエラー番号(-4095 〜 0 の範囲以外の任意の番号)を使用できますが、 表示されるのは適切なエラー・メッセージではなく、数字のみです。 たとえば、 以下のことを試してみてください:

-10 throw                    \ 標準で定義済
-267 throw                   \ システムで定義済
s" my error" exception throw \ ユーザー定義
7 throw                      \ 思いつくままの任意の番号
exception ( addr u – n  ) gforth-0.2 “exception”

n は、 -4095 〜 -256 の範囲内で以前に使用されていなかった throw 値です。 Exception を連続して呼び出すと、 連続して減少する数値が返されます。 Gforth は文字列 addr u をエラー・メッセージとして使用します(訳注: 同じエラーメッセージを使いまわすには、 s" hoge err msg" exception constant hoge-error hoge-error ! などとして得られた hoge-error を使いまわす。 hoge-error throw 等する)

エラー番号を文字列に変換するためのワード(通常は POSIX の strerror をモデルにしたもの)がある場合もあります。 以下のワードを使用すると、 これらの文字列を Gforth のエラー処理に取り込むことができます:

exceptions ( xt n1 – n2  ) gforth-1.0 “exceptions”

throw時: xt ( +n -- c-addr u ) は、 0<=n<n1 の範囲のローカルのエラー・コードをエラー・メッセージに変換します。 Exceptions は、 n2-n1<n3<=n2 の範囲で n1 個(0 〜 n1)のエラー・コードを予約します。 n2 に、 対応する Gforth エラー・コード(ローカルのエラー番号 0 に対応。 つまり、 0<=n<n1 の範囲のローカルのエラー・コードに 対応する Gforth エラー・コードは n2 <= n3 < n2+n1)を返します。 (後の時点で)その範囲に対応した Gforth エラー・コード n3 が throw されると、 n2-n3 がプッシュされ(つまりローカルのエラー番号に変換した値をプッシュし)、 xt が実行されて、 xt がエラー・メッセージを生成します(訳注: minos2/pulse-audio.fs 等で確認。 xt にC-interface ワードをセットし、 外部Cライブラリのエラー・メッセージを gforth 内から表示するのに使っているようだ)

たとえば、 C言語ライブラリの errno エラー (および strerror を使用した変換) がまだ Gforth で直接サポートされていないとした場合、 以下のようにして strerror を gforth と結び付けることができます:

' strerror 1536 exceptions constant errno-base
: errno-ior ( -- n )
\ n は errno の値に対応する Gforth ior を求めなければならないので、
\ ここでerrno 範囲と Gforth ior の範囲の間で変換する必要があります。
\ ERRNO は Gforth ワードではないため、
\ それにアクセスするには C インターフェイスを使用する必要があります。
  errno errno-base over - swap 0<> and ;

C言語の関数(C言語インターフェイス(C interface)を使用)を呼び出し、 その戻り値がエラーが発生したことを示している場合、 errno-ior throw を実行して、適切なエラー・メッセージ (“Permission denied” など)を含む例外を生成できます。

フラグが true の場合、 特定の err# でエラーを投げる(THROW)一般的な慣用句は以下のとおりです:

( flag ) 0<> err# and throw

あなたのプログラムで、 例外をキャッチする例外ハンドラーを提供できます。 例外ハンドラーを使用すると、 問題を修正したり、 一部のデータ構造をクリーンアップして例外を次の例外ハンドラーに投げたり(throw)することができます。 throw は動的に最も内側の例外ハンドラー(the dynamically innermost exception handler)にジャンプすることに注意してください。 システムの例外ハンドラーは最も外側にあり、 エラーを出力してコマンド・ラインの通訳(interpretation)を再開するだけです(または、 バッチ・モード(つまり、 シェル・コマンド・ラインの処理中)では Gforth を終了します)。

例外をキャッチする標準 Forth での方法は catch です:

catch ( x1 .. xn xt – y1 .. ym 0 / z1 .. zn error  ) exception “catch”

xt を実行します。 実行から正常に戻った場合、 catch はスタックに 0 をプッシュします。 throw を介して実行が戻った場合、 すべてのスタックは catch へ入る時点の深さにリセットされ、 TOS (xt の位置) は throw コードに置き換えられます。

nothrow ( ) gforth-0.7 “nothrow”

再 throw しない catch または endtry の後ろでこれ (または標準のシーケンス ['] false catch 2drop) を使用します。 これにより、 次の throw でバックトレースが確実に記録されます。

例外ハンドラーの最も一般的な使用法は、 エラーが発生したときに状態をクリーンアップすることです。 例:

base @ >r hex \ actually the HEX should be inside foo to protect
              \ against exceptions between HEX and CATCH
['] foo catch ( nerror|0 )
r> base !
( nerror|0 ) throw \ pass it on

myerror というエラー番号を処理するための catch の使用は以下のようになります:

['] foo catch
CASE
  myerror OF ... ( do something about it ) nothrow ENDOF
  dup throw \ default: pass other errors on, do nothing on non-errors
ENDCASE

コードを別のワードでくるむ要があるのは面倒な場合が多いため、 Gforth では代替構文を提供しています:

TRY
  code1
  IFERROR
    code2
  THEN
  code3
ENDTRY

これは、 code1 を実行しします。 code1 が正常に完了すると、 code3 の実行へ続きます。 code1 または、 endtry より前で例外があった場合、 スタックは try 時の深さにリセットされ、 throw された値をデータ・スタックにプッシュし、 code2 の実行に続き、 そして、 最終的に code3 に到達します。

try ( compilation  – orig ; run-time  – R:sys1  ) gforth-0.5 “try”

例外キャッチ領域の開始

endtry ( compilation  – ; run-time  R:sys1 –  ) gforth-0.5 “endtry”

例外キャッチ領域の終わり

iferror ( compilation  orig1 – orig2 ; run-time  –  ) gforth-0.7 “iferror”

例外処理コードを開始します(tryendtry の間に例外がある場合に実行されます)。 この部分は then で終了する必要があります。

code2 が必要ない場合は、 iferror then の代わりに restore を記述できます:

TRY
  code1
RESTORE
  code3
ENDTRY

先程の例をこの構文で身綺麗にしてみます:

base @ { oldbase }
TRY
  hex foo \ now the hex is placed correctly
  0       \ value for throw
RESTORE
  oldbase base !
ENDTRY
throw

このバリエーションの追加の利点は、 restoreendtry の間の例外(たとえば、 ユーザーが Ctrl-C を押すことによる例外)でも、 restore の直後へコードの実行が移ることです。 ゆえに、 いかなる状況であっても base は復元されます。

ただし、 このコード自体が例外を引き起こさないようにする必要があります。 そうしないと、 iferror/restore コードがループします。 さらに、 iferror/restore コードで必要なスタックの内容が tryendtry の間のあらゆる場所に存在することも確認する必要があります。 この例では、 これは try の前にデータをローカル変数(local)に置くことによって実現されます(リターン・スタック上の例外フレーム(sys1)が邪魔なのでリターン・スタックは使用できません)。

この種の使用法は、 Lisp の unwind-protect と同様のものです。

もし、あなたが、 この例外再開始(exception-restarting)の振る舞いを望まない場合は、 以下のようにしてください:

TRY
  code1
ENDTRY-IFERROR
  code2
THEN

code1 に例外がある場合は code2 が実行され、 それ以外の場合は then の後ろ (または else 分岐の可能性あり) から実行が続行されます。 これはバージョン 0.7 より前の Gforth では以下の構成要素に該当します

TRY
  code1
RECOVER
  code2
ENDTRY

つまり、 この recover を使用しているコードを直に try ... entry-iferror ... then へと置き換えることができます。 ただし、 その置き換え作業中に他の try バリエーションのいずれかを使用した方が良いかどうかも検討することをお勧めします。

移行を容易にするために、 Gforth は 2 つの互換性ファイルを提供します: 1つ目は endtry-iferror.fs で、 古いシステム用に try ... endtry-iferror ... then 構文を提供します(ただし、 iferror または restore は提供しません)。 2つ目の recover-endtry.fs は、 新しいシステム上で古い構文の try ... recover ... endtry 構文を提供するので、 古いプログラムを実行するための一時しのぎとして使用できます。 どちらのファイルもどのシステムでも動作します(システムが、 実装する構文を既に定義済みの場合は何も行わないだけです)。 そのため、 古いシステムと新しいシステムを混在させて使用している場合でも、 これらのファイルのいずれかを無条件に require することができます。

restore ( compilation  orig1 – ; run-time  –  ) gforth-0.7 “restore”

コードの復元(restore)を開始します。 これは、 例外がある場合と無い場合に行われます。

endtry-iferror ( compilation  orig1 – orig2 ; run-time  R:sys1 –  ) gforth-0.7 “endtry-iferror”

例外キャッチ領域を終了し、 その領域外で例外処理コードを開始します(tryendtry-iferror の間に例外がある場合に実行されます)。 この部分は then (または else...then) で終了する必要があります。

ここで、 エラー処理の例を以下に示します:

TRY
  foo
ENDTRY-IFERROR
  CASE
    myerror OF ... ( do something about it ) nothrow ENDOF
    throw \ pass other errors on
  ENDCASE
THEN

プログラミング・スタイル・メモ: いつものように、 エラーを渡すための throw の後、 または ENDTRY の後のいずれか(または、catch を使用する場合は、エラーを処理するための選択構造の終了後)で、 スタックの深さが静的に明白であることを保証する必要があります。

throw の代替は 2 つあります: Abort" は条件付きでエラー・メッセージを提供できます。 Abort は「中止」(Abort)エラーを生成するだけです。

これらのワードの問題は、 例外ハンドラーが、 異なる abort" を区別できないことです。 例外ハンドラーにとってはそれらは -2 throw のように見えるだけです(標準のプログラムではエラー・メッセージにアクセスできません)。 同様に、 abort は例外ハンドラーに対して -1 throw のように見えます。

ABORT" ( compilation ’ccc"’ – ; run-time f –  ) core,exception-ext “abort-quote”

f のいずれかのビットがゼロ以外の場合、 -2 throw の機能を実行し、 例外スタック上例外フレームがない場合は文字列 ccc を表示します。

abort ( ?? – ??  ) core,exception-ext “abort”

-1 throw.

実行を中止する必要があるほど深刻でない問題の場合は、 警告を表示するだけで済みます。 変数 warnings を使用すると、 表示される警告の数を調整できます。

WARNING" ( compilation ’ccc"’ – ; run-time f –  ) gforth-1.0 “WARNING"”

f がゼロ以外の場合、 警告メッセージとして文字列 ccc を表示します。

warnings ( – addr  ) gforth-0.2 “warnings”

以下の警告レベルをセットしてください

0

警告オフ

-1

通常警告オン

-2

初心者警告オン

-3

偏執狂的警告オン

-4

全ての警告をエラーとして扱います(初心者警告を含む)


Previous: Calls and returns, Up: Control Structures   [Contents][Index]