emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/dict-tree 87bb7e8 019/154: Allow custom write and load


From: Stefan Monnier
Subject: [elpa] externals/dict-tree 87bb7e8 019/154: Allow custom write and load functions when saving and dumping dictionaries;
Date: Mon, 14 Dec 2020 12:21:35 -0500 (EST)

branch: externals/dict-tree
commit 87bb7e83a958f4f735d657401b25c048d764c4bd
Author: Toby Cubitt <toby-predictive@dr-qubit.org>
Commit: tsc25 <toby-predictive@dr-qubit.org>

    Allow custom write and load functions when saving and dumping dictionaries;
    Changed meta-data to property lists;
    Minor enhancements and bug-fixes.
---
 dict-tree.el | 1033 ++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 641 insertions(+), 392 deletions(-)

diff --git a/dict-tree.el b/dict-tree.el
index c8c5c0b..f747fd0 100644
--- a/dict-tree.el
+++ b/dict-tree.el
@@ -257,6 +257,9 @@ If START or END is negative, it counts from the end."
                  lookup-cache-threshold
                  complete-cache-threshold
                  complete-ranked-cache-threshold
+                 key-savefun key-loadfun
+                 data-savefun data-loadfun
+                 plist-savefun plist-loadfun
                  trie-type
                  &aux
                  (modified nil)
@@ -295,9 +298,12 @@ If START or END is negative, it counts from the end."
                  lookup-cache-threshold
                  complete-cache-threshold
                  complete-ranked-cache-threshold
+                 key-savefun key-loadfun
+                 data-savefun data-loadfun
+                 plist-savefun plist-loadfun
                  &key
                  createfun insertfun deletefun lookupfun mapfun emptyfun
