Elisp mechanism for converting PCRE regexps to emacs regexps - regex

I admit significant bias toward liking PCRE regexps much better than emacs, if no no other reason that when I type a '(' I pretty much always want a grouping operator. And, of course, \w and similar are SO much more convenient than the other equivalents.
But it would be crazy to expect to change the internals of emacs, of course. But it should be possible to convert from a PCRE experssion to an emacs expression, I'd think, and do all the needed conversions so I can write:
(defun my-super-regexp-function ...
(search-forward (pcre-convert "__\\w: \d+")))
(or similar).
Anyone know of a elisp library that can do this?
Edit: Selecting a response from the answers below...
Wow, I love coming back from 4 days of vacation to find a slew of interesting answers to sort through! I love the work that went into the solutions of both types.
In the end, it looks like both the exec-a-script and straight elisp versions of the solutions would both work, but from a pure speed and "correctness" approach the elisp version is certainly the one that people would prefer (myself included).

https://github.com/joddie/pcre2el is the up-to-date version of this answer.
pcre2el or rxt (RegeXp Translator or RegeXp Tools) is a utility for working with regular expressions in Emacs, based on a recursive-descent parser for regexp syntax. In addition to converting (a subset of) PCRE syntax into its Emacs equivalent, it can do the following:
convert Emacs syntax to PCRE
convert either syntax to rx, an S-expression based regexp syntax
untangle complex regexps by showing the parse tree in rx form and highlighting the corresponding chunks of code
show the complete list of strings (productions) matching a regexp, provided the list is finite
provide live font-locking of regexp syntax (so far only for Elisp buffers – other modes on the TODO list)
The text of the original answer follows...
Here's a quick and ugly Emacs lisp solution (EDIT: now located more permanently here). It's based mostly on the description in the pcrepattern man page, and works token by token, converting only the following constructions:
parenthesis grouping ( .. )
alternation |
numerical repeats {M,N}
string quoting \Q .. \E
simple character escapes: \a, \c, \e, \f, \n, \r, \t, \x, and \ + octal digits
character classes: \d, \D, \h, \H, \s, \S, \v, \V
\w and \W left as they are (using Emacs' own idea of word and non-word characters)
It doesn't do anything with more complicated PCRE assertions, but it does try to convert escapes inside character classes. In the case of character classes including something like \D, this is done by converting into a non-capturing group with alternation.
It passes the tests I wrote for it, but there are certainly bugs, and the method of scanning token-by-token is probably slow. In other words, no warranty. But perhaps it will do enough of the simpler part of the job for some purposes. Interested parties are invited to improve it ;-)
(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
"\\|")
"\\)")))))

I made a few minor modifications to a perl script I found on perlmonks (to take values from the command line) and saved it as re_pl2el.pl (given below). Then the following does a decent job of converting PCRE to elisp regexps, at least for non-exotic the cases that I tested.
(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:]]+"
It doesn't handle a few "corner" cases like perl's shy {N,M}? constructs, and of course not code execution etc. but it might serve your needs or be a good starting place for such. Since you like PCRE I presume you know enough perl to fix any cases you use often. If not let me know and we can probably fix them.
I would be happier with a script that parsed the regex into an AST and then spit it back out in elisp format (since then it could spit it out in rx format too), but I couldn't find anything doing that and it seemed like a lot of work when I should be working on my thesis. :-) I find it hard to believe that noone has done it though.
Below is my "improved" version of re_pl2el.pl. -i means don't double escape for strings, and -n means don't print a final newline.
#! /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 ???

The closest previous work on this have been extensions to M-x re-builder, see
http://www.emacswiki.org/emacs/ReBuilder
or the work of Ye Wenbin on PDE.
http://cpansearch.perl.org/src/YEWENBIN/Emacs-PDE-0.2.16/lisp/doc/pde.html

Possibly relevant is visual-regexp-steroids, which extends query-replace to use a live preview and allows you to use different regexp backends, including PCRE.

Related

Elisp Regex Match Commas Outside of Parenthesis

