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関数を宣言する。これはC23の[[noreturn]]
、C11の_Noreturn
GCCの__attribute__ ((__noreturn__))
(Function Attributes in Using the GNU Compiler
Collectionを参照)に対応している(未サポートのプラットフォームにおいて_Noreturn
はマクロとして定義されているので、Emacs独自コードは内部的にはこれを使っている)。
const
引数以外の値を検査せず、リターン値以外に影響しない関数を宣言する。これはC23の[[unsequenced]]
、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つの引数を期待します:
lname
Lispプログラムが使用する変数名。
vname
Cソース内の変数名。
doc
Cコメントとしての変数用のドキュメント。詳細はドキュメントの基礎を参照のこと。
慣例として“ネイティブ”なタイプ(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
を与えてください(シンボルの標準的なプロパティを参照)。