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

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

[elpa] master cf2001d: Upgrade data structure packages to latest version


From: Toby Cubitt
Subject: [elpa] master cf2001d: Upgrade data structure packages to latest versions.
Date: Wed, 16 Aug 2017 11:34:49 -0400 (EDT)

branch: master
commit cf2001d3e83e05af5820174e9fa1f9638a4f8c08
Author: Toby S. Cubitt <address@hidden>
Commit: Toby S. Cubitt <address@hidden>

    Upgrade data structure packages to latest versions.
---
 packages/dict-tree/dict-tree.el | 2763 ++++++++++++++++++++++++---------------
 packages/heap/heap.el           |   98 +-
 packages/queue/queue.el         |   19 +-
 packages/trie/trie.el           | 1595 +++++++++++++++++-----
 4 files changed, 3015 insertions(+), 1460 deletions(-)

diff --git a/packages/dict-tree/dict-tree.el b/packages/dict-tree/dict-tree.el
index a21fcc4..18a87b3 100644
--- a/packages/dict-tree/dict-tree.el
+++ b/packages/dict-tree/dict-tree.el
@@ -1,12 +1,12 @@
-;;; dict-tree.el --- Dictionary data structure
+;;; dict-tree.el --- Dictionary data structure  -*- lexical-binding: t; -*-
 
-;; Copyright (C) 2004-2012  Free Software Foundation, Inc
+;; Copyright (C) 2004-2015, 2017  Free Software Foundation, Inc
 
 ;; Author: Toby Cubitt <address@hidden>
-;; Version: 0.12.8
+;; Version: 0.14
 ;; Keywords: extensions, matching, data structures
 ;;           trie, tree, dictionary, completion, regexp
-;; Package-Requires: ((trie "0.2.5") (tNFA "0.1.1") (heap "0.3"))
+;; Package-Requires: ((trie "0.3) (tNFA "0.1.1") (heap "0.3"))
 ;; URL: http://www.dr-qubit.org/emacs.php
 
 ;; This file is part of Emacs.
@@ -27,24 +27,63 @@
 
 ;;; 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.
+;; A dict-tree (created using `dictree-create') is used to store strings,
+;; along with arbitrary data associated with each string. (Note that the
+;; "strings" can be any sequence data type, not just Elisp strings.) As well
+;; as basic data insertion (`dictree-insert'), manipulation
+;; (`dictree-insert'), and retrieval (`dictree-lookup', `dictree-member-p'), a
+;; dict-tree can perform sophisticated queries on strings, including:
 ;;
-;; 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.
+;; - retrieve all completions of a prefix
+;;   (`dictree-complete')
 ;;
-;; This package uses the trie package trie.el. the tagged NFA package tNFA.el,
+;; - retrieve all strings that match a regular expression
+;;   (`dictree-regexp-search')
+;;
+;; - retrieve all fuzzy matches to a string, i.e. matches within a specified
+;;   Lewenstein distance (a.k.a. edit distance)
+;;   (`dictree-fuzzy-match')
+;;
+;; - retrieve all fuzzy completions of a prefix, i.e. completions of prefixes
+;;   within a specified Lewenstein distance
+;;   (`dictree-fuzzy-complete')
+;;
+;; The results of all of these queries can be ranked in alphabetical order, or
+;; according to any other desired ranking. The results can also be limited to
+;; a given number of matches.
+;;
+;; These sophisticated string queries are fast even for very large dict-trees,
+;; and dict-tree's also cache query results (and automatically keep these
+;; caches synchronised) to speed up queries even further.
+;;
+;; Other functions allow you to:
+;;
+;; - create dict-tree stack objects, which allow efficient access to the
+;;   strings in the dictionary or in query results as though they were sorted
+;;   on a stack (useful for designing efficient algorithms on top of
+;;   dict-trees)
+;;   (`dictree-stack', `dictree-complete-stack', `dictree-regexp-stack',
+;;    `dictree-fuzzy-match-stack', `dictree-fuzzy-complete-stack')
+;;
+;; - generate dict-tree iterator objects which allow you to retrieve
+;;   successive elements by calling `iter-next'
+;;   (`dictree-iter', `dictree-complete-iter', `dictree-regexp-iter',
+;;    `dictree-fuzzy-match-iter', `dictree-fuzzy-complete-iter')
+;;
+;; - map over all strings in alphabetical order
+;;   (`dictree-mapc', `dictree-mapcar' and `dictree-mapf')
+;;
+;; Dict-trees can be combined together into a "meta dict-tree", which combines
+;; the data from identical keys in its constituent dict-trees, in whatever way
+;; you specify (`dictree-create-meta-dict'). Any number of dict-trees can be
+;; combined in this way. Meta-dicts behave *exactly* like dict-trees: all of
+;; the above functions work on meta-dicts as well as dict-trees, and
+;; meta-dicts can themselves be used in new meta-dicts.
+;;
+;; The package also provides persistent storage of dict-trees to file.
+;; (`dictree-save', `dictree-write', `dictee-load')
+;;
+;; This package uses the trie package trie.el, the tagged NFA package tNFA.el,
 ;; and the heap package heap.el.
 
 
@@ -53,10 +92,8 @@
 (eval-when-compile (require 'cl))
 (require 'trie)
 (require 'tNFA)
-(require 'bytecomp)
-
-
 
+
 ;;; ================================================================
 ;;;            Replacements for CL and Elisp functions
 
@@ -90,21 +127,16 @@ If START or END is negative, it counts from the end."
 
 
 ;; `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)
+(defun 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)))))
+  (goto-char 1)
+  (if (eq selective-display t)
+      (re-search-forward "[\n\C-m]" nil 'no-error (1- line))
+    (forward-line (1- line))))
 
 
 
+
 ;;; ====================================================================
 ;;;  Internal functions and variables for use in the dictionary package
 
@@ -148,7 +180,7 @@ If START or END is negative, it counts from the end."
 ;; ----------------------------------------------------------------
 ;;                 Dictionary cache entry structures
 
-;; Note: We *could* us a defstruct for the cache entries, but for
+;; Note: We *could* use 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).
@@ -163,46 +195,193 @@ If START or END is negative, it counts from the end."
 (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
+(defalias 'dictree--cache-set-results 'setcar)  ; INTERNAL USE ONLY
 
 ;; Set the completions list for cache entry CACHE
 (defalias 'dictree--cache-set-maxnum 'setcdr)  ; INTERNAL USE ONLY
 
+;; define setf methods so we can use setf abstraction wherever possible
+(defsetf dictree--cache-results dictree--cache-set-results)
+(defsetf dictree--cache-maxnum dictree--cache-set-maxnum)
 
-;; ----------------------------------------------------------------
-;;                     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)))))
 
 
+;; ----------------------------------------------------------------
+;;                     Wrapping functions
 
+;; return wrapped insfun to deal with data wrapping
+(trie--if-lexical-binding
+    (defun dictree--wrap-insfun (insfun)  ; INTERNAL USE ONLY
+      (lambda (new old)
+       (dictree--cell-set-data old (funcall insfun
+                                            (dictree--cell-data new)
+                                            (dictree--cell-data old)))
+       old))
+  (defun dictree--wrap-insfun (insfun)  ; INTERNAL USE ONLY
+    `(lambda (new old)
+       (dictree--cell-set-data old (,insfun (dictree--cell-data new)
+                                           (dictree--cell-data old)))
+       old)))
+
+
+;; return wrapped rankfun to deal with data wrapping
+(trie--if-lexical-binding
+    (defun dictree--wrap-rankfun (rankfun)  ; INTERNAL USE ONLY
+      (lambda (a b)
+       (funcall rankfun
+                (cons (car a) (dictree--cell-data (cdr a)))
+                (cons (car b) (dictree--cell-data (cdr b))))))
+  (defun dictree--wrap-rankfun (rankfun)  ; INTERNAL USE ONLY
+    `(lambda (a b)
+       (,rankfun (cons (car a) (dictree--cell-data (cdr a)))
+                (cons (car b) (dictree--cell-data (cdr b)))))))
+
+
+;; return wrapped rankfun to ignore regexp grouping data
+(trie--if-lexical-binding
+    (defun dictree--wrap-regexp-rankfun (rankfun)
+      (lambda (a b)
+       ;; if car of argument contains a key+group list rather than a straight
+       ;; key, remove group list
+       ;; FIXME: the test for straight key, below, will fail if the key is a
+       ;;        list, and the first element of the key is itself a list
+       ;;        (there might be no easy way to fully fix this...)
+       (if (or (atom (car a))
+               (and (listp (car a)) (not (sequencep (caar a)))))
+           (setq a (cons (car a) (dictree--cell-data (cdr a))))
+         (setq a (cons (caar a) (dictree--cell-data (cdr a)))))
+       (if (or (atom (car b))
+               (and (listp (car b)) (not (sequencep (caar b)))))
+           (setq b (cons (car b) (dictree--cell-data (cdr b))))
+         (setq b (cons (caar b) (dictree--cell-data (cdr b)))))
+       (funcall rankfun a b)))
+  (defun dictree--wrap-regexp-rankfun (rankfun)
+    `(lambda (a b)
+       ;; if car of argument contains a key+group list rather than a straight
+       ;; key, remove group list
+       ;; FIXME: the test for straight key, below, will fail if the key is a
+       ;;        list, and the first element of the key is itself a list
+       ;;        (there might be no easy way to fully fix this...)
+       (if (or (atom (car a))
+              (and (listp (car a)) (not (sequencep (caar a)))))
+           (setq a (cons (car a) (dictree--cell-data (cdr a))))
+        (setq a (cons (caar a) (dictree--cell-data (cdr a)))))
+       (if (or (atom (car b))
+              (and (listp (car b)) (not (sequencep (caar b)))))
+          (setq b (cons (car b) (dictree--cell-data (cdr b))))
+        (setq b (cons (caar b) (dictree--cell-data (cdr b)))))
+       (,rankfun a b))))
+
+;; return wrapped sortfun to ignore regexp grouping data
+(trie--if-lexical-binding
+    (defun dictree--wrap-regexp-sortfun (cmpfun &optional reverse)
+       (let ((sortfun (trie-construct-sortfun cmpfun reverse)))
+         (lambda (a b)
+           ;; if car of argument contains a key+group list rather than a
+           ;; straight key, remove group list
+           ;; FIXME: the test for straight key, below, will fail if the key
+           ;;        is a list, and the first element of the key is itself a
+           ;;        list (there might be no easy way to fully fix this...)
+           (if (or (atom (car a))
+                   (and (listp (car a)) (not (sequencep (caar a)))))
+               (setq a (car a))
+             (setq a (caar a)))
+           (if (or (atom (car b))
+                   (and (listp (car b)) (not (sequencep (caar b)))))
+               (setq b (car b))
+             (setq b (caar b)))
+           (funcall sortfun a b))))
+  (defun dictree--wrap-regexp-sortfun (cmpfun &optional reverse)
+    (let ((sortfun (trie-construct-sortfun cmpfun reverse)))
+      `(lambda (a b)
+        ;; if car of argument contains a key+group list rather than a
+        ;; straight key, remove group list
+        ;; FIXME: the test for straight key, below, will fail if the key
+        ;;        is a list, and the first element of the key is itself a
+        ;;        list (there might be no easy way to fully fix this...)
+        (if (or (atom (car a))
+                (and (listp (car a)) (not (sequencep (caar a)))))
+            (setq a (car a))
+          (setq a (caar a)))
+        (if (or (atom (car b))
+                (and (listp (car b)) (not (sequencep (caar b)))))
+            (setq b (car b))
+          (setq b (caar b)))
+        (,sortfun a b)))))
+
+
+;; return wrapped rankfun to ignore fuzzy query distance data
+(trie--if-lexical-binding
+    (defun dictree--wrap-fuzzy-rankfun (rankfun)  ; INTERNAL USE ONLY
+      (lambda (a b)
+       (funcall rankfun
+                (cons (nth 0 (car a)) (dictree--cell-data (cdr a)))
+                (cons (nth 0 (car b)) (dictree--cell-data (cdr b))))))
+  (defun dictree--wrap-fuzzy-rankfun (rankfun)  ; INTERNAL USE ONLY
+    `(lambda (a b)
+       (,rankfun (cons (nth 0 (car a)) (dictree--cell-data (cdr a)))
+                (cons (nth 0 (car b)) (dictree--cell-data (cdr b)))))))
+
+;; return wrapped sortfun to ignore fuzzy query distance data
+(trie--if-lexical-binding
+    (defun dictree--wrap-fuzzy-sortfun (cmpfun &optional reverse)
+      (let ((sortfun (trie-construct-sortfun cmpfun reverse)))
+       (lambda (a b) (funcall sortfun (car a) (car b)))))
+  (defun dictree--wrap-fuzzy-sortfun (cmpfun &optional reverse)
+    (let ((sortfun (trie-construct-sortfun cmpfun reverse)))
+      `(lambda (a b) (,sortfun (car a) (car b))))))
+
+
+;; return wrapped combfun to deal with data wrapping
+(trie--if-lexical-binding
+    (defun dictree--wrap-combfun (combfun)  ; INTERNAL USE ONLY
+      (lambda (cell1 cell2)
+       (dictree--cell-create
+        (funcall combfun
+                 (dictree--cell-data cell1)
+                 (dictree--cell-data cell2))
+        (append (dictree--cell-plist cell1)
+                (dictree--cell-plist cell2)))))
+  (defun dictree--wrap-combfun (combfun)  ; INTERNAL USE ONLY
+    `(lambda (cell1 cell2)
+       (dictree--cell-create
+       (,combfun (dictree--cell-data cell1)
+                 (dictree--cell-data cell2))
+       (append (dictree--cell-plist cell1)
+               (dictree--cell-plist cell2))))))
+
+
+;; return wrapped filter function to deal with data wrapping
+(trie--if-lexical-binding
+    (defun dictree--wrap-filter (filter)  ; INTERNAL USE ONLY
+      (lambda (key data) (funcall filter key (dictree--cell-data data))))
+  (defun dictree--wrap-filter (filter)  ; INTERNAL USE ONLY
+    `(lambda (key data) (,filter key (dictree--cell-data data)))))
+
+
+;; return wrapped result function to deal with data wrapping
+(trie--if-lexical-binding
+    (defun dictree--wrap-resultfun (resultfun)  ; INTERNAL USE ONLY
+      (lambda (res)
+       (funcall resultfun (car res) (dictree--cell-data (cdr res)))))
+  (defun dictree--wrap-resultfun (resultfun)  ; INTERNAL USE ONLY
+    `(lambda (res) (,resultfun (car res) (dictree--cell-data (cdr res))))))
+
+
+;; construct lexicographic sort function from DICT's comparison function
+(trie--if-lexical-binding
+    (defun dictree--construct-sortfun (dict)  ; INTERNAL USE ONLY
+      (let ((sortfun (trie-construct-sortfun
+                     (dictree-comparison-function dict))))
+       (lambda (a b) (funcall sortfun (car a) (car b)))))
+    (defun dictree--construct-sortfun (dict)  ; INTERNAL USE ONLY
+      `(lambda (a b)
+        (,(trie-construct-sortfun (dictree-comparison-function (car dict)))
+         (car a) (car b)))))
+
+
+
+
 ;; ----------------------------------------------------------------
 ;;                 The dictionary data structures
 
@@ -217,47 +396,26 @@ If START or END is negative, it counts from the end."
                             (file-name-sans-extension
                              (file-name-nondirectory filename))))
                  autosave