I'm having an issue trying to get regex grouping to occur correctly. I have an string, say:
dtASDF[a, b, c]
I use the expression:
dt\\(.*\\)\\[\\(.*\\), \\(.*\\), \\(.*\\)\\]
And it groups the a, b, and c seperatly, which is perfect. The issue is that the string might be:
dtASDF[a, .(b, c), .(d, e)]
In which case the above expression doesn't work, since it's just looking for commas. So the question is, since emacs doesn't have lookarounds, is there a way to group by comma seperation, but not if the commas are between parenthesis?
In other words, the grouping here should be: a, .(b, c), .(d, e).
I'm aware similar questions exist on SO, but I couldn't find one relevant for elisp regex, which is tricky as it doesn't feature lookarounds that are typically used for this sort of thing.
How about this as the grouping pattern?
"\\(\\.([^)]*)\\|.*?\\)"
So in full:
"dt\\(.*\\)\\[\\(\\.([^)]*)\\|.*?\\), \\(\\.([^)]*)\\|.*?\\), \\(\\.([^)]*)\\|.*?\\)\\]"
n.b. If you want to get verbose-but-readable with the rx macro, you can write that as:
(rx "dt" (group (zero-or-more not-newline)) "["
(group (or (seq ".(" (zero-or-more (not (any ")"))) ")")
(minimal-match (zero-or-more not-newline))))
", "
(group (or (seq ".(" (zero-or-more (not (any ")"))) ")")
(minimal-match (zero-or-more not-newline))))
", "
(group (or (seq ".(" (zero-or-more (not (any ")"))) ")")
(minimal-match (zero-or-more not-newline))))
"]")

Regexp doesn't evaluate meta-character \w

