2
\$\begingroup\$

I made a function that prompts me for values of variables (formatted %^{var1}) it found in a string and fills in said values.

(defun fill-var-in-string (STRING)
  "Find variables in a string, prompt for a value and fill in STRING."
  (let ((filled STRING))
    (while (string-match "%^{\\([^}]*\\)}" STRING (match-end 0))
      (setq filled (replace-regexp-in-string (format "%%^{%s}" (match-string 1 STRING))
                                             (read-string (format "%s: " (match-string 1 STRING)))
                                             filled)))
    filled))


(message "filled in: %s" (fill-var-in-string "(%^{size}, %^{center})")) 

It works, I'm wondering if I did it the lisp way. I did not make it interactive since for now I only will call this programmatically in org-mode files.

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Style-wise, it looks odd to see the function argument named in all-caps. Standard convention is to use lower-case (but still upcase it in the doc-string).


Trying to run the code, it immediately failed:

Lisp error: (args-out-of-range "(%^{size}, %^{center})" 455)
  string-match("%^{\\([^}]*\\)}" "(%^{size}, %^{center})" 455)
  (while (string-match "%^{\\([^}]*\\)}" STRING (match-end 0)) (setq filled (replace-regexp-in-string (format "%%^{%s}" (match-string 1 STRING)) (read-string (format "%s: " (match-string 1 STRING))) filled)))
  (let ((filled STRING)) (while (string-match "%^{\\([^}]*\\)}" STRING (match-end 0)) (setq filled (replace-regexp-in-string (format "%%^{%s}" (match-string 1 STRING)) (read-string (format "%s: " (match-string 1 STRING))) filled))) filled)
  fill-var-in-string("(%^{size}, %^{center})")

This is clearly becase (match-end 0) could have any value at entry. I think we need another variable (start-pos):

(defun fill-var-in-string (string)
  "Find variables in a string, prompt for a value and fill in STRING."
  (let ((filled string)
        (start-pos 0))
    (while (string-match "%^{\\([^}]*\\)}" string start-pos)
      (setq filled (replace-regexp-in-string (format "%%^{%s}" (match-string 1 string))
                                             (read-string (format "%s: " (match-string 1 string)))
                                             filled)
            start-pos (match-end 0)))
    filled))

We can simplify a little, because (format "%%^{%s}" (match-string 1 string)) is exactly what we just found, so we can replace all that with (match-string 0 string):

(defun fill-var-in-string (string)
  "Find variables in a string, prompt for a value and fill in STRING."
  (let ((filled string)
        (start-pos 0))
    (while (string-match "%^{\\([^}]*\\)}" string start-pos)
      (setq filled (replace-regexp-in-string (match-string 0 string)
                                             (read-string (format "%s: " (match-string 1 string)))
                                             filled)
            start-pos (match-end 0)))
    filled))

I'm not convinced that we should be matching an empty name - I would use + rather than * in the regular expression there.


There's a subtle failure case. If the user provides a string that matches a replacement format, then it might get subsequently filled in (if it's a replacement we have yet to process). To avoid this, we shouldn't start replacing until we have read all the replacement strings - we'll need to populate an alist mapping variables to values, and then go through the string performing the replacements as a second phase.

\$\endgroup\$
1
  • \$\begingroup\$ Cheers mate. Thanks for the suggestions. Sorry that it failed the first time. I tested it before posting here but I guess the (match-string) still had some info in it. As for the naming convention, in most of the Emacs code I go through, the parameters are all ALLCAPS. Granted this is in the *help* buffer, this is why I used ALLCAPS. I clicked now further and checked the source code and there it's lowercaps. Will use the lowercase convention for now on. \$\endgroup\$ May 27, 2023 at 10:49

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.