[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
В GNU Emacs очень часто используется поиск с использованием регулярных
выражений. Две функции forward-sentence
и
forward-paragraph
наглядно продемонстрируют технику такого
поиска.
Поиск с использованием регулярных выражений описан в section `Regular Expression Search' in The GNU Emacs Manual,
также как и в section `Regular Expressions' in The GNU Emacs Lisp Reference Manual (7). В этой главе я полагаю,
что вы по имеете какое-то представление о синтаксисе регулярных
выражений. Главное, что надо помнить --- регулярные выражения
разрешают вам производить поиск не только текстовых строк или
символов, но и шаблонов. Например, функция forward-sentence
ищет шаблон, который может обозначать конец предложения, и перемещает
точку на это место.
До того, как мы приступим к изучению функции forward-sentence
полезно представить, какой шаблон должен обозначать конец предложения.
Это мы обсудим в следующем разделе; затем идет раздел, где мы
познакомимся с функцией поиска по регулярным выражениям
re-search-forward
. После этого мы разберем принцип работы
функции forward-sentence
. И наконец, в последнем разделе главы
рассматривается функция forward-paragraph
. Это довольно сложная
функция, на примере которой мы изучим несколько новых возможностей
Emacs Lisp.
12.1 Регулярное выражение для sentence-end
12.2 Функция re-search-forward
Похожа на search-forward
.12.3 forward-sentence
Простой пример поиска regexp. 12.4 forward-paragraph
: сокровищница функцийДавайте усложним задачу. 12.5 Создание своего собственного файла `TAGS' 12.6 Заключение 12.7 Упражнения с re-search-forward
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
sentence-end
Символ sentence-end
связан с шаблоном, который отмечает конец
предложения. Каким должно быть это регулярное выражение?
Ясно, что предложение может заканчиваться точкой, знаком вопроса или восклицательным знаком. Действительно только фразы, которые заканчиваются этими тремя символами будут считаться предложениями. Это значит, что шаблон должен включать следующий набор символов:
[.?!] |
Однако, мы не хотим чтобы forward-sentence
перемещало курсор к
первой найденной точке, знаку вопроса или восклицательному знаку,
потому что такие символы могут использоваться и в середине
предложения. Точка например, используется для обозначения сокращений.
Поэтому необходима дополнительная информация.
Договоримся(8), что после каждого предложения надо печатать два пробела, а после точки, вопросительного знака и восклицательного знака, если они встретились в теле предложения только один пробел. Поэтому точка, знак вопроса или вослицательный знак за которым следует два пробела --- хороший индикатор конца предложения. Это значит, что регулярное выражение должно включать эти три элемента как альтернативы. Группа альтернатив будет выглядеть следующим образом:
\\($\\| \\| \\) ^ ^^ TAB SPC |
Здесь `$' обозначает конец строки и я отметил, где в выражение вставлены два пробела и табуляция. Оба этих символа фактически входят в выражение.
Перед скобками и вертикальными черточками требуется поставить два обратных слэша `\\': первый обратный слэш для того, чтобы маскировать следующий обратный слэш, а второй, чтобы отметить следующий за ним символ, как специальный.
Также предложение может оканчиваться одним или несколькими символами перевода строки, вот так:
[ ]* |
Как пробелы и табуляция, перевод каретки на самом деле вставляется в регулярное выражение. Звездочка означает, что RET может повториться ноль или более раз.
Но конец предложения не всегда состоит только из точки, знака вопроса или восклицательного знака, за которым следует соответствующее число пробелов --- перед пробелами могут также стоять закрывающие скобки или кавычки. Для учета этих символов требуется следующее выражение:
[]\"')}]* |
В этом выражении, первый знак `]' --- это первый символ в выражении; перед вторым символом `"' стоит обратный слэш `\' для того, чтобы Emacs не считал `"' специальным символом. Последние три символа --- это `'', `)', и `}'.
Все выше изложенное предполагает, что шаблон регулярного выражения,
которое может обозначать конец предложения должно быть; и в самом деле,
если мы вычислим sentence-end
мы обнаружим что это вернет
следующее значение:
sentence-end => "[.?!][]\"')}]*\\($\\| \\| \\)[ ]*" |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
re-search-forward
Функция re-search-forward
очень похожа на функцию
search-forward
(See section The search-forward
Function.)
Функция re-search-forward
производит поиск регулярного
выражения. Если поиск успешен, то она оставляет курсор сразу за
последним символом искомого текста. Если поиск производился в обратном
направлении, то функция оставляет точку сразу перед первым символом
искомого текста. Вы можете заставить re-search-forward
вернуть
t
в случае успеха. (Перемещение точки является "побочным
эффектом").
Как и функция search-forward
, функция re-search-forward
получает четыре аргумента:
nil
в качестве третьего аргумента
заставляет функцию сообщить об ошибке (и отобразить сообщение в
эхо-области) когда поиск неудачен; любое другое значение заставляет
функцию вернуть nil
, если поиск окончится неудачно, и t
в случае успеха.
re-search-forward
производить
поиск в обратном направлении.
Шаблон для re-search-forward
выглядит следующим образом:
(re-search-forward "регулярное-выражение" границы-поиска что-делать-в-случае-неудачи число-повторов) |
Второй, третий и четвертый аргумент необязательны. Однако, если вы хотите передать значение к последнему аргументу, то вы также должны передать значение и для всех предыдущих аргументов. В противном случае интерпретатор Лиспа неправильно присвоит значения аргументам функции.
В функции forward-sentence
, регулярное выражение будет
значением переменной sentence-end
, то есть:
"[.?!][]\"')}]*\\($\\| \\| \\)[ ]*" |
Границей поиска будет конец параграфа (так как предложение не может
заканчиваться после окончания параграфа). Если поиск будет неудачен,
то функция вернет nil
; число повторов можно обеспечить числовым
аргументом при вызове функции forward-sentence
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
forward-sentence
Команда для перемещения курсора вперед на предложение хорошо показывает, как в Emacs Lisp используется поиск по регулярным выражениям. И хотя функция выглядит длиннее и сложнее чем должна была быть --- это потому что она также осуществляет и поиск в обратном направлении, и может перемещать курсор через несколько предложений. Эта команда обычно связана с M-e
Ниже код функции forward-sentence
:
(defun forward-sentence (&optional arg) "Перемещает точку к концу предложения. Аргумент -- число-повторов. При задании отрицательного аргумента, перемещает точку к началу предложения. Концом предложения считается регулярное выражение значение sententce-end. Поиск также завершается и при достижении конца параграфа." (interactive "p") (or arg (setq arg 1)) (while (< arg 0) (let ((par-beg (save-excursion (start-of-paragraph-text) (point)))) (if (re-search-backward (concat sentence-end "[^ \t\n]") par-beg t) (goto-char (1- (match-end 0))) (goto-char par-beg))) (setq arg (1+ arg))) (while (> arg 0) (let ((par-end (save-excursion (end-of-paragraph-text) (point)))) (if (re-search-forward sentence-end par-end t) (skip-chars-backward " \t\n") (goto-char par-end))) (setq arg (1- arg)))) |
На первый взгляд функция выглядит сложной и лучше всего сначала посмотреть на ее скелет, а затем изучить все остальные детали (нарастить мышцы). Скелет функции получится, если отобразить только строки начинающиеся ближе всех к левой границе. (9)
(defun forward-sentence (&optional arg) "документация..." (interactive "p") (or arg (setq arg 1)) (while (< arg 0) тело-цикла-while (while (> arg 0) тело-цикла-while |
Это уже выглядит намного проще! Сразу видно, что определение функции
состоит из документации, выражения interactive
, выражения
or
и двух циклов while
.
Давайте рассмотрим каждую из этих частей по очереди.
Нужно отметить, что документация как всегда обстоятельная и понятна.
Функция имеет обьявление interactive "p"
. Это значит, что
обработанный префикс-аргумент, если он задан, будет передан в функцию
в качестве ее аргумента. (Это будет целое число). Если при вызове
функции ей не будет задан аргумент (он необязательный), то этому
аргументу arg
будет присвоено значение 1. Если функцию
forward-sentence
вызвали не интерактивно и без аргумента, то
аргументу arg
будет присвоено значение nil
.
Выражение or
управляет префикс-аргументом. Оно или оставляет
значение аргумента как есть, если с ним уже связано значение; или
присваивает аргументу arg
значение 1, если с arg
было
связано значение nil
.
Циклы while
Поиск по регулярному выражению
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
while
За выражением or
следуют два цикла while
.
Проверка-истина-ложь первого цикла while
возвращает истину,
если аргумент forward-sentence
--- отрицательное число.
Это код обратного поиска. Тело этого цикла очень похоже на тело
второго цикла while
, но это ни одно и тоже. Мы пропустим
первый цикл while
и сосредоточимся на втором, в котором
производится поиск вперед.
Второй цикл while
схематично выглядит следующим образом:
(while (> arg 0) ; проверка-истина-ложь
(let список-переменных
(if (проверка-истина-ложь)
then-часть
else-часть
(setq arg (1- arg)))) ; |
Это цикл while
с уменьшением счетчика (See section Цикл с уменьшающимся счетчиком.) Проверка-истина-ложь возвращает
значение истина до тех пор пока счетчик (в нашем случае arg
)
больше нуля; и каждый раз когда цикл повторяется значение счетчика
уменьшается на единицу.
Если при вызове функции не задан префикс-аргумент, самый обычный способ
использования этой команды, тело цикла while
будет вычислено
только один раз, так как значение arg
в таком случае равно 1.
Тело цикла while
состоит из выражения let
, в котором
создаются и инициализируются локальные переменные, а телом выражения
let
является выражение if
.
Тело выражения while
выглядит следующим образом:
(let ((par-end (save-excursion (end-of-paragraph-text) (point)))) (if (re-search-forward sentence-end par-end t) (skip-chars-backward " \t\n") (goto-char par-end))) |
В выражении let
создается и инициализируется локальная
переменная par-end
. Как мы увидим, эта переменная
используется, для того, чтобы ограничить поиск регулярного выражения.
Если функции поиска не удастся найти правильное окончание предложения
в параграфе, то она все равно остановится достигнув конца параграфа.
Но вначале давайте посмотрим как переменной par-end
присваивается значение конца параграфа. Это происходит, когда в
выражении let
переменной par-end
присваивается значение,
которое возвращает интерпретатор Лиспа, после вычисления выражения:
(save-excursion (end-of-paragraph-text) (point)) |
В этом выражении, (end-of-paragraph-text)
перемещает курсор к
концу параграфа, (point)
возвращает значение точки и после
этого save-excursion
восстанавливает первоначальное положение
курсора. То есть let
связывает par-end
со значением,
которое возвращает выражение save-excursion
, которое в свою
очередь равно позиции конца параграфа. (Функция
(end-of-paragraph-text)
используется функцией
forward-paragraph
, которую мы вскоре рассмотрим.)
После этого Emacs вычисляет тело let
, а это выражение
if
, которое выглядит следующим образом:
(if (re-search-forward sentence-end par-end t) ; if-часть (skip-chars-backward " \t\n") ; then-часть (goto-char par-end))) ; else-часть |
Напомним, что if
проверяет значение возвращаемое его первым
аргументом и если это истина, то вычисляется then-часть; в противном
случае интерпретатор Emacs Lisp вычисляет else-часть.
Проверка-истина-ложь в выражении if
--- это функция поиска
регулярного выражения.
Может показаться странным, но "настоящая работа" функции
forward-sentence
выполняется именно здесь, но это самое обычное
дело, если вы программирует на Лиспе.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Функция re-search-forward
ищет конец предложения или шаблон,
который определен регулярным выражением sentence-end
. Если
шаблон найден ---то функция re-search-forward
выполняет две
вещи:
re-search-forward
имеет побочный эффект ---
перемещает курсор в конец найденого образца.
re-search-forward
возвращает истину в качестве
значения. Это значение и получает if
; это означает, что поиск
завершился успешно.
Побочный эффект функции (перемещение курсора) завершается до того как
выражение if
начнет анализировать значение, которое вернулось в
случае успешного поиска.
Когда функция if
получает значение истина (если вызов
re-search-forward
был успешен), то if
вычисляет
then-часть --- выражение (skip-chars-backward " \t\n")
.
Это выражение перемещает курсор назад через пробелы, символы новой
строки и символы табуляции, до первого печатного символа. Поскольку
курсор уже переместился к концу найденого образца, который означал
конец предложения, то это действие оставляет курсор сразу за
завершающим печатным символом предложения --- то есть точкой.
С другой стороны, если функция re-search-forward
не смогла
найти образец сопоставимый с концом предложения, то она возвращает
ложное значение. Ложное значение заставляет if
вычислить свой
третий аргумент --- выражение (goto-char par-end)
, которое
перемещает курсор к концу параграфа.
Поиск регулярных выражений --- очень полезная вещь и шаблон,
который мы использовали в функции forward-sentence
(когда
функция re-search-forward
используется, как проверка в
выражении if
) весьма часто применяется на практике.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
forward-paragraph
: сокровищница функций
Функция forward-paragraph
перемещает точку в конец параграфа.
Обычно эта команда связана с сочетанием клавишM-} и в ней
используется несколько новых, интересных функции: let*
,
match-beginning
и looking-at
.
Определение функции для forward-paragraph
значительно длиннее,
чем определение функции forward-sentence
, потому что она должна
работать и с параграфами, у которых каждая строка которых начинается с
префикса заполнения.
Префикс заполнения --- это строка символов, котороя повторяется в начале каждой строки. Например, в программах на Лиспе существует соглашение начинать каждую строку комментария длинной в параграф со строки `;;; '. В режиме Text используется другой префикс заполнения --- четыре пробела, таким образом создаются выровненные параграфы. (See section `Fill Prefix' in The GNU Emacs Manual, для получения дополнительной информации о префиксах заполнения).
Кроме того, существование префикса заполнения означает, что функция
должна найти конец параграфа, строки которого начинаются с левой
границы экрана. Функция forward-paragraph
также должна находить
конец параграфа когда все, или многие строки в буфере начинаются с
префикса заполнения.
Более того иногда стоит проигнорировать существование префикса заполнения, особенно тогда, когда параграфы разделены пустыми строками. Это добавляет сложности в реализацию.
Мы приведем здесь только части функции forward-paragraph
, так
как трудно не ужаснуться, если посмотреть на ее полное определение.
Схематично функция выглядит следующим образом:
(defun forward-paragraph (&optional arg) "документация..." (interactive "p") (or arg (setq arg 1)) (let* список-переменных (while (< arg 0) ; код-движения-назад ... (setq arg (1+ arg))) (while (> arg 0) ; код-движения-вперед ... (setq arg (1- arg))))) |
Первые части функции нам уже хорошо знакомы --- список аргументов функции состоит из одного необязательного аргумента. Затем следует документация.
Знак `p' в нижнем регистре из обьявления interactive
означает, что в функцию будет передан обработанный префикс-аргумент.
Это будет целое число --- на сколько параграфов переместить
курсор.
Выражение or
на следующей строке обрабатывает самый общий
случай --- когда при вызове функции не был задано никакого
аргумента. Это происходит когда функция вызывается не интерактивно из
другой программы. Мы уже рассмотрели такой случай ранее
(See section The forward-sentence
function.)
Здесь мы подошли наконец к незнакомым частям функции.
Выражение let*
Цикл while
движения впередМежду параграфами Внутри параграфа Без префикса заполнения С префиксом заполнения Итог forward-paragraph
полностью.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
let*
Следующая строка функции forward-paragraph
начинается с
выражения let*
. Это назнакомая нам функция. Символ
let*
не является тем же самым что и let
.
let*
особая форма, как и let
, только в этом случае Emacs
инициализирует переменные последовательно, одну за другой и переменные
в конце списка-переменных могут использовать значения переменных
проинициализированных в начале списка-переменных.
В выражении let*
для нашей функции Emacs связывает две
переменные: fill-prefix-regexp
и paragraph-separate
.
Значение, которое будет связано с paragraph-separate
зависит от
того, какое значение было присвоено
переменнойfill-prefix-regexp
.
Давайте рассмотрим их по очереди. Символу fill-prefix-regexp
присваивается значение, которое возвращается после вычисления
следующего списка:
(and fill-prefix (not (equal fill-prefix "")) (not paragraph-ignore-fill-prefix) (regexp-quote fill-prefix)) |
В этом выражении первым элементом является функция and
.
Функция and
вычисляет свои аргументы до тех пор, пока один из
них не вернет значение nil
, в этом случае для всего выражения
and
возвращается значение nil
; однако, если ни один из
аргументов не возвратил nil
, то результатом всего выражения
and
будет результат вычисления последнего аргумента функции.
Другими словами выражение and
возвращает истину только тогда,
когда все его аргументы возвращают значение не равное nil
.
В нашем случае переменная fill-prefix-regexp
будет связана со
значением не равным nil
, только если следующие четыре выражения
вернут истину (т.е. не-nil
) после вычисления; в противном
случает, fill-prefix-regexp
будет назначено значение
nil
.
fill-prefix
nil
.
(not (equal fill-prefix "")
(not paragraph-ignore-fill-prefix)
nil
, в том случае если была включена
переменная paragraph-ignore-fill-prefix
, то есть ей присвоили
истинное значение, например t
.
(regexp-quote fill-prefix)
and
. Если все аргументы
and
были истины, то значение возвращаемое после вычисления
этого выражения будет связано с переменной fill-prefix-regexp
.
Если вычисление выражения and
было успешно, то
fill-prefix-regexp
будет связан со значением
fill-prefix
, но измененного функцией regexp-quote
.
Функция regexp-quote
принимает строку в качестве аргумента и
возвращает регулярное выражение, которое точно сопоставимо с этой
строкой и не подходит ни под что другое. Это означает, что
fill-prefix-regexp
будет связан со значением, которое точно
сопоставимо с префиксом заполнения, если префикс заполнения конечно
существует. В противном случае этой переменной будет присвоено
значение nil
.
Вторая локальная переменная в выражении let*
--- это
paragraph-separate
. Она связывается со значением, которое
возвращается после вычисления следующего выражения:
(if fill-prefix-regexp (concat paragraph-separate "\\|^" fill-prefix-regexp "[ \t]*$") paragraph-separate))) |
Это выражение обьясняет почему в функции forward-paragraph
используется особая форма let*
, а не let
.
Проверка-истина-ложь в if
зависит от того, равна ли переменная
fill-prefix-regexp
значению nil
или нет.
Если fill-prefix-regexp
не имеет значения, то Emacs вычисляет
else-часть выражения if
и связывает paragraph-separate
с
ее локальным значением. (paragraph-separate
--- это
регулярное выражение, которое сопоставимо с разделителем параграфа).
Но если fill-prefix-regexp
присвоено какое-нибудь значение, то
тогда Emacs вычисляет then-часть выражения if
и связывает
paragraph-separate
с регулярным выражением одна из частей
которого равна fill-prefix-regexp
.
Если уточнить, то paragraph-separate
присваивается
первоначальное значение регулярного выражения для разделителей
параграфа, к которому добавлено в виде альтернативы регулярное
выражение fill-prefix-regexp
, за которым следует пустая строка.
`^' означает, что fill-prefix-regexp
должен находиться в
начале строки, а необязательные пробелы в конце строки заданы
выражением "[ \t]*$"
. `\\|' определяет, что эта часть
регулярного выражения является альтернативой
paragraph-separate
.
Теперь мы готовы анализировать тело let*
. Первый цикл
while
исполняется в том случае, если функция при вызове
получила отрицательный аргумент и следовательно должна переместить
курсор назад. Мы пропустим эту часть.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
while
движения вперед
Вторая часть тела let*
обрабатывает движение вперед. Это цикл
while
, который повторяется пока значение arg
больше
нуля. Обычно значение аргумента равно 1, поэтому тело цикла
while
будет вычислено только один раз и курсор переместится
вперед на один параграф.
В этом цикле обрабатываются три ситуации: когда курсор находиться между параграфами, когда курсор находится внутри параграфа и существует префикс заполнения, и когда курсор внутри параграфа, но префикса заполнения нет.
Цикл while
выглядит следующим образом:
(while (> arg 0) (beginning-of-line) ;; между параграфами (while (prog1 (and (not (eobp)) (looking-at paragraph-separate)) (forward-line 1))) ;; внутри параграфа, с префиксом заполнения (if fill-prefix-regexp ;; Существующий префикс заполнения перекрывает paragraph-start. (while (and (not (eobp)) (not (looking-at paragraph-separate)) (looking-at fill-prefix-regexp)) (forward-line 1)) ;; внутри параграфа, без префикса заполнения (if (re-search-forward paragraph-start nil t) (goto-char (match-beginning 0)) (goto-char (point-max)))) (setq arg (1- arg))) |
Мы сразу видим что это цикл while
с уменьшением счетчика, и
выражение (setq (1- arg))
служит уменьшителем. Тело цикла
состоит из трех выражений:
;; между параграфами (beginning-of-line) (while тело-while) ;; внутри параграфа, с префиксом заполнения (if проверка-истина-ложь then-часть ;; внутри параграфа, нет префикса заполнения else-часть |
Когда интерпретатор Emacs Lisp вычисляет тело цикла while
, то
самое первое что он делает --- вычисляет выражение
(beginning-of-line)
и перемещает курсор в начало строки. Затем
следует внутренний цикл while
. Цель этого цикла ---
переместить курсор из пустого пространства между параграфами, если он
оказался там. Наконец, в последнем выражении if
и происходит
фактическое перемещение курсора в конец параграфа.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Вначале давайте взглянем на внутренний цикл while
. Этот цикл
обрабатывает случай когда курсор находится между параграфами; там
используются три новых для нас функции: prog1
, eobp
и
looking-at
.
prog1
очень похожа на функцию progn
, кроме того, что
после вычисления своих аргументов она возвращает значение первого
аргумента, как значение вычисления всей функции prog1
.
(progn
возвращает значение последнего аргумента как значение
всего выражения). Второй и последующие аргументы функции prog1
вычисляются только ради их побочных эффектов.
eobp
--- это сокращение от `End Of Buffer P' и эта
функция возвращает истину, если курсор находится в конце буфера.
looking-at
--- это функция, которая возвращает истину, если
текст следующий за курсором сопоставим с регулярным выражением,
переданным looking-at
в качестве аргумента.
Цикл while
, который мы сейчас рассмотрим, выглядит следующим
образом:
(while (prog1 (and (not (eobp)) (looking-at paragraph-separate)) (forward-line 1))) |
У этого цикла while
нет тела! Проверка-истина-ложь для этого
цикла приведена ниже:
(prog1 (and (not (eobp)) (looking-at paragraph-separate)) (forward-line 1))) |
Первый аргумент в функции prog1
--- это выражение
and
. Там проверяется находится ли курсор в конце буфера и
сопоставим ли текст после курсора с регулярным выражением для
разделения параграфов.
Если курсор не находится в конце буфера и символы следующие за
курсором похожи на разделители параграфов, то значением всего
выражения and
является истина. После вычисления выражения
and
интерпретатор Лиспа вычисляет второй аргумент к
prog1
--- это выражение forward-line
. Здесь
происходит перемещение курсора на одну строку вперед. Тем не менее
возвращаемое значение для всего выражения prog1
---
значение его первого аргумента, поэтому цикл while
будет
продолжаться до тех пор пока курсор находиться между параграфами и не
в конце буфера. Когда наконец, курсор подойдет к параграфу текста
выражение and
возвратит ложь. Обратите внимание, что команда
forward-line
будет выполнена и в этом случае. Это значит, что
когда курсор перемещается от параграфа к параграфу, то он
останавливается в начале второй строки параграфа.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Следующее выражение внешнего цикла while
--- это выражение
if
. Интерпретатор Лиспа вычисляет then-часть выражения
if
, в том случае, когда переменной fill-prefix-regexp
было присвоено какое-нибудь значение отличное от nil
, в
противном случае, если нет префикса заполнения, вычисляется
else-часть.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Сначала давайте рассмотрим случай, когда префикс заполнения
отсутствует. Этот случай немного проще. Это еще одно внутреннее
выражение if
, и оно выглядит следующим образом:
(if (re-search-forward paragraph-start nil t) (goto-char (match-beginning 0)) (goto-char (point-max))) |
В этом выражении на самом деле и выполняется та работа, которую
большинство пользователей считают основной целью команды
forward-paragraph
--- здесь производится поиск регулярного
выражения сопоставимого с началом следующего параграфа, и если он
найден, то курсор перемещается на это место; но если начало следующего
параграфа не обнаружено, то тогда курсор перемещается в конец
доступной области буфера.
Единственная незнакомая здесь для нас функция --- это
match-beginning
. Функция match-beginning
возвращает
число, описывающее начало текста сопоставленого в последнем поиске
регулярного выражения.
Использование здесь функции match-beginning
обьясняется одним
свойством поиска вперед --- в случае успешного поиска, не важно
был ли это поиск регулярного выражения или простой поиск, курсор
перемещается в конец найденного текста. В нашем случае успешный поиск
переместит курсор в конец образца для paragraph-start
---
это будет началом следующего параграфа, а не конец текущего.
Однако, мы хотим расположить курсор в конце текущего параграфа, а не в начале следующего. Эти две позиции могут сильно отличаться, поскольку их может разделять несколько пустых строк.
Если функции match-beginning
передан аргумент равный 0, то она
возвращает позицию, совпадающую с началом текста, который был найден
при последнем поиске по регулярному выражению. В нашем случае самый
последний поиск регулярного выражения осуществлял поиск
paragraph-start
, поэтому функция match-beginning
вернет
позицию начала шаблона, а не конец. Начальная позиция --- это
конец текущего параграфа.
(Между прочим, когда в качестве аргумента передается положительное
число, ир функция match-beginning
поместит точку на выражение,
заключенное в скобки в последнем поиске по регулярному выражению. Это
очень полезная функция).
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Внутреннее выражение if
, которое мы только что обсудили ---
это else-часть внешнего выражения if
, где проверка-истина-ложь
проверяет наличие префикса заполнения. Если префикс заполнения
существует, то тогда вычисляется then-часть внешнего выражения
if
. Она выглядит следующим образом:
(while (and (not (eobp)) (not (looking-at paragraph-separate)) (looking-at fill-prefix-regexp)) (forward-line 1)) |
В этом выражении курсор будет перемещаться вперед на строку до тех пор, пока следующие три выражения истинны:
Последнее условие может выглядеть загадочным, если вы не вспомните,
что в самом начале функции forward-paragraph
мы переместили
курсор в начало строки. Это значит, что если в тексте есть префикс
заполнения, то функция looking-at
увидит его.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Повторим, когда курсор перемещается вперед, функция
forward-paragraph
делает следующее:
Для повтора, здесь мы приводим только что рассмотреную функцию, немного выравненную для ясности:
(interactive "p") (or arg (setq arg 1)) (let* ( (fill-prefix-regexp (and fill-prefix (not (equal fill-prefix "")) (not paragraph-ignore-fill-prefix) (regexp-quote fill-prefix))) (paragraph-separate (if fill-prefix-regexp (concat paragraph-separate "\\|^" fill-prefix-regexp "[ \t]*$") paragraph-separate))) код перемещения назад пропущен ... (while (> arg 0) ; код-перемещения-вперед (beginning-of-line) (while (prog1 (and (not (eobp)) (looking-at paragraph-separate)) (forward-line 1))) (if fill-prefix-regexp (while (and (not (eobp)) ; then-часть (not (looking-at paragraph-separate)) (looking-at fill-prefix-regexp)) (forward-line 1)) ; else-часть: внутренний if (if (re-search-forward paragraph-start nil t) (goto-char (match-beginning 0)) (goto-char (point-max)))) (setq arg (1- arg))))) ; уменьшение счетчика |
Полное определение функции forward-paragraph
включает также и
код для того случая, когда осуществляется движение назад.
Если вы читаете этот текст в GNU Emacs и хотите увидеть всю функцию,
то вы можете набрать M-. (find-tag
) и после этого в
появившейся эхо-области набрать имя функции. Если функция
find-tag
запросит у вас имя таблицы `TAGS', то задайте в
качестве аргумента файл `TAGS' в вашем каталоге `emacs/src',
то есть, что то подобное `/usr/local/lib/emacs/19.23/src/TAGS'.
(Точное имя каталога содержащего `emacs/src' зависит от того, как
была установлена ваша копия Emacs. Если вы не знаете его, то вы
можете выяснить эту информацию нажав C-h i, чтобы запустить Info
и здесь напечатав C-x C-f, эхо-области вы увидите полное
маршуртное имя для каталога `emacs/info'. Имя файла `TAGS'
часто аналогично; тем не менее, иногда, файлы Info хранятся в другом
месте). (10)
Вы может сами создать файлы `TAGS' для тех каталогов, в которых хранятся исходные тексты написанные на Emacs Lisp. See section Create Your Own `TAGS' File.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Вы можете создать свои собственные файлы `TAGS', которые будут
вам помогать перемещаться по исходным текстам. Например, если у вас в
каталоге `~/emacs' находится такое же большое количество файлов,
как и у меня --- целых 137 файлов Emacs Lisp `.el', из
которых я загружаю 17 --- то вы обнаружите, что если вы создадите
файл `TAGS' для этого каталога, вам будет удобнее разыскивать
нужную функцию, чем при использовании grep
или какого-нибудь
другого средства.
Создать файл `TAGS' можно с помощью программы etags
,
которая входит в стандартный дистрибутив Emacs. Обычно etags
компилируется и устанавливается при установке Emacs. (Программа
etags
это не функция на Emacs Lisp и она не входит в состав
Emacs; это отдельная программа, написанная на Си).
Чтобы создать файл `TAGS' сначала перейдите в тот каталог для
которого вы хотите создать таблицу тэгов. В Emacs вы можете сделать
это с помощью команды M-x cd или открыв какой-нибудь файл из
этого каталога, или выполнить для этого каталога команду C-x d
(dired
). Теперь нажмите
M-! etags *.el |
для создания файла `TAGS'. При запуске программы etags
можно использовать все расширения оболочки sh
. Например, если
у вас два каталога для которых вы хотите создать один файл `TAGS
file', то для этого выполните команду подобную следующей, где
`../elisp/' это второй каталог:
M-! etags *.el ../elisp/*.el |
Наберите
M-! etags --help |
для того чтобы изучить все ключи, которые можно использовать с
etags
.
Программа etags
обрабатывает исходные тексты программ на языках
Emacs Lisp, Common Lisp, Scheme, C, Fortran, Pascal, LaTeX, и
большинство ассемблеров. При анализе файла программа etags
автоматически узнает используемый в файле язык по расширению файла и
его содержанию.
Файлы `TAGS' очень помогают тогда, когда вы сами работаете над
большим программным проектом и хотите найти функцию расположеную в
неизвестном вам файле. После создания новых функции все что вам
надо --- это снова запустить программу etags
и новые функции
будут занесены в таблицу тегов.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Ниже приведено краткое перечисление недавно изученных функций.
while
nil
цикл заканчивается, и все выражение возвращает nil
.
(Тело цикла вычисляется только ради побочных эффектов).
Например:
(let ((foo 2)) (while (> foo 0) (insert (format "foo is %d.\n" foo)) (setq foo (1- foo)))) => foo is 2. foo is 1. nil |
insert
вставляет свои аргументы после курсора; функция
format
возвращает строку отформатированную подобно тому, как
функция message
форматирует свои аргументы; строка \n
для обозначения перевода строки).
re-search-forward
Как и функция search-forward
принимает четыре аргумента:
nil
или сигнализировать об ошибке (необязательный параметр).
let*
Например:
(let* ((foo 7) (bar (* 3 foo))) (message "`bar' равно %d." bar)) => `bar' is 21. |
match-beginning
looking-at
t
, если текст следующий за курсором сопоставим с
аргументом, которое должно быть регулярным выражением.
eobp
t
, если курсор находится в конце доступной области
буфера. Конец доступной области буфера --- это конец буфера, если
не включено сужение, или конец суженной области, если сужение
включено.
prog1
Например:
(prog1 1 2 3 4) => 1 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
re-search-forward
the-the
Duplicated Words Function.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |