Программирование GNU/Emacs для редактирования и рефакторинга кода

222 44 827KB

Russian Pages [48]

Report DMCA / Copyright

DOWNLOAD FILE

Программирование GNU/Emacs для редактирования и рефакторинга кода

Table of contents :
Благодарности
К читателю
Оглавление
Введение
Коротко о GNU Emacs
Получение справки и поиск информации
Буфера, пакеты, режимы
Основы Emacs Lisp
Lisp, формы, списки и префиксная нотация
Как писать программы на Emacs Lisp
Динамическое вычисление кода (eval)
Функции и команды
Загрузка и выполнение скриптов
Типы данных
Символы и переменные
Объявление функций
Управление ходом программы
(progn )
(if )
(cond )
(while )
(dotimes ( [результат]) )
(dolist ( [результат]) )
do, do*
loop
Работа со списками
Примеры elisp-скриптов
Импорт Java класса в программе на Clojure
Описание проблемы
План решения
Код
Описание решения
Генерация аксессоров в классе Java
Описание проблемы
План решения
Код
Генерация и инициализация полей Java-класса в Android-приложении
Описание проблемы
План решения
Код
Описание решения
Преобразование html-файла в haml-формат
Описание проблемы
План решения
Код
Описание решения
Создание конструктора для неизменяемых объектов
Описание проблемы
Справочник часто употребимых функций
Перемещение курсора и получение позиции
Группа функций point-
goto-char
line-number-at-pos
current-column
Группа функций beginning-of-/end-of-
Регион/Выделение текста
region-active-p
Группа функций region-
kill-region
kill-ring-save
Группа функций forward-/backward-
Группа функций search-
Работа с буфером
buffer-name
buffer-list
with-current-buffer
switch-to-buffer
current-word
insert
count-lines
find-file
delete-and-extract-region
Прочее
save-excursion
Группа функций completing-read/ido-completing-read
Дополнительная информация
Другие полезные пакеты
Литература

Citation preview

1

Д.Бушенко А.Отт Программирование Emacs для редактирования и рефакторинга кода. – Epam, 2011. – 48c. В пособии показаны основы редактора Emacs, начала программирования на Emacs Lisp; разобраны несколько примеров скриптов и наиболее часто употребляемые функции. Особый акцент сделан на применении Emacs Lisp для разработки программ автоматизированного рефакторинга кода.

2

БЛАГОДАРНОСТИ Авторы выражают благодарность всем, кто помогал им в работе: компании Epam System за поддержку в создании пособия; Амине Идиговой и Руслану Щуцкому за дизайн, верстку и печать книги; Василию Ременюку за организацию встречи любителей функционального программирования (scala.by), для которой и было написано это пособие.

К ЧИТАТЕЛЮ Друг, ты держишь в руках первое издание книги о программировании Emacs. Мы надеемся, что в будущем сможем добавить много материала и выпустить новое издание. С твоей помощью мы можем сделать его лучше! Если ты заметил ошибку, или хочешь предложить свой скрипт, тему или даже главу – напиши об этом авторам. Мы будем тебе признательны! Дмитрий Бушенко ([email protected]) Алекс Отт ([email protected])

3

ОГЛАВЛЕНИЕ Благодарности .................................................................................... 3 К читателю ......................................................................................... 3 Оглавление ......................................................................................... 4 Введение ............................................................................................ 6 Коротко о GNU Emacs ....................................................................... 8 Получение справки и поиск информации ..................................... 9 Буфера, пакеты, режимы................................................................ 9 Основы Emacs Lisp .......................................................................... 11 Lisp, формы, списки и префиксная нотация................................ 11 Как писать программы на Emacs Lisp ......................................... 12 Типы данных ................................................................................ 13 Символы и переменные ............................................................... 15 Объявление функций ................................................................... 17 Управление ходом программы .................................................... 17 Работа со списками ...................................................................... 21 Примеры elisp-скриптов .................................................................. 24 Импорт Java класса в программе на Clojure ................................ 24 Генерация аксессоров в классе Java ............................................ 27 Генерация и инициализация полей Java-класса в Androidприложении .................................................................................. 29 Преобразование html-файла в haml-формат ................................ 33 Создание конструктора для неизменяемых объектов ................. 35 Справочник часто употребимых функций ...................................... 39 Перемещение курсора и получение позиции .............................. 39 Группа функций point- ............................................................. 39 goto-char.................................................................................... 39 line-number-at-pos ..................................................................... 39 current-column ........................................................................... 39 Группа функций beginning-of-/end-of- ..................................... 40 Регион/Выделение текста ............................................................ 40 region-active-p ........................................................................... 40 Группа функций region- ........................................................... 40 kill-region .................................................................................. 40 kill-ring-save.............................................................................. 40 Группа функций forward-/backward-........................................ 41 Группа функций search- ........................................................... 41 4

Работа с буфером ......................................................................... 42 buffer-name ............................................................................... 42 buffer-list ................................................................................... 42 with-current-buffer ..................................................................... 42 switch-to-buffer ......................................................................... 43 current-word .............................................................................. 43 insert .......................................................................................... 43 count-lines ................................................................................. 44 find-file ...................................................................................... 44 delete-and-extract-region............................................................ 44 Прочее .......................................................................................... 44 save-excursion ........................................................................... 45 Группа функций completing-read/ido-completing-read ............. 45 Дополнительная информация.......................................................... 47 Другие полезные пакеты.............................................................. 48 Литература ................................................................................... 48