-                 unlisted
-                 (comparison-function '<)
-                 (insert-function (lambda (a b) a))
+                 _unlisted
+                 (comparison-function #'<)
+                 (insert-function (lambda (a _b) a))
                  (rank-function (lambda (a b) (> (cdr a) (cdr b))))
                  (cache-policy 'time)
+                 cache-threshold
                  (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
+                 (trie-type 'avl)
                  &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)
+                 (trie (make-trie comparison-function trie-type))
+                 (lookup-cache nil)
+                 (complete-cache nil)
+                 (regexp-cache nil)
+                 (fuzzy-match-cache nil)
+                 (fuzzy-complete-cache nil)
+                 (meta-dict-list nil)
                  ))
    (:constructor dictree--create-custom
                 (&optional
@@ -266,17 +424,13 @@ If START or END is negative, it counts from the end."
                             (file-name-sans-extension
                              (file-name-nondirectory filename))))
                  autosave
-                 unlisted
-                 (comparison-function '<)
-                 (insert-function (lambda (a b) a))
+                 _unlisted
+                 (comparison-function #'<)
+                 (insert-function (lambda (a _b) a))
                  (rank-function (lambda (a b) (> (cdr a) (cdr b))))
                  (cache-policy 'time)
+                 cache-threshold
                  (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
@@ -287,7 +441,7 @@ If START or END is negative, it counts from the end."
                  transform-for-print transform-from-read
                  &aux
                  (modified nil)
-                 (trie (trie-create-custom
+                 (trie (make-trie-custom
                         comparison-function
                         :createfun createfun
                         :insertfun insertfun
@@ -300,39 +454,19 @@ If START or END is negative, it counts from the end."
                         :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)
+                 (lookup-cache nil)
+                 (complete-cache nil)
+                 (regexp-cache nil)
+                 (fuzzy-match-cache nil)
+                 (fuzzy-complete-cache nil)
+                 (meta-dict-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
+  comparison-function insert-function rank-function
+  cache-policy cache-threshold cache-update-policy
+  lookup-cache complete-cache regexp-cache
+  fuzzy-match-cache fuzzy-complete-cache
   key-savefun key-loadfun
   data-savefun data-loadfun
   plist-savefun plist-loadfun
@@ -347,62 +481,40 @@ If START or END is negative, it counts from the end."
                 (dictionary-list
                  &optional
                  filename
-                 (name (file-name-sans-extension
-                        (file-name-nondirectory filename)))
+                 (name (when filename
+                         (file-name-sans-extension
+                          (file-name-nondirectory filename))))
                  autosave
-                 unlisted
-                 (combine-function '+)
+                 _unlisted
+                 (combine-function #'+)
                  (cache-policy 'time)
+                 cache-threshold
                  (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))
+                      ((symbolp dic) (symbol-value 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))
+                 (lookup-cache nil)
+                 (complete-cache nil)
+                 (regexp-cache nil)
+                 (fuzzy-match-cache nil)
+                 (fuzzy-complete-cache 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
+  name filename autosave modified combine-function
+  cache-policy cache-threshold cache-update-policy
+  lookup-cache complete-cache regexp-cache
+  fuzzy-match-cache fuzzy-complete-cache
   dictlist meta-dict-list)
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;           Miscelaneous internal functions and macros
 
@@ -410,15 +522,13 @@ If START or END is negative, it counts from the end."
   ;; 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))
+  (dictree--do-trielist dict))
 
 (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))))
+      (apply #'nconc (mapcar #'dictree--do-trielist
+                            (dictree--meta-dict-dictlist dict)))
+    (list (dictree--trie dict))))
 
 
 (defun dictree--merge (list1 list2 cmpfun &optional combfun maxnum)
@@ -480,7 +590,7 @@ If START or END is negative, it counts from the end."
 
 
 
-
+
 ;;; ================================================================
 ;;;    The (mostly) public functions which operate on dictionaries
 
@@ -489,12 +599,7 @@ If START or END is negative, it counts from the end."
   (&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
+   cache-policy cache-threshold cache-update-policy
    key-savefun key-loadfun
    data-savefun data-loadfun
    plist-savefun plist-loadfun
@@ -533,40 +638,46 @@ RANK-FUNCTION sets the function used to rank the results 
of
 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\).
+defaults to \"lexicographic\" 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',
+`time-and-length' or `time-or-length'), which determines which
+query operations are cached. The `time' setting caches queries
+that take longer (in seconds) than the CACHE-THRESHOLD value.
+
+The `length' setting caches query operations based on the length
+of the string involved the query. For this setting, CACHE-POLICY
+should be a plist with properties :long and :short. Lookups,
+fuzzy matches, and regexp queries that do not end in \".*\" will
+be cached if the string is longer than the :long value (since
+long strings are likely to be the slower ones in these
+cases). Completions, fuzzy completions, and regexp queries that
+end in \".*\" will be cached if the string or regexp is shorter
+than the :short value \(since short strings are likely to be the
+slower ones for those cases\).
+
+The `time-and-length' setting only caches results if both
+conditions are satisfied simultaneously, whereas the
+`time-or-length' setting caches results if either condition is
+satisfied. For these settings, CACHE-THRESHOLD must be a plist
+with properties :time, :long and :short, specifying the
+corresponding cache thresholds.
+
+CACHE-THRESHOLD defaults to nil. The values nil and t are
+special. If CACHE-THRESHOLD is set to nil, no caching is done. If
+it is t, everything is cached for that type of query \(similar
+behaviour can be obtained by setting the a `time' CACHE-THRESHOLD
+of 0, but it is better to use t\).
+
+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.
 
 KEY-SAVEFUN, DATA-SAVEFUN and PLIST-SAVEFUN are functions used to
 convert keys, data and property lists into lisp objects that have
@@ -594,27 +705,23 @@ 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)))))
+  ;; sadly, passing null values overrides the defaults in the defstruct
+  ;; dictree--create, so we have to explicitly set the defaults again here
+  (or name (setq name (and filename (make-symbol
+                                    (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 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))
+  (or trie-type (setq trie-type 'avl))
 
   (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
+         cache-policy cache-threshold cache-update-policy
          key-savefun key-loadfun
          data-savefun data-loadfun
          plist-savefun plist-loadfun
@@ -637,12 +744,7 @@ structure. See `trie-create' for details."
      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
+     cache-policy cache-threshold cache-update-policy
      key-savefun key-loadfun
      data-savefun data-loadfun
      plist-savefun plist-loadfun
@@ -662,8 +764,8 @@ underlying data structure. See `trie-create' for details."
   ;; 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 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))
@@ -672,12 +774,7 @@ underlying data structure. See `trie-create' for details."
         (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
+         cache-policy cache-threshold cache-update-policy
          key-savefun key-loadfun
          data-savefun data-loadfun
          plist-savefun plist-loadfun
@@ -710,12 +807,7 @@ underlying data structure. See `trie-create' for details."
    &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)
+   cache-policy cache-threshold cache-update-policy)
   "Create a meta-dictionary based on the list of dictionaries
 in DICTIONARY-LIST.
 
@@ -726,7 +818,7 @@ 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."
+CACHE-THRESHOLD argument is ignored and caching is disabled."
 
   ;; sadly, passing null values over-rides the defaults in the defstruct
   ;; `dictree--create', so we have to explicitly set the defaults again
@@ -734,7 +826,7 @@ cache-threshold arguments are ignored."
   (or name (setq name (and filename
                           (file-name-sans-extension
                            (file-name-nondirectory filename)))))
-  (or combine-function (setq combine-function '+))
+  (or combine-function (setq combine-function #'+))
   (or cache-policy (setq cache-policy 'time))
   (or cache-update-policy (setq cache-update-policy 'synchronize))
 
@@ -743,28 +835,18 @@ cache-threshold arguments are ignored."
          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))
-        ))
+         cache-policy (when name cache-threshold) cache-update-policy
+        )))
     ;; 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)))
+    (unless (or (null name) (not cache-threshold))
       (mapc
        (lambda (dic)
-        (if (symbolp dic) (setq dic (eval dic)))
+        (if (symbolp dic) (setq dic (symbol-value dic)))
         (setf (dictree--meta-dict-list dic)
               (cons dict (dictree--meta-dict-list dic))))
        dictionary-list))
@@ -855,12 +937,6 @@ cache-threshold arguments are ignored."
       (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.")
@@ -882,108 +958,129 @@ for meta-dictionary 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."
+(defsubst dictree-cache-threshold (dict)
+  "Return the cache threshold for dictionary DICT."
   (if (dictree--meta-dict-p dict)
-      (dictree--meta-dict-lookup-cache-threshold dict)
-    (dictree--lookup-cache-threshold dict)))
+      (dictree--meta-dict-cache-threshold dict)
+    (dictree--cache-threshold dict)))
 
-(defsetf dictree-lookup-cache-threshold (dict) (param)
-  ;; setf method for lookup cache threshold
+(defsetf dictree-cache-threshold (dict) (param)
+  ;; setf method for cache threshold
   `(if (dictree--meta-dict-p ,dict)
-       (setf (dictree--meta-dict-lookup-cache-threshold ,dict)
+       (setf (dictree--meta-dict-cache-threshold ,dict)
             ,param)
-     (setf (dictree--lookup-cache-threshold ,dict)
+     (setf (dictree--cache-threshold ,dict)
           ,param)))
 
-(defsubst dictree-lookup-cache (dict)
+
+(defun dictree-lookup-cache (dict)
   ;; Return the lookup cache for dictionary DICT.
   (if (dictree--meta-dict-p dict)
       (dictree--meta-dict-lookup-cache dict)
     (dictree--lookup-cache dict)))
 
-(defsubst dictree-complete-cache-threshold (dict)
-  "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
+(defsetf dictree-lookup-cache (dict) (param)
+  ;; setf method for lookup cache
   `(if (dictree--meta-dict-p ,dict)
-       (setf (dictree--meta-dict-complete-cache-threshold ,dict)
+       (setf (dictree--meta-dict-lookup-cache ,dict)
             ,param)
-     (setf (dictree--complete-cache-threshold ,dict)
+     (setf (dictree--lookup-cache ,dict)
           ,param)))
 
+(defun dictree-create-lookup-cache (dict)
+  ;; Create DICT's lookup cache if it doesn't already exist.
+  (unless (dictree-lookup-cache dict)
+    (setf (dictree-lookup-cache dict)
+         (make-hash-table :test 'equal))))
+
+
 (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."
+(defsetf dictree-complete-cache (dict) (param)
+  ;; setf method for complete cache
+  `(if (dictree--meta-dict-p ,dict)
+       (setf (dictree--meta-dict-complete-cache ,dict)
+            ,param)
+     (setf (dictree--complete-cache ,dict)
+          ,param)))
+
+(defun dictree-create-complete-cache (dict)
+  ;; Create DICT's completion cache if it doesn't already exist.
+  (unless (dictree-complete-cache dict)
+    (setf (dictree-complete-cache dict)
+         (make-hash-table :test 'equal))))
+
+
+(defun dictree-regexp-cache (dict)
+  ;; Return the regexp cache for dictionary DICT.
   (if (dictree--meta-dict-p dict)
-      (dictree--meta-dict-complete-ranked-cache-threshold dict)
-    (dictree--complete-ranked-cache-threshold dict)))
+      (dictree--meta-dict-regexp-cache dict)
+    (dictree--regexp-cache dict)))
 
-(defsetf dictree-complete-ranked-cache-threshold (dict) (param)
-  ;; setf method for ranked completion cache threshold
+(defsetf dictree-regexp-cache (dict) (param)
+  ;; setf method for regexp cache
   `(if (dictree--meta-dict-p ,dict)
-       (setf (dictree--meta-dict-complete-ranked-cache-threshold ,dict)
+       (setf (dictree--meta-dict-regexp-cache ,dict)
             ,param)
-     (setf (dictree--complete-ranked-cache-threshold ,dict)
+     (setf (dictree--regexp-cache ,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)))
+(defun dictree-create-regexp-cache (dict)
+  ;; Create DICT's regexp cache if it doesn't already exist.
+  (unless (dictree-regexp-cache dict)
+    (setf (dictree-regexp-cache dict)
+         (make-hash-table :test 'equal))))
+
 
-(defsubst dictree-regexp-cache-threshold (dict)
-  "Return the regexp cache threshold for dictionary DICT."
+(defun dictree-fuzzy-match-cache (dict)
+  ;; Return the fuzzy match cache for dictionary DICT.
   (if (dictree--meta-dict-p dict)
-      (dictree--meta-dict-regexp-cache-threshold dict)
-    (dictree--regexp-cache-threshold dict)))
+      (dictree--meta-dict-fuzzy-match-cache dict)
+    (dictree--fuzzy-match-cache dict)))
 
-(defsetf dictree-regexp-cache-threshold (dict) (param)
-  ;; setf method for regexp cache threshold
+(defsetf dictree-fuzzy-match-cache (dict) (param)
+  ;; setf method for fuzzy match cache
   `(if (dictree--meta-dict-p ,dict)
-       (setf (dictree--meta-dict-regexp-cache-threshold ,dict)
+       (setf (dictree--meta-dict-fuzzy-match-cache ,dict)
             ,param)
-     (setf (dictree--regexp-cache-threshold ,dict)
+     (setf (dictree--fuzzy-match-cache ,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)))
+(defun dictree-create-fuzzy-match-cache (dict)
+  ;; Create DICT's fuzzy match cache if it doesn't already exist.
+  (unless (dictree-fuzzy-match-cache dict)
+    (setf (dictree-fuzzy-match-cache dict)
+         (make-hash-table :test 'equal))))
+
 
-(defsubst dictree-regexp-ranked-cache-threshold (dict)
-  "Return the ranked regexp cache threshold for dictionary DICT."
+(defun dictree-fuzzy-complete-cache (dict)
+  ;; Return the regexp cache for dictionary DICT.
   (if (dictree--meta-dict-p dict)
-      (dictree--meta-dict-regexp-ranked-cache-threshold dict)
-    (dictree--regexp-ranked-cache-threshold dict)))
+      (dictree--meta-dict-fuzzy-complete-cache dict)
+    (dictree--fuzzy-complete-cache dict)))
 
-(defsetf dictree-regexp-ranked-cache-threshold (dict) (param)
-  ;; setf method for ranked regexp cache threshold
+(defsetf dictree-fuzzy-complete-cache (dict) (param)
+  ;; setf method for fuzzy completion cache
   `(if (dictree--meta-dict-p ,dict)
-       (setf (dictree--meta-dict-regexp-ranked-cache-threshold ,dict)
+       (setf (dictree--meta-dict-fuzzy-complete-cache ,dict)
             ,param)
-     (setf (dictree--regexp-ranked-cache-threshold ,dict)
+     (setf (dictree--fuzzy-complete-cache ,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)))
+(defun dictree-create-fuzzy-complete-cache (dict)
+  ;; Create DICT's fuzzy completion cache if it doesn't already exist.
+  (unless (dictree-fuzzy-complete-cache dict)
+    (setf (dictree-fuzzy-complete-cache dict)
+         (make-hash-table :test 'equal))))
+
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                  Inserting and deleting data
 
@@ -1007,20 +1104,23 @@ becomes the new association for KEY."
            (dictree--meta-dict-dictlist dict))
 
     ;; otherwise...
-    (let (newdata)
+    (let ((insfun (or (and insert-function
+                          (dictree--wrap-insfun insert-function))
+                     (dictree--wrap-insfun (dictree--insert-function dict))))
+         olddata 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))))
+            (lambda (nd od)
+              (setq olddata od)
+              (funcall insfun nd od))))
       ;; update dictionary's caches
-      (dictree--update-cache dict key newdata)
+      (dictree--update-cache dict key olddata newdata)
       ;; update cache's of any meta-dictionaries based on dict
-      (mapc (lambda (dic) (dictree--update-cache dic key newdata))
+      (mapc (lambda (dic) (dictree--update-cache dic key olddata newdata))
            (dictree--meta-dict-list dict))
 
       ;; return the new data
@@ -1038,7 +1138,7 @@ associated property list. The key will then only be 
deleted if
 TEST returns non-nil."
 
   (let ((dictree--delete-test test)
-       deleted del)
+       olddata deleted del)
     (cond
      ;; if DICT is a meta-dictionary, delete KEY from all dictionaries
      ;; it's based on
@@ -1053,18 +1153,20 @@ TEST returns non-nil."
      (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))))))
+                        (lambda (k cell)
+                          (setq olddata (dictree--cell-data cell))
+                          (if dictree--delete-test
+                              (funcall dictree--delete-test
+                                       k (dictree--cell-data cell)
+                                       (dictree--cell-plist cell))
+                            t))))
       ;; if key was deleted, have to update the caches
       (when deleted
-       (dictree--update-cache dict key nil t)
+       (dictree--update-cache dict key olddata 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--update-cache dic key olddata nil t))
              (dictree--meta-dict-list dict)))))
 
     ;; return deleted key/data pair
@@ -1073,6 +1175,7 @@ TEST returns non-nil."
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                     Cache updating
 
@@ -1110,198 +1213,161 @@ PREFIX is a prefix of STR."
   (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)
+                   (>= length (plist-get threshold :long))
+                 (<= length (plist-get threshold :short))))
+          (and (eq policy 'time-and-length)
+               (>= time (plist-get threshold :time))
+               (if cache-long-keys
+                   (>= length (plist-get threshold :long))
+                 (<= length (plist-get threshold :short))))
+          (and (eq policy 'time-or-length)
                (or (>= time (plist-get threshold :time))
                    (if cache-long-keys
-                       (>= length (plist-get threshold :length))
-                     (<= length (plist-get threshold :length))))))))
+                       (>= length (plist-get threshold :long))
+                     (<= length (plist-get threshold :short))))))))
+
 
 
-(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)
+(defun dictree--update-cache (dict key olddata newdata &optional deleted)
+  ;; Synchronise dictionary DICT's caches, given that the data associated with
+  ;; KEY has been updated from OLDDATA to NEWDATA, or KEY has been deleted if
+  ;; DELETED is non-nil (NEWDATA is ignored in that case)."
+  (when (dictree-cache-threshold dict)
 
-    ;; 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))
+    ;; synchronise lookup cache if dict is a meta-dictionary, since it doesn't
+    ;; happen automatically for a meta-dict
+    (when (dictree--meta-dict-p dict)
       (cond
-       ;; if updating dirty cache entries...
+       ;; 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))
-    ))
+       (when (and (dictree--lookup-cache dict)
+                  (gethash key (dictree--lookup-cache dict)))
+         (if deleted
+             (remhash key (dictree--lookup-cache dict))
+           (puthash key newdata (dictree--lookup-cache dict)))))
+       ;; deleting dirty cache entries
+       (t (remhash key (dictree--lookup-cache dict)))))
+
+    ;; synchronize query caches if something's actually changed
+    (when (or deleted (not (equal olddata newdata)))
+      (dolist (cachefuns
+              '((dictree-complete-cache
+                 dictree--synchronize-completion-cache
+                 dictree--prefix-p)
+                (dictree-regexp-cache
+                 dictree--synchronize-regexp-cache
+                 (lambda (arg key)
+                   (tNFA-regexp-match
+                    arg key :test (trie--construct-equality-function
+                                   (dictree--comparison-function dict)))))
+                (dictree-fuzzy-match-cache
+                 dictree--synchronize-fuzzy-match-cache
+                 (lambda (string dist key)
+                   (<= (Lewenstein-distance string key) dist)))
+                (dictree-fuzzy-complete-cache
+                 dictree--synchronize-fuzzy-completion-cache
+                 (lambda (prefix dist key)
+                   (<= (Lewenstein-distance prefix key) dist)))
+                ))
+       (when (funcall (nth 0 cachefuns) dict)
+         (maphash
+          (lambda (cache-key cache-entry)
+            (destructuring-bind
+                (arg auxargs rank-function reverse filter) cache-key
+              (when (apply (nth 2 cachefuns)
+                           (append (list arg) auxargs (list key)))
+                (cond
+                 ;; updating dirty cache entries
+                 ((eq (dictree-cache-update-policy dict) 'synchronize)
+                  (funcall (nth 1 cachefuns)
+                           dict key olddata newdata deleted cache-entry
+                           arg auxargs rank-function reverse filter))
+                 ;; deleting dirty cache entries
+                 (t (remhash (list arg auxargs rank-function reverse filter)
+                             (funcall (nth 0 cachefuns) dict)))))))
+          (funcall (nth 0 cachefuns) dict)))
+       ))))
 
 
 
 (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.
+    (dict key olddata newdata deleted cache-entry
+         arg auxargs rank-function reverse filter)
+  ;; Synchronize DICT's completion CACHE-ENTRY for a query with arguments ARG,
+  ;; AUXARGS, RANK-FUNCTION, REVERSE and FILTER, where KEY's data was either
+  ;; updated from OLDDATA 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
-     )))
+        (rankfun (cond ((eq rank-function t)
+                        (dictree--wrap-rankfun
+                         (dictree--rank-function dict)))
+                       (rank-function
+                        (dictree--wrap-rankfun rank-function)))))
+    ;; for meta-dict, get old data from cache instead of OLDDATA
+    (when (dictree--meta-dict-p dict) (setq olddata (cdr cmpl)))
+    ;; skip cache update if key/data pair doesn't pass FILTER
+    (when (or (null filter)
+             (funcall filter key olddata)
+             (funcall filter key newdata))
+      ;; 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 (list arg auxargs rank-function reverse filter)
+                (dictree-complete-cache dict))
+       (dictree-complete dict arg rank-function maxnum reverse filter))
+
+       ;; modified and not in cached result: merge it into the completion
+       ;; list, retaining only the first maxnum
+       ((and (not deleted) (not cmpl))
+       (when (or (null filter) (funcall filter key newdata))
+         (setf (dictree--cache-results cache-entry)
+               (dictree--merge
+                (list (cons key newdata)) completions
+                (or rankfun
+                    `(lambda (a b)
+                       (,(trie-construct-sortfun
+                          (dictree-comparison-function dict))
+                        (car a) (car b))))
+                (when (dictree--meta-dict-p dict)
+                  (dictree--wrap-combfun
+                   (dictree--meta-dict-combine-function dict)))
+                maxnum))))
+
+       ;; modified and in the cached result
+       ((and (not deleted) cmpl)
+       ;; update the associated data if dict is a meta-dictionary (this
+       ;; happens automatically for a normal dict)
+       (when (dictree--meta-dict-p dict) (setcdr cmpl newdata))
+       ;; if updated entry gets filtered, or gets sorted at end of list,
+       ;; re-run the same query to update the cache
+       (when (or (and filter (not (funcall filter key newdata)))
+                 (and rankfun
+                      (setf (dictree--cache-results cache-entry)
+                            (sort completions rankfun))
+                      (equal key (car (last (dictree--cache-results
+                                             cache-entry))))))
+         (remhash (list arg auxargs rank-function reverse filter)
+                  (dictree-complete-cache dict))
+         (dictree-complete dict arg rank-function maxnum reverse filter)))
+
+       ;; 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.
+    (dict key olddata newdata deleted cache-entry
+         arg auxargs rank-function reverse filter)
+  ;; Synchronize DICT's regexp CACHE-ENTRY for a query with arguments ARG,
+  ;; AUXARGS, RANK-FUNCTION, REVERSE and FILTER, where KEY's data was either
+  ;; updated from OLDDATA to NEWDATA or DELETED,
+
   (let* ((completions (dictree--cache-results cache-entry))
         (maxnum (dictree--cache-maxnum cache-entry))
         group-data
@@ -1312,118 +1378,241 @@ PREFIX is a prefix of STR."
                                (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
-     )))
+                    (when (equal key (car c)) (throw 'found c))))))
+        (rankfun (cond ((eq rank-function t)
+                        (dictree--wrap-regexp-rankfun
+                         (dictree-rank-function dict)))
+                       (rank-function
+                        (dictree--wrap-regexp-rankfun rank-function)))))
+    ;; for meta-dict, get old data from cache instead of OLDDATA
+    (when (dictree--meta-dict-p dict) (setq olddata (cdr cmpl)))
+    ;; skip cache update if key/data pair doesn't pass FILTER
+    (when (or (null filter)
+             (funcall filter key olddata)
+             (funcall filter key newdata))
+      ;; 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 (list arg auxargs rank-function reverse filter)
+                (dictree-regexp-cache dict))
+       (dictree-regexp-search dict arg rank-function maxnum reverse filter))
+
+       ;; modified and not in cached result: merge it into the completion
+       ;; list, retaining only the first maxnum
+       ((and (not deleted) (not cmpl))
+       (when (or (null filter) (funcall filter key newdata))
+         (save-match-data
+           (set-match-data nil)
+           (tNFA-regexp-match arg key
+                              :test (trie--construct-equality-function
+                                     (dictree--comparison-function dict)))
+           (when (setq group-data (nthcdr 2 (match-data)))
+             (setq key (cons key group-data))))
+         (setf (dictree--cache-results cache-entry)
+               (dictree--merge
+                (list (cons key newdata)) completions
+                (or rankfun
+                    `(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--wrap-combfun
+                   (dictree--meta-dict-combine-function dict)))
+                maxnum))))
+
+       ;; modified and in the cached result
+       ((and (not deleted) cmpl)
+       ;; update the associated data if dict is a meta-dictionary (this
+       ;; happens automatically for a normal dict)
+       (when (dictree--meta-dict-p dict) (setcdr cmpl newdata))
+       ;; if updated entry gets filtered, or gets sorted at end of list,
+       ;; re-run the same query to update the cache
+       (when (or (and filter (not (funcall filter key newdata)))
+                 (and rankfun
+                      (setf (dictree--cache-results cache-entry)
+                            (sort completions rankfun))
+                      (equal key (car (last (dictree--cache-results
+                                             cache-entry))))))
+         (remhash (list arg auxargs rank-function reverse filter)
+                  (dictree-regexp-cache dict))
+         (dictree-regexp-search dict arg rank-function maxnum reverse filter)
+         ))
+
+       ;; deleted and not in cached result: requires no action
+       ))))
+
+
+(defun dictree--synchronize-fuzzy-match-cache
+    (dict key olddata newdata deleted cache-entry
+         arg auxargs rank-function reverse filter)
+  ;; Synchronize DICT's fuzzy match CACHE-ENTRY for a query with arguments
+  ;; ARG, AUXARGS, RANK-FUNCTION, REVERSE and FILTER, where KEY's data was
+  ;; either updated from OLDDATA to NEWDATA or DELETED,
+
+  (let* ((completions (dictree--cache-results cache-entry))
+        (maxnum (dictree--cache-maxnum cache-entry))
+        (cmpl (catch 'found
+                (dolist (c completions)
+                  (when (equal key (caar c)) (throw 'found c)))))
+        (distance (Lewenstein-distance key arg))
+        (rankfun (cond ((eq rank-function t)
+                        (dictree--wrap-fuzzy-rankfun
+                         (dictree-rank-function dict)))
+                       ((eq rank-function 'distance)
+                        (dictree--wrap-fuzzy-rankfun
+                         (trie--construct-Lewenstein-rankfun
+                          (dictree-comparison-function dict))))
+                       (rank-function
+                        (dictree--wrap-fuzzy-rankfun rank-function)))))
+    ;; for meta-dict, get old data from cache instead of OLDDATA
+    (when (dictree--meta-dict-p dict) (setq olddata (cdr cmpl)))
+    ;; skip cache update if key/data pair doesn't pass FILTER
+    (when (or (null filter)
+             (funcall filter key olddata)
+             (funcall filter key newdata))
+      ;; 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 (list arg auxargs rank-function reverse filter)
+                (dictree-fuzzy-match-cache dict))
+       (dictree-fuzzy-match dict arg (car auxargs)
+                            rank-function maxnum reverse filter))
+
+       ;; modified and not in cached result: merge it into the completion
+       ;; list, retaining only the first maxnum
+       ((and (not deleted) (not cmpl))
+       (when (or (null filter) (funcall filter key newdata))
+         (setf (dictree--cache-results cache-entry)
+               (dictree--merge
+                (list (cons (cons key distance) newdata)) completions
+                (or rankfun
+                    `(lambda (a b)
+                       (,(trie-construct-sortfun
+                          (dictree-comparison-function dict))
+                        (caar a) (caar b))))
+                (when (dictree--meta-dict-p dict)
+                  (dictree--wrap-combfun
+                   (dictree--meta-dict-combine-function dict)))
+                maxnum))))
+
+       ;; modified and in the cached result
+       ((and (not deleted) cmpl)
+       ;; update the associated data if dict is a meta-dictionary (this
+       ;; happens automatically for a normal dict)
+       (when (dictree--meta-dict-p dict) (setcdr cmpl newdata))
+       ;; if updated entry gets filtered, or gets sorted at end of list,
+       ;; re-run the same query to update the cache
+       (when (or (and filter (not (funcall filter key newdata)))
+                 (and rankfun
+                      (setf (dictree--cache-results cache-entry)
+                            (sort completions rankfun))
+                      (equal key (car (last (dictree--cache-results
+                                             cache-entry))))))
+         (remhash (list arg auxargs rank-function reverse filter)
+                  (dictree-fuzzy-match-cache dict))
+         (dictree-fuzzy-match dict arg (car auxargs)
+                              rank-function maxnum reverse filter)))
+
+       ;; deleted and not in cached result: requires no action
+       ))))
+
+
+(defun dictree--synchronize-fuzzy-complete-cache
+    (dict key olddata newdata deleted cache-entry
+         arg auxargs rank-function reverse filter)
+  ;; Synchronize DICT's fuzzy completion CACHE-ENTRY for a query with
+  ;; arguments ARG, AUXARGS, RANK-FUNCTION, REVERSE and FILTER, where KEY's
+  ;; data was either updated from OLDDATA to NEWDATA or DELETED,
+
+  (let* ((completions (dictree--cache-results cache-entry))
+        (maxnum (dictree--cache-maxnum cache-entry))
+        (cmpl (catch 'found
+                (dolist (c completions)
+                  (when (equal key (caar c)) (throw 'found c)))))
+        (distance (Lewenstein-distance key arg))
+        (rankfun (cond ((eq rank-function t)
+                        (dictree--wrap-fuzzy-rankfun
+                         (dictree-rank-function dict)))
+                       ((eq rank-function 'distance)
+                        (dictree--wrap-fuzzy-rankfun
+                         (trie--construct-Lewenstein-rankfun
+                          (dictree-comparison-function dict))))
+                       (rank-function
+                        (dictree--wrap-fuzzy-rankfun rank-function)))))
+    ;; for meta-dict, get old data from cache instead of OLDDATA
+    (when (dictree--meta-dict-p dict) (setq olddata (cdr cmpl)))
+    ;; skip cache update if key/data pair doesn't pass FILTER
+    (when (or (null filter)
+             (funcall filter key olddata)
+             (funcall filter key newdata))
+      ;; 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 (list arg auxargs rank-function reverse filter)
+                (dictree-fuzzy-complete-cache dict))
+       (dictree-fuzzy-complete dict arg (car auxargs)
+                               rank-function maxnum reverse filter))
+
+       ;; modified and not in cached result: merge it into the completion
+       ;; list, retaining only the first maxnum
+       ((and (not deleted) (not cmpl))
+       (when (or (null filter) (funcall filter key newdata))
+         (setf (dictree--cache-results cache-entry)
+               (dictree--merge
+                (list (cons key (cons distance newdata))) completions
+                (or rankfun
+                    `(lambda (a b)
+                       (,(trie-construct-sortfun
+                          (dictree-comparison-function dict))
+                        (car a) (car b))))
+                (when (dictree--meta-dict-p dict)
+                  (dictree--wrap-combfun
+                   (dictree--meta-dict-combine-function dict)))
+                maxnum))))
+
+       ;; modified and in the cached result
+       ((and (not deleted) cmpl)
+       ;; update the associated data if dict is a meta-dictionary (this
+       ;; happens automatically for a normal dict)
+       (when (dictree--meta-dict-p dict) (setcdr cmpl newdata))
+       ;; if updated entry gets filtered, or gets sorted at end of list,
+       ;; re-run the same query to update the cache
+       (when (or (and filter (not (funcall filter key newdata)))
+                 (and rankfun
+                      (setf (dictree--cache-results cache-entry)
+                            (sort completions rankfun))
+                      (equal key (car (last (dictree--cache-results
+                                             cache-entry))))))
+         (remhash (list arg auxargs rank-function reverse filter)
+                  (dictree-fuzzy-complete-cache dict))
+         (dictree-fuzzy-complete dict arg (car auxargs)
+                                 rank-function maxnum reverse filter)))
+
+       ;; 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: ")))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
   (dolist (cachefun '(dictree-lookup-cache
                      dictree-complete-cache
-                     dictree-complete-ranked-cache
                      dictree-regexp-cache
-                     dictree-regexp-ranked-cache))
+                     dictree-fuzzy-match-cache
+                     dictree-fuzzy-complete-cache))
     (when (funcall cachefun dict)
       (clrhash (funcall cachefun dict))))
   (when (called-interactively-p 'interactive)
@@ -1431,7 +1620,7 @@ PREFIX is a prefix of STR."
 
 
 
-
+
 ;; ----------------------------------------------------------------
 ;;                        Retrieving data
 
@@ -1481,10 +1670,11 @@ also `dictree-member-p' for testing existence alone.)"
            (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)))))
+               ;; otherwise, combine the previous data with the new data
+               (setq data
+                     (funcall (dictree--wrap-combfun
+                               (dictree--meta-dict-combine-function dict))
+                              data newdata)))))
          (setq time (- (float-time) time))))
 
        ;; otherwise, DICT is a normal dictionary, so look in it's trie
@@ -1494,13 +1684,15 @@ also `dictree-member-p' for testing existence alone.)"
        (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
+      ;; if lookup found something, and we're above the 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))
+                 (dictree-cache-threshold dict) 'long-keys))
        (setf (dictree-modified dict) t)
+       ;; create lookup cache if it doesn't already exist
+       (dictree-create-lookup-cache dict)
        (puthash key data (dictree-lookup-cache dict))))
 
     ;; return the desired data
@@ -1508,6 +1700,7 @@ also `dictree-member-p' for testing existence alone.)"
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                 Getting and setting meta-data
 
@@ -1528,12 +1721,12 @@ additional information, and can only be retrieved using
 `dictree-get-property'."
 
   ;; sort out arguments
-  (and (symbolp dict) (setq dict (eval dict)))
+  (and (symbolp dict) (setq dict (symbol-value 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))
+ dictionaries of meta-dictionary %s" property key (dictree-name dict))
     (setf (dictree-modified dict) t)
     (let (dictree--put-property-ret)
       (mapc (lambda (dic k p v)
@@ -1566,7 +1759,7 @@ still be detected by supplying the optional argument to
 Note that if DICT is a meta-dictionary, then this will delete
 KEY's PROPERTY in *all* its constituent dictionaries."
   ;; sort out arguments
-  (and (symbolp dict) (setq dict (eval dict)))
+  (and (symbolp dict) (setq dict (symbol-value dict)))
   (cond
    ;; delete PROPERTY from KEY in all constituent dicts of a meta-dict
    ((dictree--meta-dict-p dict)
@@ -1577,7 +1770,7 @@ KEY's PROPERTY in *all* its constituent dictionaries."
            (dictree--meta-dict-dictlist dict)))
    (t  ;; delete PROPERTY from KEY in normal dict
     (let* ((cell (trie-member (dictree--trie dict) key))
-          plist tail tail)
+          plist tail)
       (when (and cell
                 (setq tail
                       (plist-member
@@ -1606,7 +1799,7 @@ set. (See also `dictree-member-p' for testing existence 
alone.)"
 
 
 
-
+
 ;; ----------------------------------------------------------------
 ;;                        Mapping functions
 
@@ -1617,25 +1810,22 @@ 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.
+entries will be traversed in \"lexicographic\" 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
+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 \"--\"."
+REVERSE is non-nil."
 
   ;; "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)
+     (lambda (key data _plist)
        (funcall --dictree-mapc--function key data))
      dict type reverse)))
 
@@ -1683,13 +1873,9 @@ 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
+asscending \"lexicographic\" 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 \"--\"."
+descending order if REVERSE is non-nil."
 
   ;; try to avoid dynamic scoping bugs
   (let ((--dictree-mapf--function function)
@@ -1725,28 +1911,25 @@ 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
+Optional argument TYPE (one of the symbols `vector', `list' 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
+asscending \"lexicographic\" 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))
+  (dictree-mapf function #\\='cons dict 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)))
+is more efficient."
+  (nreverse (dictree-mapf function #'cons dict type reverse)))
 
 
 
@@ -1755,9 +1938,9 @@ bind any variables with names commencing \"--\"."
 Interactively, DICT is read from the mini-buffer."
   (interactive (list (read-dict "Dictionary: ")))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
   (let ((count 0))
-    (dictree-mapc (lambda (&rest dummy) (incf count)) dict)
+    (dictree-mapc (lambda (&rest _dummy) (incf count)) dict)
     (when (called-interactively-p 'interactive)
       (message "Dictionary %s contains %d entries"
               (dictree--name dict) count))
@@ -1765,14 +1948,14 @@ Interactively, DICT is read from the mini-buffer."
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                        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.
+;; 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
+;; lexicographic order over the keys at the top of the trie-stacks.
 
 (defstruct
   (dictree--meta-stack
@@ -1780,22 +1963,24 @@ Interactively, DICT is read from the mini-buffer."
    (:constructor dictree--meta-stack-create
                 (dict &optional (type 'vector) reverse
                  &aux
-                 (combfun (dictree--meta-dict-combfun dict))
+                 (combfun (dictree--wrap-combfun
+                           (dictree--meta-dict-combine-function 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)))))
+                 (_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))
+                 (combfun (dictree--wrap-combfun
+                           (dictree--meta-dict-combine-function dict)))
                  (sortfun (trie-construct-sortfun
                            (dictree-comparison-function dict)))
                  (heap (heap-create
@@ -1803,31 +1988,70 @@ Interactively, DICT is read from the mini-buffer."
                          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)))))
+                 (_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
+                 (combfun (dictree--wrap-combfun
+                           (dictree--meta-dict-combine-function dict)))
+                 (sortfun (dictree--wrap-regexp-sortfun
+                           (dictree-comparison-function dict) 'reverse))
+                 (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)))))
+      (:constructor dictree--fuzzy-match-meta-stack-create
+                (dict string distance &optional reverse
+                 &aux
+                 (combfun (dictree--wrap-combfun
+                           (dictree--meta-dict-combine-function dict)))
+                 (sortfun (dictree--wrap-fuzzy-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)))))
+                 (_dummy (mapc
+                          (lambda (trie)
+                            (let ((stack (trie-fuzzy-match-stack
+                                          trie string distance reverse)))
+                              (unless (trie-stack-empty-p stack)
+                                (heap-add heap stack))))
+                          (dictree--trielist dict)))))
+      (:constructor dictree--fuzzy-complete-meta-stack-create
+                (dict prefix distance &optional reverse
+                 &aux
+                 (combfun (dictree--wrap-combfun
+                           (dictree--meta-dict-combine-function dict)))
+                 (sortfun (dictree--wrap-fuzzy-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-fuzzy-complete-stack
+                                          trie prefix distance reverse)))
+                              (unless (trie-stack-empty-p stack)
+                                (heap-add heap stack))))
+                          (dictree--trielist dict)))))
    (:copier nil))
   combfun sortfun heap pushed)
 
@@ -1846,13 +2070,13 @@ Interactively, DICT is read from the mini-buffer."
 (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.
+The stack is sorted in \"lexicographic\" 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.
+Optional argument TYPE (one of the symbols `vector', `list' 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,
@@ -1874,29 +2098,29 @@ those instead."
   "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.
+The stack is sorted in \"lexicographic\" 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.
+initial part of a DICT key. (If PREFIX is a string, it must be
+possible to apply `string' to individual elements of DICT keys.)
+The returned keys will be sequences of the same type as
+PREFIX. 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,
+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 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."
+in implementing efficient algorithms on dict-trees. However, in
+cases where `dictree-complete' is sufficient, it is better to use
+that instead."
   (if (dictree--meta-dict-p dict)
       (dictree--complete-meta-stack-create dict prefix reverse)
     (trie-complete-stack (dictree--trie dict) prefix reverse)))
@@ -1906,18 +2130,18 @@ sufficient, it is better to use one of those instead."
   "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.
+The stack is sorted in \"lexicographic\" 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
+keys in DICT (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
+individual elements of the keys stored in DICT. The matches
 returned in the alist will be sequences of the same type as KEY.
 
 Back-references and non-greedy postfix operators are *not*
@@ -1932,20 +2156,87 @@ 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,
+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 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."
+in implementing efficient algorithms on dict-trees. However, in
+cases where `dictree-regexp-search' is sufficient, it is better
+to use that 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-fuzzy-match-stack (dict string distance &optional reverse)
+  "Return an object that allows fuzzy matches to be accessed
+as if they were a stack.
+
+The stack is sorted in \"lexicographic\" 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.
+
+STRING must be a sequence (vector, list or string), and DISTANCE
+must be an integer. (If STRING is a string, it must be possible
+to apply `string' to individual elements of DICT keys.) The
+matches returned in the alist will be sequences of the same type
+as STRING that are within Lewenstein distance DISTANCE of
+STRING. If STRING is a list of sequences, keys withing DISTANCE
+of any 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
+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 fuzzy matches within DISTANCE
+of STRING in DICT and using standard stack functions. As such,
+they can be useful in implementing efficient algorithms on
+dict-trees. However, in cases where `dictree-fuzzy-match' is
+sufficient, it is better to use that instead."
+  (if (dictree--meta-dict-p dict)
+      (dictree--fuzzy-match-meta-stack-create dict string distance reverse)
+    (trie-fuzzy-match-stack (dictree--trie dict) string distance reverse)))
+
+
+(defun dictree-fuzzy-complete-stack (dict prefix distance &optional reverse)
+  "Return an object that allows fuzzy completions to be accessed
+as if they were a stack.
+
+The stack is sorted in \"lexicographic\" 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), and DISTANCE
+must be an integer. (If PREFIX is a string, it must be possible
+to apply `string' to individual elements of DICT keys.) The
+completions returned in the alist will be sequences of the same
+type as STRING that are completions of prefixes within Lewenstein
+distance DISTANCE of PREFIX. If PREFIX is a list of sequences,
+completions within DISTANCE of any prefix 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
+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 fuzzy matches within DISTANCE
+of STRING in DICT and using standard stack functions. As such,
+they can be useful in implementing efficient algorithms on
+dict-trees. However, in cases where `dictree-fuzzy-complete' is
+sufficient, it is better to use that instead."
+  (if (dictree--meta-dict-p dict)
+      (dictree--fuzzy-complete-meta-stack-create dict prefix distance reverse)
+    (trie-fuzzy-complete-stack (dictree--trie dict) prefix distance reverse)))
+
+
 (defun dictree-stack-pop (dictree-stack)
   "Pop the first element from the DICTREE-STACK.
 Returns nil if the stack is empty."
@@ -2029,7 +2320,7 @@ Returns nil if the stack is empty."
       ;; otherwise...
       (let ((heap (dictree--meta-stack-heap dictree-stack))
            (sortfun (dictree--meta-stack-sortfun dictree-stack))
-           stack curr next cell)
+           stack curr next)
        (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
@@ -2050,16 +2341,10 @@ Returns nil if the stack is empty."
                                         (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))))))
+               (setq curr (cons (car curr)
+                                (funcall
+                                 (dictree--meta-stack-combfun dictree-stack)
+                                 (cdr curr) (cdr next))))
                (heap-add heap stack)
                (setq next (dictree--stack-first (heap-root heap))))))
          ;; return the combined dictionary element
@@ -2067,172 +2352,308 @@ Returns nil if the stack is empty."
 
 
 
+
+;; ----------------------------------------------------------------
+;;                    dictree iterator generators
+
+;; dictree-stacks *are* iterators (with additional push and
+;; inspect-first-element operations). If we're running on a modern Emacs that
+;; includes the `generator' library, we can trivially define dictree iterator
+;; generators in terms of dictree-stacks.
+
+(heap--when-generators
+ (iter-defun dictree-iter (dict &optional type reverse)
+   "Return a dictree iterator object.
+
+Calling `iter-next' on this object will retrieve the next
+element (a cons cell containing a key and its associated data)
+from DICT in \"lexicographic\" order, i.e. the order defined by
+the DICT's comparison function, or in reverse order if REVERSE is
+non-nil.
+
+Optional argument TYPE (one of the symbols `vector', `list' or
+`string') sets the type of sequence used for the keys.
+
+Note that any modification to DICT *immediately* invalidates all
+iterators created from DICT before the modification (in
+particular, calling `iter-next' will give unpredictable
+results). If DICT is a meta-dict, this includes any modifications
+to its constituent dicts."
+   (let ((stack (dictree-stack dict type reverse)))
+     (while (not (dictree-stack-empty-p stack))
+       (iter-yield (dictree-stack-pop stack))))))
+
+
+(heap--when-generators
+ (iter-defun dictree-complete-iter (dict prefix &optional reverse)
+   "Return an iterator object for completions of PREFIX in DICT.
+
+Calling `iter-next' on this object will retrieve the next
+completion of PREFIX (a cons cell containing a key and its
+associated data) from DICT in \"lexicographic\" order, i.e. the
+order defined by DICT's comparison function, or in reverse order
+if REVERSE is non-nil.
+
+PREFIX must be a sequence (vector, list or string) that forms the
+initial part of a DICT key. (If PREFIX is a string, it must be
+possible to apply `string' to individual elements of DICT keys.)
+The returned keys will be sequences of the same type as
+PREFIX. 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
+iterators created from DICT before the modification (in
+particular, calling `iter-next' will give unpredictable
+results). If DICT is a meta-dict, this includes any modifications
+to its constituent dicts."
+   (let ((stack (dictree-complete-stack dict prefix reverse)))
+     (while (not (dictree-stack-empty-p stack))
+       (iter-yield (dictree-stack-pop stack))))))
+
+
+(heap--when-generators
+ (iter-defun dictree-regexp-iter (dict regexp &optional reverse)
+   "Return an iterator object for REGEXP matches in DICT.
+
+Calling `iter-next' on this object will retrieve the next match
+\(a cons cell containing a key and its associated data\) in
+\"lexicographic\" order, i.e. the order defined by DICT's
+comparison function, or in reverse order if REVERSE is non-nil.
 
+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
+keys in DICT (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 DICT. 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
+iterators created from DICT before the modification (in
+particular, calling `iter-next' will give unpredictable
+results). If DICT is a meta-dict, this includes any modifications
+to its constituent dicts."
+   (let ((stack (dictree-regexp-stack dict regexp reverse)))
+     (while (not (dictree-stack-empty-p stack))
+       (iter-yield (dictree-stack-pop stack))))))
+
+(heap--when-generators
+ (iter-defun dictree-fuzzy-match-iter (dict string distance &optional reverse)
+   "Return an iterator object for fuzzy matches to STRING in DICT.
+
+Calling `iter-next' on this object will retrieve the next match
+\(a cons cell containing a key and its associated data\) in
+\"lexicographic\" order, i.e. the order defined by DICT's
+comparison function, or in reverse order if REVERSE is non-nil.
+
+STRING must be a sequence (vector, list or string), and DISTANCE
+must be an integer. (If STRING is a string, it must be possible
+to apply `string' to individual elements of DICT keys.) The
+returned keys will be sequences of the same type as STRING that
+are within Lewenstein distance DISTANCE of STRING. If STRING is a
+list of sequences, keys withing DISTANCE of any 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
+iterators created from DICT before the modification (in
+particular, calling `iter-next' will give unpredictable
+results). If DICT is a meta-dict, this includes any modifications
+to its constituent dicts."
+   (let ((stack (dictree-fuzzy-match-stack dict string distance reverse)))
+     (while (not (dictree-stack-empty-p stack))
+       (iter-yield (dictree-stack-pop stack))))))
+
+
+(heap--when-generators
+ (iter-defun dictree-fuzzy-complete-iter (dict prefix distance &optional 
reverse)
+   "Return an iterator object for fuzzy completions of PREFIX in DICT.
+
+Calling `iter-next' on this object will retrieve the next fuzzy
+completion in \"lexicographic\" order, i.e. the order defined by
+DICT's comparison function, or in reverse order if REVERSE is
+non-nil. Each returned element has the form:
+
+    ((KEY . DIST) . DATA)
+
+PREFIX must be a sequence (vector, list or string), and DISTANCE
+must be an integer. (If PREFIX is a string, it must be possible
+to apply `string' to individual elements of DICT keys.) The
+returned keys will be sequences of the same type as STRING that
+are completions of prefixes within Lewenstein distance DISTANCE
+of PREFIX. If PREFIX is a list of sequences, completions within
+DISTANCE of any prefix 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
+iterators created from DICT before the modification (in
+particular, calling `iter-next' will give unpredictable
+results). If DICT is a meta-dict, this includes any modifications
+to its constituent dicts."
+   (let ((stack (dictree-fuzzy-complete-stack dict prefix distance reverse)))
+     (while (not (dictree-stack-empty-p stack))
+       (iter-yield (dictree-stack-pop stack))))))
+
+
+
+
 ;; ----------------------------------------------------------------
 ;;             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
+    (dict triefun stackfun cachefun cachecreatefun cache-long no-cache arg
+     &optional auxargs rank-function rankfun maxnum reverse filter resultfun)
+  ;; Return results of querying DICT with argument ARG (and AUXARGS list, if
+  ;; any) using TRIEFUN or STACKFUN. If DICT's cache-threshold is non-nil,
+  ;; look first for cached result in cache returned by calling CACHEFUN on
+  ;; DICT, and cache result if query fulfils caching conditions. Non-nil
+  ;; CACHE-LONG indicates long ARGs should be cached, rather than short
+  ;; ARGs. If RANK-FUNCTION is non-nil, return results ordered
+  ;; accordingly. RANKFUN should be the appropriately wrapped version of
+  ;; RANK-FUNCTION. 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 FILTER is 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.
+
+  ;; map over all dictionaries in list
   (when (dictree-p dict) (setq dict (list dict)))
-
-  (let (cache cacheparam completions cmpl cache-entry)
-    ;; map over all dictionaries in list
+  (let ((sort-function (dictree--construct-sortfun (car dict)))
+       cache results res cache-entry)
     (dolist (dic dict)
-      (setq cache (funcall cachefun dic)
-           cacheparam (funcall cacheparamfun dic))
+      (when cachefun (setq cache (funcall cachefun 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))
+
+       ;; if there's a cache entry with enough results, use it
+       ((and (symbolp rank-function) (symbolp filter)
+            (setq cache-entry
+                  (when cache
+                    (gethash (list arg auxargs rank-function reverse filter)
+                             cache)))
             (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
+       (setq res (dictree--cache-results cache-entry))
+       ;; drop any excess results
        (when (and maxnum
                   (or (null (dictree--cache-maxnum cache-entry))
                       (> (dictree--cache-maxnum cache-entry) maxnum)))
-         (setcdr (nthcdr (1- maxnum) completions) nil)))
-
+         (setcdr (nthcdr (1- maxnum) results) nil)))
 
-       ;; if there was nothing useful in the cache, do query and time it
-       (t
+       (t  ;; if there was nothing useful in the cache, do query and time it
        (let (time)
          (setq time (float-time))
-         (setq cmpl
+         (setq res
                (dictree--do-query
-                dic arg triefun stackfun
-                (when rank-function
-                  (dictree--wrap-rankfun rank-function))
-                maxnum reverse nil))
+                dic triefun stackfun arg auxargs rankfun maxnum reverse
+                (when filter (dictree--wrap-filter filter))))
          (setq time (- (float-time) time))
-         ;; if we're above the dictionary's completion cache threshold,
-         ;; cache the result
-         (when (and (not no-cache)
+         ;; if we're above the dictionary's cache threshold, cache the result
+         (when (and cachefun (not no-cache)
                     (dictree--above-cache-threshold-p
                      time (length arg) (dictree-cache-policy dic)
-                     cacheparam))
+                     (dictree-cache-threshold dic) cache-long))
            (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)))
+           ;; create query cache if it doesn't already exist
+           (funcall cachecreatefun dic)
+           (puthash (list arg auxargs rank-function reverse filter)
+                    (dictree--cache-create res maxnum)
+                    (funcall cachefun dic))))))
+
+      ;; merge new result into results list
+      (setq results
+           (dictree--merge results res (or rankfun sort-function)
+                           nil maxnum)))
+
+
+    ;; return results list, applying RESULTFUN if specified, otherwise just
+    ;; stripping meta-data
+    (mapcar (if resultfun
+               (dictree--wrap-resultfun resultfun)
+             (lambda (el) (cons (car el) (dictree--cell-data (cdr el)))))
+           results)))
 
 
 
 (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).
+   (dict triefun stackfun arg &optional auxargs rankfun maxnum reverse filter)
+  ;; Return first MAXNUM results of querying DICT with argument ARG (and
+  ;; AUXARGS list, if any) using TRIEFUN or STACKFUN that satisfy FILTER,
+  ;; ordered according to RANKFUN (defaulting to "lexicographic" 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
+      (let ((stack (apply stackfun
+                         (append (list dict arg) auxargs (list reverse))))
+           (heap (when rankfun
                    (heap-create   ; heap order is inverse of rank order
                        (if reverse
-                           rank-function
+                           rankfun
                          (lambda (a b)
-                           (not (funcall rank-function a b))))
+                           (not (funcall rankfun a b))))
                        (1+ maxnum))))
-           (i 0) cmpl completions)
-       ;; pop MAXNUM completions from the stack
+           (i 0) res results)
+       ;; pop MAXNUM results 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
+                   (setq res (dictree--stack-pop stack)))
+         ;; check result passes FILTER
+         (when (or (null filter) (funcall filter res))
+           (if rankfun
+               (heap-add heap res)   ; for ranked query, add to heap
+             (push res results))     ; for lexicographic query, add to list
            (incf i)))
-       (if (null rank-function)
-           ;; for lexical query, reverse and return completion list (we
+       (if (null rankfun)
+           ;; for lexicographic query, reverse and return result 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)
+           (nreverse results)
+         ;; for ranked query, pass rest of results through heap
+         (while (setq res (dictree--stack-pop stack))
+           (heap-add heap res)
            (heap-delete-root heap))
-         ;; extract completions from heap
-         (while (setq cmpl (heap-delete-root heap))
-           (push cmpl completions))
-         completions))  ; return completion list
+         ;; extract results from heap
+         (while (setq res (heap-delete-root heap))
+           (push res results))
+         results))  ; return result 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)))
+    (apply triefun
+          (append (list (dictree--trie dict) arg) auxargs
+                  (list rankfun maxnum reverse filter)))))
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                        Completing
 
 (defun dictree-complete
   (dict prefix
-   &optional rank-function maxnum reverse no-cache filter resultfun)
+   &optional rank-function maxnum reverse filter resultfun no-cache)
   "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,
+RANK-FUNCTION (defaulting to \"lexicographic\" 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
+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
@@ -2243,15 +2664,15 @@ with the data from a different dictionary. If you want 
to combine
 identical keys, use a meta-dictionary; see
 `dictree-create-meta-dict'.)
 
-If optional argument RANK-FUNCTION is any non-nil value that is
-not a function, the completions are sorted according to the
-dictionary's rank-function (see `dictree-create'). Any non-nil
-value that *is* a function over-rides this. In that case,
-RANK-FUNCTION should accept two arguments, both cons cells. The
-car of each contains a sequence from the trie (of the same type
-as PREFIX), the cdr contains its associated data. The
-RANK-FUNCTION should return non-nil if first argument is ranked
-strictly higher than the second, nil otherwise.
+If optional argument RANK-FUNCTION is t, 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 completion
+from DICT (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.
@@ -2273,20 +2694,17 @@ 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
+   dict #'trie-complete #'dictree-complete-stack
+   #'dictree-complete-cache #'dictree-create-complete-cache
+   nil no-cache  ; cache short PREFIXes
+   prefix nil
+   rank-function
    (when rank-function
      (if (functionp rank-function)
-        rank-function
-       (dictree-rank-function (if (listp dict) (car dict) dict))))
-   maxnum reverse no-cache filter resultfun))
-
+        (dictree--wrap-rankfun rank-function)
+       (dictree--wrap-rankfun
+       (dictree--rank-function (if (listp dict) (car dict) dict)))))
+   maxnum reverse filter resultfun))
 
 
 (defun dictree-collection-function (dict string predicate all)
@@ -2300,24 +2718,24 @@ following as the COLLECTION argument of any of those 
functions:
 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))))
+        (dictree-complete dict string 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
+   &optional rank-function maxnum reverse filter resultfun no-cache)
+  "Return an alist containing all matches for REGEXP in DICT
 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.
+RANKFUN, defauling to \"lexicographic\" order. 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
@@ -2328,12 +2746,13 @@ keys, use a meta-dictionary; see 
`dictree-create-meta-dict'.)
 
 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
+elements are either of the same type as elements of DICT
+keys (these 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.
+individual elements of the keys stored in DICT. The matches
+returned in the alist will be sequences of the same type as
+REGEXP.
 
 Only a subset of the full Emacs regular expression syntax is
 supported. There is no support for regexp constructs that are
@@ -2347,20 +2766,14 @@ 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
+each match is no longer just a key. Instead, each element of the
+results list has the form
+
+    ((KEY (START1 . END1) (START2 . END2) ...) . DATA)
+
+where the (START . END) cons cells 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.
@@ -2369,36 +2782,242 @@ 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.
 
+
+If optional argument RANK-FUNCTION is t, the matches are sorted
+according to the dictionary's rank-function (see
+`dictree-create').
+
+Any other non-nil value of RANK-FUNCTION should be a function
+which accepts two arguments. If the regexp does not contain any
+non-shy grouping constructs, both arguments are (KEY . DATA) cons
+cells, where the car is a sequence of the same type as REGEXP. If
+the regexp does contain non-shy grouping constructs, both
+arguments are of the form
+
+    ((KEY (START1 . END1) (START2 . END2) ...) . DATA)
+
+RANKFUN should return non-nil if first argument is ranked
+strictly higher than the second, nil otherwise.
+
+
 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.
+arguments: a key and its associated data. If the regexp contains
+non-shy grouping constructs, the first argument is of the form
+
+    (KEY (START1 . END1) (START2 . END2) ...)
+
+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."
+accept two arguments, of the same form as those for FILTER (see
+above). Its 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
+   dict #'trie-regexp-search #'dictree-regexp-stack
+   #'dictree-regexp-cache #'dictree-create-regexp-cache
+   (if (and (eq (elt regexp (- (length regexp) 2)) ?.)
+           (eq (elt regexp (- (length regexp) 1)) ?*))
+       nil  ; cache short REGEXP if it ends in .*
+     t)     ; cache long REGEXPs otherwise
+   no-cache
+   regexp nil
+   rank-function
    (when rank-function
      (if (functionp rank-function)
-        rank-function
-       (dictree-rank-function (if (listp dict) (car dict) dict))))
-   maxnum reverse no-cache filter resultfun))
+        (dictree--wrap-regexp-rankfun rank-function)
+       (dictree--wrap-regexp-rankfun
+       (dictree-rank-function (if (listp dict) (car dict) dict)))))
+   maxnum reverse filter resultfun))
+
 
 
+
+;; ----------------------------------------------------------------
+;;                      Fuzzy queries
+
+(defun dictree-fuzzy-match
+  (dict string distance
+   &optional rank-function maxnum reverse filter resultfun no-cache)
+  "Return matches for STRING in DICT within Lewenstein DISTANCE
+\(edit distance\) of STRING along with their associated data, in
+the order defined by RANKFUN, defauling to \"lexicographic\"
+order. If REVERSE is non-nil, the matches are sorted in the
+reverse order. Returns nil if no completions are found.
 
+Returns a list of matches, with elements of the form:
 
+    ((KEY . DIST) . DATA)
+
+where KEY is a matching key from the trie, DATA its associated
+data, and DIST is its Lewenstein distance \(edit distance\) from
+STRING.
+
+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-create-meta-dict'.)
+
+STRING is a sequence (vector, list or string), whose elements
+must be of the same type as elements of the keys stored in
+DICT. If STRING is a string, it must be possible to apply
+`string' to individual elements of DICT keys. The KEYs returned
+in the list will be sequences of the same type as STRING.
+
+DISTANCE must be an integer, and specifies the maximum Lewenstein
+distance \(edit distances\) of matches from STRING.
+
+
+If optional argument RANK-FUNCTION is the symbol `distance', the
+matches are sorted according to their Lewenstein distance from
+STRING. If it is t, the matches are sorted according to the
+dictionary's rank-function (see `dictree-create').
+
+Any other non-nil value of RANK-FUNCTION should be a function
+which accepts two arguments, both of the form
+
+  ((KEY . DIST) . DATA)
+
+where KEY is a sequence from the dictionary (of the same type as
+STRING), DIST is its Lewenstein distance from STRING, and DATA is
+its associated data. The RANK-FUNCTION should return non-nil if
+the 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 disables any
+caching of the result.
+
+The FILTER argument sets a filter function for the matches. If
+supplied, it is called for each possible match with two
+arguments: a (KEY . DIST) cons cell, and 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 . DIST) cons cell, and DATA. Its
+return value is what gets added to the final result list, instead
+of the default key-dist-data list."
+
+  ;; run fuzzy-match query
+  (dictree--query
+   dict #'trie-fuzzy-match #'dictree-fuzzy-match-stack
+   #'dictree-fuzzy-match-cache #'dictree-create-fuzzy-match-cache
+   t no-cache  ; cache long STRINGs
+   string (list distance)
+   rank-function
+   (when rank-function
+     (cond
+      ((eq rank-function 'distance) t)
+      ((functionp rank-function) (dictree--wrap-fuzzy-rankfun rank-function))
+      ((eq rank-function t)
+       (dictree--wrap-fuzzy-rankfun
+       (dictree-rank-function (if (listp dict) (car dict) dict))))))
+   maxnum reverse filter resultfun))
+
+
+(defun dictree-fuzzy-complete
+  (dict prefix distance
+   &optional rank-function maxnum reverse filter resultfun no-cache)
+  "Return completion of prefixes in DICT within Lewenstein DISTANCE
+\(edit distance\) of PREFIX along with their associated data, in
+the order defined by RANKFUN, defauling to \"lexicographic\"
+order. If REVERSE is non-nil, the matches are sorted in the
+reverse order. Returns nil if no completions are found.
+
+Returns a list of completions, with elements of the form:
+
+    ((KEY DIST PFXLEN) . DATA)
+
+where KEY is a matching completion from the trie, DATA its
+associated data, PFXLEN is the length of the prefix part of KEY,
+and DIST is its Lewenstein distance \(edit distance\) from
+PREFIX.
+
+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-create-meta-dict'.)
+
+PREFIX is a sequence (vector, list or string), whose elements
+must be of the same type as elements of the keys stored in
+DICT. If PREFIX is a string, it must be possible to apply
+`string' to individual elements of DICT keys. The KEYs returned
+in the list will be sequences of the same type as PREFIX.
+
+DISTANCE must be an integer, and specifies the maximum Lewenstein
+distance \(edit distances\) of prefixes from PREFIX.
+
+
+If optional argument RANK-FUNCTION is the symbol `distance', the
+matches are sorted by increasing Lewenstein distance of their
+prefix \(with same-distance prefixes ordered
+lexicographically\). If it is t, the matches are sorted according
+to the dictionary's rank-function (see `dictree-create').
+
+Any other non-nil value of RANK-FUNCTION should be a function
+that accepts two arguments, both of the form:
+
+    ((KEY DIST PFXLEN) . DATA)
+
+where KEY is a completion (of the same type as PREFIX), DIST is
+its Lewenstein distances from PREFIX, and DATA is its associated
+data. RANKFUN 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
+fuzzy-match caching enabled.
+
+
+The FILTER argument sets a filter function for the matches. If
+supplied, it is called for each possible match with two
+arguments: a (KEY DIST PFXLEN) list, and 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 DIST PFXLEN) list, and DATA. Its
+return value is what gets added to the final result list, instead
+of the default key-dist-data list."
+
+  ;; run fuzzy-complete query
+  (dictree--query
+   dict #'trie-fuzzy-complete #'dictree-fuzzy-complete-stack
+   #'dictree-fuzzy-complete-cache #'dictree-create-fuzzy-complete-cache
+   nil no-cache  ; cache short PREFIXes
+   prefix (list distance)
+   rank-function
+   (when rank-function
+     (cond
+      ((eq rank-function 'distance) t)
+      ((functionp rank-function) (dictree--wrap-fuzzy-rankfun rank-function))
+      ((eq rank-function t)
+       (dictree--wrap-fuzzy-rankfun
+       (dictree-rank-function (if (listp dict) (car dict) dict))))))
+   maxnum reverse filter resultfun))
+
+
+
+
 ;; ----------------------------------------------------------------
 ;;                    Persistent storage
 
@@ -2413,7 +3032,7 @@ both forms. See `dictree-write'.
 Interactively, DICT is read from the mini-buffer."
   (interactive (list (read-dict "Dictionary: ")))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
 
   (let ((filename (dictree-filename dict)))
     ;; if dictionary has no associated file, prompt for one
@@ -2461,7 +3080,7 @@ and OVERWRITE is the prefix argument."
                                     nil "")
                     current-prefix-arg))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
   ;; default to DICT's current file, if any
   (when (or (null filename)
            (and (called-interactively-p 'any) (string= filename "")))
@@ -2599,12 +3218,12 @@ asked whether they wish to continue after a failed 
save."
               (concat
                "Error: failed to save the following modified "
                "dictionaries: "
-               (mapconcat 'dictree--name save-failures ", ")))
+               (mapconcat #'dictree--name save-failures ", ")))
              nil)
          (yes-or-no-p
           (concat "Error: failed to save the following modified "
                   "dictionaries: "
-                  (mapconcat 'dictree--name save-failures ", ")
+                  (mapconcat #'dictree--name save-failures ", ")
                   "; continue anyway? ")))
       t)))
 
@@ -2638,7 +3257,7 @@ Interactively, FILE is read from the mini-buffer."
       (let (dictname dict)
        (setq dictname
              (file-name-nondirectory (file-name-sans-extension file))
-             dict (eval (intern-soft dictname)))
+             dict (symbol-value (intern-soft dictname)))
        (if (not (dictree-p dict))
            ;; if loading failed, throw error interactively, return nil
            ;; non-interactively
@@ -2676,7 +3295,7 @@ is the prefix argument."
   (interactive (list (read-dict "Dictionary: ")
                     current-prefix-arg))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
 
   ;; if dictionary has been modified, autosave is set and not overidden,
   ;; save it first
@@ -2702,7 +3321,8 @@ is the prefix argument."
 
   ;; remove dictionary from list of loaded dictionaries and unload it
   (setq dictree-loaded-list (delq dict dictree-loaded-list))
-  (unintern (dictree-name dict))
+  ;; We used `unintern' here before, but that's too dangerous!
+  (makunbound (dictree-name dict))
   (message "Dictionary %s unloaded" (dictree-name dict)))
 
 
