37.2 Действия парсинга

По умолчанию процесс парсинга просто перемещает точку в текущем буфере, в конечном итоге возвращая t, если парсинг успешен, и nil, если нет. Также возможно определить действия парсинга, которые могут выполнять произвольный Elisp в определенных точках в разобранном тексте. Эти действия могут опционально влиять на так называемую стек парсинга, которая является списком значений, возвращаемых процессом парсинга. Эти действия выполняются (и возвращают значения) только в том случае, если процесс парсинга в конечном счете успешен; если он завершается неудачей, код действия не выполняется вообще.

Действия могут быть добавлены в любое место определения правила. Они отличаются от выражений парсинга начальной обратной кавычкой (‘`’), за которой следует круглые скобки, внутри которых должен присутствовать пар "двойной дефис" (‘--’). Символы слева от дефисов связываются с значениями, извлекаемыми из стека (они несколько аналогичны списку аргументов лямбда-формы). Значения, генерируемые кодом справа от дефисов, помещаются в стек (аналогично значению возврата лямбды). Например, предыдущая грамматика может быть дополнена действиями для возврата разобранного числа в виде фактического целого числа:

(with-peg-rules ((number sign digit (* digit
                                       `(a b -- (+ (* a 10) b)))
                         `(sign val -- (* sign val)))
                 (sign (or (and "+" `(-- 1))
                           (and "-" `(-- -1))
                           (and ""  `(-- 1))))
                 (digit [0-9] `(-- (- (char-before) ?0))))
  (peg-run (peg number)))

Должны быть значения в стеке, прежде чем они могут быть извлечены и возвращены – если недостаточно значений в стеке для привязки к левым членам действия, они будут привязаны к nil. Действие, содержащее только правые члены, поместит значения в стек; действие, содержащее только левые члены, потребит (и отбросит) значения из стека. В конце парсинга значения стека возвращаются в виде плоского списка.

Чтобы вернуть строку, соответствующую PEX (вместо простого перемещения точки над ней), грамматика может использовать такое правило:

(one-word
  `(-- (point))
  (+ [word])
  `(start -- (buffer-substring start (point))))

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

(substring e)

Сопоставляет PEX e и помещает сопоставленную строку в стек.

(region e)

Сопоставляет e и помещает в стек начальные и конечные позиции сопоставленной области.

(replace e replacement)

Сопоставляет e и заменяет сопоставленную область строкой replacement.

(list e)

Сопоставляет e, собирает все значения, возвращаемые e (и его подвыражениями), в список и помещает этот список в стек. Значения стека обычно возвращаются в виде плоского списка; это способ "группировки" значений вместе.