nptのドキュメントです。
参照元:ANSI Common Lisp npt
前へ:LISP ABORT
次へ:スタートアップ
脱出の操作を説明ます。
以前、脱出の理由を5つ挙げました。
tagbody/goblock/return-fromcatch/throwhandler-caserestart-caseこの中で、tagbody/goとblock/return-fromは レキシカルな環境での使用を前提としているため、 C言語では使用不可とします。
残り3つの操作はC言語でも実装可能です。
脱出の操作は、発生と終了だけではなく割り込みも考えなくてはいけません。
脱出の割り込みとは、unwind-protectのことです。
まとめると、本章では下記の機能について説明します。
catch/throwhandler-caserestart-caseunwind-protectcatch/throw脱出の操作で最も簡単なのがcatch/throwです。
catchはスタックフレームに対してsymbolを登録します。
よってlisp_push_controlは必ず実行する必要があります。
登録は下記の関数を使用します。
void lisp_catch(addr symbol);throwは下記の関数を使用します。
int lisp_throw_(addr symbol);throw関数は現在のスタックフレームから順に遡って行き、 引数symbolに対応するcatchが無い場合はerror、 存在する場合はcatchの脱出を行います。
必ず脱出するため、戻り値は判定するまでもなく0以外です。
脱出中は、どこまで遡るかというスタックフレームの位置情報を保有しており、 現在のスタックフレームが脱出の到達地点に達したかどうかは 下記の関数を使用することで確認できます。
int lisp_break_control(void);現在脱出中でありかつ現在のスタックフレームが 到達地点にいる場合は真(0以外)を返却します。
catch/throwの場合は、この関数が真のときに 非脱出に切り替えることでthrowが完了します。
非脱出に切り替える関数は下記のとおりです。
void lisp_reset_control(void);手順をまとめると次のようになります。
lisp_push_controlでスタックフレームの作成lisp_catchでsymbolを登録lisp_throw_で脱出lisp_break_controlで到達点を調査lisp_reset_controlで非脱出に移行例として下記の文を示します。
(catch 'hello
(throw 'hello 222)
333)throwにより222が返却されるので、333は無視されて 全体の返却値は222となります。
int throw_hello_222_(addr x)
{
lisp_fixnum(x, 222);
lisp_set_result_control(x);
if (lisp_intern8_(x, NULL, "HELLO"))
return 1;
return lisp_throw_(x);
}
int main_call_(addr x)
{
addr control;
lisp_push_control(&control);
/* (catch 'hello ...) */
if (lisp_intern8_(x, NULL, "HELLO"))
goto escape;
lisp_catch(x);
/* (throw 'hello 222) */
if (throw_hello_222_(x))
goto escape;
/* (values 333) */
lisp_fixnum(x, 333);
lisp_set_result_control(x);
escape:
if (lisp_break_control())
lisp_reset_control();
return lisp_pop_control_(control);
}
int main_lisp(void *ignore)
{
addr control, x;
lisp_push_control(&control);
x = Lisp_hold();
if (main_call_(x))
goto escape;
lisp_result_control(x);
if (lisp_format8_(NULL, "RESULT: ~A~%", x, NULL))
goto escape;
escape:
return lisp_pop_control_(control);
}実行結果はRESULT: 222です。
handler-case基本的な構成はcatch/throwと変わりません。
conditionの登録は下記の関数を使います。
int lisp_handler_case_(addr name, addr call);そしてthrowにあたるのがsignalかerrorです。
例文を下記に示します。
int main_call_(addr x)
{
addr control, y;
lisp_push_control(&control);
y = Lisp_hold();
/* handler-case */
if (lisp_intern8_(x, NULL, "AAA"))
goto escape;
if (lisp_eval8_(y, "(lambda (c) (declare (ignore c)) 222)"))
goto escape;
if (lisp_handler_case_(x, y))
goto escape;
/* (signal 'aaa) */
if (lisp_eval8_(NULL, "(signal 'aaa)"))
goto escape;
/* (values 333) */
lisp_fixnum(x, 333);
lisp_set_result_control(x);
escape:
if (lisp_break_control())
lisp_reset_control();
return lisp_pop_control_(control);
}
int main_lisp(void *ignore)
{
addr control, x;
lisp_push_control(&control);
x = Lisp_hold();
if (lisp_eval8_(NULL, "(define-condition aaa () ())"))
goto escape;
if (main_call_(x))
goto escape;
lisp_result_control(x);
if (lisp_format8_(NULL, "RESULT: ~A~%", x, NULL))
goto escape;
escape:
return lisp_pop_control_(control);
}実行結果はRESULT: 222です。
restart-case構成はhandler-caseと同じです。
restartオブジェクトを生成し、 現在のスタックフレームに対して登録することで実現します。
restartオブジェクトは下記の関数で生成します。
void lisp_restart_make(addr x, addr name, addr call, int casep);restartの登録は下記の関数を使います。
void lisp_restart_push(addr restart);使用例をあげます。
int main_call_(addr x)
{
addr control, y;
lisp_push_control(&control);
y = Lisp_hold();
/* restart-case */
if (lisp_intern8_(x, NULL, "HELLO"))
goto escape;
if (lisp_eval8_(y, "(lambda () 222)"))
goto escape;
lisp_restart_make(x, x, y, 1);
lisp_restart_push(x);
/* (invoke_restart 'hello) */
if (lisp_eval8_(NULL, "(invoke-restart 'hello)"))
goto escape;
/* (values 333) */
lisp_fixnum(x, 333);
lisp_set_result_control(x);
escape:
if (lisp_break_control())
lisp_reset_control();
return lisp_pop_control_(control);
}
int main_lisp(void *ignore)
{
addr control, x;
lisp_push_control(&control);
x = Lisp_hold();
if (main_call_(x))
goto escape;
lisp_result_control(x);
if (lisp_format8_(NULL, "RESULT: ~A~%", x, NULL))
goto escape;
escape:
return lisp_pop_control_(control);
}実行結果はRESULT: 222です。
unwind-protectunwind-protectは、第一引数の式を実行した後、 cleanupフォームを実行する前に下記の値の退避が必要です。
脱出情報とは、現在の脱出モードと 脱出先のスタックフレームの情報です。
返却値の情報とは、list_result_controlなどで取得できる値であり、 valuesの値そのもののため、ひとつではなく複数の値が対象となります。
unwind-protectを実行する前に 現在の状態を一括して退避しておき、 cleanupフォームを実行した後で状態をロールバックさせます。
下記の関数は、脱出情報と返却値を同時に保存する命令です。
void lisp_save_control(addr *ret);情報はhold変数と同様、スタックフレームを利用して保存します。
保存後は下記の関数を用いて非脱出に移行します。
void lisp_reset_control(void);もともと非脱出の場合でも実行はできます。
それでは保存が終わりましたので、cleanup処理を実施します。
もしcleanup処理中に新たな脱出が生じた場合は、 lisp_save_controlで保存した情報は無視して、 最新の脱出処理を優先させます。
無事cleanup処理が完了したら、下記の関数で復元します。
void lisp_rollback_control(addr value);最後にlisp_pop_control_を実行して保存情報を破棄します。
実行例を示します。
下記の例では、返却値の保存と復元がされたことを示しています。
int main_lisp(void *ignore)
{
addr control, save, x;
lisp_push_control(&control);
x = Lisp_hold();
/* 100 */
lisp_fixnum(x, 100);
lisp_set_result_control(x);
/* save */
lisp_save_control(&save);
lisp_reset_control();
/* 200 */
lisp_fixnum(x, 200);
lisp_set_result_control(x);
/* rollback */
lisp_rollback_control(save);
lisp_result_control(x);
lisp_format8_(NULL, "RESULT: ~A~%", x, NULL);
return lisp_pop_control_(control);
}実行結果はRESULT: 100です。