5

ВВЕДЕНИЕ Большая часть времени программиста занята написанием и редактированием программного кода. Существует множество инструментов, позволяющих быстро разрабатывать программный продукт, автоматизировать типовые задачи, проводить рефакторинг. Среди них – известные среды интегрированной разработки, сложные текстовые редакторы и консольные утилиты. Каждый из этих инструментов позволяет легко решать свой собственный класс задач. Здесь мы будем рассматривать только задачу автоматизированного редактирования и рефакторинга кода. Сравнивая возможности современных IDE, таких как MS Visual Studio, Eclipse, Netbeans, Idea, легко заметить, что они обладают схожей функциональностью. У них есть сниппеты и средства автоматического рефакторинга, например, переименование класса или инкапсуляция члена класса в аксессоре. Это очень хорошие средства, зачастую они покрывают большую часть потребностей программиста. Но что делать, если появились задачи, для которых в IDE еще нет решения? Сложные задачи, для которых пока нет готовых инструментов, чаще всего возникают на переднем крае Computer Science. Они появляются вместе с новыми языками программирования и экспериментальными технологиями. Больше всего нетипичных задач у любителей немейнстримных языков программирования, таких как Clojure, Scala, Haskell и т.д. Что же делать, если вы пользуетесь новейшими языками и технологиями, а ваш любимый текстовый редактор заставляет писать массу кода вручную? Разумеется, выбрать такой редактор, который может автоматизировать повторяющиеся задачи! В этом пособии мы вкратце рассмотрим возможности текстового редактора GNU Emacs, позволяющие автоматизировать рутину. Emacs – программируемый текстовый редактор, именно поэтому он идеально подходит для решения нетипичных задач. Если в нем не хватает какой-то возможности, то ее всегда можно добавить самостоятельно. Emacs написан, в основном, на языке Emacs Lisp (далее – elisp). Мы воспользуемся этим языком для того, чтобы реализовать нужные 6

нам функции текстового редактора. Поначалу это, конечно, может показаться сложным делом. Но усилия, потраченные на изучение Emacs и elisp, впоследствии окупятся.

7

КОРОТКО О GNU EMACS Emacs — программа с длиной историей. Еще недавно существовало несколько популярных ее разновидностей (XEmacs, SXEmacs, GNU Emacs), но в настоящее время, самой активно развивающейся является GNU Emacs. Стабильной версией GNU Emacs является версия 23. Но мы рекомендуем использовать бетаверсии GNU Emacs 24, в которой реализовано много новинок, включая пакетный менеджер, и многое другое. Пользователи Linux могут найти сборки для своих дистрибутивов, например, для Debian & Ubuntu 1. Пользователи Windows могут получить бинарную сборку2. При запуске, Emacs читает свою конфигурацию из файла ~/.emacs.d/init.el (или из ~/.emacs, если он существует). Эта конфигурация является программой на Emacs и описывает какие пакеты должны быть загружены, переменные выставлены и т.п. Но вначале, вам не обязательно все писать самим — можете начать с адаптации существующих конфигураций, например, из проекта Emacs Starter Kit 3, который был описан на русском языке4. Просто следуйте описанным шагам. Поведение Emacs можно настраивать почти не зная Emacs Lisp — используя механизм Customize. Имеется несколько команд для установки значений переменных, шрифтов (faces) и т.п. Самыми популярными являются команды customize-variable, customizegroup & customize-face. Эти команды можно запустить при помощи сочетания клавиш M-x , затем задайте имя переменной или группы переменных (используйте TAB для дополнения имен). Подробней о работе с Emacs вы можете в руководстве (есть и русская версия, но она для версии 20.x), которое идет вместе с ним, или в следующей статье5.

1

http://emacs.naquadah.org/ http://alpha.gnu.org/gnu/emacs/windows/ 3 https://github.com/technomancy/emacs-starter-kit 4 http://habrahabr.ru/blogs/emacs/94256/ , http://zahardzhan.github.com/2010/emacs-starter-kit-the-program.html 5 http://alexott.net/ru/writings/altlinux-emacs/index.html 2

8

Получение справки и поиск информации В поставке GNU Emacs имеется вся информация необходимая для начала работы с ним и его программирования. Для начинающих пользователей будет полезен учебник Emacs (вызываемый сочетанием клавиш C-h t), переведенный на разные языки, в том числе и на русский. Подробные руководства по Emacs & Emacs Lisp (а также многим другим пакетам и программам) поставляются в формате info, и вы можете обратиться к ним через меню или набрав сочетание C-h i. Кроме того, вы можете получить подробную информацию о переменных (текущее значение, описание) с помощью команды describe-variable (C-h v), функциях (описание, где определена) -клавиши (C-h f) и текущем режиме (сочетание клавиш C-h m).

