Механизм Elisp для преобразования регулярных выражений PCRE в регулярные выражения emacs - PullRequest
25 голосов
/ 02 февраля 2012

Я допускаю значительный уклон в сторону симпатии PCRE . Выражается гораздо лучше, чем emacs, если нет других причин, когда я набираю '(' Я почти всегда хочу оператор группировки. И, конечно же, \ w и подобные им НАСТОЛЬКО более удобны, чем другие эквиваленты.

Но было бы сумасшествием ожидать, что внутренности Emacs будут изменены, конечно. Но, я думаю, должна быть возможность преобразовать эксперимент PCRE в выражение emacs и выполнить все необходимые преобразования, чтобы я мог написать:

(defun my-super-regexp-function ...
   (search-forward (pcre-convert "__\\w: \d+")))

(или аналогичный).

Кто-нибудь знает библиотеку elisp, которая может это сделать?

<ч />

Редактировать: Выбор ответа из ответов ниже ...

Ух ты, я люблю возвращаться после 4 дней отпуска, чтобы найти множество интересных ответов, чтобы разобраться! Мне нравится работа, которая вошла в решения обоих типов.

В конце концов, похоже, что обе версии решений exec-a-script и прямой elisp будут работать, но с точки зрения чистой скорости и "правильности", версия elisp определенно является той, которую люди предпочтут ( я в том числе).

Ответы [ 4 ]

24 голосов
/ 14 февраля 2012

https://github.com/joddie/pcre2el - актуальная версия этого ответа.

pcre2el или rxt (RegeXp Translator или RegeXp Tools) - утилита для работы с регулярными выражениями в Emacs, основанная на парсере рекурсивного спуска для синтаксиса regexp. В дополнение к преобразованию (подмножеству) синтаксиса PCRE в его эквивалент Emacs, он может выполнять следующие действия:

  • преобразование синтаксиса Emacs в PCRE
  • преобразовать любой синтаксис в rx, синтаксис регулярного выражения на основе S-выражений
  • распутывает сложные регулярные выражения, показывая дерево разбора в форме rx и выделяя соответствующие фрагменты кода
  • показать полный список строк (произведений), соответствующих регулярному выражению, при условии, что список конечен
  • обеспечивает оперативную блокировку шрифтов синтаксиса регулярных выражений (пока только для буферов Elisp - другие режимы в списке TODO)

Текст исходного ответа следует ...

<Ч />

Вот быстрое и уродливое решение для Eiscs lisp (РЕДАКТИРОВАТЬ: теперь находится более постоянно здесь ). Он основан в основном на описании на справочной странице pcrepattern и работает по токену по токену, преобразуя только следующие конструкции:

  • группировка скобок ( .. )
  • чередование |
  • числовые повторы {M,N}
  • строка цитирования \Q .. \E
  • экранирование простых символов: \a, \c, \e, \f, \n, \r, \t, \x и \ + восьмеричные цифры
  • классы символов: \d, \D, \h, \H, \s, \S, \v, \V
  • \w и \W оставлены как есть (используя собственное представление Emacs о словах и несловесных символах)

Он ничего не делает с более сложными утверждениями PCRE, но он пытается преобразовать escape-символы внутри классов символов. В случае классов символов, включающих что-то вроде \D, это делается путем преобразования в группу без захвата с чередованием.

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

