Pold
Pold

Reputation: 347

LISP File I/O - Extract and Convert Information

I have a file (furniture.lisp) that looks basically like this (with many more entries):

(addgv :furniture 'stove
  (make-instance 'stove
    :pose (tf:make-pose-stamped
            "map"                         ; frame-id
            0.0
            (tf:make-3d-vector -3.1 -0.9 0)     ; translation/origin
            (tf:euler->quaternion :az 0))))

(addgv :furniture 'drawers-cupboard
  (make-instance 'cupboard
    :pose (tf:make-pose-stamped
            "map"
            0.0
            (tf:make-3d-vector -3.1 0.1 0)
            (tf:euler->quaternion :az 0))))

Now, I'd like to have a function (get-locations "furniture.lisp" "locations.txt") that extracts the objects coordinates in the 3d-vector and writes its output to a file:

(location stove -3.1 -0.9 9)
(location drawers-cupboard -3.1 0.1 0)
...

I started by writing an expression that reads in the file (so far without parametrization) line by line:

(ql:quickload "split-sequence")

(with-open-file (stream "furniture.lisp")
(do ((line (read-line stream nil)
           (read-line stream nil)))
    ((null line))
    (princ (split-sequence::split-sequence #\Space line)) ; Just for demonstration
 ))

But I realized that I have no chance/idea to "connect" the name of the object (e.g. stove) and its coordinates. I'd need the second symbol after "(addgv " for the name and variable "distance of words" for the coordinates. So I tried to read the file into one big list:

(defun make-list-from-text (fn)
  (with-open-file (stream fn)
    (loop for line = (read-line stream nil nil)
          while line
          collect
               (split-sequence::split-sequence #\Space line))))

Whereby every line is a sublist (I don't know if this substructure is a advantage, perhaps I should 'flatten' the result). Now I'm stuck. Furthermore, I have the feeling, that my approach is somehow inelegant.

EDIT:

I followed Svante's approach and finally got the desired output! Besides creating a dummy package, I also had to create dummy exports for the package (e.g. :export :make-3d-vector). Additionally,:key #'car did not work, as my list was a 'mixed' list, consisting of sublists (e.g. (make-instance ...)) and symbols (e.g. addgv). So I created a helper function:

(defun find-helper (list-or-symbol)
    (if (listp list-or-symbol)
       (car list-or-symbol)
        list-or-symbol))

And replaced #'car by #'find-helper.

Upvotes: 4

Views: 262

Answers (3)

Svante
Svante

Reputation: 51501

My idea would be to create a dummy tf package, then read the forms and parse whatever you need from them. Something like this (untested):

(eval-when (:compile-toplevel :load-toplevel :execute)
  (unless (find-package #:tf)
    (defpackage #:tf)))

(defun extract-location-file ()
  (let ((*read-eval* nil))
    (with-open-file (in "furniture.lisp")
      (with-open-file (out "locations.txt"
                           :direction :output
                           :if-exists :supersede
                           :if-does-not-exist :create)
        (loop :for form := (read in nil)
              :while form
              :do (print (extract-location form) out)
                  (terpri)))))

(defun extract-location (form)
  `(location ,(third form)
             ,@(rest (find 'tf::make-3d-vector
                           (find 'tf::make-pose-stamped
                                 (find 'make-instance
                                       form
                                       :key #'car)
                                 :key #'car)
                           :key #'car))))

Be sure not to omit to bind *read-eval* to nil.

Upvotes: 1

Rainer Joswig
Rainer Joswig

Reputation: 139251

Unfortunately this would be a non-portable solution:

  • handle the reader error
  • do what is necessary to fix the problem
  • continue

For example in LispWorks I could do something like this (just a sketch):

CL-USER 60 > (defun test ()
               (handler-bind ((conditions:package-not-found-reader
                               (lambda (c)
                                 (continue c)))
                              (conditions:simple-reader-error
                               (lambda (c)
                                 (continue c))))
                     (read-from-string "'(foo27:bar19 bar18:foo44)")))
TEST

CL-USER 61 > (test)
(QUOTE (FOO27::BAR19 BAR18::FOO44))

It calls the continue restarts for the missing package error and then for the error that the symbol is not exported. The restarts create the package and the other one is returning a non-exported symbol...

Upvotes: 0

Vsevolod Dyomkin
Vsevolod Dyomkin

Reputation: 9451

The general way would be:

  1. read the whole file as string (see here)
  2. (cl-ppcre:regex-replace-all "tf::?" content ""), i.e. replace all references to package tf to avoid package-related errors
  3. put '() around the contents
  4. read it and assign to a variable
  5. now you have structured data that you can process using various list-manipulating functions

Upvotes: 0

Related Questions