-                 stackfun popfun stackemptyfun
+                 stack-createfun stack-popfun stack-emptyfun
                  &aux
                  (modified nil)
                  (trie (trie-create-custom comparison-function
@@ -307,9 +313,9 @@ If START or END is negative, it counts from the end."
                                     :lookupfun lookupfun
                                     :mapfun mapfun
                                     :emptyfun emptyfun
-                                    :stackfun stackfun
-                                    :popfun popfun
-                                    :stackemptyfun stackemptyfun))
+                                    :stack-createfun stack-createfun
+                                    :stack-popfun stack-popfun
+                                    :stack-emptyfun stack-emptyfun))
                  (insfun (eval (macroexpand
                                 `(dictree--wrap-insfun ,insert-function))))
                  (rankfun (eval (macroexpand
@@ -335,6 +341,9 @@ If START or END is negative, it counts from the end."
   lookup-cache lookup-cache-threshold
   complete-cache complete-cache-threshold
   complete-ranked-cache complete-ranked-cache-threshold
+  key-savefun key-loadfun
+  data-savefun data-loadfun
+  plist-savefun plist-loadfun
   trie meta-dict-list)
 
 
@@ -396,47 +405,43 @@ If START or END is negative, it counts from the end."
 
 
 
-(defmacro dictree--wrap-data (data &optional meta-data)
+(defmacro dictree--cell-create (data &optional meta-data)
   ;; INTERNAL USE ONLY
   ;; wrap the data in a cons cell
   `(cons ,data ,meta-data))
 
 ;; get data component from data cons cell
-(defalias 'dictree--unwrap-data 'car)  ; INTERNAL USE ONLY
-
-;; set data component of data cons cell
-(defalias 'dictree--set-data 'setcar)  ; INTERNAL USE ONLY
+(defmacro dictree--cell-data (cell)  ; INTERNAL USE ONLY
+  `(car ,cell))
 
 ;; get meta-data component of data cons cell
-(defalias 'dictree--unwrap-metadata 'cdr)  ; INTERNAL USE ONLY
-
-;; set meta-data component of data cons cell
-(defalias 'dictree--set-metadata 'setcdr)  ; INTERNAL USE ONLY
-
+(defmacro dictree--cell-plist (cell)  ; INTERNAL USE ONLY
+  `(cdr ,cell))
 
 (defmacro dictree--wrap-insfun (insfun)  ; INTERNAL USE ONLY
   ;; return wrapped insfun to deal with data wrapping
   `(lambda (new old)
-     (dictree--set-data old (,insfun (dictree--unwrap-data new)
-                                    (dictree--unwrap-data old)))
+     (setf (dictree--cell-data old)
+          (,insfun (dictree--cell-data new)
+                   (dictree--cell-data old)))
      old))
 
 (defmacro dictree--wrap-rankfun (rankfun)  ; INTERNAL USE ONLY
   ;; return wrapped rankfun to deal with data wrapping
   `(lambda (a b)
-     (,rankfun (cons (car a) (dictree--unwrap-data (cdr a)))
-              (cons (car b) (dictree--unwrap-data (cdr b))))))
+     (,rankfun (cons (car a) (dictree--cell-data (cdr a)))
+              (cons (car b) (dictree--cell-data (cdr b))))))
 
 (defmacro dictree--wrap-filter (filter)  ; INTERNAL USE ONLY
   ;; return wrapped filter function to deal with data wrapping
-  `(lambda (key data) (,filter key (dictree--unwrap-data data))))
+  `(lambda (key data) (,filter key (dictree--cell-data data))))
 
 (defmacro dictree--wrap-combfun (combfun)  ; INTERNAL USE ONLY
   `(lambda (cell1 cell2)
-     (cons (,combfun (dictree--unwrap-data cell1)
-                    (dictree--unwrap-data cell2))
-          (append (list (dictree--unwrap-metadata cell1))
-                  (list (dictree--unwrap-metadata cell2))))))
+     (cons (,combfun (dictree--cell-data cell1)
+                    (dictree--cell-data cell2))
+          (append (list (dictree--cell-metadata cell1))
+                  (list (dictree--cell-metadata cell2))))))
 
 ;; Construct and return a completion cache entry
 (defalias 'dictree--cache-create 'cons)  ; INTERNAL USE ONLY
@@ -524,6 +529,9 @@ If START or END is negative, it counts from the end."
    lookup-cache-threshold
    complete-cache-threshold
    complete-ranked-cache-threshold
+   key-savefun key-loadfun
+   data-savefun data-loadfun
+   plist-savefun plist-loadfun
    trie-type)
   "Create an empty dictionary and return it.
 
@@ -533,37 +541,35 @@ extension. (Regardless of the value of NAME, the 
dictionary will
 be stored in the default variable name when it is reloaded from
 file.)
 
-Optional argument FILENAME supplies a directory and file name to
-use when saving the dictionary. If the AUTOSAVE flag is non-nil,
-then the dictionary will automatically be saved to this file when
-it is unloaded or when exiting Emacs.
-
-If optional argument UNLISTED is non-nil, the dictionary will not
-be added to the list of loaded dictionaries. Note that this
-disables autosaving.
-
-Optional argument COMPARE-FUNCTION sets the function used to
-compare elements of the keys. It should take two arguments, A and
-B, both of the type contained by the sequences used as keys
-\(e.g. if the keys will be strings, the function will be passed
-two characters\). It should return t if the first is \"less
-than\" the second. Defaults to `<'.
-
-Optional argument INSERT-FUNCTION sets the function used to
-insert data into the dictionary. It should take two arguments:
-the new data, and the data already in the dictionary, and should
-return the data to insert. Defaults to replacing any existing
-data with the new data.
-
-Optional argument RANK-FUNCTION sets the function used to rank
-the results of `dictree-complete'. It should take two arguments,
-each a cons whose car is a dictree key (a sequence) and whose cdr
-is the data associated with that key. It should return non-nil if
-the first argument is \"better\" than the second, nil
-otherwise. It defaults to \"lexical\" comparison of the keys,
-ignoring the data \(which is not very useful, since the
-`dictree-complete' function already does this much more
-efficiently\).
+FILENAME supplies a directory and file name to use when saving
+the dictionary. If the AUTOSAVE flag is non-nil, then the
+dictionary will automatically be saved to this file when it is
+unloaded or when exiting Emacs.
+
+If UNLISTED is non-nil, the dictionary will not be added to the
+list of loaded dictionaries. Note that this disables autosaving.
+
+COMPARE-FUNCTION sets the function used to compare elements of
+the keys. It should take two arguments, A and B, both of the type
+contained by the sequences used as keys \(e.g. if the keys will
+be strings, the function will be passed two characters\). It
+should return t if the first is \"less than\" the
+second. Defaults to `<'.
+
+INSERT-FUNCTION sets the function used to insert data into the
+dictionary. It should take two arguments: the new data, and the
+data already in the dictionary, and should return the data to
+insert. Defaults to replacing any existing data with the new
+data.
+
+RANK-FUNCTION sets the function used to rank the results of
+`dictree-complete'. It should take two arguments, each a cons
+whose car is a dictree key (a sequence) and whose cdr is the data
+associated with that key. It should return non-nil if the first
+argument is \"better\" than the second, nil otherwise. It
+defaults to \"lexical\" comparison of the keys, ignoring the data
+\(which is not very useful, since an unranked `dictree-complete'
+query already does this much more efficiently\).
 
 CACHE-POLICY should be a symbol (time or length), which
 determines which query operations are cached. The former caches
@@ -589,6 +595,29 @@ for that type of query. If it is t, everything is cached 
for that
 type of query \(similar behaviour can be obtained by setting the
 CACHE-THRESHOLD to 0, but it is better to use t\).
 
+KEY-SAVEFUN, DATA-SAVEFUN and PLIST-SAVEFUN are functions used to
+convert keys, data and property lists into lisp objects that have
+a valid read syntax, for writing to file. DATA-SAVEFUN and
+PLIST-SAVEFUN are used when saving the dictionary (see
+`dictree-save' and `dictree-write'), and all three functions are
+used when dumping the contents of the dictionary \(see
+`dictree-dump-to-buffer' and `dictree-dump-to-file'\).
+KEY-SAVEFUN, DATA-SAVEFUN and PLIST-SAVEFUN should each accept
+one argument: a key, data or property list from DICT,
+respectively. They should return a lisp object which has a valid
+read syntax. When defining these functions, be careful not to
+accidentally modify the lisp object in the dictionary; usually,
+you will need to make a copy before converting it.
+
+KEY-LOADFUN, DATA-LOADFUN and PLIST-LOADFUN are used to convert
+keys, data and property lists back again when loading a
+dictionary (only DATA-LOADFUN and PLIST-LOADFUN, see
+`dictree-save' and `dictree-write') or populating it from a
+file (all three, see `dictree-populate-from-file'). They should
+accept one argument: a lisp object of the type produced by the
+corresponding SAVEFUN, and return a lisp object to use in the
+loaded dictionary.
+
 TRIE-TYPE sets the type of trie to use as the underlying data
 structure. See `trie-create' for details."
 
@@ -610,6 +639,9 @@ structure. See `trie-create' for details."
          lookup-cache-threshold
          complete-cache-threshold
          complete-ranked-cache-threshold
+         key-savefun key-loadfun
+         data-savefun data-loadfun
+         plist-savefun plist-loadfun
          trie-type)))
     ;; store dictionary in variable NAME
     (when name (set name dict))
@@ -629,8 +661,11 @@ structure. See `trie-create' for details."
    lookup-cache-threshold
    complete-cache-threshold
    complete-ranked-cache-threshold
+   key-savefun key-loadfun
+   data-savefun data-loadfun
+   plist-savefun plist-loadfun
    createfun insertfun deletefun lookupfun mapfun emptyfun
-   stackfun popfun stackemptyfun)
+   stack-createfun stack-popfun stack-emptyfun)
   "Create an empty dictionary and return it.
 
 If NAME is supplied, the dictionary is stored in the variable
@@ -639,37 +674,35 @@ extension. (Regardless of the value of NAME, the 
dictionary will
 be stored in the default variable name when it is reloaded from
 file.)
 
-Optional argument FILENAME supplies a directory and file name to
-use when saving the dictionary. If the AUTOSAVE flag is non-nil,
-then the dictionary will automatically be saved to this file when
-it is unloaded or when exiting Emacs.
-
-If optional argument UNLISTED is non-nil, the dictionary will not
-be added to the list of loaded dictionaries. Note that this
-disables autosaving.
-
-Optional argument COMPARE-FUNCTION sets the function used to
-compare elements of the keys. It should take two arguments, A and
-B, both of the type contained by the sequences used as keys
-\(e.g. if the keys will be strings, the function will be passed
-two characters\). It should return t if the first is \"less
-than\" the second. Defaults to `<'.
-
-Optional argument INSERT-FUNCTION sets the function used to
-insert data into the dictionary. It should take two arguments:
-the new data, and the data already in the dictionary, and should
-return the data to insert. Defaults to replacing any existing
-data with the new data.
-
-Optional argument RANK-FUNCTION sets the function used to rank
-the results of `dictree-complete'. It should take two arguments,
-each a cons whose car is a dictree key (a sequence) and whose cdr
-is the data associated with that key. It should return non-nil if
-the first argument is \"better\" than the second, nil
-otherwise. It defaults to \"lexical\" comparison of the keys,
-ignoring the data \(which is not very useful, since the
-`dictree-complete' function already does this much more
-efficiently\).
+FILENAME supplies a directory and file name to use when saving
+the dictionary. If the AUTOSAVE flag is non-nil, then the
+dictionary will automatically be saved to this file when it is
+unloaded or when exiting Emacs.
+
+If UNLISTED is non-nil, the dictionary will not be added to the
+list of loaded dictionaries. Note that this disables autosaving.
+
+COMPARE-FUNCTION sets the function used to compare elements of
+the keys. It should take two arguments, A and B, both of the type
+contained by the sequences used as keys \(e.g. if the keys will
+be strings, the function will be passed two characters\). It
+should return t if the first is \"less than\" the
+second. Defaults to `<'.
+
+INSERT-FUNCTION sets the function used to insert data into the
+dictionary. It should take two arguments: the new data, and the
+data already in the dictionary, and should return the data to
+insert. Defaults to replacing any existing data with the new
+data.
+
+RANK-FUNCTION sets the function used to rank the results of
+`dictree-complete'. It should take two arguments, each a cons
+whose car is a dictree key (a sequence) and whose cdr is the data
+associated with that key. It should return non-nil if the first
+argument is \"better\" than the second, nil otherwise. It
+defaults to \"lexical\" comparison of the keys, ignoring the data
+\(which is not very useful, since the `dictree-complete' function
+already does this much more efficiently\).
 
 CACHE-POLICY should be a symbol (time or length), which
 determines which query operations are cached. The former caches
@@ -695,6 +728,29 @@ that type of query. If it is t, everything is cached for 
that
 type of query \(similar behaviour can be obtained by setting the
 CACHE-THRESHOLD to 0, but it is better to use t\).
 
+KEY-SAVEFUN, DATA-SAVEFUN and PLIST-SAVEFUN are functions used to
+convert keys, data and property lists into lisp objects that have
+a valid read syntax, for writing to file. DATA-SAVEFUN and
+PLIST-SAVEFUN are used when saving the dictionary (see
+`dictree-save' and `dictree-write'), and all three functions are
+used when dumping the contents of the dictionary \(see
+`dictree-dump-to-buffer' and `dictree-dump-to-file'\).
+KEY-SAVEFUN, DATA-SAVEFUN and PLIST-SAVEFUN should each accept
+one argument: a key, data or property list from DICT,
+respectively. They should return a lisp object which has a valid
+read syntax. When defining these functions, be careful not to
+accidentally modify the lisp object in the dictionary; usually,
+you will need to make a copy before converting it.
+
+KEY-LOADFUN, DATA-LOADFUN and PLIST-LOADFUN are used to convert
+keys, data and property lists back again when loading a
+dictionary (only DATA-LOADFUN and PLIST-LOADFUN, see
+`dictree-save' and `dictree-write') or populating it from a
+file (all three, see `dictree-populate-from-file'). They should
+accept one argument: a lisp object of the type produced by the
+corresponding SAVEFUN, and return a lisp object to use in the
+loaded dictionary.
+
 The remaining arguments determine the type of trie to use as the
 underlying data structure. See `trie-create' for details."
 
@@ -716,15 +772,18 @@ underlying data structure. See `trie-create' for details."
          lookup-cache-threshold
          complete-cache-threshold
          complete-ranked-cache-threshold
+         key-savefun key-loadfun
+         data-savefun data-loadfun
+         plist-savefun plist-loadfun
          :createfun createfun
          :insertfun insertfun
          :deletefun deletefun
          :lookupfun lookupfun
          :mapfun mapfun
          :emptyfun emptyfun
-         :stackfun stackfun
-         :popfun popfun
-         :stackemptyfun stackemptyfun)))
+         :stack-createfun stack-createfun
+         :stack-popfun stack-popfun
+         :stack-emptyfun stack-emptyfun)))
     ;; store dictionary in variable NAME
     (when name (set name dict))
     ;; add it to loaded dictionary list, unless it's unlisted
@@ -835,7 +894,7 @@ The other arguments are as for `dictree-create'."
        (setf (dictree--meta-dict-name ,dict) ,name)
     (setf (dictree--name ,dict) ,name)))
 
-(defsubst dictree-filename (dict)
+(defun dictree-filename (dict)
   "Return dictionary DICT's associated file name."
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-filename dict)
@@ -876,7 +935,7 @@ The other arguments are as for `dictree-create'."
   'dictree--meta-dict-dictlist
   "Return the list of constituent dictionaries for meta-dictionary DICT.")
 
-(defsubst dictree-lookup-cache-threshold (dict)
+(defun dictree-lookup-cache-threshold (dict)
   "Return the lookup cache threshold for dictionary DICT."
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-lookup-cache-threshold dict)
@@ -888,13 +947,13 @@ The other arguments are as for `dictree-create'."
        (setf (dictree--meta-dict-lookup-cache-threshold ,dict) ,param)
      (setf (dictree--lookup-cache-threshold ,dict) ,param)))
 
