3

I would like to write a function that counts the number of nested parentheses while moving the cursor to the innermost bracket without throwing an error.

  (defun count-and-move ()
    (interactive)
    (setq c 0)
    (while t
      (down-list)
      (incf c)))

If we run this code, the cursor would eventually reach the innermost bracket.

|「「「「」」」」 --> 「「「「|」」」」

After which it throws an error:

down-list: Scan error: "Containing expression ends prematurely", 254, 255

What should the condition for while be so that the movement stops automatically when the cursor has reached the innermost point?

In human language, it would be something like: "Keep running (down-list) and increase counter by 1 until the cursor is stuck." OR "Break out of loop if (down-list) is going to throw an error!"

3 Answers 3

3

For down-list and its worker scan-lists there doesn't appear to be an option to prevent the error, so we would need to handle it somehow.

In some instances, a simplistic use of ignore-errors is fine. E.g.:

(ignore-errors (down-list))

In this instance that's problematic, because down-list will return nil even when it works, so we probably want to handle the error; although you could use the non-movement of point instead:

(if (eql (point) (progn (ignore-errors (down-list)) (point)))
    didn't move
  moved)

If we want to handle the error explicitly, we can use M-x toggle-debug-on-error to find out what we're dealing with. Here we learn:

Debugger entered--Lisp error: (scan-error "Containing expression ends prematurely" 359 360)
  (scan-lists 359 1 -1)
  (down-list 1)
  (funcall-interactively down-list 1)
  (call-interactively down-list)
  (repeat nil)
  (funcall-interactively repeat nil)
  (call-interactively repeat nil nil)
  (command-execute repeat)

So the error to handle is scan-error.

We can handle errors using condition-case, like so:

(condition-case err
    (down-list)
  (scan-error (message "Scan error: %S" err)))

See C-hig (elisp)Handling Errors for details.

2
  • I don't see any reason to not use ignore-errors in this particular case. You actually beat me in answering the question for a second and I considered not posting my answer at all. But after reading yours in full I thought the question deserved a straight simple answer. I think we should keep both our answers -- one deeper / one straight and simple.
    – Tobias
    Commented Feb 10, 2020 at 6:58
  • Yes, your usage of ignore-errors is better than mine.
    – phils
    Commented Feb 10, 2020 at 7:11
3

You can use ignore-errors. You should also use let instead of setq for c to keep the scope of c local.

(defun count-and-move ()
  (interactive)
  (let ((c 0))
    (ignore-errors
      (while t
        (down-list)
        (incf c)))
    c))
1
  • Yes, I like this answer very much as well. Thank you.
    – Sati
    Commented Feb 11, 2020 at 0:36
1

I'd probably just use this. But all of the answers provided so far are pretty much the same. The idea is, in each iteration, to increment and return the counter but "handle" a down-list error by returning nil. ignore-errors returns nil for an error or whatever evaluating its arg returns - in this case, the counter value, c.

(defun count-and-move ()
  (interactive)
  (let ((c  0))
    (while (ignore-errors (progn (down-list) (incf c))))))

The ignore-errors returns the result of the progn, which returns the result of incf, which is non-nil. But if down-list raises an error then ignore-errors returns nil. The while evaluates its body as long as the test (the ignore-errors) returns non-nil. So it stops when down-list raises an error.

2
  • Can you explain if or how your solution differs from Tobias in terms of thinking and performance? You omitted the t condition of while and used progn to handle (down-list) and (incf c).
    – Sati
    Commented Feb 11, 2020 at 0:42
  • The last sentence, or the last 2, before the code explain it. But I've added additional explanation now, to make it even clearer. But as I said, all of the answers are essentially the same.
    – Drew
    Commented Feb 11, 2020 at 1:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.