14.3 The count-words-in-defun Function

We have seen that there are several ways to write a count-words-region function. To write a count-words-in-defun, we need merely adapt one of these versions.

The version that uses a while loop is easy to understand, so I am going to adapt that. Because count-words-in-defun will be part of a more complex program, it need not be interactive and it need not display a message but just return the count. These considerations simplify the definition a little.

On the other hand, count-words-in-defun will be used within a buffer that contains function definitions. Consequently, it is reasonable to ask that the function determine whether it is called when point is within a function definition, and if it is, to return the count for that definition. This adds complexity to the definition, but saves us from needing to pass arguments to the function.

These considerations lead us to prepare the following template:

(defun count-words-in-defun ()
  (set up…
     (while loop…)
   return count)

As usual, our job is to fill in the slots.

First, the set up.

We are presuming that this function will be called within a buffer containing function definitions. Point will either be within a function definition or not. For count-words-in-defun to work, point must move to the beginning of the definition, a counter must start at zero, and the counting loop must stop when point reaches the end of the definition.

The beginning-of-defun function searches backwards for an opening delimiter such as a ‘(’ at the beginning of a line, and moves point to that position, or else to the limit of the search. In practice, this means that beginning-of-defun moves point to the beginning of an enclosing or preceding function definition, or else to the beginning of the buffer. We can use beginning-of-defun to place point where we wish to start.

The while loop requires a counter to keep track of the words or symbols being counted. A let expression can be used to create a local variable for this purpose, and bind it to an initial value of zero.

The end-of-defun function works like beginning-of-defun except that it moves point to the end of the definition. end-of-defun can be used as part of an expression that determines the position of the end of the definition.

The set up for count-words-in-defun takes shape rapidly: first we move point to the beginning of the definition, then we create a local variable to hold the count, and finally, we record the position of the end of the definition so the while loop will know when to stop looping.

The code looks like this:

(let ((count 0)
      (end (save-excursion (end-of-defun) (point))))

The code is simple. The only slight complication is likely to concern end: it is bound to the position of the end of the definition by a save-excursion expression that returns the value of point after end-of-defun temporarily moves it to the end of the definition.

The second part of the count-words-in-defun, after the set up, is the while loop.

The loop must contain an expression that jumps point forward word by word and symbol by symbol, and another expression that counts the jumps. The true-or-false-test for the while loop should test true so long as point should jump forward, and false when point is at the end of the definition. We have already redefined the regular expression for this, so the loop is straightforward:

(while (and (< (point) end)
             "\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*" end t))
  (setq count (1+ count)))

The third part of the function definition returns the count of words and symbols. This part is the last expression within the body of the let expression, and can be, very simply, the local variable count, which when evaluated returns the count.

Put together, the count-words-in-defun definition looks like this:

(defun count-words-in-defun ()
  "Return the number of words and symbols in a defun."
  (let ((count 0)
        (end (save-excursion (end-of-defun) (point))))
        (and (< (point) end)
              "\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*"
              end t))
      (setq count (1+ count)))

How to test this? The function is not interactive, but it is easy to put a wrapper around the function to make it interactive; we can use almost the same code as for the recursive version of count-words-example:

;;; Interactive version.
(defun count-words-defun ()
  "Number of words and symbols in a function definition."
   "Counting words and symbols in function definition ... ")
  (let ((count (count-words-in-defun)))
     ((zerop count)
       "The definition does NOT have any words or symbols."))
     ((= 1 count)
       "The definition has 1 word or symbol."))
       "The definition has %d words or symbols." count)))))

Let’s reuse C-c = as a convenient key binding:

(global-set-key "\C-c=" 'count-words-defun)

Now we can try out count-words-defun: install both count-words-in-defun and count-words-defun, and set the key binding. Then copy the following to an Emacs Lisp buffer (like, for instance, *scratch*), place the cursor within the definition, and use the C-c = command.

(defun multiply-by-seven (number)
  "Multiply NUMBER by seven."
  (* 7 number))
     ⇒ 10

Success! The definition has 10 words and symbols.

The next problem is to count the numbers of words and symbols in several definitions within a single file.