E.7 Написание примитивов Emacs

Примитивы Lisp — это функции Lisp, реализованные на C. Подробности взаимодействия функции C так, чтобы Lisp мог её вызывать, обрабатываются несколькими макросами C. Единственный способ действительно понять, как писать новый код на C, — это читать исходный код, но мы можем кое-что объяснить здесь.

Примером специальной формы является определение or из eval.c. (Обычная функция имела бы такой же общий вид.)

DEFUN ("or", For, Sor, 0, UNEVALLED, 0,
       doc: /* Вычисляет аргументы до тех пор, пока один из них не даст ненулевое значение,
затем возвращает это значение.
Остальные аргументы вообще не вычисляются.
Если все аргументы возвращают nil, возвращает 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 для вызова функции. По соглашению, это имя — «F», добавленное перед именем Lisp, причём все дефисы («-») в имени Lisp заменяются на подчёркивания. Таким образом, чтобы вызвать эту функцию из кода C, вызывайте For.

sname

Это имя переменной C, используемой для структуры, содержащей данные для объекта подпрограммы, который представляет функцию в Lisp. Эта структура передаёт имя символа Lisp инициализирующей подпрограмме, которая создаст символ и сохранит объект подпрограммы как его определение. По соглашению, это имя всегда fname с заменой «F» на «S».

min

Это минимальное количество аргументов, которое требуется функции. Функция or допускает минимум ноль аргументов.

max

Это максимальное количество аргументов, которое принимает функция, если существует фиксированный максимум. Альтернативно, это может быть UNEVALLED, что указывает на специальную форму, получающую невычисленные аргументы, или MANY, что указывает на неограниченное количество вычисленных аргументов (эквивалент &rest). Оба UNEVALLED и MANY являются макросами. Если max является числом, оно должно быть больше min, но меньше 8.

interactive

Это интерактивная спецификация — строка, подобная той, которая может использоваться в качестве аргумента interactive в функции Lisp (см. Использование 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, а не синтаксис строк C, потому что синтаксис комментариев не требует ничего особого для включения нескольких строк. «doc:» идентифицирует следующий комментарий как строку документации. Разделители «/*» и «*/», которые начинают и заканчивают комментарий, не являются частью строки документации.

Если последняя строка строки документации начинается с ключевого слова «usage:», остальная часть строки обрабатывается как список аргументов для целей документации. Таким образом, вы можете использовать в строке документации имена аргументов, отличные от тех, которые используются в коде C. «usage:» требуется, если функция имеет неограниченное количество аргументов.

Некоторые примитивы имеют несколько определений — по одному на платформу (например, x-create-frame). В таких случаях, вместо того чтобы писать одну и ту же строку документации в каждом определении, только одно определение содержит фактическую документацию. Остальные содержат заполнители, начинающиеся с «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__)) (см. Атрибуты функций в Руководстве по использованию коллекции компиляторов GNU). (Внутренне собственный код Emacs использует _Noreturn, так как он может быть определён как макрос на платформах C, которые его не поддерживают.)

const

Объявляет, что функция не проверяет никакие значения, кроме своих аргументов, и не имеет никаких эффектов, кроме возвращаемого значения. Это соответствует C23 [[unsequenced]] и GCC __attribute__ ((__const__)).

noinline

Это соответствует атрибуту GCC __attribute__ ((__noinline__)), который предотвращает рассмотрение функции для встраивания. Это может потребоваться, например, для противодействия эффектам оптимизаций во время компоновки для переменных на стеке.

После вызова макроса DEFUN вы должны написать список аргументов для функции C, включая типы аргументов. Если примитив принимает фиксированное максимальное количество аргументов Lisp, для каждого аргумента Lisp должен быть один аргумент C, и каждый аргумент должен иметь тип Lisp_Object. (Различные макросы и функции для создания значений типа Lisp_Object объявлены в файле lisp.h.) Если примитив является специальной формой, он должен принимать список Lisp, содержащий его невычисленные аргументы Lisp, как единственный аргумент типа Lisp_Object. Если примитив не имеет верхнего предела для количества вычисленных аргументов Lisp, он должен иметь ровно два аргумента C: первый — это количество аргументов Lisp, а второй — адрес блока, содержащего их значения. Они имеют типы ptrdiff_t и Lisp_Object * соответственно. Поскольку Lisp_Object может содержать любой объект Lisp любого типа данных, вы можете определить фактический тип данных только во время выполнения; поэтому, если вы хотите, чтобы примитив принимал только аргумент определённого типа, вы должны явно проверить тип с помощью подходящего предиката (см. Предикаты типов).

Внутри самой функции For локальная переменная args ссылается на объекты, управляемые сборщиком мусора Emacs с пометкой стека. Хотя сборщик мусора не освобождает объекты, достижимые из стековых переменных C типа Lisp_Object, он может перемещать некоторые компоненты объекта, такие как содержимое строки или текст буфера. Поэтому функции, обращающиеся к этим компонентам, должны позаботиться о повторном получении их адресов после выполнения вычислений Lisp. Это означает, что вместо хранения указателей C на содержимое строк или текст буфера код должен хранить позицию буфера или строки и пересчитывать указатель C из позиции после выполнения вычислений Lisp. Вычисление Lisp может происходить через вызовы eval_sub или Feval, как прямо, так и косвенно.

