Subject: 30.0.50; [PATCH] New keyboard macro counter functions
From: Alex Bochannek <[email-masked]>
To: [email-masked]
Date: Thu, 16 Feb 2023 00:17:25 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

Hello!

I have been working on blog posts about keyboard macros and found that
it would be useful to have comparison functions for the keyboard macro
counter.

I implemented two functions to load and save macro counter values from
and to number registers; three comparison functions of the macro counter
with a number register that conditionally increment the counter; three
comparison functions of the macro counter with a prefix that terminate
the macro execution. This simplifies handling multiple counters and
conditional macro termination.

I am attaching the changes to:
  emacs.texi
  kmacro.texi
  NEWS
  kmacro.el
  kmacro-tests.el

I hope this functionality is useful and that I followed the coding and
style standards.

Thanks!

	Advanced keyboard macro counter commands for register
	integration and conditional macro termination

	* doc/emacs/emacs.texi (Top):
	Document advanced keyboard macro counter commands.

	* doc/emacs/kmacro.texi (Keyboard Macros, Keyboard Macro Counter):
	Document advanced keyboard macro counter commands.

	* etc/NEWS:
	Document advanced keyboard macro counter commands.

	* lisp/kmacro.el (kmacro-keymap, kmacro-reg-load-counter)
	(kmacro-reg-save-counter, kmacro-reg-add-counter-equal)
	(kmacro-reg-add-counter-equal, kmacro-reg-add-counter-less)
	(kmacro-reg-add-counter-greater, kmacro-reg-add-counter)
	(kmacro-quit-counter-equal, kmacro-quit-counter-less)
	(kmacro-quit-counter-greater, kmacro-quit-counter):
	Add advanced keyboard macro counter commands to kmacro keymap.
	Implement advanced keyboard macro counter commands.

	* test/lisp/kmacro-tests.el (kmacro-tests-test-reg-load)
	(kmacro-tests-test-reg-save)
	(kmacro-tests-test-reg-add-counter-equal-01)
	(kmacro-tests-test-reg-add-counter-equal-02)
	(kmacro-tests-test-reg-add-counter-equal-03)
	(kmacro-tests-test-reg-add-counter-equal-04)
	(kmacro-tests-test-reg-add-counter-less)
	(kmacro-tests-test-reg-add-counter-greater)
	(kmacro-tests-test-quit-counter-equal-01)
	(kmacro-tests-test-quit-counter-equal-02)
	(kmacro-tests-test-quit-counter-equal-03)
	(kmacro-tests-test-quit-counter-equal-04)
	(kmacro-tests-test-quit-counter-less)
	(kmacro-tests-test-quit-counter-greater):
	Implement unit tests for advanced keyboard macro counter
	commands.
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index 7071ea44edd..2584dce8d44 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -434,6 +434,7 @@ Top
 * Basic Keyboard Macro::     Defining and running keyboard macros.
 * Keyboard Macro Ring::      Where previous keyboard macros are saved.
 * Keyboard Macro Counter::   Inserting incrementing numbers in macros.
+* Advanced Macro Counter::   Advanced macro counter commands.
 * Keyboard Macro Query::     Making keyboard macros do different things each
                                 time.
 * Save Keyboard Macro::      Giving keyboard macros names; saving them in
diff --git a/doc/emacs/kmacro.texi b/doc/emacs/kmacro.texi
index fc1402b489d..27c84c0f96f 100644
--- a/doc/emacs/kmacro.texi
+++ b/doc/emacs/kmacro.texi
@@ -35,6 +35,7 @@ Keyboard Macros
 * Basic Keyboard Macro::     Defining and running keyboard macros.
 * Keyboard Macro Ring::      Where previous keyboard macros are saved.
 * Keyboard Macro Counter::   Inserting incrementing numbers in macros.
+* Advanced Macro Counter::   Advanced macro counter commands.
 * Keyboard Macro Query::     Making keyboard macros do different things each
                                time.
 * Save Keyboard Macro::      Giving keyboard macros names; saving them in
@@ -364,6 +365,123 @@ Keyboard Macro Counter
 keyboard macro counter.  @xref{Number Registers}.  For most purposes,
 it is simpler to use a keyboard macro counter.
 
+@node Advanced Macro Counter
+@section Advanced Macro Counter Commands
+
+  The counter associated with a keyboard macro is sufficient in most
+cases.  If additional counters are required for a macro, registers can
+be used and these advanced macro counter commands simplify the
+interaction between the two.  Additional commands are provided to
+terminate a macro after a predefined number of runs.
+
+
+@table @kbd
+@item C-x C-k C-r l
+Load the value of a number register into the macro counter
+(@code{kmacro-reg-load-counter}).
+@item C-x C-k C-r s
+Save the value of the macro counter to a number register
+(@code{kmacro-reg-save-counter}).
+@end table
+
+@table @kbd
+@item C-x C-k C-r a =
+Compare if the macro counter is equal to the value of a register and
+increment the counter if it is (@code{kmacro-reg-add-counter-equal}).
+@item C-x C-k C-r a <
+Compare if the macro counter is less than the value of a register and
+increment the counter if it is (@code{kmacro-reg-add-counter-less}).
+@item C-x C-k C-r a >
+Compare if the macro counter is greater than the value of a register
+and increment the counter if it is
+(@code{kmacro-reg-add-counter-greater}).
+@end table
+
+@table @kbd
+@item C-x C-k C-q =
+Compare if the macro counter is equal to the prefix and terminate the
+macro if it is (@code{kmacro-quit-counter-equal}).
+@item C-x C-k C-q <
+Compare if the macro counter is less than the prefix and terminate the
+macro if it is (@code{kmacro-quit-counter-less}).
+@item C-x C-k C-q >
+Compare if the macro counter is greater than the prefix and terminate
+the macro if it is (@code{kmacro-quit-counter-greater}).
+@end table
+
+@findex kmacro-reg-load-counter
+@kindex C-x C-k C-r l
+@findex kmacro-reg-save-counter
+@kindex C-x C-k C-r s
+  The command @kbd{C-x C-k C-r l} (@code{kmacro-reg-load-counter})
+prompts for the register name from which to load a number into the
+macro counter.  The command @kbd{C-x C-k C-r s}
+(@code{kmacro-reg-save-counter}) prompts for the register name into
+which to save the macro counter's value.  Both @kbd{C-x C-k C-r l}
+(@code{kmacro-reg-load-counter}) and @kbd{C-x C-k C-r s}
+(@code{kmacro-reg-save-counter}) show a preview of the registers by
+default.  @xref{Registers}.  Both commands can be used during or
+outside a keyboard macro definition.
+
+@findex kmacro-reg-add-counter-equal
+@kindex C-x C-k C-r a =
+@findex kmacro-reg-add-counter-less
+@kindex C-x C-k C-r a <
+@findex kmacro-reg-add-counter-greater
+@kindex C-x C-k C-r a >
+  The @kbd{C-x C-k C-r a =} (@code{kmacro-reg-add-counter-equal}),
+@kbd{C-x C-k C-r a <} (@code{kmacro-reg-add-counter-less}), and
+@kbd{C-x C-k C-r a >} (@code{kmacro-reg-add-counter-greater}) commands
+all follow the same pattern.  During keyboard macro definition, the
+command prompts for a register name (with preview by default), the
+contents of which will be compared with the macro counter's value.  If
+the counter is equal to (@code{=}), less than (@code{<}), or greater
+than (@code{>}) the number register's contents, the counter will be
+incremented by the numeric prefix or one if no prefix was given to the
+command.
+
+  For example,
+
+@example
+C-u 2 C-x C-k C-r a > N
+@end example
+
+@noindent
+compares the counter with the contents of register @code{N} and if the
+counter is greater than that, increases it by two.
+
+@findex kmacro-quit-counter-equal
+@kindex C-x C-k C-q =
+@findex kmacro-quit-counter-less
+@kindex C-x C-k C-q <
+@findex kmacro-quit-counter-greater
+@kindex C-x C-k C-q >
+  Finally, the @kbd{C-x C-k C-q =} (@code{kmacro-quit-counter-equal}),
+@kbd{C-x C-k C-q <} (@code{kmacro-quit-counter-less}), and @kbd{C-x
+C-k C-q >} (@code{kmacro-quit-counter-greater}) commands compare the
+macro counter with the prefix given and terminate the execution of the
+macro, if the comparison succeeds.  If no numeric prefix or only
+@code{C-u} are given, the counter will be compared with zero.  The
+macro is terminated using the @code{keyboard-quit} function.  Using
+this command to exit from a macro that has been called by another
+macro is not supported, the entire executing macro is terminated.
+
+  The quit commands can be used to construct the equivalent of a
+@code{while}-loop. This example will stop after ten executions
+assuming the starting value for the macro counter is the default zero.
+
+@example
+C-u 10 C-x C-k C-q = C-x C-k C-i @key{RET}
+@end example
+
+  With the default counter value of zero, the macro called with a
+prefix of @code{C-u C-u} to execute sixteen times, will stop after ten
+iterations.  The counter values that have been inserted will be from 0
+to 9.  If the counter starts out a different value below ten, it will
+still stop at ten, because the counter does not actually count macro
+executions, but is incremented explicitly by the @code{C-x C-k C-i}
+command.
+
 @node Keyboard Macro Query
 @section Executing Macros with Variations
