Search code examples
listlispelisp

How to iterate through a list of literals in Emacs lisp?


I want to replace text in an Emacs buffer and this function works:

(defun sanitize-md ()
  "Replace characters."
  (interactive)
  (query-replace "\\" "/" nil (point-min) (point-max))
  (query-replace "fi" "fi" nil (point-min) (point-max))
  )

I want to refactor it with a list with:

(defun sanitize-md ()
  "Replace characters."
  (interactive)
  (let (replacement-list '("\\" "/" "fi" "fi"))
    (while (progn
         (let* (
            (to-find (pop replacement-list))
            (to-replace (pop replacement-list))
            )
           (query-replace to-find to-replace nil (point-min) (point-max)))
         (replacement-list)
         ))))

But this throws an error Invalid function: "\\", even though I quoted the declaration of the list. I think I am getting confused with LISP's system of quotes and functions at the start of a list, so I tried (let (replacement-list '(list "\\" "/" "fi" "fi")) and got another error Wrong type argument: stringp, nil.

How to iterate through a list of literals in LISP until it is empty?


Solution

  • The problem is with the let form's syntax. The first argument should be a list of lists, not just a single list.

    Also, you will want to use (interactive "*") to avoid attempting to modify content which is read-only. And the let* form (where you are already using the correct syntax!) is already a single form, so you can take out the progn.

    Finally, replacement-list is not a function, so you should take out the parentheses around it in the last expression.

    (defun sanitize-md ()
      "Replace characters."
      (interactive "*")
      (let ((replacement-list '("\\" "/" "fi" "fi")))
        (while (let* ((to-find (pop replacement-list))
                      (to-replace (pop replacement-list)) )
                 (query-replace to-find to-replace nil (point-min) (point-max))
                 replacement-list))) ))
    

    It would perhaps be more idiomatic to make replacement-list be a list of conses with to-find and to-replace values, but this should at least get you back up onto the road.

    Note that when replacement-list is the last form of of the let* structure, that is then what the while expression evaluates, thus ending the loop when the list is empty. If query-replace were the last form in the condition of while, the loop would end when no match is found. See this other thread.