Search code examples
common-lispfile-readclos

From file stream to assoc-list in common lisp


I have a file that starts with (defparameter *myfile* '(((KEY 1) (A1 CAN) (A2 4) (SUR (((BCZ S) (FEATS NIL)) (DIR FS) (LADOM ALL) (((NNEW S) (FEATS NIL)) (DIR BS) (LADOM ALL) ((NNEW NP) (FEATS ((BIG NOM))))))) (SEM (LAM P (P "CAN"))) (PARAM 1.0)) ((KEY 2) (A1 KEDIYI) (A2 4)...........and goes on like this.

I am guessing this is a CLOS but it is stored in a file. I need to be able to get this data in an assoc-list to reach the A1 or A2 etc. as keys to obtain their values afterwards. What I do now is reading the file line by line and do string operations on it. But I think it is really a bad practice. Here is my code for now;

(defun open_ded (path)
 (defvar last_id 0)
 (let ((in (open path :if-does-not-exist nil)))
  (when in
    (loop for line = (read-line in nil)
        while line 
            do 
                (if (setq key_id (findkeyid line)) ;search "KEY" and return its id value
                (setq last_id key_id)) ;if it is not nil, set it to last_id

And I know that I can get the whole file with (defparameter *s* (open "path")) but the when I want to do (assoc 'A1 (read *s*)) or (assoc 'KEY (read *s*)) it gets me nowhere. Do you have any ideas about how to achieve this?


Solution

  • You can use the built-in function load to read the file:

    (load "/tmp/data.lisp")
    

    This will set variable *myfile* so you can then do:

    * (print *myfile*)
    
    (((KEY 1) (A1 CAN) (A2 4)
      (SUR
       (((BCZ S) (FEATS NIL)) (DIR FS) (LADOM ALL)
        (((NNEW S) (FEATS NIL)) (DIR BS) (LADOM ALL)
         ((NNEW NP) (FEATS ((BIG NOM)))))))
      (SEM (LAM P (P "CAN"))) (PARAM 1.0))
     ((KEY 2) (A1 KEDIYI) (A2 4)))
    (((KEY 1) (A1 CAN) (A2 4)
      (SUR
       (((BCZ S) (FEATS NIL)) (DIR FS) (LADOM ALL)
        (((NNEW S) (FEATS NIL)) (DIR BS) (LADOM ALL)
         ((NNEW NP) (FEATS ((BIG NOM)))))))
      (SEM (LAM P (P "CAN"))) (PARAM 1.0))
     ((KEY 2) (A1 KEDIYI) (A2 4)))
    
    * (loop for i from 0
            for entry in *myfile*
            do (format t "Entry #~D: ~S~%" i entry))
    Entry #0: ((KEY 1) (A1 CAN) (A2 4)
                 (SUR
                  (((BCZ S) (FEATS NIL)) (DIR FS) (LADOM ALL)
                   (((NNEW S) (FEATS NIL)) (DIR BS) (LADOM ALL)
                    ((NNEW NP) (FEATS ((BIG NOM)))))))
                 (SEM (LAM P (P "CAN"))) (PARAM 1.0))
    Entry #1: ((KEY 2) (A1 KEDIYI) (A2 4))
    NIL
    
    * (dolist (entry *myfile*)
         (print (assoc 'key entry)))
    
    (KEY 1)
    (KEY 2)
    NIL
    

    If you don't trust the file contents, and you want to read the source code in the file but not execute it (which load does), you would use read. In that case also bind *read-eval* to nil to safeguard against uses of #. that would otherwise execute code while being read:

    (with-open-file (f "/tmp/data.lisp")
      (let ((*read-eval* nil))
        (loop for form = (read f nil nil)
              while form
              do (print form)))
    

    The file contents looks like a collection of entries, with each entry a list of key-value pairs. There is nothing in it that connects it to CLOS, although you could define a structure or class named entry with slot names key, bcz, feats et cetera and then use accessors like (entry-bcz x) instead of (second (assoc 'bcz x)). This might help readability and efficiency, but to do that you need to create objects from this list-based data representation.