-(defsubst dictree-lookup-cache (dict)
+(defun dictree-lookup-cache (dict)
   ;; Return the lookup cache for dictionary DICT.
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-lookup-cache dict)
     (dictree--lookup-cache dict)))
 
-(defsubst dictree-complete-cache-threshold (dict)
+(defun dictree-complete-cache-threshold (dict)
   "Return the completion cache threshold for dictionary DICT."
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-complete-cache-threshold dict)
@@ -906,13 +965,13 @@ The other arguments are as for `dictree-create'."
        (setf (dictree--meta-dict-complete-cache-threshold ,dict) ,param)
      (setf (dictree--complete-cache-threshold ,dict) ,param)))
 
-(defsubst dictree-complete-cache (dict)
+(defun dictree-complete-cache (dict)
   ;; Return the completion cache for dictionary DICT.
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-complete-cache dict)
     (dictree--complete-cache dict)))
 
-(defsubst dictree-complete-ranked-cache-threshold (dict)
+(defun dictree-complete-ranked-cache-threshold (dict)
   "Return the ranked completion cache threshold for dictionary DICT."
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-complete-ranked-cache-threshold dict)
@@ -924,7 +983,7 @@ The other arguments are as for `dictree-create'."
        (setf (dictree--meta-dict-complete-ranked-cache-threshold ,dict) ,param)
      (setf (dictree--complete-ranked-cache-threshold ,dict) ,param)))
 
-(defsubst dictree-complete-ranked-cache (dict)
+(defun dictree-complete-ranked-cache (dict)
   ;; Return the ranked completion cache for dictionary DICT.
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-complete-ranked-cache dict)
@@ -973,15 +1032,10 @@ inserted depends on the dictionary's insertion function 
\(see
 `dictree-create'\).
 
 The optional INSERT-FUNCTION over-rides the dictionary's own
-insertion function. It should take two arguments: the data DATA,
-and the data associated with KEY in the dictionary (nil if none
-already exists). It should return the data to insert."
-  ;; make sure SEQUENCE is a sequence
-  (when (not (sequencep key))
-    (error "Wrong argument type stringp, %s"
-          (prin1-to-string key)))
-  (when (not (dictree-p dict))
-    (error "Wrong argument type dictree-p"))
+insertion function. If KEY already exists in DICT,
+INSERT-FUNCTION is called with two arguments: the data DATA, and
+the data associated with KEY in the dictionary. Its return value
+becomes the new association for KEY."
 
   ;; if dictionary is a meta-dictionary, insert key into all the
   ;; dictionaries it's based on
@@ -997,7 +1051,7 @@ already exists). It should return the data to insert."
       ;; insert key in dictionary's ternary search tree
       (setq newdata
            (trie-insert
-            (dictree--trie dict) key (dictree--wrap-data data)
+            (dictree--trie dict) key (dictree--cell-create data)
             (or (and insert-function
                      (eval (macroexpand
                             `(dictree--wrap-insfun ,insert-function))))
@@ -1009,26 +1063,40 @@ already exists). It should return the data to insert."
            (dictree--meta-dict-list dict))
 
       ;; return the new data
-      (dictree--unwrap-data newdata))))
+      (dictree--cell-data newdata))))
 
 
 
-(defun dictree-delete (dict key)
+(defun dictree-delete (dict key &optional test)
   "Delete KEY from DICT.
-Returns non-nil if KEY was deleted, nil if KEY was not in DICT."
+Returns non-nil if KEY was deleted, nil if KEY was not in DICT.
+
+If TEST is supplied, it should be a function that accepts three
+arguments: the key being deleted, its associated data, and its
+associated property list. The key will then only be deleted if
+TEST returns non-nil."
 
-  (let (deleted)
+  (let ((dictree--delete-test test)
+       deleted del)
     (cond
      ;; if DICT is a meta-dictionary, delete KEY from all dictionaries
      ;; it's based on
      ((dictree--meta-dict-p dict)
       (dolist (dic (dictree--meta-dict-dictlist dict))
-       (setq deleted (or deleted (dictree-delete dic key))))
-      (setf (dictree-modified dict) (and deleted t)))
+       (when (setq del (dictree-delete dic key))
+         (setq deleted (cons del deleted))))
+      (setf (dictree-modified dict) (and deleted t))
+      (setq deleted (nreverse deleted)))
 
      ;; otherwise...
      (t
-      (setq deleted (trie-delete (dictree--trie dict) key))
+      (setq deleted
+           (trie-delete (dictree--trie dict) key
+                        (when dictree--delete-test
+                          (lambda (k cell)
+                            (funcall dictree--delete-test
+                                     k (dictree--cell-data cell)
+                                     (dictree--cell-plist cell))))))
       ;; if key was deleted, have to update the caches
       (when deleted
        (dictree-update-cache dict key nil t)
@@ -1040,7 +1108,7 @@ Returns non-nil if KEY was deleted, nil if KEY was not in 
DICT."
 
     ;; return deleted key/data pair
     (when deleted
-      (cons (car deleted) (dictree--unwrap-data (cdr deleted))))))
+      (cons (car deleted) (dictree--cell-data (cdr deleted))))))
 
 
 
