Написание примитивов 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