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?

3
  • 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
    Commented Jun 16, 2017 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
    Commented Jun 16, 2017 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
    Commented Jun 16, 2017 at 14:54

2 Answers 2

8

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))))
4
  • 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
    Commented Jun 16, 2017 at 15:02
  • I edited my answer to make it easier to wrap the code as you describe. Commented Jun 17, 2017 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
    Commented Jun 17, 2017 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. Commented Jun 17, 2017 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)))
5
  • Why do you not just use load?
    – Tobias
    Commented Jun 17, 2017 at 18:11
  • 1
    @Tobias: load both reads and evaluates the forms.
    – phils
    Commented Jun 18, 2017 at 2:04
  • @phils Right, sorry for the noise.
    – Tobias
    Commented Jun 18, 2017 at 3:45
  • Shouldn't (read (current-buffer)) be (read (buffer-string))?
    – vfclists
    Commented Nov 6, 2020 at 21:48
  • @vfclists, (read (current-buffer)) because read can read from a stream, and buffers work as a stream. Commented Oct 25, 2022 at 0:02

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.