エラーを通知した際の通常の動作は、実行中のコマンドを終了し、直ちにEmacsエディタのコマンドループに戻ることです。特殊なフォーム condition-case
を用いてエラーハンドラを定義することで、プログラムの一部で発生したエラーを捕捉するように設定できます。簡単な例は次のようになります。
(condition-case nil
(delete-file filename)
(error nil))
これにより filename という名前のファイルが削除され、エラーが発生した場合はそのエラーを捕捉して
nil を返します。(このような単純なケースではマクロ ignore-errors を使用できます。詳細は以下を参照してください。)
condition-case 構文は、insert-file-contents
の呼び出しでファイルが開けないといった予測可能なエラーを捕捉するためによく用いられます。また、プログラムがユーザーから読み込んだ式を評価する場合など、全く予測不可能なエラーを捕捉するためにも用いられます。
condition-case の第2引数は 保護形式
と呼ばれます。(上記の例では、保護形式は delete-file
の呼び出しです。)エラーハンドラは、この形式の実行が始まると有効になり、この形式が復帰すると無効になります。その間はずっと有効なままとなります。特に、この形式で呼び出された関数やそのサブルーチンなどで実行される間も有効です。これは好ましいことです。というのも、厳密に言えば、エラーを通知できるのは保護形式によって呼び出された
Lisp プリミティブ(signal や error を含む)のみであり、保護形式自体では通知できないからです。
保護形式に続く引数はハンドラです。各ハンドラは、どのエラーを処理するかを指定するために、1つ以上の条件名(シンボル)を列挙します。エラーが通知される際に指定されるエラーシンボルもまた、条件名のリストを定義します。ハンドラとエラーに共通の条件名がある場合、そのハンドラはそのエラーに適用されます。上記の例ではハンドラが1つあり、すべてのエラーをカバーする1つの条件名
error が指定されています。
適用可能なハンドラの探索では、直近に設定されたものから順に、設定済みのすべてのハンドラがチェックされます。したがって、入れ子になった2つのcondition-caseフォームが同一のエラーを処理しようとした場合、内側のフォームが処理を担当します。
エラーが何らかの condition-case 形式で処理される場合、たとえ debug-on-error
でこのエラーによりデバッガを起動すべきと指定されていても、通常はデバッガの実行が阻止されます。
condition-case で捕捉されたエラーをデバッグできるようにするには、変数 debug-on-signal に nil
以外の値を設定してください。また、次のように条件の中に debug
を記述することで、特定のハンドラでまずデバッガを実行させるよう指定することも可能です。
(condition-case nil
(delete-file filename)
((debug error) nil))
ここでの debug の役割は、単に condition-case
がデバッガの呼び出しを抑制しないようにすることだけです。個々のエラーがデバッガを呼び出すのは、debug-on-error やその他の通常のフィルタリング機構がそう判断した場合に限られます。エラー発生時のデバッガ起動 を参照してください。
マクロ condition-case-unless-debug
は、こうした形式のデバッグを処理する別の手段を提供します。これは condition-case
と全く同様に動作しますが、変数 debug-on-error が nil でない場合は、適用可能なハンドラを実行する前に Emacs
をデバッガに移行させます。(適用可能なハンドラがある場合は、デバッガが終了した後に引き続き実行されます。)
Emacsがあるハンドラがエラーを処理すると判断すると、制御をそのハンドラに返します。そのために、Emacsは終了処理中のバインディング構文によって作成されたすべての変数バインディングを解除し、終了処理中のすべてのunwind-protectフォームのクリーンアップを実行します。制御がハンドラに到達すると、ハンドラの本体が通常通り実行されます。
ハンドラ本体の実行後、処理は condition-case
フォームから戻ります。保護されたフォームはハンドラの実行前に完全に終了するため、ハンドラはエラーが発生した箇所から実行を再開することも、保護されたフォーム内で行われた変数束縛を調べることもできません。できるのは、後処理を行って処理を続行することだけです。
エラーの通知と処理は、throw や catch
といくらか似ていますが(明示的な非ローカル脱出: catch と throw
を参照)、これらは完全に別個の機能です。エラーを catch で捕捉することはできず、また
throw をエラーハンドラで処理することもできません(ただし、適切な catch が存在しない場合に throw
を使用すると、処理可能なエラーが通知されます)。
この特殊フォームは、protected-form の実行前後にエラーハンドラ handlers を設定します。protected-form
がエラーなく実行された場合、その戻り値が condition-case
フォームの値となります(成功ハンドラがない場合は。後述)。このとき、condition-case
は効果を発揮しません。protected-form の実行中にエラーが発生した場合には、condition-case フォームが影響します。
各 handlers は (conditions body…)
形式のリストです。ここで conditions
は、処理対象となるエラー条件名、または条件名のリスト(ハンドラの実行前にデバッガが動作するように debug を含めることもできます)です。条件名 t
は、あらゆる条件に一致します。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"))
発生する各エラーには、それがどのような種類のエラーであるかを示すエラーシンボルがあり、そこには条件名の一覧も記載されています(エラーシンボルと条件名を参照)。Emacsは、すべてのアクティブなcondition-caseフォームの中から、これらの条件名の1つ以上を指定するハンドラを検索し、最も内側にある一致するcondition-caseがエラーを処理します。このcondition-case内では、最初に見つかった適用可能なハンドラがエラーを処理します。
ハンドラの本体を実行した後、condition-case
はハンドラ本体内の最後のフォームの値を全体の値として、正常に返されます。
引数 var は変数です。condition-case
は、protected-form
の実行時にはこの変数をバインドせず、エラーを処理する際にのみバインドします。その際、var をローカルに
エラー記述子(エラー説明とも呼ばれます)にバインドします。エラー記述子とは、エラーの詳細を示すリストのことです。エラー記述子は
(error-symbol . data)
という形式をとります。ハンドラはこのリストを参照して、どのような処理を行うかを決定できます。例えば、ファイルのオープン失敗によるエラーの場合、ファイル名は
data(エラー記述子の第3要素)の第2要素となります。
var が nil
である場合、変数がバインドされていないことを意味します。この場合、エラーシンボルおよび関連データはハンドラから利用できません。
特殊なケースとして、handlers のうちの 1 つが (:success body…)
形式のリストである場合があり、このとき body は、当該式がエラーなく終了した際に protected-form の戻り値に var(nil でない場合)がバインドされた状態で実行されます。
condition-case
でキャッチしたシグナルを、外側のハンドラでキャッチできるように再送する必要がある場合があります。その方法は以下の通りです。
(signal err)
ここで err はエラー記述子変数、condition-case の第1引数は、エラー条件を再スローしたい対象です。シグナルの定義を参照してください。
この関数は、エラー記述子 error のエラーシンボルを返します。
この関数は、エラー記述子 error のフィールド番号 pos に対応する値を返します。フィールド番号は 1 から始まります。例えば、型 wrong-type-argument のエラーの場合、(error-slot-value error 2)
は型テストに失敗したオブジェクトを、(error-slot-value error 1) は失敗した述語を返します。
この関数は、指定されたエラー記述子に対応するエラーメッセージ文字列を返します。そのエラーに対する通常のエラーメッセージを出力して処理したい場合に有用です。シグナルの定義を参照してください。
以下に、ゼロ除算で発生するエラーを処理するために condition-case
を使用する例を示します。ハンドラはエラーメッセージを表示し(ただしビープ音は鳴らしません)、その後、非常に大きな数値を返します。
(defun safe-divide (dividend divisor)
(condition-case err
;; Protected form.
(/ dividend divisor)
;; The handler. (arith-error ; Condition. ;; Display the usual message for this 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
;; This is a call to the function error.
(error "Rats! The variable %s was %s, not 35" 'baz baz))
;; This is the handler; it is not a form.
(error (princ (format "The error was: %s" err))
2))
⊣ The error was: (error "Rats! The variable baz was 34, not 35")
⇒ 2
この構文は body
を実行し、その実行中に発生したエラーは無視します。実行中にエラーが発生しなかった場合、ignore-errors は body
の最後のフォームの値を返し、それ以外の場合は nil を返します。
この小節の冒頭にある例をignore-errorsを用いて書き直したものがこちらです:
(ignore-errors (delete-file filename))
このマクロはignore-errorsと同様ですが、指定した特定のエラー条件のみを無視します。
(ignore-error end-of-file
(read ""))
condition はエラー条件のリストである場合もあります。
このマクロは、ignore-errors
のマイルド版のようなものです。エラーを完全に抑制するのではなく、メッセージに変換します。メッセージの書式設定には文字列 format を使用します。format には単一の
‘%’ シーケンス(例:"Error:
%S")を指定します。エラーを通知する想定はないが、万が一エラーが発生した場合でも堅牢であるべきコードを囲む際は、with-demoted-errors を使用してください。なお、このマクロでは condition-case ではなく condition-case-unless-debug が使用されています。
場合によっては、エラーを捕捉し、完全なバックトレースや現在のバッファなど、エラーが発生した際の状況に関する情報を記録したいことがあります。残念ながら、condition-case
のハンドラではこうした情報は取得できません。というのも、ハンドラが実行される前にスタックが巻き戻されるため、ハンドラはエラーが通知された場所ではなく、condition-case
の動的コンテキストで実行されるからです。このような状況では、以下の形式を使用できます。
この特殊フォームはbodyを実行し、エラーなく実行された場合、返される値がhandler-bindフォームの値となります。この場合、handler-bindは影響を及ぼしません。
handlers は、(conditions handler)
形式の要素のリスト(ここで conditions
は処理対象となるエラー条件名、または条件名のリスト)である必要があり、handler
は、その評価結果が関数となる形式である必要があります。condition-case
と同様に、条件名はシンボルです。
body を実行する前に、handler-bind
はすべての handler フォームを評価し、body
の評価中に有効となるようそれらのハンドラをインストールします。エラーが通知されると、Emacs はアクティブなすべての condition-case および handler-bind
フォームの中から、これらの条件名の1つ以上を指定しているハンドラを検索します。最も内側にある一致するハンドラが handler-bind によってインストールされたものである場合、エラー記述子を保持する単一の引数とともに
handler 関数が呼び出されます。
condition-case の場合とは異なり、handler はエラーが発生した動的コンテキストで呼び出されます。つまり、変数バインディングの解除や unwind-protect
のクリーンアップ処理が行われることなく実行されるため、それらすべての動的バインディングが引き続き有効な状態となります。ただし、1点例外があります。handler 関数の実行中は、エラーを通知したコードから handler-bind
までの間にあるすべてのエラーハンドラが一時的に中断されます。そのため、エラーが通知された際、Emacs は handler 関数内または現在の handler-bind
の外にあるアクティブな condition-case および handler-bind フォームのみを検索します。また、字句的にバインドされた変数(字句的バインディング
を参照)は動的なスコープを持たないため、影響を受けないことに注意してください。
通常の関数と同様に、handler は(通常は throw を介して)非局所的に抜けるか、あるいは正常に復帰することができます。handler が正常に復帰した場合、それはハンドラがエラー処理を拒否したことを意味し、エラーハンドラの探索は中断された箇所から再開されます。
例えば、特定のコードの実行中に発生したすべてのエラーと、エラーが通知された時点でのバッファをログに記録しつつ、そのコードの動作にはそれ以外の影響を与えたくない場合、次のように記述できます。
(handler-bind
((error
(lambda (err)
(push (cons err (current-buffer)) my-log-of-errors))))
body-forms...)
これにより、内部で捕捉されなかったエラー、つまり body-forms…
から「エスケープ」したエラーのみが body-forms…
にログ出力されます。また、上記のハンドラは正常に復帰するため、これらのエラーが周囲の condition-case ハンドラ(あるいは handler-bind ハンドラ)に伝播するのを防ぐことはありません。
また、以下のコードのように、handler-bind
を使ってあるエラーを別のエラーに置き換えることもできます。このコードでは、body-forms
の実行中に発生する user-error 型のすべてのエラーを、単なる error に変換しています:
(handler-bind
((user-error
(lambda (err)
(signal 'error (cdr err)))))
body-forms...)
condition-case でもほぼ同様の結果が得られます:
(condition-case err
(progn body-forms...)
(user-error (signal 'error (cdr err))))
ただし、handler-bind
で新しいエラーを(再)通知しても、元のエラー時の動的環境が引き続き有効であるという点が異なります。つまり、例えばこの時点でデバッガを起動すれば、元のエラーを通知した箇所を含む完全なバックトレースが表示されるということです:
Debugger entered--Lisp error: (error "Oops")
signal(error ("Oops"))
#f(lambda (err) [t] (signal 'error (cdr err)))((user-error "Oops"))
user-error("Oops")
...
eval((handler-bind ((user-error (lambda (err) ...