Next: 動的にロードされるモジュールの記述, Previous: C方言, Up: GNU Emacsの内部 [Contents][Index]
LispプリミティブとはCで実装されたLisp関数です。Lispから呼び出せるようにC関数インターフェースの詳細はCのマクロで処理されます。新たなCコードの記述のしかたを真に理解するにはソースを読むのが唯一の方法ですが、ここではいくつかの事項について説明します。
スペシャルフォームの例として以下はeval.cのorです(通常の関数は同様の一般的な外観をもつ)。
DEFUN ("or", For, Sor, 0, UNEVALLED, 0,
doc: /* Eval args until one of them yields non-nil,
then return that value.
The remaining args are not evalled at all.
If all args return nil, return nil.
usage: (or CONDITIONS...) */)
(Lisp_Object args)
{
Lisp_Object val = Qnil;
while (CONSP (args))
{
val = eval_sub (XCAR (args));
if (!NILP (val))
break;
args = XCDR (args);
maybe_quit ();
}
return val; }
ではDEFUNマクロの引数について詳細に説明しましょう。以下はそれらのテンプレートです:
DEFUN (lname, fname, sname, min, max, interactive, doc)
これは関数名として定義するLispシンボル名。上記例ではor。
これは関数のC関数名。これはCコードでその関数を呼び出すために使用される名前。名前は慣習として‘F’の後にLisp名をつけて、Lisp名のすべてのダッシュ(‘-’)をアンダースコアに変更する。つまりCコードから呼び出す場合にはForを呼び出す。
これはLispでその関数を表すsubrオブジェクト用にデータ保持のための構造体に使用されるC変数名。この構造体はそのシンボルを作成してそれの定義にsubrオブジェクトを格納する初期化ルーチンでLispシンボル名を伝達する。慣習により常にfnameの‘F’を‘S’に置き換えた名前になる。
これは関数が要求する引数の最小個数。関数orは最小で0個の引数を受け入れる。
これは関数が受け入れる引数の最大個数が定数なら引数の最大個数。あるいはUNEVALLEDなら未評価の引数を受け取るスペシャルフォーム、MANYなら評価される引数の個数に制限がないことを意味する(&restと等価)。UNEVALLEDとMANYはいずれもマクロ。maxが数字ならminより大きく8より小さいこと。
これはLisp関数でinteractiveの引数として使用されるようなインタラクティブ仕様(文字列)である(interactiveの使用を参照)。orの場合は0
(nullポインター)でありorがインタラクティブに呼び出せないことを示す。値""はインタラクティブに呼び出し時に、関数が引数を受け取るべきではないことを示す。値が‘"(’で始まる場合には、その文字列はLispフォームとして評価される。たとえば:
DEFUN ("foo", Ffoo, Sfoo, 0, 3,
"(list (read-char-by-name \"Insert character: \")\
(prefix-numeric-value current-prefix-arg)\
t)",
doc: /* … */)
これはドキュメント文字列。複数行を含むために特別なことを要しないので、これにはCの文字列構文ではなくCコメント構文を使用する。‘doc:’の後のコメントはドキュメント文字列として認識される。コメントの開始と終了の区切り文字‘/*’と‘*/’はドキュメント文字列の一部にはならない。
ドキュメント文字列の最後の行がキーワード‘usage:’で始まる場合には、その行の残りの部分は引数リストをドキュメント化するためのものとして扱われる。この方法によりCコード内で使用される引数名とは異なる引数名をドキュメント文字列内で使用することができる。その関数の引数の個数に制限がなければ‘usage:’は必須。
プロットフォームにたいして1つといったように、複数の定義をもつプリミティブがいくつかある(たとえばx-create-frame)。このような場合には各定義に同じドキュメント文字列を記述するよりも、ただ1つの定義が実際のドキュメントをもつようにしたほうがよい。他の定義はDOCファイルをパースする関数から無視される、‘SKIP’で始まるプレースホルダーをもつ。
Lispコードでのドキュメント文字列にたいするすべての通常ルール(ドキュメント文字列のヒントを参照)はCコードのドキュメント文字列にも適用される。
以下のようにドキュメント文字列の後に、そのプリミティブを実装するC関数にたいするC関数属性のリストがあるかもしれない:
DEFUN ("bar", Fbar, Sbar, 0, UNEVALLED, 0
doc: /* … */
attributes: attr1 attr2 …)
後に続けることにより複数の属性を指定できる。現在のところ以下の属性が認識される:
noreturn決してリターンしないC関数を宣言する。これはC11のキーワード_Noreturn、GCCの属性__attribute__ ((__noreturn__))に対応している(Function Attributes in Using the GNU
Compiler Collectionを参照)。
const引数以外の値を検査せず、リターン値以外に影響しない関数を宣言する。これはGCCの属性__attribute__ ((__const__))に対応している。
noinlineこれは関数がインラインとみなされることを抑止するGCCの属性__attribute__ ((__noinline__))に対応している。これはたとえばスタックベースの変数にたいするリンク時の最適化の効果を取り消すために必要になるかもしれない。
DEFUNマクロ呼び出しの後には、そのC関数にたいする引数リストを引数のタイプを含めて記述しなければなりません。そのプリミティブがLispで固定された最大個数をもつ引数を受け入れるなら、Lisp引数それぞれにたいして1つのC引数をもち、各引数のタイプはLisp_Objectでなければなりません(ファイルlisp.hではタイプLisp_Objectの値を作成する種々のマクロと関数が宣言されている)。プリミティブがスペシャルフォームなら、評価されないタイプLisp_Objectの単一のLisp引数を含むLispリストを受け取らなければなりません。プリミティブのLispの最大引数個数に上限がない場合には正確に2つのC引数をもたなければなりません。1つ目はLisp引数の個数、2つ目はそれらの値を含むブロックのアドレスです。これらはそれぞれptrdiff_t、Lisp_Object *のタイプをもちます。Lisp_Objectは任意のデータ型と任意のLispオブジェクトを保持できるので、実行時のみ実際のデータ型を判断できます。特定のタイプの引数だけを受け入れるプリミティブを記述したい場合は、適切な述語を使用してタイプを明確にチェックしなければなりません(型のための述語を参照)。
関数For自体ではローカル変数argsはEmacsのスタックマーキングによるガーベージコレクターが制御するオブジェクトを参照します。ガーベージコレクターはたとえCのLisp_Objectスタック変数から到達可能なオブジェクトを回収しなくても、文字列コンテンツやバッファーのテキストのようなオブジェクトの何らかのコンポーネントを移動するかもしれません。したがってこれらのコンポーネントにアクセスする関数はLispの評価を行なった後に、それらのアドレスの再取得に留意しなければなりません。これはコードが文字列コンテンツやバッファーテキストにたいするCポインターを維持するかわりに、バッファーや文字列の位置を維持して、Lispの評価を行なった後にその位置からCポインターを再計算する必要があることを意味しています。Lisp評価は直接と間接を問わず、eval_subやFevalの呼び出しを通じて発生する可能性があります。
ループ内部のmaybe_quit呼び出しに注意してください。この関数はユーザーがC-gを渡したかどうかをチェックして、もしそうなら処理をabortします。多数の繰り返しを要する可能性があるすべてのループ内でこれを行うべきです。この場合には引数のリストは非常に長くなるかもしれません。これはEmacsの応答性とユーザーエクスペリエンスを向上させます。
Emacsが一度ダンプされた後に変数に何か書き込まれているときには、その静的変数やグローバル変数にCの初期化を使用してはなりません。初期化されたこれらの変数はEmacsのダンプの結果として、(特定のオペレーティングシステムでは)読み取り専用となるメモリーエリアに割り当てられます。純粋ストレージを参照してください。
C関数の定義だけではLispプリミティブを利用可能にするのに十分ではありません。そのプリミティブにたいしてLispシンボルを作成して関数セルに適切なsubrオブジェクトを格納しなければなりません。このコードは以下のようになるでしょう:
defsubr (&sname);
ここでsnameはDEFUNの3つ目の引数として使用する名前です。
すでにLispプリミティブが定義されたファイルにプリミティブを追加する場合には、(そのファイル終端付近にある)syms_of_somethingという名前の関数を探してdefsubrの呼び出しを追加してください。ファイルにこの関数がない、または新たなファイルを作成する場合にはsyms_of_filename
(例:
syms_of_myfile)を追加します。それからemacs.cでそれらの関数が呼び出されるすべての箇所を探してsyms_of_filenameの呼び出しを追加してください。
関数syms_of_filenameはLisp変数として可視となるすべてのC変数を定義する場所でもあります。DEFVAR_LISPはタイプLisp_ObjectのC変数をLispから可視にします。DEFVAR_INTはタイプintのC変数を常に整数となる値をもつようにしてLispから可視にします。DEFVAR_BOOLはタイプintのC変数を常にtかnilのいずれかとなる値をもつようにしてLispから可視にします。DEFVAR_BOOLで定義された変数はバイトコンパイラーに使用されるリストbyte-boolean-varsに自動的に追加されることに注意してください。
これらのマクロはすべて3つの引数を期待します:
lnameLispプログラムが使用する変数名。
vnameCソース内の変数名。
docCコメントとしての変数用のドキュメント。詳細はドキュメントの基礎を参照のこと。
慣例として“ネイティブ”なタイプ(intとbool)の変数の定義時には、Cの変数名はLisp変数の-が_で置換されます。変数がタイプLisp_Objectをもつ際には、Cの変数名にVも前置します。たとえば
DEFVAR_INT ("my-int-variable", my_int_variable,
doc: /* An integer variable. */);
DEFVAR_LISP ("my-lisp-variable", Vmy_lisp_variable,
doc: /* A Lisp variable. */);
Lispではシンボルの値ではなくシンボル自身の参照を要する状況が存在します。1つは変数の値の一時的なオーバーライドであり、これはLispではletで行われます。これはCソースでは、specbindを使用して対応する定数シンボルを定義することにより行われます。慣例によりQmy_lisp_variableはVmy_lisp_variableに対応します。これを定義するにはDEFSYMマクロを使用します。たとえば
DEFSYM (Qmy_lisp_variable, "my-lisp-variable");
実際にバインディングを行うには:
specbind (Qmy_lisp_variable, Qt);
Lispシンボルではクォートの使用が必要な場合がありますが、Cで同様の効果を達成するためには対応する定数シンボルQmy_lisp_variableを使用します。たとえばLispでバッファーローカル変数(バッファーローカル変数を参照)を作成する際には以下のように記述します:
(make-variable-buffer-local 'my-lisp-variable)
C側の対応するコードは、以下のようにDEFSYMと組み合わせてFmake_variable_buffer_localを使用します。
DEFSYM (Qmy_lisp_variable, "my-lisp-variable"); Fmake_variable_buffer_local (Qmy_lisp_variable);
Cで定義されたLisp変数をdefcustomで宣言された変数のように振る舞わせたい場合には、cus-start.elに適切なエントリーを追加します。使用するフォーマットの説明はカスタマイゼーション変数の定義を参照してください。
タイプLisp_ObjectのファイルをスコープとするC変数を直接定義する場合には、以下のようにsyms_of_filename内でstaticproを呼び出してガーベージコレクションから保護しなければなりません:
staticpro (&variable);
以下はより複雑な引数をもつ別の関数例です。これはwindow.cからのコードであり、Lispオブジェクトを操作するためのマクロと関数の使用を示すものです。
DEFUN ("coordinates-in-window-p", Fcoordinates_in_window_p,
Scoordinates_in_window_p, 2, 2, 0,
doc: /* Return non-nil if COORDINATES are in WINDOW.
…
or `right-margin' is returned. */)
(register Lisp_Object coordinates, Lisp_Object window)
{
struct window *w;
struct frame *f;
int x, y;
Lisp_Object lx, ly;
w = decode_live_window (window); f = XFRAME (w->frame); CHECK_CONS (coordinates); lx = Fcar (coordinates); ly = Fcdr (coordinates); CHECK_NUMBER (lx); CHECK_NUMBER (ly); x = FRAME_PIXEL_X_FROM_CANON_X (f, lx) + FRAME_INTERNAL_BORDER_WIDTH (f); y = FRAME_PIXEL_Y_FROM_CANON_Y (f, ly) + FRAME_INTERNAL_BORDER_WIDTH (f);
switch (coordinates_in_window (w, x, y))
{
case ON_NOTHING: /* NOT in window at all. */
return Qnil;
…
case ON_MODE_LINE: /* In mode line of window. */
return Qmode_line;
…
case ON_SCROLL_BAR: /* On scroll-bar of window. */
/* Historically we are supposed to return nil in this case. */
return Qnil;
default:
emacs_abort ();
}
}
CコードはCで記述されていなければ名前で呼び出すことはできないことに注意してください。Lispで記述された関数を呼び出すには関数funcallをCで具現化したFfuncallを使用します。Lisp関数funcallは個数制限なしの引数を受け付けるので、Cでの引数はLispレベルでの引数個数とそれらの値を含む1次元配列という2個の引数になります。Lispレベルでの1つ目の引数は呼び出す関数、残りはそれに渡す引数です。
C関数call0、call1、call2、...は個数が固定された引数でLisp関数を手軽に呼び出す便利な方法を提供します。これらはFfuncallを呼び出すことにより機能します。
eval.cは例を探すのに適したファイルです。lisp.hには重要なマクロと関数の定義がいくつか含まれています。
副作用がない関数や純粋関数を定義したら、side-effect-freeやpureのプロパティに非nilを与えてください(シンボルの標準的なプロパティを参照)。
Next: 動的にロードされるモジュールの記述, Previous: C方言, Up: GNU Emacsの内部 [Contents][Index]