Буфера, пакеты, режимы Практически вся функциональность Emacs (кроме низкоуровневых модулей) реализована на Emacs Lisp. В состав дистрибутива входит огромное количество пакетов расширений. Начиная с версии 24, в состав Emacs входит менеджер пакетов, который используется для установки "внешних" пакетов, в основном это release-версии. Если же вы хотите использовать bleeding edge-версии, то можете ставить пакеты сами (правда их потом неудобно поддерживать), либо воспользоваться пакетом el-get6, который автоматизирует установку и поддержание пакетов в актуальном состоянии. Работа с текстом происходит в буферах Emacs, которые обычно связаны с файлами на диске. Каждый буфер имеет один основной (major) режим, который определяет, как текст будет редактироваться, и может также иметь несколько дополнительных (minor) режимов. Например, основными режимами являются режимы для редактирования исходного кода на C/C++, Java, Clojure, Scala и т.д., а дополнительными режимами являются режим подсветки ключевых слов (font-lock), проверки орфографии и т.д. Посмотреть список включенных режимов для текущего буфера можно при помощи сочетания клавиш C-h m (вам также будут показаны все keybindings — имеющиеся сочетания клавиш, и команды к ним привязанные). Обычно основные режимы включаются автоматически, в зависимости от расширения файла, 6

https://github.com/dimitri/el-get

9

но вы можете включить их явно с помощью M-x (например, M-x clojure-mode) или изменив маппинг между именем файла и режимом, например: (add-to-list 'auto-mode-alist '("\\.ipp$" . c++-mode))

Режимы могут иметь собственные настройки, обычно это переменные и хуки (набор функций, которые будут вызваны при включении режима). Переменные могут быть глобальными — общими для всех буферов, и локальными — специфическими для конкретных буферов. Обычно в описании переменных (сочетание C-h v) можно увидеть — локальная эта переменная или глобальная. Глобальные переменные устанавливаются либо через M-x customize-variable, либо с помощью setq из Emacs Lisp. А локальные переменные в основном устанавливаются из хуков. Хук (hook) — это набор функций без аргументов, которые будут вызваны при включении режима (открытии файла, или создании буфера, или явном включении режима). Для каждого режима вы можете определить множество таких функций, которые будут вызваны по очереди. Обычно с помощью таких хуков устанавливаются значения для локальных переменных, локальные привязки клавиш, дополнительные режимы и т.п. Функции добавляются в хук с помощью функции add-hook, а удаляются с помощью remove-hook (удаление хуков – редко встречающаяся операция). Например: (defun common-hook () (local-set-key "\C-c:" 'uncomment-region) (local-set-key "\C-c;" 'comment-region) (local-set-key "\C-c\C-c" 'comment-region) (font-lock-mode 1)) (add-hook 'emacs-lisp-mode-hook 'common-hook) (defun elisp-mode-hook () ;; set local variables (setq indent-tabs-mode t) ;; enable minor modes (turn-on-eldoc-mode) (paredit-mode 1) ;; bind local keys (local-set-key [(control c) /] 'semantic-ia-complete-symbol)) (add-hook 'emacs-lisp-mode-hook 'elisp-mode-hook)

10

ОСНОВЫ EMACS LISP Lisp, формы, списки и префиксная нотация В elisp объект, который может быть ―вычислен‖ (получено значение для него), называют формой. Имеется три вида форм: ● символ7 -- объект с уникальным именем, при вычислении которого (в зависимости от контекста), мы либо получаем значение, связанное с символом, либо вызываем функцию, которая сохранена в функциональном слоте символа; ● список -- набор форм, заключенный в скобки, где первый элемент списка определяет подтип формы -- функция, макрос или специальная форма. ● остальные формы, вычисляемые сами в себя (self-evaluating forms) -- числа, строки, буквы. Список, содержащий код, по сути, ничем не отличается от списка, содержащего данные. Интерпретатор Lisp-а считает первый элемент списка – вызовом функции (или специальной формы, или макроса), а остальные элементы – параметрами функции. Например, список (+ 2 3 4) является вызовом функции + с параметрами 2, 3, 4 и будет вычислен как 9. Такая запись вызова функции называется префиксной. Практически все -- математические операции, работа с файлами и т.п. -- это обычные функции, точно такие же, какие будем писать далее в этом пособии. Помимо функций имеются также специальные формы -- примитивные функции, параметры которых не вычисляются в момент вызова. Это поведение отличается от обычных функций, аргументы которых вычисляются до вызова функции. Сюда относятся if, cond, defun, defvar, let, and, и т.д. А макросы -- функции, выполняющиеся во время компиляции кода, позволяют генерировать код, который затем будет выполнен. Интерпретатор Lisp-а не будет вычислять форму, перед которой стоит кавычка8, т.к. считается, что это данные. Например, если мы 7

Символ чем-то похож на ―переменные‖ в других языках программирования. Подробнее о символах читайте дальше. 8

Это синтаксический сахар для специальной формы quote.

11

запишем „(+ 2 2), то для интерпретатора этот список останется списком, а не вычислится в число 4.

Как писать программы на Emacs Lisp Динамическое вычисление кода (eval) Скрипты Emacs можно писать и вычислять9 (eval) в любом открытом буфере, в котором включен режим emacs-lisp, независимо от его содержания. Например, откройте буфер *scratch*10 и напишите функцию hello. (defun hello () "Hello world function" (interactive) (message "Hello, World!"))

Для того чтобы вычислить этот код, необходимо поставить курсор после самой последней скобки функции и нажать комбинацию C-x C-e (функция eval-last-sexp). Кроме того, вы можете вычислить этот код с помощью функции eval-defun, которая привязана к клавишам C-M-x, при этом вы можете находиться в любой точке этой функции. Выполнить функцию hello можно тем же способом, что и любые другие команды Emacs: M-x hello11. Справка по этой функции, как и по другим, доступна по комбинации C-h f . Функции и команды Emacs различает два типа функций: «чистые функции», возвращающие какой-то результат, и команды (интерактивные функции), выполняемые при помощи M-x в основном ради 9