diff --git a/etc/NEWS b/etc/NEWS
index 4fbe09e0541..d5a3ebb1df5 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -187,6 +187,25 @@ This command adds a docstring comment to the current defun.  If a
 comment already exists, point is only moved to the comment.  It is
 bound to 'C-c C-d' in 'go-ts-mode'.
 
+** Kmacro
+
++++
+*** New Advanced Macro Counter functions.
+New commands have been added to to implement advanced macro counter
+functions.
+
+The commands 'C-x C-k C-r l' and 'C-x C-k C-r s' load and save the
+macro counter from a to a number register respectively.
+
+The commands 'C-x C-k C-r a =', 'C-x C-k C-r a <', and
+'C-x C-k C-r a >' compare the macro counter with the contents of a
+number register and increment the counter by a prefix if the
+comparison succeeds.
+
+The commands 'C-x C-k C-q =', 'C-x C-k C-q <', and 'C-x C-k C-q >'
+compare the macro counter with a prefix and terminate the macro if the
+comparison succeeds.
+
 
 * New Modes and Packages in Emacs 30.1

diff --git a/lisp/kmacro.el b/lisp/kmacro.el
index 94d8794bd23..e7c3f75efd0 100644
--- a/lisp/kmacro.el
+++ b/lisp/kmacro.el
@@ -183,10 +183,18 @@ kmacro-keymap
   "C-l"  #'kmacro-call-ring-2nd-repeat
 
   ;; macro counter
-  "C-f"  #'kmacro-set-format
-  "C-c"  #'kmacro-set-counter
-  "C-i"  #'kmacro-insert-counter
-  "C-a"  #'kmacro-add-counter
+  "C-f"     #'kmacro-set-format
+  "C-c"     #'kmacro-set-counter
+  "C-i"     #'kmacro-insert-counter
+  "C-a"     #'kmacro-add-counter
+  "C-r l"   #'kmacro-reg-load-counter
+  "C-r s"   #'kmacro-reg-save-counter
+  "C-r a =" #'kmacro-reg-add-counter-equal
+  "C-r a <" #'kmacro-reg-add-counter-less
+  "C-r a >" #'kmacro-reg-add-counter-greater
+  "C-q ="   #'kmacro-quit-counter-equal
+  "C-q <"   #'kmacro-quit-counter-less
+  "C-q >"   #'kmacro-quit-counter-greater
 
   ;; macro editing
   "C-e"  #'kmacro-edit-macro-repeat
@@ -347,6 +355,89 @@ kmacro-add-counter
     (kmacro-display-counter)))

