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

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

[elpa] externals/dict-tree ae0dc0e 001/154: Version 0.10 of the predicti


From: Stefan Monnier
Subject: [elpa] externals/dict-tree ae0dc0e 001/154: Version 0.10 of the predictive completion package.
Date: Mon, 14 Dec 2020 12:21:32 -0500 (EST)

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

    Version 0.10 of the predictive completion package.
---
 dict-tree.el | 1601 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1601 insertions(+)

diff --git a/dict-tree.el b/dict-tree.el
new file mode 100644
index 0000000..b740a90
--- /dev/null
+++ b/dict-tree.el
@@ -0,0 +1,1601 @@
+
+;;; dict-tree.el --- dictionary data structure package
+
+
+;; Copyright (C) 2004-2006 Toby Cubitt
+
+;; Author: Toby Cubitt <toby-predictive@dr-qubit.org>
+;; Version: 0.8
+;; Keywords: dictionary, tree
+;; URL: http://www.dr-qubit.org/emacs.php
+
+
+;; This file is NOT part of Emacs.
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation; either version 2
+;; of the License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+;; MA 02110-1301, USA.
+
+
+;;; Commentary:
+;;
+;; A dictionary consists of a list containing either 5 or 10 elements
+;; (see the dict-create function for details).
+;;
+;; A dictionary is used to store strings, along with arbitrary data
+;; associated with each string. The 'string' can be a sequence of any
+;; data type, not just a string of characters. As well as basic data
+;; insertion, manipulation and retrieval, a dictionary can perform
+;; advanced searches on those strings (see the dict-complete and
+;; dict-complete-ordered functions), and is able to cache results in
+;; order to speed up those searches.
+;;
+;; This package uses the ternary search tree package, tstree.el.
+
+
+;;; Change log:
+;;
+;; Version 0.8
+;; * changed `dict-map(car)' into functions and made them work with
+;;   lookup-only dicts
+;; * `dict-insert' now returns the new data value
+;; * rewrote cache data structures: data is now wrapped inside a cons cell, so
+;;   that cache entries can point to it instead of duplicating it. This fixes
+;;   some caching bugs and makes updating cached data when inserting words
+;;   much faster
+;; * dictionaries (but not lookup-only) can now associate two pieces of data
+;;   with each word: normal data, used to rank words returned by
+;;   `dict-complete-ordered', and meta-data, not used for ranking
+;; * modified functions to work with new caching and meta-data, and added
+;;   `dict-set-meta-data' and `dict-lookup-meta-data'
+;; * renamed to `dict-tree' to help avoid conflicts with other packages
+;;
+;; Version 0.7
+;; * added `dict-mapcar' macro
+;;
+;; Version 0.6.2
+;; * minor bug fixes
+;;
+;; Version 0.6.1
+;; * minor bug fixes
+;;
+;; Version 0.6
+;; * added dict-size function
+;; * added dict-dump-words-to-buffer function
+;; * dictionaries now set their names and filenames by doing a library search
+;;   for themselves when loaded using require
+;; * added `read-dict' minibuffer completion function
+;; * interactive commands that read a dictionary name now provide completion
+;;
+;; Version 0.5
+;; * added dict-dump-words-to-file function
+;;
+;; Version 0.4
+;; * fixed bug in dict-read-line
+;;
+;; Version 0.3
+;; * added dict-map function
+;;
+;; Version 0.2
+;; * added dictionary autosave flag and related functions;
+;; * fixed bug preventing dict caches being loaded properly;
+;; * explicitly require cl.el;
+;;
+;; Note: version 0.1 dictionaries not compatible with version 0.2 and above!
+;;
+;; Version 0.1
+;; * initial release
+
+
+
+;;; Code:
+
+(provide 'dict-tree)
+(require 'tstree)
+;; the only required common-lisp functions are `subseq', `map' and `merge'
+(require 'cl)
+
+
+
+
+;;; ====================================================================
+;;;  Internal functions and variables for use in the dictionary package
+
+
+(defvar dict-loaded-list nil
+  "Stores list of loaded dictionaries.")
+
+
+(defmacro dic-name (dict)  ; INTERNAL USE ONLY
+  ;; Return the name of dictonary DICT
+  `(nth 1 ,dict)
+)
+
+
+(defmacro dic-set-name (dict name)  ; INTERBAL USE ONLY
+  ;; Set the name of dictionary DICT
+  `(setcar (cdr ,dict) ,name)
+)
+
+
+(defmacro dic-filename (dict)  ; INTERNAL USE ONLY.
+  ;; Return the filename of dictionary DICT
+  `(nth 2 ,dict)
+)
+
+
+(defmacro dic-set-filename (dict filename)  ; INTERNAL USE ONLY.
+  ;; Set the filename of dictionary DICT
+  `(setcar (cdr (cdr ,dict)) ,filename)
+)
+
+
+(defmacro dic-autosave (dict)  ; INTERNAL USE ONLY
+  ;; Return the autosave flag of dictionary DICT
+  `(nth 3 ,dict)
+)
+
+
+(defmacro dic-set-autosave (dict flag)  ; INTERNAL USE ONLY
+  ;; Set the autosave flag of dictionary DICT
+  `(setcar (cdr (cdr (cdr ,dict))) ,flag)
+)
+
+
+(defmacro dic-modified (dict)  ; INTERNAL USE ONLY
+  ;; Return the modified flag of dictionary DICT
+  `(nth 4 ,dict)
+)
+
+
+(defmacro dic-set-modified (dict flag)  ; INTERNAL USE ONLY
+  ;; Set the modified flag of dictionary DICT
+  `(setcar (cdr (cdr (cdr (cdr ,dict)))) ,flag)
+)
+
+
+(defmacro dic-tstree (dict)  ; INTERNAL USE ONLY.
+  ;; Return the ternary search tree of dictionary DICT
+  `(nth 5 ,dict)
+)
+
+
+(defmacro dic-lookup-only (dict)  ; INTERNAL USE ONLY.
+  ;; Return the lookup-only setting of dictionary DICT
+  `(nth 6 ,dict)
+)
+
+
+(defmacro dic-lookup-hash (dict)  ; INTERNAL USE ONLY
+  ;; Return the lookup hash table of dictionary DICT
+  `(nth 7 ,dict)
+)
+
+
+(defmacro dic-set-lookup-hash (dict hash)  ; INTERNAL USE ONLY
+  ;; Set the completion hash for dictionary DICT
+  `(setcar (cdr (cdr (cdr (cdr (cdr (cdr (cdr ,dict))))))) ,hash)
+)
+
+
+(defmacro dic-lookup-speed (dict)  ; INTERNAL USE ONLY
+  ;; Return the lookup speed of dictionary DICT
+  `(nth 8 ,dict)
+)
+
+
+(defmacro dic-completion-hash (dict)  ; INTERNAL USE ONLY
+  ;; Return the completion hash table of dictionary DICT
+  `(nth 9 ,dict)
+)
+
+
+(defmacro dic-set-completion-hash (dict hash)  ; INTERNAL USE ONLY
+  ;; Set the completion hash for dictionary DICT
+  `(setcar (cdr (cdr (cdr (cdr (cdr (cdr (cdr (cdr (cdr ,dict))))))))) ,hash)
+)
+
+
+(defmacro dic-completion-speed (dict)  ; INTERNAL USE ONLY
+  ;; Return the completion speed of dictionary DICT
+  `(nth 10 ,dict)
+)
+
+
+(defmacro dic-ordered-hash (dict)  ; INTERNAL USE ONLY
+  ;; Return the ordered completion hash table of dictionary DICT
+  `(nth 11 ,dict)
+)
+
+
+(defmacro dic-set-ordered-hash (dict hash)  ; INTERNAL USE ONLY
+  ;; Set the completion hash for dictionary DICT
+  `(setcar (cdr (cdr (cdr (cdr (cdr (cdr (cdr (cdr (cdr (cdr (cdr ,dict)
+                                                            ))))))))))
+          ,hash)
+)
+
+
+(defmacro dic-ordered-speed (dict)  ; INTERNAL USE ONLY
+  ;; Return the ordered completion speed of dictionary DICT
+  `(nth 12 ,dict)
+)
+
+
+(defmacro dic-insfun (dict)  ; INTERNAL USE ONLY.
+  ;; Return the insert function of dictionary DICT.
+  `(if (dic-lookup-only ,dict)
+       (nth 2 ,dict)
+     (tst-tree-insfun (dic-tstree ,dict)))
+)
+
+
+(defmacro dic-rankfun (dict)  ; INTERNAL USE ONLY
+  ;; Return the rank function of dictionary DICT.
+  `(if (dic-lookup-only ,dict)
+       nil
+     (tst-tree-rankfun (dic-tstree ,dict)))
+)
+
+
+(defmacro dic-wrap-data (data &optional meta-data)  ; INTERNAL USE ONLY
+  ;; wrap the data in a cons cell
+  `(cons ,data ,meta-data))
+
+
+(defmacro dic-get-data (cell)  ; INTERNAL USE ONLY
+  ;; get data component from data cons cell
+  `(car ,cell))
+
+
+(defmacro dic-set-data (cell data)  ; INTERNAL USE ONLY
+  ;; set data component of data cons cell
+  `(setcar ,cell ,data))
+
+
+(defmacro dic-get-metadata (cell)  ; INTERNAL USE ONLY
+  ;; get meta-data component of data cons cell
+  `(cdr ,cell))
+
+
+(defmacro dic-set-metadata (cell meta-data)  ; INTERNAL USE ONLY
+  ;; set meta-data component of data cons cell
+  `(setcdr ,cell ,meta-data))
+
+
+(defmacro dic-wrap-insfun (insfun)  ; INTERNAL USE ONLY
+  ;; return wrapped insfun to deal with data wrapping
+  `(lambda (new cell)
+     ;; if data doesn't already exist, wrap and return new data
+     (if (null cell)
+        (dic-wrap-data (funcall ,insfun new nil))
+       ;; oterhwise, update data cons cell with new data and return it
+       (dic-set-data cell (funcall ,insfun new (dic-get-data cell)))
+       cell))
+)
+
+
+(defmacro dic-wrap-rankfun (rankfun)  ; INTERNAL USE ONLY
+  ;; return wrapped rankfun to deal with data wrapping
+  `(lambda (a b) (funcall ,rankfun (cons (car a) (dic-get-data (cdr a)))
+                         (cons (car b) (dic-get-data (cdr b))))))
+
+
+(defmacro dic-wrap-filter (filter)  ; INTERNAL USE ONLY
+  ;; return wrapped filter function to deal with data wrapping
+  `(lambda (str data) (funcall ,filter str (dic-get-data data))))
+
+
+
+(defmacro dic-cache-create (list maxnum)  ; INTERNAL USE ONLY
+  ;; Return a completion cache entry
+  `(cons ,list ,maxnum))
+
+
+(defmacro dic-cache-completions (cache)  ; INTERNAL USE ONLY
+  ;; Return the completions list for cache entry CACHE
+  `(car ,cache))
+
+
+(defmacro dic-cache-maxnum (cache)  ; INTERNAL USE ONLY
+  ;; Return the max number of completions returned for cache entry CACHE
+  `(cdr ,cache))
+
+
+(defmacro dic-set-cache-completions (cache completions)  ; INTERNAL USE ONLY
+  ;; Set the completions list for cache entry CACHE
+  `(setcar ,cache ,completions))
+
+
+(defmacro dic-set-cache-maxnum (cache maxnum)  ; INTERNAL USE ONLY
+  ;; Set the completions list for cache entry CACHE
+  `(setcdr ,cache ,maxnum))
+
+
+
+
+;;; ================================================================
+;;;      The public functions which operate on dictionaries
+
+
+(defun dict-p (obj)
+  "Return t if OBJ is a dictionary, nil otherwise."
+  (eq (car-safe obj) 'DICT)
+)
+
+
+(defun dict-name (dict)
+  "Return dictionary DICT's name."
+  (dic-name dict)
+)
+
+
+(defun dict-create (name &optional filename autosave
+                             lookup-speed complete-speed
+                             ordered-speed lookup-only
+                             insert-function rank-function
+                             unlisted)
+  "Create an empty dictionary stored in variable NAME, and return it.
+
+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.
+
+The SPEED settings set the desired speed for the corresponding
+dictionary search operations (lookup, completion, ordered
+completion), in seconds. If a particular instance of the
+operation \(e.g. looking up the word \"cat\"\) takes longer than
+this, the results will be cached in a hash table. If exactly the
+same operation is requested subsequently, it should perform
+significantly faster. \(Note \"should\": there's no guarantee!\)
+The down side is that the memory or disk space required to store
+the dictionary grows, and inserting words into the dictionary
+becomes slower, since the cache has to be synchronized.
+
+All SPEED's default to nil. The values nil and t are special. If
+a SPEED is set to nil, no caching is done for that operation. If
+it is set to t, everything is cached for that operation \(similar
+behaviour can be obtained by setting the SPEED to 0, but it is
+better to use t\).
+
+If LOOKUP-ONLY is non-nil, it disables all advanced search
+features for the dictionary \(currently, completion\). All the
+SPEED settings are ignored, as is the RANK-FUNCTION, and
+everything is stored in the lookup cache, even when inserting
+data. This is appropriate when a dictionary is only going to be
+used for lookup, since it speeds up lookups *and* decreases the
+memory required.
+
+
+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 (or nil if
+none exists yet). It should return the data to insert. It
+defaults to replacing any existing data with the new data.
+
+Optional argument RANK-FUNCTION sets the function used to rank
+the results of the `dict-complete-ordered' function. It should
+take two arguments, each a cons whose car is a word in the
+dictionary and whose cdr is the data associated with that
+word. It should return non-nil if the first argument is
+\"better\" than the second, nil otherwise. It defaults to string
+comparison of the words, ignoring the data \(which is not very
+useful, since the `dict-complete' function already returns
+completions in alphabetical order much more efficiently, but at
+least will never cause any errors, whatever data is stored!\)
+
+If optional argument UNLISTED is non-nil, the dictionary will not
+be added to the list of loaded dictionaries. Note that this will
+disable autosaving."
+
+  ;; a dictionary is a list containing:
+  ;; ('DICT
+  ;;  name
+  ;;  filename
+  ;;  autosave flag
+  ;;  modified flag
+  ;;  tstree / insert-function (if lookup-only)
+  ;   lookup-only
+  ;;  lookup-hash
+  ;;  --- rest only if not lookup-only ---
+  ;;  lookup-speed
+  ;;  complete-hash
+  ;;  complete-speed
+  ;;  ordered-hash
+  ;;  ordered-speed)
+  (let (dict insfun rankfun)
+    
+    ;; wrap insert-function and rank-function to deal with data wrapping
+    (setq insfun (if insert-function
+                    (eval (macroexpand `(dic-wrap-insfun ,insert-function)))
+                  ;; insert-function defaults to "replace"
+                  (lambda (a b) a))
+         
+         rankfun (if rank-function
+                     (eval (macroexpand `(dic-wrap-rankfun ,rank-function)))
+                   ;; rank-function defaults to numeric comparison of data
+                   (lambda (a b) (> (dic-get-data (cdr a))
+                                    (dic-get-data (cdr b))))))
+    
+    (setq dict
+        (if lookup-only
+            ;; if dict is lookup only, use insert-function since there's no
+            ;; need to wrap data, and store it where tstree usually goes
+            (list 'DICT (symbol-name name) filename
+                  autosave t insert-function t
+                  (make-hash-table :test 'equal))
+          
+          (list 'DICT (symbol-name name) filename autosave t
+                (tstree-create '- insfun rankfun) nil
+                (if lookup-speed (make-hash-table :test 'equal) nil)
+                lookup-speed
+                (if complete-speed (make-hash-table :test 'equal) nil)
+                complete-speed
+                (if ordered-speed (make-hash-table :test 'equal) nil)
+                ordered-speed)))
+    ;; add dictionary to loaded list
+    (unless unlisted (push dict dict-loaded-list))
+    dict)
+)
+
+
+
+
+(defun dict-create-type (name type &optional filename autosave
+                             lookup-speed complete-speed ordered-speed)
+  "Create an empty dictionary of type TYPE stored in variable NAME, and return
+it. Type can be one of dictionary, spell-check, lookup, or
+frequency. `dict-create-type' is a simplified interface to `dict-create'.
+
+The \"dictionary\" type is exactly like a normal, paper-based dictionary: it
+can associate arbitrary data with any word in the dictionary. Inserting data
+for a word will replace any existing data for that word. All SPEED arguments
+default to nil.
+
+A \"spell-check\" dictionary stores words, but can not associate any data with
+the words. It is appropriate when the dictionary will only be used for
+checking if a word is in the dictionary (e.g. for spell-checking). All SPEED
+arguments default to nil.
+
+A \"lookup\" dictionary is like a dictionary-type dictionary, but can only be
+used to look up words, not for more advanced searches (e.g. word
+completion). This has both speed and memory benefits. It is appropriate when
+the more advanced searches are not required. Any SPEED arguments are ignored.
+
+A \"frequency\" dictionary associates a number with each word in the
+dictionary. Inserting new data adds it to the existing data. It is
+appropriate, for instance, when storing word-frequencies\; the
+`dict-complete-ordered' function can then be used to return the most likely
+completions. All SPEED arguments default to nil.
+
+See `dict-create' for more details.
+
+
+Technicalities:
+
+For the \"dictionary\" type, INSERT-FUNCTION is set to \"replace\", and
+RANK-FUNCTION to string comparison of the words (not very useful, since the
+`dict-complete' function already returns completions sorted alphabetically,
+and does it much more efficiently than `dict-complete-ordered', but at least
+it will not cause errors!).
+
+For the \"spell-check\" type, INSERT-FUNCTION is set to a function that always
+returns t. RANK-FUNCTION is set to string comparison of the words.
+
+For the \"lookup\" type, INSERT-FUNCTION is set to \"replace\", and
+LOOKUP-ONLY is set to t.
+
+For the \"frequency\" type, INSERT-FUNCTION sums the new and existing
+data. Nil is treated as 0. The RANK-FUNCTION is set to numerical
+\"greater-than\" comparison of the data."
+  
+  (let (insfun rankfun lookup-only)
+    ;; set arguments based on type
+    (cond
+     ;; dictionary type
+     ((eq type 'dictionary)
+      (setq insfun (lambda (a b) a))
+      (setq rankfun (lambda (a b) (string< (car a) (car b)))))
+     
+     ;; spell-check type
+     ((eq type 'spell-check)
+      (setq insfun (lambda (a b) t))
+      (setq rankfun (lambda (a b) (string< (car a) (car b)))))
+     
+     ;; lookup type
+     ((eq type 'lookup)
+      (setq insfun (lambda (a b) a))
+      (setq rankfun (lambda (a b) (string< (car a) (car b))))
+      (setq lookup-only t))
+     
+     ;; frequency type
+     ((eq type 'frequency)
+      (setq insfun (lambda (new old)
+                    (cond ((and (null new) (null old)) 0)
+                          ((null new) old)
+                          ((null old) new)
+                          (t (+ old new)))))
+      (setq rankfun (lambda (a b) (> (cdr a) (cdr b)))))
+     )
+    
+    (dict-create name filename autosave
+                lookup-speed complete-speed ordered-speed
+                lookup-only insfun rankfun))
+)
+
+
+
+
+(defun dict-insert-function (dict)
+  "Return the insertion function for dictionary DICT."
+  (dic-insfun dict)
+)
+
+
+
+(defun dict-rank-function (dict)
+  "Return the rank function for the dictionary DICT (note: returns nil if
+lookup-only is set for the dictionary)."
+  (dic-rankfun dict)
+)
+
+
+
+(defun dict-empty (dict)
+  "Return t if the dictionary DICT is empty, nil otherwise."
+  (if (dic-lookup-only dict)
+      (= 0 (hash-table-count (dic-lookup-hash dict)))
+    (tstree-empty (dic-tstree dict)))
+)
+
+
+
+
+(defun dict-insert (dict word &optional data insert-function)
+  "Insert WORD and DATA into dictionary DICT.
+If WORD does not already exist, this creates it. How the data is inserted
+depends on the dictionary's insertion function (see `dict-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 WORD in the dictionary (nil if none already exists). It should return the
+data to insert."
+  ;; make sure WORD is a string
+  (when (not (stringp word))
+    (error "Wrong argument type stringp, %s" (prin1-to-string word)))
+  (when (not (dict-p dict))
+    (error "Wrong argument type dict-p"))
+  
+  (let ((insfun (if insert-function
+                   (eval (macroexpand `(dic-wrap-insfun ,insert-function)))
+                 (dic-insfun dict))))
+    ;; set the dictionary's modified flag
+    (dic-set-modified dict t)
+    
+    ;; if dictionary is lookup-only, just insert the data in the lookup cache
+    (if (dic-lookup-only dict)
+       (let ((lookup-hash (dic-lookup-hash dict)))
+         (puthash
+          word (funcall insfun data (gethash word lookup-hash))
+          lookup-hash))
+
+      
+      ;; otherwise...
+      (let ((tstree (dic-tstree dict))
+           newdata)
+       
+        ;; insert word in dictionary's ternary search tree
+       (setq newdata (tstree-insert tstree word data insfun))
+       
+       
+        ;; synchronize the completion caches
+       (when (or (dic-completion-speed dict) (dic-ordered-speed dict))
+         (let ((completion-hash (dic-completion-hash dict))
+               (ordered-hash (dic-ordered-hash dict))
+               (rankfun (dic-rankfun dict))
+               str wrd cache cmpl maxnum)
+           
+           ;; have to check every possible substring that could be cached!
+           (dotimes (i (1+ (length word)))
+             (setq str (substring word 0 i))
+
+             ;; synchronize the completion hash, if it exists
+             (when (and (dic-completion-speed dict)
+                        (setq cache (gethash str completion-hash)))
+               (setq cmpl (dic-cache-completions cache))
+               (setq maxnum (dic-cache-maxnum cache))
+               ;; if word is already in the completion list, it doesn't need
+               ;; updating, otherwise update it from the tree
+               ;; (Note: we could instead add word to the list and re-sort,
+               ;;  but it's probably not worth it)
+               (unless (assoc word cmpl)
+                 (setcar cache
+                         (tstree-complete (dic-tstree dict) str maxnum))))
+             
+             
+             ;; synchronize the ordered completion hash, if it exists
+             (when (and (dic-ordered-speed dict)
+                        (setq cache (gethash str ordered-hash)))
+               (setq cmpl (dic-cache-completions cache))
+               (setq maxnum (dic-cache-maxnum cache))
+               (setq wrd (substring word i))
+               (cond
+                
+                ;; if word is in the completion list...
+                ((assoc wrd cmpl)
+                 ;; re-sort the list
+                 (dic-set-cache-completions cache (sort cmpl rankfun))
+                 (setq cmpl (dic-cache-completions cache))
+                 ;; if word is now at the end of the list, we've no choice
+                 ;; but to update from the tree
+                 (when (equal (caar (last cmpl)) wrd)
+                   (dic-set-cache-completions
+                    cache (tstree-complete-ordered tstree str maxnum
+                                                   nil rankfun))))
+                
+                ;; if word isn't in the completion list...
+                (t
+                 ;; add word to the end of the list and re-sort
+                 (setcdr (last cmpl) (list (cons wrd newdata)))
+                 (dic-set-cache-completions cache (sort cmpl rankfun))
+                 (setq cmpl (dic-cache-completions cache))
+                 ;; remove excess completions
+                 (when (> (length cmpl) maxnum)
+                   (setcdr (nthcdr (1- maxnum) cmpl) nil)))))
+             )))
+       
+       ;; return the new data value
+       (dic-get-data newdata))))
+)
+
+
+
+(defun dict-lookup (dict word)
+  "Return the data associated with WORD in dictionary DICT, or nil if WORD is
+not in the dictionary.
+
+Note: this will not distinguish between a non-existent WORD and a WORD whose
+data is nil. \(\"spell-check\" type dictionaries created using
+`dict-create-type' store t as the data for every word to avoid this problem)
+Use `dict-member-p' to distinguish non-existent words from nil data."
+  
+  ;; first check the lookup hash for the word
+  (let ((data (if (dic-lookup-speed dict)
+                 (gethash word (dic-lookup-hash dict))
+               nil))
+       time)
+    
+    ;; if it wasn't in the lookup hash and the dictionary isn't lookup-only,
+    ;; search in the ternary search tree
+    (unless (or data (dic-lookup-only dict))
+      ;; time the lookup
+      (let (time)
+       (setq time (float-time))
+       (setq data (tstree-member (dic-tstree dict) word))
+       (setq time (- (float-time) time))
+       
+        ;; if the lookup was slower than the dictionary's lookup speed, add it
+        ;; to the lookup hash and set the modified flag
+       (when (and (dic-lookup-speed dict)
+                  (or (eq (dic-lookup-speed dict) t)
+                      (> time (dic-lookup-speed dict))))
+         (dic-set-modified dict t)
+         (puthash word data (dic-lookup-hash dict)))))
+    
+    ;; return the data
+    (dic-get-data data))
+)
+
+
+
+(defun dict-set-meta-data (dict word meta-data)
+  "Set meta-data (data not used to rank words) for WORD
+in dictionary DICT."
+  
+  ;; make sure WORD is a string
+  (when (not (stringp word))
+    (error "Wrong argument type stringp, %s" (prin1-to-string word)))
+  (when (not (dict-p dict))
+    (error "Wrong argument type dict-p"))
+  
+  ;; set the dictionary's modified flag
+  (dic-set-modified dict t)
+    
+  ;; if dictionary is lookup-only, refuse!
+  (if (dic-lookup-only dict)
+      (error "Lookup-only dictionaries can't contain meta-data")
+    ;; otherwise, set word's meta-data
+    (dic-set-metadata (tstree-member (dic-tstree dict) word) meta-data))
+)
+
+
+       
+(defun dict-lookup-meta-data (dict word)
+  "Return any meta-data (data not used to rank words)
+associated with WORD in dictionary DICT, or nil if WORD is not in
+the dictionary.
+
+Note: this will not distinguish between a non-existent WORD and a
+WORD with no meta-data. Use `dict-member-p' to distinguish
+non-existent words."
+
+  (when (dic-lookup-only dict)
+    (error "Lookup-only dictionaries can't contain meta-data"))
+  
+  ;; first check the lookup hash for the word
+  (let ((data (if (dic-lookup-speed dict)
+                 (gethash word (dic-lookup-hash dict))
+               nil))
+       time)
+    
+    ;; if it wasn't in the lookup hash, search in the ternary search tree
+    (unless data
+      ;; time the lookup
+      (let (time)
+       (setq time (float-time))
+       (setq data (tstree-member (dic-tstree dict) word))
+       (setq time (- (float-time) time))
+       
+        ;; if the lookup was slower than the dictionary's lookup speed, add it
+        ;; to the lookup hash and set the modified flag
+       (when (and (dic-lookup-speed dict)
+                  (or (eq (dic-lookup-speed dict) t)
+                      (> time (dic-lookup-speed dict))))
+         (dic-set-modified dict t)
+         (puthash word data (dic-lookup-hash dict)))))
+    
+    ;; return the meta-data
+    (dic-get-metadata data))
+)
+
+
+
+
+(defun dict-member-p (dict word)
+  "Return t if WORD is in dictionary DICT, nil otherwise."
+  
+  ;; if dictionary is lookup-only, look in lookup hash and use dummy variable
+  ;; to distinguish non-existent words from those with nil data
+  (if (dic-lookup-only dict)
+      (if (eq (gethash word (dic-lookup-hash dict) 'not-in-here)
+             'not-in-here) nil t)
+    ;; otherwise look in the ternary search tree
+    (tstree-member-p (dic-tstree dict) word))
+)
+
+
+
+;; (defun dict-delete (dict word)
+;;   "Delete WORD from DICT"
+;; )
+
+
+
+(defun dict-map (function dict)
+  "Apply FUNCTION to all entries in dictionary DICT, for side-effects only.
+
+FUNCTION will be passed two arguments: a word from the
+dictionary, and the data associated with that word. It is safe to
+assume the dictionary entries will be traversed in alphabetical
+order."
+  
+  (if (dic-lookup-only dict)
+      (maphash function (dic-lookup-hash dict))
+    (tstree-map
+     (lambda (word data) (funcall function word (dic-get-data data)))
+     (dic-tstree dict) t))
+)
+
+
+
+(defun dict-mapcar (function dict)
+  "Apply FUNCTION to all entries in dictionary DICT,
+and make a list of the results.
+
+FUNCTION will be passed two arguments: a word from the
+dictionary, and the data associated with that word. It is safe to
+assume the dictionary entries will be traversed in alphabetical
+order."
+  
+  (if (dic-lookup-only dict)
+      (let (result)
+       (maphash `(lambda function (word data)
+                   (cons (,function word data) result))
+                (dic-lookup-hash dict))
+       result)
+    (tstree-map
+     (lambda (word data) (funcall function word (dic-get-data data)))
+     (dic-tstree dict) t t))
+)
+
+
+
+(defun dict-size (dict)
+  "Return the number of entries in dictionary DICT."
+  (interactive (list (read-dict "Dictionary: ")))
+
+  (if (dic-lookup-only dict)
+      (hash-table-size dict)
+    (let ((count 0))
+      (tstree-map (lambda (&rest dummy) (setq count (1+ count)))
+                 (dic-tstree dict))
+      (when (interactive-p)
+       (message "Dictionary %s contains %d entries" (dic-name dict) count))
+      count))
+)
+
+
+
+(defun dict-complete (dict string &optional maxnum all filter no-cache)
+  "Return an alist containing all completions of STRING found in
+dictionary DICT, along with their associated data, in alphabetial
+order. If no completions are found, return nil.
+
+DICT can also be a list of dictionaries, in which case
+completions are sought in all dictionaries in the list, as though
+they were one large dictionary.
+
+STRING can be a single string or a list of strings. If a list is
+supplied, completions of all elements of the list are returned.
+
+The optional numerical argument MAXNUM limits the results to the
+first MAXNUM completions. If it is absent or nil, all completions
+are included in the returned alist.
+
+Normally, only the remaining characters needed to complete STRING
+are returned. If the optional argument ALL is non-nil, the entire
+completion is returned.
+
+The FILTER argument sets a filter function for the
+completions. If supplied, it is called for each possible
+completion with two arguments: the completion, and its associated
+data. If the filter function returns nil, the completion is not
+included in the results.
+
+If the optional argument NO-CACHE is non-nil, it prevents caching
+of the result."
+  
+  (let* ((dictlist (if (dict-p dict) (list dict) dict))
+        dic)
+    (cond
+
+     ;; if a filter was supplied, look in the ternary search tree since we
+     ;; don't cache filtered searches
+     (filter
+      ;; redefine filter to deal with data wrapping
+      (setq filter `(lambda (str data) (,filter str (dic-get-data data))))
+      
+      (let (treelist)
+       (while dictlist
+         (setq dic (pop dictlist))
+         ;; better check that none of the dictionaries in the list are
+         ;; lookup-only
+         (when (dic-lookup-only dic)
+           (error "Dictionary is lookup-only. Completion disabled."))
+         (setq treelist (append (dic-tstree dic) treelist)))
+       ;; search the ternary search tree
+       (tstree-complete treelist string maxnum all filter)))
+     
+
+     ;; if no filter was supplied...
+     (t
+      (let (completions
+           strlist str
+           cache cmpl
+           time speed)
+       ;; search each dictionary in the list
+       (while dictlist
+         (setq dic (pop dictlist))
+         ;; throw a wobbly if dictionary is lookup-only
+         (when (dic-lookup-only dic)
+           (error "Dictionary is lookup-only. Completion disabled."))
+         
+         ;; search each string in the list
+         (setq strlist (if (stringp string) (list string) string))
+         (while strlist
+           (setq str (pop strlist))
+           
+           ;; look in completion cache first
+           (setq cache (if (dic-completion-speed dic)
+                           (gethash str (dic-completion-hash dic))
+                         nil))
+           
+           ;; if we've found a cached result with enough completions...
+           (if (and cache (or (null (dic-cache-maxnum cache))
+                              (and (not (null maxnum))
+                                   (<= maxnum (dic-cache-maxnum cache)))))
+               (progn
+                 (setq cmpl (dic-cache-completions cache))
+                 ;; drop any excess cached completions
+                 (when (and maxnum (> (length cmpl) maxnum))
+                   (setcdr (nthcdr (1- maxnum) cmpl) nil)))
+             
+             ;; if nothing was in the cache or the cached result contained
+             ;; fewer completions than asked for, look in the ternary search
+             ;; tree and time it
+             (setq time (float-time))
+             (setq cmpl (tstree-complete (dic-tstree dic) str maxnum))
+             (setq time (- (float-time) time))
+             ;; if the completion function was slower than the dictionary's
+             ;; completion speed, add the results to the completion hash and
+             ;; set the dictionary's modified flag
+             (when (and (not no-cache)
+                        (setq speed (dic-completion-speed dic))
+                        (or (eq speed t) (> time speed)))
+               (dic-set-modified dic t)
+               (puthash str (dic-cache-create cmpl maxnum)
+                        (dic-completion-hash dic))))
+           
+           ;; unwrap data, and add string to the fronts of the completions if
+           ;; ALL is set
+           ;;  and add string to the fronts of the completions if ALL is set
+           (when all
+             (setq cmpl
+                   (mapcar (lambda (s) (cons (concat str (car s)) (cdr s)))
+                           cmpl)))
+           ;; merge the cached completions with those already found
+           (setq completions
+                 (merge 'list completions cmpl
+                        (lambda (a b) (string< (car a) (car b)))))
+           ;; drop any excess completions
+           (when (and maxnum (> (length completions) maxnum))
+             (setcdr (nthcdr (1- maxnum) completions) nil))
+           ))
+       ;; return the completions list, unwrapping the data
+       (mapcar (lambda (c) (cons (car c) (dic-get-data (cdr c))))
+               completions)
+       ))))
+)
+
+
+
+
+
+(defun dict-complete-ordered
+  (dict string &optional maxnum all rank-function filter no-cache)
+  "Return an alist containing all completions of STRING found in
+dictionary DICT, along with their associated data. If no
+completions are found, return nil.
+
+Note that `dict-complete' is significantly more efficient than
+`dict-complete-ordered', especially when a maximum number of
+completions is specified. Always use `dict-complete' when you
+don't care about the ordering of the completions, or you need the
+completions ordered alphabetically.
+
+DICT can also be a list of dictionaries, in which case
+completions are sought in all trees in the list. If RANK-FUCTION
+is ot specified, the rank function of the first dictionary in the
+list is used. All the dictionaries' rank functions had better be
+compatible, otherwise at best you will get unexpected results, at
+worst errors.
+
+STRING must either be a single string, or a list of strings. If a
+list is supplied, completions of all elements of the list are
+included in the returned alist.
+
+The optional numerical argument MAXNUM limits the results to the
+\"best\" MAXNUM completions. If nil, all completions are
+returned.
+
+Normally, only the remaining characters needed to complete STRING
+are returned. If the optional argument ALL is non-nil, the entire
+completion is returned.
+
+The optional argument RANK-FUNCTION over-rides the dictionary's
+default rank function. It should take two arguments, each a cons
+whose car is a string referencing data in the tree, and whose cdr
+is the data at that reference. It should return non-nil if the
+first argument is \"better than\" the second, nil otherwise. The
+elements of the returned list are sorted according to this
+rank-function, in descending order.
+
+The FILTER argument sets a filter function for the
+completions. If supplied, it is called for each possible
+completion with two arguments: the completion, and its associated
+data. If the filter function returns nil, the completion is not
+included in the results.
+
+If the optional argument NO-CACHE is non-nil, it prevents caching
+of the result."
+  
+  (let ((dictlist (if (dict-p dict) (list dict) dict))
+       dic rankfun)
+    (cond
+
+     ;; if the default rank function has been over-ridden or a filter
+     ;; supplied, look in the ternary search tree since we don't cache
+     ;; non-default rank functions or filtered searches
+     ((or rank-function filter)
+      ;; redefine the rank function and filter to deal with data wrapping
+      (setq rankfun (eval (macroexpand `(dic-wrap-rankfun ,rank-function))))
+      (setq filter (eval (macroexpand `(dic-wrap-filter ,filter))))
+      
+      (let (treelist)
+       (while dictlist
+         (setq dic (pop dictlist))
+         ;; better check that none of the dictionaries in the list are
+         ;; lookup-only
+         (when (dic-lookup-only dic)
+           (error "Dictionary is lookup-only. Completion disabled."))
+         (setq treelist (append (dic-tstree dic) treelist)))
+       ;; search the ternary search tree
+       (tstree-complete-ordered treelist string maxnum all
+                                rankfun filter)))
+     
+     
+     ;; if we're using the dictionary's default rank-function...
+     ;; (Note: we use the rank function of first dict in list, and hope it's
+     ;;        compatible with the data in the other dictionaries)
+     (t
+      (let ((rankfun (dic-rankfun (car dictlist)))
+            completions
+            strlist str
+            cache cmpl
+            time speed)
+       
+       ;; search each dictionary in the list
+       (while dictlist
+         (setq dic (pop dictlist))
+          ;; throw a wobbly if dictionary is lookup-only
+         (when (dic-lookup-only dic)
+           (error "Dictionary is lookup-only. Completion disabled."))
+         
+          ;; search each string in the list
+         (setq strlist (if (stringp string) (list string) string))
+         (while strlist
+           (setq str (pop strlist))
+
+           
+           ;; look in completion cache first
+           (setq cache (if (dic-ordered-speed dic)
+                           (gethash str (dic-ordered-hash dic))
+                         nil))
+           
+           ;; if we've found a cached result with enough completions...
+           (if (and cache (or (null (dic-cache-maxnum cache))
+                              (and (not (null maxnum))
+                                   (<= maxnum (dic-cache-maxnum cache)))))
+               (progn
+                 (setq cmpl (dic-cache-completions cache))
+                 ;; drop any excess cached completions
+                 (when (and maxnum (> (length cmpl) maxnum))
+                   (setcdr (nthcdr (1- maxnum) cmpl) nil)))
+             
+             ;; if nothing was in the cache or the cached result didn't
+             ;; contain enough completions, search tree and time the search
+             (setq time (float-time))
+             (setq cmpl (tstree-complete-ordered (dic-tstree dic)
+                                                 str maxnum nil rankfun))
+             (setq time (- (float-time) time))
+             ;; if the completion function was slower than the dictionary's
+             ;; completion speed, add the results to the completion hash and
+             ;; set the dictionary's modified flag
+             (when (and (not no-cache)
+                        (setq speed (dic-ordered-speed dic))
+                        (or (eq speed t) (> time speed)))
+               (dic-set-modified dic t)
+               (puthash str (dic-cache-create cmpl maxnum)
+                        (dic-ordered-hash dic))))
+           
+           ;;  and add string to the fronts of the completions if ALL is set
+           (when all
+             (setq cmpl
+                   (mapcar (lambda (s) (cons (concat str (car s)) (cdr s)))
+                           cmpl)))
+           ;; merge the cached completions with those already found
+           (setq completions (merge 'list completions cmpl rankfun))
+           ;; drop any excess completions
+           (when (and maxnum (> (length completions) maxnum))
+             (setcdr (nthcdr (1- maxnum) completions) nil))
+           ))
+       
+        ;; return the completions list, unwrapping the data
+       (mapcar (lambda (c) (cons (car c) (dic-get-data (cdr c))))
+               completions)
+       ))))
+)
+
+
+
+
+(defun dict-populate-from-file (dict file)
+  "Populate dictionary DICT from the word list in file FILE. Each
+line of the file should contain a word, delimeted by \"\". Use
+the escape sequence \\\" to include a \" in the string. If a line
+does not contain a delimeted string, it is silently ignored. The
+words should ideally be sorted alphabetically.
+
+Each line can also include data to be associated with the word,
+separated from the word by whitespace. Anything after the
+whitespace is considered data. String data should be
+\"\"-delimited, and must be on a single line. However, the escape
+sequence \"\\n\" can be used to include a newline, the escape
+sequence \\\" can again be used to include a \", and the escape
+sequence \\\\ must be used to include a \\.
+
+
+Technicalities:
+
+The word and data can actually be separated by any character that
+is not a word-constituent according to the standard syntax
+table. However, you're safest sticking to whitespace.
+
+The data is read as a lisp expression and evaluated, so can be
+more complex than a simple constant. However, it must be entirely
+on one line. The symbol \"_word\" can be used to refer to the
+word associated with the data.
+
+The word list is read from the middle outwards, i.e. first the
+middle word is read, then the word directly after it, then the
+word directly before it, then the one two lines after the middle,
+and so on. Assuming the words in the file are sorted
+alphabetically, this helps produce a reasonably efficient
+dictionary. However, it may have implications if the data is a
+lisp expression that has side-effects."
+  
+  (save-excursion
+    (let ((buff (generate-new-buffer " *dict-populate*")))
+      ;; insert the word list into a temporary buffer
+      (set-buffer buff)
+      (insert-file-contents file)
+      
+      ;; insert the words starting from the median to ensure a well-balanced
+      ;; tree
+      (let* ((lines (count-lines (point-min) (point-max)))
+            (midpt (+ (/ lines 2) (mod lines 2)))
+            entry)
+        ;; insert the median word and set the dictionary's modified flag
+       (goto-line midpt)
+       (when (setq entry (dict-read-line))
+         (dict-insert dict (car entry) (nth 1 entry))
+         (dict-set-meta-data dict (car entry) (nth 2 entry)))
+       (message "Inserting words in %s...(1 of %d)" (dic-name dict) lines)
+        ;; insert words successively further away from the median in both
+        ;; directions
+       (dotimes (i (1- midpt))
+         (goto-line (+ midpt i 1))
+         (when (setq entry (dict-read-line))
+           (dict-insert dict (car entry) (nth 1 entry))
+           (dict-set-meta-data dict (car entry) (nth 2 entry)))
+         (when (= 49 (mod i 50))
+           (message "Inserting words in %s...(%d of %d)"
+                    (dic-name dict) (+ (* 2 i) 2) lines))
+         (goto-line (- midpt i 1))
+         (when (setq entry (dict-read-line))
+           (dict-insert dict (car entry) (nth 1 entry))
+           (dict-set-meta-data dict (car entry) (nth 2 entry))))
+       
+        ;; if file contains an even number of words, we still have to add
+        ;; the last one
+       (when (= 0 (mod lines 2))
+         (goto-line lines)
+         (when (setq entry (dict-read-line))
+           (dict-insert dict (car entry) (nth 1 entry))
+           (dict-set-meta-data dict (car entry) (nth 2 entry))))
+       (message "Inserting words in %s...done" (dic-name dict)))
+      
+      (kill-buffer buff)))
+)
+
+
+
+;;; FIXME: doesn't fail gracefully if file has invalid format
+(defun dict-read-line ()
+  "Return a cons containing the word 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 (_word data meta-data)
+      ;; search for text between quotes "", ignoring escaped quotes \"
+      (beginning-of-line)
+      (setq _word (read (current-buffer)))
+      ;; if there is anything after the quoted text, use it as data
+      (if (eq (line-end-position) (point))
+         (list _word)
+       (setq data (eval (read (current-buffer))))
+       (if (eq (line-end-position) (point))
+           (list _word data)
+         (setq meta-data (read (current-buffer)))
+         ;; return the word and data
+         (list _word data meta-data)))
+      ))
+)
+
+
+
+
+(defun dict-save (dict)
+  "Save dictionary DICT to it's associated file.
+Use `dict-write' to save to a different file."
+  (interactive (list (read-dict "Dictionary to save: ")))
+  
+  (let* ((filename (dic-filename dict)))
+    
+    ;; if dictionary has no associated file, prompt for one
+    (unless (and filename (> (length filename) 0))
+      (setq filename
+           (read-file-name (format "Save %s to file: " (dic-name dict)))))
+    
+    ;; if filename is blank, don't save
+    (if (string= filename "")
+       (message "Dictionary %s NOT saved" (dic-name dict))
+      ;; otherwise write dictionary to file without requiring confirmation
+      (dict-write dict filename t)))
+)
+
+
+
+
+(defun dict-write (dict filename &optional overwrite uncompiled)
+  "Write dictionary DICT to file FILENAME.
+
+If optional argument OVERWRITE is non-nil, no confirmation will
+be asked for before overwriting an existing file.
+
+If optional argument UNCOMPILED is set, an uncompiled copy of the
+dictionary will be created.
+
+Interactivley, DICT and FILENAME are read from the minibuffer,
+and OVERWRITE is the prefix argument."
+  
+  (interactive (list (read-dict "Dictionary to write: ")
+                    (read-file-name "File to write to: ")
+                    current-prefix-arg))
+
+  (let* (dictname  ; saved dictionary name is constructed from the filename
+        (autosave (dic-autosave dict))
+        (lookup-only (dic-lookup-only dict))
+        lookup-speed completion-speed ordered-speed
+        tmpdict lookup-alist completion-alist ordered-alist
+        hashcode buff tmpfile)
+    
+    ;; add .el(c) extension to the filename if not already there
+    (if uncompiled
+       (unless (string= (substring filename -3) ".el")
+         (setq filename (concat filename ".el")))
+      (unless (string= (substring filename -4) ".elc")
+       (setq filename (concat filename ".elc"))))
+    ;; remove .el(c) extension from filename to create saved dictionary name
+    (setq dictname (if uncompiled
+                      (substring (file-name-nondirectory filename) 0 -3)
+                    (substring (file-name-nondirectory filename) 0 -4)))
+    
+    (save-excursion
+      ;; create a temporary file
+      (setq buff (find-file-noselect
+                 (setq tmpfile (make-temp-file dictname))))
+      (set-buffer buff)
+      
+      ;; if the dictionary is lookup only, dump the lookup cache to an alist
+      (if lookup-only
+         (progn
+           (maphash (lambda (key val) (push (cons key val) lookup-alist))
+                    (dic-lookup-hash dict))
+           ;; generate code to reconstruct the lookup hash table
+           (setq hashcode
+                 (concat
+                  "(let ((lookup-hash (make-hash-table :test 'equal)))\n"
+                  "  (mapcar (lambda (entry)\n"
+                  "    (puthash (car entry) (cdr entry) lookup-hash))\n"
+                   "    (dic-lookup-hash " dictname "))\n"
+                  "  (dic-set-lookup-hash " dictname " lookup-hash)\n"))
+           ;; generate the structure to save
+           (setq tmpdict (list 'DICT dictname filename autosave
+                               (dic-insfun dict) lookup-only lookup-alist)))
+       
+       
+       ;; otherwise, dump caches to alists as necessary and generate code to
+       ;; reonstruct the hash tables from the alists
+       (setq lookup-speed (dic-lookup-speed dict)
+             completion-speed (dic-completion-speed dict)
+             ordered-speed (dic-ordered-speed dict))
+       
+       ;; create the lookup alist, if necessaru
+       (when lookup-speed
+         (maphash (lambda (key val) (push (cons key val) lookup-alist))
+                  (dic-lookup-hash dict))
+         ;; generate code to reconstruct the lookup hash table
+         (setq hashcode
+               (concat
+                hashcode
+                "(let ((lookup-hash (make-hash-table :test 'equal)))\n"
+                "  (mapcar (lambda (entry)\n"
+                "    (puthash (car entry) (cdr entry) lookup-hash)\n"
+                "    (dic-lookup-hash " dictname ")))\n"
+                "  (dic-set-lookup-hash " dictname " lookup-hash))\n")))
+       
+       ;; create the completion alist, if necessary
+       (when completion-speed
+         (maphash (lambda (key val) (push (cons key val) completion-alist))
+                  (dic-completion-hash dict))
+         ;; generate code to reconstruct the completion hash table
+         (setq hashcode
+               (concat
+                hashcode
+                "(let ((completion-hash (make-hash-table :test 'equal)))\n"
+                "  (mapcar (lambda (entry)\n"
+                "    (puthash (car entry) (cdr entry) completion-hash)\n"
+                "    (dic-completion-hash " dictname ")))\n"
+                "  (dic-set-completion-hash " dictname " completion-hash))"
+                "\n")))
+       
+       ;; create the ordered completion alist, if necessary
+       (when ordered-speed
+         (maphash (lambda (key val) (push (cons key val) ordered-alist))
+                  (dic-ordered-hash dict))
+         ;; generate code to reconstruct the ordered hash table
+         (setq hashcode
+               (concat
+                hashcode
+                "(let ((ordered-hash (make-hash-table :test 'equal)))\n"
+                "  (mapcar (lambda (entry)\n"
+                "    (puthash (car entry) (cdr entry) ordered-hash))\n"
+                "    (dic-ordered-hash " dictname "))\n"
+                "  (dic-set-ordered-hash " dictname " ordered-hash))\n")))
+       
+       ;; generate the structure to save
+       (setq tmpdict (list 'DICT nil nil autosave nil
+                           (dic-tstree dict) lookup-only
+                           lookup-alist lookup-speed
+                           completion-alist completion-speed
+                           ordered-alist ordered-speed))
+      )
+      
+      
+      ;; write lisp code that generates the dictionary object
+      (insert "(provide '" dictname ")\n")
+      (insert "(require 'dict-tree)\n")
+      (insert "(defvar " dictname " nil \"Dictionary " dictname ".\")\n")
+      (insert "(setq " dictname " '" (prin1-to-string tmpdict) ")\n")
+      (insert hashcode)
+      (insert "(dic-set-name " dictname " \"" dictname "\")\n")
+      (insert "(dic-set-filename " dictname
+             " (locate-library \"" dictname "\"))\n")
+      (insert "(unless (memq " dictname " dict-loaded-list)"
+             " (push " dictname " dict-loaded-list))\n")
+      (save-buffer)
+      (kill-buffer buff)
+       
+      ;; byte-compile the code (unless uncompiled option is set) and move the
+      ;; file to its final destination
+      (if (or uncompiled (save-window-excursion (byte-compile-file tmpfile)))
+         (progn
+           (when (or (not (file-exists-p filename))
+                     overwrite
+                     (y-or-n-p
+                      (format "File %s already exists. Overwrite? "
+                              filename)))
+             (if uncompiled
+                 (rename-file tmpfile filename t)
+               ;; if writing a compiled version, associate dictionary with
+               ;; the new file and mark it as unmodified
+               (rename-file (concat tmpfile ".elc") filename t)
+               (dic-set-filename dict filename)
+               (dic-set-modified dict nil)
+               (delete-file tmpfile)
+               )
+             (message "Dictionary %s saved to %s" dictname filename)
+             t))  ; return t if dictionary was successfully saved
+       ;; if there were errors compiling, throw error
+       (error "Error saving %s. Dictionary not saved" dictname))
+      ))
+)
+
+
+
+
+(defun dict-save-modified (&optional ask all)
+  "Save all modified dictionaries that have a non-nil autosave flag.
+
+If optional argument ASK is non-nil, ask for confirmation before
+saving. Interactively, ASK is the prefix argument.
+
+If optional argument ALL is non-nil, save all dictionaries, even
+those without the autosave flag."
+  (interactive "P")
+  ;; For each loaded dictionary, check if dictionary has been modified. If so,
+  ;; save it if autosave is on
+  (dolist (dict dict-loaded-list)
+    (when (and (dic-modified dict)
+              (or all (dic-autosave dict))
+              (or (not ask) (y-or-n-p (format "Save modified dictionary %s? "
+                                              (dic-filename dict)))))
+      (dict-save dict)
+      (dic-set-modified dict nil)))
+)
+
+
+
+
+(defun dict-load (file)
+  "Load a dictionary object from file FILE.
+Returns t if successful, nil otherwise."
+  (interactive "fDictionary file to load: ")
+  
+  ;; sort out dictionary name and file name
+  (let (dictname dict)
+    (when (not (string= (substring file -4) ".elc"))
+      (setq file (concat file ".elc")))
+    (setq dictname (substring (file-name-nondirectory file) 0 -4))
+    
+    ;; load the dictionary
+    (load file t)
+    (setq dict (eval (intern-soft dictname)))
+    (when (not (dict-p dict))
+      (beep)
+      (error "Error loading dictionary from %s" file))
+    
+    ;; ensure the dictionary name and file name associated with the dictionary
+    ;; match the file it was loaded from
+    (dic-set-filename dict (expand-file-name file))
+    (dic-set-name dict dictname)
+    
+    ;; make sure the dictionary is in dict-loaded-list (normally the lisp code
+    ;; in the dictionary itself should do that)
+    (unless (memq dict dict-loaded-list) (push dict dict-loaded-list))
+    (message (format "Loaded dictionary %s" dictname)))
+)
+
+
+
+
+(defun dict-unload (dict &optional dont-save)
+  "Unload dictionary DICT.
+If optional argument DONT-SAVE is non-nil, the dictionary will
+NOT be saved even if its autosave flag is set."
+  (interactive (list (read-dict "Dictionary to unload: ")
+                    current-prefix-arg))
+  
+  ;; if dictionary has been modified, autosave is set and not overidden, save
+  ;; it first
+  (when (and (dic-modified dict)
+            (null dont-save)
+            (or (eq (dic-autosave dict) t)
+                (and (eq (dic-autosave dict) 'ask)
+                     (y-or-n-p
+                      (format
+                       "Dictionary %s modified. Save before unloading? "
+                       (dic-name dict))))))
+    (dict-save dict)
+    (dic-set-modified dict nil))
+  
+  ;; remove dictionary from list of loaded dictionaries and unload it
+  (setq dict-loaded-list (delq dict dict-loaded-list))
+  (unintern (dic-name dict))
+  (message "Dictionary %s unloaded" (dic-name dict))
+)
+
+
+
+
+(defun dict-dump-words-to-buffer (dict &optional buffer)
+  "Dump words and their associated data
+from dictionary DICT to BUFFER, in the same format as that used
+by `dict-populate-from-file'. If BUFFER exists, words will be
+appended to the end of it. Otherwise, a new buffer will be
+created. If BUFFER is omitted, the current buffer is used.
+
+Note that if the data does not have a read syntax, the dumped
+data can not be used to recreate the dictionary using
+`dict-populate-from-file'."
+  
+  (interactive (list (read-dict "Dictionary to dump: ")
+                    (read-buffer "Buffer to dump to: "
+                                 (buffer-name (current-buffer)))))
+  
+  ;; 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 words
+  (message "Dumping words from %s to %s..."
+          (dic-name dict) (buffer-name buffer))
+  (let ((count 0) (dictsize (dict-size dict)))
+    (message "Dumping words from %s to %s...(word 1 of %d)"
+            (dic-name dict) (buffer-name buffer) dictsize)
+    ;; construct dump function
+    (let ((dump-func
+          (lambda (word cell)
+            (when (= 99 (mod count 100))
+              (message "Dumping words from %s to %s...(word %d of %d)"
+                       (dic-name dict) (buffer-name buffer)
+                       (1+ count) dictsize))
+            (insert "\"" word "\"")
+            (let (data)
+              (when (setq data (dic-get-data cell))
+                (insert " " (prin1-to-string data)))
+              (when (setq data (dic-get-metadata cell))
+                (insert " " (prin1-to-string data)))
+              (insert "\n"))
+            (setq count (1+ count)))))
+      ;; map dump function over dictionary
+      (if (dic-lookup-only dict)
+         (maphash dump-func (dic-lookup-hash dict))
+       (tstree-map dump-func (dic-tstree dict) t)))
+    (message "Dumping words from %s to %s...done"
+            (dic-name dict) (buffer-name buffer)))
+  (switch-to-buffer buffer)
+)
+
+
+
+
+(defun dict-dump-words-to-file (dict filename &optional overwrite)
+  "Dump words and their associated data
+from dictionary DICT to a text file FILENAME, in the same format
+as that used by `dict-populate-from-file'.
+
+Note that if the data does not have a read syntax, the dumped
+data can not be used to recreate the dictionary using
+`dict-populate-from-file'."
+  
+  (interactive (list (read-dict "Dictionary to dump: ")
+                    (read-file-name "File to dump to: ")
+                    current-prefix-arg))
+
+  (let (buff)
+    ;; create temporary buffer and dump words to it
+    (setq buff (generate-new-buffer filename))
+    (save-window-excursion
+      (dict-dump-words-to-buffer dict buff)
+      
+      ;; save file, prompting to overwrite if necessary
+      (if (and (file-exists-p filename)
+              (not overwrite)
+              (not (y-or-n-p
+                    (format "File %s already exists. Overwrite? " filename))))
+         (message "Word dump cancelled")
+       (write-file filename))
+      (kill-buffer buff)))
+)
+
+
+
+(defvar dict-history nil
+  "History list for commands that read an existing ditionary name.")
+
+
+(defun read-dict (prompt &optional default)
+  "Read the name of a dictionary with completion, and return it.
+Prompt with PROMPT. By default, return DEFAULT."
+  (let (dictlist)
+    (mapc (lambda (dict)
+           (unless (or (null (dic-name dict))
+                       (member (dic-name dict) dictlist))
+             (push (dic-name dict) dictlist)))
+         dict-loaded-list)
+    (eval (intern-soft
+          (completing-read prompt dictlist
+                           nil t nil 'dict-history default))))
+)
+
+
+
+;; Add the dict-save-modified function to the kill-emacs-hook to save modified
+;; dictionaries when exiting emacs
+(add-hook 'kill-emacs-hook 'dict-save-modified)
+
+
+
+;;; dict-tree.el ends here



reply via email to

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