Emacs компилирует исходный код в байт-код, который потом выполняется. 10 Этот буфер не связан с файлом на диске, так что при выходе из редактора, его содержимое не будет сохранено. 11 Этот метод запуска работает только для интерактивных функций. Если вам нужно вызвать неинтерактивную функцию, то вы можете воспользоваться сочетанием клавиш M-: и ввести нужное выражение в минибуфере.

12

побочных эффектов. Технически, команды отличаются от простых функций только вызовом (interactive). Если вы хотите выполнить вашу функцию при помощи M-x, то в ней обязательно должен присутствовать вызов (interactive). Загрузка и выполнение скриптов Как правило, скрипты Emacs хранятся в каталоге $HOME/.emacs.d/ в файлах с расширением *.el. Загрузка и компиляция выполняется командой load-file. При выполнении этой команды необходимо задать имя загружаемого файла, после чего Emacs скомпилирует указанный скрипт. Функции и команды из этого скрипта сразу же станут доступны пользователю.

Типы данных Emacs Lisp поддерживает стандартный набор типов. К базовым типам относятся: ● логические значения -- nil используется для обозначения false, и t -- для обозначения true. На самом деле, nil также обозначает и пустой список (), и взаимозаменяем с ними в условных операторах. А вместо t может использоваться любое не-nil значение -- число, строка, список и т.д.; ● целые числа в диапазоне -2^29 до 2^29, (2 бита отведено для поля типа объекта); ● числа с плавающей точкой -- стандартный double из C; ● буквы (character) -- записываются как ?S для обычных букв и цифр, ?\n, ?\t, ?\\ и т.д. -- для стандартных последовательностей, или ?\NNN, ?\xNN для записи символа в 8- или 16-ричной системе исчисления. Поддерживается и Unicode с помощью записи ?\uNNNN; ● строки - набор букв, записанный в двойных кавычках. ● точечная пара (cons cell) и списки -- основные типы для ―сложных структур данных‖. Точечная пара -- объект состоящий из 2-х слотов, с традиционными названиями car и cdr. Точечная пара записывается как (val1 . val2). Списки в свою очередь строятся на основе точечных пар, где cdr указывает на следующий элемент списка, так что список (1 2 3) можно рассматривать как набор точечных пар вида: (1 . (2 . (3 . nil))). Список может содержать элементы разных типов. Имеется также специальная запись для так называемых 13

ассоциативных списков, где каждый элемент является точечной парой, представляющей собой ключ/значение. Например, ((rose . red) (lily . white) (buttercup . yellow)).12 ● массивы -- также как и в других языках программирования, массивы обеспечивают константную скорость доступа к элементам. Вариантами массивов являются вектора, строки и т.п. типы. Помимо базовых типов данных при программировании на Emacs Lisp часто используются сложные объекты, которые стоит упомянуть: ● точка (point) -- это место в буфере, в котором происходит вставка или удаление данных. Она же обозначается курсором в активном буфере. Каждый буфер всегда имеет точку, даже если он не активен. ● метка (mark) -- это специальная позиция, которая указывает на конец выделенной области. Вторым концом области является текущая точка. Стоит отметить, что даже если выделение снято, то метка все равно остается, пока не будет сделано новое выделение текста. ● область (region) -- выделенный текст в буфере, ограниченный точкой и меткой. Пользователь может выполнять различные команды над выделенной областью. ● буфер -- объект содержащий текст, чаще всего это данные из файла на диске, но не обязательно. В каждый момент времени может существовать только один активный буфер, но вы можете переключаться и в другие буфера по необходимости. Каждый буфер имеет набор связанных с ним данных - точка и маркер, локальные переменные и т.д. Обычно буфер содержит текст, так что вы можете манипулировать им, также как и при работе с обычными строками. В некоторых случаях ваш код должен работать с данными разных типов, так что в этих случаях вам нужны функции проверки типов. В соответствии со стандартами программирования на Emacs Lisp, функции предикаты имеют имена, оканчивающиеся на `p‘, так что вы можете найти функцию проверки типа путем добавления `p‘ к названию типа -- listp, integerp, symbolp, bufferp, и т.д. Кроме того, 12

Ассоциативные списки хороши для небольших объемов данных, но если вам нужно хранить много данных, то лучше воспользоваться хештаблицами.

14

вы можете получить символ, описывающий заданный объект с помощью функции type-of.

Символы и переменные В Emacs, как и в других Lisp‘ах, имеется понятие символа (symbol) -- объекта с уникальным именем. Каждый символ имеет четыре компонента -- печатное имя, значение (value), связанную функцию и список свойств (property list). Определение символов производится с помощью различных выражений, которые определяют как этот символ будет использоваться 13. defvar & defconst определяют символ как глобальную переменную или константу14. Но в Emacs Lisp объявление переменных не является обязательным, так что вы можете создавать их с помощью специальной формы setq, которая присваивает значение символу (или нескольким символам15). (setq a 17) (setq s "Hello!")

что эквивалентно: (defvar a 17) (defvar s "Hello!")

