CodeReading: EmacsLisp "interactive"関数

Kazuki Ohta, 2006/02/12


実際に.elファイルを読んでみようと思う。インタプリタの構造を大体理解しているので、分からない部分が有ればすぐにCの下層へと降りて調べれば良い。で、lisp/以下の何かのファイルを読もうと思うのだが、なんとなく気になるのが各関数の冒頭に登場するinteractiveという関数。これは何だろうと気になったので調べてみる事にする。interactiveは例えば次のようにして使われる(lisp/tabify.el)。untabify関数はmarkで指定された範囲のtabをspaceに置き換える関数なのであるのに対し、tabifyは指定された範囲のspaceをtabに置き換える関数だ。
(defun untabify (start end)
  "Convert all tabs in region to multiple spaces, preserving columns.
Called non-interactively, the region is specified by arguments
START and END, rather than by the position of point and mark.
The variable `tab-width' controls the spacing of tab stops."
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region (point-min) end)
      (goto-char start)
      (while (search-forward "\t" nil t)	; faster than re-search
	(forward-char -1)
	(let ((tab-beg (point))
	      (indent-tabs-mode nil)
	      column)
	  (skip-chars-forward "\t")
	  (setq column (current-column))
	  (delete-region tab-beg (point))
	  (indent-to column))))))
さて、Cの層へ舞い戻る。肝心のinteractive関数はdata/callint.cで定義されている。
DEFUN ("interactive", Finteractive, Sinteractive, 0, UNEVALLED, 0,
       doc: /* Specify a way of parsing arguments for interactive use of a function.
For example, write
  (defun foo (arg) "Doc string" (interactive "p") ...use arg...)
to make ARG be the prefix argument when `foo' is called as a command.
The "call" to `interactive' is actually a declaration rather than a function;
 it tells `call-interactively' how to read arguments
 to pass to the function.
When actually called, `interactive' just returns nil.

The argument of `interactive' is usually a string containing a code letter
 followed by a prompt.  (Some code letters do not use I/O to get
 the argument and do not need prompts.)  To prompt for multiple arguments,
 give a code letter, its prompt, a newline, and another code letter, etc.
 Prompts are passed to format, and may use % escapes to print the
 arguments that have already been read.
If the argument is not a string, it is evaluated to get a list of
 arguments to pass to the function.
Just `(interactive)' means pass no args when calling interactively.

Code letters available are:
a -- Function name: symbol with a function definition.
b -- Name of existing buffer.
B -- Name of buffer, possibly nonexistent.
c -- Character (no input method is used).
C -- Command name: symbol with interactive function definition.
d -- Value of point as number.  Does not do I/O.
D -- Directory name.
e -- Parametrized event (i.e., one that's a list) that invoked this command.
     If used more than once, the Nth `e' returns the Nth parameterized event.
     This skips events that are integers or symbols.
f -- Existing file name.
F -- Possibly nonexistent file name.
G -- Possibly nonexistent file name, defaulting to just directory name.
i -- Ignored, i.e. always nil.  Does not do I/O.
k -- Key sequence (downcase the last event if needed to get a definition).
K -- Key sequence to be redefined (do not downcase the last event).
m -- Value of mark as number.  Does not do I/O.
M -- Any string.  Inherits the current input method.
n -- Number read using minibuffer.
N -- Raw prefix arg, or if none, do like code `n'.
p -- Prefix arg converted to number.  Does not do I/O.
P -- Prefix arg in raw form.  Does not do I/O.
r -- Region: point and mark as 2 numeric args, smallest first.  Does no I/O.
s -- Any string.  Does not inherit the current input method.
S -- Any symbol.
U -- Mouse up event discarded by a previous k or K argument.
v -- Variable name: symbol that is user-variable-p.
x -- Lisp expression read but not evaluated.
X -- Lisp expression read and evaluated.
z -- Coding system.
Z -- Coding system, nil if no prefix arg.
In addition, if the string begins with `*'
 then an error is signaled if the buffer is read-only.
 This happens before reading any arguments.
If the string begins with `@', then Emacs searches the key sequence
 which invoked the command for its first mouse click (or any other
 event which specifies a window), and selects that window before
 reading any arguments.  You may use both `@' and `*'; they are
 processed in the order that they appear.
usage: (interactive ARGS)  */)
     (args)
     Lisp_Object args;
{
  return Qnil;
}
何ぃ、return Qnilとな。コメントを良く読むと、'call-interactively'関数が使用しているらしい。なるほど、 確かに調べてみると対話的に関数を呼び出す為の仕組みであるようだ。それならばとsrc/callint.cのcall-interactively関数を見てみる。ちょっと長い関数だが、エッセンスを抜き出してみる。
DEFUN ("call-interactively", Fcall_interactively, Scall_interactively, 1, 3, 0,
       doc: /* Call FUNCTION, reading args according to its interactive calling specs.
Return the value FUNCTION returns.
The function contains a specification of how to do the argument reading.
In the case of user-defined functions, this is specified by placing a call
to the function `interactive' at the top level of the function body.
See `interactive'.

Optional second arg RECORD-FLAG non-nil
means unconditionally put this command in the command-history.
Otherwise, this is done only if an arg is read using the minibuffer.
Optional third arg KEYS, if given, specifies the sequence of events to
supply if the command inquires which events were used to invoke it.
If KEYS is omitted or nil, the return value of `this-command-keys' is used.  */)
     (function, record_flag, keys)
     Lisp_Object function, record_flag, keys;
{
  関数宣言

  fun = indirect_function (function);

  /* Decode the kind of function.  Either handle it and return,
     or go to `lose' if not interactive, or go to `retry'
     to specify a different function, or set either STRING or SPECS.  */

  if (SUBRP (fun))
    {
      string = (unsigned char *) XSUBR (fun)->prompt;
      if (!string)
	{
	lose:
	  function = wrong_type_argument (Qcommandp, function);
	  goto retry;
	}
    }
  else if (COMPILEDP (fun))
    {
      if ((XVECTOR (fun)->size & PSEUDOVECTOR_SIZE_MASK) <= COMPILED_INTERACTIVE)
	goto lose;
      specs = XVECTOR (fun)->contents[COMPILED_INTERACTIVE];
    }
  else
    {
      Lisp_Object form;
      GCPRO2 (function, prefix_arg);
      form = Finteractive_form (function);
      UNGCPRO;
      if (CONSP (form))
	specs = filter_specs = Fcar (XCDR (form));
      else
	goto lose;
    }

  適当に解析

  tem = string;
  for (i = 1; *tem; i++)
    {
      switch (*tem)
	{
        case hogehoge:
          ..
          break;

	case 'r':		/* Region, point and mark as 2 args. */
	  check_mark (1);
	  set_marker_both (point_marker, Qnil, PT, PT_BYTE);
	  /* visargs[i+1] = Qnil; */
	  foo = marker_position (current_buffer->mark);
	  /* visargs[i] = Qnil; */
	  args[i] = PT < foo ? point_marker : current_buffer->mark;
	  varies[i] = 3;
	  args[++i] = PT > foo ? point_marker : current_buffer->mark;
	  varies[i] = 4;
	  break;
        }
    }

  ....

  {
    Lisp_Object val;
    specbind (Qcommand_debug_status, Qnil);

    val = Ffuncall (count + 1, args);
    UNGCPRO;
    return unbind_to (speccount, val);
  }
}
処理に関係の有りそうな所だけを抜き出してみた。interactive関数のコメントにも有るように、stringの無いようが'r'の時は2つのポイントをargsに突っ込んでいる事が分かる。そして最後にFfuncall関数を呼び出すと。ふぅ、なるほど。そのstringはどこから来ているのか調べる。