@@ -1168,7 +1236,7 @@ to be distinguished from an element with a null 
association. (See
 also `dictree-member-p' for testing existence alone.)"
   (let ((data (dictree--lookup dict key nilflag)))
     (unless (eq data nilflag)
-      (dictree--unwrap-data data))))
+      (dictree--cell-data data))))
 
 
 (defalias 'dictree-member 'dictree-lookup)
@@ -1236,45 +1304,97 @@ also `dictree-member-p' for testing existence alone.)"
 ;; ----------------------------------------------------------------
 ;;                 Getting and setting meta-data
 
-(defun dictree-set-meta-data (dict key meta-data)
-  "Set meta-data for KEY in dictionary DICT.
-Returns META-DATA if successful, nil if KEY was not found in
-DICT.
+(defun dictree-put-property (dict key property value)
+  "Set PROPERTY for KEY in dictionary DICT.
+PROPERTY should be a symbol. Returns VALUE if successful, nil if
+KEY was not found in DICT.
 
-Note that if DICT is a meta-dictionary, then this will set the
-meta-data for KEY in *all* its constituent dictionaries.
+Note that if DICT is a meta-dictionary, then this will set KEY's
+PROPERTY to VALUE in *all* its constituent dictionaries.
 
 Unlike the data associated with a key (cf. `dictree-insert'),
-meta-data is not included in the results of queries on the
+properties are not included in the results of queries on the
 dictionary \(`dictree-lookup', `dictree-complete',
-`dictree-complete-ordered'\), nor does it affect the outcome of
-any of the queries. It merely serves to tag a key with some
+`dictree-complete-ordered'\), nor do they affect the outcome of
+any of the queries. They merely serves to tag a key with some
 additional information, and can only be retrieved using
-`dictree-lookup-meta-data'."
+`dictree-get-property'."
+
+  ;; sort out arguments
+  (when (symbolp dict) (setq dict (eval dict)))
   (cond
+   ;; set PROPERTY for KEY in all constituent dicts of a meta-dict
    ((dictree--meta-dict-p dict)
-    (warn "Setting meta-data in all constituent dictionaries of a meta-dict")
-    (setf (dictree-modified dict) t)
-    (mapc 'dictree-set-meta-data (dictree--meta-dict-dictlist dict)))
-   (t
+    (warn "Setting %s property for key %s in all constituent dictionaries\
+ of meta-dicttionary %s" property key (dictree-name dict))
     (setf (dictree-modified dict) t)
+    (let (dictree--put-property-ret)
+      (mapc (lambda (dic k p v)
+             (setq dictree--put-property-ret
+                   (or dictree--put-property-ret
+                       (dictree-put-property dic k p v))))
+           (dictree--meta-dict-dictlist dict))
+      ;; return VALUE if KEY was found in at least one constituent dict
+      dictree--put-property-ret))
+   (t  ;; set PROPERTY for KEY in normal dict
     (let ((cell (trie-member (dictree--trie dict) key)))
-      (when cell (dictree--set-metadata cell meta-data))))))
+      (when cell
+       (setf (dictree-modified dict) t)
+       (setf (dictree--cell-plist cell)
+             (plist-put (dictree--cell-plist cell) property value))
+       value)))  ; return VALUE
+   ))
 
 
 
-(defun dictree-get-meta-data (dict key &optional nilflag)
-  "Return the meta-data associated with KEY in dictionary DICT,
-or nil if KEY is not in the dictionary.
+(defun dictree-delete-property (dict key property)
+  "Delete PROPERTY from KEY in dictionary DICT.
+Returns the new property list for KEY, with PROPERTY deleted.
+
+Setting PROPERTY to nil using `dictree-put-property' is not quite
+the same thing as deleting it, since null property values can
+still be detected by supplying the optional argument to
+`dictree-get-propery' (which see).
+
+Note that if DICT is a meta-dictionary, then this will delete
+KEY's PROPERTY in *all* its constituent dictionaries."
+  ;; sort out arguments
+  (when (symbolp dict) (setq dict (eval dict)))
+  (cond
+   ;; delete PROPERTY from KEY in all constituent dicts of a meta-dict
+   ((dictree--meta-dict-p dict)
+    (warn "Deleting %s property from key %s in all constituent dictionaries\
+ of meta-dicttionary %s" property key (dictree-name dict))
+    (setf (dictree-modified dict) t)
+    (mapcar (lambda (dic k p) (dictree-delete-property dic k p))
+           (dictree--meta-dict-dictlist dict)))
+   (t  ;; delete PROPERTY from KEY in normal dict
+    (let* ((cell (trie-member (dictree--trie dict) key))
+          plist tail tail)
+      (when (and cell
+                (setq tail
+                      (plist-member (setq plist (dictree--cell-plist cell))
+                                    property)))
+       (setf (dictree-modified dict) t)
+       ;; delete property and value from plist
+       (setcdr tail (cddr tail))
+       (setq plist (delq property plist))
+       (setf (dictree--cell-plist cell) plist))))
+   ))
+
+
+
+(defun dictree-get-property (dict key property &optional nilflag)
+  "Get the value of PROPERTY for KEY in dictionary DICT,
+or return nil if KEY is not in the dictionary.
 
 Optional argument NILFLAG specifies a value to return instead of
 nil if KEY does not exist in TREE. This allows a non-existent KEY
-to be distinguished from a key that does not have any
-meta-data. (See also `dictree-member-p' for testing existence
-alone.)"
-  (let ((data (dictree--lookup dict key nilflag)))
-    (unless (eq data nilflag)
-      (dictree--unwrap-metadata data))))
+to be distinguished from a key for which PROPERTY is not
+set. (See also `dictree-member-p' for testing existence alone.)"
+  (let ((cell (dictree--lookup dict key nilflag)))
+    (unless (eq cell nilflag)
+      (plist-get (dictree--cell-plist cell) property))))
 
 
 
@@ -1303,15 +1423,14 @@ REVERSE is non-nil."
   ;; dynamical scoping bugs
   (let ((dictree-mapc--function function))
     (dictree--mapc
-     (lambda (key data metadata)
+     (lambda (key data plist)
        (funcall dictree-mapc--function key data))
      dict type reverse)))
 
 
 (defun dictree--mapc (function dict &optional type reverse)
-  ;; Like `dictree-mapc', but FUNCTION is passed a cons cell containing the
-  ;; data (car) and meta-data (cdr) as its second argument, instead of just
-  ;; the data.
+  ;; Like `dictree-mapc', but FUNCTION is passed three arguments: the key, the
+  ;; data, and the property list, instead of just key and data.
 
   ;; "rename" FUNCTION to something hopefully unique, to help avoid nasty
   ;; dynamical scoping bugs
@@ -1319,11 +1438,11 @@ REVERSE is non-nil."
     ;; for a normal dictionary, map the function over its trie
     (if (not (dictree--meta-dict-p dict))
        (trie-mapc
-        (lambda (key data)
+        (lambda (key cell)
           (funcall dictree--mapc--function
                    key
-                   (dictree--unwrap-data data)
-                   (dictree--unwrap-metadata data)))
+                   (dictree--cell-data cell)
+                   (dictree--cell-plist cell)))
         (dictree--trie dict)
         type reverse)
       ;; for a meta-dict, use a dictree-stack
@@ -1332,8 +1451,8 @@ REVERSE is non-nil."
        (while (setq entry (dictree--stack-pop stack))
          (funcall dictree--mapc--function
                   (car entry)
-                  (dictree--unwrap-data (cdr entry))
-                  (dictree--unwrap-metadata (cdr entry)))))
+                  (dictree--cell-data (cdr entry))
+                  (dictree--cell-plist (cdr entry)))))
       )))
 
 
@@ -1364,7 +1483,7 @@ descending order if REVERSE is non-nil."
     (if (not (dictree--meta-dict-p dict))
        (trie-mapf
         `(lambda (key data)
-           (,dictree-mapf--function key (dictree--unwrap-data data)))
+           (,dictree-mapf--function key (dictree--cell-data data)))
         dictree-mapf--combinator (dictree--trie dict) type reverse)
 
       ;; for a meta-dict, use a dictree-stack
@@ -1397,8 +1516,8 @@ function `string' to the individual elements of key 
sequences
 stored in DICT.
 
 The FUNCTION will be applied and the results combined in
-asscending \"lexical\" order (i.e. the order defined by the
-dictionary's comparison function; cf. `dictree-create'), or
+asscending \"lexical\" order \(i.e. the order defined by the
+dictionary's comparison function; cf. `dictree-create'\), or
 descending order if REVERSE is non-nil.
 
 Note that if you don't care about the order in which FUNCTION is
