diff options
author | Toby S. Cubitt <tsc25@cantab.net> | 2012-04-29 13:45:44 +0200 |
---|---|---|
committer | Toby S. Cubitt <tsc25@cantab.net> | 2012-04-29 13:45:44 +0200 |
commit | 85daf9db8bacbf4932671398da24d169a6b80a38 (patch) | |
tree | 5218d159ff971360aca89c198b37a369f0a9e8f3 /packages/dict-tree | |
parent | 9615c7f2e73b429feffd5387744600294f0974f4 (diff) | |
download | emacs-85daf9db8bacbf4932671398da24d169a6b80a38.tar.gz |
Add dict-tree.el
Diffstat (limited to 'packages/dict-tree')
-rw-r--r-- | packages/dict-tree/dict-tree.el | 3584 |
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 |