summaryrefslogtreecommitdiff
path: root/packages/dict-tree
diff options
context:
space:
mode:
authorToby S. Cubitt <tsc25@cantab.net>2012-04-29 13:45:44 +0200
committerToby S. Cubitt <tsc25@cantab.net>2012-04-29 13:45:44 +0200
commit85daf9db8bacbf4932671398da24d169a6b80a38 (patch)
tree5218d159ff971360aca89c198b37a369f0a9e8f3 /packages/dict-tree
parent9615c7f2e73b429feffd5387744600294f0974f4 (diff)
downloademacs-85daf9db8bacbf4932671398da24d169a6b80a38.tar.gz
Add dict-tree.el
Diffstat (limited to 'packages/dict-tree')
-rw-r--r--packages/dict-tree/dict-tree.el3584
1 files changed, 3584 insertions, 0 deletions
diff --git a/packages/dict-tree/dict-tree.el b/packages/dict-tree/dict-tree.el
new file mode 100644
index 00000000000..436a75ff5a2
--- /dev/null
+++ b/packages/dict-tree/dict-tree.el
@@ -0,0 +1,3584 @@
+;;; dict-tree.el --- dictionary data structure package
+
+
+;; Copyright (C) 2004-2012 Free Software Foundation, Inc
+
+;; Author: Toby Cubitt <toby-predictive@dr-qubit.org>
+;; Version: 0.12.7
+;; Keywords: extensions, matching, data structures
+;; trie, tree, dictionary, completion, regexp
+;; Package-Requires: ((trie "0.2.5") (tNFA "0.1.1") (heap "0.3"))
+;; URL: http://www.dr-qubit.org/emacs.php
+
+;; This file is part of Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or (at your option)
+;; any later version.
+;;
+;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+
+;;; Commentary:
+;;
+;; A dictionary is used to store strings, along with arbitrary data associated
+;; with each string. As well as basic data insertion, manipulation and
+;; retrieval, a dictionary can perform prefix searches on those strings,
+;; retrieving all strings with a given prefix in either alphabetical or any
+;; other order (see the `dictree-complete' and `dictree-complete-ordered'
+;; functions), and is able to cache results in order to speed up those
+;; searches. The package also provides persistent storage of the data
+;; structures to files.
+;;
+;; You create a dictionary using `dictree-create', add entries to it using
+;; `dictree-insert', lookup entries using `dictree-lookup', find completions
+;; of sequences using `dictree-complete', find completions and sort them in
+;; any order you speficy using `dictree-complete-ordered', map over it using
+;; `dictree-map' and `dictree-mapcar', save it to a file using `dictree-save'
+;; or `dictree-write', and load from file it using `dictree-load'. Various
+;; other useful functions are also provided.
+;;
+;; This package uses the trie package trie.el. the tagged NFA package tNFA.el,
+;; and the heap package heap.el.
+
+
+;;; Change Log:
+;;
+;; Version 0.12.7
+;; * create defstruct copier functions for dict-trees and meta-dict-trees
+;; * don't transform hash tables to alists when writing dictionaries if
+;; running in an Emacs version that supports print-readable hash tables
+;; * simplified `dictree-write', `dictree--write-dict-code' and
+;; `dictree--write-meta-dict-code'
+;;
+;; Version 0.12.6
+;; * replaced obsolete `interactive-p' with `called-interactively-p'
+;;
+;; Version 0.12.5
+;; * fixed default value handling in `read-dict'
+;;
+;; Version 0.12.4
+;; * minor bug-fix to `dictree--edebug-pretty-print' to print "nil" instead
+;; of "()"
+;; * modified `dictree-save-modified' to catch errors when saving
+;; dictionaries, and indicate failures via its return value
+;; * removed `dictree-save-modified' from `kill-emacs-hook' and added it
+;; instead to `kill-emacs-query-functions', so that dictionary save failures
+;; don't make it impossible to quit Emacs
+;; * fixed bug in `dictree--merge' that caused it to retain one too many list
+;; elements for non-null MAXNUM
+;; * fixed `dictree--update-cache', which previously never updated cached
+;; results for lists of prefixes in `dictree-complete' queries
+;; * fixed implementation of 'both cache policy
+;; * fixed bug in `read-dict' preventing completion on dictionary files
+;; in `load-path'
+;; * fixed bugs in synchronisation of regexp query caches, renaming
+;; `dictree--synchronise-query-cache' and
+;; `dictree--synchronise-ranked-query-cache' to
+;; `dictree--synchronise-completion-cache' and
+;; `dictree--synchronise-ranked-completion-cache', and creating separate
+;; `dictree--synchronise-regexp-cache' and
+;; `dictree--synchronise-ranked-regep-cache' functions to handle regexp
+;; query caches
+;;
+;; Version 0.12.3
+;; * bug-fix in `dictree--edebug-pretty-print'
+;;
+;; Version 0.12.2
+;; * bug-fix to DEFAULT argument handling in `read-dict'
+;;
+;; Version 0.12.1
+;; * added `edebug-prin1' and `edebug-prin1-to-string' advice to prevent
+;; edebug hanging whilst printing large dictionaries
+;;
+;; Version 0.12
+;; * complete rewrite using new trie.el library
+;;
+;; Note: version 0.11.1 dictionaries not compatible with version 0.12 and
+;; above
+;;
+;; Version 0.11.1
+;; * set and restore value of `byte-compile-disable-print-circle' instead of
+;; let-binding it, to avoid warnings when compiling
+;; * added `dictree-goto-line' macro to work around `goto-line' bug
+;;
+;; Version 0.11
+;; * modified `dictree-write' so that, by default, both compiled and
+;; uncompiled versions of dictionaries are created when writing dictionaries
+;; to file
+;; * fixed slow byte-compilation under Emacs 22
+;;
+;; Version 0.10.2
+;; * very minor changes to text of some messages
+;;
+;; Version 0.10.1
+;; * added optional DICTLIST argument to `read-dict', to allow completion from
+;; a restricted set of dictionaries
+;;
+;; Version 0.10
+;; * finally wrote a `dictree-delete' function!
+;;
+;; Version 0.9.1
+;; * fixed bug in `dictree-dump-words-to-buffer' (thanks to Dan Pomohaci for
+;; reporting it)
+;; * replaced "word" with "key" in function arguments and docstrings, since
+;; keys don't have to be words
+;; * removed "words" from dump functions' names, added TYPE argument in line
+;; with other functions, and made them non-interactive
+;; * added COMPARE-FUNCTION argument to `dictree-create', which defaults to
+;; subtraction as before
+;; * `dictree-read-line' reads the keys with `read', and no longer evals the
+;; data as this fails for simple, useful cases (e.g. constant lists)
+;;
+;; Version 0.9
+;; * added meta-dictionary functionality
+;; * dictionary data can now be referenced by any sequence type, not just
+;; strings * removed cl dependency
+;;
+;; Note: version 0.8 dictionaries not compatible with version 0.9 and above
+;;
+;; Version 0.8.4
+;; * fixed small bug in `read-dict'
+;;
+;; Version 0.8.3
+;; * fixed internal function and macro names
+;; * changed naming prefix from dict- to dictree- to avoid conflicts
+;; * `dict-write' now unloads old name and reloads new
+;;
+;; Version 0.8.2
+;; * added more commentary
+;;
+;; Version 0.8.1
+;; * fixed nasty bug in `dict-map' and `dict-mapcar' caused by dynamic scoping
+;;
+;; 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:
+
+(eval-when-compile (require 'cl))
+(require 'trie)
+(require 'tNFA)
+(require 'bytecomp)
+
+
+
+;;; ================================================================
+;;; Replacements for CL and Elisp functions
+
+;; copied from cl-extra.el
+(defun dictree--subseq (seq start &optional end)
+ "Return the subsequence of SEQ from START to END.
+If END is omitted, it defaults to the length of the sequence.
+If START or END is negative, it counts from the end."
+ (if (stringp seq) (substring seq start end)
+ (let (len)
+ (and end (< end 0) (setq end (+ end (setq len (length seq)))))
+ (when (< start 0)
+ (setq start (+ start (or len (setq len (length seq))))))
+ (cond ((listp seq)
+ (if (> start 0) (setq seq (nthcdr start seq)))
+ (if end
+ (let ((res nil))
+ (while (>= (setq end (1- end)) start)
+ (push (pop seq) res))
+ (nreverse res))
+ (copy-sequence seq)))
+ (t
+ (or end (setq end (or len (length seq))))
+ (let ((res (make-vector (max (- end start) 0) nil))
+ (i 0))
+ (while (< start end)
+ (aset res i (aref seq start))
+ (setq i (1+ i) start (1+ start)))
+ res))))))
+
+
+
+;; `goto-line' without messing around with mark and messages
+;; Note: This is a bug in simple.el. There's clearly a place for
+;; non-interactive calls to goto-line from Lisp code, and there's
+;; no warning against doing this in the documentation. Yet
+;; goto-line *always* calls push-mark, which usually *shouldn't*
+;; be invoked by Lisp programs, as its docstring warns.
+(defmacro dictree--goto-line (line)
+ "Goto line LINE, counting from line 1 at beginning of buffer."
+ `(progn
+ (goto-char 1)
+ (if (eq selective-display t)
+ (re-search-forward "[\n\C-m]" nil 'end (1- ,line))
+ (forward-line (1- ,line)))))
+
+
+
+;;; ====================================================================
+;;; Internal functions and variables for use in the dictionary package
+
+(defvar dictree-loaded-list nil
+ "Stores list of loaded dictionaries.")
+
+
+;; ----------------------------------------------------------------
+;; Dictionary data cell structures
+
+;; Note: It would be more elegant to use a defstruct for the data cells,
+;; but the problem is that the resulting setf in
+;; `dictree--wrap-insfun' won't get expanded into the cell-data
+;; accessor function at compile-time because it's burried inside a
+;; backquote construct. Not only is it inelegant to have to expand
+;; macros at run-time whenever `dictree--wrap-insfun' is called,
+;; but it also requires the 'cl-macs package to be loaded at
+;; run-time rather than just at compile-time. We could use
+;; `lexical-let' instead, but it doesn't seem worth it here.
+
+;; wrap data in a cons cell
+(defalias 'dictree--cell-create 'cons) ; INTERNAL USE ONLY
+
+;; get data component from data cons cell
+(defalias 'dictree--cell-data 'car) ; INTERNAL USE ONLY
+
+;; get property list component from data cons cell
+(defalias 'dictree--cell-plist 'cdr) ; INTERNAL USE ONLY
+
+;; set data component of data cons cell
+(defalias 'dictree--cell-set-data 'setcar) ; INTERNAL USE ONLY
+
+;; set property list component of data cons cell
+(defalias 'dictree--cell-set-plist 'setcdr) ; INTERNAL USE ONLY
+
+;; define setf methods so we can use setf abstraction wherever possible
+(defsetf dictree--cell-data dictree--cell-set-data)
+(defsetf dictree--cell-plist dictree--cell-set-plist)
+
+
+;; ----------------------------------------------------------------
+;; Dictionary cache entry structures
+
+;; Note: We *could* us a defstruct for the cache entries, but for
+;; something this simple it doesn't seem worth it, especially
+;; given that we're using the defalias approach anyway for the
+;; data cells (above).
+
+;; Construct and return a completion cache entry
+(defalias 'dictree--cache-create 'cons) ; INTERNAL USE ONLY
+
+;; Return the completions list for cache entry CACHE
+(defalias 'dictree--cache-results 'car) ; INTERNAL USE ONLY
+
+;; Return the max number of completions returned for cache entry CACHE
+(defalias 'dictree--cache-maxnum 'cdr) ; INTERNAL USE ONLY
+
+;; Set the completions list for cache entry CACHE
+(defalias 'dictree--cache-set-completions 'setcar) ; INTERNAL USE ONLY
+
+;; Set the completions list for cache entry CACHE
+(defalias 'dictree--cache-set-maxnum 'setcdr) ; INTERNAL USE ONLY
+
+
+;; ----------------------------------------------------------------
+;; Wrapping functions
+
+(defun dictree--wrap-insfun (insfun) ; INTERNAL USE ONLY
+ ;; return wrapped insfun to deal with data wrapping
+ `(lambda (new old)
+ (dictree--cell-set-data old (,insfun (dictree--cell-data new)
+ (dictree--cell-data old)))
+ old))
+
+(defun dictree--wrap-rankfun (rankfun) ; INTERNAL USE ONLY
+ ;; return wrapped rankfun to deal with data wrapping
+ `(lambda (a b)
+ (,rankfun (cons (car a) (dictree--cell-data (cdr a)))
+ (cons (car b) (dictree--cell-data (cdr b))))))
+
+(defun dictree--wrap-combfun (combfun) ; INTERNAL USE ONLY
+ ;; return wrapped combfun to deal with data wrapping
+ `(lambda (cell1 cell2)
+ (cons (,combfun (dictree--cell-data cell1)
+ (dictree--cell-data cell2))
+ (append (dictree--cell-plist cell1)
+ (dictree--cell-plist cell2)))))
+
+(defun dictree--wrap-filter (filter) ; INTERNAL USE ONLY
+ ;; return wrapped filter function to deal with data wrapping
+ `(lambda (key data) (,filter key (dictree--cell-data data))))
+
+(defun dictree--wrap-resultfun (resultfun) ; INTERNAL USE ONLY
+ ;; return wrapped result function to deal with data wrapping
+ `(lambda (res) (,resultfun (car res) (dictree--cell-data (cdr res)))))
+
+
+
+;; ----------------------------------------------------------------
+;; The dictionary data structures
+
+(defstruct
+ (dictree-
+ :named
+ (:constructor nil)
+ (:constructor dictree--create
+ (&optional
+ filename
+ (name (and filename
+ (file-name-sans-extension
+ (file-name-nondirectory filename))))
+ autosave
+ unlisted
+ (comparison-function '<)
+ (insert-function (lambda (a b) a))
+ (rank-function (lambda (a b) (> (cdr a) (cdr b))))
+ (cache-policy 'time)
+ (cache-update-policy 'synchronize)
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold
+ key-savefun key-loadfun
+ data-savefun data-loadfun
+ plist-savefun plist-loadfun
+ trie-type
+ &aux
+ (modified nil)
+ (trie (trie-create comparison-function))
+ (insfun (dictree--wrap-insfun insert-function))
+ (rankfun (dictree--wrap-rankfun rank-function))
+ (lookup-cache
+ (if lookup-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (complete-cache
+ (if complete-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (complete-ranked-cache
+ (if complete-ranked-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (regexp-cache
+ (if regexp-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (regexp-ranked-cache
+ (if regexp-ranked-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (metadict-list nil)
+ ))
+ (:constructor dictree--create-custom
+ (&optional
+ filename
+ (name (and filename
+ (file-name-sans-extension
+ (file-name-nondirectory filename))))
+ autosave
+ unlisted
+ (comparison-function '<)
+ (insert-function (lambda (a b) a))
+ (rank-function (lambda (a b) (> (cdr a) (cdr b))))
+ (cache-policy 'time)
+ (cache-update-policy 'synchronize)
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold
+ key-savefun key-loadfun
+ data-savefun data-loadfun
+ plist-savefun plist-loadfun
+ &key
+ createfun insertfun deletefun
+ lookupfun mapfun emptyfun
+ stack-createfun stack-popfun stack-emptyfun
+ transform-for-print transform-from-read
+ &aux
+ (modified nil)
+ (trie (trie-create-custom
+ comparison-function
+ :createfun createfun
+ :insertfun insertfun
+ :deletefun deletefun
+ :lookupfun lookupfun
+ :mapfun mapfun
+ :emptyfun emptyfun
+ :stack-createfun stack-createfun
+ :stack-popfun stack-popfun
+ :stack-emptyfun stack-emptyfun
+ :transform-for-print transform-for-print
+ :transform-from-read transform-from-read))
+ (insfun (dictree--wrap-insfun insert-function))
+ (rankfun (dictree--wrap-rankfun rank-function))
+ (lookup-cache
+ (if lookup-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (complete-cache
+ (if complete-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (complete-ranked-cache
+ (if complete-ranked-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (regexp-cache
+ (if regexp-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (regexp-ranked-cache
+ (if regexp-ranked-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (metadict-list nil)
+ ))
+ (:copier dictree--copy))
+ name filename autosave modified
+ comparison-function insert-function insfun rank-function rankfun
+ cache-policy cache-update-policy
+ lookup-cache lookup-cache-threshold
+ complete-cache complete-cache-threshold
+ complete-ranked-cache complete-ranked-cache-threshold
+ regexp-cache regexp-cache-threshold
+ regexp-ranked-cache regexp-ranked-cache-threshold
+ key-savefun key-loadfun
+ data-savefun data-loadfun
+ plist-savefun plist-loadfun
+ trie meta-dict-list)
+
+
+(defstruct
+ (dictree--meta-dict
+ :named
+ (:constructor nil)
+ (:constructor dictree--meta-dict-create
+ (dictionary-list
+ &optional
+ filename
+ (name (file-name-sans-extension
+ (file-name-nondirectory filename)))
+ autosave
+ unlisted
+ (combine-function '+)
+ (cache-policy 'time)
+ (cache-update-policy 'synchronize)
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold
+ &aux
+ (dictlist
+ (mapcar
+ (lambda (dic)
+ (cond
+ ((dictree-p dic) dic)
+ ((symbolp dic) (eval dic))
+ (t (error "Invalid object in DICTIONARY-LIST"))))
+ dictionary-list))
+ (combfun (dictree--wrap-combfun combine-function))
+ (lookup-cache
+ (if lookup-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (complete-cache
+ (if complete-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (complete-ranked-cache
+ (if complete-ranked-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (regexp-cache
+ (if regexp-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ (regexp-ranked-cache
+ (if regexp-ranked-cache-threshold
+ (make-hash-table :test 'equal)
+ nil))
+ ))
+ (:copier dictree--meta-dict-copy))
+ name filename autosave modified
+ combine-function combfun
+ cache-policy cache-update-policy
+ lookup-cache lookup-cache-threshold
+ complete-cache complete-cache-threshold
+ complete-ranked-cache complete-ranked-cache-threshold
+ regexp-cache regexp-cache-threshold
+ regexp-ranked-cache regexp-ranked-cache-threshold
+ dictlist meta-dict-list)
+
+
+
+;; ----------------------------------------------------------------
+;; Miscelaneous internal functions and macros
+
+(defun dictree--trielist (dict)
+ ;; Return a list of all the tries on which DICT is based. If DICT is a
+ ;; meta-dict, this recursively descends the hierarchy, gathering all
+ ;; the tries from the base dictionaries.
+ (let (accumulate)
+ (dictree--do-trielist dict)
+ accumulate))
+
+(defun dictree--do-trielist (dict)
+ (declare (special accumulate))
+ (if (dictree-meta-dict-p dict)
+ (mapc 'dictree--do-trielist (dictree--meta-dict-dictlist dict))
+ (setq accumulate (cons (dictree--trie dict) accumulate))))
+
+
+(defun dictree--merge (list1 list2 cmpfun &optional combfun maxnum)
+ ;; Destructively merge together sorted lists LIST1 and LIST2, sorting
+ ;; elements according to CMPFUN. For non-null MAXNUM, only the first
+ ;; MAXNUM are kept. For non-null COMBFUN, duplicate elements will be
+ ;; merged by passing the two elements as arguments to COMBFUN, and
+ ;; using the return value as the merged element.
+ (or (listp list1) (setq list1 (append list1 nil)))
+ (or (listp list2) (setq list2 (append list2 nil)))
+ (let (res (i 0))
+
+ ;; build up result list backwards
+ (while (and list1 list2 (or (null maxnum) (< (incf i) maxnum)))
+ ;; move smaller element to result list
+ (if (funcall cmpfun (car list1) (car list2))
+ (push (pop list1) res)
+ (if (funcall cmpfun (car list2) (car list1))
+ (push (pop list2) res)
+ ;; if elements are equal, merge them for non-null COMBFUN
+ (if combfun
+ (push (funcall combfun (pop list1) (pop list2))
+ res)
+ ;; otherwise, add both to result list, in order
+ (push (pop list1) res)
+ (push (pop list2) res)))))
+
+ ;; return result if we already have MAXNUM entries
+ (if (and maxnum (= i maxnum))
+ (nreverse res)
+ ;; otherwise, return result plus enough leftover entries to make
+ ;; up MAXNUM (only one of list1 or list2 will be non-nil)
+ (let (tmp)
+ (or (null maxnum)
+ (and (setq tmp (nthcdr (- maxnum i 1) list1))
+ (setcdr tmp nil))
+ (and (setq tmp (nthcdr (- maxnum i 1) list2))
+ (setcdr tmp nil)))
+ (nconc (nreverse res) list1 list2)))
+ ))
+
+
+;; (defun dictree--merge-sort (list sortfun &optional combfun)
+;; ;; Destructively sort LIST according to SORTFUN, combining
+;; ;; identical elements using COMBFUN if supplied.
+;; (dictree--do-merge-sort list (/ (length list) 2) sortfun combfun))
+
+
+;; (defun dictree--do-merge-sort (list1 len sortfun combfun)
+;; ;; Merge sort LIST according to SORTFUN, combining identical
+;; ;; elements using COMBFUN.
+;; (let* ((p (nthcdr (1- len) list1))
+;; (list2 (cdr p)))
+;; (setcdr p nil)
+;; (dictree--merge
+;; (dictree--do-merge-sort list1 (/ len 2) sortfun combfun)
+;; (dictree--do-merge-sort list2 (/ len 2) sortfun combfun)
+;; sortfun combfun)))
+
+
+
+
+;;; ================================================================
+;;; The (mostly) public functions which operate on dictionaries
+
+;;;###autoload
+(defun make-dictree
+ (&optional
+ name filename autosave unlisted
+ comparison-function insert-function rank-function
+ cache-policy cache-update-policy
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold
+ key-savefun key-loadfun
+ data-savefun data-loadfun
+ plist-savefun plist-loadfun
+ trie-type)
+ "Create an empty dictionary and return it.
+
+If NAME is supplied, the dictionary is stored in the variable
+NAME. Defaults to FILENAME stripped of directory and
+extension. (Regardless of the value of NAME, the dictionary will
+be stored in the default variable name when it is reloaded from
+file.)
+
+FILENAME supplies a directory and file name to use when saving
+the dictionary. If the AUTOSAVE flag is non-nil, then the
+dictionary will automatically be saved to this file when it is
+unloaded or when exiting Emacs.
+
+If UNLISTED is non-nil, the dictionary will not be added to the
+list of loaded dictionaries. Note that this disables autosaving.
+
+COMPARE-FUNCTION sets the function used to compare elements of
+the keys. It should take two arguments, A and B, both of the type
+contained by the sequences used as keys \(e.g. if the keys will
+be strings, the function will be passed two characters\). It
+should return t if the first is \"less than\" the
+second. Defaults to `<'.
+
+INSERT-FUNCTION sets the function used to insert data into the
+dictionary. It should take two arguments: the new data, and the
+data already in the dictionary, and should return the data to
+insert. Defaults to replacing any existing data with the new
+data.
+
+RANK-FUNCTION sets the function used to rank the results of
+`dictree-complete'. It should take two arguments, each a cons
+whose car is a dictree key (a sequence) and whose cdr is the data
+associated with that key. It should return non-nil if the first
+argument is \"better\" than the second, nil otherwise. It
+defaults to \"lexical\" comparison of the keys, ignoring the data
+\(which is not very useful, since an unranked `dictree-complete'
+query already does this much more efficiently\).
+
+CACHE-POLICY should be a symbol ('time, 'length, or 'both), which
+determines which query operations are cached. The 'time setting
+caches queries that take longer (in seconds) than the
+corresponding CACHE-THRESHOLD value. The 'length setting caches
+lookups of key sequences that are longer than
+LOOKUP-CACHE-THRESHOLD value (since those are likely to be the
+slower ones), and caches completions of prefixes that are shorter
+than the corresponding CACHE-THRESHOLD (since those are likely to
+be the slower ones in that case). The setting 'both requires both
+conditions to be satisfied simultaneously. In this case,
+CACHE-THRESHOLD must be a plist with properties :time and :length
+specifying the corresponding cache thresholds.
+
+CACHE-UPDATE-POLICY should be a symbol ('synchronize or 'delete),
+which determines how the caches are updated when data is inserted
+or deleted. The former updates tainted cache entries, which makes
+queries faster but insertion and deletion slower, whereas the
+latter deletes any tainted cache entries, which makes queries
+slower but insertion and deletion faster.
+
+The CACHE-THRESHOLD settings set the threshold for caching the
+corresponding dictionary query (lookup, completion, ranked
+completion). The meaning of these values depends on the setting
+of CACHE-POLICY (see above).
+
+All CACHE-THRESHOLD's default to nil. The values nil and t are
+special. If a CACHE-THRESHOLD is set to nil, no caching is done
+for that type of query. If it is t, everything is cached for that
+type of query \(similar behaviour can be obtained by setting the
+CACHE-THRESHOLD to 0, but it is better to use t\).
+
+KEY-SAVEFUN, DATA-SAVEFUN and PLIST-SAVEFUN are functions used to
+convert keys, data and property lists into lisp objects that have
+a valid read syntax, for writing to file. DATA-SAVEFUN and
+PLIST-SAVEFUN are used when saving the dictionary (see
+`dictree-save' and `dictree-write'), and all three functions are
+used when dumping the contents of the dictionary \(see
+`dictree-dump-to-buffer' and `dictree-dump-to-file'\).
+KEY-SAVEFUN, DATA-SAVEFUN and PLIST-SAVEFUN should each accept
+one argument: a key, data or property list from DICT,
+respectively. They should return a lisp object which has a valid
+read syntax. When defining these functions, be careful not to
+accidentally modify the lisp object in the dictionary; usually,
+you will need to make a copy before converting it.
+
+KEY-LOADFUN, DATA-LOADFUN and PLIST-LOADFUN are used to convert
+keys, data and property lists back again when loading a
+dictionary (only DATA-LOADFUN and PLIST-LOADFUN, see
+`dictree-save' and `dictree-write') or populating it from a
+file (all three, see `dictree-populate-from-file'). They should
+accept one argument: a lisp object of the type produced by the
+corresponding SAVEFUN, and return a lisp object to use in the
+loaded dictionary.
+
+TRIE-TYPE sets the type of trie to use as the underlying data
+structure. See `trie-create' for details."
+
+ ;; sadly, passing null values over-rides the defaults in the defstruct
+ ;; dictree--create, so we have to explicitly set the defaults again
+ ;; here
+ (or name (setq name (and filename (file-name-sans-extension
+ (file-name-nondirectory filename)))))
+ (or comparison-function (setq comparison-function '<))
+ (or insert-function (setq insert-function (lambda (a b) a)))
+ (or rank-function (setq rank-function (lambda (a b) (> (cdr a) (cdr b)))))
+ (or cache-policy (setq cache-policy 'time))
+ (or cache-update-policy (setq cache-update-policy 'synchronize))
+
+ (let ((dict
+ (dictree--create
+ filename (when name (symbol-name name)) autosave unlisted
+ comparison-function insert-function rank-function
+ cache-policy cache-update-policy
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold
+ key-savefun key-loadfun
+ data-savefun data-loadfun
+ plist-savefun plist-loadfun
+ trie-type)))
+ ;; store dictionary in variable NAME
+ (when name (set name dict))
+ ;; add it to loaded dictionary list, unless it's unlisted
+ (unless (or (null name) unlisted)
+ (push dict dictree-loaded-list))
+ dict))
+
+
+;;;###autoload
+(defalias 'dictree-create 'make-dictree)
+
+
+;;;###autoload
+(defun* make-dictree-custom
+ (&optional
+ name filename autosave unlisted
+ &key
+ comparison-function insert-function rank-function
+ cache-policy cache-update-policy
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold
+ key-savefun key-loadfun
+ data-savefun data-loadfun
+ plist-savefun plist-loadfun
+ createfun insertfun deletefun lookupfun mapfun emptyfun
+ stack-createfun stack-popfun stack-emptyfun
+ transform-for-print transform-from-read)
+ "Create an empty dictionary and return it.
+
+The NAME through PLIST-LOADFUN arguments are as for
+`dictree-create' (which see).
+
+The remaining arguments control the type of trie to use as the
+underlying data structure. See `trie-create' for details."
+
+ ;; sadly, passing null values over-rides the defaults in the defstruct
+ ;; dictree--create, so we have to explicitly set the defaults again
+ ;; here
+ (or name (setq name (and filename (file-name-sans-extension
+ (file-name-nondirectory filename)))))
+ (or comparison-function (setq comparison-function '<))
+ (or insert-function (setq insert-function (lambda (a b) a)))
+ (or rank-function (setq rank-function (lambda (a b) (< (cdr a) (cdr b)))))
+ (or cache-policy (setq cache-policy 'time))
+ (or cache-update-policy (setq cache-update-policy 'synchronize))
+
+ (let ((dict
+ (dictree--create-custom
+ filename (when name (symbol-name name)) autosave unlisted
+ comparison-function insert-function rank-function
+ cache-policy cache-update-policy
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold
+ key-savefun key-loadfun
+ data-savefun data-loadfun
+ plist-savefun plist-loadfun
+ :createfun createfun
+ :insertfun insertfun
+ :deletefun deletefun
+ :lookupfun lookupfun
+ :mapfun mapfun
+ :emptyfun emptyfun
+ :stack-createfun stack-createfun
+ :stack-popfun stack-popfun
+ :stack-emptyfun stack-emptyfun
+ :transform-for-print transform-for-print
+ :transform-from-read transform-from-read)))
+ ;; store dictionary in variable NAME
+ (when name (set name dict))
+ ;; add it to loaded dictionary list, unless it's unlisted
+ (unless (or (null name) unlisted)
+ (push dict dictree-loaded-list))
+ dict))
+
+
+;;;###autoload
+(defalias 'dictree-create-custom 'make-dictree-custom)
+
+
+;;;###autoload
+(defun make-dictree-meta-dict
+ (dictionary-list
+ &optional
+ name filename autosave unlisted
+ combine-function
+ cache-policy cache-update-policy
+ lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold)
+ "Create a meta-dictionary based on the list of dictionaries
+in DICTIONARY-LIST.
+
+COMBINE-FUNCTION is used to combine data from different
+dictionaries. It is passed two pieces of data, each an
+association of the same key, but in different dictionaries. It
+should return a combined datum.
+
+The other arguments are as for `dictree-create'. Note that
+caching is only possible if NAME is supplied, otherwise the
+cache-threshold arguments are ignored."
+
+ ;; sadly, passing null values over-rides the defaults in the defstruct
+ ;; `dictree--create', so we have to explicitly set the defaults again
+ ;; here
+ (or name (setq name (and filename
+ (file-name-sans-extension
+ (file-name-nondirectory filename)))))
+ (or combine-function (setq combine-function '+))
+ (or cache-policy (setq cache-policy 'time))
+ (or cache-update-policy (setq cache-update-policy 'synchronize))
+
+ (let ((dict
+ (dictree--meta-dict-create
+ dictionary-list filename (when name (symbol-name name))
+ autosave unlisted
+ combine-function
+ cache-policy cache-update-policy
+ (when name lookup-cache-threshold)
+ (when name complete-cache-threshold)
+ (when name complete-ranked-cache-threshold)
+ (when name regexp-cache-threshold)
+ (when name regexp-ranked-cache-threshold))
+ ))
+ ;; store dictionary in variable NAME
+ (when name (set name dict))
+ ;; add it to loaded dictionary list, unless it's unlisted
+ (unless (or (null name) unlisted)
+ (push dict dictree-loaded-list))
+ ;; update meta-dict-list cells of constituent dictionaries
+ (unless (or (null name)
+ (not (or lookup-cache-threshold
+ complete-cache-threshold
+ complete-ranked-cache-threshold
+ regexp-cache-threshold
+ regexp-ranked-cache-threshold)))
+ (mapc
+ (lambda (dic)
+ (if (symbolp dic) (setq dic (eval dic)))
+ (setf (dictree--meta-dict-list dic)
+ (cons dict (dictree--meta-dict-list dic))))
+ dictionary-list))
+ dict))
+
+(defalias 'dictree-create-meta-dict 'make-dictree-meta-dict)
+
+
+;;;###autoload
+(defun dictree-p (obj)
+ "Return t if OBJ is a dictionary tree, nil otherwise."
+ (or (dictree--p obj) (dictree--meta-dict-p obj)))
+
+
+(defalias 'dictree-meta-dict-p 'dictree--meta-dict-p
+ "Return t if argument is a meta-dictionary, nil otherwise.")
+
+(defun dictree-empty-p (dict)
+ "Return t if the dictionary DICT is empty, nil otherwise."
+ (if (dictree--meta-dict-p dict)
+ (catch 'nonempty
+ (mapc (lambda (dic)
+ (if (not (dictree-empty-p dic)) (throw 'nonempty t)))
+ (dictree--meta-dict-dictlist dict)))
+ (trie-empty (dictree--trie dict))))
+
+(defsubst dictree-autosave (dict)
+ "Return dictionary's autosave flag."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-autosave dict)
+ (dictree--autosave dict)))
+
+(defsetf dictree-autosave (dict) (val)
+ ;; setf method for dictionary autosave flag
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-autosave ,dict) ,val)
+ (setf (dictree--autosave ,dict) ,val)))
+
+(defsubst dictree-modified (dict)
+ "Return dictionary's modified flag."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-modified dict)
+ (dictree--modified dict)))
+
+(defsetf dictree-modified (dict) (val)
+ ;; setf method for dictionary modified flag
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-modified ,dict) ,val)
+ (setf (dictree--modified ,dict) ,val)))
+
+(defsubst dictree-name (dict)
+ "Return dictionary DICT's name."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-name dict)
+ (dictree--name dict)))
+
+(defsetf dictree-name (dict) (name)
+ ;; setf method for dictionary name
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-name ,dict) ,name)
+ (setf (dictree--name ,dict) ,name)))
+
+(defsubst dictree-filename (dict)
+ "Return dictionary DICT's associated file name."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-filename dict)
+ (dictree--filename dict)))
+
+(defsetf dictree-filename (dict) (filename)
+ ;; setf method for dictionary filename
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-filename ,dict) ,filename)
+ (setf (dictree--filename ,dict) ,filename)))
+
+(defun dictree-comparison-function (dict)
+ "Return dictionary DICT's comparison function."
+ (if (dictree--meta-dict-p dict)
+ (dictree-comparison-function
+ (car (dictree--meta-dict-dictlist dict)))
+ (dictree--comparison-function dict)))
+
+(defalias 'dictree-insert-function 'dictree--insert-function
+ "Return the insertion function for dictionary DICT.")
+
+(defun dictree-rank-function (dict)
+ "Return the rank function for dictionary DICT"
+ (if (dictree--meta-dict-p dict)
+ (dictree-rank-function (car (dictree--meta-dict-dictlist dict)))
+ (dictree--rank-function dict)))
+
+(defun dictree-rankfun (dict)
+ ;; Return the rank function for dictionary DICT
+ (if (dictree--meta-dict-p dict)
+ (dictree-rankfun (car (dictree--meta-dict-dictlist dict)))
+ (dictree--rankfun dict)))
+
+(defalias 'dictree-meta-dict-combine-function
+ 'dictree--meta-dict-combine-function
+ "Return the combine function for meta-dictionary DICT.")
+
+(defalias 'dictree-meta-dict-dictlist
+ 'dictree--meta-dict-dictlist
+ "Return the list of constituent dictionaries
+for meta-dictionary DICT.")
+
+(defsubst dictree-cache-policy (dict)
+ "Return the cache policy for dictionary DICT."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-cache-policy dict)
+ (dictree--cache-policy dict)))
+
+(defsubst dictree-cache-update-policy (dict)
+ "Return the cache update policy for dictionary DICT."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-cache-update-policy dict)
+ (dictree--cache-update-policy dict)))
+
+(defsubst dictree-lookup-cache-threshold (dict)
+ "Return the lookup cache threshold for dictionary DICT."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-lookup-cache-threshold dict)
+ (dictree--lookup-cache-threshold dict)))
+
+(defsetf dictree-lookup-cache-threshold (dict) (param)
+ ;; setf method for lookup cache threshold
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-lookup-cache-threshold ,dict)
+ ,param)
+ (setf (dictree--lookup-cache-threshold ,dict)
+ ,param)))
+
+(defsubst dictree-lookup-cache (dict)
+ ;; Return the lookup cache for dictionary DICT.
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-lookup-cache dict)
+ (dictree--lookup-cache dict)))
+
+(defsubst dictree-complete-cache-threshold (dict)
+ "Return the completion cache threshold for dictionary DICT."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-complete-cache-threshold dict)
+ (dictree--complete-cache-threshold dict)))
+
+(defsetf dictree-complete-cache-threshold (dict) (param)
+ ;; setf method for completion cache threshold
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-complete-cache-threshold ,dict)
+ ,param)
+ (setf (dictree--complete-cache-threshold ,dict)
+ ,param)))
+
+(defun dictree-complete-cache (dict)
+ ;; Return the completion cache for dictionary DICT.
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-complete-cache dict)
+ (dictree--complete-cache dict)))
+
+(defsubst dictree-complete-ranked-cache-threshold (dict)
+ "Return the ranked completion cache threshold for dictionary DICT."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-complete-ranked-cache-threshold dict)
+ (dictree--complete-ranked-cache-threshold dict)))
+
+(defsetf dictree-complete-ranked-cache-threshold (dict) (param)
+ ;; setf method for ranked completion cache threshold
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-complete-ranked-cache-threshold ,dict)
+ ,param)
+ (setf (dictree--complete-ranked-cache-threshold ,dict)
+ ,param)))
+
+(defun dictree-complete-ranked-cache (dict)
+ ;; Return the ranked completion cache for dictionary DICT.
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-complete-ranked-cache dict)
+ (dictree--complete-ranked-cache dict)))
+
+(defsubst dictree-regexp-cache-threshold (dict)
+ "Return the regexp cache threshold for dictionary DICT."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-regexp-cache-threshold dict)
+ (dictree--regexp-cache-threshold dict)))
+
+(defsetf dictree-regexp-cache-threshold (dict) (param)
+ ;; setf method for regexp cache threshold
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-regexp-cache-threshold ,dict)
+ ,param)
+ (setf (dictree--regexp-cache-threshold ,dict)
+ ,param)))
+
+(defun dictree-regexp-cache (dict)
+ ;; Return the regexp cache for dictionary DICT.
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-regexp-cache dict)
+ (dictree--regexp-cache dict)))
+
+(defsubst dictree-regexp-ranked-cache-threshold (dict)
+ "Return the ranked regexp cache threshold for dictionary DICT."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-regexp-ranked-cache-threshold dict)
+ (dictree--regexp-ranked-cache-threshold dict)))
+
+(defsetf dictree-regexp-ranked-cache-threshold (dict) (param)
+ ;; setf method for ranked regexp cache threshold
+ `(if (dictree--meta-dict-p ,dict)
+ (setf (dictree--meta-dict-regexp-ranked-cache-threshold ,dict)
+ ,param)
+ (setf (dictree--regexp-ranked-cache-threshold ,dict)
+ ,param)))
+
+(defun dictree-regexp-ranked-cache (dict)
+ ;; Return the ranked regexp cache for dictionary DICT.
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-dict-regexp-ranked-cache dict)
+ (dictree--regexp-ranked-cache dict)))
+
+
+
+;; ----------------------------------------------------------------
+;; Inserting and deleting data
+
+(defun dictree-insert (dict key &optional data insert-function)
+ "Insert KEY and DATA into dictionary DICT.
+If KEY does not already exist, this creates it. How the data is
+inserted depends on the dictionary's insertion function \(see
+`dictree-create'\).
+
+The optional INSERT-FUNCTION over-rides the dictionary's own
+insertion function. If KEY already exists in DICT,
+INSERT-FUNCTION is called with two arguments: the data DATA, and
+the data associated with KEY in the dictionary. Its return value
+becomes the new association for KEY."
+
+ ;; if dictionary is a meta-dictionary, insert key into all the
+ ;; dictionaries it's based on
+ (if (dictree--meta-dict-p dict)
+ (mapc (lambda (dic)
+ (dictree-insert dic key data insert-function))
+ (dictree--meta-dict-dictlist dict))
+
+ ;; otherwise...
+ (let (newdata)
+ ;; set the dictionary's modified flag
+ (setf (dictree-modified dict) t)
+ ;; insert key in dictionary's ternary search tree
+ (setq newdata
+ (trie-insert
+ (dictree--trie dict) key (dictree--cell-create data nil)
+ (or (and insert-function
+ (dictree--wrap-insfun insert-function))
+ (dictree--insfun dict))))
+ ;; update dictionary's caches
+ (dictree--update-cache dict key newdata)
+ ;; update cache's of any meta-dictionaries based on dict
+ (mapc (lambda (dic) (dictree--update-cache dic key newdata))
+ (dictree--meta-dict-list dict))
+
+ ;; return the new data
+ (dictree--cell-data newdata))))
+
+
+
+(defun dictree-delete (dict key &optional test)
+ "Delete KEY from DICT.
+Returns non-nil if KEY was deleted, nil if KEY was not in DICT.
+
+If TEST is supplied, it should be a function that accepts three
+arguments: the key being deleted, its associated data, and its
+associated property list. The key will then only be deleted if
+TEST returns non-nil."
+
+ (let ((dictree--delete-test test)
+ deleted del)
+ (cond
+ ;; if DICT is a meta-dictionary, delete KEY from all dictionaries
+ ;; it's based on
+ ((dictree--meta-dict-p dict)
+ (dolist (dic (dictree--meta-dict-dictlist dict))
+ (when (setq del (dictree-delete dic key))
+ (setq deleted (cons del deleted))))
+ (setf (dictree-modified dict) (and deleted t))
+ (setq deleted (nreverse deleted)))
+
+ ;; otherwise...
+ (t
+ (setq deleted
+ (trie-delete (dictree--trie dict) key
+ (when dictree--delete-test
+ (lambda (k cell)
+ (funcall dictree--delete-test
+ k (dictree--cell-data cell)
+ (dictree--cell-plist cell))))))
+ ;; if key was deleted, have to update the caches
+ (when deleted
+ (dictree--update-cache dict key nil t)
+ (setf (dictree-modified dict) t)
+ ;; update cache's of any meta-dictionaries based on DICT
+ (mapc (lambda (dic)
+ (dictree--update-cache dic key nil t))
+ (dictree--meta-dict-list dict)))))
+
+ ;; return deleted key/data pair
+ (when deleted
+ (cons (car deleted) (dictree--cell-data (cdr deleted))))))
+
+
+
+;; ----------------------------------------------------------------
+;; Cache updating
+
+(defun dictree--prefix-p (prefix str)
+ "Return t if PREFIX is a prefix of STR, nil otherwise.
+
+PREFIX and STR can be any sequence type (string, vector, or
+list), but they must both be the same type. PREFIX can also be a
+list of sequences, in which case it returns t if any element of
+PREFIX is a prefix of STR."
+ ;; wrap prefix in a list if necessary
+ ;; FIXME: the test for a list of prefixes, below, will fail if the
+ ;; PREFIX sequence is a list, and the elements of PREFIX are
+ ;; themselves lists (there might be no easy way to fully fix
+ ;; this...)
+ (when (or (atom prefix)
+ (and (listp prefix) (not (sequencep (car prefix)))))
+ (setq prefix (list prefix)))
+ (let (len)
+ (catch 'is-prefix
+ (dolist (pfx prefix)
+ (setq len (length pfx))
+ (when (and (<= len (length str))
+ (equal pfx (dictree--subseq str 0 len)))
+ (throw 'is-prefix t))))))
+
+
+(defun dictree--above-cache-threshold-p
+ (time length policy threshold &optional cache-long-keys)
+ ;; Return t if query taking TIME seconds for a key of length LENGTH
+ ;; should be cached according to the cache POLICY and
+ ;; THRESHOLD. Otherwise, return nil. Optional argument CACHE-LONG-KEYS
+ ;; means that keys of length longer than THRESHOLD are to be
+ ;; cached. Default is keys of length shorter than THRESHOLD.
+ (and threshold
+ (or (eq threshold t)
+ (and (eq policy 'time) (>= time threshold))
+ ;; note: we cache lookups of *longer* keys, because those are
+ ;; likely to be slower ones
+ (and (eq policy 'length)
+ (if cache-long-keys
+ (>= length threshold) (<= length threshold)))
+ (and (eq policy 'both)
+ (or (>= time (plist-get threshold :time))
+ (if cache-long-keys
+ (>= length (plist-get threshold :length))
+ (<= length (plist-get threshold :length))))))))
+
+
+(defun dictree--update-cache (dict key newdata &optional deleted)
+ ;; Synchronise dictionary DICT's caches, given that the data
+ ;; associated with KEY has been changed to NEWDATA, or KEY has been
+ ;; deleted if DELETED is non-nil (NEWDATA is ignored in that case)."
+ (let (arg reverse cache cache-entry completions cmpl maxnum)
+
+ ;; synchronise the lookup cache if dict is a meta-dictionary, since
+ ;; it's not done automatically
+ (when (and (dictree--meta-dict-p dict)
+ (dictree--meta-dict-lookup-cache-threshold dict))
+ (setq cache (dictree--lookup-cache dict))
+ (cond
+ ;; if updating dirty cache entries...
+ ((eq (dictree-cache-update-policy dict) 'synchronize)
+ (when (gethash key cache)
+ (if deleted (remhash key cache) (puthash key newdata cache))))
+ ;; if deleting dirty cache entries...
+ (t (remhash key cache))))
+
+ ;; synchronize the completion cache, if it exists
+ (when (dictree-complete-cache-threshold dict)
+ (setq cache (dictree-complete-cache dict))
+ ;; check every cache entry to see if it matches
+ (maphash
+ (lambda (cache-key cache-entry)
+ (setq arg (car cache-key))
+ (when (dictree--prefix-p arg key)
+ (setq reverse (cdr cache-key))
+ (cond
+ ;; if updating dirty cache entries...
+ ((eq (dictree-cache-update-policy dict) 'synchronize)
+ (dictree--synchronize-completion-cache
+ dict cache-entry arg reverse key newdata deleted))
+ ;; if deleting dirty cache entries...
+ (t (remhash (cons arg reverse) cache)))))
+ cache))
+
+ ;; synchronize the ranked completion cache, if it exists
+ (when (dictree-complete-ranked-cache-threshold dict)
+ (setq cache (dictree-complete-ranked-cache dict))
+ ;; check every cache entry to see if it matches
+ (maphash
+ (lambda (cache-key cache-entry)
+ (setq arg (car cache-key))
+ (when (dictree--prefix-p arg key)
+ (setq reverse (cdr cache-key))
+ (cond
+ ;; if updating dirty cache entries...
+ ((eq (dictree-cache-update-policy dict) 'synchronize)
+ (dictree--synchronize-ranked-completion-cache
+ dict cache-entry arg reverse key newdata deleted))
+ ;; if deleting dirty cache entries...
+ (t (remhash (cons arg reverse) cache)))))
+ cache))
+
+ ;; synchronize the regexp cache, if it exists
+ (when (dictree-regexp-cache-threshold dict)
+ (setq cache (dictree--regexp-cache dict))
+ ;; check every cache entry to see if it matches
+ (maphash
+ (lambda (cache-key cache-entry)
+ (setq arg (car cache-key))
+ (when (tNFA-regexp-match
+ arg key :test (dictree--comparison-function dict))
+ (setq reverse (cdr cache-key))
+ (cond
+ ;; if updating dirty cache entries...
+ ((eq (dictree-cache-update-policy dict) 'synchronize)
+ (dictree--synchronize-regexp-cache
+ dict cache-entry arg reverse key newdata deleted))
+ ;; if deleting dirty cache entries...
+ (t (remhash (cons arg reverse) cache)))))
+ cache))
+
+ ;; synchronize the ranked regexp cache, if it exists
+ (when (dictree-regexp-ranked-cache-threshold dict)
+ (setq cache (dictree-regexp-ranked-cache dict))
+ ;; have to check every cache entry to see if it matches
+ (maphash
+ (lambda (cache-key cache-entry)
+ (setq arg (car cache-key))
+ (when (tNFA-regexp-match
+ arg key :test (dictree--comparison-function dict))
+ (setq reverse (cdr cache-key))
+ (cond
+ ;; if updating dirty cache entries...
+ ((eq (dictree-cache-update-policy dict) 'synchronize)
+ (dictree--synchronize-ranked-regexp-cache
+ dict cache-entry arg reverse key newdata deleted))
+ ;; if deleting dirty cache entries...
+ (t (remhash (cons arg reverse) cache)))))
+ cache))
+ ))
+
+
+
+(defun dictree--synchronize-completion-cache
+ (dict cache-entry arg reverse key newdata deleted)
+ ;; Synchronize DICT's completion CACHE-ENTRY for ARG and REVERSE, for
+ ;; a KEY whose data was either updated to NEWDATA or DELETED.
+ (let* ((completions (dictree--cache-results cache-entry))
+ (maxnum (dictree--cache-maxnum cache-entry))
+ (cmpl (assoc key completions)))
+ ;; if key was...
+ (cond
+ ;; deleted and in cached result: remove cache entry and re-run the
+ ;; same completion to update the cache
+ ((and deleted cmpl)
+ (remhash (cons arg reverse) (dictree-complete-cache dict))
+ (dictree-complete dict arg nil maxnum reverse))
+ ;; modified and not in cached result: merge it into the completion
+ ;; list, retaining only the first maxnum
+ ((and (not deleted) (not cmpl))
+ (dictree--cache-set-completions
+ cache-entry
+ (dictree--merge
+ (list (cons key newdata)) completions
+ `(lambda (a b)
+ (,(trie-construct-sortfun
+ (dictree-comparison-function dict))
+ (car a) (car b)))
+ (when (dictree--meta-dict-p dict)
+ (dictree--meta-dict-combfun dict))
+ maxnum)))
+ ;; modified and in the cached result: update the associated data if
+ ;; dict is a meta-dictionary (this is done automatically for a
+ ;; normal dict)
+ ((and (not deleted) cmpl (dictree--meta-dict-p dict))
+ (setcdr cmpl newdata))
+ ;; deleted and not in cached result: requires no action
+ )))
+
+
+
+(defun dictree--synchronize-ranked-completion-cache
+ (dict cache-entry arg reverse key newdata deleted)
+ ;; Synchronize DICT's ranked completion CACHE-ENTRY for ARG and
+ ;; REVERSE, for a KEY whose data was either updated to NEWDATA or
+ ;; DELETED.
+ (let* ((completions (dictree--cache-results cache-entry))
+ (maxnum (dictree--cache-maxnum cache-entry))
+ (cmpl (assoc key completions))
+ (cache (dictree--complete-ranked-cache dict)))
+ ;; if key was...
+ (cond
+ ;; deleted and in cached result: remove cache entry and re-run the
+ ;; same query to update the cache
+ ((and deleted cmpl)
+ (remhash (cons arg reverse) cache)
+ (dictree-complete dict arg 'ranked maxnum reverse))
+ ;; modified and not in cached result: merge it into the completion
+ ;; list, retaining only the first maxnum
+ ((and (not deleted) (not cmpl))
+ (dictree--cache-set-completions
+ cache-entry
+ (dictree--merge
+ (list (cons key newdata)) completions
+ (dictree-rankfun dict)
+ (when (dictree--meta-dict-p dict)
+ (dictree--meta-dict-combfun dict))
+ maxnum)))
+ ;; modified and in the cached result: update the associated data if
+ ;; dict is a meta-dictionary (this is done automatically for a
+ ;; normal dict), re-sort, and if key is now at end of list re-run
+ ;; the same query to update the cache
+ ((and (not deleted) cmpl)
+ (when (dictree--meta-dict-p dict) (setcdr cmpl newdata))
+ (dictree--cache-set-completions
+ cache-entry (sort completions (dictree-rankfun dict)))
+ (when (equal key (car (last completions)))
+ (remhash (cons arg reverse) cache)
+ (dictree-complete dict arg 'ranked maxnum reverse)))
+ ;; deleted and not in cached result: requires no action
+ )))
+
+
+(defun dictree--synchronize-regexp-cache
+ (dict cache-entry arg reverse key newdata deleted)
+ ;; Synchronize DICT's completion CACHE-ENTRY for ARG and REVERSE, for
+ ;; a KEY whose data was either updated to NEWDATA or DELETED.
+ (let* ((completions (dictree--cache-results cache-entry))
+ (maxnum (dictree--cache-maxnum cache-entry))
+ group-data
+ (cmpl (catch 'found
+ (dolist (c completions)
+ (if (and (listp (car c))
+ (or (stringp (caar c))
+ (vectorp (caar c))
+ (listp (caar c))))
+ (when (equal key (caar c)) (throw 'found c))
+ (when (equal key (car c)) (throw 'found c)))))))
+ ;; if key was...
+ (cond
+ ;; deleted and in cached result: remove cache entry and re-run the
+ ;; same completion to update the cache
+ ((and deleted cmpl)
+ (remhash (cons arg reverse) (dictree-complete-cache dict))
+ (dictree-regexp-search dict arg nil maxnum reverse))
+ ;; modified and not in cached result: merge it into the completion
+ ;; list, retaining only the first maxnum
+ ((and (not deleted) (not cmpl))
+ (save-match-data
+ (set-match-data nil)
+ (tNFA-regexp-match arg key
+ :test (dictree--comparison-function dict))
+ (when (setq group-data (nthcdr 2 (match-data)))
+ (setq key (cons key group-data))))
+ (dictree--cache-set-completions
+ cache-entry
+ (dictree--merge
+ (list (cons key newdata)) completions
+ `(lambda (a b)
+ (,(trie-construct-sortfun (dictree-comparison-function dict))
+ ,(if group-data '(caar a) '(car a))
+ ,(if group-data '(caar b) '(car b))))
+ (when (dictree--meta-dict-p dict)
+ (dictree--meta-dict-combfun dict))
+ maxnum)))
+ ;; modified and in the cached result: update the associated data if
+ ;; dict is a meta-dictionary (this is done automatically for a
+ ;; normal dict)
+ ((and (not deleted) cmpl (dictree--meta-dict-p dict))
+ (setcdr cmpl newdata))
+ ;; deleted and not in cached result: requires no action
+ )))
+
+
+
+(defun dictree--synchronize-ranked-regexp-cache
+ (dict cache-entry arg reverse key newdata deleted)
+ ;; Synchronize DICT's ranked regexp CACHE-ENTRY for ARG and REVERSE,
+ ;; for a KEY whose data was either updated to NEWDATA or DELETED.
+ (let ((completions (dictree--cache-results cache-entry))
+ (maxnum (dictree--cache-maxnum cache-entry))
+ (cache (dictree--regexp-ranked-cache dict))
+ cmpl group-data)
+ (setq group-data (and (listp (caar completions))
+ (or (stringp (caar (car completions)))
+ (vectorp (caar (car completions)))
+ (listp (caar (car completions))))))
+ (setq cmpl
+ (catch 'found
+ (dolist (c completions)
+ (if group-data
+ (when (equal key (caar c)) (throw 'found c))
+ (when (equal key (car c)) (throw 'found c))))))
+ ;; if key was...
+ (cond
+ ;; deleted and in cached result: remove cache entry and re-run the
+ ;; same query to update the cache
+ ((and deleted cmpl)
+ (remhash (cons arg reverse) cache)
+ (dictree-regexp-search dict arg 'ranked maxnum reverse))
+ ;; modified and not in cached result: merge it into the completion
+ ;; list, retaining only the first maxnum
+ ((and (not deleted) (not cmpl))
+ (save-match-data
+ (set-match-data nil)
+ (tNFA-regexp-match arg key
+ :test (dictree--comparison-function dict))
+ (when (setq group-data (nthcdr 2 (match-data)))
+ (setq key (cons key group-data))))
+ (dictree--cache-set-completions
+ cache-entry
+ (dictree--merge
+ (list (cons key newdata)) completions
+ (dictree-rankfun dict)
+ (when (dictree--meta-dict-p dict)
+ (dictree--meta-dict-combfun dict))
+ maxnum)))
+ ;; modified and in the cached result: update the associated data if
+ ;; dict is a meta-dictionary (this is done automatically for a
+ ;; normal dict), re-sort, and if key is now at end of list re-run
+ ;; the same query to update the cache
+ ((and (not deleted) cmpl)
+ (when (dictree--meta-dict-p dict) (setcdr cmpl newdata))
+ (dictree--cache-set-completions
+ cache-entry
+ (sort completions
+ (if group-data
+ `(lambda (a b)
+ (,(dictree-rankfun dict)
+ (cons (caar a) (cdr a))
+ (cons (caar b) (cdr b))))
+ (dictree-rankfun dict))))
+ (when (equal key (car (last completions)))
+ (remhash (cons arg reverse) cache)
+ (dictree-complete dict arg 'ranked maxnum reverse)))
+ ;; deleted and not in cached result: requires no action
+ )))
+
+
+(defun dictree-clear-caches (dict)
+ "Clear all DICT's query caches."
+ (interactive (list (read-dict "Dictionary: ")))
+ (dolist (cachefun '(dictree-lookup-cache
+ dictree-complete-cache
+ dictree-complete-ranked-cache
+ dictree-regexp-cache
+ dictree-regexp-ranked-cache))
+ (when (funcall cachefun dict)
+ (clrhash (funcall cachefun dict))))
+ (when (called-interactively-p 'interactive)
+ (message "Cleared caches for dictionary %s" (dictree-name dict))))
+
+
+
+
+;; ----------------------------------------------------------------
+;; Retrieving data
+
+(defun dictree-member (dict key &optional nilflag)
+ "Return the data associated with KEY in dictionary DICT,
+or nil if KEY is not in the dictionary.
+
+Optional argument NILFLAG specifies a value to return instead of
+nil if KEY does not exist in TREE. This allows a non-existent KEY
+to be distinguished from an element with a null association. (See
+also `dictree-member-p' for testing existence alone.)"
+ (let* ((data (dictree--lookup dict key nilflag)))
+ (if (eq data nilflag)
+ nilflag
+ (dictree--cell-data data))))
+
+(defalias 'dictree-lookup 'dictree-member)
+
+(defun dictree-member-p (dict key)
+ "Return t if KEY exists in DICT, nil otherwise."
+ (let ((flag '(nil)))
+ (not (eq flag (dictree-member dict key flag)))))
+
+
+(defun dictree--lookup (dict key nilflag)
+ ;; Return association of KEY in DICT, or NILFLAG if KEY does not
+ ;; exist. Does not do any data/meta-data unwrapping
+
+ (let* ((flag '(nil))
+ (data flag)
+ time)
+ ;; if KEY is in the cache, then we're done
+ (unless (and (dictree-lookup-cache dict)
+ (setq data (gethash key (dictree--lookup-cache dict))))
+
+ ;; otherwise, we have to look in the dictionary itself...
+ (cond
+ ;; if DICT is a meta-dict, look in its constituent dictionaries
+ ((dictree--meta-dict-p dict)
+ (let (newdata (newflag '(nil)))
+ ;; time the lookup for caching
+ (setq time (float-time))
+ ;; look in each constituent dictionary in turn
+ (dolist (dic (dictree--meta-dict-dictlist dict))
+ (setq newdata (dictree--lookup dic key newflag))
+ ;; skip dictionary if it doesn't contain KEY
+ (unless (eq newdata newflag)
+ ;; if we haven't found KEY before, we have now!
+ (if (eq data flag) (setq data newdata)
+ ;; otherwise, combine the previous data with the new
+ ;; data
+ (setq data (funcall (dictree--meta-dict-combfun dict)
+ data newdata)))))
+ (setq time (- (float-time) time))))
+
+ ;; otherwise, DICT is a normal dictionary, so look in it's trie
+ (t
+ ;; time the lookup for caching
+ (setq time (float-time))
+ (setq data (trie-member (dictree--trie dict) key flag))
+ (setq time (- (float-time) time))))
+
+ ;; if lookup found something, and we're above the lookup
+ ;; cache-threshold, cache the result
+ (when (and (not (eq data flag))
+ (dictree--above-cache-threshold-p
+ time (length key) (dictree-cache-policy dict)
+ (dictree-lookup-cache-threshold dict) 'long-keys))
+ (setf (dictree-modified dict) t)
+ (puthash key data (dictree-lookup-cache dict))))
+
+ ;; return the desired data
+ (if (eq data flag) nilflag data)))
+
+
+
+;; ----------------------------------------------------------------
+;; Getting and setting meta-data
+
+(defun dictree-put-property (dict key property value)
+ "Set PROPERTY for KEY in dictionary DICT.
+PROPERTY should be a symbol. Returns VALUE if successful, nil if
+KEY was not found in DICT.
+
+Note that if DICT is a meta-dictionary, then this will set KEY's
+PROPERTY to VALUE in *all* its constituent dictionaries.
+
+Unlike the data associated with a key (cf. `dictree-insert'),
+properties are not included in the results of queries on the
+dictionary \(`dictree-lookup', `dictree-complete',
+`dictree-complete-ordered'\), nor do they affect the outcome of
+any of the queries. They merely serves to tag a key with some
+additional information, and can only be retrieved using
+`dictree-get-property'."
+
+ ;; sort out arguments
+ (when (symbolp dict) (setq dict (eval dict)))
+ (cond
+ ;; set PROPERTY for KEY in all constituent dicts of a meta-dict
+ ((dictree--meta-dict-p dict)
+ (warn "Setting %s property for key %s in all constituent\
+ dictionaries of meta-dicttionary %s" property key (dictree-name dict))
+ (setf (dictree-modified dict) t)
+ (let (dictree--put-property-ret)
+ (mapc (lambda (dic k p v)
+ (setq dictree--put-property-ret
+ (or dictree--put-property-ret
+ (dictree-put-property dic k p v))))
+ (dictree--meta-dict-dictlist dict))
+ ;; return VALUE if KEY was found in at least one constituent dict
+ dictree--put-property-ret))
+ (t ;; set PROPERTY for KEY in normal dict
+ (let ((cell (trie-member (dictree--trie dict) key)))
+ (when cell
+ (setf (dictree-modified dict) t)
+ (setf (dictree--cell-plist cell)
+ (plist-put (dictree--cell-plist cell) property value))
+ value))) ; return VALUE
+ ))
+
+
+
+(defun dictree-delete-property (dict key property)
+ "Delete PROPERTY from KEY in dictionary DICT.
+Returns the new property list for KEY, with PROPERTY deleted.
+
+Setting PROPERTY to nil using `dictree-put-property' is not quite
+the same thing as deleting it, since null property values can
+still be detected by supplying the optional argument to
+`dictree-get-propery' (which see).
+
+Note that if DICT is a meta-dictionary, then this will delete
+KEY's PROPERTY in *all* its constituent dictionaries."
+ ;; sort out arguments
+ (when (symbolp dict) (setq dict (eval dict)))
+ (cond
+ ;; delete PROPERTY from KEY in all constituent dicts of a meta-dict
+ ((dictree--meta-dict-p dict)
+ (warn "Deleting %s property from key %s in all constituent\
+ dictionaries of meta-dicttionary %s" property key (dictree-name dict))
+ (setf (dictree-modified dict) t)
+ (mapcar (lambda (dic k p) (dictree-delete-property dic k p))
+ (dictree--meta-dict-dictlist dict)))
+ (t ;; delete PROPERTY from KEY in normal dict
+ (let* ((cell (trie-member (dictree--trie dict) key))
+ plist tail tail)
+ (when (and cell
+ (setq tail
+ (plist-member
+ (setq plist (dictree--cell-plist cell))
+ property)))
+ (setf (dictree-modified dict) t)
+ ;; delete property and value from plist
+ (setcdr tail (cddr tail))
+ (setq plist (delq property plist))
+ (setf (dictree--cell-plist cell) plist))))
+ ))
+
+
+
+(defun dictree-get-property (dict key property &optional nilflag)
+ "Get the value of PROPERTY for KEY in dictionary DICT,
+or return nil if KEY is not in the dictionary.
+
+Optional argument NILFLAG specifies a value to return instead of
+nil if KEY does not exist in TREE. This allows a non-existent KEY
+to be distinguished from a key for which PROPERTY is not
+set. (See also `dictree-member-p' for testing existence alone.)"
+ (let ((cell (dictree--lookup dict key nilflag)))
+ (unless (eq cell nilflag)
+ (plist-get (dictree--cell-plist cell) property))))
+
+
+
+
+;; ----------------------------------------------------------------
+;; Mapping functions
+
+(defun dictree-mapc (function dict &optional type reverse)
+ "Apply FUNCTION to all entries in dictionary DICT,
+for side-effects only.
+
+FUNCTION will be passed two arguments: a key of type
+TYPE ('string, 'vector, or 'list, defaulting to 'vector) from the
+dictionary, and the data associated with that key. The dictionary
+entries will be traversed in \"lexical\" order, i.e. the order
+defined by the dictionary's comparison function (cf.
+`dictree-create').
+
+If TYPE is 'string, it must be possible to apply the function
+`string' to the elements of sequences stored in DICT.
+
+FUNCTION is applied in ascending order, or descending order if
+REVERSE is non-nil.
+
+Note: to avoid nasty dynamic scoping bugs, FUNCTION must *not*
+bind any variables with names commencing \"--\"."
+
+ ;; "rename" FUNCTION to something hopefully unique to lessen the
+ ;; likelihood of dynamic scoping bugs caused by a supplied function
+ ;; binding a variable with the same name as one of the arguments
+ (let ((--dictree-mapc--function function))
+ (dictree--mapc
+ (lambda (key data plist)
+ (funcall --dictree-mapc--function key data))
+ dict type reverse)))
+
+
+
+(defun dictree--mapc (function dict &optional type reverse)
+ ;; Like `dictree-mapc', but FUNCTION is passed three arguments: the
+ ;; key, the data, and the property list, instead of just key and data.
+
+ ;; try to avoid dynamic binding bugs
+ (let ((--dictree--mapc--function function))
+ (if (dictree--meta-dict-p dict)
+ ;; for a meta-dict, use a dictree-stack
+ (let ((stack (dictree-stack dict))
+ entry)
+ (while (setq entry (dictree--stack-pop stack))
+ (funcall --dictree--mapc--function
+ (car entry)
+ (dictree--cell-data (cdr entry))
+ (dictree--cell-plist (cdr entry)))))
+ ;; for a normal dictionary, map the function over its trie
+ (trie-mapc
+ (lambda (key cell)
+ (funcall --dictree--mapc--function
+ key
+ (dictree--cell-data cell)
+ (dictree--cell-plist cell)))
+ (dictree--trie dict)
+ type reverse)
+ )))
+
+
+
+(defun dictree-mapf (function combinator dict &optional type reverse)
+ "Apply FUNCTION to all entries in dictionary DICT,
+and combine the results using COMBINATOR.
+
+FUNCTION should take two arguments: a key sequence from the
+dictionary and its associated data.
+
+Optional argument TYPE (one of the symbols vector, lisp or
+string; defaults to vector) sets the type of sequence passed to
+FUNCTION. If TYPE is 'string, it must be possible to apply the
+function `string' to the individual elements of key sequences
+stored in DICT.
+
+The FUNCTION will be applied and the results combined in
+asscending \"lexical\" order (i.e. the order defined by the
+dictionary's comparison function; cf. `dictree-create'), or
+descending order if REVERSE is non-nil.
+
+Note: to avoid nasty dynamic scoping bugs, FUNCTION and
+COMBINATOR must *not* bind any variables with names
+commencing \"--\"."
+
+ ;; try to avoid dynamic scoping bugs
+ (let ((--dictree-mapf--function function)
+ (--dictree-mapf--combinator combinator))
+
+ ;; for a normal dictionary, map the function over its trie
+ (if (not (dictree--meta-dict-p dict))
+ (trie-mapf
+ `(lambda (key data)
+ (,--dictree-mapf--function key (dictree--cell-data data)))
+ --dictree-mapf--combinator (dictree--trie dict) type reverse)
+
+ ;; for a meta-dict, use a dictree-stack
+ (let ((--dictree-mapf--stack (dictree-stack dict))
+ --dictree-mapf--entry
+ --dictree-mapf--accumulate)
+ (while (setq --dictree-mapf--entry
+ (dictree-stack-pop --dictree-mapf--stack))
+ (setq --dictree-mapf--accumulate
+ (funcall --dictree-mapf--combinator
+ (funcall --dictree-mapf--function
+ (car --dictree-mapf--entry)
+ (cdr --dictree-mapf--entry))
+ --dictree-mapf--accumulate)))
+ --dictree-mapf--accumulate))))
+
+
+
+(defun dictree-mapcar (function dict &optional type reverse)
+ "Apply FUNCTION to all entries in dictionary DICT,
+and make a list of the results.
+
+FUNCTION should take two arguments: a key sequence from the
+dictionary and its associated data.
+
+Optional argument TYPE (one of the symbols vector, lisp or
+string; defaults to vector) sets the type of sequence passed to
+FUNCTION. If TYPE is 'string, it must be possible to apply the
+function `string' to the individual elements of key sequences
+stored in DICT.
+
+The FUNCTION will be applied and the results combined in
+asscending \"lexical\" order \(i.e. the order defined by the
+dictionary's comparison function; cf. `dictree-create'\), or
+descending order if REVERSE is non-nil.
+
+Note that if you don't care about the order in which FUNCTION is
+applied, just that the resulting list is in the correct order,
+then
+
+ (trie-mapf function 'cons trie type (not reverse))
+
+is more efficient.
+
+Note: to avoid nasty dynamic scoping bugs, FUNCTION must *not*
+bind any variables with names commencing \"--\"."
+ (nreverse (dictree-mapf function 'cons dict type)))
+
+
+
+(defun dictree-size (dict)
+ "Return the number of entries in dictionary DICT.
+Interactively, DICT is read from the mini-buffer."
+ (interactive (list (read-dict "Dictionary: ")))
+ (let ((count 0))
+ (dictree-mapc (lambda (&rest dummy) (incf count)) dict)
+ (when (called-interactively-p 'interactive)
+ (message "Dictionary %s contains %d entries"
+ (dictree--name dict) count))
+ count))
+
+
+
+;; ----------------------------------------------------------------
+;; Using dictrees as stacks
+
+;; A dictree--meta-stack is the meta-dict version of a dictree-stack
+;; (the ordinary version is just a single trie-stack). It consists of a
+;; heap of trie-stacks for its constituent tries, where the heap order
+;; is the usual lexical order over the keys at the top of the
+;; trie-stacks.
+
+(defstruct
+ (dictree--meta-stack
+ (:constructor nil)
+ (:constructor dictree--meta-stack-create
+ (dict &optional (type 'vector) reverse
+ &aux
+ (combfun (dictree--meta-dict-combfun dict))
+ (sortfun (trie-construct-sortfun
+ (dictree-comparison-function dict)))
+ (heap (heap-create
+ (dictree--construct-meta-stack-heapfun sortfun)
+ (length (dictree--trielist dict))))
+ (pushed '())
+ (dummy (mapc
+ (lambda (dic)
+ (heap-add
+ heap (trie-stack dic type reverse)))
+ (dictree--trielist dict)))))
+ (:constructor dictree--complete-meta-stack-create
+ (dict prefix &optional reverse
+ &aux
+ (combfun (dictree--meta-dict-combfun dict))
+ (sortfun (trie-construct-sortfun
+ (dictree-comparison-function dict)))
+ (heap (heap-create
+ (dictree--construct-meta-stack-heapfun
+ sortfun reverse)
+ (length (dictree--trielist dict))))
+ (pushed '())
+ (dummy (mapc
+ (lambda (trie)
+ (let ((stack (trie-complete-stack
+ trie prefix reverse)))
+ (unless (trie-stack-empty-p stack)
+ (heap-add heap stack))))
+ (dictree--trielist dict)))))
+ (:constructor dictree--regexp-meta-stack-create
+ (dict regexp &optional reverse
+ &aux
+ (combfun (dictree--meta-dict-combfun dict))
+ (sortfun (trie-construct-sortfun
+ (dictree-comparison-function dict)))
+ (heap (heap-create
+ (dictree--construct-meta-stack-heapfun
+ sortfun reverse)
+ (length (dictree--trielist dict))))
+ (pushed '())
+ (dummy (mapc
+ (lambda (trie)
+ (let ((stack (trie-regexp-stack
+ trie regexp reverse)))
+ (unless (trie-stack-empty-p stack)
+ (heap-add heap stack))))
+ (dictree--trielist dict)))))
+ (:copier nil))
+ combfun sortfun heap pushed)
+
+
+
+(defun dictree--construct-meta-stack-heapfun (sortfun &optional reverse)
+ ;; Wrap SORTFUN, which sorts keys, so it can act on
+ ;; dictree--meta-stack elements.
+ (if reverse
+ `(lambda (b a) (,sortfun (car (dictree-stack-first a))
+ (car (dictree-stack-first b))))
+ `(lambda (a b) (,sortfun (car (dictree-stack-first a))
+ (car (dictree-stack-first b))))))
+
+
+(defun dictree-stack (dict &optional type reverse)
+ "Create an object that allows DICT to be accessed as a stack.
+
+The stack is sorted in \"lexical\" order, i.e. the order defined
+by the DICT's comparison function, or in reverse order if REVERSE
+is non-nil. Calling `dictree-stack-pop' pops the top element (a
+key and its associated data) from the stack.
+
+Optional argument TYPE (one of the symbols vector, lisp or
+string) sets the type of sequence used for the keys.
+
+Note that any modification to DICT *immediately* invalidates all
+dictree-stacks created before the modification (in particular,
+calling `dictree-stack-pop' will give unpredictable results).
+
+Operations on dictree-stacks are significantly more efficient
+than constructing a real stack from the dictionary and using
+standard stack functions. As such, they can be useful in
+implementing efficient algorithms on dictionaries. However, in
+cases where mapping functions `dictree-mapc', `dictree-mapcar' or
+`dictree-mapf' would be sufficient, it is better to use one of
+those instead."
+ (if (dictree--meta-dict-p dict)
+ (dictree--meta-stack-create dict type reverse)
+ (trie-stack (dictree--trie dict) type reverse)))
+
+
+(defun dictree-complete-stack (dict prefix &optional reverse)
+ "Return an object that allows completions of PREFIX to be accessed
+as if they were a stack.
+
+The stack is sorted in \"lexical\" order, i.e. the order defined
+by DICT's comparison function, or in reverse order if REVERSE is
+non-nil. Calling `dictree-stack-pop' pops the top element (a key
+and its associated data) from the stack.
+
+PREFIX must be a sequence (vector, list or string) that forms the
+initial part of a TRIE key. (If PREFIX is a string, it must be
+possible to apply `string' to individual elements of TRIE keys.)
+The completions returned in the alist will be sequences of the
+same type as KEY. If PREFIX is a list of sequences, completions
+of all sequences in the list are included in the stack. All
+sequences in the list must be of the same type.
+
+Note that any modification to DICT *immediately* invalidates all
+trie-stacks created before the modification (in particular,
+calling `dictree-stack-pop' will give unpredictable results).
+
+Operations on dictree-stacks are significantly more efficient
+than constructing a real stack from completions of PREFIX in DICT
+and using standard stack functions. As such, they can be useful
+in implementing efficient algorithms on tries. However, in cases
+where `dictree-complete' or `dictree-complete-ordered' is
+sufficient, it is better to use one of those instead."
+ (if (dictree--meta-dict-p dict)
+ (dictree--complete-meta-stack-create dict prefix reverse)
+ (trie-complete-stack (dictree--trie dict) prefix reverse)))
+
+
+(defun dictree-regexp-stack (dict regexp &optional reverse)
+ "Return an object that allows REGEXP matches to be accessed
+as if they were a stack.
+
+The stack is sorted in \"lexical\" order, i.e. the order defined
+by DICT's comparison function, or in reverse order if REVERSE is
+non-nil. Calling `dictree-stack-pop' pops the top element (a key
+and its associated data) from the stack.
+
+REGEXP is a regular expression, but it need not necessarily be a
+string. It must be a sequence (vector, list of string) whose
+elements are either elements of the same type as elements of the
+trie keys (which behave as literals in the regexp), or any of the
+usual regexp special characters and backslash constructs. If
+REGEXP is a string, it must be possible to apply `string' to
+individual elements of the keys stored in the trie. The matches
+returned in the alist will be sequences of the same type as KEY.
+
+Back-references and non-greedy postfix operators are *not*
+supported, and the matches are always anchored, so `$' and `^'
+lose their special meanings.
+
+If the regexp contains any non-shy grouping constructs, subgroup
+match data is included in the results. In this case, the car of
+each match is no longer just a key. Instead, it is a list whose
+first element is the matching key, and whose remaining elements
+are cons cells whose cars and cdrs give the start and end indices
+of the elements that matched the corresponding groups, in order.
+
+Note that any modification to DICT *immediately* invalidates all
+trie-stacks created before the modification (in particular,
+calling `dictree-stack-pop' will give unpredictable results).
+
+Operations on dictree-stacks are significantly more efficient
+than constructing a real stack from completions of PREFIX in DICT
+and using standard stack functions. As such, they can be useful
+in implementing efficient algorithms on tries. However, in cases
+where `dictree-complete' or `dictree-complete-ordered' is
+sufficient, it is better to use one of those instead."
+ (if (dictree--meta-dict-p dict)
+ (dictree--regexp-meta-stack-create dict regexp reverse)
+ (trie-regexp-stack (dictree--trie dict) regexp reverse)))
+
+
+(defun dictree-stack-pop (dictree-stack)
+ "Pop the first element from the DICTREE-STACK.
+Returns nil if the stack is empty."
+ (cond
+ ;; if elements have been pushed onto a dict stack, pop those first
+ ;; FIXME: shouldn't be using internal trie functions!
+ ((and (trie-stack-p dictree-stack)
+ (trie--stack-pushed dictree-stack))
+ (trie-stack-pop dictree-stack))
+ ;; if elements have been pushed onto a meta-dict stack, pop those
+ ;; first
+ ((and (dictree--meta-stack-p dictree-stack)
+ (dictree--meta-stack-pushed dictree-stack))
+ (pop (dictree--meta-stack-pushed dictree-stack)))
+ ;; otherwise, pop first element from dictree-stack
+ (t (let ((popped (dictree--stack-pop dictree-stack)))
+ (when popped
+ (cons (car popped) (dictree--cell-data (cdr popped))))))
+ ))
+
+
+(defun dictree-stack-push (element dictree-stack)
+ "Push ELEMENT onto DICTREE-STACK."
+ (if (trie-stack-p dictree-stack)
+ ;; normal dict
+ (trie-stack-push element dictree-stack)
+ ;; meta-dict
+ (push element (dictree--meta-stack-pushed dictree-stack))))
+
+
+(defun dictree-stack-first (dictree-stack)
+ "Return the first element from DICTREE-STACK, without removing it.
+Returns nil if the stack is empty."
+ ;; if elements have been pushed onto the stack, return first of those
+ (if (and (dictree--meta-stack-p dictree-stack)
+ (dictree--meta-stack-pushed dictree-stack))
+ (car (dictree--meta-stack-pushed dictree-stack))
+ ;; otherwise, return first element from dictree-stack
+ (let ((first (dictree--stack-first dictree-stack)))
+ (cons (car first) (dictree--cell-data (cdr first))))))
+
+
+(defun dictree-stack-empty-p (dictree-stack)
+ "Return t if DICTREE-STACK is empty, nil otherwise."
+ (if (trie-stack-p dictree-stack)
+ ;; normal dict
+ (trie-stack-empty-p dictree-stack)
+ ;; meta-dict
+ (and (heap-empty (dictree--meta-stack-heap dictree-stack))
+ (null (dictree--meta-stack-pushed dictree-stack)))))
+
+
+(defun dictree--stack-first (dictree-stack)
+ "Return the first element from DICTREE-STACK, without removing it.
+Returns nil if the stack is empty."
+ (if (trie-stack-p dictree-stack)
+ ;; normal dict
+ (trie-stack-first dictree-stack)
+ ;; meta-dict
+ (if (dictree--meta-stack-pushed dictree-stack)
+ ;; pushed element
+ (car (dictree--meta-stack-pushed dictree-stack))
+ ;; dictree-stack element
+ (dictree--stack-first
+ (heap-root (dictree--meta-stack-heap dictree-stack))))))
+
+
+(defun dictree--stack-pop (dictree-stack)
+ ;; Pop the raw first element from DICTREE-STACK. Returns nil if the
+ ;; stack is empty.
+
+ ;; dictree-stack for normal dictionaries is a trie-stack
+ (if (trie-stack-p dictree-stack)
+ (trie-stack-pop dictree-stack)
+
+ ;; meta-dictionary dictree-stack...more work!
+ ;; if elements have been pushed onto meta-dict stack, pop those
+ ;; first
+ (if (dictree--meta-stack-pushed dictree-stack)
+ (pop (dictree--meta-stack-pushed dictree-stack))
+ ;; otherwise...
+ (let ((heap (dictree--meta-stack-heap dictree-stack))
+ (sortfun (dictree--meta-stack-sortfun dictree-stack))
+ stack curr next cell)
+ (unless (heap-empty heap)
+ ;; remove the first dictree-stack from the heap, pop it's
+ ;; first element, and add it back to the heap (note that it
+ ;; will almost certainly not end up at the root again)
+ (setq stack (heap-delete-root heap))
+ (setq curr (dictree--stack-pop stack))
+ (unless (dictree-stack-empty-p stack) (heap-add heap stack))
+ ;; peek at the first element of the stack now at the root of
+ ;; the heap
+ (unless (heap-empty heap)
+ (setq next (dictree--stack-first (heap-root heap)))
+ ;; repeat this as long as we keep finding elements with the
+ ;; same key, combining them together as we go
+ (when (dictree--meta-stack-combfun dictree-stack)
+ (while (and (null (funcall sortfun
+ (car curr) (car next)))
+ (null (funcall sortfun
+ (car next) (car curr))))
+ (setq stack (heap-delete-root heap))
+ (setq next (dictree--stack-pop stack))
+ (setq curr
+ (cons
+ (car curr)
+ (dictree--cell-create
+ (funcall
+ (dictree--meta-stack-combfun dictree-stack)
+ (dictree--cell-data (cdr curr))
+ (dictree--cell-data (cdr next)))
+ (append (dictree--cell-plist (cdr curr))
+ (dictree--cell-plist (cdr next))))))
+ (heap-add heap stack)
+ (setq next (dictree--stack-first (heap-root heap))))))
+ ;; return the combined dictionary element
+ curr)))))
+
+
+
+
+;; ----------------------------------------------------------------
+;; Functions for building advanced queries
+
+(defun dictree--query
+ (dict arg cachefun cacheparamfun triefun stackfun
+ &optional rank-function maxnum reverse no-cache filter resultfun)
+ ;; Return results of querying DICT with argument ARG using TRIEFUN or
+ ;; STACKFUN. If result of calling CACHEPARAMFUN on DICT is non-nil,
+ ;; look first for cached result in cache returned by calling CACHEFUN
+ ;; on DICT, and cache result if query fulfils caching conditions. If
+ ;; RANK-FUNCTION is non-nil, return results ordered accordingly. If
+ ;; MAXNUM is an integer, only the first MAXNUM results will be
+ ;; returned. If REVERSE is non-nil, results are in reverse order. A
+ ;; non-nil NO-CACHE prevents caching of results, irrespective of
+ ;; DICT's cache settings. If supplied, only results that pass FILTER
+ ;; are included. A non-nil RESULTFUN is applied to results before
+ ;; adding them to final results list. Otherwise, an alist of key-data
+ ;; associations is returned.
+
+ ;; wrap DICT in a list if necessary
+ (when (dictree-p dict) (setq dict (list dict)))
+
+ (let (cache cacheparam completions cmpl cache-entry)
+ ;; map over all dictionaries in list
+ (dolist (dic dict)
+ (setq cache (funcall cachefun dic)
+ cacheparam (funcall cacheparamfun dic))
+ (cond
+ ;; If FILTER or custom RANK-FUNCTION was specified, look in trie
+ ;; since we don't cache custom searches. We pass a slightly
+ ;; redefined filter to `trie-complete' to deal with data
+ ;; wrapping.
+ ((or filter
+ (and rank-function
+ (not (eq rank-function (dictree-rank-function dic)))))
+ (setq cmpl
+ (dictree--do-query dic arg triefun stackfun
+ (dictree--wrap-rankfun rank-function)
+ maxnum reverse
+ (when filter
+ (dictree--wrap-filter filter)))))
+
+
+ ;; if there's a cached result with enough completions, use it
+ ((and (setq cache-entry
+ (if cacheparam
+ (gethash (cons arg reverse) cache)
+ nil))
+ (or (null (dictree--cache-maxnum cache-entry))
+ (and maxnum
+ (<= maxnum (dictree--cache-maxnum cache-entry)))))
+ (setq cmpl (dictree--cache-results cache-entry))
+ ;; drop any excess completions
+ (when (and maxnum
+ (or (null (dictree--cache-maxnum cache-entry))
+ (> (dictree--cache-maxnum cache-entry) maxnum)))
+ (setcdr (nthcdr (1- maxnum) completions) nil)))
+
+
+ ;; if there was nothing useful in the cache, do query and time it
+ (t
+ (let (time)
+ (setq time (float-time))
+ (setq cmpl
+ (dictree--do-query
+ dic arg triefun stackfun
+ (when rank-function
+ (dictree--wrap-rankfun rank-function))
+ maxnum reverse nil))
+ (setq time (- (float-time) time))
+ ;; if we're above the dictionary's completion cache threshold,
+ ;; cache the result
+ (when (and (not no-cache)
+ (dictree--above-cache-threshold-p
+ time (length arg) (dictree-cache-policy dic)
+ cacheparam))
+ (setf (dictree-modified dic) t)
+ (puthash (cons arg reverse)
+ (dictree--cache-create cmpl maxnum)
+ cache)))))
+
+ ;; merge new completion into completions list
+ (setq completions
+ (dictree--merge
+ completions cmpl
+ (if rank-function
+ (dictree--wrap-rankfun rank-function)
+ `(lambda (a b)
+ (,(trie-construct-sortfun
+ (dictree-comparison-function (car dict)))
+ (car a) (car b))))
+ nil maxnum)))
+
+ ;; return completions list, applying RESULTFUN is specified,
+ ;; otherwise just stripping meta-data
+ (mapcar
+ (if resultfun
+ (dictree--wrap-resultfun resultfun)
+ (lambda (el) (cons (car el) (dictree--cell-data (cdr el)))))
+ completions)))
+
+
+
+(defun dictree--do-query
+ (dict arg triefun stackfun &optional rank-function maxnum reverse filter)
+ ;; Return first MAXNUM results of querying DICT with ARG using TRIEFUN
+ ;; or STACKFUN that satisfy FILTER, ordered according to RANK-FUNCTION
+ ;; (defaulting to "lexical" order).
+
+ ;; for a meta-dict, use a dictree-stack
+ (if (dictree--meta-dict-p dict)
+ (let ((stack (funcall stackfun dict arg reverse))
+ (heap (when rank-function
+ (heap-create ; heap order is inverse of rank order
+ (if reverse
+ rank-function
+ (lambda (a b)
+ (not (funcall rank-function a b))))
+ (1+ maxnum))))
+ (i 0) cmpl completions)
+ ;; pop MAXNUM completions from the stack
+ (while (and (or (null maxnum) (< i maxnum))
+ (setq cmpl (dictree--stack-pop stack)))
+ ;; check completion passes FILTER
+ (when (or (null filter) (funcall filter cmpl))
+ (if rank-function
+ (heap-add heap cmpl) ; for ranked query, add to heap
+ (push cmpl completions)) ; for lexical query, add to list
+ (incf i)))
+ (if (null rank-function)
+ ;; for lexical query, reverse and return completion list (we
+ ;; built it backwards)
+ (nreverse completions)
+ ;; for ranked query, pass rest of completions through heap
+ (while (setq cmpl (dictree--stack-pop stack))
+ (heap-add heap cmpl)
+ (heap-delete-root heap))
+ ;; extract completions from heap
+ (while (setq cmpl (heap-delete-root heap))
+ (push cmpl completions))
+ completions)) ; return completion list
+
+ ;; for a normal dict, call corresponding trie function on dict's
+ ;; trie. Note: could use a dictree-stack here too - would it be more
+ ;; efficient?
+ (funcall triefun
+ (dictree--trie dict) arg rank-function
+ maxnum reverse filter)))
+
+
+
+;; ----------------------------------------------------------------
+;; Completing
+
+(defun dictree-complete
+ (dict prefix
+ &optional rank-function maxnum reverse no-cache filter resultfun)
+ "Return an alist containing all completions of PREFIX in DICT
+along with their associated data, sorted according to
+RANK-FUNCTION (defaulting to \"lexical\" order, i.e. the order
+defined by the dictionary's comparison function,
+cf. `dictree-create'). Return nil if no completions are found.
+
+PREFIX can also be a list of sequences, in which case completions of
+all elements in the list are returned, merged together in a
+single sorted alist.
+
+DICT can also be a list of dictionaries, in which case
+completions are sought in all dictionaries in the list. (Note
+that if the same key appears in multiple dictionaries, the alist
+may contain the same key multiple times, each copy associated
+with the data from a different dictionary. If you want to combine
+identical keys, use a meta-dictionary; see
+`dictree-meta-dict-create'.)
+
+If optional argument RANK-FUNCTION is any non-nil value that is
+not a function, the completions are sorted according to the
+dictionary's rank-function (see `dictree-create'). Any non-nil
+value that *is* a function over-rides this. In that case,
+RANK-FUNCTION should accept two arguments, both cons cells. The
+car of each contains a sequence from the trie (of the same type
+as PREFIX), the cdr contains its associated data. The
+RANK-FUNCTION should return non-nil if first argument is ranked
+strictly higher than the second, nil otherwise.
+
+The optional integer argument MAXNUM limits the results to the
+first MAXNUM completions. The default is to return all matches.
+
+If the optional argument NO-CACHE is non-nil, it prevents caching
+of the result. Ignored for dictionaries that do not have
+completion caching enabled.
+
+The FILTER argument sets a filter function for the
+completions. For each potential completion, it is passed two
+arguments: the completion, and its associated data. If the filter
+function returns nil, the completion is not included in the
+results, and doesn't count towards MAXNUM.
+
+RESULTFUN defines a function used to process results before
+adding them to the final result list. If specified, it should
+accept two arguments: a key and its associated data. It's return
+value is what gets added to the final result list, instead of the
+default key-data cons cell."
+ ;; run completion query
+ (dictree--query
+ dict prefix
+ (if rank-function
+ 'dictree-complete-ranked-cache
+ 'dictree-complete-cache)
+ (if rank-function
+ 'dictree-complete-ranked-cache-threshold
+ 'dictree-complete-cache-threshold)
+ 'trie-complete 'dictree-complete-stack
+ (when rank-function
+ (if (functionp rank-function)
+ rank-function
+ (dictree-rank-function (if (listp dict) (car dict) dict))))
+ maxnum reverse no-cache filter resultfun))
+
+
+
+(defun dictree-collection-function (dict string predicate all)
+ "Function for use in `try-completion', `all-completions',
+and `completing-read'. To complete from dictionary DICT, use the
+following as the COLLECTION argument of any of those functions:
+
+ (lambda (string predicate all)
+ (dictree-collection-function dict string predicate all))
+
+Note that PREDICATE will be called with two arguments: the
+completion, and its associated data."
+ (let ((completions
+ (dictree-complete dict string nil nil nil nil
+ predicate (lambda (key data) key))))
+ (if all completions (try-completion "" completions))))
+
+
+
+;; ----------------------------------------------------------------
+;; Regexp search
+
+(defun dictree-regexp-search
+ (dict regexp
+ &optional rank-function maxnum reverse no-cache filter resultfun)
+ "Return an alist containing all matches for REGEXP in TRIE
+along with their associated data, in the order defined by
+RANKFUN, defauling to \"lexical\" order (i.e. the order defined
+by the trie's comparison function). If REVERSE is non-nil, the
+completions are sorted in the reverse order. Returns nil if no
+completions are found.
+
+DICT can also be a list of dictionaries, in which case matches
+are sought in all dictionaries in the list. (Note that if the
+same key appears in multiple dictionaries, the alist may contain
+the same key multiple times, each copy associated with the data
+from a different dictionary. If you want to combine identical
+keys, use a meta-dictionary; see `dictree-meta-dict-create'.)
+
+REGEXP is a regular expression, but it need not necessarily be a
+string. It must be a sequence (vector, list of string) whose
+elements are either elements of the same type as elements of the
+trie keys (which behave as literals in the regexp), or any of the
+usual regexp special characters and backslash constructs. If
+REGEXP is a string, it must be possible to apply `string' to
+individual elements of the keys stored in the trie. The matches
+returned in the alist will be sequences of the same type as KEY.
+
+Only a subset of the full Emacs regular expression syntax is
+supported. There is no support for regexp constructs that are
+only meaningful for strings (character ranges and character
+classes inside character alternatives, and syntax-related
+backslash constructs). Back-references and non-greedy postfix
+operators are not supported, so `?' after a postfix operator
+loses its special meaning. Also, matches are always anchored, so
+`$' and `^' lose their special meanings (use `.*' at the
+beginning and end of the regexp to get an unanchored match).
+
+If the regexp contains any non-shy grouping constructs, subgroup
+match data is included in the results. In this case, the car of
+each match is no longer just a key. Instead, it is a list whose
+first element is the matching key, and whose remaining elements
+are cons cells whose cars and cdrs give the start and end indices
+of the elements that matched the corresponding groups, in order.
+
+If optional argument RANK-FUNCTION is any non-nil value that is
+not a function, the matches are sorted according to the
+dictionary's rank-function (see `dictree-create'). Any non-nil
+value that *is* a function over-rides this. In that case,
+RANK-FUNCTION should accept two arguments, both cons cells. The
+car of each contains a sequence from the dictionary (of the same
+type as PREFIX), the cdr contains its associated data. The
+RANK-FUNCTION should return non-nil if first argument is ranked
+strictly higher than the second, nil otherwise.
+
+The optional integer argument MAXNUM limits the results to the
+first MAXNUM matches. The default is to return all matches.
+
+If the optional argument NO-CACHE is non-nil, it prevents caching
+of the result. Ignored for dictionaries that do not have wildcard
+caching enabled.
+
+The FILTER argument sets a filter function for the matches. If
+supplied, it is called for each possible match with two
+arguments: the matching key, and its associated data. If the
+filter function returns nil, the match is not included in the
+results, and does not count towards MAXNUM.
+
+RESULTFUN defines a function used to process results before
+adding them to the final result list. If specified, it should
+accept two arguments: a key and its associated data. It's return
+value is what gets added to the final result list, instead of the
+default key-data cons cell."
+ ;; run regexp query
+ (dictree--query
+ dict regexp
+ (if rank-function
+ 'dictree-regexp-ranked-cache
+ 'dictree-regexp-cache)
+ (if rank-function
+ 'dictree-regexp-ranked-cache-threshold
+ 'dictree-regexp-cache-threshold)
+ 'trie-regexp-search 'dictree-regexp-stack
+ (when rank-function
+ (if (functionp rank-function)
+ rank-function
+ (dictree-rank-function (if (listp dict) (car dict) dict))))
+ maxnum reverse no-cache filter resultfun))
+
+
+
+
+;; ----------------------------------------------------------------
+;; Persistent storage
+
+(defun dictree-save (dict &optional compilation)
+ "Save dictionary DICT to it's associated file.
+Use `dictree-write' to save to a different file.
+
+Optional argument COMPILATION determines whether to save the
+dictionary in compiled or uncompiled form. The default is to save
+both forms. See `dictree-write'.
+
+Interactively, DICT is read from the mini-buffer."
+ (interactive (list (read-dict "Dictionary: ")))
+
+ (let ((filename (dictree-filename dict)))
+
+ ;; if dictionary has no associated file, prompt for one
+ (unless (and filename (> (length filename) 0))
+ (setq filename
+ (read-file-name
+ (format "Save dictionary %s to file\
+ (leave blank to NOT save): "
+ (dictree-name dict))
+ nil "")))
+
+ ;; if filename is blank, don't save
+ (if (string= filename "")
+ (message "Dictionary %s NOT saved" (dictree-name dict))
+ ;; otherwise write dictionary to file
+ (setf (dictree-filename dict) filename)
+ (dictree-write dict filename t compilation))))
+
+
+
+(defun dictree-write (dict &optional filename overwrite compilation)
+ "Write dictionary DICT to file FILENAME.
+Defaults to dictionary's current filename if FILENAME is not
+specified (like `dictree-save').
+
+If optional argument OVERWRITE is non-nil, no confirmation will
+be asked for before overwriting an existing file.
+
+The default is to create both compiled and uncompiled versions of
+the dictionary, with extensions .elc and .el respectively (if
+FILENAME has either of these extensions, they are stripped off
+before proceeding). The compiled version is always used in
+preference to the uncomplied version, as it loads
+faster. However, only the uncompiled version is portable between
+different Emacs versions.
+
+If optional argument COMPILATION is the symbol 'compiled, only
+the compiled version will be created, whereas if it is the symbol
+'uncompiled, only the uncompiled version will be created.
+
+Interactively, DICT and FILENAME are read from the mini-buffer,
+and OVERWRITE is the prefix argument."
+ (interactive (list (read-dict "Dictionary: ")
+ (read-file-name "Write dictionary to file: "
+ nil "")
+ current-prefix-arg))
+ ;; default to DICT's current file, if any
+ (when (or (null filename)
+ (and (called-interactively-p 'any) (string= filename "")))
+ (setq filename (dictree-filename dict)))
+ (if (null filename)
+ (progn
+ (message "Dictionary %s NOT written" (dictree-name dict))
+ nil) ; indicate dictionary wasn't written
+
+ (let (dictname buff tmpfile)
+ ;; remove any .el(c) extension from filename
+ (cond
+ ((and (> (length filename) 3)
+ (string= (substring filename -3) ".el"))
+ (setq filename (substring filename 0 -3)))
+ ((and (> (length filename) 4)
+ (string= (substring filename -4) ".elc"))
+ (setq filename (substring filename 0 -4))))
+ ;; create saved dictionary name from filename
+ (setq dictname (file-name-nondirectory filename))
+
+ (save-excursion
+ ;; create a temporary file
+ (setq buff
+ (find-file-noselect
+ (setq tmpfile (make-temp-file dictname))))
+ (set-buffer buff)
+ ;; call the appropriate write function to write the dictionary
+ ;; code
+ (if (dictree--meta-dict-p dict)
+ (dictree--write-meta-dict-code dict dictname filename)
+ (dictree--write-dict-code dict dictname filename))
+ (save-buffer)
+ (kill-buffer buff))
+
+ ;; prompt to overwrite if necessary
+ (when (or overwrite
+ (and
+ (or (eq compilation 'compiled)
+ (not (file-exists-p (concat filename ".el"))))
+ (or (eq compilation 'uncompiled)
+ (not (file-exists-p (concat filename ".elc")))))
+ (y-or-n-p
+ (format "File %s already exists. Overwrite? "
+ (concat filename ".el(c)"))))
+ (condition-case nil
+ (progn
+ ;; move the uncompiled version to its final destination
+ (unless (eq compilation 'compiled)
+ (copy-file tmpfile (concat filename ".el") t))
+ ;; byte-compile and move the compiled version to its final
+ ;; destination
+ (unless (eq compilation 'uncompiled)
+ (if (save-window-excursion
+ (let ((restore byte-compile-disable-print-circle)
+ err)
+ (setq byte-compile-disable-print-circle t)
+ (setq err (byte-compile-file tmpfile))
+ (setq byte-compile-disable-print-circle restore)
+ err))
+ (rename-file (concat tmpfile ".elc")
+ (concat filename ".elc") t)
+ (error ""))))
+ (error "Error writing dictionary. Dictionary %s NOT saved"
+ dictname))
+
+ ;; if writing to a different name, unload dictionary under old
+ ;; name and reload it under new one
+ (setf (dictree-modified dict) nil)
+ (setf (dictree-filename dict) filename)
+ (unless (string= dictname (dictree-name dict))
+ (dictree-unload dict)
+ (dictree-load filename)))
+
+ (delete-file tmpfile)
+ (message "Dictionary %s saved to %s" dictname filename)
+ t) ; return t to indicate dictionary was successfully saved
+ ))
+
+
+
+(defun dictree-save-modified (&optional dict ask compilation force
+ no-fail-query)
+ "Save all modified dictionaries that have their autosave flag set.
+Returns t if all dictionaries were successfully saved. Otherwise,
+inform the user about the dictionaries which failed to save
+properly, ask them whether they wish to continue anyway, and
+return t or nil accordingly.
+
+If optional argument DICT is a list of dictionaries or a single
+dictionary, only save those.
+
+If optional argument ASK is non-nil, ask for confirmation before
+saving.
+
+Optional argument COMPILATION determines whether to save the
+dictionaries in compiled or uncompiled form. The default is to
+save both forms. See `dictree-write'.
+
+If optional argument FORCE is non-nil, save modified dictionaries
+irrespective of their autosave flag.
+
+If optional argument NO-FAIL-QUERY is non-nil, the user will not
+be queried if a dictionary fails to save properly, and the return
+value is always nil.
+
+Interactively, FORCE is the prefix argument, and the user will not be
+asked whether they wish to continue after a failed save."
+ (interactive "P")
+
+ ;; sort out arguments
+ (when (and (called-interactively-p 'any) dict) (setq dict nil force t))
+ (when (dictree-p dict) (setq dict (list dict)))
+
+ ;; For each dictionary in list / each loaded dictionary, check if
+ ;; dictionary has been modified. If so, save it if autosave is set or
+ ;; FORCE is non-nil.
+ (let (save-failures)
+ (dolist (dic (if (null dict)
+ dictree-loaded-list
+ dict))
+ (when (and (dictree-modified dic)
+ (or force (dictree-autosave dic))
+ (or (not ask)
+ (y-or-n-p (format "Save modified dictionary %s? "
+ (dictree-filename dic)))))
+ (condition-case nil
+ (progn
+ (dictree-save dic compilation)
+ (setf (dictree-modified dic) nil))
+ (error (push dic save-failures)))))
+
+ ;; prompt if dictionary saving failed
+ (if save-failures
+ (if (or (called-interactively-p 'any) no-fail-query)
+ (progn
+ (message
+ (concat
+ "Error: failed to save the following modified "
+ "dictionaries: "
+ (mapconcat 'dictree--name save-failures ", ")))
+ nil)
+ (yes-or-no-p
+ (concat "Error: failed to save the following modified "
+ "dictionaries: "
+ (mapconcat 'dictree--name save-failures ", ")
+ "; continue anyway? ")))
+ t)))
+
+
+;; Add the dictree-save-modified function to the kill-emacs-hook to save
+;; modified dictionaries when exiting emacs
+(add-hook 'kill-emacs-query-functions 'dictree-save-modified)
+
+
+
+;;;###autoload
+(defun dictree-load (file)
+ "Load a dictionary object from file FILE.
+Returns the dictionary if successful, nil otherwise.
+
+Interactively, FILE is read from the mini-buffer."
+ (interactive (list (read-dict "Load dictionary: " nil nil t t)))
+
+ ;; sort out dictionary name and file name
+ (if (dictree-p file)
+ (message "Dictionary %s already loaded" (dictree-name file))
+
+ ;; load the dictionary
+ (if (not (load file t))
+ ;; if loading failed, throw error interactively, return nil
+ ;; non-interactively
+ (if (called-interactively-p 'any)
+ (error "Cannot open dictionary file: %s" file)
+ nil)
+
+ (let (dictname dict)
+ (setq dictname
+ (file-name-nondirectory (file-name-sans-extension file))
+ dict (eval (intern-soft dictname)))
+ (if (not (dictree-p dict))
+ ;; if loading failed, throw error interactively, return nil
+ ;; non-interactively
+ (if (called-interactively-p 'any)
+ (error "Error loading dictionary file: %s" file)
+ nil)
+
+ ;; ensure the dictionary name and file name associated with
+ ;; the dictionary match the file it was loaded from
+ (when (and (string= (file-name-nondirectory file) file)
+ (setq file
+ (locate-file file load-path load-suffixes)))
+ (setf (dictree-filename dict) file))
+ (setf (dictree-name dict) dictname)
+
+ ;; make sure the dictionary is in dictree-loaded-list
+ ;; (normally the lisp code in the dictionary itself should do
+ ;; this, but just to make sure...)
+ (unless (memq dict dictree-loaded-list)
+ (push dict dictree-loaded-list))
+ (message (format "Loaded dictionary %s" dictname))
+
+ ;; return dictionary
+ dict)))))
+
+
+
+(defun dictree-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.
+
+Interactively, DICT is read from the mini-buffer, and DONT-SAVE
+is the prefix argument."
+ (interactive (list (read-dict "Dictionary: ")
+ current-prefix-arg))
+
+ ;; if dictionary has been modified, autosave is set and not overidden,
+ ;; save it first
+ (when (and (dictree-modified dict)
+ (null dont-save)
+ (or (eq (dictree-autosave dict) t)
+ (and (eq (dictree-autosave dict) 'ask)
+ (y-or-n-p
+ (format
+ "Dictionary %s modified.\
+ Save before unloading? "
+ (dictree-name dict))))))
+ (dictree-save dict))
+
+ ;; if unloading a meta-dict, remove reference to it from constituent
+ ;; dictionaries' meta-dict-list cell
+ (when (dictree--meta-dict-p dict)
+ (mapc
+ (lambda (dic)
+ (setf (dictree--meta-dict-list dic)
+ (delq dict (dictree--meta-dict-list dic))))
+ (dictree--meta-dict-dictlist dict)))
+
+ ;; remove dictionary from list of loaded dictionaries and unload it
+ (setq dictree-loaded-list (delq dict dictree-loaded-list))
+ (unintern (dictree-name dict))
+ (message "Dictionary %s unloaded" (dictree-name dict)))
+
+
+
+(defun dictree--write-dict-code (dict dictname filename)
+ ;; Write code for normal dictionary DICT to current buffer, giving it
+ ;; the name DICTNAME and file FILENAME.
+ (let (hashcode tmpdict tmptrie lookup-alist
+ complete-alist complete-ranked-alist
+ regexp-alist regexp-ranked-alist)
+
+ ;; --- convert trie data ---
+ ;; if dictionary doesn't use any custom save functions, write
+ ;; dictionary's trie directly as is
+ (setq tmptrie (dictree--trie dict))
+ ;; otherwise, create a temporary trie and populate it with the
+ ;; converted contents of the dictionary's trie
+ (when (or (dictree--data-savefun dict)
+ (dictree--plist-savefun dict))
+ (setq tmptrie
+ (trie-create-custom
+ (trie-comparison-function tmptrie)
+ :createfun (trie--createfun tmptrie)
+ :insertfun (trie--insertfun tmptrie)
+ :deletefun (trie--deletefun tmptrie)
+ :lookupfun (trie--lookupfun tmptrie)
+ :mapfun (trie--mapfun tmptrie)
+ :emptyfun (trie--emptyfun tmptrie)
+ :stack-createfun (trie--stack-createfun tmptrie)
+ :stack-popfun (trie--stack-popfun tmptrie)
+ :stack-emptyfun (trie--stack-emptyfun tmptrie)))
+ (trie-mapc
+ (lambda (key cell)
+ (trie-insert tmptrie key
+ (dictree--cell-create
+ (funcall (or (dictree--data-savefun dict)
+ 'identity)
+ (dictree--cell-data cell))
+ (funcall (or (dictree--plist-savefun dict)
+ 'identity)
+ (dictree--cell-plist cell)))))
+ (dictree--trie dict))
+
+ ;; generate code to convert contents of trie back to original form
+ (setq hashcode
+ (concat
+ hashcode
+ " (trie-map\n"
+ " (lambda (key cell)\n"
+ " (dictree--cell-create\n"
+ (if (dictree--data-loadfun dict)
+ (concat
+ "(funcall (dictree--data-loadfun " dictname ")\n"
+ " (dictree--cell-data cell))\n")
+ " (dictree--cell-data cell)\n")
+ (if (dictree--plist-loadfun dict)
+ (concat
+ "(funcall (dictree--plist-loadfun " dictname ")\n"
+ " (dictree--cell-plist cell))))\n")
+ " (dictree--cell-plist cell)))\n")
+ " (dictree--trie " dictname "))\n")))
+
+
+ ;; --- convert caches for writing to file ---
+ ;; hash tables have no read syntax in older Emacsen, so we convert
+ ;; them to alists for writing
+ (unless (featurep 'hashtable-print-readable)
+ ;; convert lookup cache hash table to alist, if it exists
+ (when (dictree--lookup-cache-threshold dict)
+ (maphash
+ (lambda (key val)
+ (push
+ (cons key
+ (cons (mapcar 'car (dictree--cache-results val))
+ (dictree--cache-maxnum val)))
+ lookup-alist))
+ (dictree--lookup-cache dict))
+ ;; generate code to reconstruct the lookup hash table
+ (setq hashcode
+ (concat
+ hashcode
+ "(let ((lookup-cache (make-hash-table :test 'equal))\n"
+ " (trie (dictree--trie " dictname ")))\n"
+ " (mapc\n"
+ " (lambda (entry)\n"
+ " (puthash\n"
+ " (car entry)\n"
+ " (dictree--cache-create\n"
+ " (mapcar\n"
+ " (lambda (key)\n"
+ " (cons key (trie-member trie key)))\n"
+ " (dictree--cache-results (cdr entry)))\n"
+ " (dictree--cache-maxnum (cdr entry)))\n"
+ " lookup-cache))\n"
+ " (dictree--lookup-cache " dictname "))\n"
+ " (setf (dictree--lookup-cache " dictname ")\n"
+ " lookup-cache))\n")))
+
+ ;; convert query caches, if they exist
+ (dolist (cache-details
+ '((dictree--complete-cache-threshold
+ complete-alist dictree--complete-cache)
+ (dictree--complete-ranked-cache-threshold
+ complete-ranked-alist dictree--complete-ranked-cache)
+ (dictree--regexp-cache-threshold
+ regexp-alist dictree--regexp-cache)
+ (dictree--regexp-ranked-cache-threshold
+ regexp-ranked-alist dictree--regexp-ranked-cache)))
+ (when (funcall (nth 0 cache-details) dict)
+ ;; convert hash table to alist
+ (set (nth 1 cache-details)
+ (let (alist)
+ (maphash
+ (lambda (key val)
+ (push
+ (cons key
+ (cons
+ (mapcar 'car (dictree--cache-results val))
+ (dictree--cache-maxnum val)))
+ alist))
+ (funcall (nth 2 cache-details) dict))
+ alist))
+ ;; generate code to reconstruct hash table from alist
+ (setq
+ hashcode
+ (concat
+ hashcode
+ "(let ((cache (make-hash-table :test 'equal))\n"
+ " (trie (dictree--trie " dictname ")))\n"
+ " (mapc\n"
+ " (lambda (entry)\n"
+ " (puthash\n"
+ " (car entry)\n"
+ " (dictree--cache-create\n"
+ " (mapcar\n"
+ " (lambda (key)\n"
+ " (cons key\n"
+ " (trie-member\n"
+ " trie (if (stringp key) key (car key)))))\n"
+ " (dictree--cache-results (cdr entry)))\n"
+ " (dictree--cache-maxnum (cdr entry)))\n"
+ " cache))\n"
+ " (" (symbol-name (nth 2 cache-details)) " " dictname "))\n"
+ " (setf (" (symbol-name (nth 2 cache-details)) " "
+ dictname ")\n"
+ " cache))\n")))))
+
+
+ ;; --- write to file ---
+ ;; generate the structure to save
+ (setq tmpdict (dictree--copy dict))
+ (setf (dictree--trie tmpdict) tmptrie
+ (dictree--name tmpdict) dictname
+ (dictree--filename tmpdict) filename
+ (dictree--modified tmpdict) nil
+ (dictree--meta-dict-list tmpdict) nil)
+ (unless (featurep 'hashtable-print-readable)
+ (setf (dictree--lookup-cache tmpdict) lookup-alist
+ (dictree--complete-cache tmpdict) complete-alist
+ (dictree--complete-ranked-cache tmpdict) complete-ranked-alist
+ (dictree--regexp-cache tmpdict) regexp-alist
+ (dictree--regexp-ranked-cache tmpdict) regexp-ranked-alist))
+
+ ;; write lisp code that generates the dictionary object
+ (let ((print-circle t) (print-level nil) (print-length nil))
+ (insert "(eval-when-compile (require 'cl))\n")
+ (insert "(require 'dict-tree)\n")
+ (insert "(defvar " dictname " nil \"Dictionary " dictname ".\")\n")
+ (insert "(setq " dictname " " (prin1-to-string tmpdict) ")\n")
+ (when hashcode (insert hashcode))
+ (insert "(unless (memq " dictname " dictree-loaded-list)\n"
+ " (push " dictname " dictree-loaded-list))\n"))))
+
+
+
+(defun dictree--write-meta-dict-code (dict dictname filename)
+ ;; Write code for meta-dictionary DICT to current buffer, giving it
+ ;; the name DICTNAME and file FILENAME.
+ (let (hashcode tmpdict lookup-alist
+ complete-alist complete-ranked-alist
+ regexp-alist regexp-ranked-alist)
+
+ ;; --- convert caches for writing to file ---
+ ;; hash tables have no read syntax in older Emacsen, so we convert
+ ;; them to alists for writing
+ (unless (featurep 'hashtable-print-readable)
+ ;; convert lookup cache hash table to an alist, if it exists
+ (when (dictree--meta-dict-lookup-cache-threshold dict)
+ (maphash (lambda (key val)
+ (push (cons key (mapcar 'car val)) lookup-alist))
+ (dictree--meta-dict-lookup-cache dict))
+ ;; generate code to reconstruct the lookup hash table
+ (setq hashcode
+ (concat
+ hashcode
+ "(let ((cache (make-hash-table :test 'equal)))\n"
+ " (mapc (lambda (entry)\n"
+ " (puthash (car entry) (cdr entry) cache))\n"
+ " (dictree--meta-dict-lookup-cache " dictname "))\n"
+ " (setf (dictree--meta-dict-lookup-cache " dictname ")\n"
+ " cache))\n")))
+
+ ;; convert query caches, if they exist
+ (dolist (cache-details
+ '((dictree--meta-dict-complete-cache-threshold
+ complete-alist
+ dictree--meta-dict-complete-cache)
+ (dictree--meta-dict-complete-ranked-cache-threshold
+ complete-ranked-alist
+ dictree--meta-dict-complete-ranked-cache)
+ (dictree--meta-dict-regexp-cache-threshold
+ regexp-alist
+ dictree--meta-dict-regexp-cache)
+ (dictree--meta-dict-regexp-ranked-cache-threshold
+ regexp-ranked-alist
+ dictree--meta-dict-regexp-ranked-cache)))
+ (when (funcall (nth 0 cache-details) dict)
+ ;; convert hash table to alist
+ (set (nth 1 cache-details)
+ (let (alist)
+ (maphash
+ (lambda (key val) (push (cons key val) alist))
+ (funcall (nth 2 cache-details) dict))
+ alist))
+ ;; generate code to reconstruct hash table from alist
+ (setq
+ hashcode
+ (concat
+ hashcode
+ "(let ((cache (make-hash-table :test 'equal)))\n"
+ " (mapc (lambda (entry)\n"
+ " (puthash (car entry) (cdr entry) cache))\n"
+ " (" (symbol-name (nth 2 cache-details)) " "
+ dictname "))\n"
+ " (setf (" (symbol-name (nth 2 cache-details)) " "
+ dictname ")\n"
+ " cache))\n")))))
+
+
+ ;; --- write to file ---
+ ;; generate the structure to save
+ (setq tmpdict (dictree--meta-dict-copy dict))
+ (setf (dictree--meta-dict-name tmpdict) dictname
+ (dictree--meta-dict-filename tmpdict) filename
+ (dictree--meta-dict-modified tmpdict) nil
+ (dictree--meta-dict-meta-dict-list tmpdict) nil
+ (dictree--meta-dict-dictlist tmpdict)
+ (mapcar (lambda (dic) (intern (dictree-name dic)))
+ (dictree--meta-dict-dictlist dict)))
+ (unless (featurep 'hashtable-print-readable)
+ (setf (dictree--meta-dict-lookup-cache tmpdict) lookup-alist
+ (dictree--meta-dict-complete-cache tmpdict) complete-alist
+ (dictree--meta-dict-complete-ranked-cache tmpdict)
+ complete-ranked-alist
+ (dictree--meta-dict-regexp-cache tmpdict) regexp-alist
+ (dictree--meta-dict-regexp-ranked-cache tmpdict)
+ regexp-ranked-alist))
+
+ ;; write lisp code that generates the dictionary object
+ (let ((print-circle t) (print-level nil) (print-length nil))
+ (insert "(eval-when-compile (require 'cl))\n"
+ "(require 'dict-tree)\n")
+ (mapc
+ (lambda (dic)
+ (insert "(unless (dictree-load \"" (dictree-filename dic) "\")\n"
+ " (error \"Failed to load dictionary \\\""
+ (dictree-name dic) "\\\" required by meta-dict \\\""
+ dictname "\\\"\"))\n"))
+ (dictree--meta-dict-dictlist dict))
+ (insert "(defvar " dictname " nil \"Dictionary " dictname ".\")\n"
+ "(setq " dictname " " (prin1-to-string tmpdict) ")\n"
+ "(setf (dictree--meta-dict-dictlist " dictname ")\n"
+ " (mapcar 'eval (dictree--meta-dict-dictlist "
+ dictname ")))\n")
+ (when hashcode (insert hashcode))
+ (insert "(unless (memq " dictname " dictree-loaded-list)"
+ " (push " dictname " dictree-loaded-list))\n"))))
+
+
+
+;; ----------------------------------------------------------------
+;; Dumping and restoring contents
+
+(defun dictree-populate-from-file
+ (dict file
+ &optional insert-function key-loadfun data-loadfun plist-loadfun
+ balance)
+ "Populate dictionary DICT from the key list in file FILE.
+
+Each line of FILE should contain a key, either a string
+\(delimited by \"\), a vector, or a list. (Use the escape
+sequence \\\" to include a \" in a string.) If a line does not
+contain a key, it is silently ignored.
+
+Each line can optionally include data and a property list (in
+that order) to be associated with the key. If present, these
+should separated from each other and the key by whitespace.
+
+INSERT-FUNCTION, KEY-LOAD-FUNCTION, DATA-LOAD-FUNCTION and
+PLIST-LOAD-FUNCTION override the corresponding default functions
+for DICT (see `dictree-create').
+
+Interactively, DICT and FILE are read from the mini-buffer.
+
+
+Technicalities:
+
+The key, data and property list are read as lisp expressions
+using `read'. The keys will be read from FILE in order, unless
+BALANCE is non-nil, in which case they are read from the median
+element outwards (which can help ensure efficient data structures
+are created when using a trie that is not self-balancing, see
+`dictree-create')."
+ (interactive (list (read-dict "Dictionary: ")
+ (read-file-name "File to populate from: "
+ nil "" t)))
+
+ (if (and (called-interactively-p 'any) (string= file ""))
+ (message "No file specified; dictionary %s NOT populated"
+ (dictree-name dict))
+
+ (unless (dictree--meta-dict-p dict)
+ (unless key-loadfun
+ (setq key-loadfun (dictree--key-loadfun dict)))
+ (unless data-loadfun
+ (setq data-loadfun (dictree--data-loadfun dict)))
+ (unless plist-loadfun
+ (setq plist-loadfun (dictree--plist-loadfun dict))))
+
+ (save-excursion
+ (let ((buff (find-file-noselect file)))
+ (set-buffer buff)
+
+ ;; insert the keys starting from the median to ensure a
+ ;; reasonably well-balanced tree
+ (let* ((lines (count-lines (point-min) (point-max)))
+ (midpt (+ (/ lines 2) (mod lines 2)))
+ entry)
+ (message "Inserting keys in %s...(1 of %d)"
+ (dictree-name dict) lines)
+ ;; insert the median key and set the dictionary's modified
+ ;; flag
+ (if balance
+ (dictree--goto-line midpt)
+ (goto-char (point-min)))
+ (when (setq entry
+ (condition-case nil
+ (dictree--read-line
+ dict key-loadfun data-loadfun
+ plist-loadfun)
+ (error (error "Error reading line %d of %s"
+ midpt file))))
+ (dictree-insert dict (car entry) (nth 1 entry)
+ insert-function)
+ (setf (dictree--cell-plist
+ (dictree--lookup dict (car entry) nil))
+ (nth 2 entry)))
+ ;; insert keys successively further away from the median in
+ ;; both directions
+ (dotimes (i (1- (if balance midpt lines)))
+ (if balance
+ (dictree--goto-line (+ midpt i 1))
+ (forward-line 1))
+ (when (setq entry
+ (condition-case nil
+ (dictree--read-line
+ dict key-loadfun data-loadfun
+ plist-loadfun)
+ (error (error "Error reading line %d of %s"
+ (+ midpt i 1) file))))
+ (dictree-insert dict (car entry) (nth 1 entry)
+ insert-function)
+ (setf (dictree--cell-plist
+ (dictree--lookup dict (car entry) nil))
+ (nth 2 entry)))
+ (when (= 49 (mod i 50))
+ (message "Inserting keys in %s...(%d of %d)"
+ (dictree-name dict)
+ (if balance (+ (* 2 i) 2) i)
+ lines))
+ (when balance
+ (dictree--goto-line (- midpt i 1))
+ (when (setq entry
+ (condition-case nil
+ (dictree--read-line
+ dict key-loadfun data-loadfun
+ plist-loadfun)
+ (error (error "Error reading line %d of %s"
+ (- midpt i 1) file))))
+ (dictree-insert dict (car entry)
+ (nth 1 entry) insert-function)
+ (setf
+ (dictree--cell-plist
+ (dictree--lookup dict (car entry) nil))
+ (nth 2 entry)))))
+
+ ;; if inserting from mid-point out, and file contains an even
+ ;; number of keys, we still have to add the last one
+ (when (and balance (= 0 (mod lines 2)))
+ (dictree--goto-line lines)
+ (when (setq entry
+ (condition-case nil
+ (dictree--read-line
+ dict key-loadfun data-loadfun
+ plist-loadfun)
+ (error (error "Error reading line %d of %s"
+ lines file))))
+ (dictree-insert dict (car entry) (nth 1 entry)
+ insert-function)
+ (setf (dictree--cell-plist
+ (dictree--lookup dict (car entry) nil))
+ (nth 2 entry))))
+
+ (message "Inserting keys in %s...done" (dictree-name dict)))
+ (kill-buffer buff)))))
+
+
+
+(defun dictree--read-line
+ (dict &optional key-loadfun data-loadfun plist-loadfun)
+ ;; Return a list containing the key, data (if any, otherwise nil) and
+ ;; property list (ditto) at the current line of the current buffer,
+ ;; for dictionary DICT.
+ (save-excursion
+ (let (key data plist)
+ ;; read key
+ (beginning-of-line)
+ (when (setq key (read (current-buffer)))
+ (when key-loadfun (setq key (funcall key-loadfun key)))
+ ;; if there's anything after the key, use it as data
+ (unless (eq (line-end-position) (point))
+ (setq data (read (current-buffer))))
+ (when data-loadfun (setq data (funcall data-loadfun data)))
+ ;; if there's anything after the data, use is as the property
+ ;; list
+ (unless (eq (line-end-position) (point))
+ (setq plist (read (current-buffer))))
+ (when plist-loadfun (funcall plist-loadfun plist))
+ ;; return the key and data
+ (list key data plist)))))
+
+
+
+(defun dictree-dump-to-buffer (dict &optional buffer type)
+ "Dump keys and their associated data
+from dictionary DICT to BUFFER, in the same format as that used
+by `dictree-populate-from-file'. If BUFFER exists, data will be
+appended to the end of it. Otherwise, a new buffer will be
+created. If BUFFER is omitted, the current buffer is used.
+
+TYPE determines the type of sequence to use to represent the
+keys, and should be one of 'string, 'vector or 'list. The default
+is 'vector.
+
+Note that if the data does not have a read syntax, the dumped
+data can not be used to recreate the dictionary using
+`dictree-populate-from-file'.
+
+Interactively, DICT and BUFFER are read from the mini-buffer,
+TYPE is always 'string."
+ (interactive (list (read-dict "Dictionary: ")
+ (read-buffer
+ "Buffer to dump to (defaults to current): "
+ (buffer-name (current-buffer)))
+ 'string))
+
+ ;; select the buffer, creating it if necessary
+ (if buffer
+ (setq buffer (get-buffer-create buffer))
+ (setq buffer (current-buffer)))
+ (set-buffer buffer)
+
+ ;; move point to end of buffer and make sure it's at start of new line
+ (goto-char (point-max))
+ (unless (= (point) (line-beginning-position))
+ (insert "\n"))
+
+ ;; dump keys
+ (message "Dumping keys from %s to %s..."
+ (dictree-name dict) (buffer-name buffer))
+ (let ((count 0) (dictsize (dictree-size dict)))
+ (message "Dumping keys from %s to %s...(key 1 of %d)"
+ (dictree-name dict) (buffer-name buffer) dictsize)
+
+ ;; map dump function over dictionary
+ (dictree--mapc
+ (lambda (key data plist)
+ (when (= 99 (mod count 100))
+ (message "Dumping keys from %s to %s...(key %d of %d)"
+ (dictree-name dict) (buffer-name buffer)
+ (1+ count) dictsize))
+ (insert (prin1-to-string
+ (funcall (or (dictree--key-savefun dict) 'identity)
+ key)))
+ (when (setq data
+ (funcall (or (dictree--data-savefun dict) 'identity)
+ data))
+ (insert " " (prin1-to-string data)))
+ (when (setq plist
+ (funcall (or (dictree--plist-savefun dict) 'identity)
+ plist))
+ (unless data (insert " nil"))
+ (insert " " (prin1-to-string plist)))
+ (insert "\n")
+ (setq count (1+ count)))
+ dict type) ; dictree-mapc target
+
+ (message "Dumping keys from %s to %s...done"
+ (dictree-name dict) (buffer-name buffer)))
+ (switch-to-buffer buffer))
+
+
+
+(defun dictree-dump-to-file (dict filename &optional type overwrite)
+ "Dump keys and their associated data
+from dictionary DICT to a text file FILENAME, in the same format
+as that used by `dictree-populate-from-file'. Prompts to overwrite
+FILENAME if it already exists, unless OVERWRITE is non-nil.
+
+TYPE determines the type of sequence to use to represent the
+keys, and should be one of 'string, 'vector or 'list. The default
+is 'vector.
+
+Note that if the data does not have a read syntax and no , the dumped
+data can not be used to recreate the dictionary using
+`dictree-populate-from-file'.
+
+Interactively, DICT and FILE are read from the mini-buffer,
+OVERWRITE is the prefix argument, and TYPE is always 'string."
+ (interactive (list (read-dict "Dictionary: ")
+ (read-file-name "File to dump to: " nil "")))
+
+ (if (and (called-interactively-p 'any) (string= filename ""))
+ (message "Dictionary %s NOT dumped" (dictree-name dict))
+
+ ;; check if file exists, and prompt to overwrite it if necessary
+ (if (and (file-exists-p filename)
+ (not overwrite)
+ (not (y-or-n-p
+ (format "File %s already exists. Overwrite? "
+ filename))))
+ (message "Key dump cancelled")
+
+ (let (buff)
+ ;; create temporary buffer, dump keys to it, and save to
+ ;; FILENAME
+ (setq buff (generate-new-buffer filename))
+ (save-window-excursion
+ (dictree-dump-to-buffer dict buff type)
+ (write-file filename))
+ (kill-buffer buff)))))
+
+
+
+
+;; ----------------------------------------------------------------
+;; Minibuffer completion
+
+(defvar dictree-history nil
+ "History list for commands that read a dictionary name.")
+
+(defvar dictree-loaded-history nil
+ "History list for commands that read a loaded dictionary name.")
+
+
+;;;###autoload
+(defun read-dict
+ (prompt &optional default dictlist allow-unloaded allow-unmatched)
+ "Read the name of a dictionary with completion, and return it.
+
+Prompt with PROMPT. By default, return DEFAULT. If DICTLIST is
+supplied, only complete on dictionaries in that list.
+
+If ALLOW-UNLOADED is non-nil, also complete on the names of
+unloaded dictionaries (actually, on any Elisp file in the current
+`load-path' restricted to subdirectories of your home directory
+whose file name starts with \"dict-\"). If an unloaded dictionary
+is read, the name of the Elisp file will be returned, without
+extension, suitable for passing to `load-library'."
+
+ (let (dictname paths)
+ ;; when allowing unloaded dictionaries...
+ (when allow-unloaded
+ ;; get paths in load-path that are subdirectories of home
+ ;; directory
+ (dolist (d load-path)
+ (when (eq (aref d 0) ?~) (push d paths)))
+ ;; gather names of all Elisp libraries in this restricted
+ ;; load-path
+ (dolist (f (all-completions
+ "" (apply-partially 'locate-file-completion-table
+ paths (get-load-suffixes))))
+ (when (and (null (file-name-directory f))
+ (and (> (length f) 5)
+ (string= (substring f 0 5) "dict-"))
+ (null (file-name-extension f))
+ (not (member (file-name-sans-extension f) dictname)))
+ (push (file-name-sans-extension f) dictname))))
+ ;; gather names of loaded dictionaries
+ (mapc (lambda (dict)
+ (unless (or (null (dictree-name dict))
+ (member (dictree-name dict) dictname))
+ (push (list (dictree-name dict)) dictname)))
+ (or dictlist dictree-loaded-list))
+ ;; do completing-read
+ (setq dictname (completing-read
+ prompt
+ (if allow-unmatched
+ (completion-table-in-turn
+ dictname 'read-file-name-internal)
+ dictname)
+ nil (not allow-unmatched) nil
+ (if allow-unloaded
+ 'dictree-history
+ 'dictree-loaded-history)
+ (and (dictree-p default) (dictree-name default))))
+ ;; return dictionary
+ (cond
+ ;; if user typed a file name, return that
+ ((and allow-unmatched (file-regular-p dictname)) dictname)
+ ;; if user selected a loaded dictionary, return dict itself
+ ((condition-case nil
+ (dictree-p (eval (intern-soft dictname)))
+ (void-variable nil))
+ (eval (intern-soft dictname)))
+ ;; if user selected an unloaded dictionary, return dict name
+ ((and allow-unloaded (stringp dictname)) dictname)
+ ;; if DEFAULT was specified, return that
+ (default default)
+ ;; should never get here!
+ (t (error "Unknown error reading dictionary")))
+ ))
+
+
+
+;; ----------------------------------------------------------------
+;; Pretty-print dictionaries during edebug
+
+;; We advise the `edebug-prin1' and `edebug-prin1-to-string' functions
+;; (actually, aliases) so that they print "#<dict-tree NAME>" instead of
+;; the full print form for dictionaries.
+;;
+;; This is because, if left to its own devices, edebug hangs for ages
+;; whilst printing large dictionaries, and you either have to wait for a
+;; *very* long time for it to finish, or kill Emacs entirely. (Even C-g
+;; C-g fails!)
+;;
+;; We do this also for lists of dictionaries, since those occur quite
+;; often, but not for other sequence types or deeper nested structures,
+;; to keep the implementation as simple as possible.
+;;
+;; Since the print form of a dictionary is practically incomprehensible
+;; anyway, we don't lose much by doing this. If you *really* want to
+;; print dictionaries in full whilst edebugging, despite this warning,
+;; disable the advice.
+;;
+;; FIXME: Should use `cedet-edebug-prin1-extensions' instead of advice
+;; when `cedet-edebug' is loaded, though I believe this still
+;; works in that case.
+
+
+(eval-when-compile
+ (require 'edebug)
+ (require 'advice))
+
+
+(defun dictree--edebug-pretty-print (object)
+ (cond
+ ((dictree-p object)
+ (concat "#<dict-tree \"" (dictree-name object) "\">"))
+ ((null object) "nil")
+ ((let ((dlist object) (test t))
+ (while (or (dictree-p (car-safe dlist))
+ (and dlist (setq test nil)))
+ (setq dlist (cdr dlist)))
+ test)
+ (concat "(" (mapconcat (lambda (d)
+ (concat "#<dict-tree \""
+ (dictree-name d) "\">"))
+ object " ") ")"))
+ ;; ((vectorp object)
+ ;; (let ((pretty "[") (len (length object)))
+ ;; (dotimes (i (1- len))
+ ;; (setq pretty
+ ;; (concat pretty
+ ;; (if (trie-p (aref object i))
+ ;; "#<trie>" (prin1-to-string (aref object i))) " ")))
+ ;; (concat pretty
+ ;; (if (trie-p (aref object (1- len)))
+ ;; "#<trie>" (prin1-to-string (aref object (1- len))))
+ ;; "]")))
+ ))
+
+
+(when (fboundp 'ad-define-subr-args)
+ (ad-define-subr-args 'edebug-prin1 '(object &optional printcharfun)))
+
+(defadvice edebug-prin1
+ (around dictree activate compile preactivate)
+ (let ((pretty (dictree--edebug-pretty-print object)))
+ (if pretty
+ (progn
+ (prin1 pretty printcharfun)
+ (setq ad-return-value pretty))
+ ad-do-it)))
+
+
+(when (fboundp 'ad-define-subr-args)
+ (ad-define-subr-args 'edebug-prin1-to-string '(object &optional noescape)))
+
+(defadvice edebug-prin1-to-string
+ (around dictree activate compile preactivate)
+ (let ((pretty (dictree--edebug-pretty-print object)))
+ (if pretty
+ (setq ad-return-value pretty)
+ ad-do-it)))
+
+
+
+(provide 'dict-tree)
+
+;;; dict-tree.el ends here