From: Eshel Yaron Date: Tue, 7 Feb 2023 19:57:30 +0000 (+0200) Subject: ADDED: new commands for managing numbered variables X-Git-Tag: V9.1.4-sweep-0.16.0~6 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=bb6449e096a2e281e44a2fb3599ac3b8f124e9c5;p=sweep.git ADDED: new commands for managing numbered variables * sweeprolog.el (sweeprolog-increment-numbered-variables) (sweeprolog-decrement-numbered-variables): new commands. (sweeprolog-mode-map): bind them to C-c C-+ and C-c C--. (sweeprolog-context-menu-for-variable): allow calling these new commands from context menus for numbered variables. * sweeprolog-tests.el: add test for incrementing numbered variables. * README.org (Numbered Variables): new manual section. --- diff --git a/README.org b/README.org index 0dd0fe3..cf466a1 100644 --- a/README.org +++ b/README.org @@ -1719,6 +1719,91 @@ right-clicking on them with the mouse and selecting =Rename Variable= from the top of the context menu. See [[#context-menu][Context Menu]] for more information about context menus in Sweep. +** Numbered Variables +:PROPERTIES: +:CUSTOM_ID: numbered-variables +:DESCRIPTION: Commands for managing numbers in names of related variables +:ALT_TITLE: Numbered Variables +:END: + +A widespread convention in Prolog is using a common prefix with a +numeric suffix to name related variables, such as ~Foo0~, ~Foo1~, etc.. +Sweep provides convenient commands for managing such /numbered variable/ +sequences consistently: + +- Key: C-c C-+ (sweeprolog-increment-numbered-variables) :: Prompt for + a numbered variable and increment it and all numbered variables with + the same base name and a greater number in the current clause. +- Key: C-c C-- (sweeprolog-decrement-numbered-variables) :: Prompt for + a numbered variable and decrement it and all numbered variables with + the same base name and a greater number in the current clause. + +Numbering variables is often used to convey the order in which they +are bound. For example: + +#+begin_src prolog + %! process(+State0, -State) is det. + + process(State0, State) :- + foo(State0, State1), + bar(State2, State1), + baz(State2, State). +#+end_src + +Here ~State0~ and ~State~ are respectively the input and output arguments +of ~process/2~, and ~State1~ and ~State2~ represent intermediary stages +between them. + +The command ~C-c C-+~ (~sweeprolog-increment-numbered-variables~) prompts +you for a numbered variable in the current clause, and increments the +number of that variable along with all other numbered variables with +the same base name and a greater number. You can use it to "make +room" for another intermediary variable between two sequentially +numbered variables. If you call this command with point on a numeric +variable, it suggests that variable as the default choice. If you +call this command with a prefix argument, it increments by the numeric +value of the prefix argument, otherwise it increments by one. + +For instance, typing ~C-c C-+ State1 RET~ with point anywhere in the +definition of ~process/2~ from the above example results in the +following code: + +#+begin_src prolog + process(State0, State) :- + foo(State0, State2), + bar(State3, State2), + baz(State3, State). +#+end_src + +Note how all occurrences of ~State1~ are replaced with ~State2~, while +occurrences of ~State2~ are replaced with ~State3~. The overall semantics +of the clause doesn't change, but we can now replace the call to ~foo/2~ +with two goals and reintroduce ~State1~ as an intermediary result +between them while keeping our numbering consistent, e.g.: + +#+begin_src prolog + process(State0, State) :- + one(State0, State1), two(State1, State2), + bar(State3, State2), + baz(State3, State). +#+end_src + +If Context Menu mode is enabled, you can also invoke +~sweeprolog-increment-numbered-variables~ by right-clicking on a +numbered variables and selecting =Increment Variable Numbers= from the +context menu. See [[#context-menu][Context Menu]]. + +The command ~C-c C--~ (~sweeprolog-decrement-numbered-variables~) is +similar to ~C-c C-+~ except it decrements all numbered variables +starting with a given numbered variable rather than incrementing them. +You can use this function after you delete a numbered variable, +leaving you with a gap in the variable numbering sequence, to +decrement the following numbered variables accordingly. + +After invoking either ~C-c C--~ or ~C-c C-+~, you can continue to +decrement or increment the same set of numbered variables by repeating +with ~-~ and ~+~. + * Prolog Help :PROPERTIES: :CUSTOM_ID: prolog-help diff --git a/sweeprolog-tests.el b/sweeprolog-tests.el index 9dad8f4..6f5f6bf 100644 --- a/sweeprolog-tests.el +++ b/sweeprolog-tests.el @@ -302,6 +302,29 @@ foo(Foo) :- bar. (should (string= (buffer-string) "foo(Baz,Bar) :- spam(Bar,Baz).")))) +(ert-deftest increment-variable () + "Tests renaming varialbes." + (let ((temp (make-temp-file "sweeprolog-test" + nil + ".pl" + " +foo(Bar0,Bar) :- + spam(Bar0,Bar1), + bar(Bar1,Bar2), + baz(Bar2, Bar). +"))) + (find-file-literally temp) + (sweeprolog-mode) + (goto-char (1+ (point-min))) + (sweeprolog-increment-numbered-variables 1 (point) "Bar1") + (should (string= (buffer-string) + " +foo(Bar0,Bar) :- + spam(Bar0,Bar2), + bar(Bar2,Bar3), + baz(Bar3, Bar). +")))) + (ert-deftest find-references () "Tests `sweeprolog-predicate-references'." (let ((temp (make-temp-file "sweeprolog-test" diff --git a/sweeprolog.el b/sweeprolog.el index 1041a4e..2bdcaa8 100644 --- a/sweeprolog.el +++ b/sweeprolog.el @@ -421,6 +421,8 @@ first." #'sweeprolog-show-diagnostics #'flymake-show-diagnostics-buffer)) (define-key map (kbd "C-c C-&") #'sweeprolog-async-goal) + (define-key map (kbd "C-c C--") #'sweeprolog-decrement-numbered-variables) + (define-key map (kbd "C-c C-+") #'sweeprolog-increment-numbered-variables) (define-key map (kbd "C-M-^") #'kill-backward-up-list) (define-key map (kbd "C-M-m") #'sweeprolog-insert-term-dwim) (define-key map (kbd "M-p") #'sweeprolog-backward-predicate) @@ -5349,6 +5351,22 @@ GOAL." sweeprolog-context-menu-point-at-click t)) +(defun sweeprolog-context-menu-increment-numbered-variables () + "Increment numbered variables starting with the variable at click." + (interactive) + (sweeprolog-increment-numbered-variables + 1 + sweeprolog-context-menu-point-at-click + sweeprolog-context-menu-variable-at-click)) + +(defun sweeprolog-context-menu-decrement-numbered-variables () + "Decrement numbered variables starting with the variable at click." + (interactive) + (sweeprolog-decrement-numbered-variables + 1 + sweeprolog-context-menu-point-at-click + sweeprolog-context-menu-variable-at-click)) + (defun sweeprolog-context-menu-for-predicate (menu tok _beg _end _point) "Extend MENU with predicate-related commands if TOK describes one." (pcase tok @@ -5413,12 +5431,29 @@ POINT is the buffer position of the mouse click." (setq sweeprolog-context-menu-point-at-click point sweeprolog-context-menu-variable-at-click (buffer-substring-no-properties beg end)) + (when-let ((var-num + (sweeprolog--decode-numbered-variable-name + sweeprolog-context-menu-variable-at-click))) + (define-key menu [sweeprolog-decrement-numbered-variables] + `(menu-item "Decrement Variable Numbers" + sweeprolog-context-menu-decrement-numbered-variables + :help ,(concat "Decrement variable numbers starting from " + (sweeprolog--format-variable + sweeprolog-context-menu-variable-at-click)) + :keys "\\[sweeprolog-decrement-numbered-variables]")) + (define-key menu [sweeprolog-increment-numbered-variables] + `(menu-item "Increment Variable Numbers" + sweeprolog-context-menu-increment-numbered-variables + :help ,(concat "Increment variable numbers starting from " + (sweeprolog--format-variable + sweeprolog-context-menu-variable-at-click)) + :keys "\\[sweeprolog-increment-numbered-variables]"))) (define-key menu [sweeprolog-rename-variable] `(menu-item "Rename Variable" sweeprolog-context-menu-rename-variable :help ,(concat "Rename variable " - (propertize sweeprolog-context-menu-variable-at-click - 'face (sweeprolog-variable-face))) + (sweeprolog--format-variable + sweeprolog-context-menu-variable-at-click)) :keys "\\[sweeprolog-rename-variable]"))))) (defvar sweeprolog-context-menu-functions @@ -5580,6 +5615,17 @@ is the name of the variable at point, if any." (not (string-match (rx bos (or "_" upper) (* alnum) eos) string)))))) +(defun sweeprolog--decode-numbered-variable-name (string) + "Return t if STRING is valid number variable name." + (save-match-data + (let ((case-fold-search nil)) + (when (string-match (rx bos (group-n 1 (or "_" upper) (or (seq (* alnum) letter) + "")) + (group-n 2 (or "0" (seq (any (?1 . ?9)) (* digit)))) eos) + string) + (cons (match-string 1 string) + (string-to-number (match-string 2 string))))))) + (defun sweeprolog-read-new-variable-try () "Try to exit the minibuffer with a new Prolog variable name. @@ -5622,7 +5668,7 @@ EXISTING-VARS is a list of existing variable names (strings)." (let ((sweeprolog-read-new-variable--existing-vars existing-vars)) (read-from-minibuffer prompt nil sweeprolog-read-new-variable-map))) -(defun sweeprolog-read-existing-variable (occurrences &optional default) +(defun sweeprolog-read-existing-variable (occurrences &optional default prompt) (let* ((max-var-len (apply #'max (mapcar #'length (mapcar #'car @@ -5643,7 +5689,7 @@ EXISTING-VARS is a list of existing variable names (strings)." n)))))))) (completing-read (concat - "Rename variable" + (or prompt "Rename variable") (when default (concat " (default " (sweeprolog--format-variable default) ")")) ": ") @@ -5664,7 +5710,7 @@ Interactively, OLD, NEW and POINT are nil, and VERBOSE is t." (var-occurrences (car term-var-occurrences)) (var-at-point (cdr term-var-occurrences))) (unless var-occurrences - (user-error "No variables to rename here!")) + (user-error "Term does not contain variables!")) (let* ((old-name (or old (sweeprolog-read-existing-variable var-occurrences @@ -5698,6 +5744,158 @@ Interactively, OLD, NEW and POINT are nil, and VERBOSE is t." old-formatted (sweeprolog--format-variable new-name)))))) +(defun sweeprolog--decode-numbered-variable (string) + (when (string-match (rx bos + (group-n 1 + (or "_" upper) + (* (or "_" alnum)) + (or "_" letter)) + (group-n 2 + (+ digit)) + eos) + string) + (cons (match-string 1 string) + (string-to-number (match-string 2 string))))) + +(defvar sweeprolog-increment-numbered-variables-last-result nil) + +(defvar sweeprolog-increment-numbered-variables-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "+") #'sweeprolog-increment-numbered-variables-more) + (define-key map (kbd "-") #'sweeprolog-decrement-numbered-variables-more) + map) + "Transient keymap activated after `sweeprolog-increment-numbered-variables'.") + +(defun sweeprolog-increment-numbered-variables (increment point &optional from) + "Increment numbered variables from at POINT starting with FROM. + +FROM is either nil or a numbered varialbe (a string) that occurs +in clause at POINT. If it is nil, prompt for a numbered variable +in the clause at POINT and use that as FROM. + +INCREMENT is an integer that is added to the number in each +occurrence of FROM and any numbered variable that has the same +base name and a greater number in the clause at POINT. + +Interactively, POINT is point, FROM is nil, and INCREMENT is the +numeric prefix argument (1 without prefix argument)." + (interactive (list (prefix-numeric-value current-prefix-arg) + (point) nil) + sweeprolog-mode) + (let* ((term-var-occurrences (sweeprolog--variables-at-point point)) + (numbered-vars (seq-filter + (lambda (v) + (sweeprolog--decode-numbered-variable-name + (car v))) + (car term-var-occurrences))) + (var-at-point (cdr term-var-occurrences))) + (unless numbered-vars + (user-error "Term does not contain numbered variables!")) + (let* ((old-name + (or from + (sweeprolog-read-existing-variable + numbered-vars var-at-point + (if (< 0 increment) + "Increment variable" + "Decrement variable")))) + (old-decoded (sweeprolog--decode-numbered-variable-name + old-name)) + (base (car old-decoded)) + (first (cdr old-decoded)) + (target (+ increment first))) + (when (< increment 0) + (let ((old-formatted (sweeprolog--format-variable old-name)) + (decrement (- increment)) + (blocker nil)) + (cond + ((< target 0) + (user-error "Cannot decrement %s by %d (negative variable number)" + old-formatted decrement)) + ((setq blocker + (caar (seq-filter (lambda (numbered-var) + (pcase (sweeprolog--decode-numbered-variable-name + (car numbered-var)) + (`(,nb . ,nn) (and (string= nb base) + (< nn first) + (<= target nn))))) + numbered-vars))) + (user-error "Cannot decrement %s by %d (blocked on %s)" + old-formatted decrement blocker))))) + (let* ((rest + (seq-filter + (lambda (v) + (pcase (sweeprolog--decode-numbered-variable-name + (car v)) + (`(,b . ,n) (and (string= b base) + (<= first n))))) + numbered-vars)) + (to-replace + (sort + (apply #'append + (mapcar + (lambda (v) + (pcase (sweeprolog--decode-numbered-variable-name + (car v)) + (`(,b . ,n) + (let* ((ni (+ n increment)) + (bn (concat b (number-to-string ni)))) + (mapcar + (lambda (p) + (list (car p) + (cdr p) + bn)) + (cdr v)))))) + rest)) + (lambda (l r) + (> (car l) (car r)))))) + (save-excursion + (combine-after-change-calls + (dolist (replace to-replace) + (let ((beg (nth 0 replace))) + (delete-region beg (nth 1 replace)) + (goto-char beg) + (insert (nth 2 replace)))))) + (setq sweeprolog-increment-numbered-variables-last-result + (concat base (number-to-string target))) + (set-transient-map sweeprolog-increment-numbered-variables-map t) + (message + (substitute-command-keys + (concat + "Repeat with " + "\\" + "\\[sweeprolog-increment-numbered-variables-more], " + "\\[sweeprolog-decrement-numbered-variables-more]"))))))) + +(defun sweeprolog-decrement-numbered-variables (decrement point &optional old) + "Decrement numbered variables from at POINT starting with FROM. + +FROM is either nil or a numbered varialbe (a string) that occurs +in clause at POINT. If it is nil, prompt for a numbered variable +in the clause at POINT and use that as FROM. + +DECREMENT is an integer that is substracted from the number in +each occurrence of FROM and any numbered variable that has the +same base name and a greater number in the clause at POINT. + +Interactively, POINT is point, FROM is nil, and DECREMENT is the +numeric prefix argument (1 without prefix argument)." + (interactive (list (prefix-numeric-value current-prefix-arg) + (point) nil) + sweeprolog-mode) + (sweeprolog-increment-numbered-variables (- decrement) point old)) + +(defun sweeprolog-increment-numbered-variables-more () + "Increment the last incremented/decremented numbered variable." + (interactive) + (sweeprolog-increment-numbered-variables + 1 (point) sweeprolog-increment-numbered-variables-last-result)) + +(defun sweeprolog-decrement-numbered-variables-more () + "Decrement the last incremented/decremented numbered variable." + (interactive) + (sweeprolog-decrement-numbered-variables + 1 (point) sweeprolog-increment-numbered-variables-last-result)) + ;;;; Footer