Next: , Previous: Processing of Errors, Up: Errors


9.5.3.3 エラーハンドラの書き方

エラーを通知することの普通の効果は、 実行中のコマンドを終了し、Emacsのエディタコマンドループにただちに戻ります。 読者のプログラムの一部で発生したエラーを捕捉するようにするには、 スペシャルフォームcondition-caseを使ってエラーハンドラを設定します。 単純な例はつぎのようになります。

     (condition-case nil
         (delete-file filename)
       (error nil))

これはfilenameという名前のファイルを削除しますが、 エラーが発生するとどんなエラーでも捕捉してnilを返します。

condition-caseの第2引数を 保護されたフォーム(protected form)と呼びます。 (上の例では、保護されたフォームはdelete-fileの呼び出し。) このフォームの実行を開始するとエラーハンドラが有効になり、 このフォームから戻るとエラーハンドラは取り除かれます。 そのあいだは、つねにエラーハンドラは有効です。 特に、このフォームから呼び出される関数の実行中、 それらのサブルーティンの実行中などには、エラーハンドラは有効です。 これは大切なことで、厳密にいえば、 エラーが通知されるのは、保護されたフォームから呼び出された (signalerrorを含む)Lisp基本関数の実行中であって、 保護されたフォームそのものからではないからです。

保護されたフォームのうしろにある引数は、ハンドラです。 各ハンドラは1つ以上の(シンボルである)条件名 (condition names)を列挙し、処理するエラーを指定します。 エラーが通知されたときのエラーシンボルも条件名のリストを定義します。 それらに共通の条件名があるとき、 エラーハンドラがエラーに適用されます。 上の例では、1つのハンドラがあり、条件名は1つ、errorを指定しています。 この条件名はすべてのエラーを意味します。

適用可能なハンドラの探索では、 もっとも最近に確立されたハンドラから始めて、 確立されたすべてのハンドラを調べます。 したがって、フォームcondition-caseが2つ入れ子になっていて 同じ名前のハンドラを確立していると、内側のものが実際に処理を受け持ちます。

フォームcondition-caseでエラーが処理されるときには、 debug-on-errorでエラーによりデバッガを起動するように指定してあっても デバッガは実行されません。 See Error Debuggingcondition-caseで捕捉されるエラーをデバッグしたいときには、 変数debug-on-signalnil以外の値を設定します。

エラーを処理できる場合には、制御はハンドラに移ります。 こうするまえに、Emacsは、抜け出し対象となる束縛作成構造が設定した すべての変数束縛を解き、抜け出し対象となるフォームunwind-protect すべての後始末を実行します。 ハンドラに制御が移ると、ハンドラの本体を実行します。

ハンドラ本体の実行を完了すると、 フォームcondition-caseから戻ります。 ハンドラを実行するまえに保護されたフォームから完全に抜けているので、 ハンドラでは、エラー発生時点から再開したり、 保護されたフォームの内側で作られた変数束縛を調べたりすることはできません。 ハンドラでできることは、後始末をして先へ進むことだけです。

condition-case構造は、insert-file-contentsの呼び出しで ファイルのオープンに失敗するなどの予測可能なエラーを捕捉するために しばしば使われます。 プログラムがユーザーから読み取った式を評価する場合のように、 まったく予測不可能なエラーを捕捉するためにも使われます。

エラー通知とエラー処理は、throwcatchに多少似ていますが、 それらはまったく別の機能です。 catchではエラーを捕捉できませんし、 エラーハンドラではthrowを処理できません (しかしながら、適切なcatchがないthrowを使うと、 処理できるエラーを通知する)。

— Special Form: condition-case var protected-form handlers...

このスペシャルフォームは、protected-formの実行中は エラーハンドラhandlersを確立する。 protected-formがエラーなしに完了すると、 その戻り値がフォームcondition-caseの値になる。 この場合、condition-caseはなんの効果もない。 フォームcondition-caseで違いがでるのは、 protected-formの実行中にエラーが起こった場合である。

handlersは、(conditions body...)の形式の リストである。 ここでconditionsは、処理すべきエラーの条件名か条件名のリストである。 bodyは1つ以上のLisp式であり、 このハンドラがエラーを処理するときに実行される。 ハンドラの例を示す。

          (error nil)
          
          (arith-error (message "Division by zero"))
          
          ((arith-error file-error)
           (message
            "Either division by zero or failure to open a file"))

生起する各エラーには、 そのエラーの種類を表すエラーシンボル(error symbol)がある。 そのシンボルの属性error-conditionsは、 条件名のリストである(see Error Symbols)。 Emacsは、有効なフォームcondition-caseすべてを探索し、 これらの条件名を1つ以上指定したハンドラを探す。 もっとも内側の一致するcondition-caseがエラーを処理する。 このcondition-caseの内側では、 適用可能な最初のハンドラがエラーを処理する。

ハンドラの本体の実行を完了すると、 condition-caseは通常のように戻り、 ハンドラの本体の最後のフォームの値を全体としての値に使う。

引数varは変数である。 condition-caseは、protected-formを実行するときには この変数を束縛せず、エラーを処理するときだけ束縛する。 そのとき、varはローカルにエラー記述 (error description)に束縛される。 これは、エラーの詳細を与えるリストである。 エラー記述は、(error-symbol . data)の形式である。 ハンドラは、動作を決定するためにこのリストを参照できる。 たとえば、ファイルのオープンに失敗したエラーであれば、 dataの第2要素、エラー記述の第3要素がファイル名である。

varnilであると、変数を束縛しなことを意味する。 そうすると、ハンドラではエラーシンボルと関連するデータを使えない。

— Function: error-message-string error-description

この関数は、指定したエラー記述に対するエラーメッセージ文字列を返す。 エラーに対する普通のエラーメッセージを表示して、 エラーを処理したい場合に便利である。

ゼロ除算の結果であるエラーを処理するcondition-caseの使用例を示します。 ハンドラはエラーメッセージを(ベルを鳴らさずに)表示して、 大きな数を返します。

     (defun safe-divide (dividend divisor)
       (condition-case err
     
           ;; 保護されたフォーム
           (/ dividend divisor)
     
         ;; ハンドラ
     
         (arith-error                        ; 条件
     
          ;; このエラーに対する普通のメッセージを表示する
          (message "%s" (error-message-string err))
          1000000)))
      safe-divide
     
     (safe-divide 5 0)
          -| Arithmetic error: (arith-error)
      1000000

ハンドラは条件名arith-errorを指定しているので、 ゼロ除算エラーだけを処理します。 少なくともこのcondition-caseでは他の種類のエラーは処理しません。 したがって、つぎのようになります

     (safe-divide nil 3)
          error--> Wrong type argument: number-or-marker-p, nil

以下は、errorで通知されるエラーも含めて、 すべての種類のエラーを捕捉するcondition-caseです。

     (setq baz 34)
           34
     
     (condition-case err
         (if (eq baz 35)
             t
     
           ;; これは関数errorの呼び出し
           (error "Rats!  The variable %s was %s, not 35" 'baz baz))
     
       ;; これはハンドラ。フォームではない
       (error (princ (format "The error was: %s" err))
              2))
     -| The error was: (error "Rats!  The variable baz was 34, not 35")
      2