SUBRP (fun)の場合は、まずindirect_function関数でfunを取得し、そっからXSUBR (fun)->promptで取得されている。indirect_function関数はsrc/data.cで定義されている。
/* If OBJECT is a symbol, find the end of its function chain and
   return the value found there.  If OBJECT is not a symbol, just
   return it.  If there is a cycle in the function chain, signal a
   cyclic-function-indirection error.

   This is like Findirect_function, except that it doesn't signal an
   error if the chain ends up unbound.  */
Lisp_Object
indirect_function (object)
     register Lisp_Object object;
{
  Lisp_Object tortoise, hare;

  hare = tortoise = object;

  for (;;)
    {
      if (!SYMBOLP (hare) || EQ (hare, Qunbound))
	break;
      hare = XSYMBOL (hare)->function;
      if (!SYMBOLP (hare) || EQ (hare, Qunbound))
	break;
      hare = XSYMBOL (hare)->function;

      tortoise = XSYMBOL (tortoise)->function;

      if (EQ (hare, tortoise))
	Fsignal (Qcyclic_function_indirection, Fcons (object, Qnil));
    }

  return hare;
}
うわ、お茶目な変数名。ここは面白いのであえて解説しません。

さて、次はXSUBR (fun)->promptの謎。XSUBRはたぶんtagged pointerのtagを除去しているだけだろうから、実際に重要なのはprompt変数。どうせこの辺りはsrc/lisp.hに有るのだろうと目星を付けると、案の定有りましたよ。
/* This structure describes a built-in function.
   It is generated by the DEFUN macro only.
   defsubr makes it into a Lisp object.

   This type is treated in most respects as a pseudovector,
   but since we never dynamically allocate or free them,
   we don't need a next-vector field.  */

