0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Common Lisp風のLISPを作ってみる(5.環境)

Last updated at Posted at 2024-08-28

今回はmy-lisp2の環境(environment)モジュールについて解説します。Common Lispにおける環境とは、束縛(binding)の集合のことです。束縛とは、ある名前とそれが指し示すものの関連(association)です。つまり、連想リストのようなものだと考えると分かりやすいでしょう。
 この環境を作るにあたって、「計算機プログラムの構造と解釈」、いわゆるSICP本のモデルを採用しています。ちなみに私はminghai版を読みました。そのモデルでは、環境は複数のフレーム(それぞれが連想リスト)で表現されていました。なので本モジュールでも環境を複数の連想リストで表現できるようにしています。
ソースコード

environment.h

まずはヘッダーファイルからです。今回は関数が多いですね。

environment.h
/* environment.h */

#ifndef ENVIRONMENT_H_
#define ENVIRONMENT_H_

void *environment_init(void *parent);
void environment_add(void *env, void *key, void *val);
int environment_exists(void *env, void *key);
int environment_get(void *env, void *key, void **val);
int environment_modify(void *env, void *key, void *val);
int environment_remove(void *env, void *key);
int environment_get_recurse(void *env, void *key, void **val);
int environment_exists_recurse(void *env, void *key);
int environment_modify_recurse(void *env, void *key, void *val);

#endif

environment.c

今回もファイルを上から見ていきましょう。まずinclude文からです。

environment.c
/*
 * environment.c
 */

#include <stdlib.h>
#include <string.h>
#include "../chapter02/type.h"
#include "helper.h"
#include "environment.h"

environment_init関数は環境を初期化します。初期化する時に、そのフレームが「指し示す」フレームをparent引数に指定します。この最初のコンスセルは環境のフレームそのものを表現しているのであって、連想リストではないことに注意してください。

environment.c
/* LISPの環境において、通常、parentという言い方はしないことに注意 */
void *environment_init(void *parent) {
    return cons(NIL, parent);
}

environment_add関数はフレームにキー・バリューのペアを1組追加します。

environment.c
void environment_add(void *env, void *key, void *val) {
    void *assoc = cons(key, val);
    void *joint = cons(assoc, car(env));
    rplaca(env, joint);
}

environment_exists関数はそのフレームにkeyがあるかどうかを判別します。

environment.c
int environment_exists(void *env, void *key) {
    SYMBOL *sym1 = (SYMBOL *)key;
    void *p = car(env);
    while (p != NIL) {
        SYMBOL *sym2 = (SYMBOL *)car(car(p));
        if (strcmp(get_symbol_string(sym2), get_symbol_string(sym1)) == 0) {
            return 1;
        }
        p = cdr(p);
    }
    return 0;
}

environment_get関数は与えられたkeyに対応するvalを取得します。もしフレーム内にkeyがない場合は返り値が0になります。

environment.c
int environment_get(void *env, void *key, void **val) {
    SYMBOL *sym1 = (SYMBOL *)key;
    void *p = car(env);
    while (p != NIL) {
        SYMBOL *sym2 = (SYMBOL *)car(car(p));
        if (strcmp(get_symbol_string(sym2), get_symbol_string(sym1)) == 0) {
            *val = cdr(car(p));
            return 1;
        }
        p = cdr(p);
    }
    return 0;
}

environment_modify関数は、フレーム内のキー・バリューのペアにおいてバリューを書き換えます。キーが存在しなかった場合は0を返します。

environment.c
int environment_modify(void *env, void *key, void *val) {
    SYMBOL *sym1 = (SYMBOL *)key;
    void *p = car(env);
    while (p != NIL) {
        SYMBOL *sym2 = (SYMBOL *)car(car(p));
        if (strcmp(get_symbol_string(sym2), get_symbol_string(sym1)) == 0) {
            rplacd(car(p), val);
            return 1;
        }
        p = cdr(p);
    }
    return 0;
}

environment_remove関数はフレーム内において与えられたkeyのペアを削除します。ペアが存在しなかった場合は0を返します。

environment.c
int environment_remove(void *env, void *key) {
    SYMBOL *sym1 = (SYMBOL *)key;
    SYMBOL *sym2;
    void *p = car(env);
    void *q;
    if (p == NIL) return 0;
    sym2 = (SYMBOL *)car(car(p));
    if (strcmp(get_symbol_string(sym2), get_symbol_string(sym1)) == 0) {
        rplaca(env, cdr(p));
        return 1;
    }
    q = p;
    p = cdr(p);
    while (p != NIL) {
        sym2 = (SYMBOL *)car(car(p));
        if (strcmp(get_symbol_string(sym2), get_symbol_string(sym1)) == 0) {
            rplacd(q, cdr(p));
            return 1;
        }
        q = p;
        p = cdr(p);
    }
    return 0;
}