@@ -2711,8 +3331,7 @@ is the prefix argument."
   ;; 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)
+       complete-alist regexp-alist fuzzy-match-alist fuzzy-complete-alist)
 
     ;; --- convert trie data ---
     ;; if dictionary doesn't use any custom save functions, write
@@ -2739,10 +3358,10 @@ is the prefix argument."
         (trie-insert tmptrie key
                      (dictree--cell-create
                       (funcall (or (dictree--data-savefun dict)
-                                   'identity)
+                                   #'identity)
                                (dictree--cell-data cell))
                       (funcall (or (dictree--plist-savefun dict)
-                                   'identity)
+                                   #'identity)
                                (dictree--cell-plist cell)))))
        (dictree--trie dict))
 
@@ -2771,12 +3390,12 @@ is the prefix argument."
     ;; 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)
+      (when (dictree--lookup-cache dict)
        (maphash
         (lambda (key val)
           (push
            (cons key
-                 (cons (mapcar 'car (dictree--cache-results val))
+                 (cons (mapcar #'car (dictree--cache-results val))
                        (dictree--cache-maxnum val)))
            lookup-alist))
         (dictree--lookup-cache dict))
@@ -2784,7 +3403,7 @@ is the prefix argument."
        (setq hashcode
              (concat
               hashcode
-              "(let ((lookup-cache (make-hash-table :test 'equal))\n"
+              "(let ((lookup-cache (make-hash-table :test #'equal))\n"
               "      (trie (dictree--trie " dictname ")))\n"
               "  (mapc\n"
               "   (lambda (entry)\n"
@@ -2803,14 +3422,10 @@ is the prefix argument."
 
       ;; 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)))
+              '((dictree--complete-cache complete-alist)
+                (dictree--regexp-cache regexp-alist)
+                (dictree--fuzzy-match-cache fuzzy-match-alist)
+                (dictree--fuzzy-complete-cache fuzzy-complete-alist)))
        (when (funcall (nth 0 cache-details) dict)
          ;; convert hash table to alist
          (set (nth 1 cache-details)
@@ -2820,17 +3435,17 @@ is the prefix argument."
                    (push
                     (cons key
                           (cons
-                           (mapcar 'car (dictree--cache-results val))
+                           (mapcar #'car (dictree--cache-results val))
                            (dictree--cache-maxnum val)))
                     alist))
-                 (funcall (nth 2 cache-details) dict))
+               (funcall (nth 0 cache-details) dict))
                 alist))
          ;; generate code to reconstruct hash table from alist
          (setq
           hashcode
           (concat
            hashcode
-           "(let ((cache (make-hash-table :test 'equal))\n"
+           "(let ((cache (make-hash-table :test #'equal))\n"
            "      (trie (dictree--trie " dictname ")))\n"
            "  (mapc\n"
            "   (lambda (entry)\n"
@@ -2845,9 +3460,9 @@ is the prefix argument."
            "        (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"
+           "   (" (symbol-name (nth 0 cache-details)) " " dictname "))\n"
+           "  (setf (" (symbol-name (nth 0 cache-details)) " "
+           dictname ")\n"
            "        cache))\n")))))
 
 