struct Lisp_Subr
  {
    EMACS_INT size;
    Lisp_Object (*function) ();
    short min_args, max_args;
    char *symbol_name;
    char *prompt;
    char *doc;
  };
さらにpromptをキーにsrc/lisp.h内を探すとこういった部分が見える。
/* Define a built-in function for calling from Lisp.
 `lname' should be the name to give the function in Lisp,
    as a null-terminated C string.
 `fnname' should be the name of the function in C.
    By convention, it starts with F.
 `sname' should be the name for the C constant structure
    that records information on this function for internal use.
    By convention, it should be the same as `fnname' but with S instead of F.
    It's too bad that C macros can't compute this from `fnname'.
 `minargs' should be a number, the minimum number of arguments allowed.
 `maxargs' should be a number, the maximum number of arguments allowed,
    or else MANY or UNEVALLED.
    MANY means pass a vector of evaluated arguments,
	 in the form of an integer number-of-arguments
	 followed by the address of a vector of Lisp_Objects
	 which contains the argument values.
    UNEVALLED means pass the list of unevaluated arguments
 `prompt' says how to read arguments for an interactive call.
    See the doc string for `interactive'.
    A null string means call interactively with no arguments.
 `doc' is documentation for the user.  */

#if (!defined (__STDC__) && !defined (PROTOTYPES)) \
    || defined (USE_NONANSI_DEFUN)

#define DEFUN(lname, fnname, sname, minargs, maxargs, prompt, doc)	\
  Lisp_Object fnname ();						\
  DECL_ALIGN (struct Lisp_Subr, sname) =				\
    { PVEC_SUBR | (sizeof (struct Lisp_Subr) / sizeof (EMACS_INT)),	\
      fnname, minargs, maxargs, lname, prompt, 0};			\
  Lisp_Object fnname

#else

/* This version of DEFUN declares a function prototype with the right
   arguments, so we can catch errors with maxargs at compile-time.  */
#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

/* Note that the weird token-substitution semantics of ANSI C makes
   this work for MANY and UNEVALLED.  */
#define DEFUN_ARGS_MANY		(int, Lisp_Object *)
#define DEFUN_ARGS_UNEVALLED	(Lisp_Object)
#define DEFUN_ARGS_0	(void)
#define DEFUN_ARGS_1	(Lisp_Object)
#define DEFUN_ARGS_2	(Lisp_Object, Lisp_Object)
#define DEFUN_ARGS_3	(Lisp_Object, Lisp_Object, Lisp_Object)
#define DEFUN_ARGS_4	(Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object)
#define DEFUN_ARGS_5	(Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, \
			 Lisp_Object)
#define DEFUN_ARGS_6	(Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, \
			 Lisp_Object, Lisp_Object)
#define DEFUN_ARGS_7	(Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, \
			 Lisp_Object, Lisp_Object, Lisp_Object)
#define DEFUN_ARGS_8	(Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object, \
			 Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object)
