[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
- [elpa] branch externals/dict-tree created (now 9242ff7), Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree ae0dc0e 001/154: Version 0.10 of the predictive completion package.,
Stefan Monnier <=
- [elpa] externals/dict-tree d86829b 007/154: Fixed bugs in predictive's auto-learn and which dict mode features., Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 47f8163 010/154: Made saving dictionaries to compiled or uncomiled forms a customization option., Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 9e6d0f2 017/154: Fixed bug when deleting non-existent entries., Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 4cd369d 027/154: Avoid breaking setf abstraction in dictree--wrap-insfun., Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 9827131 016/154: Fixed unwrapped rank-function bug in dictree--query, Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 87bb7e8 019/154: Allow custom write and load functions when saving and dumping dictionaries;, Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 2978fc7 024/154: ...and already a bug-fix to the new cache updating code, Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 7d9b258 033/154: Improved handling of dictionary file names in dictree-load, Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree f46da76 008/154: Minor bug fixes., Stefan Monnier, 2020/12/14
- [elpa] externals/dict-tree 405d11b 023/154: Implemented the other cache and cache-update policies, Stefan Monnier, 2020/12/14