4

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?

Drew
  • 75,699
  • 9
  • 109
  • 225
Alex Vong
  • 161
  • 5
  • 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?](https://emacs.stackexchange.com/questions/32811/how-should-you-read-a-lisp-file-as-lisp-for-processing-without-condition-case) – Alex Vong Jun 16 '17 at 14:54

2 Answers2

7

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))))
John Kitchin
  • 11,555
  • 1
  • 19
  • 41
  • 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)))
Alex Vong
  • 161
  • 5