Введение

Диалект Lisp, используемый в качестве основного языка реализации и расширения для Emacs. Emacs Lisp — это диалект языка программирования Lisp, используемый в качестве скриптового языка в Emacs (семейство текстовых редакторов, наиболее часто ассоциируемое с GNU Emacs и XEmacs). Он применяется для реализации большей части функциональности редактирования, встроенной в Emacs, а остальное написано на C, как и интерпретатор Lisp. Emacs Lisp также называют Elisp, хотя существуют и более старые, не связанные с ним диалекты Lisp с таким же названием. Пользователи Emacs обычно пишут код на Emacs Lisp для настройки и расширения Emacs. Сама система Customize, написанная на Emacs Lisp, предоставляет набор страниц настроек, позволяющих пользователю задавать параметры и просматривать их влияние в текущей сессии Emacs. Когда пользователь сохраняет изменения, Customize просто записывает необходимый код Emacs Lisp в конфигурационный файл пользователя, который можно настроить на специальный файл, используемый только Customize, чтобы избежать случайного изменения файла пользователя. Emacs Lisp также может использоваться как скриптовый язык, подобно Unix Bourne shell или Perl, путем запуска Emacs в пакетном режиме. В этом случае его можно вызывать из командной строки или через исполняемый файл, и функции редактирования, такие как буферы и команды перемещения, доступны программе так же, как и в обычном режиме. При запуске Emacs в пакетном режиме пользовательский интерфейс не отображается; он просто выполняет переданный скрипт и завершает работу, выводя любые результаты работы скрипта.

По сравнению с другими диалектами Lisp

Emacs Lisp наиболее тесно связан с Maclisp, с некоторым более поздним влиянием Common Lisp. Он поддерживает императивное и функциональное программирование. Lisp был языком расширения по умолчанию для производных Emacs, таких как EINE и ZWEI. Когда Ричард Столлман отделил Gosling Emacs, создав GNU Emacs, он также выбрал Lisp в качестве языка расширения благодаря его мощным возможностям, включая способность рассматривать функции как данные. Хотя стандарт Common Lisp еще не был разработан, Scheme существовал в то время, но Столлман решил не использовать его из-за сравнительно низкой производительности на рабочих станциях (в отличие от миникомпьютеров, которые традиционно использовались для Emacs), и он хотел разработать диалект, который, по его мнению, будет проще оптимизировать. Диалект Lisp, используемый в Emacs, существенно отличается от более современных диалектов Common Lisp и Scheme, применяемых для разработки приложений. Характерной особенностью Emacs Lisp является использование динамической области видимости по умолчанию, а не лексической. Это означает, что функция может обращаться к локальным переменным в области, из которой она вызвана, но не в области, где она была определена. В последнее время ведется постоянная работа по обновлению кода для использования лексической области видимости, по причинам, которые будут описаны ниже.

Код источника

Код Emacs Lisp хранится в файловых системах как обычные текстовые файлы, как правило, с расширением ".el". Файл инициализации пользователя является исключением, часто встречаясь как ".emacs", несмотря на то, что он вычисляется как любой другой код Emacs Lisp. Начиная с середины 1990-х годов, Emacs также загружает ~/.emacs.el и ~/.emacs.d/init.el. Кроме того, пользователи могут указать любой файл для загрузки в качестве конфигурационного файла через командную строку или явно указать, что загружать конфигурационный файл не нужно. При загрузке файлов интерпретатор Emacs читает и разбирает функции и переменные, сохраняя их в памяти. Затем они становятся доступны для других функций редактирования и команд пользователя. Функции и переменные можно свободно изменять и переопределять без перезапуска редактора или перезагрузки конфигурационного файла. Для экономии времени и памяти большая часть функциональности Emacs загружается только по мере необходимости. Каждый набор дополнительных возможностей, поставляемый с Emacs, реализован в виде коллекции кода Emacs, называемой пакетом или библиотекой. Например, существует библиотека для выделения ключевых слов в исходном коде программы и библиотека для игры в Тетрис. Каждая библиотека реализована с использованием одного или нескольких исходных файлов Emacs Lisp. Библиотеки могут определять один или несколько основных режимов для активации и управления их работой. Разработчики Emacs пишут некоторые функции на языке C. Это примитивы, также называемые встроенными функциями или подпрограммами. Хотя примитивы можно вызывать из кода Lisp, их можно изменять только путем редактирования исходных файлов на C и повторной компиляции. В GNU Emacs примитивы недоступны как внешние библиотеки; они являются частью исполняемого файла Emacs. В XEmacs возможна загрузка таких примитивов во время выполнения с использованием поддержки динамической линковки операционной системы. Функции могут быть реализованы как примитивы, потому что им требуется доступ к внешним данным и библиотекам, недоступным в Emacs Lisp, или потому, что они вызываются достаточно часто, чтобы более высокая скорость C по сравнению с Emacs Lisp была существенной. Однако, поскольку ошибки в коде на C могут легко привести к нарушениям сегментации или более тонким ошибкам, вызывающим сбой редактора, и поскольку написание кода на C, корректно взаимодействующего со сборщиком мусора Emacs Lisp, подвержено ошибкам, количество функций, реализованных в виде примитивов, поддерживается на необходимом минимуме.

