hold変数の使い方

nptのドキュメントです。
参照元:ANSI Common Lisp npt
前へ:nptをC言語で使用する
次へ:脱出関数

2.1 基本的な作成方法の解説

本書ではnptモジュールを用いてC言語で開発をするための基本を示します。
主な内容は、hold変数の使い方になります。

2.2 エラーの扱いについて

開発をするうえで無視できないのがエラーです。
しかしエラーの扱いは難しいため、何回かに分けて解説する必要があります。
本章では基本を説明するということなので、エラー処理については説明しません。

もしエラーが発生した場合はどうなるかを見てみます。
例として下記の文を実行します。

int main_lisp(void *ignore)
{
    lisp_error8_("Hello", NULL);
    return 0;
}

lisp_error8_関数はsimple-error conditionを実行するものであり、 Common Lispでは次の文とほぼ等しくなります。

(error "Hello")

実行結果は下記の通り。

$ ./a.out
ERROR: SIMPLE-ERROR
Hello

There is no restarts, abort.


**************
  LISP ABORT
**************
$

SIMPLE-ERRORが検知され、メッセージが出力されたあとで restartが見つからずABORTされました。
LISP ABORTとはプロセスが強制終了されたことを意味します。

本来であればSIMPLE-ERRORは適切に処理されるのが望ましいのですが、 本章ではこれで十分です。

以降の説明では、エラーが発生した場合にLISP ABORTされるとして話を進めます。

2.3 例文を用いて説明する

開発の基本的な考え方を示すために、例として階乗の関数を作ります。
階乗とは、次のようなものを言います。

6! = 6*5*4*3*2*1

6の階乗は720です。
Common Lispでは、次のように簡単に実装できます。

(defun fact (x)
  (if (not (plusp x))
    1
    (* x (fact (1- x)))))

C言語でも実装できます。

int fact(int x)
{
    if (x <= 0)
        return 1;
    else
        return x * fact(x - 1);
}

しかしC言語ではbignumが使えないため、 例えばfact(123)は求めることができません。
参考までに123の階乗は下記のとおりです。

1214630436702532967576624324188129585545421708848338231532891816182923
5892362167668831156960612640202170735835221294047782591091570411651472
186029519906261646730733907419814952960000000000000000000000000000

本章の目的は、C言語でbignumにも対応したコードを作成し、 nptモジュールの使い方に触れていくことにあります。

2.4 hold変数

Common Lispのオブジェクトを扱うには、 「hold変数」というオブジェクトを用いて値をやり取りする必要があります。
hold変数とはレキシカル変数のようなものであり、 C言語上で扱いやすいように作られたLispオブジェクトです。
この変数の扱いを覚えることが本章の目的でもあります。

使用するためには開始と終了を宣言し、 スタックフレームの確保と解放を行う必要があります。
下記の関数を用いて行います。

lisp_push_controlはhold変数の開始を宣言し、 スタックフレーム上に新しい領域を設けます。
lisp_pop_control_はhold変数の終了を宣言し、 対応するhold変数の解放処理を行います。

使用例を下記に示します。

/* 変数宣言 */
addr control, x, y;

lisp_push_control(&control);
x = Lisp_hold();  /* hold変数の確保 */
y = Lisp_hold();  /* hold変数の確保 */
lisp_pop_control_(control);

例文で示した通り、hold変数xyの確保をLisp_hold関数で行っています。

下記の例では、すでに階乗を求める関数fact_が作成されているものとして、 結果の値を表示するコードを示します。

int main_lisp(void *ignore)
{
    addr control, x, y;

    lisp_push_control(&control);
    x = Lisp_hold();
    y = Lisp_hold();
    lisp_fixnum(y, 123);
    fact_(x, y);
    lisp_format8_(NULL, "fact: ~A! = ~A~%", y, x, NULL);
    lisp_pop_control_(control);

    return 0;
}

変数xyには、Lisp_hold関数によりhold変数が代入されます。
lisp_fixnum関数では、hold変数y123という数値を代入します。
fact_関数では、hold変数xに、123の階乗の値を代入します。
lisp_format8_関数では、format文を実行します。

あとはfact_という関数を作成すれば完了です。

2.5 階乗関数の作成

関数を作成するまえに、関数名の規則について説明します。
関数の名前には、fact_lisp_format8_のように、 名前の最後がアンダーバー_で終わるものと終わらないものがあります。

簡単に言うとerrorが発生する可能性があるものにアンダーバーが付いています。
このような関数を「脱出関数」と呼びます。
脱出についての詳しい説明は別章で行います(脱出関数)。
命名規則は自主的なものなので、アンダーバーを付けるかどうかは自由です。

それではfact_関数の内容を示します。

static int fact_(addr x, addr value)
{
    addr control, y;

    if (! lisp_plus_p(value)) {
        lisp_fixnum(x, 1);
        return 0;
    }

    lisp_push_control(&control);
    y = Lisp_hold();
    lisp_funcall8_(y, "1-", value, NULL);
    fact_(y, y);
    lisp_funcall8_(x, "*", value, y, NULL);
    lisp_pop_control_(control);

    return 0;
}

最初のlisp_plus_p文では、value0以下の場合、 hold変数x1を代入して終了します。

もし変数valueの値が1以上であった場合は、 lisp_push_control以降が実行されます。

hold変数yを確保し、valueから1減算した値を格納します。
lisp_funcall8_関数は、文字列で表される関数を実行するものです。

次にhold変数yを用いてfact_関数を再帰呼出し、 その結果を変数y自身に代入します。

得られた結果yと、引数である変数valueの値を掛け算し、 結果をfact_の戻り値として返却します。

最後にlisp_pop_control_により、 hold変数の解放を行います。

2.6 階乗のコードを実行する

まとめると次のようになります。

static int fact_(addr x, addr value)
{
    addr control, y;

    if (! lisp_plus_p(value)) {
        lisp_fixnum(x, 1);
        return 0;
    }

    lisp_push_control(&control);
    y = Lisp_hold();
    lisp_funcall8_(y, "1-", value, NULL);
    fact_(y, y);
    lisp_funcall8_(x, "*", value, y, NULL);
    lisp_pop_control_(control);

    return 0;
}

int main_lisp(void *ignore)
{
    addr control, x, y;

    lisp_push_control(&control);
    x = Lisp_hold();
    y = Lisp_hold();
    lisp_fixnum(y, 123);
    fact_(x, y);
    lisp_format8_(NULL, "fact: ~A! = ~A~%", y, x, NULL);
    lisp_pop_control_(control);

    return 0;
}

実行結果を下記に示します。

$ ./a.out
fact: 123! = 121463043670253296757662432418812958554542170884833823153
2891816182923589236216766883115696061264020217073583522129404778259109
1570411651472186029519906261646730733907419814952960000000000000000000
000000000

以上にて、lisp_push_controllisp_pop_control_Lisp_holdによる hold変数の使い方が理解できたと思います。