Fiddling around in Racket I'm trying to write a simple lexer that uses regular expressions to handle patterns, but it doesn't seem to want to work with the meta-character \w.
#lang racket
(define (tokenize-broken str)
(match str
["\"" 'StringDelim]
[(regexp #rx"#\\w+") 'Message]
[_ 'Undefined]))
(define (tokenize-working str)
(match str
["\"" 'StringDelim]
[(regexp #rx"#[a-zA-Z_]+") 'Message]
[_ 'Undefined]))
Now when I try to run them in the repl I get this:
> (tokenize-broken "#msg")
'Undefined
> (tokenize-working "#msg")
'Message
So what's going on here? why can't I get \w to work? It works fine in other languages supporting regular expressions, so why not here?
I believe that \w is not included in regexp. Try pregexp (ie, "Perl" regexp), and use #px instead of #rx.
(define (tokenize-fixed str)
(match str
["\"" 'StringDelim]
[(pregexp #px"#\\w+") 'Message]
[_ 'Undefined]))
> (tokenize-fixed "#msg")
'Message
It works: http://pasterack.org/pastes/19596

how to split a string in clojure not in regular expression mode

The split in both clojure and java takes regular expression as parameter to split. But I just want to use normal char to split. The char passed in could be "|", ","," " etc. how to split a line by that char?
I need some function like (split string a-char). And this function will be called at very high frequency, so need good performance. Any good solution.
There are a few features in java.util.regex.Pattern class that support treating strings as literal regular expressions. This is useful for cases such as these. #cgrand already alluded to (Pattern/quote s) in a comment to another answer. One more such feature is the LITERAL flag (documented here). It can be used when compiling literal regular expression patterns. Remember that #"foo" in Clojure is essentially syntax sugar for (Pattern/compile "foo"). Putting it all together we have:
(import 'java.util.regex.Pattern)
(clojure.string/split "foo[]bar" (Pattern/compile "[]" Pattern/LITERAL))
;; ["foo" "bar"]
Just make your character a regex by properly escaping special characters and use the default regex split (which is fastest by far).
This version will make a regexp that automatically escapes every character or string within it
(defn char-to-regex
[c]
(re-pattern (java.util.regex.Pattern/quote (str c))))
This version will make a regexp that escapes a single character if it's within the special character range of regexps
(defn char-to-regex
[c]
(if ((set "<([{\\^-=$!|]})?*+.>") c)
(re-pattern (str "\\" c))
(re-pattern c)))
Make sure to bind the regex, so you don't call char-to-regex over and over again if you need to do multiple splits
(let [break (char-to-regex \|)]
(clojure.string/split "This is | the string | to | split" break))
=> ["This is " " the string " " to " " split"]

replace-regex to remove first element from camel case function name

I have code like this:
<?= $this->Article->getShowComments(); ?>
and need to convert it to
{{ Article.showComments }}
everything is easy with simple regex: exception .getFooBar to .fooBar, how can I do this in Elisp, is there something like replace using a function like in javascript?
Set case-fold-search to nil:
M-x set-variable RET case-fold-search RET nil
Now, use the following command for the transformation:
M-x replace-regexp RET \_<\(?:[a-z0-9]\|\s_\)+\([A-Z]\) RET \,(downcase \1)
The first argument is the regexp to search for, the second is the replacement text.
The regexp is a little complicated, but essentially matches a symbol starting (\_< is symbol start) with either lowercase letters or digits ([a-z0-9]) or non-word symbol characters (\s_), followed by a single uppercase letter [A-Z]. The first non-grouping parenthesis \(?:…\) just groups the or-operator \|.
The second parenthesis around the uppercase letter is grouping, which creates the “reference” \1 for use in our replacement text.
We wrap the reference to the matched uppercase letter into the function downcase to convert it to lowercase. The \, in the replacement text just tells Emacs, that the following text is a proper sexp to be evaluated and not just a simple string.
Edit 1) The rx variant of this RE is probably easier to understand:
(and symbol-start
(one-or-more (or (any "a-z" "0-9") (syntax symbol)))
(group-n 1 (any "A-Z"))
Unfortunately you can't use RX expressions in replace-match.
Edit 2) replace-regexp is intended for interactive use only. It should not be used non-interactively, i.e. from Emacs Lisp. Notably, when used non-interactively, this function will not compile the replacement text, so the special \, escape will not work!
From Emacs Lisp, use re-search-forward and replace-match:
(let ((case-fold-search nil)
(regexp (rx symbol-start
(one-or-more (or (any "a-z" "0-9") (syntax symbol)))
(group-n 1 (any "A-Z")))))
(while (re-search-forward regexp nil 'no-error)
(replace-match (downcase (match-string 1)) 'fixed-case 'literal)))
Make sure to wrap this in with-current-buffer to make it operate on the right buffer.
Here's what I've got in my attic for this:
(defun CamelCase->underscore (str)
(mapconcat 'identity (CamelCase->list str) "_"))
(defun CamelCase->list (str)
(let ((case-fold-search nil)
(pos 0)
words)
(while (string-match ".[^A-Z]*" str pos)
(let ((word (downcase (match-string-no-properties 0 str))))
(if (> (length word) 1)
(push word words)
(setq words (cons (concat (car words) word)
(cdr words)))))
(setq pos (match-end 0)))
(reverse words)))
(CamelCase->underscore "getShowComments")
;; => "get_show_comments"
Just needs a bit of adapting for your case.
And here's the adaptation:
(defun CamelCase->something (str)
(let ((case-fold-search nil)
(pos 0)
words)
(while (string-match ".[^A-Z]*" str pos)
(let ((word (match-string-no-properties 0 str)))
(if (> (length word) 1)
(push word words)
(setq words (cons (concat (car words) word)
(cdr words)))))
(setq pos (match-end 0)))
(setq words (cdr (reverse words)))
(mapconcat 'identity
(cons (downcase (car words)) (cdr words))
"")))

How to color # (at symbol) in Emacs?

I can color keywords in emacs using the following lisp code in .emacs:
(add-hook 'c-mode-common-hook
(lambda () (font-lock-add-keywords nil
'(("\\<\\(bla[a-zA-Z1-9_]*\\)" 1 font-lock-warning-face t)))))
This code color all keywords that start with "bla". Example: blaTest123_test
However when I try to add # (the 'at' symbol) instead of "bla", it doesn't seem to work. I don't think # is a special character for regular expressions.
Do you know how I can get emacs to highlight keywords starting with the # symbol?
Your problem is the \< in your regexp, which
matches the empty string, but only at the beginning of a word. `\<' matches at the beginning of the buffer (or string) only if a word-constituent character follows.
and # is not a word-constituent character.
See: M-: (info "(elisp) Regexp Backslash") RET
This unrestricted pattern will colour any #:
(font-lock-add-keywords nil
'(("#" 0 font-lock-warning-face t)))
And this will do something like what you want, by requiring either BOL or some white space immediately beforehand.
(font-lock-add-keywords nil
'(("\\(?:^\\|\\s-\\)\\(#[a-zA-Z1-9_]*\\)" 1 font-lock-warning-face t)))