#endif
あれ、これって前に見たDEFUNマクロ。そういえばpromptっていう引数が有った。忘れてた。これでようやく繋がりました。これはCで定義された関数の場合です。ならばlispで定義された関数の場合はどうなるんだろう。まずはsrc/eval.cのFdefun関数を見てみる。
DEFUN ("defun", Fdefun, Sdefun, 2, UNEVALLED, 0,
       doc: /* Define NAME as a function.
The definition is (lambda ARGLIST [DOCSTRING] BODY...).
See also the function `interactive'.
usage: (defun NAME ARGLIST [DOCSTRING] BODY...)  */)
     (args)
     Lisp_Object args;
{
  register Lisp_Object fn_name;
  register Lisp_Object defn;

  fn_name = Fcar (args);
  CHECK_SYMBOL (fn_name);
  defn = Fcons (Qlambda, Fcdr (args));
  if (!NILP (Vpurify_flag))
    defn = Fpurecopy (defn);
  if (CONSP (XSYMBOL (fn_name)->function)
      && EQ (XCAR (XSYMBOL (fn_name)->function), Qautoload))
    LOADHIST_ATTACH (Fcons (Qt, fn_name));
  Ffset (fn_name, defn);
  LOADHIST_ATTACH (Fcons (Qdefun, fn_name));
  return fn_name;
}
ふーむ、Ffsetでfn_nameにdefnをbindしている。defnは基本的にはリストっぽいな。ってことは、call-interactively関数のこの部分が対応する。
  if (SUBRP (fun))
    {
      ...
    }
  else if (COMPILEDP (fun))
    {
      ...
    }
  else
    {
      Lisp_Object form;
      GCPRO2 (function, prefix_arg);
      form = Finteractive_form (function);
      UNGCPRO;
      if (CONSP (form))
	specs = filter_specs = Fcar (XCDR (form));
      else
	goto lose;
    }

  /* If either SPECS or STRING is set to a string, use it.  */
  if (STRINGP (specs))
    {
      /* Make a copy of string so that if a GC relocates specs,
	 `string' will still be valid.  */
      string = (unsigned char *) alloca (SBYTES (specs) + 1);
      bcopy (SDATA (specs), string,
	     SBYTES (specs) + 1);
    }
前半はfunはSUBRPでもCOMPILEDPでも無いので最後のelse節を見れば良い。Finteractive_formとな。早速src/datas.cのFinteractive_form関数を見てみる。
DEFUN ("interactive-form", Finteractive_form, Sinteractive_form, 1, 1, 0,
       doc: /* Return the interactive form of CMD or nil if none.
If CMD is not a command, the return value is nil.
Value, if non-nil, is a list \(interactive SPEC).  */)
     (cmd)
     Lisp_Object cmd;
{
  Lisp_Object fun = indirect_function (cmd);

  if (SUBRP (fun))
    {
      if (XSUBR (fun)->prompt)
	return list2 (Qinteractive, build_string (XSUBR (fun)->prompt));
    }
  else if (COMPILEDP (fun))
    {
      if ((ASIZE (fun) & PSEUDOVECTOR_SIZE_MASK) > COMPILED_INTERACTIVE)
	return list2 (Qinteractive, AREF (fun, COMPILED_INTERACTIVE));
    }
  else if (CONSP (fun))
    {
      Lisp_Object funcar = XCAR (fun);
      if (EQ (funcar, Qlambda))
	return Fassq (Qinteractive, Fcdr (XCDR (fun)));
      else if (EQ (funcar, Qautoload))
	{
	  struct gcpro gcpro1;
	  GCPRO1 (cmd);
	  do_autoload (fun, cmd);
	  UNGCPRO;
	  return Finteractive_form (cmd);
	}
    }
  return Qnil;
}
この場合はCONSP (fun)が成り立つので、最後のelse if節を見れば良い。Fdefunにも有ったように、funcarはQlambdaなのでそこを見てみると、FassqでQinteractiveを探している。ビンゴ。こうしてcall-interactively関数でspec変数にinteractiveの引数が格納されるという訳か。すっきり。

という訳で今回はinteractiveについて追ってみました。


[ return ]