@@ -1547,7 +1666,7 @@ sufficient, it is better to use one of those instead."
   "Pop the first element from the DICTREE-STACK.
 Returns nil if the stack is empty."
   (let ((popped (dictree--stack-pop dictree-stack)))
-    (when popped (cons (car popped) (dictree--unwrap-data (cdr popped))))))
+    (when popped (cons (car popped) (dictree--cell-data (cdr popped))))))
 
 
 (defun dictree--stack-pop (dictree-stack)
@@ -1581,12 +1700,12 @@ Returns nil if the stack is empty."
              (setq next (dictree--stack-pop stack))
              (setq curr
                    (cons (car curr)
-                         (dictree--wrap-data
+                         (dictree--cell-create
                           (funcall (dictree--meta-stack-combfun dictree-stack)
-                                   (dictree--unwrap-data (cdr curr))
-                                   (dictree--unwrap-data (cdr next)))
-                          (list (dictree--unwrap-metadata (cdr curr))
-                                (dictree--unwrap-metadata (cdr next))))))
+                                   (dictree--cell-data (cdr curr))
+                                   (dictree--cell-data (cdr next)))
+                          (append (dictree--cell-plist (cdr curr))
+                                  (dictree--cell-plist (cdr next))))))
              (heap-add heap stack)
              (setq next (dictree--stack-first (heap-root heap))))))
        ;; return the combined dictionary element