Обратите внимание на вызов maybe_quit внутри цикла: эта функция проверяет, нажал ли пользователь C-g, и если да, прерывает обработку. Вы должны делать это в любом цикле, который потенциально может потребовать большого количества итераций; в данном случае список аргументов может быть очень длинным. Это повышает отзывчивость Emacs и улучшает взаимодействие с пользователем.

Вы не должны использовать инициализаторы C для статических или глобальных переменных, если только переменные никогда не изменяются после дампа Emacs. Эти переменные с инициализаторами размещаются в области памяти, которая становится доступной только для чтения (в некоторых операционных системах) в результате дампа Emacs. См. Чистое хранилище.

Определения функции C недостаточно, чтобы сделать примитив Lisp доступным; вы также должны создать символ Lisp для примитива и сохранить подходящий объект подпрограммы в его ячейке функции. Код выглядит так:

defsubr (&sname);

Здесь sname — это имя, которое вы использовали в качестве третьего аргумента DEFUN.

Если вы добавляете новый примитив в файл, в котором уже определены примитивы Lisp, найдите функцию (ближе к концу файла) с именем syms_of_something и добавьте вызов defsubr туда. Если в файле нет этой функции или если вы создаёте новый файл, добавьте в него syms_of_filename (например, syms_of_myfile). Затем найдите место в emacs.c, где вызываются все эти функции, и добавьте вызов syms_of_filename туда.

Функция syms_of_filename также является местом для определения любых переменных C, которые должны быть видны как переменные Lisp. DEFVAR_LISP делает переменную C типа Lisp_Object видимой в Lisp. DEFVAR_INT делает переменную C типа int видимой в Lisp со значением, которое всегда является целым числом. DEFVAR_BOOL делает переменную C типа int видимой в Lisp со значением, которое является либо t, либо nil. Обратите внимание, что переменные, определённые с помощью DEFVAR_BOOL, автоматически добавляются в список byte-boolean-vars, используемый байт-компилятором.

Все эти макросы ожидают три аргумента:

lname

Имя переменной, используемое программами на Lisp.

vname

Имя переменной в исходных текстах C.

doc

Документация для переменной в виде комментария C. См. Основы документации для получения более подробной информации.

По соглашению, при определении переменных «родного» типа (int и bool) имя переменной C — это имя переменной Lisp с заменой - на _. Когда переменная имеет тип Lisp_Object, принято также добавлять префикс V к имени переменной C. Вот пример:

DEFVAR_INT ("my-int-variable", my_int_variable,
           doc: /* Целочисленная переменная.  */);

DEFVAR_LISP ("my-lisp-variable", Vmy_lisp_variable,
           doc: /* Переменная Lisp.  */);

В 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);

Если вы хотите, чтобы переменная Lisp, определённая в C, вела себя как объявленная с помощью defcustom, добавьте соответствующую запись в cus-start.el. См. Определение переменных настройки для описания используемого формата.

Если вы непосредственно определяете переменную C с областью видимости файла типа Lisp_Object, вы должны защитить её от сборки мусора, вызвав staticpro в syms_of_filename, например:

staticpro (&variable);

Вот ещё один пример функции с более сложными аргументами. Он взят из кода в window.c и демонстрирует использование макросов и функций для работы с объектами Lisp.

DEFUN ("coordinates-in-window-p", Fcoordinates_in_window_p,
       Scoordinates_in_window_p, 2, 2, 0,
       doc: /* Возвращает ненулевое значение, если КООРДИНАТЫ находятся в ОКНЕ.
  …
  или возвращается `right-margin'.  */)
  (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:            /* НЕ в окне вообще.  */
      return Qnil;

    …

    case ON_MODE_LINE:          /* В строке режима окна.  */
      return Qmode_line;

    …

    case ON_SCROLL_BAR:         /* На полосе прокрутки окна.  */
      /* Исторически мы должны возвращать nil в этом случае.  */
      return Qnil;

    default:
      emacs_abort ();
    }
}

Обратите внимание, что код C не может вызывать функции по имени, если они не определены в C. Способ вызова функции, написанной на Lisp, — использовать Ffuncall, который воплощает функцию Lisp funcall. Поскольку функция Lisp funcall принимает неограниченное количество аргументов, в C она принимает два: количество аргументов уровня Lisp и одномерный массив, содержащий их значения. Первый аргумент уровня Lisp — это вызываемая функция Lisp, а остальные — аргументы, передаваемые ей.

Функции C call0, call1, call2 и т. д. предоставляют удобные способы вызова функции Lisp с фиксированным числом аргументов. Они работают путём вызова Ffuncall.

eval.c — очень хороший файл для просмотра примеров; lisp.h содержит определения некоторых важных макросов и функций.

Если вы определяете функцию, которая не имеет побочных эффектов или является чистой, присвойте ей соответственно ненулевое свойство side-effect-free или pure (см. Стандартные свойства символов). См. списки, определённые в «byte-opt.el».