regex - Mecanismo de Elisp para convertir las expresiones regulares de PCRE a las expresiones regulares de emacs
(4)
Admito un sesgo significativo hacia el gusto. Las expresiones de expresión PCRE son mucho mejores que las de Emacs, si no hay ninguna otra razón por la que cuando escribo una '''' ('''' Siempre quiero un operador de agrupación. Y, por supuesto, / wy similares son mucho más convenientes que los otros equivalentes.
Pero sería una locura esperar cambiar las partes internas de emacs, por supuesto. Pero debería ser posible convertir de una expersión de PCRE a una expresión de emacs, pensaría, y hacer todas las conversiones necesarias para poder escribir:
(defun my-super-regexp-function ...
(search-forward (pcre-convert "__//w: /d+")))
(o similar).
Alguien sabe de una biblioteca elisp que puede hacer esto?
Editar: seleccionando una respuesta de las respuestas a continuación ...
Wow, me encanta regresar de 4 días de vacaciones para encontrar una serie de respuestas interesantes para ordenar. Me encanta el trabajo que entró en las soluciones de ambos tipos.
Al final, parece que tanto la versión exec-a-script como la versión directa de las soluciones funcionarán, pero desde un enfoque de "velocidad" y "velocidad" pura, la versión "elisp" es sin duda la que la gente preferiría (yo incluido) .
El trabajo anterior más cercano en esto han sido las extensiones de Mx re-builder, ver
http://www.emacswiki.org/emacs/ReBuilder
o el trabajo de Ye Wenbin en PDE.
http://cpansearch.perl.org/src/YEWENBIN/Emacs-PDE-0.2.16/lisp/doc/pde.html
Hice algunas modificaciones menores a un script de perl que encontré en perlmonks (para tomar valores de la línea de comandos) y lo re_pl2el.pl
como re_pl2el.pl
(que se muestra a continuación). Luego, lo siguiente hace un trabajo decente de convertir PCRE a elisp regexps, al menos para los casos no exóticos que probé.
(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:]]+"
¿No maneja unos pocos casos "de esquina" como el tímido {N,M}?
Perl {N,M}?
las construcciones, y por supuesto no la ejecución de código, etc., pero podrían satisfacer sus necesidades o ser un buen punto de partida para tales. Como le gusta PCRE, supongo que sabe suficiente perl para solucionar los casos que usa a menudo. Si no, hágamelo saber y probablemente podamos arreglarlos.
Estaría más contento con un script que analizó el regex en un AST y luego lo escupió en formato elisp (desde entonces podría escupirlo también en formato rx
), pero no pude encontrar nada haciendo eso y me pareció que Mucho trabajo cuando debería estar trabajando en mi tesis. :-) Me cuesta creer que nadie lo haya hecho.
A continuación se muestra mi versión "mejorada" de re_pl2el.pl. -i
significa no hacer doble escape para cadenas, y -n
significa no imprimir una nueva línea final.
#! /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 ???
Posiblemente relevante es visual-regexp-steroids , que extiende la consulta-reemplazo para usar una vista previa en vivo y le permite usar diferentes back-end de regexp, incluyendo PCRE.
https://github.com/joddie/pcre2el es la versión actualizada de esta respuesta.
pcre2el
orxt
(RegeXp Translator o RegeXp Tools) es una utilidad para trabajar con expresiones regulares en Emacs, basada en un analizador de descendencia recursiva para la sintaxis de regexp. Además de convertir (un subconjunto de) la sintaxis de PCRE en su equivalente de Emacs, puede hacer lo siguiente:
- convertir la sintaxis de Emacs a PCRE
- convierta la sintaxis a
rx
, una sintaxis de expresión regular basada en expresiones S- Desenrede las expresiones regulares complejas mostrando el árbol de análisis en forma de
rx
y resaltando los fragmentos de código correspondientes- muestra la lista completa de cadenas (producciones) que coinciden con una expresión regular, siempre que la lista sea finita
- proporcionar el bloqueo de fuente en vivo de la sintaxis de expresiones regulares (hasta ahora solo para los buffers de Elisp - otros modos en la lista TODO)
El texto de la respuesta original sigue ...
Aquí hay una solución rápida y fea de Emacs (EDIT: ahora se encuentra de forma más permanente https://github.com/joddie/pcre2el ). Se basa principalmente en la descripción en la página del manual de pcrepattern
, y funciona token por token, convirtiendo solo las siguientes construcciones:
- agrupación paréntesis
( .. )
- alternancia
- repeticiones numéricas
{M,N}
- cadena cotizando
/Q .. /E
- escapes de caracteres simples:
/a
,/c
,/e
,/f
,/n
,/r
,/t
,/x
y/
+ dígitos octales - clases de caracteres:
/d
,/D
,/h
,/H
,/s
,/S
,/v
,/V
-
/w
y/W
dejan como están (utilizando la propia idea de Emacs de los caracteres de palabras y no palabras)
No hace nada con aserciones de PCRE más complicadas, pero sí intenta convertir escapes dentro de clases de caracteres. En el caso de las clases de caracteres que incluyen algo como /D
, esto se hace convirtiéndolo en un grupo que no captura con alternancia.
Pasa las pruebas que escribí para él, pero ciertamente hay errores, y el método de escanear token por token es probablemente lento. En otras palabras, no hay garantía. Pero quizás haga bastante de la parte más simple del trabajo para algunos propósitos. Los interesados están invitados a mejorarlo ;-)
(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
"//|")
"//)")))))