@@ -2860,11 +3475,11 @@ is the prefix argument."
          (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))
+      (setf (dictree--lookup-cache tmpdict)         lookup-alist
+           (dictree--complete-cache tmpdict)       complete-alist
+           (dictree--regexp-cache tmpdict)         regexp-alist
+           (dictree--fuzzy-match-cache tmpdict)    fuzzy-match-alist
+           (dictree--fuzzy-complete-cache tmpdict) fuzzy-complete-alist))
 
     ;; write lisp code that generates the dictionary object
     (let ((print-circle t) (print-level nil) (print-length nil))
@@ -2895,23 +3510,22 @@ is the prefix argument."
   ;; 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)
+       complete-alist regexp-alist fuzzy-match-alist fuzzy-complete-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)
+      (when (dictree--meta-dict-lookup-cache dict)
        (maphash (lambda (key val)
-                  (push (cons key (mapcar 'car val)) lookup-alist))
+                  (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"
+              "(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"
@@ -2920,37 +3534,28 @@ is the prefix argument."
 
       ;; 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)))
+              '((dictree--meta-dict-complete-cache       complete-alist)
+                (dictree--meta-dict-regexp-cache         regexp-alist)
+                (dictree--meta-dict-fuzzy-match-cache    fuzzy-match-alist)
+                (dictree--meta-dict-fuzzy-complete-cache 
fuzzy-complete-alist)))
        (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))
+                (maphash (lambda (key val) (push (cons key val) alist))
+                         (funcall (nth 0 cache-details) dict))
                 alist))
          ;; generate code to reconstruct hash table from alist
          (setq
           hashcode
           (concat
            hashcode
-           "(let ((cache (make-hash-table :test 'equal)))\n"
+           "(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)) " "
+           "    (" (symbol-name (nth 0 cache-details)) " "
                    dictname "))\n"
-           "  (setf (" (symbol-name (nth 2 cache-details)) " "
+           "  (setf (" (symbol-name (nth 0 cache-details)) " "
                        dictname ")\n"
            "        cache))\n")))))
 
@@ -2966,13 +3571,16 @@ is the prefix argument."
            (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))