(eval-when-compile (require 'cl))

(defvar pcre-horizontal-whitespace-chars
  (mapconcat 'char-to-string
             '(#x0009 #x0020 #x00A0 #x1680 #x180E #x2000 #x2001 #x2002 #x2003
                      #x2004 #x2005 #x2006 #x2007 #x2008 #x2009 #x200A #x202F
                      #x205F #x3000)
             ""))

(defvar pcre-vertical-whitespace-chars
  (mapconcat 'char-to-string
             '(#x000A #x000B #x000C #x000D #x0085 #x2028 #x2029) ""))

(defvar pcre-whitespace-chars
  (mapconcat 'char-to-string '(9 10 12 13 32) ""))

(defvar pcre-horizontal-whitespace
  (concat "[" pcre-horizontal-whitespace-chars "]"))

(defvar pcre-non-horizontal-whitespace
  (concat "[^" pcre-horizontal-whitespace-chars "]"))

(defvar pcre-vertical-whitespace
  (concat "[" pcre-vertical-whitespace-chars "]"))

(defvar pcre-non-vertical-whitespace
  (concat "[^" pcre-vertical-whitespace-chars "]"))

(defvar pcre-whitespace (concat "[" pcre-whitespace-chars "]"))

(defvar pcre-non-whitespace (concat "[^" pcre-whitespace-chars "]"))

(eval-when-compile
  (defmacro pcre-token-case (&rest cases)
    "Consume a token at point and evaluate corresponding forms.

CASES is a list of `cond'-like clauses, (REGEXP FORMS
...). Considering CASES in order, if the text at point matches
REGEXP then moves point over the matched string and returns the
value of FORMS. Returns `nil' if none of the CASES matches."
    (declare (debug (&rest (sexp &rest form))))
    `(cond
      ,@(mapcar
         (lambda (case)
           (let ((token (car case))
                 (action (cdr case)))
             `((looking-at ,token)
               (goto-char (match-end 0))
               ,@action)))
         cases)
      (t nil))))

