5

I have a file of elisp code, say foo.el. I want to turn the content into a s-expression, so that I can play with it. Right now, I use the following method:

  1. Read foo.el into a string.
  2. Turn the string into a s-expression using read.

The code is as followed:

(read (format "(progn %s)"
              (with-temp-buffer
                (insert-file-contents "foo.el")
                (buffer-string))))

The above code works, but is there a simpler, more direct method of reading elisp file into s-expression?

  • 1
    As @John Kitchin mentions, you can read text from a buffer. And working with buffers is generally preferred over working with strings, in Emacs Lisp. – Drew Jun 16 '17 at 13:53
  • FYI, there are related questions (some are near-duplicates) under tag read. Type [read] into the SE search field, to see them. – Drew Jun 16 '17 at 14:00
  • 1
    @Drew Indeed, this question is very closed to How should you read a Lisp file as Lisp for processing without condition-case? – Alex Vong Jun 16 '17 at 14:54
6

This is a better approach I think.

(with-current-buffer (find-file-noselect "foo.el")
  (goto-char (point-min))
  (read (current-buffer)))

If you want to wrap this as you describe below, and excecute it, try this:

(defmacro my-read (fname)
  `(progn
     ,(with-current-buffer (find-file-noselect fname)
    (goto-char (point-min))
    (read (current-buffer)))))

Both of these leave the file open.

Edit: The code above only reads one sexp, so it won't read the whole file unless it is wrapped in a single pair of parens. That is basically what your solution achieves below. This code below reads them all and closes the buffer. One issue with the code below is I had to wrap it in ignore-errors because read raises an error at the end of the file. I couldn't figure out a way to not get an error or handle that particular error.

(defmacro my-read (fname)
  (let ((sexps '())
    (sexp)
    (buf (find-file-noselect fname)))
    (with-current-buffer buf
      (goto-char (point-min))
      (ignore-errors
    (while (setq sexp (read buf))
      (message "%s" sexp)
      (push sexp sexps))))
    (kill-buffer buf)

    `(progn
       ,@(reverse sexps))))
  • 1
    I end up using another solution since I need to sandwich the content in the file with "(progn\n" and "\n)\n", but your idea of reading from a buffer is good, upvoted. – Alex Vong Jun 16 '17 at 15:02
  • I edited my answer to make it easier to wrap the code as you describe. – John Kitchin Jun 17 '17 at 14:20
  • Both of your macros expand into a constant value, which I don't think is intended. A function may be more appropriate. – politza Jun 17 '17 at 17:04
  • I think it depends on the goal after the read. If the goal is to eval it I think the macro is correct. If the goal is to further modify it then a function might be better. – John Kitchin Jun 17 '17 at 17:31
2

Based on John Kitchin's solution, I come up with the following solution which inserts the needed text before and after the file contents:

(with-temp-buffer
  (save-excursion
    (insert "(progn\n")
    (insert-file-contents "foo.el")
    (goto-char (point-max))
    (insert "\n)\n"))
  (read (current-buffer)))
  • Why do you not just use load? – Tobias Jun 17 '17 at 18:11
  • 1
    @Tobias: load both reads and evaluates the forms. – phils Jun 18 '17 at 2:04
  • @phils Right, sorry for the noise. – Tobias Jun 18 '17 at 3:45

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.