Байт-код

Байт-компиляция может ускорить выполнение кода Emacs Lisp. Emacs содержит компилятор, который преобразует исходные файлы Emacs Lisp в специальное представление, называемое байт-кодом. Файлы байт-кода Emacs Lisp имеют расширение имени файла ".elc". По сравнению с исходными файлами, файлы байт-кода загружаются и выполняются быстрее, занимают меньше места на диске и требуют меньше памяти при загрузке. Байт-код по-прежнему работает медленнее, чем машинный код, но функции, загруженные как байт-код, можно легко изменять и перезагружать. Кроме того, файлы байт-кода не зависят от платформы. Стандартный код Emacs Lisp, поставляемый с Emacs, загружается как байт-код, хотя соответствующие исходные файлы обычно предоставляются для справки пользователя. Расширения, создаваемые пользователями, обычно не байт-компилируются, поскольку они не столь велики и не требуют значительных вычислительных ресурсов.

Языковые особенности

Примечательно, что пакет "cl lib" реализует довольно большой поднабор Common Lisp. Этот пакет заменяет более ранний пакет "cl", который перезаписывал существующие определения функций Emacs Lisp, заменяя их определениями, более похожими на те, что используются в Common Lisp. Пакет "cl lib", напротив, более строго следует соглашениям о стиле Emacs Lisp и добавляет префикс "cl " ко всем определяемым им функциям и макросам (например, cl-defun, что не конфликтует с именем встроенной функции defun), избегая неожиданных изменений в поведении, которые могли возникать при загрузке пакета "cl". Emacs Lisp (в отличие от некоторых других реализаций Lisp) не выполняет оптимизацию хвостовых вызовов. Без этой оптимизации хвостовая рекурсия может в конечном итоге привести к переполнению стека. Библиотека apel помогает в написании переносимого кода Emacs Lisp, используя платформенный мост polysylabi. Emacs Lisp, как и Common Lisp, является Lisp-2, то есть имеет отдельное пространство имен для функций и пространство имен для других переменных.

От динамического к лексическому

Как и MacLisp, Emacs Lisp использует динамическую область видимости, предлагая статическую (или лексическую) область видимости в качестве опции, начиная с версии 24. Её можно активировать, установив локальную переменную файла `lexical-binding`. До добавления этой опции можно было использовать лексический макрос `let` из (ныне устаревшего) пакета "cl" для обеспечения эффективной лексической области видимости. При динамической области видимости, если программист объявляет переменную в области видимости функции, она становится доступной подпрограммам, вызываемым из этой функции. Изначально это задумывалось как оптимизация; лексическая область видимости все еще была редкостью и её производительность была неопределенной. По воспоминаниям ученого-компьютера Олина Шиверса, "Я спросил RMS, почему он реализовал Emacs Lisp с динамической областью видимости, и он ответил, что лексическая область видимости была слишком неэффективной". Динамическая область видимости также должна была обеспечить большую гибкость для пользовательских настроек. Однако динамическая область видимости имеет несколько недостатков. Во-первых, она может легко приводить к ошибкам в больших программах из-за непреднамеренного взаимодействия между переменными в разных функциях. Во-вторых, доступ к переменным при динамической области видимости обычно медленнее, чем при лексической области видимости.