Search code examples
packagecommon-lispreader

Why I can't read this file in Common Lisp?


I am trying to read files using Common Lisp with-open-file. In my environment, I am using SBCL, Emacs, and Slime.

I can open files like this one (bookmarks-to-read.lisp) using:

(defun read-my-file ()
  (with-open-file
   (stream "~/miscellaneous/misc/symbolic-computation/bookmarks-to-read.lisp")
    (let ((contents
            (read-all-objects stream (list '$eof$))))
      (format t "~&Read ~S objects from the file."
              (length contents))
      contents)))

(defun read-all-objects (stream eof-indicator)
  (let ((result (read stream nil eof-indicator)))
    (if (eq result eof-indicator)
        nil
        (cons result (read-all-objects stream eof-indicator)))))

After calling the function on the REPL I get the expected content:

CL-USER> (read-my-file)

Read 1 objects from the file.
(((:URL "https://calendar.google.com/calendar/u/0/r/week?pli=1" :TITLE
.
.
. 

(:URL "https://www.rescuetime.com/dashboard" :TITLE   
"RescueTime - Your Daily dashboard" :DATE
"2021-05-16T23:08:46.605691Z"    :TAGS ("rescue"))))

Note this is .lisp. However, If I try the same thing with this other file (history-to-read.lisp) I get an error.

I just need to change the location:

(defun read-my-file ()
  (with-open-file
   (stream "~/miscellaneous/misc/symbolic-computation/history-to-read.lisp")
    (let ((contents
            (read-all-objects stream (list '$eof$))))
      (format t "~&Read ~S objects from the file."
              (length contents))
      contents)))

(defun read-all-objects (stream eof-indicator)
  (let ((result (read stream nil eof-indicator)))
    (if (eq result eof-indicator)
        nil
        (cons result (read-all-objects stream eof-indicator))))

And then, after calling the function on the REPL:

CL-USER> (read-my-file) ; Evaluation aborted on
#<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003AB9273}>.

I get this error message:

Package HISTORY-TREE does not exist.

Line: 3, Column: 45, File-Position: 110

Stream: #<SB-SYS:FD-STREAM for "file /home/pedro/miscellaneous/misc/symbolic-computation/history-to-read.lisp" {1003AB83A3}> [Condition of type SB-INT:SIMPLE-READER-PACKAGE-ERROR]

"HISTORY-TREE" is just something that was written on the text. Like foo.

In addition, I tryied to reproduce the suggestion described by @RainerJoswig here. Unfortunately, the same problem happened.

1 - Since both files are .lisp and the function to read them is the same, why is this happening?

2 - Why is the meaning/content of the text important here?

3 - And how can I read this file?


Solution

  • Package HISTORY-TREE does not exist

    That message is clear: the package HISTORY-TREE does not exist. But you try to read symbols into this non-existent package, for example HISTORY-TREE:OWNER.

    Every package which is mentioned in a printed symbol in that file needs to exist, before one can successfully read the file.

    The default Common Lisp reader does not create packages on the fly by just reading symbols.

    Example:

    CL-USER 130 > (read-from-string "bar::foo")
    
    Error: Reader cannot find package BAR.
      1 (continue) Create the BAR package.
      2 Use another package instead of BAR.
      3 Try finding package BAR again.
      4 (abort) Return to top loop level 0.
    
    Type :b for backtrace or :c <option number> to proceed.
    Type :bug-form "<subject>" for a bug report template or :? for other options.
    

    now let's use the error restart to create that package:

    CL-USER 131 : 1 > :c 1
    BAR::FOO
    8
    

    Alternative solutions:

    1. create the packages before reading the data
    2. don't print symbols with package prefixes
    3. use a custom reader, which can create packages or marks symbols as having an undefined package

    Remarks

    (defun read-all-objects (stream eof-indicator)
      (let ((result (read stream nil eof-indicator)))
        (if (eq result eof-indicator)
            nil
            (cons result (read-all-objects stream eof-indicator))))
    

    One could for example write something like this:

    (defun read-all-objects (stream &aux (eof '#:eof))
      (loop for item = (read stream nil eof)
            until (eq item eof)
            collect item))
    

    Benefits:

    • there is no EOF argument needed, here we use an uninterned symbol
    • the number of list items is not limited by the stack depth