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
#'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)
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
(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
(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.
(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
n))))))))
(completing-read
(concat
- "Rename variable"
+ (or prompt "Rename variable")
(when default
(concat " (default " (sweeprolog--format-variable default) ")"))
": ")
(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
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