Переменные a и s будут объявлены глобальными, только если их значения не устанавливаются внутри области видимости локальных a и s. В случае если в функции уже объявлены a или s как параметры либо как локальные переменные внутри let, то setq изменит значения именно этих, локальных, переменных. Локальные переменные объявляются при помощи функций let и let*. (let (a b (c 3)) (setq a 1 b 2) 13

Сюда относятся defvar, defconst, defun, defmacro. Одним из преимуществ использования defvar & defconst является возможность добавления документации к символу. 14

15

Этот пример может быть записан как (setq a 17 s "Hello!"), поскольку setq принимает список пар символ/значение.

15

(message (number-to-string (+ a b c))))

Оператор let действует примерно как фигурные скобки в java: создает новую область видимости. Let создает переменные, действующие только в этой области, здесь – a и b; изначально они содержат значение nil. Если переменную нужно инициализировать некоторым значением, то имя переменной и значение нужно взять в скобки, как здесь значение 3 присвоено переменной с. По умолчанию, на переменные объявленные в let нельзя ссылаться в этом же списке инициализации, для этого имеется форма let*. Например, вот этот код не будет работать: (let ((a 2) (b (+ a 2))) ...) а вот этот, будет: (let* ((a 2) (b (+ a 2))) ...) Локальным переменным a и b присваивает значения оператор setq.16 Функция message выведет в минибуфере любую строку. Для того чтобы результат суммы переменных a, b и c перевести в строку здесь используется функция number-to-string. Все тело выражения let можно выполнить при помощи C-x e так же, как до этого мы компилировали elisp-функции. Параметры функций задаются в скобках после названия функции17. (defun plus (a b) (interactive) (message (number-to-string (+ a b))))

Достаточно часто возникает задача сравнения значений переменных, и для этого имеется набор функций, как общих, так и

16

Также есть более общая функция setf, которая умеет присваивать значения ссылкам, например, элементам списка. 17 Вызов (interactive) здесь нужен только для запуска функции с помощью M-x. Если ваша функция не будет интерактивной, а будет использована из других функций или хуков, то (interactive) не нужен. То же самое справедливо и для других примеров.

16

специализированных для конкретных типов. К общим функциям относятся: ● (eq obj1 obj2) -- возвращает t если obj1 и obj2 являются одним и тем же объектом, целым числом с одинаковым значением, или пустой строкой; ● (eql obj1 obj2) -- аналогично eq, но также работает и для чисел с плавающей точкой; ● (equal obj1 obj2) -- возвращает t если obj1 и obj2 имеют одинаковые компоненты, что позволяет использовать ее для сравнения списков, строк и других объектов. К специализированным функциям сравнения относятся: ● =, /=, , = -- для сравнения чисел; ● char-equal, string=, string< -- для сравнения строк и букв;

Объявление функций Есть два способа объявить функцию. Первый, как мы уже видели ранее, при помощи defun: (defun ()

) Второй способ – при помощи макроса lambda создать анонимную функцию. (lambda ()

) Результатом вычисления будет тело самой функции. Поскольку никакого имени ей не присваивается, то лямбда функцию можно либо сохранить в символе при помощи setq, либо вызвать ее в funcall или mapcar. (setq l-hello (lambda () (message "Hello, World!"))) (defun hello2 () (interactive) (funcall l-hello))

Управление ходом программы (progn ) Последовательно выполняет все функции, переданные progn в качестве параметров. Возвращает результат выполнения последней 17

функции. Используется везде, где нужно вместо одного выражения выполнить несколько, например, из-за побочных эффектов.18 (if ) Специальная форма if работает так же, как и в любом другом языке программирования. Параметр является необязательным. (let ((a 5) b) (if (> a 0) (progn (setq b 17) (message (number-to-string b))) (message "The ELSE branch")))

В данном примере локальной переменной а присваивается значение 5, объявляется локальная переменная b. В условии if проверяется, является ли a положительным числом. Если да, то выполняется последовательность функций: 1) переменной b присваивается значение 17; 2) выводится в минибуфер значение переменной b. Иначе выводится строка «The ELSE branch». (cond ) cond работает по тому же принципу, что и switch/case в Java, но ветвление производится по произвольному предикату, а не по фиксированному значению. Пример: (let ((a 5)) (cond ((> a 0) (message "Positive")) ((= a 0) (message "Zero")) ((< a 0) (message "Negative"))))

Примечание: строки и списки нужно сравнивать функцией equal. (while ) Специальная форма while работает также как и в любом другом языке программирования. (let ((s "") (i 0)) (while (< i 10) (setq s (concat s (number-to-string i))) 18

Есть также формы prog1, prog2, и т.д.

18

(setq i (1+ i))) (message s))

После выполнения данного примера будет выведена строка «0123456789». (dotimes ( [результат]) ) Предыдущий пример можно переписать следующим образом: (let ((s "")) (dotimes (i 10) (setq s (concat s (number-to-string i)))) (message s))

