Search code examples
common-lisp

Getting a setf-able place from a nested plist tree?


I've got a nested plist structure, for example:

(:title "A title"
 :repeat (:row    #(:a :b :c)
          :column #(:c :a :b))
 :spec (:data my-data
        :late t))

and I need to set :data to a different value. The challange is that this key may appear anywhere in the tree, possibly even deeper in the tree than this example. It will only appear once. I know about the access library, but can't use it. I can find the key easy enough using a recursive search:

(defun find-in-tree (item tree &key (test #'eql))
           (labels ((find-in-tree-aux (tree)
                      (cond ((funcall test item tree)
                             (return-from find-in-tree tree))
                            ((consp tree)
                             (find-in-tree-aux (car tree))
                             (find-in-tree-aux (cdr tree))))))
             (find-in-tree-aux tree)))

But I can't quite work out if there's any way to get the place when it's nested in the tree. Ideally something like:

(setf (find-place-in-tree :data tree) 'foo)

is what I'm after.

Any ideas?


Solution

  • I could not work out your recursive searcher so I wrote a simpler one, which also solves the 'item is present but value is nil' in the usual way:

    (defun find-in-tree (item tree &key (test #'eql))
      ;; really just use iterate here  
      (labels ((fit-loop (tail)
                 (cond 
                  ((null tail)
                   ;; not there
                   (return-from find-in-tree (values nil nil)))
                  ((null (rest tail))
                   ;; not a plist
                   (error "botched plist"))
                  (t
                   (destructuring-bind (this val . more) tail
                     (cond
                      ((funcall test this item)
                       ;; gotit
                       (return-from find-in-tree (values val t)))
                      ((consp val)
                       ;; Search in the value if it's a list
                       (fit-loop val)
                       (fit-loop more))
                      (t
                       ;; just keep down this list
                       (fit-loop more))))))))
        (fit-loop tree)))
    

    Given that the setf function is essentially trivial if you don't want it to add entries (which it can not always do anyway):

    (defun (setf find-in-tree) (new item tree &key (test #'eql))
      ;; really just use iterate here  
      (labels ((fit-loop (tail)
                 (cond 
                  ((null tail)
                   (error "not in tree"))
                  ((null (rest tail))
                   (error "botched plist"))
                  (t
                   (destructuring-bind (this val . more) tail
                     (cond
                      ((funcall test this item)
                       (return-from find-in-tree
                         (car (setf (cdr tail) (cons new more)))))
                      ((consp val)
                       (fit-loop val)
                       (fit-loop more))
                      (t
                       (fit-loop more))))))))
        (fit-loop tree)))