+(defun kmacro-reg-load-counter (register)
+  "Load the value of a register into `kmacro-counter'"
+  (interactive
+   (list (register-read-with-preview "Load register to counter: ")))
+  (let ((register-val (get-register register)))
+    (when (numberp register-val)
+     (setq kmacro-counter register-val))))
+
+
+(defun kmacro-reg-save-counter (register)
+  "Save the value of `kmacro-counter' to a register"
+  (interactive
+   (list (register-read-with-preview "Save counter to register: ")))
+  (set-register register kmacro-counter))
+
+
+(defun kmacro-reg-add-counter-equal (&optional arg)
+  "Increment counter by ARG if it is equal to register value"
+  (interactive "p")
+  (let
+      ((register (register-read-with-preview "Compare counter to register: ")))
+    (kmacro-reg-add-counter '= register arg)))
+
+
+(defun kmacro-reg-add-counter-less (&optional arg)
+  "Increment counter by ARG if it is less than register value"
+  (interactive "p")
+  (let
+      ((register (register-read-with-preview "Compare counter to register: ")))
+    (kmacro-reg-add-counter '< register arg)))
+
+
+(defun kmacro-reg-add-counter-greater (&optional arg)
+  "Increment counter by ARG if it is greater than register value"
+  (interactive "p")
+  (let
+      ((register (register-read-with-preview "Compare counter to register: ")))
+    (kmacro-reg-add-counter '> register arg)))
+
+
+(defun kmacro-reg-add-counter (func register &optional arg)
+  "Increment the counter by ARG if (FUNC kmacro-counter REGISTER-VALUE)
+is true.
+With no ARG, ARG is set to 1"
+  (let ((register-val (get-register register))
+        (arg (if (null arg) 1 arg)))
+    (when (apply func (list kmacro-counter register-val))
+      (setq current-prefix-arg nil)
+      (kmacro-add-counter arg))))
+
+
+(defun kmacro-quit-counter-equal (&optional arg)
+  "Quit the keyboard macro if the counter is equal to ARG"
+  (interactive "P")
+  (kmacro-quit-counter '= arg))
+
+
+(defun kmacro-quit-counter-less (&optional arg)
+  "Quit the keyboard macro if the counter is less than ARG"
+  (interactive "P")
+  (kmacro-quit-counter '< arg))
+
+
+(defun kmacro-quit-counter-greater (&optional arg)
+  "Quit the keyboard macro if the counter is greater than ARG"
+    (interactive "P")
+    (kmacro-quit-counter '> arg))
+
+
+(defun kmacro-quit-counter (func &optional arg)
+  "Quit the keyboard macro if (FUNC kmacro-counter ARG) is true.
+With \\[universal-argument] or no ARG, ARG is set to 0"
+  (when kmacro-initial-counter-value
+      (setq kmacro-counter kmacro-initial-counter-value
+	    kmacro-initial-counter-value nil))
+  (let ((arg
+	 (cond ((or (consp arg) (null arg)) 0)
+	       ((eq '- arg) -1)
+	       (t arg))))
+    (when (apply func (list kmacro-counter arg))
+      (keyboard-quit))))
+
+
 (defun kmacro-loop-setup-function ()
   "Function called prior to each iteration of macro."
   ;; Restore macro counter format to initial format, so it is ok to change
diff --git a/test/lisp/kmacro-tests.el b/test/lisp/kmacro-tests.el
index 551fd8b60fc..e30681db539 100644
--- a/test/lisp/kmacro-tests.el
+++ b/test/lisp/kmacro-tests.el
@@ -275,6 +275,220 @@ kmacro-tests-start-insert-counter-appends-to-macro
     ;;  Verify that the recording state has changed.
     (should (equal defining-kbd-macro 'append))))
 
+
+(kmacro-tests-deftest kmacro-tests-test-reg-load ()
+  "`kmacro-reg-load-counter' loads the value of register to into the counter"
+  (set-register ?\C-r 4) ;; Should be safe as a register name
+  (kmacro-tests-simulate-command '(kmacro-set-counter 1))
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              "\C-x\C-k\C-i"
+                              ;; Load from register
+                              "\C-x\C-k\C-rl\C-r"
+                              ))
+  (kmacro-tests-should-insert "1245"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 2)))
+    (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-save ()
+  "`kmacro-reg-save-counter' save the counter to a register"
+  (set-register ?\C-r nil) ;; Should be safe as a register name
+  (kmacro-tests-simulate-command '(kmacro-set-counter 1))
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Save to register
+                              "\C-x\C-k\C-rs\C-r"
+                              ;; Add to counter
+                              "\C-u2\C-x\C-k\C-a"
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Insert register
+                              "\C-xri\C-r"
+                              ))
+  (kmacro-tests-should-insert "142586"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 2)))
+  (set-register ?\C-r nil))
+
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-01 ()
+  "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+  (set-register ?\C-r 2) ;; Should be safe as a register name
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Increment counter if it matches
+                              "\C-x\C-k\C-ra=\C-r"
+                              ))
+  (kmacro-tests-should-insert "0134"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-02 ()
+  "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+  (set-register ?\C-r 2) ;; Should be safe as a register name
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Add two to counter if it matches
+                              "\C-u2\C-x\C-k\C-ra=\C-r"
+                              ))
+  (kmacro-tests-should-insert "0145"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-03 ()
+  "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+  (set-register ?\C-r 2) ;; Should be safe as a register name
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Add four to counter if it matches
+                              "\C-u\C-x\C-k\C-ra=\C-r"
+                              ))
+  (kmacro-tests-should-insert "0167"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-04 ()
+  "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+  (set-register ?\C-r 2) ;; Should be safe as a register name
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Decrement counter if it matches
+                              "\C-u-\C-x\C-k\C-ra=\C-r"
+                              ))
+  (kmacro-tests-should-insert "0111"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-less ()
+  "`kmacro-reg-add-counter-less' increments counter if less than register"
+  (set-register ?\C-r 6) ;; Should be safe as a register name
+  (kmacro-tests-simulate-command '(kmacro-set-counter 8))
+  (kmacro-tests-define-macro (vconcat
+                              ;; Decrement counter if it's
+                              ;; less than the register
+                              "\C-u-1\C-x\C-k\C-ra<\C-r"
+                              ;; Insert and decrement counter
+                              "\C-u-\C-x\C-k\C-i"
+                              ))
+  (kmacro-tests-should-insert "8764"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+    (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-greater ()
+  "`kmacro-reg-add-counter-greater' increments counter if greater than register"
+  (set-register ?\C-r 2) ;; Should be safe as a register name
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Increment counter if it's greater
+                              ;; than the register
+                              "\C-x\C-k\C-ra>\C-r"
+                              ))
+  (kmacro-tests-should-insert "0124"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (set-register ?\C-r nil))
+
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-equal-01 ()
+  "`kmacro-quit-counter-equal' stops macro if counter is equal to prefix"
+  (kmacro-tests-simulate-command '(kmacro-set-counter 5))
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and decrement counter
+                              "\C-u-\C-x\C-k\C-i"
+                              ;; Stop if the counter is at 0
+                              "\C-x\C-k\C-q="
+                              ))
+  (kmacro-tests-should-insert "5432"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (should (condition-case abort
+              (should (= 1 kmacro-counter))
+              (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+            (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-equal-02 ()
+  "`kmacro-quit-counter-equal' stops macro if counter is equal to prefix"
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Stop if the counter is at 5
+                              "\C-u5\C-x\C-k\C-q="
+                              ))
+  (kmacro-tests-should-insert "0123"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (should (condition-case abort
+              (should (= 4 kmacro-counter))
+              (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+            (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-equal-03 ()
+  "`kmacro-quit-counter-equal' stops macro if counter is equal to prefix"
+  (kmacro-tests-simulate-command '(kmacro-set-counter 5))
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and decrement counter
+                              "\C-u-\C-x\C-k\C-i"
+                              ;; Stop if the counter is at 0
+                              "\C-u\C-x\C-k\C-q="
+                              ))
+  (kmacro-tests-should-insert "5432"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (should (condition-case abort
+              (should (= 1 kmacro-counter))
+              (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+            (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-equal-04 ()
+  "`kmacro-quit-counter-equal' stops macro if counter is equal to prefix"
+  (kmacro-tests-simulate-command '(kmacro-set-counter 4))
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and decrement counter
+                              "\C-u-\C-x\C-k\C-i"
+                              ;; Stop if the counter is at -1
+                              "\C-u-\C-x\C-k\C-q="
+                              ))
+  (kmacro-tests-should-insert "4321"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (should (condition-case abort
+              (should (= 0 kmacro-counter))
+              (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+            (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-less ()
+  "`kmacro-quit-counter-less' stops macro if counter is less than prefix"
+  (kmacro-tests-simulate-command '(kmacro-set-counter 8))
+  (kmacro-tests-define-macro (vconcat
+                              ;; Stop if the counter is less than 5
+                              "\C-u5\C-x\C-k\C-q<"
+                              ;; Insert and decrement counter
+                              "\C-u-\C-x\C-k\C-i"
+                              ))
+  (kmacro-tests-should-insert "8765"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (should (condition-case abort
+              (should (= 4 kmacro-counter))
+              (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+            (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-greater ()
+  "`kmacro-quit-counter-greater' stops macro if counter is greater than prefix"
+  (kmacro-tests-define-macro (vconcat
+                              ;; Insert and increment counter
+                              "\C-x\C-k\C-i"
+                              ;; Stop if the counter is greater than 4
+                              "\C-u4\C-x\C-k\C-q>"
+                              ))
+  (kmacro-tests-should-insert "0123"
+    (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+  (should (condition-case abort
+              (should (= 4 kmacro-counter))
+              (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+            (quit abort))))
+
+
 (kmacro-tests-deftest kmacro-tests-end-call-macro-prefix-args ()
   "kmacro-end-call-macro changes behavior based on prefix arg."
   ;; "Record" two macros.
-- 
Alex.