[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12. Поиск с использованием регулярных выражений

В 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] [ ? ]

12.1 Регулярное выражение для sentence-end

Символ sentence-end связан с шаблоном, который отмечает конец предложения. Каким должно быть это регулярное выражение?

Ясно, что предложение может заканчиваться точкой, знаком вопроса или восклицательным знаком. Действительно только фразы, которые заканчиваются этими тремя символами будут считаться предложениями. Это значит, что шаблон должен включать следующий набор символов:

 
[.?!]

Однако, мы не хотим чтобы forward-sentence перемещало курсор к первой найденной точке, знаку вопроса или восклицательному знаку, потому что такие символы могут использоваться и в середине предложения. Точка например, используется для обозначения сокращений. Поэтому необходима дополнительная информация.

Договоримся(8), что после каждого предложения надо печатать два пробела, а после точки, вопросительного знака и восклицательного знака, если они встретились в теле предложения только один пробел. Поэтому точка, знак вопроса или вослицательный знак за которым следует два пробела --- хороший индикатор конца предложения. Это значит, что регулярное выражение должно включать эти три элемента как альтернативы. Группа альтернатив будет выглядеть следующим образом:

 
\\($\\| \\|  \\)
       ^   ^^
      TAB  SPC

Здесь `$' обозначает конец строки и я отметил, где в выражение вставлены два пробела и табуляция. Оба этих символа фактически входят в выражение.

Перед скобками и вертикальными черточками требуется поставить два обратных слэша `\\': первый обратный слэш для того, чтобы маскировать следующий обратный слэш, а второй, чтобы отметить следующий за ним символ, как специальный.

Также предложение может оканчиваться одним или несколькими символами перевода строки, вот так:

 
[
]*

Как пробелы и табуляция, перевод каретки на самом деле вставляется в регулярное выражение. Звездочка означает, что RET может повториться ноль или более раз.

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

 
[]\"')}]*

В этом выражении, первый знак `]' --- это первый символ в выражении; перед вторым символом `"' стоит обратный слэш `\' для того, чтобы Emacs не считал `"' специальным символом. Последние три символа --- это `'', `)', и `}'.

Все выше изложенное предполагает, что шаблон регулярного выражения, которое может обозначать конец предложения должно быть; и в самом деле, если мы вычислим sentence-end мы обнаружим что это вернет следующее значение:

 
sentence-end
     => "[.?!][]\"')}]*\\($\\|     \\|  \\)[       
]*"


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.2 Функция 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 получает четыре аргумента:

  1. Первый аргумент --- это само регулярное выражение, которое необходимо найти. Регулярное выражение должно быть строкой в двойных кавычках.

  2. Необязательный второй аргумент ограничивает зону поиска; он может быть связан с определенной позицией в буфере.

  3. Необязательный третий аргумент задает, то каким образом функция сообщит о неудачном поиске: nil в качестве третьего аргумента заставляет функцию сообщить об ошибке (и отобразить сообщение в эхо-области) когда поиск неудачен; любое другое значение заставляет функцию вернуть nil, если поиск окончится неудачно, и t в случае успеха.

  4. Необязательный третий аргумент --- это число повторов. Отрицательное значение заставляет re-search-forward производить поиск в обратном направлении.

Шаблон для re-search-forward выглядит следующим образом:

 
(re-search-forward "регулярное-выражение"
                границы-поиска
                что-делать-в-случае-неудачи
                число-повторов)

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

В функции forward-sentence, регулярное выражение будет значением переменной sentence-end, то есть:

 
"[.?!][]\"')}]*\\($\\|  \\|  \\)[       
]*"

Границей поиска будет конец параграфа (так как предложение не может заканчиваться после окончания параграфа). Если поиск будет неудачен, то функция вернет nil; число повторов можно обеспечить числовым аргументом при вызове функции forward-sentence.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.3 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 уменьшение счетчика

Это цикл 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 выполняет две вещи:

  1. Функция re-search-forward имеет побочный эффект --- перемещает курсор в конец найденого образца.

  2. Функция 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] [ ? ]

12.4 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.

Цикл 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))

В этом выражении курсор будет перемещаться вперед на строку до тех пор, пока следующие три выражения истинны:

  1. Точка не находится в конце буфера.

  2. Текст, который следует за точкой не является разделителем параграфа.

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

Последнее условие может выглядеть загадочным, если вы не вспомните, что в самом начале функции 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] [ ? ]

12.5 Создание своего собственного файла `TAGS'

Вы можете создать свои собственные файлы `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] [ ? ]

12.6 Заключение

Ниже приведено краткое перечисление недавно изученных функций.

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 принимает четыре аргумента:

  1. Образец для поиска, в виде регулярного выражения.

  2. Границы поиска (необязательный параметр).

  3. Что делать в случае неуспешного поиска --- вернуть значение nil или сигнализировать об ошибке (необязательный параметр).

  4. Сколько раз повторять поиск; если задан отрицательный аргумент, то поиск происходит в обратном направлении (необязательный аргумент).

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] [ ? ]

12.7 Упражнения с re-search-forward


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated on March, 10 2004 using texi2html