Возвращаемое значение из dotimes -- [результат] или nil, если [результат] не задан. (dolist ( [результат]) ) Еще один способ выполнить конкатенацию всех цифр от 0 до 9 – задать их в списке. Функция dolist работает как foreach (формат вызова примерно такой же, как у dotimes): (let ((s "")) (dolist (i '(0 1 2 3 4 5 6 7 8 9)) (setq s (concat s (number-to-string i)))) (message s))

Здесь список цифр от 0 до 9 не является вызовом функции, т.к. перед ним стоит одинарная кавычка. do, do* Макрос do – одна из самых сложных в этой главе. Выглядит он так: (do () () ) Обратите внимание: секции «объявление переменных» и «условия выхода из цикла» -- это списки. Поэтому, даже если переменная всего одна, и условие выхода только одно, все равно их нужно заключать в дополнительные скобки. Вот как выглядит предыдущий пример с конкатенацией цифр при помощи функции do: (let ((s "")) (do ((i 0 (1+ i)))

19

((equal i 9)) (setq s (concat s (number-to-string i)))) (message s))

Первый параметр – список объявлений переменных, каждое из которых также является списком. Здесь объявляется переменная i, начальное значение – 0, функция для изменения значения переменной i – (1+ i). Второй параметр – список условий выхода из цикла. Каждое условие – вызов какой-либо функции. Поскольку этот вызов – также является списком, то условие выхода из цикла выглядит ((equal I 9)). Последний параметр – тело цикла. Здесь – это конкатенация цифр и сохранение значения в переменной s. Функция do* отличается от do тем же, чем let* от let. loop Макрос loop имеет довольно большое описание. Начнем с простого. Еѐ можно использовать для итераций вообще безо всяких дополнительных церемоний. Для выхода из цикла воспользуйтесь функцией return. (let ((a 0) s) (loop (if (< a 10) (progn (setq s (concat s (number-to-string a))) (setq a (1+ a))) (return))) (message s)) Также, как и dolist, макрос loop можно применить для итерации по списку. (let ((a 0) s) (loop for x in '(0 1 2 3 4 5 6 7 8 9) do (setq s (concat s (number-to-string x))) ) (message s)) По двум спискам одновременно: (let ((a 0) s) (loop for x in '(0 1 2) for y in '("a" "b" "c") 20

do (setq s (concat s (concat (number-to-string x) y)))) (message s)) В отличие от двух вложенных циклов for в java, здесь итерация по спискам проходит одновременно. Поэтому результат выполнения приведенного кода будет ―0a1b2c‖. Если на вход предложены списки разной длины, то итерация будет происходит по длине наименьшего. Следующий пример показывает еще один способ использования loop: (loop for x from 1 to 5 for y = (* x 2) collect y) Результат выполнения этого кода: (2 4 6 8 10).

Работа со списками Пара из двух элементов создается функцией cons. Первый элемент пары можно получить функцией car, второй -- cdr. (setq a (cons 1 2)) ; ‗(1 2) (car a) ;1 (cdr a) ;2 Список из нескольких элементов создается при помощи кобинации пар. Например: (cons 1 (cons 2 (cons 3 nil))) Такая запись для большого числа аргументов неудобна; на замену ей существует функция list: (list 1 2 3) Третий способ записать список -- при помощи кавычки: ‗(1 2 3) Добавить еще один элемент к началу списка можно при помощи функции cons: (cons 0 (list 1 2 3)) ; ‗(0 1 2 3) 21

Длину списка вычислит функция length: (length ‗(1 2 3)) ; 3 N-й элемент списка вычисляет функция nth: (nth 3 '(1 2 3 4 5 6)) ; 4 N-й cdr -- функция nthcdr: (nthcdr 3 '(1 2 3 4 5 6)) ; ‗(4 5 6) Первую ячейку списка можно изменить при помощи setcar: (setq a '(1 2 3)) (setcar a 0) ; a теперь содержит ‗(0 2 3) Аналогично для cdr: (setq a '(1 2 3)) (setcdr a '(4 5)) ; a теперь содержит ‗(1 4 5) Для того, чтобы изменить n-й член списка, необходимо скомбинировать несколько функций: (setq a '(1 2 3 4 5)) (setcdr a '(4 5)) (setcar (nthcdr 2 a) 7) ; a теперь содержит ‗(1 2 7 4 5)

Ввод-вывод Для того, чтобы вывести строку в минибуфер 19, необходимо воспользоваться функцией message. Для преобразования числа в строку предназначена функция number-to-string, строку в число – string-to-number. Ввод данных из минибуфера осуществляется при помощи функции read-string: (read-string “” “”) Пример: (let (a) 19

Она также будет выведена в буфер *Messages*

22

(setq a (string-to-number (read-string ">> " "5"))) (message (number-to-string (+ a 2))))

Если в Emacs установлен пакет IDO, то удобно пользоваться функцией ido-completing-read: (ido-completing-read “” nil ) Первый параметр – строка приглашения. Работает также, как и в read-string. Второй параметр – список вариантов на выбор. Третий параметр здесь всегда nil и используется для совместимости с функцией completing-read. Четвертый параметр указывает, должна ли функция ido-completing-read требовать точное соответствие введенного текста с одним из вариантов. Например: (message (ido-completing-read

">> " '("One" "Two" "Three") nil t))

23

ПРИМЕРЫ ELISP-СКРИПТОВ В этой части пособия мы рассмотрим несколько примеров решения реальных задач с применением elisp. Для понимания материала очень желательно начальное знакомство с Emacs и любым Lisp-ом. Все elisp-функции, использованные в примерах, а также некоторые другие, будут подробно разобраны в последней части -справочнике наиболее часто употребляемых функций.

Импорт Java класса в программе на Clojure Описание проблемы При разработке программ на Clojure c применением библиотеки Swing часто приходится импортировать классы вручную. Например, на рис.1 показано создание объекта JSlider. Для того, чтобы этот код заработал, необходимо импортировать класс javax.swing.JSlider, как это показано на рис.2.

Рис.1 Создание объекта класса javax.swing.JSlider

24

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

Рис.2 Импорт класса javax.swing.JSlider

План решения Необходимо разработать функцию my-import, которая запросит название импортируемого класса (функция get-java-class-name получает название класса под курсором), название пакета класса и добавит указанный класс в список импорта. Код 1. (defun get-java-class-name () 2. (condition-case nil 3. (let* ((class-name-regex "\\([[:alpha:]][_$[:alnum:]]*\\)") 4. (class-name-regex-full (concat "(\\(" class-name-regex 5. "\\.\\|new[[:space:]]*" class-name-regex 6. "\\)[[:space:])]"))) 7. (search-forward ")") 8. (when (search-backward-regexp class-name-regex-full) 9. (or (match-string 2) (match-string 3)))) 10. (error "")))

25

11. (defun my-import (cname nsname) 12. (interactive 13. (let ((cname (read-string "Insert class: " 14. (get-java-class-name))) 15. (nsname (read-string "Namespace: " "javax.swing"))) 16. (list cname nsname))) 17. (save-excursion 18. (beginning-of-buffer) 19. (search-forward-regexp (concat ":import \\[" nsname)) 20. (insert (concat " " cname)))) Описание решения Строка 1 объявляет функцию get-java-class-name, которая будет использоваться в команде my-import для запроса имени импортируемого java-класса. В строках 2-10 реализована сложная конструкция condition-case. Она имеет следующую структуру: (condition-case VAR BODYFORM &rest HANDLERS) Здесь никаких переменных не объявляется, поэтому второй параметр -- nil. Тело формы выполняет необходимые операции и возвращает значение -- это значение вернет и вся форма conditioncase. В случае, если во время выполнения тела формы произойдет ошибка (например, мы не найдем название класса), то управление перейдет к третьему параметру -- вызову функции error, которая вернет пустую строку). Таким образом, здесь функция conditioncase работает примерно также, как и try/catch в Java. В строке 3 создается часть паттерна регулярного выражения для имени класса, которое может включать все алфавитно-цифровые символы и знак подчеркивания. В строке 4 создается полный паттерн регулярного выражения, позволяющего найти имя импортируемого класса как для формы (JSlider.), так и для (new JSlider). Созданный паттерн будет выплнять поиск для выражений вида ―(ClassName.) | (new ClassName)‖. Соответственно, в регулярном выражении есть несколько групп матчинга. Первая группа матчинга будет соответствовать всему выражению вместе с символом ―.‖ или словом ―new‖. Вторая группа -- найденный класс в (ClassName.), третья -- найденный класс в (new ClassName).

26

В строке 8 проверяется, найдена ли нужная нам строка. Если да -то возвращается найденное имя класса из второй или третьей группы матчинга (строка 9). Строки 11-12 создают команду my-import. Строки 13-14 предложат ввести имя импортируемого класса. Поумолчанию будет предложено слово, на котором пользователь оставил курсор. Введенное имя класса будет сохранено в переменной cname. Строка 15 аналогичным образом запрашивает имя пакета, в котором находится импортируемый класс, и сохраняет в переменной nsname. Обратите внимание, что команда my-import является также и обыкновенной функцией, которая принимает два аргумента: имя класса (cname) и имя пакета (nsname). Вызов interactive передает интерпретатору Lisp-а эти два параметра в виде списка (cname nsname), если значения параметров не заданы пользователем при вызове my-import. Строка 17 сохраняет текущее положение курсора для того, чтобы после вызова функции my-import курсор вернулся на исходную позицию. Строка 18 переводит указатель на начало буфера. Строка 19 находит место, где импортируются другие классы из указанного пакета. Строка 20 вставляет импортируемый класс в строку с импортом. После всех операций указатель вернется на исходную позицию. Примечание: это примерная версия такой функции -- она имеет несколько недостатков, например, она требует, чтобы директива (:import [nsname ..] уже была в тексте, но это тоже можно реализовать с помощью elisp.

Генерация аксессоров в классе Java Описание проблемы Приватные члены класса необходимо сделать доступными извне при помощи геттеров/сеттеров (аксессоров). Eclipse и Netbeans неплохо справляются с этой задачей, но вставляют аксессоры в неподходящие места файла. Поэтому, сгенерированные функции приходится вручную переносить туда, где им следует быть. 27

План решения Необходимо разработать функцию my-accessor, создаст аксессоры для переменной, на которую указывает курсор (рис. 3).

Рис. 3 Генерация аксессоров

Код 1. (defun my-accessor () 2. (interactive) 3. (save-excursion 4. (let* ((word (current-word)) 5. (upcased (capitalize word)) 6. (type 7. (progn (backward-word) 8. (let ((w2 (current-word))) 9. (if (string= word w2) 10. (progn 11. (backward-word) 12. (current-word)) 13. w2))))) 14. (beginning-of-buffer) 15. (search-forward "// Accessors") 16. (insert (concat "\n\tpublic " type " get" 17. upcased "(){\n\t\treturn " 28

18. word ";\n\t}\n")) 19. (insert (concat "\tpublic void set" upcased 20. "(" type " " word ") {\n\t\t" 21. "this." word " = " word ";\n\t}"))))) Описание решения Строки 1-2 объявляют команду my-accessor. В строке 3 сохраняется текущее положение курсора. После выполнения команды my-accessor, оно будет восстановлено. В строке 4 объявляются локальные переменные. Переменная word при помощи функции current-word получит слово, на котором находится курсор. Переменная upcased получит значение слова с заглавной первой буквой (нужно будет для названия аксессора). Переменная type зарезервирована для типа члена класса. Строка 7 установит курсор на предыдущее слово. Строки 8-13 получат название класса. Код здесь сложный потому, что если изначально курсор стоит в середине имени члена класса, то backward-word вернет курсор к началу этого слова, а не на предыдущее слово. Поэтому в строке 9 проверяется, является ли скопированное слово названием идентификатора. Если да, то переместить курсор на еще одно слово влево и скопировать предыдущее, т.е. тип члена класса. Строка 14 переводит курсор на начало буфера. Строка 15 установит курсор за найденной строкой «// Accessors». Строки 16-21 вставят текст аксессоров.

Генерация и инициализация полей Java-класса в Android-приложении Описание проблемы При разработке формы на Java под Android приходится редактировать два файла: описание UI в формате XML, и java-код, отвечающий за логику приложения. Каждый элемент управления, описанный в xml-файле, необходимо соответствующим образом описать в java-классе. Для каждого элемента необходимо создать приватное поле и проинициализировать его в методе onCreate(). На рис. 4 показано описание формы в xml и выделен элемент wifiInfo класса TextView. Необходимо объявить приватный член 29

TextView wifiInfo класса WiFiDemoActivity и присвоить ему значение, как это показано на рис.4. Проблема заключается в том, что если элементов на форме много, то такой ручной труд быстро наскучивает. Необходимо его автоматизировать.

Рис.4 Генерация полей java-класса по описанию формы в xml

30

План решения Будут разработаны три функции. Функция my-xml-buffer запросит название xml-буфера (по-дефолту предложит название активного буфера). Аналогичным образом функция my-java-buffer запросит название буфера с java-классом (по-дефолту также предложит название активного буфера). Функция my-widget извлечет id-шник элемента и его класс из xml-тега, создаст в java-классе приватный член (после комментария «// Members»)‖ и проинициализирует его (после комментария «// Initialize»)‖. Код 1. (defun my-xml-buffer (buf-name) 2. (interactive "BName of the xml buffer: ") 3. (setq xml-buffer buf-name)) 4. (defun my-java-buffer (buf-name) 5. (interactive "BName of the java buffer: ") 6. (setq java-buffer buf-name)) 7. (defun get-widget-class-name () 8. "Returns class name for current widget" 9. (let (a) 10. (forward-char 1) 11. (search-backward ">" '("111" "122" "133"))

46

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ Пакеты для разработчика Поскольку Emacs разрабатывался разработчиками для разработчиков, то он поддерживает почти все существующие языки программирования, а также утилиты, такие как контроль версий, различные чекеры и т.п. (достаточно полный список можно найти на EmacsWiki20). Например (почти все пакеты можно установить через менеджер пакетов или через el-get): ● CC-Mode (включен в Emacs) — пакет для редактирования кода на C/C++/Objective-C/Java/Pike/AWK ● Lisp mode (включен в Emacs) — для редактирования кода на разных вариантах Lisp ● SLIME — де-факто стандарт для разработки на Common Lisp, Clojure и Scheme — реализует интерактивную разработку ● CEDET21 — набор пакетов для работы с исходным кодом на разных языках. Включает навигацию по коду, дополнение имен (для некоторых режимов) и т.п. ● yasnippet — реализует поддержку разворачиваемых шаблонов ● ensime — пакет для интерактивной работы со Scala ● erlang-mode и distel — пакеты для работы с кодом на Erlang22 ● haskell-mode — пакет для работы с кодом на haskell ● paredit — незаменимый пакет для работы с Lisp-like языками ● malabar-mode23 -- улучшенный режим для работы с кодом на Java, с поддержкой Maven и т.п. Для работы с системами контроля версий, имеется огромное количество пакетов24, которые позволяют эффективно 20

http://emacswiki.org http://alexott.net/ru/writings/emacs-devenv/EmacsCedet.html 22 http://alexott.net/ru/writings/emacs-devenv/EmacsErlang.html 21

23 24

https://github.com/espenhw/malabar-mode http://alexott.net/ru/writings/emacs-vcs/

47

использовать эти системы не покидая Emacs, и при необходимости перезагружать измененные файлы и т.п.

Другие полезные пакеты ● ● ●

tramp (включен в Emacs) — реализует удаленный доступ к файлам используя разные методы доступа — ssh, ftp, sudo и т.д. org-mode (включен в Emacs) — мощная смесь органайзера, TODO и Wiki. Множество расширений, в том числе и для literate programming AUCTeX, сотоварищи — очень мощный пакет для писания документов с помощью TeX/LaTeX/...

Литература 1. ―An Introduction to Programming in Emacs Lisp‖, R.J.Chassel, 3rd edition, GNU Press, 2009. 2. ―GNU Emacs Lisp Reference Manual‖, B.Lewis, D. LaLiberte, R.Stallman and others, 3rd edition, Free Software Foundation, 2010. 3. ―Writing GNU Emacs Extensions‖, B.Glickstein, O‘Reilly, 1997. 4. Перевод Emacs Lisp Intro (http://alexott.net/ru/emacs/elispintro/elisp-intro-ru.html) 5. Перевод Emacs Manual для версии 20.x (http://alexott.net/ru/emacs/emacs-manual/emacs_toc.html) 6. Emacs Wiki (http://emacswiki.org) 7. Агрегатор новостей об Emacs (http://planet.emacsen.org) и его русская версия (http://planet.emacsen.org/ru/)

48