CodeReading: EmacsLisp "DEFUN"マクロ

Kazuki Ohta, 2005/01/27


前回は"eval"の内容を見ていった。次は"if"や"and"といった重要なsyntaxがどう定義されているかを見たいと思う。

ところで、こういった関数はどうやってインタプリタに登録されているのだろうか。まずはその仕組みから解き明かしてみる。その謎は"DEFUN"マクロにある。DEFUNはsrc/lisp.hで次のように定義されている。
#define DEFUN(lname, fnname, sname, minargs, maxargs, prompt, doc)      \
  Lisp_Object fnname DEFUN_ARGS_ ## maxargs ;                           \
  DECL_ALIGN (struct Lisp_Subr, sname) =                                \
    { PVEC_SUBR | (sizeof (struct Lisp_Subr) / sizeof (EMACS_INT)),     \
      fnname, minargs, maxargs, lname, prompt, 0};                      \
  Lisp_Object fnname
また、DECL_ALIGNは次の様に定義されている。
/* First, try and define DECL_ALIGN(type,var) which declares a static
   variable VAR of type TYPE with the added requirement that it be
   TYPEBITS-aligned. */
#ifndef NO_DECL_ALIGN
# ifndef DECL_ALIGN
/* What compiler directive should we use for non-gcc compilers?  -stef  */
#  if defined (__GNUC__)
#   define DECL_ALIGN(type, var) \
     type __attribute__ ((__aligned__ (1 << GCTYPEBITS))) var
#  endif
# endif
#endif
DECL_ALIGNはgccの__aligned__を使用して、GCTYPEBITSを確保する為のアラインメントを取るようにコンパイラに指示する為のマクロである。つまりこのマクロを使用して定義された変数のアドレスの下位 GCTYPEBITS ビットは0になる事が保証される。それを覗けば単なる変数定義に置き換えているだけである。

ここで、実際にDEFUNを使用している部分を見てみる。
DEFUN ("eval", Feval, Seval, 1, 1, 0,
       doc: /* Evaluate FORM and return its value.  */)
     (form)
     Lisp_Object form;
{
  hogehoge;
}
さて、これは次のように置き換えられる(__aligned__の部分は省く)。プリプロセッサの##は文字列の連結を行う事が出来る。
Lisp_Object Feval DEFUN_ARGS_1;
struct Lisp_Subr Seval = 
  {
    PVEC_SUBR | (sizeof (struct Lisp_Subr) / sizeof (EMACS_INT)),
    Feval,
    1,
    1,
    "eval",
    0,
    0
  };
Lisp_Object Feval(form)
  Lisp_Object form;
{
  hogehoge;
}
さらにDEFUN_ARGS_1は(Lisp_Object)と定義されている事から、結局次のようになる。
Lisp_Object Feval (Lisp_Object);
struct Lisp_Subr Seval = 
  {
    PVEC_SUBR | (sizeof (struct Lisp_Subr) / sizeof (EMACS_INT)),
    Feval,
    1,
    1,
    "eval",
    0,
    0
  };
Lisp_Object Feval(form)
  Lisp_Object form;
{
  hogehoge;
}
こうして見ると、変数Sevalと関数Fevalの宣言+定義だという事が分かる。さて、このSevalはどこで使われているかと言うと、同じsrc/eval.c内のsyms_of_eval関数内で次のように使われている。
void
syms_of_eval ()
{
  hogehoge();

  defsubr (&Seval);

  hogehoge();
}
なるほど、Sevalの情報を元にdefsubrで関数を登録しているという事が分かる。DEFUNを使用して関数宣言をすると同時に半ば自動でインタプリタに関数を登録する為のテクニックだ。

さて、ここまで見ておけば後は何でも一緒だ。まずは"if"文を見てみる。if文を定義しているのは、同じくsrc/eval.cの次の部分だ。
DEFUN ("if", Fif, Sif, 2, UNEVALLED, 0,
       doc: /* If COND yields non-nil, do THEN, else do ELSE...
Returns the value of THEN or the value of the last of the ELSE's.
THEN must be one expression, but ELSE... can be zero or more expressions.
If COND yields nil, and there are no ELSE's, the value is nil.
usage: (if COND THEN ELSE...)  */)
     (args)
     Lisp_Object args;
{
  register Lisp_Object cond;
  struct gcpro gcpro1;

  GCPRO1 (args);
  cond = Feval (Fcar (args));
  UNGCPRO;

  if (!NILP (cond))
    return Feval (Fcar (Fcdr (args)));
  return Fprogn (Fcdr (Fcdr (args)));
}
UNEVALLEDとなっている所に注目。ifは特殊形式なので、呼ばれる前に引数を評価されてしまってはいけません。"eval"がUNEVALLEDをどう扱っていたか思い出してみて。

さて中身をみると、まずは第一引数をFevalし、その結果が!NILPだったら、第二引数(THENの部分)を評価しそれをreturn。NILPで有れば第三引数移行を順番に評価して行く。prognはSchemeで言う所のbegin。確かめてみましょうか。
DEFUN ("progn", Fprogn, Sprogn, 0, UNEVALLED, 0,
       doc: /* Eval BODY forms sequentially and return value of last one.
usage: (progn BODY ...)  */)
     (args)
     Lisp_Object args;
{
  register Lisp_Object val = Qnil;
  struct gcpro gcpro1;

  GCPRO1 (args);

  while (CONSP (args))
    {
      val = Feval (XCAR (args));
      args = XCDR (args);
    }

  UNGCPRO;
  return val;
}
argsを順番に評価していって、最後の値を返す。うーん、分かり易い。"and"とかも一応見てみますか。
DEFUN ("and", Fand, Sand, 0, UNEVALLED, 0,
       doc: /* Eval args until one of them yields nil, then return nil.
The remaining args are not evalled at all.
If no arg yields nil, return the last arg's value.
usage: (and CONDITIONS ...)  */)
     (args)
     Lisp_Object args;
{
  register Lisp_Object val = Qt;
  struct gcpro gcpro1;

  GCPRO1 (args);

  while (CONSP (args))
    {
      val = Feval (XCAR (args));
      if (NILP (val))
	break;
      args = XCDR (args);
    }

  UNGCPRO;
  return val;
}
先ほどのprognの様にargsを順番に評価して行くが、その結果がNILとなったらそこで処理は終了。NILにならなかったら最後まで処理を続行。うん、and。

後は個々の関数の問題なんで、Emacsソースコードをある程度渡り歩けそう。


[ return ]