environment_get_recurse, environment_exists_recurse, environment_modify_recurse関数は上記の操作を再帰的に行います。つまり、フレームが指し示すフレームについても、その先が無くなるまで繰り返します。

environment.c
int environment_get_recurse(void *env, void *key, void **val) {
    while (env != NIL) {
        if (environment_get(env, key, val)) return 1;
        env = cdr(env);
    }
    return 0;
}

int environment_exists_recurse(void *env, void *key) {
    while (env != NIL) {
        if (environment_exists(env, key)) return 1;
        env = cdr(env);
    }
    return 0;
}

int environment_modify_recurse(void *env, void *key, void *val) {
    while (env != NIL) {
        if (environment_modify(env, key, val)) return 1;
        env = cdr(env);
    }
    return 0;
}

helper.h

ヘルパー関数が追加されています。また、SYMBOL型はtype.hで定義されているのでtype.hのインクルードも行います。

helper.h
char * get_symbol_string(SYMBOL *symbol);
int consp(void *obj);
void *car(void *obj);
void *cdr(void *obj);
void *rplaca(void *cons1, void *obj);
void *rplacd(void *cons1, void *obj);

helper.c

get_symbol_string関数はシンボル型オブジェクトが保持している文字列を取得します。

helper.c
char * get_symbol_string(SYMBOL *symbol) {
    return symbol->str;
}

consp関数はLispのconsp関数同様、コンスセルの判定を行います。

helper.c
int consp(void *obj) {
    return (((HEADER *)obj)->type == TYPE_CONS);
}

car関数も同様です。ただしアトムがこの関数に与えられるとステートをエラーにして0を返します。my-lisp2におけるvoid *0は異常状態を表します。詳細はstate.h, state.cの項で解説します。

helper.c
void *car(void *obj) {
    if (obj == NIL) return NIL;
    if (!consp(obj)) {
        fprintf(stderr, "CARはアトムに適用できません\n");
        state = STATE_ERROR;
        return 0;
    }
    return ((CONS *)obj)->car;
}

cdr関数です。

helper.c
void *cdr(void *obj) {
    if (obj == NIL) return NIL;
    if (!consp(obj)) {
        fprintf(stderr, "CDRはアトムに適用できません\n");
        state = STATE_ERROR;
        return 0;
    }
    return ((CONS *)obj)->cdr;
}

rplaca関数です。

helper.c
void *rplaca(void *cons1, void *obj) {
    if (!consp(cons1)) {
        fprintf(stderr, "コンスセルではありません\n");
        state = STATE_ERROR;
        return 0;
    }
    ((CONS *)cons1)->car = obj;
    return cons1;
}

rplacd関数です。

helper.c
void *rplacd(void *cons1, void *obj) {
    if (!consp(cons1)) {
        fprintf(stderr, "コンスセルではありません\n");
        state = STATE_ERROR;
        return 0;
    }
    ((CONS *)cons1)->cdr = obj;
    return cons1;
}

state.h

STATE列挙体はアプリケーションのステートを表現します。block_name_when_return, return_value_when_return, tag_name_when_goはスペシャルオペレーターreturn-from, goで用いる変数です。

state.h
/*
 * state.h
 */

#ifndef STATE_H_
#define STATE_H_

typedef enum {
    STATE_NORMAL,
    STATE_ERROR,
    STATE_EXIT,
    STATE_JUMP_RETURN,
    STATE_JUMP_GO
} STATE;

extern STATE state;
extern void *block_name_when_return;
extern void *return_value_when_return;
extern void *tag_name_when_go;

#endif

STATE列挙体の意味を記します。

STATEの値 意味
STATE_NORMAL 通常の状態
STATE_ERROR エラーの状態
STATE_EXIT 終了するよう指示された状態(exit関数など)
STATE_JUMP_RETURN return-fromからのジャンプ
STATE_JUMP_GO goからのジャンプ

my-lisp2では例外処理の機構は各関数が責任を持つこととします。void *0の時はとにかく確保したリソースを解放してreturn 0することを心がけます。そのことにより、アプリケーションの安全性を高めます。

state.c

このファイルには共有されるグローバル変数の実体が配置されています。

state.c
/*
 * state.c
 */

#include "state.h"

STATE state = STATE_NORMAL;
void *block_name_when_return = 0;
void *return_value_when_return = 0;
void *tag_name_when_go = 0;
0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?