Spec-Zone.ru › Elisp

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

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

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

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

Это имя переменной C, используемой для структуры, содержащей данные объекта subr, представляющего функцию в Lisp. Эта структура передает имя символа Lisp процедуре инициализации, которая создает символ и сохраняет объект subr в качестве его определения. По соглашению, это имя всегда совпадает с 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, реализующей примитив, например:

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

Можно указать несколько атрибутов подряд. В настоящее время распознаются только следующие атрибуты:

noreturn

Объявляет функцию C как функцию, которая никогда не возвращает управление. Это соответствует [[noreturn]] в C23, _Noreturn в C11 и __attribute__ ((__noreturn__)) в GCC (см. Атрибуты функций в Использование коллекции компиляторов GNU). (Внутри Emacs в собственном коде C используется _Noreturn, поскольку его можно определить как макрос на платформах C, которые его не поддерживают.)

const

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

noinline

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

После вызова макроса 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 на содержимое строк или текст буфера код должен хранить позицию в буфере или строке, а после вычисления Lisp повторно вычислять указатель C по этой позиции. Вычисление Lisp может происходить при вызовах eval_sub или Feval, непосредственно или косвенно.

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

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

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

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, по соглашению перед именем переменной 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);

Если вы хотите, чтобы переменная 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: /* 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, вызывают с помощью Ffuncall, которая представляет функцию Lisp funcall. Поскольку функция Lisp funcall принимает неограниченное число аргументов, в C она принимает два аргумента: число аргументов уровня Lisp и одномерный массив, содержащий их значения. Первый аргумент уровня Lisp — вызываемая функция Lisp, остальные — аргументы, передаваемые ей.

Макрос C calln — удобный способ вызвать функцию Lisp, не указывая число аргументов. Он работает, вызывая Ffuncall.

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

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

Copyright © 1990-1996, 1998-2026 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Writing-Emacs-Primitives.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API