(defun pcre-to-elisp (pcre)
  "Convert PCRE, a regexp in PCRE notation, into Elisp string form."
  (with-temp-buffer
    (insert pcre)
    (goto-char (point-min))
    (let ((capture-count 0) (accum '())
          (case-fold-search nil))
      (while (not (eobp))
        (let ((translated
               (or
                ;; Handle tokens that are treated the same in
                ;; character classes
                (pcre-re-or-class-token-to-elisp)   

                ;; Other tokens
                (pcre-token-case
                 ("|" "\\|")
                 ("(" (incf capture-count) "\\(")
                 (")" "\\)")
                 ("{" "\\{")
                 ("}" "\\}")

                 ;; Character class
                 ("\\[" (pcre-char-class-to-elisp))

                 ;; Backslash + digits => backreference or octal char?
                 ("\\\\\\([0-9]+\\)"
                  (let* ((digits (match-string 1))
                         (dec (string-to-number digits)))
                    ;; from "man pcrepattern": If the number is
                    ;; less than 10, or if there have been at
                    ;; least that many previous capturing left
                    ;; parentheses in the expression, the entire
                    ;; sequence is taken as a back reference.   
                    (cond ((< dec 10) (concat "\\" digits))
                          ((>= capture-count dec)
                           (error "backreference \\%s can't be used in Emacs regexps"
                                  digits))
                          (t
                           ;; from "man pcrepattern": if the
                           ;; decimal number is greater than 9 and
                           ;; there have not been that many
                           ;; capturing subpatterns, PCRE re-reads
                           ;; up to three octal digits following
                           ;; the backslash, and uses them to
                           ;; generate a data character. Any
                           ;; subsequent digits stand for
                           ;; themselves.
                           (goto-char (match-beginning 1))
                           (re-search-forward "[0-7]\\{0,3\\}")
                           (char-to-string (string-to-number (match-string 0) 8))))))

                 ;; Regexp quoting.
                 ("\\\\Q"
                  (let ((beginning (point)))
                    (search-forward "\\E")
                    (regexp-quote (buffer-substring beginning (match-beginning 0)))))

                 ;; Various character classes
                 ("\\\\d" "[0-9]")
                 ("\\\\D" "[^0-9]")
                 ("\\\\h" pcre-horizontal-whitespace)
                 ("\\\\H" pcre-non-horizontal-whitespace)
                 ("\\\\s" pcre-whitespace)
                 ("\\\\S" pcre-non-whitespace)
                 ("\\\\v" pcre-vertical-whitespace)
                 ("\\\\V" pcre-non-vertical-whitespace)

                 ;; Use Emacs' native notion of word characters
                 ("\\\\[Ww]" (match-string 0))

                 ;; Any other escaped character
                 ("\\\\\\(.\\)" (regexp-quote (match-string 1)))

                 ;; Any normal character
                 ("." (match-string 0))))))
          (push translated accum)))
      (apply 'concat (reverse accum)))))

(defun pcre-re-or-class-token-to-elisp ()
  "Consume the PCRE token at point and return its Elisp equivalent.

Handles only tokens which have the same meaning in character
classes as outside them."
  (pcre-token-case
   ("\\\\a" (char-to-string #x07))  ; bell
   ("\\\\c\\(.\\)"                  ; control character
    (char-to-string
     (- (string-to-char (upcase (match-string 1))) 64)))
   ("\\\\e" (char-to-string #x1b))  ; escape
   ("\\\\f" (char-to-string #x0c))  ; formfeed
   ("\\\\n" (char-to-string #x0a))  ; linefeed
   ("\\\\r" (char-to-string #x0d))  ; carriage return
   ("\\\\t" (char-to-string #x09))  ; tab
   ("\\\\x\\([A-Za-z0-9]\\{2\\}\\)"
    (char-to-string (string-to-number (match-string 1) 16)))
   ("\\\\x{\\([A-Za-z0-9]*\\)}"
    (char-to-string (string-to-number (match-string 1) 16)))))

(defun pcre-char-class-to-elisp ()
  "Consume the remaining PCRE character class at point and return its Elisp equivalent.

Point should be after the opening \"[\" when this is called, and
will be just after the closing \"]\" when it returns."
  (let ((accum '("["))
        (pcre-char-class-alternatives '())
        (negated nil))
    (when (looking-at "\\^")
      (setq negated t)
      (push "^" accum)
      (forward-char))
    (when (looking-at "\\]") (push "]" accum) (forward-char))

    (while (not (looking-at "\\]"))
      (let ((translated
             (or
              (pcre-re-or-class-token-to-elisp)
              (pcre-token-case              
               ;; Backslash + digits => always an octal char
               ("\\\\\\([0-7]\\{1,3\\}\\)"    
                (char-to-string (string-to-number (match-string 1) 8)))

               ;; Various character classes. To implement negative char classes,
               ;; we cons them onto the list `pcre-char-class-alternatives' and
               ;; transform the char class into a shy group with alternation
               ("\\\\d" "0-9")
               ("\\\\D" (push (if negated "[0-9]" "[^0-9]")
                              pcre-char-class-alternatives) "")
               ("\\\\h" pcre-horizontal-whitespace-chars)
               ("\\\\H" (push (if negated
                                  pcre-horizontal-whitespace
                                pcre-non-horizontal-whitespace)
                              pcre-char-class-alternatives) "")
               ("\\\\s" pcre-whitespace-chars)
               ("\\\\S" (push (if negated
                                  pcre-whitespace
                                pcre-non-whitespace)
                              pcre-char-class-alternatives) "")
               ("\\\\v" pcre-vertical-whitespace-chars)
               ("\\\\V" (push (if negated
                                  pcre-vertical-whitespace
                                pcre-non-vertical-whitespace)
                              pcre-char-class-alternatives) "")
               ("\\\\w" (push (if negated "\\W" "\\w") 
                              pcre-char-class-alternatives) "")
               ("\\\\W" (push (if negated "\\w" "\\W") 
                              pcre-char-class-alternatives) "")

               ;; Leave POSIX syntax unchanged
               ("\\[:[a-z]*:\\]" (match-string 0))

               ;; Ignore other escapes
               ("\\\\\\(.\\)" (match-string 0))

               ;; Copy everything else
               ("." (match-string 0))))))
        (push translated accum)))
    (push "]" accum)
    (forward-char)
    (let ((class
           (apply 'concat (reverse accum))))
      (when (or (equal class "[]")
                (equal class "[^]"))
        (setq class ""))
      (if (not pcre-char-class-alternatives)
          class
        (concat "\\(?:"
                class "\\|"
                (mapconcat 'identity
                           pcre-char-class-alternatives
                           "\\|")
                "\\)")))))
8 голосов
/ 11 февраля 2012

Я сделал несколько незначительных изменений в скрипте perl, который нашел в perlmonks (для получения значений из командной строки) и сохранил его как re_pl2el.pl (приведено ниже). Затем следующее делает достойную работу по преобразованию PCRE в регулярные выражения elisp, по крайней мере для неэкзотических случаев, которые я тестировал.

(defun pcre-to-elre (regex)
  (interactive "MPCRE expression: ")
  (shell-command-to-string (concat "re_pl2el.pl -i -n "
                                   (shell-quote-argument regex))))

(pcre-to-elre "__\\w: \\d+") ;-> "__[[:word:]]: [[:digit:]]+"

Он не обрабатывает несколько «угловых» случаев, таких как застенчивые {N,M}? конструкции perl, и, конечно, не выполнение кода и т. Д., Но он может служить вашим потребностям или послужить хорошей отправной точкой для такого. Поскольку вам нравится PCRE, я предполагаю, что вы знаете достаточно perl, чтобы исправить любые случаи, которые вы часто используете. Если нет, дайте мне знать, и мы, возможно, сможем их исправить.

Я был бы счастлив с помощью сценария, который анализировал бы регулярное выражение в AST, а затем выплевывал его обратно в формате elisp (с тех пор он мог выплевывать его и в формате rx), но я не мог найти ничего, что могло бы сделать это и казалось большой работой, когда я должен был работать над диссертацией. :-) Мне трудно поверить, что никто этого не сделал.

Ниже моя "улучшенная" версия re_pl2el.pl. -i означает, что не удваивать escape для строк, а -n означает, что не печатать последний перевод строки.

#! /usr/bin/perl
#
# File: re_pl2el.pl
# Modified from http://perlmonks.org/?node_id=796020
#
# Description:
#
use strict;
use warnings;

# version 0.4


# TODO
# * wrap converter to function
# * testsuite

#--- flags
my $flag_interactive; # true => no extra escaping of backslashes
if ( int(@ARGV) >= 1 and $ARGV[0] eq '-i' ) {
    $flag_interactive = 1;
    shift @ARGV;
}

if ( int(@ARGV) >= 1 and $ARGV[0] eq '-n' ) {
    shift @ARGV;
} else {
    $\="\n";
}

if ( int(@ARGV) < 1 ) {
    print "usage: $0 [-i] [-n] REGEX";
    exit;
}

my $RE='\w*(a|b|c)\d\(';
$RE='\d{2,3}';
$RE='"(.*?)"';
$RE="\0".'\"\t(.*?)"';
$RE=$ARGV[0];

# print "Perlcode:\t $RE";

#--- encode all \0 chars as escape sequence
$RE=~s#\0#\\0#g;

#--- substitute pairs of backslashes with \0
$RE=~s#\\\\#\0#g;

#--- hide escape sequences of \t,\n,... with
#    corresponding ascii code
my %ascii=(
       t =>"\t",
       n=> "\n"
      );
my $kascii=join "|",keys %ascii;

$RE=~s#\\($kascii)#$ascii{$1}#g;


#---  normalize needless escaping
# e.g.  from /\"/ to /"/, since it's no difference in perl
# but might confuse elisp

$RE=~s#\\"#"#g;

#--- toggle escaping of 'backslash constructs'
my $bsc='(){}|';
$RE=~s#[$bsc]#\\$&#g;  # escape them once
$RE=~s#\\\\##g;        # and erase double-escaping



#--- replace character classes
my %charclass=(
        w => 'word' ,   # TODO: emacs22 already knows \w ???
        d => 'digit',
        s => 'space'
       );

my $kc=join "|",keys %charclass;
$RE=~s#\\($kc)#[[:$charclass{$1}:]]#g;



#--- unhide pairs of backslashes
$RE=~s#\0#\\\\#g;

#--- escaping for elisp string
unless ($flag_interactive){
  $RE=~s#\\#\\\\#g; # ... backslashes
  $RE=~s#"#\\"#g;   # ... quotes
}

#--- unhide escape sequences of \t,\n,...
my %rascii= reverse %ascii;
my $vascii=join "|",keys %rascii;
$RE=~s#($vascii)#\\$rascii{$1}#g;

# print "Elispcode:\t $RE";
print "$RE";
#TODO whats the elisp syntax for \0 ???
1 голос
/ 07 февраля 2012

Ближайшей предыдущей работой над этим были расширения для M-x re-build, см.

http://www.emacswiki.org/emacs/ReBuilder

или работа Е. Вэньбина на PDE.

http://cpansearch.perl.org/src/YEWENBIN/Emacs-PDE-0.2.16/lisp/doc/pde.html

0 голосов
/ 21 января 2017

Возможно, имеет значение visual-regexp-steroids , которое расширяет запрос-замену для использования предварительного просмотра в реальном времени и позволяет использовать различные бэкэнды регулярного выражения, включая PCRE.

...