ample-regexps 20151023.300(in MELPA)
ample regular expressions for Emacs

概要

Emacsの正規表現は古い方言のため、PCREなどの現代的な正規表現よりも
バックスラッシュが多くなります。

しかもelispには正規表現リテラルがないため、
文字列で表現するとバックスラッシュてんこもりになってしまいます。

まさに「読めるかバカヤロー!」状態ですが、
これを解決する方法がS式で正規表現を記述する rx です。

rxマクロは正規表現を表すためのミニ言語であり、
大量のバックスラッシュよりもいくぶん理解しやすくなります。

rxがわからないならば<f1> f rxでdocstringを読みましょう。

ものすごい長いですがカンで読めるはずです(笑)

で、実際のelispプログラミングでたくさんの正規表現を定義するとき、
部分的な正規表現はぜひとも再利用したいわけです。

しかし、rxのミニ言語自体は語彙を追加できません。

ample-regexps.el は、語彙を追加したrxを提供します。

;;; a=10のようにふつうの言語の変数設定の正規表現
(let ((assign (rx (and (regexp "[[:alnum:]_]+")
                       "="
                       (regexp "[[:alnum:]_]+")))))
  assign                            ; => "[[:alnum:]_]+=[[:alnum:]_]+"
  (string-match assign "a=10")          ; => 0
  )
;;; alnumの部分がかぶってるので変数にしてしまいましょうか。
(let* ((name '(regexp "[[:alnum:]_]+"))
       (assign (eval `(rx (and ,name "=" ,name)))))
  assign                            ; => "[[:alnum:]_]+=[[:alnum:]_]+"
  (string-match assign "a=10")          ; => 0
  )
;;; やっぱりevalまで持ち出すのは嫌だな…ample-regexpsを使おう
(require 'ample-regexps)
(define-arx arx-assign-regexp
  '((name (regexp "[[:alnum:]_]+"))))
;; arx-assign-regexpはnameという語彙が追加されたrxとして使える!
(let ((assign (arx-assign-regexp (and name "=" name))))
  assign                            ; => "[[:alnum:]_]+=[[:alnum:]_]+"
  (string-match assign "a=10")      ; => 0
  )
;; しかも (and 〜) は省略できる
(let ((assign (arx-assign-regexp name "=" name)))
  assign                            ; => "[[:alnum:]_]+=[[:alnum:]_]+"
  (string-match assign "a=10")      ; => 0
  )

nameという語彙が追加され、再利用できることがわかりました。

rxを無理に使わなくても手軽に語彙を追加できます。

(define-arx sample-rx
  '((notnewline (regexp "."))
    (chars (regexp ".+"))))
(sample-rx notnewline)                  ; => "."
(sample-rx chars)                       ; => ".+"
(sample-rx notnewline chars)            ; => "..+"

これだけだとあまり威力を感じないかもしれないので、
もっと複雑な例を示しましょう。

語彙に関数を追加することもできてしまいます。

;;; たくさん語彙を定義した例。
(define-arx cond-assignment-rx
  '((alpha_ (regexp "[[:alpha:]_]"))         ;アルファベットと_
    (alnum_ (regexp "[[:alnum:]_]"))         ;アルファベットと数字と_
    (ws (* blank))                           ;whitespace
    (sym (:func (lambda (_form &rest args) ; foo -> \_<foo\_> の変換関数
                  `(and symbol-start (or ,@args) symbol-end))))
    ;; (and symbol-start (or "if" "elif" "while") symbol-end) になる
    (cond-keyword (sym "if" "elif" "while"))
    (id (sym (+ alpha_) (* alnum_)))))
(cond-assignment-rx ws)  ; => "[[:blank:]]*"
(cond-assignment-rx (sym "foo"))  ; => "\\_<\\(?:foo\\)\\_>"
(cond-assignment-rx (sym "foo" "bar"))  ; => "\\_<\\(?:bar\\|foo\\)\\_>"
(let ((id (cond-assignment-rx id)))
  id              ; => "\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>"
  (string-match id "1")                 ; => 0
  (string-match id "a")                 ; => 0
  (string-match id "p111")              ; => 0
  )
(let ((assign (cond-assignment-rx id ws "=" ws id)))
  assign ; => "\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>[[:blank:]]*=[[:blank:]]*\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>"
  (string-match assign "p111 = a")      ; => 0
  (string-match assign "p111 = 1")         ; => 0
  ;; 【注意】メジャーモード依存!
  ;; emacs-lisp-modeでは p111=1 自体がシンボルになるのでマッチしない
  (string-match assign "p111=1")         ; => nil
  ;; c-modeならばp111と1がシンボルになるのでマッチする
  (with-temp-buffer
    (c-mode)
    (string-match assign "p111=1"))     ; => 0
  )
;;; 条件文にマッチさせる
(let ((cond-assign (cond-assignment-rx cond-keyword ws id ws "==" ws id)))
  cond-assign
  ;; => "\\_<\\(?:elif\\|if\\|while\\)\\_>[[:blank:]]*\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>[[:blank:]]*==[[:blank:]]*\\_<\\(?:[[:alpha:]_]+\\|[[:alnum:]_]*\\)\\_>"
  (string-match cond-assign "if p111 == 10") ; => 0
  )

このように、elispでパーサを書いたり、font-lockを定義したり、
それなりの規模のテキストを処理するときに役立ちます。

インストール

パッケージシステムを初めて使う人は
以下の設定を ~/.emacs.d/init.el の
先頭に加えてください。

(package-initialize)
(setq package-archives
      '(("gnu" . "http://elpa.gnu.org/packages/")
        ("melpa" . "http://melpa.org/packages/")
        ("org" . "http://orgmode.org/elpa/")))

初めてample-regexpsを使う方は
以下のコマンドを実行します。

M-x package-install ample-regexps

アップグレードする方は、
以下のコマンドでアップグレードしてください。
そのためにはpackage-utilsパッケージが必要です。

M-x package-install package-utils (初めてアップグレードする場合のみ)
M-x package-utils-upgrade-by-name ample-regexps


本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。