From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: =?utf-8?Q?Daniel_Mart=C3=ADn?= Newsgroups: gmane.emacs.devel Subject: [PATCH] A program to overlay Elisp regular expressions in rx form Date: Sun, 15 Nov 2020 12:16:52 +0100 Message-ID: References: Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="28523"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (darwin) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sun Nov 15 12:18:29 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1keG3B-0007Je-Ad for ged-emacs-devel@m.gmane-mx.org; Sun, 15 Nov 2020 12:18:29 +0100 Original-Received: from localhost ([::1]:35988 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1keG3A-0006pN-DW for ged-emacs-devel@m.gmane-mx.org; Sun, 15 Nov 2020 06:18:28 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:54602) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1keG1z-0006He-1m for emacs-devel@gnu.org; Sun, 15 Nov 2020 06:17:15 -0500 Original-Received: from sonic312-25.consmr.mail.ir2.yahoo.com ([77.238.178.96]:43570) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1keG1w-0001YW-31 for emacs-devel@gnu.org; Sun, 15 Nov 2020 06:17:14 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.es; s=s2048; t=1605439021; bh=HRChDuUyo5T6Ps8eGFRfKBPDAvA4kG+zWyZtamCrGbc=; h=From:To:Subject:Date:References:From:Subject; b=I6EXgsiFBNqlsFyTWyGgKfVr00Pxj4bu2Elzr+jhVa17ZRxDiQixXynStpD+1htmKmnuoW3XuOA7lHYeBhevq6fjWfehSLxefeHYPzTb6/udtEx9rDB8ZwE6XHGQ+xv7Vh/3jNsNFyRwuZWrwDuPwTMVxBiJED8wjBiP/QdCPo07cX9b/SpKb1ptYssnYQ5k7UDWHgwD3qrvGZAHrSD0U8gpEGL2uITbUVWkubDdHXQoNkONufkvichEyLOE8Hk4iAyYV3VdKVQEoZPYr5X1JtUpC9H5PjBJoAnJAHVe0vJxa9AE8jH4mnWZ/B62bJ4mexdjvu2GcRpo1HJUgkotKA== X-SONIC-DKIM-SIGN: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.com; s=s2048; t=1605439021; bh=+qMNug43+LnTnTBZ8YhPaLGqwmK6inC3IrYy0Kcmw8l=; h=From:To:Subject:Date:From:Subject; b=H277SzO03bMTDGAKNAAjD0i76G0PWy0n6DwdfvElvWfPbXcWQPZDRiT13QEjiYmNEyuBVsU0hJABumnebl/O1zSdg6039bgoJ3KwwtpegZTxLVARKIFGTcayjUyPd2/epK5raTKeRIQZY19xPEUUH3oa+fmJi7DgSAe6tU8WmTrcg6VBLNDZusjQ11kXrKuotGet2bL3SfK+R9Sv4/MPmJo3rlxxdiDUEb1RP43ctATAZQWUpxqe2dld2OF6f0YT/AhJnV+Zm0YBpwv8MG0D5dWHZbZZsKHXMI/Rk2XAJXF9A1h3c93nJXGI/EO9GNQPrk2D8dBE0zQK2qM9BbZY7A== X-YMail-OSG: 59peUCIVM1kvK.A1K_E7S8A7arXmAgwDJuT_ADOQ4go6JqynkWgrfuKn5u0FL5z yCtVox2_jK9sl_XQqnsv8VMWTnuSYxOfrdOQLmHLoNQ9whw1Mbp59NnQZRG_j59XAmMSjPTvhabH glcwNTfv2FM1NY0Yuj9mjDmLLfBvYH3_J.AHCHErQ9wv3161.qcgCIGG0mQdmFd5GXkxxHfrUK6T 4GlzG_rM2lCMp1jKR1QozTSq8DBD9JxvdA2t0fJQF1M3eGcPfd_f7vXNJkHZ0fSY_hnCjXLTKxJZ G36Czmq8qAqR1cQxPFomfy_QwCcvRkoRh4XF_BLhEc1RJYzHchOWS5fd.KFE4P_ybwffHcy.vAuh JbR.SoKJx0xL4tZClxWfMDn5bVOb1ayprbc6NctLpHenW1RpAMFphHNds7WKT9P95oAe2A18fFko 9tnxFzpCdLfPGlhH45YfqNlHoJtMb1v_bJDRkL5iV5vIobSlBov2_JtLS.TqZzL6NA0e_fDPhO5. hm5QHPUOGLWUEDUZP_W5lo.IS7BnrUv3jWrQaNrVEka_c5sgY8XvVowZAMkUBf6k6Srl2DL7HcKx xP15t0v336bIoeX67NPgbu8X60uT4HIkHpPgaO4e1DE1IO4WLLfsedNZ5b6lkQ7YKa8m3lmzMQM4 xqvKKXSaHp8vlWIayK_ili3BWJZqgTRDnd_wobxF0QZ540BhJqjSA9Vjt4af_cJt4OTrTEDQwZk. Qc9UtYK6h_6az15tl7XLNztdUm_i_INtgpAdRj4LfDR.cpr_3Fau6NKwHBan1yS2Dc5SJSmGvOwY yDbgmsh3PNIqfQh.tPOswxEWee4t3TMDG5P3n9iC61 Original-Received: from sonic.gate.mail.ne1.yahoo.com by sonic312.consmr.mail.ir2.yahoo.com with HTTP; Sun, 15 Nov 2020 11:17:01 +0000 Original-Received: by smtp403.mail.bf1.yahoo.com (VZM Hermes SMTP Server) with ESMTPA ID 46bdfe49f0304625984e655a48930d31; Sun, 15 Nov 2020 11:16:55 +0000 (UTC) X-Mailer: WebService/1.1.16944 mail.backend.jedi.jws.acl:role.jedi.acl.token.atz.jws.hermes.yahoo Apache-HttpAsyncClient/4.1.4 (Java/11.0.8) Received-SPF: pass client-ip=77.238.178.96; envelope-from=mardani29@yahoo.es; helo=sonic312-25.consmr.mail.ir2.yahoo.com X-detected-operating-system: by eggs.gnu.org: First seen = 2020/11/15 06:17:02 X-ACL-Warn: Detected OS = Linux 3.11 and newer [fuzzy] X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:259195 Archived-At: --=-=-= Content-Type: text/plain Understanding complex regular expressions in Emacs Lisp code (for example, the ones in compile.el) is sometimes difficult with all the backslashes, so I've written a program that, using the ELPA package xr[1], enters a minor mode that overlays the rx form of any regular expression string. This idea came from macroexpand and how it helps with understanding Lisp macros. This program autoloads a new function: regexp-expand. When the point is in a program string and regexp-expand is invoked, Emacs enters a minor mode that puts the buffer temporarily in read-only mode and shows the regular expression in rx form as an overlay. You can ask for help about the rx notation by pressing e. You can exit the minor mode and return to the original text by pressing q. Is there general interest in this kind of functionality as a free program? I don't know if this could be a new feature of the xr package (most probably), or if it could be its own separate package. A first version of this program is attached to this email, along with some tests that demonstrate how it works. Thanks. [1] https://elpa.gnu.org/packages/xr.html --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=regexp-expand.el Content-Transfer-Encoding: quoted-printable ;;; regexp-expand.el --- Show the ELisp regular expression at point in `rx'= form. -*- lexical-binding: t; -*- ;; Copyright (C) 2020 Daniel Mart=C3=ADn ;; Author: Daniel Mart=C3=ADn ;; URL: https://github.com/danielmartin/regexp-expand ;; Keywords: lisp, regexps, debugging ;; Version: 0.1 ;; Package-Requires: ((emacs "25.1") (xr "1.18")) ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This package shows an overlay with the regexp at point in `rx' ;; notation, by using the `xr' package ;; (https://elpa.gnu.org/packages/xr.html). Regexps in `rx' notation ;; are much more readable than in their original string form, ;; specially if they contain a lot of backslashes. ;;; Code: (require 'xr) (defgroup regexp-expand nil "Explain the Emacs List regular expression at point." :group 'lisp :link '(url-link :tag "web page" "https://github.com/danielmartin/regexp-= expand")) (defface regexp-expand-highlight-face '((((min-colors 16581375) (background light)) :background "#eee8d5") (((min-colors 16581375) (background dark)) :background "#222222")) "Face for regular expression explanation highlight." :group 'regexp-expand) (defvar regexp-expand-overlay nil "`regexp-expand' overlay in the current buffer.") (make-variable-buffer-local 'regexp-expand-overlay) (defvar regexp-expand-explain-function #'regexp-expand-explain "Function to explain the regular expression at point.") (defun regexp-expand--point-inside-string-p () "Return if point is inside a string." (nth 3 (syntax-ppss))) (defun regexp-expand--move-point-backward-outside-of-string () "Move point backward to place it one position before the first character = in a string." (goto-char (nth 8 (syntax-ppss)))) (defun regexp-expand--move-point-forward-outside-of-string () "Move point forward to place it one position after the last character in = a string." (regexp-expand--move-point-backward-outside-of-string) (forward-sexp)) (defun regexp-expand--looking-back-on-line (regexp) "Return non-nil if there is a match for REGEXP in the text before point i= n the current line." (looking-back regexp (line-beginning-position))) (defun regexp-expand--bounds-of-string-at-point () "Return the start and end locations for the string at point. If the point is not inside a string, return nil." (save-excursion (if (regexp-expand--point-inside-string-p) (regexp-expand--move-point-backward-outside-of-string) (when (regexp-expand--looking-back-on-line "\\s\"") (backward-char) (regexp-expand--move-point-backward-outside-of-string))) (when (looking-at "\\s\"") (let (beg end) (setq beg (copy-marker (point))) (forward-char) (regexp-expand--move-point-forward-outside-of-string) (setq end (point)) (cons beg end))))) (defun regexp-expand--collapse-overlay (overlay) "Collapse a regular expression explanation represented by OVERLAY and res= tore the original text." (with-current-buffer (overlay-buffer overlay) (let* ((start (overlay-start overlay)) (end (overlay-end overlay)) (text (overlay-get overlay 'regexp-expand-original-regexp)) (regexp-end (copy-marker (if (equal (char-before end) ?\n) (1- end) end)))) (goto-char start) (save-excursion (insert text) (delete-region (point) regexp-end))) (let ((highlight-overlay (overlay-get overlay 'regexp-expand-highlight-= overlay))) (when highlight-overlay (delete-overlay highlight-overlay))) (delete-overlay overlay))) (defun regexp-expand-collapse () "Collapse the current regexp explanation and disable `regexp-expand-mode'= ." (interactive) (let ((inhibit-read-only t)) (with-silent-modifications (regexp-expand--collapse-overlay regexp-expand-overlay))) (setq regexp-expand-overlay nil) (regexp-expand-mode 0)) (defun regexp-expand-command-hook () "Disable `regexp-expand-mode' if the buffer is writable." (if (not buffer-read-only) (regexp-expand-mode 0))) (defun regexp-expand-explain (regexp) "Run `xr' to convert REGEXP to an `rx' form." (insert (string-trim-right (xr-pp-rx-to-str (xr regexp))))) (defun regexp-expand-explain-rx-syntax () "Show a help buffer that documents the Lisp syntax of `rx' forms." (interactive) (describe-function #'rx)) (defvar regexp-expand-keymap (let ((map (make-sparse-keymap))) (define-key map "e" #'regexp-expand-explain-rx-syntax) (define-key map "q" #'regexp-expand-collapse) map) "Keymap for `regexp-expand-mode'.") ;;;###autoload (define-minor-mode regexp-expand-mode "Minor mode for inline explanation of regular expressions in Emacs Lisp s= ource buffers." nil " Regexp-Expand" :keymap regexp-expand-keymap :group regexp-expand (if regexp-expand-mode ;; Enter the minor mode. (progn ;; Persist undo information as we need to restore it when the ;; user exits the mode. (setq regexp-expand-saved-undo-list buffer-undo-list buffer-undo-list t) ;; We also need to persist the read-only status of the current ;; buffer and set it to read-only. (setq regexp-expand-saved-read-only buffer-read-only buffer-read-only t) (add-hook 'post-command-hook #'regexp-expand-command-hook nil t) (message (substitute-command-keys "\\Press \\[regexp-expand-explain-rx-syntax= ] for more information about `rx' syntax, or \\[regexp-expand-collapse] to = show the regular expression as a string again."))) ;; Exit the minor mode. (when regexp-expand-overlay (regexp-expand-collapse)) (setq buffer-undo-list regexp-expand-saved-undo-list buffer-read-only regexp-expand-saved-read-only regexp-expand-saved-undo-list nil) (remove-hook 'post-command-hook #'regexp-expand-command-hook t))) (defun regexp-expand--pretty-print-explanation () "Pretty prints the ELisp expression at point." (save-excursion (backward-sexp) (indent-pp-sexp))) ;;;###autoload (defun regexp-expand () "Explain the Emacs Lisp regular expression following point." (interactive) (pcase-let ((`(,start . ,end) (regexp-expand--bounds-of-string-at-point))) (unless (or start end) (error "Point is not in a string")) (setq end (copy-marker end)) (goto-char start) (let ((regexp (save-excursion (read (current-buffer)))) ;; We need to keep the original regexp around to put it back ;; in the buffer when the mode is disabled. (original-regexp (buffer-substring-no-properties start end))) (unless regexp-expand-mode (regexp-expand-mode t)) (with-silent-modifications (atomic-change-group (let ((inhibit-read-only t)) (save-excursion (funcall regexp-expand-explain-function regexp) (regexp-expand--pretty-print-explanation) (delete-region (point) end) ;; Create a new overlay. (let* ((overlay (make-overlay start (if (looking-at "\n") (progn (1+ (point))) (point)))) (highlight-overlay (copy-overlay overlay))) (overlay-put highlight-overlay 'face 'regexp-expand-highlig= ht-face) (overlay-put highlight-overlay 'priority -1) (overlay-put overlay 'regexp-expand-highlight-overlay highl= ight-overlay) (overlay-put overlay 'priority 1) (overlay-put overlay 'regexp-expand-original-regexp origina= l-regexp) (setq regexp-expand-overlay overlay))))))))) (provide 'regexp-expand) ;;; regexp-expand.el ends here --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=regexp-expand-test.el Content-Transfer-Encoding: quoted-printable ;;; regexp-expand-test.el --- Tests for regexp-expand.el. -*- lexical-bindi= ng: t; -*- ;; Copyright (C) 2020 Daniel Mart=C3=ADn ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . (require 'regexp-expand) (require 'ert) ;; Helpers (defun check-regexp-explanation (regexp) "Check if calling `regexp-expand' on REGEXP adds a correct overlay to the= buffer." (with-temp-buffer (insert (prin1-to-string regexp)) (goto-char (point-min)) (regexp-expand) (should (equal (overlay-get regexp-expand-overlay 'regexp-expand-origin= al-regexp) (prin1-to-string regexp))) (should (equal (buffer-substring-no-properties (overlay-start regexp-ex= pand-overlay) (overlay-end regexp-expa= nd-overlay)) (string-trim-right (xr-pp-rx-to-str (xr regexp))))))) ;; Regexp texts (ert-deftest regexp-expand-no-regexp () (check-regexp-explanation "Hello")) (ert-deftest regexp-expand-regexp () (check-regexp-explanation "\\([0-9]\\{5\\}\\): \\([0-9]\\{10\\}\\) \\([0-= 9]\\{5\\}\\) \\(.\\)")) (ert-deftest regexp-expand-regexp-with-control-characters () (check-regexp-explanation "^\\([^: \n\t]+\\): line \\([0-9]+\\):")) (ert-deftest regexp-expand-regexp-multiline () (check-regexp-explanation "\\(?:^cucumber\\(?: -p [^[:space:]]+\\)?\\|#\\= )\ \\(?: \\)\\([^(].*\\):\\([1-9][0-9]*\\)")) (ert-deftest regexp-expand-regexp-string-quote () (check-regexp-explanation "\\s\"")) (ert-deftest regexp-expand-not-string () (should-error (with-temp-buffer (insert "Not a string constant") (goto-char (point-min)) (regexp-expand)))) ;; Minor mode behavior tests (ert-deftest regexp-expand-enter-mode () (with-temp-buffer (insert (prin1-to-string "Hello")) (goto-char (point-min)) (regexp-expand) (should (bound-and-true-p regexp-expand-mode)))) (ert-deftest regexp-expand-enter-mode-inserts-rx-notation-in-buffer () (with-temp-buffer (insert (prin1-to-string "\\([0-9]\\{5\\}\\): \\([0-9]\\{10\\}\\) \\([0-9]\\{5\\}\\) \\= (.\\)")) (goto-char (point-min)) (regexp-expand) (should (equal (buffer-substring-no-properties (point-min) (point-max)) "(seq (group (=3D 5 (any \"0-9\"))) \": \" (group (=3D 10 (any \"0-9\"))) \" \" (group (=3D 5 (any \"0-9\"))) \" \" (group nonl))")))) (ert-deftest regexp-expand-buffer-should-not-be-writable () (should-error (with-temp-buffer (insert (prin1-to-string "Hello")) (goto-char (point-min)) (regexp-expand) (insert "Buffer should not be modifiable")))) (ert-deftest regexp-expand-exit-mode () (with-temp-buffer (insert (prin1-to-string "Hello")) (goto-char (point-min)) (regexp-expand) (call-interactively (key-binding (kbd "q"))) (should-not (bound-and-true-p regexp-expand-mode)))) (ert-deftest regexp-expand-exit-mode-restores-original-regexp () (with-temp-buffer (insert (prin1-to-string "Hello")) (goto-char (point-min)) (regexp-expand) (call-interactively (key-binding (kbd "q"))) (should (equal (buffer-substring-no-properties (point-min) (point-max)) "\"Hello\"")))) (ert-deftest regexp-expand-exit-mode-buffer-is-not-modified () (with-temp-buffer (with-silent-modifications (insert (prin1-to-string "Hello"))) (goto-char (point-min)) (regexp-expand) (call-interactively (key-binding (kbd "q"))) (should-not (buffer-modified-p)))) (ert-deftest regexp-expand-exit-mode-buffer-was-modified-before () (with-temp-buffer (insert (prin1-to-string "Hello")) (goto-char (point-min)) (regexp-expand) (call-interactively (key-binding (kbd "q"))) (should (buffer-modified-p)))) (ert-deftest regexp-expand-exit-mode-persists-undo () (with-current-buffer (get-buffer-create "Undo Test") (insert (prin1-to-string "Hello")) (setq before-buffer-undo-list buffer-undo-list) (goto-char (point-min)) (regexp-expand) (call-interactively (key-binding (kbd "q"))) (should (and buffer-undo-list (> (length buffer-undo-list) 0) (equal before-buffer-undo-list buffer-undo-list))))) --=-=-=--