]> git.eshelyaron.com Git - sweep.git/commitdiff
ADDED: new commands for managing numbered variables
authorEshel Yaron <me@eshelyaron.com>
Tue, 7 Feb 2023 19:57:30 +0000 (21:57 +0200)
committerEshel Yaron <me@eshelyaron.com>
Tue, 7 Feb 2023 19:57:30 +0000 (21:57 +0200)
* 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.

README.org
sweeprolog-tests.el
sweeprolog.el

index 0dd0fe3a7e5f5224049ef04643e810cf6c96d573..cf466a1be0fdca2abc4840b4adfa60e350191039 100644 (file)
@@ -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
index 9dad8f45607d3d7660df8634f8b41a23af7030c4..6f5f6bf9496a52ab6064a047c32197cd9bf098ac 100644 (file)
@@ -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"
index 1041a4e9a3fdf9d125192ee72a884baeed6bfcd8..2bdcaa83dfe90f82b7e353a275cfab66858cdb1a 100644 (file)
@@ -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-map>"
+           "\\[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