0

I am using emacs 25.3 and I observe that one indentation function that I wrote loop mysteriously. My function goes following:

(defun aut-indent-func ()
  (interactive)

  (defun loop (begin)
    (goto-char begin)
    (let* ((end (line-end-position 1))
           (substr (buffer-substring-no-properties begin end)))
      (if (string-match-p (regexp-quote "=") substr)
          ; then we are in a definition line
          (let ((field (string-trim
                        (substring-no-properties
                         substr
                         0
                         (string-match (regexp-quote "=") substr)))))
            (string-equal field "transitions"))
        ; then we need to call recursively
        (if (/= begin (line-beginning-position 0))
            (loop (line-beginning-position 0))))))

  (if (save-excursion
        (loop (line-beginning-position 0)))
      (progn
        (indent-to-left-margin)
        (back-to-indentation)
        (indent-to-column (+ 2 (aut-eq-line-offset))))
    (indent-relative)))

Essentially, it tries to loop over each line and try to identify a lines comes with transitions = and put the indentation at the same column as the equal sign there.

However, this functions loops.

It's impossible, since it keep scanning up and stop when it hits the top. What's even more mysterious is, if I evaluate the function definition again, it stops looping, until the next time I open up emacs again(and the file containing this function got reloaded). So I do M-x toggle-debug-on-quit, and it shows following:

Debugger entered--Lisp error: (quit)
  (while t (line-beginning-position 0))
  (save-excursion (while t (line-beginning-position 0)))
  (if (save-excursion (while t (line-beginning-position 0))) (progn (indent-to-left-margin) (back-to-indentation) (indent-to-column (+ 2 (aut-eq-line-offset)))) (indent-relative))
  aut-indent-func()
  indent-according-to-mode()
  electric-indent-post-self-insert-function()
  self-insert-command(1)
  newline(nil 1)
  funcall-interactively(newline nil 1)
  call-interactively(newline nil nil)
  command-execute(newline)

well, this trace shows something seriously insane. As you can tell from the code, there isn't (while t ...), so who adds those? Is it a bug that emacs add while automatically in some occasion?

  • 1
    loop already exists in Emacs. Maybe you call the loop which is an alias to cl-loop when you think you call your loop? – choroba Feb 8 '18 at 21:35
  • @choroba but that doesn't explain while t does it? let me try to rename loop to a different name. – Jason Hu Feb 8 '18 at 21:37
  • @choroba wow, actually you are right. hmm, that's really curious. – Jason Hu Feb 8 '18 at 21:39
  • 2
    Don't nest function definitions, they don't define internal functions. Elisp ain't Scheme. – wasamasa Feb 8 '18 at 22:14
  • 2
    @HuStmpHrrr Not curious at all, loop/cl-loop is a well-known macro which expands to (while t ...) in this case. See (cl) Loop Facility. The only curiosity is that one of the packages you are using has loaded the deprecated cl feature instead of cl-lib at runtime. – Basil Feb 8 '18 at 22:14
2

As pointed out in the comments, the infinite loop is due to an identification of the call to (loop ...) as referring to the loop/cl-loop macro from Emacs' Common Lisp emulation library and its subsequent macroexpansion to an Elisp (while t ...) loop.

I believe you can make your code simpler, more idiomatic and a good deal faster whilst avoiding the other issues mentioned in the comments by writing something like:

(defun aut-search-definition-backward ()
  "Return non-nil if current line is preceded by a definition."
  (save-excursion
    (beginning-of-line)
    (re-search-backward (rx bow "transitions" (* space) ?=) nil t)))

(defun aut-indent ()
  "Forever indentured."
  (interactive)
  (if (not (aut-search-definition-backward))
      (indent-relative)
    (indent-to-left-margin)
    (back-to-indentation)
    (indent-to-column (+ 2 (aut-eq-line-offset)))))

The regexp used is intended for illustration; you should, of course, modify it for your needs/preferences.

Here are some relevant tips for the future:

  • Prefer buffer over string operations where reasonable, as the former are much more optimised than the latter. Emacs provides a plethora of relevant functionality; see (elisp) Searching and Matching.
  • Avoid recursion where iteration is reasonable, lest you go too far down the rabbit hole and hit rock bottom. In addition, function calls are still not terribly efficient in Elisp.
  • Avoid declarations such as defvar, defun etc. anywhere other than the top-level of your file, otherwise the reader/byte-compiler may not "see" them, as "seen" here. :)
  • I wouldn't go as far as avoiding all recursion (processing trees iteratively is rather confusing), but would steer clear of all cases where iteration is the goal. – wasamasa Feb 8 '18 at 23:57
  • @wasamasa I never meant to imply never, rather that Elisp is not well-suited for recursion. Is my new wording better or should I dial it down even further? – Basil Feb 9 '18 at 0:03
  • Yeah, it's better now. – wasamasa Feb 9 '18 at 0:43
  • yeah I will look more into elisp's APIs. – Jason Hu Feb 9 '18 at 3:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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