+      (setf (dictree--meta-dict-lookup-cache tmpdict)
+             lookup-alist
+           (dictree--meta-dict-complete-cache tmpdict)
+             complete-alist
+           (dictree--meta-dict-regexp-cache tmpdict)
+             regexp-alist
+           (dictree--meta-dict-fuzzy-match-cache tmpdict)
+             fuzzy-match-alist
+           (dictree--meta-dict-fuzzy-complete-cache tmpdict)
+             fuzzy-complete-alist))
 
     ;; write lisp code that generates the dictionary object
     (let ((print-circle t) (print-level nil) (print-length nil))
@@ -2988,7 +3596,7 @@ is the prefix argument."
       (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 "
+             "      (mapcar #'eval (dictree--meta-dict-dictlist "
                                    dictname ")))\n")
       (when hashcode (insert hashcode))
       (insert "(unless (memq " dictname " dictree-loaded-list)"
@@ -2996,6 +3604,7 @@ is the prefix argument."
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                Dumping and restoring contents
 
@@ -3033,7 +3642,7 @@ are created when using a trie that is not self-balancing, 
see
                     (read-file-name "File to populate from: "
                                     nil "" t)))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
 
   (if (and (called-interactively-p 'any) (string= file ""))
       (message "No file specified; dictionary %s NOT populated"
@@ -3065,9 +3674,8 @@ are created when using a trie that is not self-balancing, 
see
            (goto-char (point-min)))
          (when (setq entry
                      (condition-case nil
-                         (dictree--read-line
-                          dict key-loadfun data-loadfun
-                          plist-loadfun)
+                         (dictree--read-line key-loadfun data-loadfun
+                                             plist-loadfun)
                        (error (error "Error reading line %d of %s"
                                      midpt file))))
            (dictree-insert dict (car entry) (nth 1 entry)
@@ -3083,9 +3691,8 @@ are created when using a trie that is not self-balancing, 
see
              (forward-line 1))
            (when (setq entry
                        (condition-case nil
-                           (dictree--read-line
-                            dict key-loadfun data-loadfun
-                            plist-loadfun)
+                           (dictree--read-line 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)
@@ -3102,9 +3709,8 @@ are created when using a trie that is not self-balancing, 
see
              (dictree--goto-line (- midpt i 1))
              (when (setq entry
                          (condition-case nil
-                             (dictree--read-line
-                              dict key-loadfun data-loadfun
-                              plist-loadfun)
+                             (dictree--read-line key-loadfun data-loadfun
+                                                 plist-loadfun)
                            (error (error "Error reading line %d of %s"
                                          (- midpt i 1) file))))
                (dictree-insert dict (car entry)
@@ -3120,9 +3726,8 @@ are created when using a trie that is not self-balancing, 
see
            (dictree--goto-line lines)
            (when (setq entry
                        (condition-case nil
-                           (dictree--read-line
-                            dict key-loadfun data-loadfun
-                            plist-loadfun)
+                           (dictree--read-line key-loadfun data-loadfun
+                                               plist-loadfun)
                          (error (error "Error reading line %d of %s"
                                        lines file))))
              (dictree-insert dict (car entry) (nth 1 entry)
@@ -3137,10 +3742,9 @@ are created when using a trie that is not 
self-balancing, see
 
 
 (defun dictree--read-line
-  (dict &optional key-loadfun data-loadfun plist-loadfun)
+  (&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.
+  ;; property list (ditto) at the current line of the current buffer.
   (save-excursion
     (let (key data plist)
       ;; read key
@@ -3151,12 +3755,11 @@ are created when using a trie that is not 
self-balancing, see
        (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
+       ;; if there's anything after the data, use it 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
+       ;; return what we've read
        (list key data plist)))))
 
 
@@ -3169,8 +3772,8 @@ 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'.
+keys, and should be one of the symbols `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
@@ -3184,7 +3787,7 @@ TYPE is always `string'."
                      (buffer-name (current-buffer)))
                     'string))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
 
   ;; select the buffer, creating it if necessary
   (if buffer
@@ -3212,14 +3815,14 @@ TYPE is always `string'."
                  (dictree-name dict) (buffer-name buffer)
                  (1+ count) dictsize))
        (insert (prin1-to-string
-               (funcall (or (dictree--key-savefun dict) 'identity)
+               (funcall (or (dictree--key-savefun dict) #'identity)
                         key)))
        (when (setq data
-                  (funcall (or (dictree--data-savefun dict) 'identity)
+                  (funcall (or (dictree--data-savefun dict) #'identity)
                            data))
         (insert " " (prin1-to-string data)))
        (when (setq plist
-                  (funcall (or (dictree--plist-savefun dict) 'identity)
+                  (funcall (or (dictree--plist-savefun dict) #'identity)
                            plist))
         (unless data (insert " nil"))
         (insert " " (prin1-to-string plist)))
@@ -3240,19 +3843,19 @@ 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'.
+keys, and should be one of the symbols `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'."
+OVERWRITE is the prefix argument, and TYPE is always string."
   (interactive (list (read-dict "Dictionary: ")
                     (read-file-name "File to dump to: " nil "")))
   (when (and (called-interactively-p 'any) (symbolp dict))
-    (setq dict (eval dict)))
+    (setq dict (symbol-value dict)))
 
   (if (and (called-interactively-p 'any) (string= filename ""))
       (message "Dictionary %s NOT dumped" (dictree-name dict))
@@ -3276,7 +3879,7 @@ OVERWRITE is the prefix argument, and TYPE is always 
`string'."
 
 
 
-
+
 ;; ----------------------------------------------------------------
 ;;                     Minibuffer completion
 
@@ -3312,7 +3915,7 @@ extension, suitable for passing to `load-library'."
       ;; gather names of all Elisp libraries in this restricted
       ;; load-path
       (dolist (f (all-completions
-                 "" (apply-partially 'locate-file-completion-table
+                 "" (apply-partially #'locate-file-completion-table
                                      paths (get-load-suffixes))))
        (when (and (null (file-name-directory f))
                   (and (> (length f) 5)
@@ -3331,7 +3934,7 @@ extension, suitable for passing to `load-library'."
                    prompt
                    (if allow-unmatched
                        (completion-table-in-turn
-                        dictname 'read-file-name-internal)
+                        dictname #'read-file-name-internal)
                      dictname)
                    nil (not allow-unmatched) nil
                    (if allow-unloaded
@@ -3344,7 +3947,7 @@ extension, suitable for passing to `load-library'."
      ((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)))
+         (dictree-p (symbol-value (intern-soft dictname)))
        (void-variable nil))
       (intern-soft dictname))
      ;; if user selected an unloaded dictionary, return dict name
@@ -3357,6 +3960,7 @@ extension, suitable for passing to `load-library'."
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;            Pretty-print dictionaries during edebug
 
@@ -3377,21 +3981,82 @@ extension, suitable for passing to `load-library'."
 ;; 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--prin1 (dict stream)
+  (princ (concat "#<dict-tree \"" (dictree-name dict) "\""
+                (if (dictree--lookup-cache dict)
+                    (concat " lookup "
+                            (prin1-to-string
+                             (hash-table-count
+                              (dictree--lookup-cache dict))))
+                  "")
+                (if (dictree--complete-cache dict)
+                    (concat " complete "
+                            (prin1-to-string
+                             (hash-table-count
+                              (dictree--complete-cache dict))))
+                  "")
+                (if (dictree--regexp-cache dict)
+                    (concat " regexp "
+                            (prin1-to-string
+                             (hash-table-count
+                              (dictree--regexp-cache dict))))
+                  "")
+                (if (dictree--fuzzy-match-cache dict)
+                    (concat " fuzzy-match "
+                            (prin1-to-string
+                             (hash-table-count
+                              (dictree--fuzzy-match-cache dict))))
+                  "")
+                (if (dictree--fuzzy-complete-cache dict)
+                    (concat " fuzzy-complete "
+                            (prin1-to-string
+                             (hash-table-count
+                              (dictree--fuzzy-complete-cache dict))))
+                  "")
+                ">")
+        stream))
 
 (defun dictree--edebug-pretty-print (object)
   (cond
    ((dictree-p object)
-    (concat "#<dict-tree \"" (dictree-name object) "\">"))
+    (concat "#<dict-tree \"" (dictree-name object) "\""
+           (if (dictree--lookup-cache object)
+               (concat " lookup "
+                       (prin1-to-string
+                        (hash-table-count
+                         (dictree--lookup-cache object))))
+             "")
+           (if (dictree--complete-cache object)
+               (concat " complete "
+                       (prin1-to-string
+                        (hash-table-count
+                         (dictree--complete-cache object))))
+             "")
+           (if (dictree--regexp-cache object)
+               (concat " regexp "
+                       (prin1-to-string
+                        (hash-table-count
+                         (dictree--regexp-cache object))))
+             "")
+           (if (dictree--fuzzy-match-cache object)
+               (concat " fuzzy-match "
+                       (prin1-to-string
+                        (hash-table-count
+                         (dictree--fuzzy-match-cache object))))
+             "")
+           (if (dictree--fuzzy-complete-cache object)
+               (concat " fuzzy-complete "
+                       (prin1-to-string
+                        (hash-table-count
+                         (dictree--fuzzy-complete-cache object))))
+             "")
+           ">"))
    ((null object) "nil")
    ((let ((dlist object) (test t))
       (while (or (dictree-p (car-safe dlist))
@@ -3402,43 +4067,47 @@ extension, suitable for passing to `load-library'."
                             (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))))
-;;           "]")))
+   ;; ((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))))
+   ;;        "]")))
    ))
 
+(if (fboundp 'cl-print-object)
+    (progn
+      (cl-defmethod cl-print-object ((object dictree-) stream)
+        (dictree--prin1 object stream))
+      (cl-defmethod cl-print-object ((object dictree--meta-dict) stream)
+        (dictree--prin1 object stream)))
 
-(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)))
+  (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)
diff --git a/packages/heap/heap.el b/packages/heap/heap.el
index 4904a2e..c526e9a 100644
--- a/packages/heap/heap.el
+++ b/packages/heap/heap.el
@@ -1,9 +1,10 @@
+;; -*- lexical-binding: t; -*-
 ;;; heap.el --- Heap (a.k.a. priority queue) data structure
 
-;; Copyright (C) 2004-2006, 2008, 2012  Free Software Foundation, Inc
+;; Copyright (C) 2004-2006, 2008, 2012-2013, 2017  Free Software Foundation, 
Inc
 
 ;; Author: Toby Cubitt <address@hidden>
-;; Version: 0.3
+;; Version: 0.5
 ;; Keywords: extensions, data structures, heap, priority queue
 ;; URL: http://www.dr-qubit.org/emacs.php
 ;; Repository: http://www.dr-qubit.org/git/predictive.git
@@ -45,8 +46,8 @@
 ;; advance. Although the heap will grow dynamically if it becomes full, this
 ;; requires copying the entire heap, so insertion has worst-case complexity
 ;; O(n) instead of O(log n), though the amortized complexity is still
-;; O(n). (For applications where the maximum size of the heap is not known in
-;; advance, an implementation based on binary trees might be more suitable,
+;; O(log n). (For applications where the maximum size of the heap is not known
+;; in advance, an implementation based on binary trees might be more suitable,
 ;; but is not currently implemented in this package.)
 ;;
 ;; You create a heap using `make-heap', add elements to it using `heap-add',
@@ -57,51 +58,15 @@
 ;; should never be used outside this package.
 
 
-;;; Change Log:
-;;
-;; Version 0.3
-;; * converted heap data structures into defstructs
-;; * increased default resize-factor to 2
-;; * added `heap-build' function for efficiently building a heap out of a
-;;   vector
-;; * added `heap-merge' function for merging heaps (not very efficient for
-;;   binary -- or ternary -- heaps, only O(n))
-;;
-;; Version 0.2.2
-;; * fixed bug in `heap-copy'
-;;
-;; Version 0.2.1
-;; * modified Commentary
-;;
-;; Version 0.2
-;; * fixed efficiency issue: vectors are no longer copied all the time (thanks
-;;   to Stefan Monnier for pointing this out)
-;;
-;; Version 0.1.5
-;; * renamed `vswap' to `heap--vswap'
-;; * removed cl dependency
-;;
-;; Version 0.1.4
-;; * fixed internal function and macro names
-;;
-;; Version 0.1.3
-;; * added more commentary
-;;
-;; Version 0.1.2
-;; * moved defmacros before their first use so byte-compilation works
-;;
-;; Version 0.1.1
-;; * added cl dependency
-;;
-;; version 0.1
-;; * initial release
-
-
-
 ;;; Code:
 
 (eval-when-compile (require 'cl))
 
+(defmacro heap--when-generators (then)
+  "Evaluate THEN if `generator' library is available."
+  (declare (debug t))
+  (if (require 'generator nil 'noerror) then))
+
 
 ;;; ================================================================
 ;;;        Internal functions for use in the heap package
@@ -137,11 +102,11 @@
              j (+ 3 k)))))))
 
 
-(defmacro heap--vswap (vect i j)   ; INTERNAL USE ONLY
+(defsubst heap--vswap (vect i j)   ; INTERNAL USE ONLY
   ;; Swap elements I and J of vector VECT.
-  `(let ((tmp (aref ,vect ,i)))
-     (aset ,vect ,i (aref ,vect ,j))
-     (aset ,vect ,j tmp) ,vect))
+  (let ((tmp (aref vect i)))
+    (aset vect i (aref vect j))
+    (aset vect j tmp) vect))
 
 
 (defun heap--sift-up (heap n)   ; INTERNAL USE ONLY
@@ -205,7 +170,7 @@ defaulting to 2."
  "Return a copy of heap HEAP."
  (let ((newheap (heap--create (heap--cmpfun heap) (heap--size heap)
                              (heap--resize heap))))
-   (setf (heap--vect newheap) (vconcat (heap--vect heap) [])
+   (setf (heap--vect newheap) (vconcat (heap--vect heap))
         (heap--count newheap) (heap--count heap))
    newheap))
 
@@ -316,8 +281,8 @@ RESIZE-FACTOR sets the factor by which the heap's size is
 increased if it runs out of space, defaulting to 2."
   (or resize-factor (setq resize-factor 2))
   (let ((heap (heap--create compare-function (length vec) resize-factor))
-       (i (ceiling (1- (expt 3
-            (ceiling (1- (log (1+ (* 2 (length vec))) 3))))) 2)))
+       (i (ceiling
+           (1- (expt 3 (ceiling (1- (log (1+ (* 2 (length vec))) 3))))) 2)))
     (setf (heap--vect heap) vec
          (heap--count heap) (length vec))
     (while (>= (decf i) 0) (heap--sift-down heap i))
@@ -332,12 +297,37 @@ of the first HEAP argument.
 
 \(Note that in this heap implementation, the merge operation is
 not very efficient, taking O(n) time for combined heap size n\)."
-  (setq heaps (mapcar 'heap--vect heaps))
+  (setq heaps (mapcar #'heap--vect heaps))
   (heap-build (heap--cmpfun heap)
-             (apply 'vconcat (heap--vect heap) heaps)
+             (apply #'vconcat (heap--vect heap) heaps)
              (heap--resize heap)))
 
 
+(defun heap-clear (heap)
+  "Remove all entries from HEAP.
+
+Return number of entries removed."
+  (prog1
+      (heap--count heap)
+    (setf (heap--vect heap) (make-vector (length (heap--vect heap)) nil)
+          (heap--count heap) 0)))
+
+
+(heap--when-generators
+ (iter-defun heap-iter (heap)
+   "Return a heap iterator object.
+
+Calling `iter-next' on this object will retrieve the next element
+from the heap. The heap itself is not modified.
+
+\(Note that in this heap implementation, constructing a heap
+iterator is not very efficient, taking O(n) time for a heap of
+size n. Each call to `iter-next' on the other hand *is*
+efficient, taking O(log n) time.\)"
+   (let ((heap (heap-copy heap)))
+     (while (not (heap-empty heap))
+       (iter-yield (heap-delete-root heap))))))
+
 
 (provide 'heap)
 
diff --git a/packages/queue/queue.el b/packages/queue/queue.el
index 4c9c954..4e173b3 100644
--- a/packages/queue/queue.el
+++ b/packages/queue/queue.el
@@ -1,11 +1,11 @@
 ;;; queue.el --- Queue data structure  -*- lexical-binding: t; -*-
 
-;; Copyright (C) 1991-1995, 2008-2009, 2012  Free Software Foundation, Inc
+;; Copyright (C) 1991-1995, 2008-2009, 2012, 2017  Free Software Foundation, 
Inc
 
 ;; Author: Inge Wallin <address@hidden>
 ;;         Toby Cubitt <address@hidden>
 ;; Maintainer: Toby Cubitt <address@hidden>
-;; Version: 0.1.1
+;; Version: 0.2
 ;; Keywords: extensions, data structures, queue
 ;; URL: http://www.dr-qubit.org/emacs.php
 ;; Repository: http://www.dr-qubit.org/git/predictive.git
@@ -46,6 +46,11 @@
 
 (eval-when-compile (require 'cl))
 
+(defmacro queue--when-generators (then)
+  "Evaluate THEN if `generator' library is available."
+  (declare (debug t))
+  (if (require 'generator nil 'noerror) then))
+
 
 (defstruct (queue
             ;; A tagged list is the pre-defstruct representation.
@@ -144,6 +149,16 @@ order. The elements themselves are *not* copied."
        (queue-tail queue) nil))
 
 
+(queue--when-generators
+ (iter-defun queue-iter (queue)
+   "Return a queue iterator object.
+
+Calling `iter-next' on this object will retrieve the next element
+from the queue. The queue itself is not modified."
+   (let ((list (queue-head queue)))
+     (while list (iter-yield (pop list))))))
+
+
 (provide 'queue)
 
 
diff --git a/packages/trie/trie.el b/packages/trie/trie.el
index 71ef128..80baecd 100644
--- a/packages/trie/trie.el
+++ b/packages/trie/trie.el
@@ -1,9 +1,9 @@
-;;; trie.el --- Trie data structure
+;;; trie.el --- Trie data structure  -*- lexical-binding: t; -*-
 
-;; Copyright (C) 2008-2010, 2012, 2014  Free Software Foundation, Inc
+;; Copyright (C) 2008-2010, 2012, 2014, 2017  Free Software Foundation, Inc
 
 ;; Author: Toby Cubitt <address@hidden>
-;; Version: 0.2.6
+;; Version: 0.4
 ;; Keywords: extensions, matching, data structures
 ;;           trie, ternary search tree, tree, completion, regexp
 ;; Package-Requires: ((tNFA "0.1.1") (heap "0.3"))
@@ -37,20 +37,29 @@
 ;; can also be performed efficiently: for example, returning all strings with
 ;; a given prefix, searching for keys matching a given wildcard pattern or
 ;; regular expression, or searching for all keys that match any of the above
-;; to within a given Lewenstein distance (though this last is not yet
-;; implemented in this package - code contributions welcome!).
+;; to within a given Lewenstein distance.
 ;;
 ;; You create a trie using `make-trie', create an association using
 ;; `trie-insert', retrieve an association using `trie-lookup', and map over a
 ;; trie using `trie-map', `trie-mapc', `trie-mapcar', or `trie-mapf'. You can
-;; find completions of a prefix sequence using `trie-complete', or search for
-;; keys matching a regular expression using `trie-regexp-search'. Using
-;; `trie-stack', you can create an object that allows the contents of the trie
-;; to be used like a stack, useful for building other algorithms on top of
-;; tries; `trie-stack-pop' pops elements off the stack one-by-one, in
-;; "lexical" order, whilst `trie-stack-push' pushes things onto the
-;; stack. Similarly, `trie-complete-stack', and `trie-regexp-stack' create
-;; "lexically-ordered" stacks of query results.
+;; find completions of a prefix sequence using `trie-complete', search for
+;; keys matching a regular expression using `trie-regexp-search', find fuzzy
+;; matches within a given Lewenstein distance (edit distance) of a string
+;; using `trie-fuzzy-match', and find completions of prefixes within a given
+;; distance using `trie-fuzzy-complete'.
+;;
+;; Using `trie-stack', you can create an object that allows the contents of
+;; the trie to be used like a stack, useful for building other algorithms on
+;; top of tries; `trie-stack-pop' pops elements off the stack one-by-one, in
+;; "lexicographic" order, whilst `trie-stack-push' pushes things onto the
+;; stack. Similarly, `trie-complete-stack', `trie-regexp-stack',
+;; `trie-fuzzy-match-stack' and `trie-fuzzy-complete-stack' create
+;; "lexicographicly-ordered" stacks of query results.
+;;
+;; Very similar to trie-stacks, `trie-iter', `trie-complete-iter',
+;; `trie-regexp-iter', `trie-fuzzy-match-iter' and `trie-fuzzy-complete-iter'
+;; generate iterator objects, which can be used to retrieve successive
+;; elements by calling `iter-next' on them.
 ;;
 ;; Note that there are two uses for a trie: as a lookup table, in which case
 ;; only the presence or absence of a key in the trie is significant, or as an
@@ -62,7 +71,7 @@
 ;; however, the underlying data structures naturally support associative
 ;; arrays at no extra cost, so this package does the opposite: it implements
 ;; associative arrays, and leaves it up to you to use them as lookup tables if
-;; you so desire.
+;; you so desire, by ignoring the associated data.
 ;;
 ;;
 ;; Different Types of Trie
@@ -99,12 +108,12 @@
 ;; efficienct insertion operations, and less efficient deletion
 ;; operations. Splay trees give good average-case complexity and are simpler
 ;; to implement than AVL or red-black trees (which can mean they're faster in
-;; practice!), at the expense of poor worst-case complexity.
+;; practice), at the expense of poor worst-case complexity.
 ;;
 ;; If your tries are going to be static (i.e. created once and rarely
 ;; modified), then using perfectly balanced binary search trees might be
 ;; appropriate. Perfectly balancing the binary trees is very inefficient, but
-;; it only has to be when the trie is first created or modified. Lookup
+;; it only has to be done when the trie is first created or modified. Lookup
 ;; operations will then be as efficient as possible for ternary search trees,
 ;; and the implementation will also be simpler (so probably faster) than a
 ;; self-balancing tree, without the space and time overhead required to keep
@@ -145,6 +154,7 @@
 
 
 
+
 ;;; ================================================================
 ;;;                   Pre-defined trie types
 
@@ -153,7 +163,7 @@
 
 ;; --- avl-tree ---
 (put 'avl :trie-createfun
-     (lambda (cmpfun seq) (avl-tree-create cmpfun)))
+     (lambda (cmpfun _seq) (avl-tree-create cmpfun)))
 (put 'avl :trie-insertfun 'avl-tree-enter)
 (put 'avl :trie-deletefun 'avl-tree-delete)
 (put 'avl :trie-lookupfun 'avl-tree-member)
@@ -167,15 +177,79 @@
 
 
 
+
 ;;; ================================================================
 ;;;           Internal utility functions and macros
 
-;;; ----------------------------------------------------------------
-;;;           Functions and macros for handling a trie.
-
 ;; symbol used to denote a trie leaf node
 (defconst trie--terminator '--trie--terminator)
 
+
+(defmacro trie--if-lexical-binding (then else)
+  "If lexical binding is in effect, evaluate THEN, otherwise ELSE."
+  (declare (indent 1) (debug t))
+  (if (let ((tempvar nil)
+           (f (let ((tempvar t)) (lambda () tempvar))))
+       tempvar  ;; shut up "unused lexical variable" byte-compiler warning
+       (funcall f))
+      then else))
+
+
+;; wrap CMPFUN for use in a subtree
+(trie--if-lexical-binding
+    (defun trie--wrap-cmpfun (cmpfun)
+      (lambda (a b)
+       (setq a (trie--node-split a)
+             b (trie--node-split b))
+       (cond ((eq a trie--terminator)
+              (if (eq b trie--terminator) nil t))
+             ((eq b trie--terminator) nil)
+             (t (funcall cmpfun a b)))))
+  (defun trie--wrap-cmpfun (cmpfun)
+    `(lambda (a b)
+       (setq a (trie--node-split a)
+            b (trie--node-split b))
+       (cond ((eq a trie--terminator)
+             (if (eq b trie--terminator) nil t))
+            ((eq b trie--terminator) nil)
+            (t (,cmpfun a b))))))
+
+
+;; create equality function from trie comparison function
+(trie--if-lexical-binding
+    (defun trie--construct-equality-function (comparison-function)
+      (lambda (a b)
+        (not (or (funcall comparison-function a b)
+                 (funcall comparison-function b a)))))
+  (defun trie--construct-equality-function (comparison-function)
+    `(lambda (a b)
+       (not (or (,comparison-function a b)
+               (,comparison-function b a))))))
+
+
+;; create Lewenstein rank function from trie comparison function
+(trie--if-lexical-binding
+    (defun trie--construct-Lewenstein-rankfun (comparison-function)
+      (let ((compfun (trie-construct-sortfun comparison-function)))
+       (lambda (a b)
+         (cond
+          ((< (nth 1 (car a)) (nth 1 (car b))) t)
+          ((> (nth 1 (car a)) (nth 1 (car b))) nil)
+          (t (funcall compfun (nth 0 (car a)) (nth 0 (car b))))))))
+  (defun trie--construct-Lewenstein-rankfun (comparison-function)
+    `(lambda (a b)
+       (cond
+       ((< (nth 1 (car a)) (nth 1 (car b))) t)
+       ((> (nth 1 (car a)) (nth 1 (car b))) nil)
+       (t ,(trie-construct-sortfun comparison-function)
+          (nth 0 (car a)) (nth 0 (car b)))))))
+
+
+
+
+;;; ----------------------------------------------------------------
+;;;           Functions and macros for handling a trie.
+
 (defstruct
   (trie-
    :named
@@ -183,7 +257,7 @@
    (:constructor trie--create
                 (comparison-function &optional (type 'avl)
                  &aux
-                 (dummy
+                 (_dummy
                   (or (memq type trie--types)
                       (error "trie--create: unknown trie TYPE, %s" type)))
                  (createfun (get type :trie-createfun))
@@ -203,17 +277,17 @@
    (:constructor trie--create-custom
                 (comparison-function
                  &key
-                 (createfun 'avl-tree-create-bare)
-                 (insertfun 'avl-tree-enter)
-                 (deletefun 'avl-tree-delete)
-                 (lookupfun 'avl-tree-member)
-                 (mapfun 'avl-tree-mapc)
-                 (emptyfun 'avl-tree-empty)
-                 (stack-createfun 'avl-tree-stack)
-                 (stack-popfun 'avl-tree-stack-pop)
-                 (stack-emptyfun 'avl-tree-stack-empty-p)
-                 (transform-for-print 'trie--avl-transform-for-print)
-                 (transform-from-read 'trie--avl-transform-from-read)
+                 (createfun #'avl-tree-create-bare)
+                 (insertfun #'avl-tree-enter)
+                 (deletefun #'avl-tree-delete)
+                 (lookupfun #'avl-tree-member)
+                 (mapfun #'avl-tree-mapc)
+                 (emptyfun #'avl-tree-empty)
+                 (stack-createfun #'avl-tree-stack)
+                 (stack-popfun #'avl-tree-stack-pop)
+                 (stack-emptyfun #'avl-tree-stack-empty-p)
+                 (transform-for-print #'trie--avl-transform-for-print)
+                 (transform-from-read #'trie--avl-transform-from-read)
                  &aux
                  (cmpfun (trie--wrap-cmpfun comparison-function))
                  (root (trie--node-create-root createfun cmpfun))
@@ -225,25 +299,8 @@
   transform-for-print transform-from-read print-form)
 
 
-(defun trie--wrap-cmpfun (cmpfun)
-  ;; wrap CMPFUN for use in a subtree
-  `(lambda (a b)
-     (setq a (trie--node-split a)
-          b (trie--node-split b))
-     (cond ((eq a trie--terminator)
-           (if (eq b trie--terminator) nil t))
-          ((eq b trie--terminator) nil)
-          (t (,cmpfun a b)))))
-
-
-(defun trie--construct-equality-function (comparison-function)
-  ;; create equality function from trie comparison function
-  `(lambda (a b)
-     (and (not (,comparison-function a b))
-         (not (,comparison-function b a)))))
-
-
 
+
 ;;; ----------------------------------------------------------------
 ;;;          Functions and macros for handling a trie node.
 
@@ -273,25 +330,24 @@
 (defsetf trie--node-data (node) (data)
   `(setf (trie--node-subtree ,node) ,data))
 
-(defmacro trie--node-data-p (node)
+(defsubst trie--node-data-p (node)
   ;; Return t if NODE is a data node, nil otherwise.
-  `(eq (trie--node-split ,node) trie--terminator))
+  (eq (trie--node-split node) trie--terminator))
 
-(defmacro trie--node-p (node)
+(defsubst trie--node-p (node)
   ;; Return t if NODE is a TRIE trie--node, nil otherwise.  Have to
   ;; define this ourselves, because we created a defstruct without any
   ;; identifying tags (i.e. (:type vector)) for efficiency, but this
   ;; means we can only perform a rudimentary and very unreliable test.
-  `(and (vectorp ,node) (= (length ,node) 2)))
+  (and (vectorp node) (= (length node) 2)))
 
 
 (defun trie--node-find (node seq lookupfun)
   ;; Returns the node below NODE corresponding to SEQ, or nil if none
   ;; found.
-  (let ((len (length seq))
-       (i -1))
+  (let ((i -1))
     ;; descend trie until we find SEQ or run out of trie
-    (while (and node (< (incf i) len))
+    (while (and node (< (incf i) (length seq)))
       (setq node
            (funcall lookupfun
                     (trie--node-subtree node)
@@ -300,23 +356,24 @@
     node))
 
 
-(defmacro trie--find-data-node (node lookupfun)
+(defsubst trie--find-data-node (node lookupfun)
   ;; Return data node from NODE's subtree, or nil if NODE has no data
   ;; node in its subtree.
-  `(funcall ,lookupfun
-           (trie--node-subtree ,node)
-           (trie--node-create-dummy trie--terminator)
-           nil))
+  (funcall lookupfun
+          (trie--node-subtree node)
+          (trie--node-create-dummy trie--terminator)
+          nil))
 
 
-(defmacro trie--find-data (node lookupfun)
+(defsubst trie--find-data (node lookupfun)
   ;; Return data associated with sequence corresponding to NODE, or nil
   ;; if sequence has no associated data.
-  `(let ((node (trie--find-data-node ,node ,lookupfun)))
-     (when node (trie--node-data node))))
+  (let ((node (trie--find-data-node node lookupfun)))
+    (when node (trie--node-data node))))
 
 
 
+
 ;;; ----------------------------------------------------------------
 ;;;              print/read transformation functions
 
@@ -338,18 +395,18 @@
       (setf (trie--print-form trie) nil))))
 
 
-(defmacro trie-transform-from-read-warn (trie)
+(defsubst trie-transform-from-read-warn (trie)
   "Transform TRIE from print form, with warning."
-  `(when (trie--print-form ,trie)
-     (warn (concat "Attempt to operate on trie in print-form;\
+  (when (trie--print-form trie)
+    (warn (concat "Attempt to operate on trie in print-form;\
  converting to normal form"))
-     (trie-transform-from-read ,trie)))
+    (trie-transform-from-read trie)))
 
 
 (defun trie--avl-transform-for-print (trie)
   ;; transform avl-tree based TRIE to print form.
   (trie-mapc-internal
-   (lambda (avl seq) (setf (avl-tree--cmpfun avl) nil))
+   (lambda (avl _seq) (setf (avl-tree--cmpfun avl) nil))
    trie))
 
 
@@ -357,12 +414,13 @@
   ;; transform avl-tree based TRIE from print form."
   (let ((--trie-avl-transform--cmpfun (trie--cmpfun trie)))
     (trie-mapc-internal
-     (lambda (avl seq)
+     (lambda (avl _seq)
        (setf (avl-tree--cmpfun avl) --trie-avl-transform--cmpfun))
      trie)))
 
 
 
+
 ;;; ----------------------------------------------------------------
 ;;;                Replacements for CL functions
 
@@ -419,13 +477,13 @@ Comparison is done with `equal'."
   "Concatenate SEQ and SEQUENCES, and make the result the same
 type of sequence as SEQ."
   (cond
-   ((stringp seq) (apply 'concat  seq sequences))
-   ((vectorp seq) (apply 'vconcat seq sequences))
-   ((listp seq)          (apply 'append  seq sequences))))
-
+   ((stringp seq) (apply #'concat  seq sequences))
+   ((vectorp seq) (apply #'vconcat seq sequences))
+   ((listp seq)          (apply #'append  seq sequences))))
 
 
 
+
 ;;; ================================================================
 ;;;                     Basic trie operations
 
@@ -442,7 +500,7 @@ The optional argument TYPE specifies the type of trie to
 create. However, the only one that is currently implemented is
 the default, so this argument is useless for now.
 
-(See also `make-trie-custom'.)")
+\(See also `make-trie-custom'.\)")
 
 
 ;;;###autoload
@@ -577,32 +635,55 @@ functions must *never* bind any variables with names 
commencing
           (trie--node-subtree (trie--root trie))))
 
 
-(defun trie-construct-sortfun (cmpfun &optional reverse)
-  "Construct function to compare key sequences, based on a CMPFUN
+(trie--if-lexical-binding
+    (defun trie-construct-sortfun (cmpfun &optional reverse)
+      "Construct function to compare key sequences, based on a CMPFUN
 that compares individual elements of the sequence. Order is
 reversed if REVERSE is non-nil."
-  (if reverse
-      `(lambda (a b)
-        (let (cmp)
+      (if reverse
+         (lambda (a b)
+           (catch 'compared
+             (dotimes (i (min (length a) (length b)))
+               (cond ((funcall cmpfun (elt b i) (elt a i))
+                      (throw 'compared t))
+                     ((funcall cmpfun (elt a i) (elt b i))
+                      (throw 'compared nil))))
+             (< (length a) (length b))))
+       (lambda (a b)
+         (catch 'compared
+           (dotimes (i (min (length a) (length b)))
+             (cond ((funcall cmpfun (elt a i) (elt b i))
+                    (throw 'compared t))
+                   ((funcall cmpfun (elt b i) (elt a i))
+                    (throw 'compared nil))))
+           (< (length a) (length b))))))
+
+  (defun trie-construct-sortfun (cmpfun &optional reverse)
+    "Construct function to compare key sequences, based on a CMPFUN
+that compares individual elements of the sequence. Order is
+reversed if REVERSE is non-nil."
+    (if reverse
+       `(lambda (a b)
           (catch 'compared
             (dotimes (i (min (length a) (length b)))
               (cond ((,cmpfun (elt b i) (elt a i))
                      (throw 'compared t))
                     ((,cmpfun (elt a i) (elt b i))
                      (throw 'compared nil))))
-            (< (length a) (length b)))))
-    `(lambda (a b)
-       (let (cmp)
+            (< (length a) (length b))))
+      `(lambda (a b)
         (catch 'compared
           (dotimes (i (min (length a) (length b)))
             (cond ((,cmpfun (elt a i) (elt b i))
                    (throw 'compared t))
                   ((,cmpfun (elt b i) (elt a i))
                    (throw 'compared nil))))
-          (< (length a) (length b)))))))
+          (< (length a) (length b))))))
+)
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                        Inserting data
 
@@ -641,7 +722,7 @@ bind any variables with names commencing \"--\"."
       (setq node (funcall (trie--insertfun trie)
                          (trie--node-subtree node)
                          (trie--node-create (elt key i) key trie)
-                         (lambda (a b)
+                         (lambda (_a b)
                            (setq --trie-insert--old-node-flag t) b))))
     ;; Create or update data node.
     (setq node (funcall (trie--insertfun trie)
@@ -661,9 +742,57 @@ bind any variables with names commencing \"--\"."
 
 
 
+
 ;; ----------------------------------------------------------------
 ;;                        Deleting data
 
+;; The absurd argument names are to lessen the likelihood of dynamical scoping
+;; bugs caused by a supplied function binding a variable with the same name as
+;; one of the arguments, which would cause a nasty bug when they're called.
+;; FIXME: not needed with lexical binding
+(defun trie--do-delete (node --trie--do-delete--seq
+                            --trie--do-delete--test
+                            --trie--do-delete--deletefun
+                            --trie--do-delete--emptyfun
+                            --trie--do-delete--cmpfun
+                            --trie--do-delete--key)
+  ;; Delete --TRIE--DO-DELETE--SEQ starting from trie node NODE, and
+  ;; return non-nil if we deleted a node. If --TRIE--DO-DELETE--TEST is
+  ;; supplied, it is called with two arguments, the key being deleted
+  ;; and the associated data, and the deletion is only carried out if it
+  ;; returns non-nil.
+
+  ;; if --TRIE--DO-DELETE--SEQ is empty, try to delete data node and
+  ;; return non-nil if we did (return value of a trie's deletefun is the
+  ;; deleted data, which is always non-nil for a trie)
+  (if (= (length --trie--do-delete--seq) 0)
+      (funcall --trie--do-delete--deletefun
+              (trie--node-subtree node)
+              (trie--node-create-dummy trie--terminator)
+              (when --trie--do-delete--test
+                (lambda (n)
+                  (funcall --trie--do-delete--test
+                           --trie--do-delete--key (trie--node-data n)))))
+    ;; otherwise, delete on down (return value of trie's deletion function is
+    ;; the deleted data, which is always non-nil for a trie)
+    (let (--trie-deleted--node)
+      (funcall --trie--do-delete--deletefun
+              (trie--node-subtree node)
+              (trie--node-create-dummy (elt --trie--do-delete--seq 0))
+              (lambda (n)
+                (and (setq --trie-deleted--node
+                           (trie--do-delete
+                            n (trie--subseq --trie--do-delete--seq 1)
+                            --trie--do-delete--test
+                            --trie--do-delete--deletefun
+                            --trie--do-delete--emptyfun
+                            --trie--do-delete--cmpfun
+                            --trie--do-delete--key))
+                     (funcall --trie--do-delete--emptyfun
+                              (trie--node-subtree n)))))
+      --trie-deleted--node)))
+
+
 (defun trie-delete (trie key &optional test)
   "Delete KEY and its associated data from TRIE.
 
@@ -680,65 +809,13 @@ any variables with names commencing \"--\"."
   ;; convert trie from print-form if necessary
   (trie-transform-from-read-warn trie)
   ;; set up deletion (real work is done by `trie--do-delete'
-  (let (--trie-deleted--node
-       (--trie-delete--key key))
-    (declare (special --trie-deleted--node)
-            (special --trie-delete--key))
-    (trie--do-delete (trie--root trie) key test
-                    (trie--deletefun trie)
-                    (trie--emptyfun trie)
-                    (trie--cmpfun trie))
-    (when --trie-deleted--node
-      (cons key (trie--node-data --trie-deleted--node)))))
-
-
-(defun trie--do-delete (node --trie--do-delete--seq
-                            --trie--do-delete--test
-                            --trie--do-delete--deletefun
-                            --trie--do-delete--emptyfun
-                            --trie--do-delete--cmpfun)
-  ;; Delete --TRIE--DO-DELETE--SEQ starting from trie node NODE, and
-  ;; return non-nil if we deleted a node. If --TRIE--DO-DELETE--TEST is
-  ;; supplied, it is called with two arguments, the key being deleted
-  ;; and the associated data, and the deletion is only carried out if it
-  ;; returns non-nil.
-
-  ;; The absurd argument names are to lessen the likelihood of dynamical
-  ;; scoping bugs caused by a supplied function binding a variable with
-  ;; the same name as one of the arguments, which would cause a nasty
-  ;; bug when the lambda's (below) are called.
-  (declare (special --trie-deleted--node)
-          (special --trie-delete--key))
-  ;; if --TRIE--DO-DELETE--SEQ is empty, try to delete data node and
-  ;; return non-nil if we did (return value of
-  ;; --TRIE--DO-DELETE--DELETEFUN is the deleted data, which is always
-  ;; non-nil for a trie)
-  (if (= (length --trie--do-delete--seq) 0)
-      (setq --trie-deleted--node
-           (funcall --trie--do-delete--deletefun
-                    (trie--node-subtree node)
-                    (trie--node-create-dummy trie--terminator)
-                    (when --trie--do-delete--test
-                      (lambda (n)
-                        (funcall --trie--do-delete--test
-                                 --trie-delete--key (trie--node-data n))))
-                    nil))
-    ;; otherwise, delete on down (return value of
-    ;; --TRIE--DO-DELETE--DELETEFUN is the deleted data, which is always
-    ;; non-nil for a trie)
-    (funcall --trie--do-delete--deletefun
-            (trie--node-subtree node)
-            (trie--node-create-dummy (elt --trie--do-delete--seq 0))
-            (lambda (n)
-              (and (trie--do-delete
-                    n (trie--subseq --trie--do-delete--seq 1)
-                    --trie--do-delete--test
-                    --trie--do-delete--deletefun
-                    --trie--do-delete--emptyfun
-                    --trie--do-delete--cmpfun)
-                   (funcall --trie--do-delete--emptyfun
-                            (trie--node-subtree n))))
-            nil)))
+  (let ((deleted-node
+        (trie--do-delete (trie--root trie) key test
+                         (trie--deletefun trie)
+                         (trie--emptyfun trie)
+                         (trie--cmpfun trie)
+                         key)))
+    (when deleted-node (cons key (trie--node-data deleted-node)))))
 
 
 
@@ -775,7 +852,7 @@ also `trie-member-p', which does this for you.)"
 
 
 
-
+
 ;;; ================================================================
 ;;;                      Mapping over tries
 
@@ -965,18 +1042,15 @@ 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 \"--\"."
+is more efficient."
   ;; convert from print-form if necessary
   (trie-transform-from-read-warn trie)
   ;; map FUNCTION over TRIE and accumulate in a list
-  (nreverse (trie-mapf function 'cons trie type reverse)))
-
+  (nreverse (trie-mapf function #'cons trie type reverse)))
 
 
 
+
 ;;; ================================================================
 ;;;                    Using tries as stacks
 
@@ -991,10 +1065,10 @@ bind any variables with names commencing \"--\"."
              &aux
              (comparison-function (trie--comparison-function trie))
              (lookupfun (trie--lookupfun trie))
-             (stack-createfun (trie--stack-createfun trie))
-             (stack-popfun (trie--stack-popfun trie))
-             (stack-emptyfun (trie--stack-emptyfun trie))
-             (repopulatefun 'trie--stack-repopulate)
+             (stackcreatefun (trie--stack-createfun trie))
+             (stackpopfun (trie--stack-popfun trie))
+             (stackemptyfun (trie--stack-emptyfun trie))
+             (repopulatefun #'trie--stack-repopulate)
              (store
               (if (trie-empty trie)
                   nil
@@ -1004,27 +1078,27 @@ bind any variables with names commencing \"--\"."
                               ((eq type 'string) "")
                               (t []))
                         (funcall
-                         stack-createfun
+                         stackcreatefun
                          (trie--node-subtree (trie--root trie))
                          reverse)))
                  reverse
                  comparison-function lookupfun
-                 stack-createfun stack-popfun stack-emptyfun)))
+                 stackcreatefun stackpopfun stackemptyfun)))
              (pushed '())
              ))
            (:constructor
-            trie--completion-stack-create
+            trie--complete-stack-create
             (trie prefix
              &optional
              reverse
              &aux
              (comparison-function (trie--comparison-function trie))
              (lookupfun (trie--lookupfun trie))
-             (stack-createfun (trie--stack-createfun trie))
-             (stack-popfun (trie--stack-popfun trie))
-             (stack-emptyfun (trie--stack-emptyfun trie))
-             (repopulatefun 'trie--stack-repopulate)
-             (store (trie--completion-stack-construct-store
+             (stackcreatefun (trie--stack-createfun trie))
+             (stackpopfun (trie--stack-popfun trie))
+             (stackemptyfun (trie--stack-emptyfun trie))
+             (repopulatefun #'trie--stack-repopulate)
+             (store (trie--complete-stack-construct-store
                      trie prefix reverse))
              (pushed '())
              ))
@@ -1036,41 +1110,77 @@ bind any variables with names commencing \"--\"."
              &aux
              (comparison-function (trie--comparison-function trie))
              (lookupfun (trie--lookupfun trie))
-             (stack-createfun (trie--stack-createfun trie))
-             (stack-popfun (trie--stack-popfun trie))
-             (stack-emptyfun (trie--stack-emptyfun trie))
-             (repopulatefun 'trie--regexp-stack-repopulate)
+             (stackcreatefun (trie--stack-createfun trie))
+             (stackpopfun (trie--stack-popfun trie))
+             (stackemptyfun (trie--stack-emptyfun trie))
+             (repopulatefun #'trie--regexp-stack-repopulate)
              (store (trie--regexp-stack-construct-store
                      trie regexp reverse))
              (pushed '())
              ))
+           (:constructor
+            trie--fuzzy-match-stack-create
+            (trie string distance
+             &optional
+             reverse
+             &aux
+             (comparison-function (trie--comparison-function trie))
+             (lookupfun (trie--lookupfun trie))
+             (stackcreatefun (trie--stack-createfun trie))
+             (stackpopfun (trie--stack-popfun trie))
+             (stackemptyfun (trie--stack-emptyfun trie))
+             (repopulatefun #'trie--fuzzy-match-stack-repopulate)
+             (store (trie--fuzzy-match-stack-construct-store
+                     trie string distance reverse))
+             (pushed '())
+             ))
+           (:constructor
+            trie--fuzzy-complete-stack-create
+            (trie prefix distance
+             &optional
+             reverse
+             &aux
+             (comparison-function (trie--comparison-function trie))
+             (lookupfun (trie--lookupfun trie))
+             (stackcreatefun (trie--stack-createfun trie))
+             (stackpopfun (trie--stack-popfun trie))
+             (stackemptyfun (trie--stack-emptyfun trie))
+             (repopulatefun #'trie--fuzzy-complete-stack-repopulate)
+             (store (trie--fuzzy-complete-stack-construct-store
+                     trie prefix distance reverse))
+             (pushed '())
+             ))
            (:copier nil))
   reverse comparison-function lookupfun
-  stack-createfun stack-popfun stack-emptyfun
+  stackcreatefun stackpopfun stackemptyfun
   repopulatefun store pushed)
 
 
 (defun trie-stack (trie &optional type reverse)
   "Return an object that allows TRIE to be accessed as a stack.
 
-The stack is sorted in \"lexical\" order, i.e. the order defined
-by the trie's comparison function, or in reverse order if REVERSE
-is non-nil. Calling `trie-stack-pop' pops the top element (a key
-and its associated data) from the stack.
+The stack is sorted in \"lexicographic\" order, i.e. the order
+defined by the trie's comparison function, or in reverse order if
+REVERSE is non-nil. Calling `trie-stack-pop' pops the top element
+\(a cons cell containing 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.
+Optional argument TYPE \(one of the symbols `vector', `lisp' or
+`string'\) sets the type of sequence used for the keys,
+defaulting to `vector'. \(If TYPE is string, it must be possible
+to apply `string' to individual elements of TRIE keys.\)
 
 Note that any modification to TRIE *immediately* invalidates all
-trie-stacks created before the modification (in particular,
-calling `trie-stack-pop' will give unpredictable results).
+trie-stacks created before the modification \(in particular,
+calling `trie-stack-pop' will give unpredictable results\).
 
 Operations on trie-stacks are significantly more efficient than
-constructing a real stack from the trie and using standard stack
-functions. As such, they can be useful in implementing efficient
-algorithms on tries. However, in cases where mapping functions
-`trie-mapc', `trie-mapcar' or `trie-mapf' would be sufficient, it
-is better to use one of those instead."
+constructing a real stack containing all the contents of the trie
+and using standard stack functions. As such, they can be useful
+in implementing efficient algorithms over tries. However, in
+cases where mapping functions `trie-mapc', `trie-mapcar' or
+`trie-mapf' would be sufficient, it may be better to use one of
+those instead."
   ;; convert trie from print-form if necessary
   (trie-transform-from-read-warn trie)
   ;; if stack functions aren't defined for trie type, throw error
@@ -1101,9 +1211,9 @@ element stored in the trie.)"
                       (trie--stack-reverse trie-stack)
                       (trie--stack-comparison-function trie-stack)
                       (trie--stack-lookupfun trie-stack)
-                      (trie--stack-stack-createfun trie-stack)
-                      (trie--stack-stack-popfun trie-stack)
-                      (trie--stack-stack-emptyfun trie-stack)))))))
+                      (trie--stack-stackcreatefun trie-stack)
+                      (trie--stack-stackpopfun trie-stack)
+                      (trie--stack-stackemptyfun trie-stack)))))))
 
 
 (defun trie-stack-push (element trie-stack)
@@ -1140,7 +1250,7 @@ element stored in the trie.)"
 
 
 (defun trie--stack-repopulate
-  (store reverse comparison-function lookupfun
+  (store reverse _comparison-function _lookupfun
         stack-createfun stack-popfun stack-emptyfun)
   ;; Recursively push children of the node at the head of STORE onto the
   ;; front of STORE, until a data node is reached.
@@ -1150,44 +1260,74 @@ element stored in the trie.)"
     (let ((node (funcall stack-popfun (cdar store)))
          (seq (caar store)))
       (when (funcall stack-emptyfun (cdar store))
-       ;; (pop store) here produces irritating compiler warnings
+       ;; using (pop store) here produces irritating compiler warnings
        (setq store (cdr store)))
 
       (while (not (trie--node-data-p node))
        (push
         (cons (trie--seq-append seq (trie--node-split node))
-              (funcall stack-createfun
-                       (trie--node-subtree node) reverse))
+              (funcall stack-createfun (trie--node-subtree node) reverse))
         store)
        (setq node (funcall stack-popfun (cdar store))
              seq (caar store))
        (when (funcall stack-emptyfun (cdar store))
-         ;; (pop store) here produces irritating compiler warnings
          (setq store (cdr store))))
 
       (push (cons seq (trie--node-data node)) store))))
 
 
 
+;; trie-stacks *are* iterators (with additional push and inspect-first-element
+;; operations). If we're running on a modern Emacs that includes the
+;; `generator' library, we can trivially define trie iterator generators in
+;; terms of trie-stacks.
+
+(heap--when-generators
+ (iter-defun trie-iter (trie &optional type reverse)
+   "Return a trie iterator object.
+
+Calling `iter-next' on this object will retrieve the next element
+\(a cons cell containing a key and its associated data\) from
+TRIE, in \"lexicographic\" order, i.e. the order defined by the
+trie's comparison function, or in reverse order if REVERSE is
+non-nil.
+
+Optional argument TYPE \(one of the symbols `vector', `list' or
+`string'\) sets the type of sequence used for the keys,
+defaulting to `vector'. \(If TYPE is string, it must be possible
+to apply `string' to individual elements of TRIE keys.\)
+
+Note that any modification to TRIE *immediately* invalidates all
+iterators created from TRIE before the modification \(in
+particular, calling `iter-next' will give unpredictable
+results\)."
+   (let ((stack (trie-stack trie type reverse)))
+     (while (not (trie-stack-empty-p stack))
+       (iter-yield (trie-stack-pop stack))))))
+
+
 
+
+
+
 ;; ================================================================
 ;;                   Query-building utility macros
 
 ;; Implementation Note
 ;; -------------------
-;; For queries ranked in anything other than lexical order, we use a
-;; partial heap-sort to find the k=MAXNUM highest ranked matches among
-;; the n possibile matches. This has worst-case time complexity
-;; O(n log k), and is both simple and elegant. An optimal algorithm
-;; (e.g. partial quick-sort discarding the irrelevant partition at each
-;; step) would have complexity O(n + k log k), but is probably not worth
-;; the extra coding effort, and would have worse space complexity unless
-;; coded to work "in-place", which would be highly non-trivial. (I
-;; haven't done any benchmarking, though, so feel free to do so and let
-;; me know the results!)
+;; For queries ranked in anything other than lexicographic order, we use a
+;; partial heap-sort to find the k=MAXNUM highest ranked matches among the n
+;; possibile matches. This has worst-case time complexity O(n log k), and is
+;; both simple and elegant. An optimal algorithm (e.g. partial quick-sort
+;; discarding the irrelevant partition at each step) would have complexity
+;; O(n + k log k), but is probably not worth the extra coding effort. It would
+;; also have worse space complexity unless coded to work "in-place", which
+;; would be highly non-trivial. (I haven't done any benchmarking, though, so
+;; feel free to do so and let me know the results!)
 
 (defmacro trie--construct-accumulator (maxnum filter resultfun)
   ;; Does what it says on the tin! | sed -e 's/tin/macro name/'
+  (declare (debug t))
   `(cond
     ;; filter, maxnum, resultfun
     ((and ,filter ,maxnum ,resultfun)
@@ -1197,7 +1337,7 @@ element stored in the trie.)"
               (cons (funcall ,resultfun seq data)
                     (aref trie--accumulate 0)))
         (and (>= (length (aref trie--accumulate 0)) ,maxnum)
-             (throw 'trie-accumulate--done nil)))))
+             (throw 'trie--accumulate-done nil)))))
     ;; filter, maxnum, !resultfun
     ((and ,filter ,maxnum (not ,resultfun))
      (lambda (seq data)
@@ -1206,7 +1346,7 @@ element stored in the trie.)"
               (cons (cons seq data)
                     (aref trie--accumulate 0)))
         (and (>= (length (aref trie--accumulate 0)) ,maxnum)
-             (throw 'trie-accumulate--done nil)))))
+             (throw 'trie--accumulate-done nil)))))
     ;; filter, !maxnum, resultfun
     ((and ,filter (not ,maxnum) ,resultfun)
      (lambda (seq data)
@@ -1228,7 +1368,7 @@ element stored in the trie.)"
             (cons (funcall ,resultfun seq data)
                   (aref trie--accumulate 0)))
        (and (>= (length (aref trie--accumulate 0)) ,maxnum)
-           (throw 'trie-accumulate--done nil))))
+           (throw 'trie--accumulate-done nil))))
     ;; !filter, maxnum, !resultfun
     ((and (not ,filter) ,maxnum (not ,resultfun))
      (lambda (seq data)
@@ -1236,7 +1376,7 @@ element stored in the trie.)"
             (cons (cons seq data)
                   (aref trie--accumulate 0)))
        (and (>= (length (aref trie--accumulate 0)) ,maxnum)
-           (throw 'trie-accumulate--done nil))))
+           (throw 'trie--accumulate-done nil))))
     ;; !filter, !maxnum, resultfun
     ((and (not ,filter) (not ,maxnum) ,resultfun)
      (lambda (seq data)
@@ -1255,6 +1395,7 @@ element stored in the trie.)"
 
 (defmacro trie--construct-ranked-accumulator (maxnum filter)
   ;; Does what it says on the tin! | sed -e 's/tin/macro name/'
+  (declare (debug t))
   `(cond
     ;; filter, maxnum
     ((and ,filter ,maxnum)
@@ -1283,17 +1424,19 @@ element stored in the trie.)"
 
 (defmacro trie--accumulate-results
   (rankfun maxnum reverse filter resultfun accfun duplicates &rest body)
-  ;; Accumulate results of running BODY code, and return them in
-  ;; appropriate order. BODY should call ACCFUN to accumulate a result,
-  ;; passing it two arguments: a trie data node, and the corresponding
-  ;; sequence. BODY can throw 'trie-accumulate--done to terminate the
-  ;; accumulation and return the results. A non-null DUPLICATES flag
-  ;; signals that the accumulated results might contain duplicates,
-  ;; which should be deleted. Note that DUPLICATES is ignored if RANKFUN
-  ;; is null. The other arguments should be passed straight through from
-  ;; the query function.
+  (declare (debug t))
+  ;; Accumulate results of running BODY code, and return them in appropriate
+  ;; order. BODY should call ACCFUN to accumulate a result, passing it two
+  ;; arguments: a trie key and its associated data. BODY can throw
+  ;; trie--accumulate-done to terminate the accumulation and return the
+  ;; results. A non-null DUPLICATES flag signals that the accumulated results
+  ;; might contain duplicates, which should be deleted. Note that DUPLICATES
+  ;; is ignored if RANKFUN is null, and that duplicates *do* count towards
+  ;; MAXNUM. The remaining arguments have the usual meanings, and should be
+  ;; passed straight through from the query function's arguments.
 
   ;; rename functions to help avoid dynamic-scoping bugs
+  ;; FIXME: not needed with lexical scoping
   `(let* ((--trie-accumulate--rankfun ,rankfun)
          (--trie-accumulate--filter ,filter)
          (--trie-accumulate--resultfun ,resultfun)
@@ -1308,7 +1451,7 @@ element stored in the trie.)"
                    (not (funcall --trie-accumulate--rankfun a b))))
                (when ,maxnum (1+ ,maxnum)))
             (make-vector 1 nil)))
-         ;; construct function to accumulate completions
+         ;; construct function to accumulate results
          (,accfun
           (if ,rankfun
               (trie--construct-ranked-accumulator
@@ -1318,39 +1461,39 @@ element stored in the trie.)"
              --trie-accumulate--resultfun))))
 
      ;; accumulate results
-     (catch 'trie-accumulate--done ,@body)
+     (catch 'trie--accumulate-done ,@body)
 
-     ;; return list of completions
+     ;; return list of results
      (cond
-      ;; for a ranked query, extract completions from heap
+      ;; for a ranked query, extract results from heap
       (,rankfun
-       (let (completions)
+       (let (results)
         ;; check for and delete duplicates if flag is set
         (if ,duplicates
             (while (not (heap-empty trie--accumulate))
               (if (equal (car (heap-root trie--accumulate))
-                         (caar completions))
+                         (caar results))
                   (heap-delete-root trie--accumulate)
                 (push (heap-delete-root trie--accumulate)
-                      completions)))
+                      results)))
           ;; skip duplicate checking if flag is not set
           (while (not (heap-empty trie--accumulate))
             (if ,resultfun
                 (let ((res (heap-delete-root trie--accumulate)))
                   (push (funcall ,resultfun (car res) (cdr res))
-                        completions))
+                        results))
               (push (heap-delete-root trie--accumulate)
-                    completions))))
-        completions))
+                    results))))
+        results))
 
-      ;; for lexical query, reverse result list if MAXNUM supplied
+      ;; for lexicographic query, reverse result list if MAXNUM supplied
       (,maxnum (nreverse (aref trie--accumulate 0)))
       ;; otherwise, just return list
       (t (aref trie--accumulate 0)))))
 
 
 
-
+
 ;; ================================================================
 ;;                          Completing
 
@@ -1358,10 +1501,10 @@ element stored in the trie.)"
   (trie prefix &optional rankfun maxnum reverse filter resultfun)
   "Return an alist containing all completions of PREFIX in TRIE
 along with their associated data, in the order defined by
-RANKFUN, defaulting 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.
+RANKFUN, defaulting to \"lexicographic\" 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.
 
 PREFIX must be a sequence (vector, list or string) containing
 elements of the type used to reference data in the trie. (If
@@ -1404,7 +1547,7 @@ default key-data cons cell."
   (if (or (atom prefix)
          (and (listp prefix) (not (sequencep (car prefix)))))
       (setq prefix (list prefix))
-    ;; sort list of prefixes if sorting completions lexically
+    ;; sort list of prefixes if sorting completions lexicographicly
     (when (null rankfun)
       (setq prefix
            (sort prefix (trie-construct-sortfun
@@ -1412,7 +1555,6 @@ default key-data cons cell."
 
   ;; accumulate completions
   (let (node)
-    (declare (special accumulator))
     (trie--accumulate-results
      rankfun maxnum reverse filter resultfun accumulator nil
      (mapc (lambda (pfx)
@@ -1433,40 +1575,41 @@ default key-data cons cell."
   "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 TRIE's comparison function, or in reverse order if REVERSE is
-non-nil. Calling `trie-stack-pop' pops the top element (a key and
-its associated data) from the stack.
+The stack is sorted in \"lexicographic\" order, i.e. the order
+defined by TRIE's comparison function, or in reverse order if
+REVERSE is non-nil. Calling `trie-stack-pop' pops the top element
+\(a cons cell containing the next completion 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, or a list of such sequences. (If
+initial part of a TRIE key, or a list of such sequences. \(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.
+individual elements of TRIE keys.\) The completions returned by
+`trie-stack-pop' will be sequences of the same type as KEY. If
+PREFIX is a list of sequences, they must all be of the same
+type. In this case, completions of all sequences in the list are
+included in the stack.
 
 Note that any modification to TRIE *immediately* invalidates all
-trie-stacks created before the modification (in particular,
-calling `trie-stack-pop' will give unpredictable results).
+trie-stacks created before the modification \(in particular,
+calling `trie-stack-pop' will give unpredictable results\).
 
 Operations on trie-stacks are significantly more efficient than
 constructing a real stack from completions of PREFIX in TRIE and
 using standard stack functions. As such, they can be useful in
-implementing efficient algorithms on tries. However, in cases
-where `trie-complete' or `trie-complete-ordered' is sufficient,
-it is better to use one of those instead."
+implementing efficient algorithms over tries. However, in cases
+where `trie-complete' is sufficient, it is better to use that
+instead."
   ;; convert trie from print-form if necessary
   (trie-transform-from-read-warn trie)
   ;; if stack functions aren't defined for trie type, throw error
   (if (not (functionp (trie--stack-createfun trie)))
       (error "Trie type does not support stack operations")
     ;; otherwise, create and initialise a stack
-    (trie--completion-stack-create trie prefix reverse)))
+    (trie--complete-stack-create trie prefix reverse)))
 
 
-(defun trie--completion-stack-construct-store (trie prefix reverse)
+(defun trie--complete-stack-construct-store (trie prefix reverse)
   ;; Construct store for completion stack based on TRIE.
   (let (store node)
     (if (or (atom prefix)
@@ -1494,28 +1637,56 @@ it is better to use one of those instead."
      (trie--stack-emptyfun trie))))
 
 
+(heap--when-generators
+ (iter-defun trie-complete-iter (trie prefix &optional reverse)
+   "Return an iterator object for completions of PREFIX in TRIE.
+
+Calling `iter-next' on this object will retrieve the next
+completion \(a cons cell containing a completion and its
+associated data\) of PREFIX in the TRIE, in \"lexicographic\"
+order, i.e. the order defined by the trie's comparison function,
+or in reverse order if REVERSE is non-nil.
+
+PREFIX must be a sequence (vector, list or string) that forms the
+initial part of a TRIE key, or a list of such sequences. \(If
+PREFIX is a string, it must be possible to apply `string' to
+individual elements of TRIE keys.\) The completions returned by
+`iter-next' will be sequences of the same type as KEY. If PREFIX
+is a list of sequences, they must all be of the same type. In
+this case, the iterator yields completions of all sequences in
+the list.
+
+Note that any modification to TRIE *immediately* invalidates all
+iterators created from TRIE before the modification \(in
+particular, calling `iter-next' will give unpredictable
+results\)."
+   (let ((stack (trie-complete-stack trie prefix reverse)))
+     (while (not (trie-stack-empty-p stack))
+       (iter-yield (trie-stack-pop stack))))))
+
 
 
+
 ;; ================================================================
 ;;                        Regexp search
 
 (defun trie-regexp-search
-  (trie regexp &optional rankfun maxnum reverse filter resultfun type)
+  (trie regexp &optional rankfun maxnum reverse 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.
+RANKFUN, defaulting to \"lexicographic\" order \(i.e. the order
+defined by the trie's comparison function\). If REVERSE is
+non-nil, the results are sorted in the reverse order. Returns nil
+if no results are found.
 
 REGEXP is a regular expression, but it need not necessarily be a
-string. It must be a sequence (vector, list of string) whose
+string. It must be a sequence (vector, list, or 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.
+trie keys (which behave as literals in the regexp), or a regexp
+special character or backslash construct. 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 REGEXP.
 
 Only a subset of the full Emacs regular expression syntax is
 supported. There is no support for regexp constructs that are
@@ -1529,59 +1700,52 @@ 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
+each match is no longer just a key. Instead, each element of the
+results list has the form
+
+    ((KEY (START1 . END1) (START2 . END2) ...) . DATA)
+
+where the (START . END) cons cells give the start and end indices
 of the elements that matched the corresponding groups, in order.
 
+
 The optional integer argument MAXNUM limits the results to the
 first MAXNUM matches. Otherwise, all matches are returned.
 
-If specified, RANKFUN must accept two arguments, both cons
-cells. The car contains a sequence from the trie (of the same
-type as PREFIX), the cdr contains its associated data. It should
-return non-nil if first argument is ranked strictly higher than
-the second, nil otherwise.
+
+If specified, RANKFUN must accept two arguments. If the regexp
+does not contain any non-shy grouping constructs, both arguments
+are (KEY . DATA) cons cells, where the car is a sequence of the
+same type as REGEXP. If the regexp does contain non-shy grouping
+constructs, both arguments are of the form
+
+    ((KEY (START1 . END1) (START2 . END2) ...) . DATA)
+
+RANKFUN should return non-nil if first argument is ranked
+strictly higher than the second, nil otherwise.
+
 
 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.
+arguments: a key and its associated data. If the regexp contains
+non-shy grouping constructs, the first argument is of the form
+
+    (KEY (START1 . END1) (START2 . END2) ...)
+
+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. Its return
-value is what gets added to the final result list, instead of the
-default key-data cons cell."
+accept two arguments, of the same form as those for FILTER (see
+above). Its return value is what gets added to the final result
+list, instead of the default key-data cons cell."
 
   ;; convert trie from print-form if necessary
   (trie-transform-from-read-warn trie)
 
-  ;; massage rankfun to cope with grouping data
-  ;; FIXME: could skip this if REGEXP contains no grouping constructs
-  (when rankfun
-    (setq rankfun
-         `(lambda (a b)
-            ;; if car of argument contains a key+group list rather than
-            ;; a straight key, remove group list
-            ;; FIXME: the test for straight key, below, will fail if
-            ;;        the key is a list, and the first element of the
-            ;;        key is itself a list (there might be no easy way
-            ;;        to fully fix this...)
-            (unless (or (atom (car a))
-                        (and (listp (car a))
-                             (not (sequencep (caar a)))))
-              (setq a (cons (caar a) (cdr a))))
-            (unless (or (atom (car b))
-                        (and (listp (car b))
-                             (not (sequencep (caar b)))))
-              (setq b (cons (caar b) (cdr b))))
-            ;; call rankfun on massaged arguments
-            (,rankfun a b))))
-
-  ;; accumulate completions
-  (declare (special accumulator))
+  ;; accumulate results
   (trie--accumulate-results
    rankfun maxnum reverse filter resultfun accumulator nil
    (trie--do-regexp-search
@@ -1590,21 +1754,26 @@ default key-data cons cell."
                                    (trie--comparison-function trie)))
     (cond ((stringp regexp) "") ((listp regexp) ()) (t []))  0
     (or (and maxnum reverse) (and (not maxnum) (not reverse)))
+    ;; FIXME: Is this a case where it would pay to replace these arguments
+    ;;        with dynamically-scoped variables, to save stack space during
+    ;;        the recursive calls to `trie--do-regexp-search'?  Alternatively,
+    ;;        with lexical scoping, we could use a closure for
+    ;;        `trie--do-regexp-search' instead of a function.
     (trie--comparison-function trie)
     (trie--lookupfun trie)
-    (trie--mapfun trie))))
+    (trie--mapfun trie)
+    accumulator)))
 
 
 
 (defun trie--do-regexp-search
   (--trie--regexp-search--node tNFA seq pos reverse
-                              comparison-function lookupfun mapfun)
+                              cmpfun lookupfun mapfun accumulator)
   ;; Search everything below the node --TRIE--REGEXP-SEARCH-NODE for
   ;; matches to the regexp encoded in tNFA. SEQ is the sequence
   ;; corresponding to NODE, POS is it's length. REVERSE is the usual
   ;; query argument, and the remaining arguments are the corresponding
   ;; trie functions.
-  (declare (special accumulator))
 
   ;; if NFA has matched and we're accumulating in normal order, check if
   ;; trie contains current string
@@ -1628,7 +1797,7 @@ default key-data cons cell."
 
    ;; wildcard transition: map over all nodes in subtree
    ((tNFA-wildcard-p tNFA)
-    (let (state groups)
+    (let (state)
       (funcall mapfun
               (lambda (node)
                 (unless (trie--node-data-p node)
@@ -1642,24 +1811,30 @@ default key-data cons cell."
                     (trie--do-regexp-search
                      node state
                      (trie--seq-append seq (trie--node-split node))
-                     (1+ pos) reverse comparison-function
-                     lookupfun mapfun))))
+                     (1+ pos)
+                     reverse cmpfun lookupfun mapfun accumulator))))
               (trie--node-subtree --trie--regexp-search--node)
               reverse)))
 
    (t ;; no wildcard transition: loop over all transitions
-    (let (node state)
+    ;; rename function to mitigate against dynamic scoping bugs
+    ;; FIXME: not needed with lexical scoping
+    (let ((--trie--do-regexp-search--cmpfun cmpfun)
+         node state)
       (dolist (chr (sort (tNFA-transitions tNFA)
                         (if reverse
-                            `(lambda (a b) (,comparison-function b a))
-                          comparison-function)))
+                            (lambda (a b)
+                              (funcall
+                               --trie--do-regexp-search--cmpfun
+                               b a))
+                          cmpfun)))
        (when (and (setq node (trie--node-find
                               --trie--regexp-search--node
                               (vector chr) lookupfun))
                   (setq state (tNFA-next-state tNFA chr pos)))
          (trie--do-regexp-search
           node state (trie--seq-append seq chr) (1+ pos)
-          reverse comparison-function lookupfun mapfun))))))
+          reverse cmpfun lookupfun mapfun accumulator))))))
 
   ;; if NFA has matched and we're accumulating in reverse order, check if
   ;; trie contains current string
@@ -1674,23 +1849,27 @@ default key-data cons cell."
 
 
 
-(defun trie-regexp-stack  (trie regexp &optional reverse)
+(defun trie-regexp-stack (trie regexp &optional reverse)
   "Return an object that allows matches to REGEXP to be accessed
 as if they were a stack.
 
-The stack is sorted in \"lexical\" order, i.e. the order defined
-by TRIE's comparison function, or in reverse order if REVERSE is
-non-nil. Calling `trie-stack-pop' pops the top element (a cons
-cell containing a key and its associated data) from the stack.
+The stack is sorted in \"lexicographic\" order, i.e. the order
+defined by TRIE's comparison function, or in reverse order if
+REVERSE is non-nil. Calling `trie-stack-pop' pops the top element
+\(a cons cell containing 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
+string. It must be a sequence \(vector, list or string\) whose
+elements either have the same type as elements of the trie keys
+\(which behave as literals in the regexp\), or are any of the
+usual regexp special characters \(character type\) or backslash
+constructs \(string type\).
+
+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.
+returned by `trie-stack-pop' 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 `^'
@@ -1698,7 +1877,7 @@ 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 (as returned by a call to `trie-stack-pop' is no
+each match \(as returned by a call to `trie-stack-pop'\) 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
@@ -1773,16 +1952,22 @@ elements that matched the corresponding groups, in 
order."
                    store))
 
             (t ;; non-wildcard transition: add all possible next nodes
-             (dolist (chr (sort (tNFA-transitions state)
-                                (if reverse
-                                    comparison-function
-                                  `(lambda (a b)
-                                     (,comparison-function b a)))))
-               (when (and (setq n (trie--node-find
-                                   node (vector chr) lookupfun))
-                          (setq s (tNFA-next-state state chr pos)))
-                 (push (list (trie--seq-append seq chr) n s (1+ pos))
-                       store)))
+             ;; rename function to mitigate against lexical scoping bugs
+             ;; FIXME: not needed with lexical scoping
+             (let ((--trie--regexp-stack-repopulate--cmpfun
+                    comparison-function))
+               (dolist (chr (sort (tNFA-transitions state)
+                                  (if reverse
+                                      --trie--regexp-stack-repopulate--cmpfun
+                                  (lambda (a b)
+                                     (funcall
+                                      --trie--regexp-stack-repopulate--cmpfun
+                                      b a)))))
+                 (when (and (setq n (trie--node-find
+                                     node (vector chr) lookupfun))
+                            (setq s (tNFA-next-state state chr pos)))
+                   (push (list (trie--seq-append seq chr) n s (1+ pos))
+                         store))))
              t)))  ; return t to keep looping
 
           ;; otherwise, stack element is a node stack...
@@ -1813,7 +1998,713 @@ elements that matched the corresponding groups, in 
order."
   store)
 
 
+(heap--when-generators
+ (iter-defun trie-regexp-iter (trie regexp &optional reverse)
+   "Return an iterator object for REGEXP matches in TRIE.
+
+Calling `iter-next' on this object will retrieve the next match
+\(a cons cell containing a key and its associated data\) for
+REGEXP in the TRIE, in \"lexicographic\" order, i.e. the order
+defined by the trie's comparison function, or in reverse order if
+REVERSE is non-nil.
+
+REGEXP is a regular expression, but it need not necessarily be a
+string. It must be a sequence \(vector, list or string\) whose
+elements either have the same type as elements of the trie keys
+\(which behave as literals in the regexp\), or are any of the
+usual regexp special characters \(character type\) or backslash
+constructs \(string type\).
+
+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 by `iter-next' 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 \(as returned by a call to `iter-next'\) 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 TRIE *immediately* invalidates all
+iterators created from TRIE before the modification \(in
+particular, calling `iter-next' will give unpredictable
+results\)."
+   (let ((stack (trie-regexp-stack trie regexp reverse)))
+     (while (not (trie-stack-empty-p stack))
+       (iter-yield (trie-stack-pop stack))))))
+
+
+
+
+;; ================================================================
+;;                        Fuzzy matching
+
+
+;; Basic Lewenstein distance (edit distance) functions
+;; ---------------------------------------------------
+
+(defun* Lewenstein-distance (str1 str2 &key (test 'equal))
+  "Return the Lewenstein distance between strings STR1 and STR2
+\(a.k.a. edit distance\).
 
+The Lewenstein distance is the minimum number of single-character
+insertions, deletions or substitutions required to transform STR1
+into STR2.
+
+More generally, STR1 and STR2 can be sequences of elements all of
+the same type. The optional keyword argument :test specifies the
+function to use to test equality of sequence elements, defaulting
+to `equal'."
+  (let ((row (apply #'vector (number-sequence 0 (length str2)))))
+    (dotimes (i (length str1))
+      (setq row (Lewenstein--next-row row str2 (elt str1 i) test)))
+    (aref row (1- (length row)))))
+
+
+(defalias 'edit-distance 'Lewenstein-distance)
+
+
+(defun Lewenstein--next-row (row string chr equalfun)
+  ;; Compute next row of Lewenstein distance matrix.
+  (let ((next-row (make-vector (length row) nil))
+       (i 0) inscost delcost subcost)
+    (aset next-row 0 (1+ (aref row 0)))
+    (while (< (incf i) (length row))
+      (setq inscost (1+ (aref next-row (1- i)))
+           delcost (1+ (aref row i))
+           subcost (if (funcall equalfun chr (elt string (1- i)))
+                       (aref row (1- i))
+                     (1+ (aref row (1- i)))))
+      (aset next-row i (min inscost delcost subcost)))
+    next-row))
+
+
+
+;; Implementation Note
+;; -------------------
+;; The standard dynamical-programming solution to computing Lewenstein
+;; distance constructs a table of Lewenstein distances to successive prefixes
+;; of the target string, row-by-row. Our trie search algorithms are based on
+;; constructing the next row of this table as we (recursively) descend the
+;; trie. Since the each row only depends on entries in the previous row, we
+;; only need to pass a single row of the table down the recursion stack. (A
+;; nice description of this algorithm can be found at
+;; http://stevehanov.ca/blog/index.php?id=114.)
+;;
+;; I haven't benchmarked this (let me know the results if you do!), but it
+;; seems clear that this algorithm will be much faster than constructing a
+;; Lewenstein automata and stepping through it as we descend the trie
+;; (similarly to regexp searches, cf. `trie-regexp-match'.)
+
+
+(defun trie-fuzzy-match
+  (trie string distance &optional rankfun maxnum reverse filter resultfun)
+  "Return matches for STRING in TRIE within Lewenstein DISTANCE
+\(edit distance\) of STRING along with their associated data, in
+the order defined by RANKFUN, defaulting to \"lexicographic\"
+order \(i.e. the order defined by the trie's comparison
+function\). If REVERSE is non-nil, the results are sorted in the
+reverse order. Returns nil if no results are found.
+
+Returns a list of matches, with elements of the form:
+
+    ((KEY . DIST) . DATA)
+
+where KEY is a matching key from the trie, DATA its associated
+data, and DIST is its Lewenstein distance \(edit distance\) from
+STRING.
+
+STRING is a sequence (vector, list or string), whose elements are
+of the same type as elements of the trie keys. If STRING is a
+string, it must be possible to apply `string' to individual
+elements of the keys stored in the trie. The KEYs returned in the
+list will be sequences of the same type as STRING.
+
+DISTANCE must be a positive integer. (Note that DISTANCE=0 will
+not give meaningful results; use `trie-member' instead.)
+
+
+RANKFUN overrides the default ordering of the results. If it is t,
+matches are instead ordered by increasing Lewenstein distance
+\(with same-distance matches ordered lexicographically\).
+
+If RANKFUN is a function, it must accept two arguments, both of
+the form:
+
+    ((KEY . DIST) . DATA)
+
+where KEY is a key from the trie, DIST is its Lewenstein
+distances from STRING, and DATA is its associated data. RANKFUN
+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. Otherwise, all matches are returned.
+
+The FILTER argument sets a filter function for the matches. If
+supplied, it is called for each possible match with two
+arguments: a (KEY . DIST) cons cell, and 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 . DIST) cons cell, and DATA. Its
+return value is what gets added to the final result list, instead
+of the default key-dist-data list."
+
+  ;; convert trie from print-form if necessary
+  (trie-transform-from-read-warn trie)
+
+  ;; construct rankfun to sort by Lewenstein distance if requested
+  (when (eq rankfun t)
+    (setq rankfun (trie--construct-Lewenstein-rankfun
+                  (trie--comparison-function trie))))
+
+  ;; accumulate results
+  (trie--accumulate-results
+   rankfun maxnum reverse filter resultfun accumulator nil
+   (funcall (trie--mapfun trie)
+           (lambda (node)
+             (trie--do-fuzzy-match
+              node
+              (apply #'vector (number-sequence 0 (length string)))
+              (cond ((stringp string) "") ((listp string) ()) (t []))
+              ;; FIXME: Would it pay to replace these arguments with
+              ;;        dynamically-scoped variables, to save stack space?
+              string distance (if maxnum reverse (not reverse))
+              (trie--comparison-function trie)
+              (trie--construct-equality-function
+               (trie--comparison-function trie))
+              (trie--lookupfun trie)
+              (trie--mapfun trie)
+              accumulator))
+           (trie--node-subtree (trie--root trie))
+           (if maxnum reverse (not reverse)))))
+
+
+(defun trie--do-fuzzy-match (node row seq string distance reverse
+                            cmpfun equalfun lookupfun mapfun accumulator)
+  ;; Search everything below NODE for matches within Lewenstein distance
+  ;; DISTANCE of STRING. ROW is the previous row of the Lewenstein table. SEQ
+  ;; is the sequence corresponding to NODE. If COMPLETE is non-nil, return
+  ;; completions of matches, otherwise return matches themselves. Remaining
+  ;; arguments are corresponding trie functions.
+
+  ;; if we're at a data node and SEQ is within DISTANCE of STRING (i.e. last
+  ;; entry of row is <= DISTANCE), accumulate result
+  (if (trie--node-data-p node)
+      (when (<= (aref row (1- (length row))) distance)
+       (funcall accumulator
+                (cons seq (aref row (1- (length row))))
+                (trie--node-data node)))
+
+    ;; build next row of Lewenstein table
+    (setq row (Lewenstein--next-row
+              row string (trie--node-split node) equalfun)
+         seq (trie--seq-append seq (trie--node-split node)))
+
+    ;; as long as some row entry is <= DISTANCE, recursively search below NODE
+    (when (<= (apply #'min (append row nil)) distance)
+      (funcall mapfun
+              (lambda (n)
+                (trie--do-fuzzy-match
+                 n row seq string distance reverse
+                 cmpfun equalfun lookupfun mapfun accumulator))
+              (trie--node-subtree node)
+              reverse))))
+
+
+
+(defun trie-fuzzy-match-stack (trie string distance &optional reverse)
+  "Return an object that allows fuzzy matches to be accessed
+as if they were a stack.
+
+The stack is sorted in \"lexicographic\" order, i.e. the order
+defined by TRIE's comparison function, or in reverse order if
+REVERSE is non-nil. Calling `trie-stack-pop' pops the top element
+from the stack. Each stack element has the form:
+
+    ((KEY . DIST) . DATA)
+
+where KEY is a matching key from the trie, DATA its associated
+data, and DIST is its Lewenstein distance \(edit distance\) from
+STRING.
+
+STRING is a sequence (vector, list or string), whose elements are
+of the same type as elements of the trie keys. If STRING is a
+string, it must be possible to apply `string' to individual
+elements of the keys stored in the trie. The KEYs in the matches
+returned by `trie-stack-pop' will be sequences of the same type
+as STRING.
+
+DISTANCE is a positive integer. The fuzzy matches in the stack
+will be within Lewenstein distance \(edit distance\) DISTANCE of
+STRING."
+  ;; convert trie from print-form if necessary
+  (trie-transform-from-read-warn trie)
+  ;; if stack functions aren't defined for trie type, throw error
+  (cond
+   ((not (functionp (trie--stack-createfun trie)))
+    (error "Trie type does not support stack operations"))
+   ;; fuzzy-match-stacks don't work for distance=0; return a `trie-stack'
+   ;; instead
+   ((= distance 0)
+    (trie--stack-create trie string reverse))
+   (t ;; otherwise, create and initialise a fuzzy match stack
+    (trie--fuzzy-match-stack-create trie string distance reverse))))
+
+
+(defun trie--fuzzy-match-stack-construct-store
+    (trie string distance &optional reverse)
+  ;; Construct store for fuzzy stack based on TRIE.
+  (let ((seq (cond ((stringp string) "") ((listp string) ()) (t [])))
+       store)
+    (push (list seq
+               (funcall (trie--stack-createfun trie)
+                        (trie--node-subtree (trie--root trie))
+                        reverse)
+               string distance
+               (apply #'vector (number-sequence 0 (length string))))
+         store)
+    (trie--fuzzy-match-stack-repopulate
+     store reverse
+     (trie--comparison-function trie)
+     (trie--lookupfun trie)
+     (trie--stack-createfun trie)
+     (trie--stack-popfun trie)
+     (trie--stack-emptyfun trie))))
+
+
+(defun trie--fuzzy-match-stack-repopulate
+  (store reverse comparison-function _lookupfun
+        stack-createfun stack-popfun stack-emptyfun)
+  ;; Recursively push matching children of the node at the head of STORE
+  ;; onto STORE, until a data node is reached. REVERSE is the usual
+  ;; query argument, and the remaining arguments are the corresponding
+  ;; trie functions.
+
+  (when store
+    (let ((equalfun (trie--construct-equality-function comparison-function))
+         nextrow)
+
+      (destructuring-bind (seq node string distance row) (car store)
+       (setq node (funcall stack-popfun node))
+       (when (funcall stack-emptyfun (nth 1 (car store)))
+         ;; using (pop store) here produces irritating compiler warnings
+         (setq store (cdr store)))
+
+       ;; push children of node at head of store that are within DISTANCE of
+       ;; STRING, until we find a data node where entire SEQ is within
+       ;; DISTANCE of STRING (i.e. last entry of row is <= DISTANCE)
+       (while (and node
+                   (not (and (trie--node-data-p node)
+                             (<= (aref row (1- (length row))) distance))))
+         ;; drop data nodes whose SEQ is greater than DISTANCE
+         (unless (trie--node-data-p node)
+           (setq nextrow (Lewenstein--next-row
+                          row string (trie--node-split node) equalfun))
+           ;; push children of non-data nodes whose SEQ is less than DISTANCE
+           ;; onto stack
+           (when (<= (apply #'min (append row nil)) distance)
+             (push
+              (list (trie--seq-append seq (trie--node-split node))
+                    (funcall stack-createfun
+                             (trie--node-subtree node) reverse)
+                    string distance nextrow)
+              store)))
+         ;; get next node from stack
+         (when (setq node (car store))
+           (setq seq (nth 0 node)
+                 string (nth 2 node)
+                 distance (nth 3 node)
+                 row (nth 4 node)
+                 node (funcall stack-popfun (nth 1 node)))
+           ;; drop head of stack if nodes are exhausted
+           (when (funcall stack-emptyfun (nth 1 (car store)))
+             (setq store (cdr store)))))
+
+       ;; push next fuzzy match onto head of stack
+       (when node
+         (push (cons (cons seq (aref row (1- (length row))))
+                     (trie--node-data node))
+               store))))))
+
+
+(heap--when-generators
+ (iter-defun trie-fuzzy-match-iter (trie string distance &optional reverse)
+   "Return an iterator object for fuzzy matches to STRING in TRIE.
+
+Calling `iter-next' on this object will return the next match
+within DISTANCE of STRING in TRIE, in \"lexicographic\" order,
+i.e. the order defined by the trie's comparison function, or in
+reverse order if REVERSE is non-nil. Each returned element has
+the form:
+
+    ((KEY . DIST) . DATA)
+
+where KEY is a matching key from the trie, DATA its associated
+data, and DIST is its Lewenstein distance \(edit distance\) from
+STRING.
+
+STRING is a sequence (vector, list or string) whose elements are
+of the same type as elements of the trie keys. If STRING is a
+string, it must be possible to apply `string' to individual
+elements of the keys stored in the trie. The KEYs in the matches
+returned by `iter-next' will be sequences of the same type as
+STRING.
+
+DISTANCE is a positive integer. The fuzzy matches in the stack
+will be within Lewenstein distance \(edit distance\) DISTANCE of
+STRING.
+
+Note that any modification to TRIE *immediately* invalidates all
+iterators created from TRIE before the modification \(in
+particular, calling `iter-next' will give unpredictable
+results\)."
+   (let ((stack (trie-fuzzy-match-stack trie string distance reverse)))
+     (while (not (trie-stack-empty-p stack))
+       (iter-yield (trie-stack-pop stack))))))
+
+
+
+
+;; ================================================================
+;;                        Fuzzy completing
+
+(defun trie-fuzzy-complete
+  (trie prefix distance &optional rankfun maxnum reverse filter resultfun)
+  "Return completions of prefixes within Lewenstein DISTANCE of PREFIX
+along with their associated data, in the order defined by
+RANKFUN, defaulting to \"lexicographic\" order \(i.e. the order
+defined by the trie's comparison function\). If REVERSE is
+non-nil, the results are sorted in the reverse order. Returns nil
+if no results are found.
+
+Returns a list of completions, with elements of the form:
+
+    ((KEY DIST PFXLEN) . DATA)
+
+where KEY is a matching completion from the trie, DATA its
+associated data, PFXLEN is the length of the prefix part of KEY,
+and DIST is its Lewenstein distance \(edit distance\) from
+PREFIX.
+
+PREFIX is a sequence (vector, list or string), whose elements are
+of the same type as elements of the trie keys. If PREFIX is a
+string, it must be possible to apply `string' to individual
+elements of the keys stored in the trie. The KEYs returned in the
+list will be sequences of the same type as PREFIX.
+
+DISTANCE must be a positive integer. (Note that DISTANCE=0 will
+not give meaningful results; use `trie-complete' instead.)
+
+The optional integer argument MAXNUM limits the results to the
+first MAXNUM matches. Otherwise, all matches are returned.
+
+
+RANKFUN overrides the default ordering of the results. If it is t,
+matches are instead ordered by increasing Lewenstein distance of
+their prefix \(with same-distance prefixes ordered
+lexicographically\).
+
+If RANKFUN is a function, it must accept two arguments, both of
+the form:
+
+    ((KEY DIST PFXLEN) . DATA)
+
+where KEY is a key from the trie, DIST is its Lewenstein
+distances from PREFIX, and DATA is its associated data. RANKFUN
+should return non-nil if first argument is ranked strictly higher
+than the second, nil otherwise.
+
+
+The FILTER argument sets a filter function for the matches. If
+supplied, it is called for each possible match with two
+arguments: a (KEY DIST PFXLEN) list, and 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 DIST PFXLEN) list, and DATA. Its
+return value is what gets added to the final result list, instead
+of the default key-dist-data list."
+
+  ;; convert trie from print-form if necessary
+  (trie-transform-from-read-warn trie)
+
+  ;; construct rankfun to sort by Lewenstein distance if requested
+  (when (eq rankfun t)
+    (setq rankfun (trie--construct-Lewenstein-rankfun
+                  (trie--comparison-function trie))))
+
+  ;; accumulate results
+  (trie--accumulate-results
+   rankfun maxnum reverse filter resultfun accumulator nil
+   (funcall (trie--mapfun trie)
+           (lambda (node)
+             (trie--do-fuzzy-complete
+              node
+              (apply #'vector (number-sequence 0 (length prefix)))
+              (cond ((stringp prefix) "") ((listp prefix) ()) (t []))
+              (length prefix) 0
+              ;; FIXME: Would it pay to replace these arguments with
+              ;;        dynamically-scoped variables, to save stack space?
+              prefix distance (if maxnum reverse (not reverse))
+              (trie--comparison-function trie)
+              (trie--construct-equality-function
+               (trie--comparison-function trie))
+              (trie--lookupfun trie)
+              (trie--mapfun trie)
+              accumulator))
+           (trie--node-subtree (trie--root trie))
+           (if maxnum reverse (not reverse)))))
+
+
+(defun trie--do-fuzzy-complete (node row seq pfxcost pfxlen
+                               prefix distance reverse
+                               cmpfun equalfun lookupfun mapfun accumulator)
+  ;; Search everything below NODE for completions of prefixes within
+  ;; Lewenstein distance DISTANCE of PREFIX. ROW is the previous row of the
+  ;; Lewenstein table. SEQ is the sequence corresponding to NODE. PFXCOST is
+  ;; minimum distance of any prefix of seq. Remaining arguments are
+  ;; corresponding trie functions.
+
+  ;; if we're at a data node and SEQ is within DISTANCE of PREFIX (i.e. last
+  ;; entry of row is <= DISTANCE), accumulate result
+  (if (trie--node-data-p node)
+      (when (<= (aref row (1- (length row))) distance)
+       (funcall accumulator
+                (list seq (aref row (1- (length row))) (length seq))
+                (trie--node-data node)))
+
+    ;; build next row of Lewenstein table
+    (setq row (Lewenstein--next-row
+              row prefix (trie--node-split node) equalfun)
+         seq (trie--seq-append seq (trie--node-split node)))
+    (when (<= (aref row (1- (length row))) pfxcost)
+      (setq pfxcost (aref row (1- (length row)))
+           pfxlen (length seq)))
+
+    ;; as long as some row entry is < DISTANCE, recursively search below NODE
+    (if (<= (apply #'min (append row nil)) distance)
+       (funcall mapfun
+                (lambda (n)
+                  (trie--do-fuzzy-complete
+                   n row seq pfxcost pfxlen prefix distance reverse
+                   cmpfun equalfun lookupfun mapfun accumulator))
+                (trie--node-subtree node)
+                reverse)
+
+      ;; otherwise, if we've found a prefix within DISTANCE of PREFIX,
+      ;; accumulate all completions below node
+      (when (<= pfxcost distance)
+       (trie--mapc
+        (lambda (n s)
+          (funcall accumulator (list s pfxcost pfxlen) (trie--node-data n)))
+        mapfun node seq reverse))
+      )))
+
+
+
+(defun trie-fuzzy-complete-stack (trie prefix distance &optional reverse)
+  "Return an object that allows fuzzy completions to be accessed
+as if they were a stack.
+
+The stack is sorted in \"lexicographic\" order, i.e. the order
+defined by TRIE's comparison function, or in reverse order if
+REVERSE is non-nil. Calling `trie-stack-pop' pops the top element
+from the stack. Each stack element has the form:
+
+    ((KEY DIST PFXLEN) . DATA)
+
+where KEY is a matching completion from the trie, DATA its
+associated data, PFXLEN is the length of the prefix part of KEY,
+and DIST is the Lewenstein distance \(edit distance\) from PREFIX
+of the prefix whose completion is KEY.
+
+PREFIX is a sequence (vector, list or string), whose elements are
+of the same type as elements of the trie keys. If PREFIX is a
+string, it must be possible to apply `string' to individual
+elements of the keys stored in the trie. The KEYs in the stack
+elements will be sequences of the same type as PREFIX.
+
+DISTANCE is a positive integer. The fuzzy completions in the
+stack will have prefixes within Lewenstein distance \(edit
+distance\) DISTANCE of PREFIX. (Note that DISTANCE=0 will not
+give meaningful results; use `trie-complete-stack' instead.)"
+  ;; convert trie from print-form if necessary
+  (trie-transform-from-read-warn trie)
+  (cond
+   ;; if stack functions aren't defined for trie type, throw error
+   ((not (functionp (trie--stack-createfun trie)))
+    (error "Trie type does not support stack/iterator operations"))
+   ;; fuzzy-complete-stacks don't work for distance=0; return
+   ;; a `trie-complete-stack' instead
+   ((= distance 0)
+    (trie--complete-stack-create trie prefix reverse))
+   (t ;; otherwise, create and initialise a fuzzy stack
+    (trie--fuzzy-complete-stack-create trie prefix distance reverse))))
+
+
+(defun trie--fuzzy-complete-stack-construct-store
+    (trie prefix distance &optional reverse)
+  ;; Construct store for fuzzy completion stack based on TRIE.
+  (let ((seq (cond ((stringp prefix) "") ((listp prefix) ()) (t [])))
+       store)
+    (push (list seq
+               (funcall (trie--stack-createfun trie)
+                        (trie--node-subtree (trie--root trie))
+                        reverse)  ; node
+               prefix distance
+               (apply #'vector (number-sequence 0 (length prefix)))  ; row
+               (length prefix) 0)  ; pfxcost pfxlen
+         store)
+    (trie--fuzzy-complete-stack-repopulate
+     store reverse
+     (trie--comparison-function trie)
+     (trie--lookupfun trie)
+     (trie--stack-createfun trie)
+     (trie--stack-popfun trie)
+     (trie--stack-emptyfun trie))))
+
+
+(defun trie--fuzzy-complete-stack-repopulate
+  (store reverse comparison-function _lookupfun
+        stack-createfun stack-popfun stack-emptyfun)
+  ;; Recursively push matching children of the node at the head of STORE
+  ;; onto STORE, until a data node is reached. REVERSE is the usual
+  ;; query argument, and the remaining arguments are the corresponding
+  ;; trie functions.
+
+  (when store
+    (let ((equalfun (trie--construct-equality-function comparison-function)))
+
+      (destructuring-bind (seq node prefix distance row pfxcost pfxlen)
+         (car store)
+       (setq node (funcall stack-popfun node))
+       (when (funcall stack-emptyfun (nth 1 (car store)))
+         ;; using (pop store) here produces irritating compiler warnings
+         (setq store (cdr store)))
+
+       ;; push children of node at head of store that are within DISTANCE of
+       ;; PREFIX, until we either find a data node whose entire SEQ is within
+       ;; DISTANCE of PREFIX (i.e. last entry of row is <= DISTANCE), or
+       ;; we've found a prefix within DISTANCE of PREFIX and are gathering
+       ;; all its completions
+       (while (and node
+                   (not (and (trie--node-data-p node)
+                             (or (eq distance t)  ; completing a prefix
+                                 (<= (aref row (1- (length row))) distance))
+                             )))
+          ;; drop data nodes whose SEQ is greater than DISTANCE
+         (unless (trie--node-data-p node)
+           ;; build next row of Lewenstein table
+           (setq row (Lewenstein--next-row
+                      row prefix (trie--node-split node) equalfun)
+                 seq (trie--seq-append seq (trie--node-split node)))
+           (when (<= (aref row (1- (length row))) pfxcost)
+             (setq pfxcost (aref row (1- (length row)))
+                   pfxlen (length seq)))
+
+           (cond
+            ;; if we're completing a prefix, always push next node onto stack
+            ((eq distance t)
+             (push
+              (list seq
+                    (funcall stack-createfun
+                             (trie--node-subtree node) reverse)
+                    prefix t row pfxcost pfxlen)
+              store))
+
+            ;; if we've found a prefix within DISTANCE of PREFIX, then
+            ;; everything below node belongs on stack
+            ((<= (aref row (1- (length row))) distance)
+             (push
+              (list seq
+                    (funcall stack-createfun
+                             (trie--node-subtree node) reverse)
+                    ;; t in distance slot indicates completing
+                    prefix t row pfxcost pfxlen)
+              store))
+
+            ;; if some row entry for non-data node is <= DISTANCE, push node
+            ;; onto stack
+            ((<= (apply #'min (append row nil)) distance)
+             (push
+              (list seq
+                    (funcall stack-createfun
+                             (trie--node-subtree node) reverse)
+                    prefix distance row pfxcost pfxlen)
+              store))))
+
+         ;; get next node from stack
+         (when (setq node (car store))
+           (setq seq (nth 0 node)
+                 prefix (nth 2 node)
+                 distance (nth 3 node)
+                 row (nth 4 node)
+                 node (funcall stack-popfun (nth 1 node)))
+           ;; drop head of stack if nodes are exhausted
+           (when (funcall stack-emptyfun (nth 1 (car store)))
+             (setq store (cdr store)))))
+
+
+       ;; push next fuzzy completion onto head of stack
+       (when node
+         (push (cons (list seq pfxcost pfxlen) (trie--node-data node))
+               store))))))
+
+
+(heap--when-generators
+ (iter-defun trie-fuzzy-complete-iter (trie prefix distance &optional reverse)
+   "Return an iterator object for fuzzy matches of STRING in TRIE.
+
+Calling `iter-next' on this object will return the next match
+within DISTANCE of STRING in TRIE, in \"lexicographic\" order,
+i.e. the order defined by the trie's comparison function, or in
+reverse order if REVERSE is non-nil. Each returned element has
+the form:
+
+    ((KEY DIST PFXLEN) . DATA)
+
+where KEY is a matching completion from the trie, DATA its
+associated data, PFXLEN is the length of the prefix part of KEY,
+and DIST is the Lewenstein distance \(edit distance\) of that
+prefix part from PREFIX
+
+PREFIX is a sequence (vector, list or string), whose elements are
+of the same type as elements of the trie keys. If PREFIX is a
+string, it must be possible to apply `string' to individual
+elements of the keys stored in the trie. The KEYs in the elements
+returned by `iter-next' will be sequences of the same type as
+PREFIX.
+
+DISTANCE is a positive integer. The fuzzy completions returned by
+`iter-next' will have prefixes within Lewenstein distance \(edit
+distance\) DISTANCE of PREFIX.
+
+Note that any modification to TRIE *immediately* invalidates all
+iterators created from TRIE before the modification \(in
+particular, calling `iter-next' will give unpredictable
+results\)."
+   (let ((stack (trie-fuzzy-complete-stack trie prefix distance reverse)))
+     (while (not (trie-stack-empty-p stack))
+       (iter-yield (trie-stack-pop stack))))))
+
+
+
+
+
 ;; ----------------------------------------------------------------
 ;;            Pretty-print tries during edebug
 
@@ -1838,15 +2729,16 @@ elements that matched the corresponding groups, in 
order."
 ;; print tries in full whilst edebugging, despite this warning, disable
 ;; the advice.
 ;;
-;; FIXME: We could use `cedet-edebug-prin1-extensions' instead of advice
-;;        when `cedet-edebug' is loaded, though I believe the current
-;;        implementation still works in that case.
+;; FIXME: We should probably use the `cust-print' features instead of advice
+;; here.
 
 
 (eval-when-compile
   (require 'edebug)
   (require 'advice))
 
+(defun trie--prin1 (_trie stream)
+  (princ "#<trie>" stream))
 
 (defun trie--edebug-pretty-print (object)
   (cond
@@ -1857,7 +2749,7 @@ elements that matched the corresponding groups, in order."
                 (and tlist (setq test nil)))
        (setq tlist (cdr tlist)))
       test)
-    (concat "(" (mapconcat (lambda (dummy) "#<trie>") object " ") ")"))
+    (concat "(" (mapconcat (lambda (_dummy) "#<trie>") object " ") ")"))
 ;; ((vectorp object)
 ;;  (let ((pretty "[") (len (length object)))
 ;;    (dotimes (i (1- len))
@@ -1871,42 +2763,31 @@ elements that matched the corresponding groups, in 
order."
 ;;           "]")))
    ))
 
-(defun trie--edebug-prin1 (orig object &optional printcharfun)
-  (let ((pretty (trie--edebug-pretty-print object)))
-    (if pretty
-       (progn
-         (prin1 pretty printcharfun)
-          pretty)
-      (funcall orig object printcharfun))))
-
-(defun trie--edebug-prin1-to-string (orig object &optional noescape)
-  (or (trie--edebug-pretty-print object)
-      (funcall orig object noescape)))
-
-(if (fboundp 'advice-add)
-    (progn
-      (advice-add 'edebug-prin1 :around #'trie--edebug-prin1)
-      (advice-add 'edebug-prin1-to-string
-                  :around #'trie--edebug-prin1-to-string))
+(if (fboundp 'cl-print-object)
+    (cl-defmethod cl-print-object ((object trie-) stream)
+      (trie--prin1 object stream))
 
   (when (fboundp 'ad-define-subr-args)
     (ad-define-subr-args 'edebug-prin1 '(object &optional printcharfun)))
 
   (defadvice edebug-prin1
       (around trie activate compile preactivate)
-    (setq ad-return-value
-          (trie--edebug-prin1 (lambda (object printcharfun) ad-do-it)
-                              object printcharfun)))
+    (let ((pretty (trie--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 trie activate compile preactivate)
-    (setq ad-return-value
-          (trie--edebug-prin1-to-string (lambda (object noescape) ad-do-it)
-                                        object noescape))))
-
+    (let ((pretty (trie--edebug-pretty-print object)))
+      (if pretty
+         (setq ad-return-value pretty)
+        ad-do-it))))
 
 
 (provide 'trie)



reply via email to

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