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 для вызова функции. По соглашению имя начинается с 'F' перед именем Lisp, при этом все тире ('-') в имени 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 (см. Использование интерактивного режима). В случае 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 как функцию, которая никогда не возвращает значение. Это соответствует ключевому слову C11 _Noreturn и атрибуту __attribute__ ((__noreturn__)) GCC (см. Атрибуты функций в Использование GNU компилятора).

const

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

noinline

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

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

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

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

Copyright © 1990-1996, 1998-2022 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