C言語でLispを作ろうとしている人がおられるみたい。
(古くからのLispファンの私としては、大歓迎)
私だったらSymbolはこう作るを書いてみます。
基礎知識
お気楽 Common Lisp プログラミング入門から引用させていただきます。
http://www.nct9.ne.jp/m_hiroi/clisp/abcl12.html
シンボルの構造
私も上の記事と同じく、こういう認識です。
私なら パッケージも足します。
なので、私が定義するならこうします。
typedef struct SYMBOL {
HEADER h;
HEADER* name; // 名前の文字列
HEADER* value; //変数値を格納
HEADER* function; // 関数定義を格納
HEADER* plist; // 属性リストを格納
HEADER* package;
} SYMBOL;
HEADER*としましたが、void*でも構いません。
それぞれのフィールドは以下の Symbol-XX 関数に対応します。
SBCLで実験してみました。
This is SBCL 2.4.5, an implementation of ANSI Common Lisp.
* (symbol-name 'car)
"CAR"
*(symbol-value 'car)
→ エラー
* (symbol-function 'car)
#<FUNCTION CAR>
* (symbol-plist 'car)
NIL
* (symbol-package 'car)
#<PACKAGE "COMMON-LISP">
注意点ですが、carの定義は環境の中に入っているのではありません。
symbolの中のフィールドに入っています。
イメージ図
C言語でLispを作ろうとされている方のtypeの数字になるべく合わせました。
carというシンボルは、このようなメモリ配置になります。
SBCLで実験
* (setq foo 123)
ワーニングが出ます(defvarしろよとSBCLが文句を言う)
* (let ((foo 999))
(list foo (symbol-value 'foo)))
(999 123) ;; (1)
(1) で、 999と123と、別の値が返ってきているため、それぞれが別の場所に入っているのがわかります。
999は環境であり、123はsymbol-valueのフィールドですね。
ダイナミック束縛
再び、お気楽 Common Lisp プログラミング入門から引用させていただきます。
http://www.nct9.ne.jp/m_hiroi/clisp/abcl19.html
ふたたびSBCLで実験します。
(defvar y 10) ; (1)
(defun bar ()
(list y (symbol-value 'y))) ; (2)
(defun bar2 (y) ; (3)
(format t "y = ~S" y) ; (4-1)
(bar))
* (bar2 5) ; (5)
y = 5 ; (4-2)
(5 5) ; (6)
(1)CommonLispの仕様では、defvarすると束縛の動きが変わると書いてあります
(2)で、yが2つ登場しますが、この関数の引数にはyがありません。
さて、どこのyを見ているのでしょうか?
(3) barを呼び出す為の関数 bar2を定義します。
この引数には、yがあります。
これは (2)の動作を見るために、わざと y という変数名を使っています。
(5) で、bar2を引数5で呼び出します。
すると y=5 となりますが、ダイナミックバインディングなので、通常とは動作が違うそうです。
(4-1)でformatで、yの値を確認します。
(4-2)に結果が表示されており、5と、予想どおりです。
そのあと関数barを呼び出します。
(2)の位置で、 y と(symbol-value 'y)をリストにしていますが、
(5 5)が帰ってきた事から、symbolのvalue欄をダイナミックの値を入れるのに使っていると、想像できます。
もし、yが環境の中から値を取り出しているなら、symbol-valueは(1)の10になると、私は思います。
以上がSBCLでの動作確認になります。
変数から値を取り出す方法
再度、お気楽 Common Lisp プログラミング入門より引用させてもらいます。
局所変数にアクセスするときは、この連想リストから該当する変数を探すのです。
見つからない場合は大域的な環境 (スペシャル変数) を検索します。
私なりに書き直すと、
レキシカルスコープから変数を検索する
if (見つかった) {
レキシカルスコープから見つかった値を返す
} else {
symbol-value欄の値を返す
}
想像ですが、symbolに、レキシカル変数か、ダイナミック変数かを区別するフラグが付いている可能性が高いです。
その場合は、以下のような処理だと思います。
その変数は、ダイナミック変数か?(フラグを見る)
if (ダイナミック変数の時) {
symbol-valueの値を返す
}
レキシカルスコープから変数を検索する
if (見つかった) {
レキシカルスコープから見つかった値を返す
} else {
symbol-value欄の値を返す
}
蛇足、Clojureの場合
Clojureの場合、Symbolの大半の機能は、Varという別のクラスに移動しています。
このVarクラスには、ダイナミックかどうかというフラグが存在します。(ソースを確認済みです)
ここまでの記事は、xyzzyのソースは全く見ずに書いています。
全て私が、今まで作ってきた、いくつかのLisp実装の知見によるものです。
xyzzyの場合
お気楽 Common Lisp プログラミング入門のサイトではxyzzyによるCommonLispの記事が多数あります。
私が一番よく使ったCommonLispも、実はxyzzyです。
その頃、私もCommonLispを自作していたのですが、xyzzyのあまりの凄さに舌を巻きました。
xyzzyは天才、亀井さんが作られたもので、私の作品ではありません。
ソースは以下からダウンロードできます。
ソースは巨大なので、深入りしないように。
今回の話題である、シンボルを見てみます。
# define SFconstant (1 << 0) // 定数
# define SFspecial (1 << 1) // グローバルなスペシャル変数
# define SFlambda_key (1 << 2) // lambda-listキーワード
# define SFbuffer_local (1 << 3) // バッファローカル
# define SFmake_buffer_local (1 << 4) // 値がセットされたらバッファローカル
# define SFdynamic_bind (1 << 5) // 動的にバインドされている
class lsymbol: public lisp_object
{
public:
u_int flags;
lisp value;
lisp fn;
lisp plist;
lisp package;
lisp name;
};
私が提案したのと、亀井さんの実装は同じでした。
私は亀井さんのソースを覚えている訳がありません。
でも一致している。さすが私。(自画自賛)
終わりに
C言語でLispを作成しようとしている作者の人へ
いろいろツッコミを入れて恐縮ですが、やる気をそぎたい訳ではないので、誤解無きように、お願いします。
古くからLispを愛するもの、Lispインタープリタの自作愛好家として
貴殿の実装を応援してます。