GNU Emacs 32.0.50 (9008bf64c3) のマニュアルを PLaMo 翻訳で機械翻訳したものです。誤訳を含むことがあります。原文: https://www.gnu.org/software/emacs/manual/html_node/elisp/Writing-Emacs-Primitives.html ・ emacs-doc.conao3.com

E.6 Emacsプリミティブの記述

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)
lname

これは関数名として定義するLispシンボルの名前で、上記の例では or です。

fname

これはこの関数のC言語における関数名です。C言語のコードでこの関数を呼び出す際に使用される名前です。慣例として、Lispの名前の先頭に‘F’が付加され、Lispの名前に含まれるすべてのハイフン(‘-’)がアンダースコアに置き換えられます。したがって、C言語のコードからこの関数を呼び出すには、Forを呼び出します。

sname

これは、Lispの関数を表すsubrオブジェクトのデータを保持する構造体に使用するC言語の変数名です。この構造体は、Lispのシンボル名を、シンボルを生成してsubrオブジェクトをその定義として格納する初期化ルーチンに渡します。慣例として、この名前は常にfnameとなり、‘F’は‘S’に置き換えられます。

min

これは、関数が必要とする最小の引数数です。関数 or は、最小0個の引数を受け付けます。

max

これは、最大数が固定されている場合に、その関数が受け付ける引数の最大数です。あるいは、未評価の引数を受け取る特殊な形式を示す UNEVALLED、または評価済み引数の数に制限がないことを示す MANY(&rest と同等)となる場合もあります。UNEVALLED と MANY はいずれもマクロです。max が数値である場合、それは min より大きく、かつ 8 より小さくなければなりません。

interactive

これはインタラクティブな仕様であり、Lisp関数のinteractiveの引数として用いられ得るような文字列です(interactiveの使用を参照)。orの場合は0(ヌルポインタ)であり、orをインタラクティブに呼び出せないことを示します。""の値は、インタラクティブに呼び出された際に引数を受け取らないべき関数を示します。値が‘"(’で始まる場合、その文字列はLispフォームとして評価されます。例えば:

DEFUN ("foo", Ffoo, Sfoo, 0, 3,
       "(list (read-char-by-name \"Insert character: \")\
              (prefix-numeric-value current-prefix-arg)\
              t)",
       doc: /* ... */)
doc

これはドキュメント文字列です。コメント構文であれば複数行を記述するのに特別な記述が不要なため、C言語の文字列構文ではなくコメント構文が使用されています。‘doc:’ は、それに続くコメントがドキュメント文字列であることを示します。コメントの開始と終了を示す ‘/*’ および ‘*/’ の区切り文字は、ドキュメント文字列の一部ではありません。

ドキュメント文字列の最終行がキーワード ‘usage:’ で始まる場合、その行の残りの部分はドキュメント用の引数リストとして扱われます。これにより、ドキュメント文字列ではCコードで使用しているものとは異なる引数名を使用できます。関数の引数数が無制限である場合は、‘usage:’ が必要です。

一部のプリミティブには、プラットフォームごとに1つずつ、複数の定義が存在します(例:x-create-frame)。このような場合、各定義に同じドキュメント文字列を記述するのではなく、そのうち1つの定義にのみ実際のドキュメントを記述します。その他の定義には ‘SKIP’ で始まるプレースホルダーが含まれており、これらは DOC ファイルを解析する関数によって無視されます。

Lispコードのドキュメント文字列に関する一般的なルール(ドキュメント文字列のヒントを参照)は、Cコードのドキュメント文字列にも適用されます。

ドキュメント文字列の後には、プリミティブを実装するC関数のC関数属性の一覧を、次のように記述できます。

DEFUN ("bar", Fbar, Sbar, 0, UNEVALLED, 0
       doc: /* ... */
       attributes: attr1 attr2 ...)

属性は複数、連続して指定できます。現在、認識されている属性は以下のみです。

noreturn

C関数を、決して復帰しない関数として宣言します。これはC23の[[noreturn]]、C11の_Noreturn、およびGCCの__attribute__ ((__noreturn__))に相当します(Using the GNU Compiler CollectionのFunction Attributesを参照)。(内部的には、Emacs自身のCコードでは、これをサポートしていないCプラットフォーム上でマクロとして定義できるため、_Noreturnを使用しています。)

const

この関数は、引数以外の値を一切参照せず、戻り値以外の効果も持たないことを宣言します。これはC23の[[unsequenced]]およびGCCの__attribute__ ((__const__))に相当します。

noinline

これはGCCの__attribute__ ((__noinline__))属性に対応しており、これにより関数がインライン展開の候補から除外されます。例えば、スタックベースの変数に対するリンク時最適化の影響を打ち消す場合などに必要となることがあります。

DEFUN マクロを呼び出した後は、引数の型を含めて C 関数の引数リストを記述する必要があります。プリミティブが Lisp 引数の最大数を固定して受け付ける場合、Lisp 引数1つにつき1つの C 引数が必要であり、各引数は Lisp_Object 型でなければなりません。(Lisp_Object 型の値を生成するための各種マクロや関数は、ファイル lisp.h で宣言されています。)プリミティブが特殊形式である場合、未評価の Lisp 引数を含む Lisp リストを、Lisp_Object 型の単一の引数として受け取る必要があります。プリミティブが評価済み Lisp 引数の数に上限を設けない場合、C 引数はちょうど2つ必要です。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 を押したかどうかを判定し、押されていた場合は処理を中断します。大量の反復処理が必要になる可能性があるあらゆるループにおいて、同様の処理を行うべきです。今回のケースでは、引数のリストが非常に長くなる可能性があります。これにより、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では、そのシンボルの値ではなく、シンボルそのものを参照する必要がある場面があります。その一つが、変数の値を一時的に上書きする場合で、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言語では、対応するコードでFmake_variable_buffer_localをDEFSYMと組み合わせて使用します:

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で書かれた関数を呼び出すには、Lisp関数 funcall を具現化した Ffuncall を使用します。Lisp関数 funcall は無制限の数の引数を取るため、C言語ではLispレベルの引数の数と、その値を含む1次元配列の2つを取ります。最初のLispレベルの引数は呼び出すLisp関数であり、残りはそれに渡す引数となります。

Cマクロ calln は、引数の数を指定せずにLisp関数を呼び出すための便利な方法です。これは Ffuncall を呼び出すことで動作します。

eval.cは例を確認するのに非常に適したファイルです。lisp.hにはいくつかの重要なマクロや関数の定義が含まれています。

副作用のない関数または純粋な関数を定義する場合は、それぞれ nil、side-effect-free、または pure 以外のプロパティを指定してください(標準シンボルプロパティを参照)。‘byte-opt.el’ で定義されているリストを参照してください。