nptのドキュメントです。
参照元:ANSI Common Lisp npt
前へ:hold変数の使い方
次へ:関数の登録
脱出関数とは、脱出が行われる可能性のある関数のことです。
以前、脱出関数とはerror
が生じる可能性のある関数だと説明しました。
しかし脱出とはerror
だけを意味するものではありません。
脱出とは、現在の処理を中断し、スタックフレームを 強制的に終わらせる命令のことを意味します。
脱出の要因となる命令は、次の5つ存在します。
tagbody
/ go
block
/ return-from
catch
/ throw
handler-case
restart-case
つまり、実行の流れを大きく変更し、 別のスタックフレームにジャンプすることを脱出と呼びます。
脱出はC言語のsetjmp
/longjmp
、C++言語のtry
/catch
のようなものです。
しかしnptではこれらの機能を使わず、 ただ脱出関数を終了させることで脱出を実現しています。
脱出関数とは何らかの技術のことを言っているのではなく、 C言語で関数を作成するときの決まり事のことです。
これから説明する決まり事を守ることで、 Common Lispの動作を実現できるようになります。
例文を用いて脱出関数の作成を説明します。
int test(void)
{
(NULL, "Start~%", NULL);
lisp_format8_(NULL, "TEST-THROW", NULL);
lisp_funcall8_(NULL, "End~%", NULL);
lisp_format8_
return 0;
}
脱出関数の名前はアンダーバー_
で終わるので、 例文にあるlisp_format8_
とlisp_funcall8_
は脱出関数です。
test
関数では、脱出関数の戻り値を全て無視していますが、 本来であれば正しく処理する必要があります。
正しい脱出関数に書き換えたものを下記に示します。
int test_(void)
{
if (lisp_format8_(NULL, "Start~%", NULL))
return 1;
if (lisp_funcall8_(NULL, "TEST-THROW", NULL))
return 1;
if (lisp_format8_(NULL, "End~%", NULL))
return 1;
return 0;
}
それぞれの脱出関数が値を返却した場合に return 1
を実行することで、 正しい脱出関数test_
を作成できました。
ここで、test-throw
関数が次のように定義されているとします。
defun test-throw ()
(throw 'hello 999)) (
test_
がlisp_funcall8_
を実行して test-throw
を呼び出したとき、 対応するcatch
が存在するならばthrow
が実施されるので、 lisp_funcall8_
関数は1
を返却します。
するとtest_
関数もそのままreturn 1
が実行されるため、 次のformat
文は実行されずにtest_
関数が終了します。
このようにして脱出が実現されます。
lisp_push_control
がある場合通常の関数test
内でlisp_push_control
が使用されているとします。
int test(void)
{
, v;
addr control
(&control);
lisp_push_control= Lisp_hold();
v (NULL, "Start~%", NULL);
lisp_format8_(v, "TEST-THROW", NULL);
lisp_funcall8_(NULL, "End: ~A~%", v, NULL);
lisp_format8_(control);
lisp_pop_control_
return 0;
}
脱出関数に書き換える際、push / popで囲まれている文は、 もし脱出が生じた場合でもすぐにreturn 1
を実行するのではなく、 必ずpopしてスタックフレームを解放する必要があります。
例えば次のように変更します。
int test_(void)
{
, v;
addr control
(&control);
lisp_push_control= Lisp_hold();
v if (lisp_format8_(NULL, "Start~%", NULL))
goto escape;
if (lisp_funcall8_(v, "TEST-THROW", NULL))
goto escape;
if (lisp_format8_(NULL, "End: ~A~%", v, NULL))
goto escape;
:
escapereturn lisp_pop_control_(control);
}
例文のように単純な場合はgoto
を用いても問題ありませんが、 複雑な構文になると分かりづらくなってしまいます。
そこで、nptの開発では次のような書き換えを行っていました。
static int test_call_(void)
{
;
addr v
= Lisp_hold();
v if (lisp_format8_(NULL, "Start~%", NULL))
return 1;
if (lisp_funcall8_(v, "TEST-THROW", NULL))
return 1;
if (lisp_format8_(NULL, "End: ~A~%", v, NULL))
return 1;
return 0;
}
int test_(void)
{
;
addr control
(&control);
lisp_push_control(void)test_call_();
return lisp_pop_control_(control);
}
さらに例文では、脱出の判定を行う際に
if (...)
return 1;
という文を多用しますが、 この構文は非常に多くの場所で使用するため、 下記のようなマクロを作成します。
#define Return(x) {if (x) return 1;}
このマクロを用いることで、test_call_
関数は次のように書き換えることができます。
static int test_call_(void)
{
;
addr v
= Lisp_hold();
v (lisp_format8_(NULL, "Start~%", NULL));
Return(lisp_funcall8_(v, "TEST-THROW", NULL));
Return(lisp_format8_(NULL, "End: ~A~%", v, NULL));
Return
return 0;
}
脱出関数を多く作成する予定があるのであれば、 この単純なマクロが非常に使いやすいので 使用を検討してみるのも良いかと思います。
まとめますと、脱出関数は次のような規則を課したものを言います。
_
で終わる(必須ではない)int
1
、通常は0
前章(hold変数の使い方)では、 階乗を出力する例文を作りました。
しかし説明の都合上エラー処理を行っていなかったため、 脱出関数の戻り値をすべて無視していました。
正しい脱出関数に書き換えたものを下記に示します。
static int fact_(addr x, addr value)
{
, y;
addr control
if (! lisp_plus_p(value)) {
(x, 1);
lisp_fixnumreturn 0;
}
(&control);
lisp_push_control= Lisp_hold();
y if (lisp_funcall8_(y, "1-", value, NULL))
goto escape;
if (fact_(y, y))
goto escape;
if (lisp_funcall8_(x, "*", value, y, NULL))
goto escape;
:
escapereturn lisp_pop_control_(control);
}
int main_lisp(void *ignore)
{
, x, y;
addr control
(&control);
lisp_push_control= Lisp_hold();
x = Lisp_hold();
y (y, 123);
lisp_fixnumif (fact_(x, y))
goto escape;
if (lisp_format8_(NULL, "fact: ~A! = ~A~%", y, x, NULL))
goto escape;
:
escapereturn lisp_pop_control_(control);
}
例文のfact_
関数はこれ以上修正の必要が無い完成されたものになります。