@@ -1608,7 +1727,7 @@ Returns nil if the stack is empty."
   "Return the first element from DICTREE-STACK, without removing it.
 Returns nil if the stack is empty."
   (let ((first (dictree--stack-first dictree-stack)))
-    (cons (car first) (dictree--unwrap-data (cdr first)))))
+    (cons (car first) (dictree--cell-data (cdr first)))))
 
 
 (defun dictree-stack-empty-p (dictree-stack)
@@ -1624,7 +1743,8 @@ Returns nil if the stack is empty."
 ;;                        Advanced queries
 
 (defun dictree--query (query-type dict arg
-                      &optional rank-function maxnum reverse no-cache filter)
+                      &optional
+                      rank-function maxnum reverse no-cache filter)
   ;; Return results of QUERY-TYPE (currently, only 'complete is implemented)
   ;; on DICT. If RANK-FUNCTION is non-nil, return results ordered accordingly.
 
@@ -1704,7 +1824,8 @@ Returns nil if the stack is empty."
 
 
 (defun dictree--do-query (query-type dict arg
-                         &optional rank-function maxnum reverse filter)
+                         &optional
+                         rank-function maxnum reverse filter)
   ;; Return first MAXNUM results of running QUERY-TYPE on DICT that satisfy
   ;; FILTER, ordered according to RANK-FUNCTION (defaulting to "lexical"
   ;; order).
@@ -1757,7 +1878,8 @@ Returns nil if the stack is empty."
 
 (defun dictree-complete (dict prefix
                         &optional
-                        rank-function maxnum reverse no-cache filter)
+                        rank-function maxnum reverse no-cache filter
+                        strip-data)
   "Return an alist containing all completions of sequence PREFIX
 from dictionary DICT, along with their associated data, sorted
 according to RANK-FUNCTION (defaulting to \"lexical\" order, i.e. the
@@ -1776,6 +1898,16 @@ with the data from a different dictionary. If you want 
to combine
 identical keys, use a meta-dictionary; see
 `dictree-meta-dict-create'.)
 
+If optional argument RANK-FUNCTION is any non-nil value that is
+not a function, the completions are sorted according to the
+dictionary's rank-function (see `dictree-create'). Any non-nil
+value that *is* a function over-rides this. In that case,
+RANK-FUNCTION should accept two arguments, both cons cells. The
+car of each contains a sequence from the trie (of the same type
+as PREFIX), the cdr contains its associated data. The
+RANK-FUNCTION should return non-nil if first argument is ranked
+strictly higher than the second, nil otherwise.
+
 The optional integer argument MAXNUM limits the results to the
 first MAXNUM completions.
 
@@ -1789,23 +1921,38 @@ arguments: the completion, and its associated data. If 
the filter
 function returns nil, the completion is not included in the
 results, and doesn't count towards MAXNUM.
 
-If optional argument RANK-FUNCTION is any non-nil value that is
-not a function, the completions are sorted according to the
-dictionary's rank-function (see `dictree-create'). Any non-nil
-value that *is* a function over-rides this. In that case,
-RANK-FUNCTION should accept two arguments, both cons cells. The
-car of each contains a sequence from the trie (of the same type
-as PREFIX), the cdr contains its associated data. The
-RANK-FUNCTION should return non-nil if first argument is ranked
-strictly higher than the second, nil otherwise."
+If STRIP-DATA is non-nil, a list of completions is
+returned (rather than an alist), without the data."
   ;; run completion query
-  (dictree--query
-   'complete dict prefix
-   (when rank-function
-     (if (functionp rank-function)
-        rank-function
-       (dictree-rank-function (if (listp dict) (car dict) dict))))
-   maxnum reverse no-cache filter))
+  (let ((completions
+        (dictree--query
+         'complete dict prefix
+         (when rank-function
+           (if (functionp rank-function)
+               rank-function
+             (dictree-rank-function (if (listp dict) (car dict) dict))))
+         maxnum reverse no-cache filter)))
+    (if strip-data
+       (mapcar 'car completions)
+      completions)))
+
+
+
+(defun dictree-collection-function (dict string predicate all)
+  "Function for use in `try-completion', `all-completions',
+and `completing-read'. To complete from dictionary DICT, use the
+following as the COLLECTION argument of any of those functions:
+
+  (lambda (string predicate all)
+    (dictree-collection-function dict string predicate all))
+
+Note that PREDICATE will be called with two arguments: the
+completion, and its associated data."
+  (let ((completions
+        (dictree-complete dict string nil nil nil nil predicate t)))
+    (if all
+       completions
+      (try-completion "" completions))))
 
 
 
@@ -1855,11 +2002,8 @@ faster. However, only the uncompiled version is portable 
between
 different Emacs versions.
 
 If optional argument COMPILATION is the symbol 'compiled, only
-the uncompiled version will be created, whereas if it is the
-symbol 'uncompiled, only the uncompiled version will be created.
-
-Interactivley, DICT and FILENAME are read from the minibuffer,
-and OVERWRITE is the prefix argument."
+the compiled version will be created, whereas if it is the symbol
+'uncompiled, only the uncompiled version will be created."
 
   (let (dictname buff tmpfile)
     ;; add .el(c) extension to the filename if not already there
@@ -1880,8 +2024,8 @@ and OVERWRITE is the prefix argument."
       (set-buffer buff)
       ;; call the appropriate write function to write the dictionary code
       (if (dictree--meta-dict-p dict)
-         (dictree-write-meta-dict-code dict dictname)
-       (dictree-write-dict-code dict dictname))
+         (dictree--write-meta-dict-code dict dictname)
+       (dictree--write-dict-code dict dictname))
       (save-buffer)
       (kill-buffer buff))
 
@@ -2037,201 +2181,83 @@ NOT be saved even if its autosave flag is set."
 
 
 
-;; ----------------------------------------------------------------
-;;                Dumping and restoring contents
-
-(defun dictree-populate-from-file (dict file)
-  "Populate dictionary DICT from the key list in file FILE.
-
-Each line of the file should contain a key, either a string
-\(delimeted by \"\), a vector or a list. (Use the escape sequence
-\\\" to include a \" in a string.) If a line does not contain a
-key, it is silently ignored. The keys should ideally be sorted
-\"lexically\", as defined by the dictionary's comparison-function
-\(see `dictree-create'\).
-
-Each line can optionally include data and meta-data to be
-associated with the key, in that order, and separated from each
-other and the key by whitespace.
-
-
-Technicalities:
-
-The key, data and meta-data are read as lisp expressions using
-`read', and are read from the middle outwards, i.e. first the
-middle key is read, then the key directly after it, then the key
-directly before it, then the one two lines after the middle, and
-so on. Assuming the keys in the file are sorted \"lexically\",
-this helps produce a reasonably efficient dictionary structure."
-
-  (save-excursion
-    (let ((buff (generate-new-buffer " *dictree-populate*")))
-      ;; insert the key list into a temporary buffer
-      (set-buffer buff)
-      (insert-file-contents file)
-
-      ;; insert the keys starting from the median to ensure a reasonably
-      ;; well-balanced tree
-      (let* ((lines (count-lines (point-min) (point-max)))
-            (midpt (+ (/ lines 2) (mod lines 2)))
-            entry)
-        ;; insert the median key and set the dictionary's modified flag
-       (dictree-goto-line midpt)
-       (when (setq entry (dictree-read-line))
-         (dictree-insert dict (car entry) (nth 1 entry))
-         (dictree-set-meta-data dict (car entry) (nth 2 entry)))
-       (message "Inserting keys in %s...(1 of %d)"
-                (dictree-name dict) lines)
-        ;; insert keys successively further away from the median in both
-        ;; directions
-       (dotimes (i (1- midpt))
-         (dictree-goto-line (+ midpt i 1))
-         (when (setq entry (dictree-read-line))
-           (dictree-insert dict (car entry) (nth 1 entry))
-           (dictree-set-meta-data dict (car entry) (nth 2 entry)))
-         (when (= 49 (mod i 50))
-           (message "Inserting keys in %s...(%d of %d)"
-                    (dictree-name dict) (+ (* 2 i) 2) lines))
-         (dictree-goto-line (- midpt i 1))
-         (when (setq entry (dictree-read-line))
-           (dictree-insert dict (car entry) (nth 1 entry))
-           (dictree-set-meta-data dict (car entry) (nth 2 entry))))
-
-        ;; if file contains an even number of keys, we still have to add
-        ;; the last one
-       (when (= 0 (mod lines 2))
-         (dictree-goto-line lines)
-         (when (setq entry (dictree-read-line))
-           (dictree-insert dict (car entry) (nth 1 entry))
-           (dictree-set-meta-data dict (car entry) (nth 2 entry))))
-       (message "Inserting keys in %s...done" (dictree-name dict)))
-
-      (kill-buffer buff))))
-
-
-
-;;; FIXME: doesn't fail gracefully if file has invalid format
-(defun dictree-read-line ()
-  "Return a cons containing the key and data \(if any, otherwise
-nil\) at the current line of the current buffer. Returns nil if
-line is in wrong format."
-  (save-excursion
-    (let (key data meta-data)
-      ;; search for text between quotes "", ignoring escaped quotes \"
-      (beginning-of-line)
-      (setq key (read (current-buffer)))
-      ;; if there is anything after the quoted text, use it as data
-      (if (eq (line-end-position) (point))
-         (list key)
-       (setq data (read (current-buffer)))
-       (if (eq (line-end-position) (point))
-           (list key data)
-         (setq meta-data (read (current-buffer)))
-         ;; return the key and data
-         (list key data meta-data))))))
-
-
-
-(defun dictree-dump-to-buffer (dict &optional buffer type)
-  "Dump keys and their associated data
-from dictionary DICT to BUFFER, in the same format as that used
-by `dictree-populate-from-file'. If BUFFER exists, data will be
-appended to the end of it. Otherwise, a new buffer will be
-created. If BUFFER is omitted, the current buffer is used.
-
-TYPE determines the type of sequence to use to represent the
-keys, and should be one of 'string, 'vector or 'list. The default
-is 'vector.
-
-Note that if the data does not have a read syntax, the dumped
-data can not be used to recreate the dictionary using
-`dictree-populate-from-file'."
-
-  ;; select the buffer, creating it if necessary
-  (if buffer
-      (setq buffer (get-buffer-create buffer))
-    (setq buffer (current-buffer)))
-  (set-buffer buffer)
-
-  ;; move point to end of buffer and make sure it's at start of new line
-  (goto-char (point-max))
-  (unless (= (point) (line-beginning-position))
-    (insert "\n"))
-
-  ;; dump keys
-  (message "Dumping keys from %s to %s..."
-          (dictree-name dict) (buffer-name buffer))
-  (let ((count 0) (dictsize (dictree-size dict)))
-    (message "Dumping keys from %s to %s...(key 1 of %d)"
-            (dictree-name dict) (buffer-name buffer) dictsize)
-
-    ;; map dump function over dictionary
-    (dictree--mapc
-     (lambda (key data metadata)
-       (when (= 99 (mod count 100))
-        (message "Dumping keys from %s to %s...(key %d of %d)"
-                 (dictree-name dict) (buffer-name buffer)
-                 (1+ count) dictsize))
-       (insert (prin1-to-string key))
-       (let (data)
-        (when data (insert " " (prin1-to-string data)))
-        (when metadata (insert " " (prin1-to-string metadata)))
-        (insert "\n"))
-       (setq count (1+ count)))
-     dict type)  ; dictree-mapc target
-
-    (message "Dumping keys from %s to %s...done"
-            (dictree-name dict) (buffer-name buffer)))
-  (switch-to-buffer buffer))
-
-
-
-(defun dictree-dump-to-file (dict filename &optional type overwrite)
-  "Dump keys and their associated data
-from dictionary DICT to a text file FILENAME, in the same format
-as that used by `dictree-populate-from-file'. Prompts to overwrite
-FILENAME if it already exists, unless OVERWRITE is non-nil.
-
-TYPE determines the type of sequence to use to represent the
-keys, and should be one of 'string, 'vector or 'list. The default
-is 'vector.
-
-Note that if the data does not have a read syntax, the dumped
-data can not be used to recreate the dictionary using
-`dictree-populate-from-file'."
-
-  ;; check if file exists, and prompt to overwrite it if necessary
-  (if (and (file-exists-p filename)
-          (not overwrite)
-          (not (y-or-n-p
-                (format "File %s already exists. Overwrite? "
-                        filename))))
-      (message "Key dump cancelled")
-
-    (let (buff)
-      ;; create temporary buffer, dump keys to it, and save to FILENAME
-      (setq buff (generate-new-buffer filename))
-      (save-window-excursion
-       (dictree-dump-to-buffer dict buff type)
-       (write-file filename))
-      (kill-buffer buff))))
-
-
-
-(defun dictree-write-dict-code (dict dictname)
-  "Write code for normal dictionary DICT to current buffer,
-giving it the name DICTNAME."
-
-  (let (hashcode
-       tmpdict
-       lookup-alist
-       complete-alist
-       complete-ranked-alist)
+(defun dictree--write-dict-code (dict dictname)
+  ;; Write code for normal dictionary DICT to current buffer, giving it the
+  ;; name DICTNAME.
+  (let (hashcode tmpdict tmptrie
+       lookup-alist complete-alist complete-ranked-alist)
+
+    ;; --- convert trie data ---
+    ;; if dictionary doesn't use any custom save functions, write dictionary's
+    ;; trie directly as is
+    (setq tmptrie (dictree--trie dict))
+    ;; otherwise, create a temporary trie and populate it with the converted
+    ;; contents of the dictionary's trie
+    (when (or (dictree--data-savefun dict) (dictree--plist-savefun dict))
+      (setq tmptrie
+           (trie-create-custom
+            (trie-comparison-function tmptrie)
+            :createfun (trie--createfun tmptrie)
+            :insertfun (trie--insertfun tmptrie)
+            :deletefun (trie--deletefun tmptrie)
+            :lookupfun (trie--lookupfun tmptrie)
+            :mapfun (trie--mapfun tmptrie)
+            :emptyfun (trie--emptyfun tmptrie)
+            :stack-createfun (trie--stack-createfun tmptrie)
+            :stack-popfun (trie--stack-popfun tmptrie)
+            :stack-emptyfun (trie--stack-emptyfun tmptrie)))
+      (trie-mapc
+       (lambda (key cell)
+        (trie-insert tmptrie key
+                     (dictree--cell-create
+                      (funcall (or (dictree--data-savefun dict) 'identity)
+                               (dictree--cell-data cell))
+                      (funcall (or (dictree--plist-savefun dict) 'identity)
+                               (dictree--cell-plist cell)))))
+       (dictree--trie dict)))
+    ;; generate code to convert contents of trie back to original form
+    (cond
+     ;; convert both data and plist
+     ((and (dictree--data-loadfun dict) (dictree--plist-loadfun dict))
+      (setq hashcode
+           (concat
+            hashcode
+            "(trie-map\n"
+            " (lambda (key cell)\n"
+            "    (dictree--cell-create\n"
+            "     (funcall (dictree--data-loadfun " dictname ")\n"
+            "              (dictree--cell-data cell))\n"
+            "     (funcall (dictree--plist-loadfun " dictname ")\n"
+            "              (dictree--cell-plist cell))))\n"
+            " (dictree--trie " dictname "))\n")))
+     ;; convert only data
+     ((dictree--data-loadfun dict)
+      (setq hashcode
+           (concat
+            hashcode
+            "(trie-map\n"
+            " (lambda (key cell)\n"
+            "    (dictree--cell-create\n"
+            "     (funcall (dictree--data-loadfun " dictname ")\n"
+            "              (dictree--cell-data cell))\n"
+            "     (dictree--cell-plist cell)))\n"
+            " (dictree--trie " dictname "))\n")))
+     ;; convert only plist
+     ((dictree--plist-loadfun dict)
+      (setq hashcode
+           (concat
+            hashcode
+            "(trie-map\n"
+            " (lambda (key cell)\n"
+            "    (dictree--cell-create\n"
+            "     (dictree--cell-data cell)\n"
+            "     (funcall (dictree--plist-loadfun " dictname ")\n"
+            "              (dictree--cell-plist cell))))\n"
+            " (dictree--trie " dictname "))\n"))))
 
-    ;; dump caches to alists as necessary and generate code to reonstruct the
-    ;; hash tables from the alists
 
-    ;; create the lookup alist, if necessary
+    ;; --- convert hash tables to alists ---
+    ;; convert lookup cache hash table to alist, if it exists
     (when (dictree--lookup-cache-threshold dict)
       (maphash
        (lambda (key val)
@@ -2259,10 +2285,11 @@ giving it the name DICTNAME."
             "       (dictree--cache-maxnum (cdr entry)))\n"
             "      lookup-cache))\n"
             "   (dictree--lookup-cache " dictname "))\n"
-            "  (setf (dictree--lookup-cache " dictname ") lookup-cache))\n"
+            "  (setf (dictree--lookup-cache " dictname ")\n"
+            "        lookup-cache))\n"
             )))
 
-    ;; create the completion alist, if necessary
+    ;; convert completion cache hash table to alist, if it exists
     (when (dictree--complete-cache-threshold dict)
       (maphash
        (lambda (key val)
@@ -2291,10 +2318,11 @@ giving it the name DICTNAME."
        "       (dictree--cache-maxnum (cdr entry)))\n"
        "      complete-cache))\n"
        "   (dictree--complete-cache " dictname "))\n"
-       "  (setf (dictree--complete-cache " dictname ") complete-cache))\n"
+       "  (setf (dictree--complete-cache " dictname ")\n"
+       "        complete-cache))\n"
        )))
 
-    ;; create the ordered completion alist, if necessary
+    ;; convert ranked completion cache hash table to alist, if it exists
     (when (dictree--complete-ranked-cache-threshold dict)
       (maphash
        (lambda (key val)
@@ -2308,8 +2336,7 @@ giving it the name DICTNAME."
       (setq hashcode
            (concat
             hashcode
-            "(let ((complete-ranked-cache"
-            "(make-hash-table :test 'equal))\n"
+            "(let ((complete-ranked-cache (make-hash-table :test 'equal))\n"
             "      (trie (dictree--trie " dictname ")))\n"
             "  (mapc\n"
             "   (lambda (entry)\n"
@@ -2323,10 +2350,12 @@ giving it the name DICTNAME."
             "       (dictree--cache-maxnum (cdr entry)))\n"
             "      complete-ranked-cache))\n"
             "   (dictree--complete-ranked-cache " dictname "))\n"
-            "  (setf (dictree--complete-ranked-cache " dictname ")"
-                   " complete-ranked-cache))\n"
+            "  (setf (dictree--complete-ranked-cache " dictname ")\n"
+            "        complete-ranked-cache))\n"
             )))
 
+
+    ;; --- write to file ---
     ;; generate the structure to save
     (setq tmpdict (dictree-create))
     (setf (dictree--name tmpdict) dictname)
@@ -2356,7 +2385,13 @@ giving it the name DICTNAME."
     (setf (dictree--complete-ranked-cache tmpdict) complete-ranked-alist)
     (setf (dictree--complete-ranked-cache-threshold tmpdict)
          (dictree--complete-ranked-cache-threshold dict))
-    (setf (dictree--trie tmpdict) (dictree--trie dict))
+    (setf (dictree--trie tmpdict) tmptrie)
+    (setf (dictree--key-savefun tmpdict) (dictree--key-savefun dict))
+    (setf (dictree--key-loadfun tmpdict) (dictree--key-loadfun dict))
+    (setf (dictree--data-savefun tmpdict) (dictree--data-savefun dict))
+    (setf (dictree--data-loadfun tmpdict) (dictree--data-loadfun dict))
+    (setf (dictree--plist-savefun tmpdict) (dictree--plist-savefun dict))
+    (setf (dictree--plist-loadfun tmpdict) (dictree--plist-loadfun dict))
     (setf (dictree--meta-dict-list tmpdict) nil)
 
     ;; write lisp code that generates the dictionary object
@@ -2365,16 +2400,16 @@ giving it the name DICTNAME."
     (insert "(defvar " dictname " nil \"Dictionary " dictname ".\")\n")
     (insert "(setq " dictname " '" (prin1-to-string tmpdict) ")\n")
     (insert hashcode)
-    (insert "(setf (dictree-filename " dictname ")"
-           " (locate-library \"" dictname "\"))\n")
-    (insert "(unless (memq " dictname " dictree-loaded-list)"
-           " (push " dictname " dictree-loaded-list))\n")
-        (insert "(provide '" dictname ")\n")))
+    (insert "(setf (dictree-filename " dictname ")\n"
+           "      (locate-library \"" dictname "\"))\n")
+    (insert "(unless (memq " dictname " dictree-loaded-list)\n"
+           "  (push " dictname " dictree-loaded-list))\n")
+    (insert "(provide '" dictname ")\n")))
 
 
 
 
-(defun dictree-write-meta-dict-code (dict dictname)
+(defun dictree--write-meta-dict-code (dict dictname)
   "Write code for meta-dictionary DICT to current buffer,
 giving it the name DICTNAME."
 
@@ -2485,6 +2520,220 @@ giving it the name DICTNAME."
 
 
 ;; ----------------------------------------------------------------
+;;                Dumping and restoring contents
+
+(defun dictree-populate-from-file (dict file)
+  "Populate dictionary DICT from the key list in file FILE.
+
+Each line of the file should contain a key, either a string
+\(delimeted by \"\), a vector or a list. (Use the escape sequence
+\\\" to include a \" in a string.) If a line does not contain a
+key, it is silently ignored. The keys should ideally be sorted
+\"lexically\", as defined by the dictionary's comparison-function
+\(see `dictree-create'\).
+
+Each line can optionally include data and meta-data to be
+associated with the key, in that order, and separated from each
+other and the key by whitespace.
+
+
+Technicalities:
+
+The key, data and property list are read as lisp expressions
+using `read', and are read from the middle outwards, i.e. first
+the middle key is read, then the key directly after it, then the
+key directly before it, then the one two lines after the middle,
+then the one two lines before, and so on. Assuming the keys in
+the file are sorted \"lexically\", for some types of dictionary
+this can help produce an efficient data-structure."
+
+  (save-excursion
+    (let ((buff (generate-new-buffer " *dictree-populate*")))
+      ;; insert the key list into a temporary buffer
+      (set-buffer buff)
+      (insert-file-contents file)
+
+      ;; insert the keys starting from the median to ensure a reasonably
+      ;; well-balanced tree
+      (let* ((lines (count-lines (point-min) (point-max)))
+            (midpt (+ (/ lines 2) (mod lines 2)))
+            entry)
+        ;; insert the median key and set the dictionary's modified flag
+       (dictree-goto-line midpt)
+       (when (setq entry
+                   (condition-case nil
+                       (dictree--read-line dict)
+                     (error (error "Error reading line %d of %s"
+                                   midpt file))))
+         (dictree-insert dict (car entry) (nth 1 entry))
+         (setf (dictree--cell-plist (dictree--lookup dict (car entry) nil))
+               (nth 2 entry)))
+       (message "Inserting keys in %s...(1 of %d)"
+                (dictree-name dict) lines)
+        ;; insert keys successively further away from the median in both
+        ;; directions
+       (dotimes (i (1- midpt))
+         (dictree-goto-line (+ midpt i 1))
+         (when (setq entry
+                     (condition-case nil
+                         (dictree--read-line dict)
+                       (error (error "Error reading line %d of %s"
+                                     (+ midpt i 1) file))))
+           (dictree-insert dict (car entry) (nth 1 entry))
+           (setf (dictree--cell-plist (dictree--lookup dict (car entry) nil))
+                 (nth 2 entry)))
+         (when (= 49 (mod i 50))
+           (message "Inserting keys in %s...(%d of %d)"
+                    (dictree-name dict) (+ (* 2 i) 2) lines))
+         (dictree-goto-line (- midpt i 1))
+         (when (setq entry
+                     (condition-case nil
+                         (dictree--read-line dict)
+                       (error (error "Error reading line %d of %s"
+                                     (- midpt i 1) file))))
+           (dictree-insert dict (car entry) (nth 1 entry))
+           (setf (dictree--cell-plist (dictree--lookup dict (car entry) nil))
+                 (nth 2 entry))))
+
+        ;; if file contains an even number of keys, we still have to add
+        ;; the last one
+       (when (= 0 (mod lines 2))
+         (dictree-goto-line lines)
+         (when (setq entry
+                     (condition-case nil
+                         (dictree--read-line dict)
+                       (error (error "Error reading line %d of %s"
+                                     lines file))))
+           (dictree-insert dict (car entry) (nth 1 entry))
+           (setf (dictree--cell-plist (dictree--lookup dict (car entry) nil))
+                 (nth 2 entry))))
+       (message "Inserting keys in %s...done" (dictree-name dict)))
+
+      (kill-buffer buff))))
+
+
+
+(defun dictree--read-line (dict)
+  ;; Return a list containing the key, data (if any, otherwise nil) and
+  ;; property list (ditto) at the current line of the current buffer, for
+  ;; dictionary DICT.
+  (save-excursion
+    (let (key data plist)
+      ;; read key
+      (beginning-of-line)
+      (setq key (read (current-buffer)))
+      (when (dictree--key-loadfun dict)
+       (setq key (funcall (dictree--key-loadfun dict) key)))
+      ;; if there's anything after the key, use it as data
+      (if (eq (line-end-position) (point))
+         (list key)
+       (setq data (read (current-buffer)))
+       (when (dictree--data-loadfun dict)
+         (setq data (funcall (dictree--data-loadfun dict) data)))
+       (if (eq (line-end-position) (point))
+           (list key data)
+         ;; if there's anything after the data, use is as the property list
+         (setq plist (read (current-buffer)))
+         (when (dictree--plist-loadfun dict)
+           (funcall (dictree--plist-loadfun dict) plist))
+         ;; return the key and data
+         (list key data plist))))))
+
+
+
+(defun dictree-dump-to-buffer (dict &optional buffer type)
+  "Dump keys and their associated data
+from dictionary DICT to BUFFER, in the same format as that used
+by `dictree-populate-from-file'. If BUFFER exists, data will be
+appended to the end of it. Otherwise, a new buffer will be
+created. If BUFFER is omitted, the current buffer is used.
+
+TYPE determines the type of sequence to use to represent the
+keys, and should be one of 'string, 'vector or 'list. The default
+is 'vector.
+
+Note that if the data does not have a read syntax, the dumped
+data can not be used to recreate the dictionary using
+`dictree-populate-from-file'."
+
+  ;; select the buffer, creating it if necessary
+  (if buffer
+      (setq buffer (get-buffer-create buffer))
+    (setq buffer (current-buffer)))
+  (set-buffer buffer)
+
+  ;; move point to end of buffer and make sure it's at start of new line
+  (goto-char (point-max))
+  (unless (= (point) (line-beginning-position))
+    (insert "\n"))
+
+  ;; dump keys
+  (message "Dumping keys from %s to %s..."
+          (dictree-name dict) (buffer-name buffer))
+  (let ((count 0) (dictsize (dictree-size dict)))
+    (message "Dumping keys from %s to %s...(key 1 of %d)"
+            (dictree-name dict) (buffer-name buffer) dictsize)
+
+    ;; map dump function over dictionary
+    (dictree--mapc
+     (lambda (key data plist)
+       (when (= 99 (mod count 100))
+        (message "Dumping keys from %s to %s...(key %d of %d)"
+                 (dictree-name dict) (buffer-name buffer)
+                 (1+ count) dictsize))
+       (insert (prin1-to-string
+               (funcall (or (dictree--key-savefun dict) 'identity) key)))
+       (when (setq data
+                  (funcall (or (dictree--data-savefun dict) 'identity) data))
+        (insert " " (prin1-to-string data)))
+       (when (setq plist
+                  (funcall (or (dictree--plist-savefun dict) 'identity) plist))
+        (unless data (insert " nil"))
+        (insert " " (prin1-to-string plist)))
+       (insert "\n")
+       (setq count (1+ count)))
+     dict type)  ; dictree-mapc target
+
+    (message "Dumping keys from %s to %s...done"
+            (dictree-name dict) (buffer-name buffer)))
+  (switch-to-buffer buffer))
+
+
+
+(defun dictree-dump-to-file (dict filename &optional type overwrite)
+  "Dump keys and their associated data
+from dictionary DICT to a text file FILENAME, in the same format
+as that used by `dictree-populate-from-file'. Prompts to overwrite
+FILENAME if it already exists, unless OVERWRITE is non-nil.
+
+TYPE determines the type of sequence to use to represent the
+keys, and should be one of 'string, 'vector or 'list. The default
+is 'vector.
+
+Note that if the data does not have a read syntax, the dumped
+data can not be used to recreate the dictionary using
+`dictree-populate-from-file'."
+
+  ;; check if file exists, and prompt to overwrite it if necessary
+  (if (and (file-exists-p filename)
+          (not overwrite)
+          (not (y-or-n-p
+                (format "File %s already exists. Overwrite? "
+                        filename))))
+      (message "Key dump cancelled")
+
+    (let (buff)
+      ;; create temporary buffer, dump keys to it, and save to FILENAME
+      (setq buff (generate-new-buffer filename))
+      (save-window-excursion
+       (dictree-dump-to-buffer dict buff type)
+       (write-file filename))
+      (kill-buffer buff))))
+
+
+
+
+;; ----------------------------------------------------------------
 ;;                     Minibuffer completion
 
 (defvar dictree-history nil



reply via email to

[Prev in Thread] Current Thread [Next in Thread]