(eval-when-compile
(require 'cl))
(defgroup decipher nil
"Cryptanalyze monoalphabetic substitution ciphers."
:prefix "decipher-"
:group 'games)
(defcustom decipher-force-uppercase t
"*Non-nil means to convert ciphertext to uppercase.
Nil means the case of the ciphertext is preserved.
This variable must be set before typing `\\[decipher]'."
:type 'boolean
:group 'decipher)
(defcustom decipher-ignore-spaces nil
"*Non-nil means to ignore spaces and punctuation when counting digrams.
You should set this to `nil' if the cipher message is divided into words,
or `t' if it is not.
This variable is buffer-local."
:type 'boolean
:group 'decipher)
(make-variable-buffer-local 'decipher-ignore-spaces)
(defcustom decipher-undo-limit 5000
"The maximum number of entries in the undo list.
When the undo list exceeds this number, 100 entries are deleted from
the tail of the list."
:type 'integer
:group 'decipher)
(defcustom decipher-mode-hook nil
"Hook to run upon entry to decipher."
:type 'hook
:group 'decipher)
(defvar decipher-font-lock-keywords
'(("^:.*" . font-lock-keyword-face)
("^>.*" . font-lock-string-face)
("^%!.*" . font-lock-constant-face)
("^%.*" . font-lock-comment-face)
("\\`(\\([a-z]+\\) +\\([A-Z]+\\)"
(1 font-lock-string-face)
(2 font-lock-keyword-face))
("^)\\([A-Z ]+\\)\\([a-z ]+\\)"
(1 font-lock-keyword-face)
(2 font-lock-string-face)))
"Expressions to fontify in Decipher mode.
Ciphertext uses `font-lock-keyword-face', plaintext uses
`font-lock-string-face', comments use `font-lock-comment-face', and
checkpoints use `font-lock-constant-face'. You can customize the
display by changing these variables. For best results, I recommend
that all faces use the same background color.
For example, to display ciphertext in the `bold' face, use
(add-hook 'decipher-mode-hook
(lambda () (set (make-local-variable 'font-lock-keyword-face)
'bold)))
in your `.emacs' file.")
(defvar decipher-mode-map nil
"Keymap for Decipher mode.")
(if (not decipher-mode-map)
(progn
(setq decipher-mode-map (make-keymap))
(suppress-keymap decipher-mode-map)
(define-key decipher-mode-map "A" 'decipher-show-alphabet)
(define-key decipher-mode-map "C" 'decipher-complete-alphabet)
(define-key decipher-mode-map "D" 'decipher-digram-list)
(define-key decipher-mode-map "F" 'decipher-frequency-count)
(define-key decipher-mode-map "M" 'decipher-make-checkpoint)
(define-key decipher-mode-map "N" 'decipher-adjacency-list)
(define-key decipher-mode-map "R" 'decipher-restore-checkpoint)
(define-key decipher-mode-map "U" 'decipher-undo)
(define-key decipher-mode-map " " 'decipher-keypress)
(substitute-key-definition 'undo 'decipher-undo
decipher-mode-map global-map)
(substitute-key-definition 'advertised-undo 'decipher-undo
decipher-mode-map global-map)
(let ((key ?a))
(while (<= key ?z)
(define-key decipher-mode-map (vector key) 'decipher-keypress)
(incf key)))))
(defvar decipher-stats-mode-map nil
"Keymap for Decipher-Stats mode.")
(if (not decipher-stats-mode-map)
(progn
(setq decipher-stats-mode-map (make-keymap))
(suppress-keymap decipher-stats-mode-map)
(define-key decipher-stats-mode-map "D" 'decipher-digram-list)
(define-key decipher-stats-mode-map "F" 'decipher-frequency-count)
(define-key decipher-stats-mode-map "N" 'decipher-adjacency-list)
))
(defvar decipher-mode-syntax-table nil
"Decipher mode syntax table")
(if decipher-mode-syntax-table
()
(let ((table (make-syntax-table))
(c ?0))
(while (<= c ?9)
(modify-syntax-entry c "_" table) (incf c))
(setq decipher-mode-syntax-table table)))
(defvar decipher-alphabet nil)
(make-variable-buffer-local 'decipher-alphabet)
(defvar decipher-stats-buffer nil
"The buffer which displays statistics for this ciphertext.
Do not access this variable directly, use the function
`decipher-stats-buffer' instead.")
(make-variable-buffer-local 'decipher-stats-buffer)
(defvar decipher-undo-list-size 0
"The number of entries in the undo list.")
(make-variable-buffer-local 'decipher-undo-list-size)
(defvar decipher-undo-list nil
"The undo list for this buffer.
Each element is either a cons cell (PLAIN-CHAR . CIPHER-CHAR) or a
list of such cons cells.")
(make-variable-buffer-local 'decipher-undo-list)
(defvar decipher-pending-undo-list nil)
(defvar decipher-char nil
"See the functions decipher-loop-with-breaks and decipher-loop-no-breaks.")
(defvar decipher--prev-char)
(defvar decipher--digram)
(defvar decipher--digram-list)
(defvar decipher--before)
(defvar decipher--after)
(defvar decipher--freqs)
(defun decipher ()
"Format a buffer of ciphertext for cryptanalysis and enter Decipher mode."
(interactive)
(goto-char (point-max))
(or (bolp)
(insert "\n"))
(goto-char (point-min))
(if (looking-at "^(abcdefghijklmnopqrstuvwxyz \
ABCDEFGHIJKLMNOPQRSTUVWXYZ -\\*-decipher-\\*-\n)")
(message "Buffer is already formatted, entering Decipher mode...")
(insert "(abcdefghijklmnopqrstuvwxyz \
ABCDEFGHIJKLMNOPQRSTUVWXYZ -*-decipher-*-\n)\n\n")
(let (begin)
(while (not (eobp))
(if (looking-at "^%")
(forward-line) (delete-horizontal-space)
(if (eolp)
(forward-line) (insert ":") (setq begin (point))
(forward-line)
(if decipher-force-uppercase
(upcase-region begin (point))) (insert ">\n"))))) (delete-blank-lines) (delete-blank-lines)) (goto-line 4)
(decipher-mode))
(defun decipher-mode ()
"Major mode for decrypting monoalphabetic substitution ciphers.
Lower-case letters enter plaintext.
Upper-case letters are commands.
The buffer is made read-only so that normal Emacs commands cannot
modify it.
The most useful commands are:
\\<decipher-mode-map>
\\[decipher-digram-list] Display a list of all digrams & their frequency
\\[decipher-frequency-count] Display the frequency of each ciphertext letter
\\[decipher-adjacency-list]\
Show adjacency list for current letter (lists letters appearing next to it)
\\[decipher-make-checkpoint] Save the current cipher alphabet (checkpoint)
\\[decipher-restore-checkpoint] Restore a saved cipher alphabet (checkpoint)"
(interactive)
(kill-all-local-variables)
(setq buffer-undo-list t indent-tabs-mode nil major-mode 'decipher-mode
mode-name "Decipher")
(if decipher-force-uppercase
(setq case-fold-search nil)) (use-local-map decipher-mode-map)
(set-syntax-table decipher-mode-syntax-table)
(decipher-read-alphabet)
(set (make-local-variable 'font-lock-defaults)
'(decipher-font-lock-keywords t))
(make-local-hook 'change-major-mode-hook)
(add-hook 'change-major-mode-hook
(lambda () (setq buffer-read-only nil
buffer-undo-list nil))
nil t)
(run-hooks 'decipher-mode-hook)
(setq buffer-read-only t))
(put 'decipher-mode 'mode-class 'special)
(defmacro decipher-last-command-char ()
(if (fboundp 'event-to-character)
'(event-to-character last-command-event)
'last-command-event))
(defun decipher-keypress ()
"Enter a plaintext or ciphertext character."
(interactive)
(let ((decipher-function 'decipher-set-map)
buffer-read-only) (save-excursion
(or (save-excursion
(beginning-of-line)
(let ((first-char (following-char)))
(cond
((= ?: first-char)
t)
((= ?> first-char)
nil)
((= ?\( first-char)
(setq decipher-function 'decipher-alphabet-keypress)
t)
((= ?\) first-char)
(setq decipher-function 'decipher-alphabet-keypress)
nil)
(t
(error "Bad location")))))
(let (goal-column)
(previous-line 1)))
(let ((char-a (following-char))
(char-b (decipher-last-command-char)))
(or (and (not (= ?w (char-syntax char-a)))
(= char-b ?\ )) (funcall decipher-function char-a char-b)))))
(forward-char))
(defun decipher-alphabet-keypress (a b)
(cond ((and (>= a ?A) (<= a ?Z))
(decipher-set-map a b))
((and (>= a ?a) (<= a ?z))
(if (= b ?\ )
(if (/= ?\ (setq b (cdr (assoc a decipher-alphabet))))
(decipher-set-map b ?\ ))
(decipher-set-map b a)))
(t
(error "Bad character"))))
(defun decipher-undo ()
"Undo a change in Decipher mode."
(interactive)
(setq this-command t)
(or (eq major-mode 'decipher-mode)
(error "This buffer is not in Decipher mode"))
(or (eq last-command 'decipher-undo)
(setq decipher-pending-undo-list decipher-undo-list))
(or decipher-pending-undo-list
(error "No further undo information"))
(let ((undo-rec (pop decipher-pending-undo-list))
buffer-read-only redo-map redo-rec undo-map)
(or (consp (car undo-rec))
(setq undo-rec (list undo-rec)))
(while (setq undo-map (pop undo-rec))
(setq redo-map (decipher-get-undo (cdr undo-map) (car undo-map)))
(if redo-map
(setq redo-rec
(if (consp (car redo-map))
(append redo-map redo-rec)
(cons redo-map redo-rec))))
(decipher-set-map (cdr undo-map) (car undo-map) t))
(decipher-add-undo redo-rec))
(setq this-command 'decipher-undo)
(message "Undo!"))
(defun decipher-add-undo (undo-rec)
"Add UNDO-REC to the undo list."
(if undo-rec
(progn
(push undo-rec decipher-undo-list)
(incf decipher-undo-list-size)
(if (> decipher-undo-list-size decipher-undo-limit)
(let ((new-size (- decipher-undo-limit 100)))
(setcdr (nthcdr (1- new-size) decipher-undo-list) nil)
(setq decipher-undo-list-size new-size))))))
(defun decipher-copy-cons (cons)
(if cons
(cons (car cons) (cdr cons))))
(defun decipher-get-undo (cipher-char plain-char)
(let ((cipher-map (decipher-copy-cons (rassoc cipher-char decipher-alphabet)))
(plain-map (decipher-copy-cons (assoc plain-char decipher-alphabet))))
(cond ((equal ?\ plain-char)
cipher-map)
((equal cipher-char (cdr plain-map))
nil) ((equal ?\ (cdr plain-map))
(or cipher-map (cons ?\ cipher-char)))
(cipher-map
(list plain-map cipher-map))
(t
plain-map))))
(defun decipher-set-map (cipher-char plain-char &optional no-undo)
(setq cipher-char (upcase cipher-char))
(or (and (>= cipher-char ?A) (<= cipher-char ?Z))
(error "Bad character")) (or no-undo
(decipher-add-undo (decipher-get-undo cipher-char plain-char)))
(let ((cipher-string (char-to-string cipher-char))
(plain-string (char-to-string plain-char))
case-fold-search mapping bound)
(save-excursion
(goto-char (point-min))
(if (setq mapping (rassoc cipher-char decipher-alphabet))
(progn
(setcdr mapping ?\ )
(search-forward-regexp (concat "^([a-z]*"
(char-to-string (car mapping))))
(decipher-insert ?\ )
(beginning-of-line)))
(if (setq mapping (assoc plain-char decipher-alphabet))
(progn
(if (/= ?\ (cdr mapping))
(decipher-set-map (cdr mapping) ?\ t))
(setcdr mapping cipher-char)
(search-forward-regexp (concat "^([a-z]*" plain-string))
(decipher-insert cipher-char)
(beginning-of-line)))
(search-forward-regexp (concat "^([a-z]+ [A-Z]*" cipher-string))
(decipher-insert plain-char)
(setq case-fold-search t cipher-string (downcase cipher-string))
(let ((font-lock-fontify-region-function 'ignore))
(while (search-forward-regexp "^:" nil t)
(setq bound (save-excursion (end-of-line) (point)))
(while (search-forward cipher-string bound 'end)
(decipher-insert plain-char)))))))
(defun decipher-insert (char)
(let ((col (1- (current-column))))
(save-excursion
(forward-line)
(or (= ?\> (following-char))
(= ?\) (following-char))
(error "Bad location"))
(move-to-column col t)
(or (eolp)
(delete-char 1))
(insert-and-inherit char))))
(defun decipher-make-checkpoint (desc)
"Checkpoint the current cipher alphabet.
This records the current alphabet so you can return to it later.
You may have any number of checkpoints.
Type `\\[decipher-restore-checkpoint]' to restore a checkpoint."
(interactive "sCheckpoint description: ")
(or (stringp desc)
(setq desc ""))
(let (alphabet
buffer-read-only mapping)
(goto-char (point-min))
(re-search-forward "^)")
(move-to-column 27 t)
(setq alphabet (buffer-substring-no-properties (- (point) 26) (point)))
(if (re-search-forward "^%![A-Z ]+!" nil 'end)
nil (if (re-search-backward "^% *Local Variables:" nil t)
(progn (forward-line -1)
(or (looking-at "^ *$")
(progn (forward-line) (insert ?\n) (forward-line -1)))))
(insert "\n%" (make-string 69 ?\-)
"\n% Checkpoints:\n% abcdefghijklmnopqrstuvwxyz\n"))
(beginning-of-line)
(insert "%!" alphabet "! " desc ?\n)))
(defun decipher-restore-checkpoint ()
"Restore the cipher alphabet from a checkpoint.
If point is not on a checkpoint line, moves to the first checkpoint line.
If point is on a checkpoint, restores that checkpoint.
Type `\\[decipher-make-checkpoint]' to make a checkpoint."
(interactive)
(beginning-of-line)
(if (looking-at "%!\\([A-Z ]+\\)!")
(let ((alphabet (match-string 1))
buffer-read-only) (goto-char (point-min))
(re-search-forward "^)")
(or (eolp)
(delete-region (point) (progn (end-of-line) (point))))
(insert alphabet)
(decipher-resync))
(goto-char (point-min))
(if (re-search-forward "^%![A-Z ]+!" nil t)
(message "Select the checkpoint to restore and type `%s'"
(substitute-command-keys "\\[decipher-restore-checkpoint]"))
(error "No checkpoints in this buffer"))))
(defun decipher-complete-alphabet ()
"Complete the cipher alphabet.
This fills any blanks in the cipher alphabet with the unused letters
in alphabetical order. Use this when you have a keyword cipher and
you have determined the keyword."
(interactive)
(let ((cipher-char ?A)
(ptr decipher-alphabet)
buffer-read-only plain-map undo-rec)
(while (setq plain-map (pop ptr))
(if (equal ?\ (cdr plain-map))
(progn
(while (rassoc cipher-char decipher-alphabet)
(incf cipher-char))
(push (cons ?\ cipher-char) undo-rec)
(decipher-set-map cipher-char (car plain-map) t))))
(decipher-add-undo undo-rec)))
(defun decipher-show-alphabet ()
"Display the current cipher alphabet in the message line."
(interactive)
(message
(mapconcat (lambda (a)
(concat
(char-to-string (car a))
(char-to-string (cdr a))))
decipher-alphabet
"")))
(defun decipher-resync ()
"Reprocess the buffer using the alphabet from the top.
This regenerates all deciphered plaintext and clears the undo list.
You should use this if you edit the ciphertext."
(interactive)
(message "Reprocessing buffer...")
(let (alphabet
buffer-read-only mapping)
(save-excursion
(decipher-read-alphabet)
(setq alphabet decipher-alphabet)
(goto-char (point-min))
(and (re-search-forward "^).+" nil t)
(replace-match ")" nil nil))
(while (re-search-forward "^>.+" nil t)
(replace-match ">" nil nil))
(decipher-read-alphabet)
(while (setq mapping (pop alphabet))
(or (equal ?\ (cdr mapping))
(decipher-set-map (cdr mapping) (car mapping))))))
(setq decipher-undo-list nil
decipher-undo-list-size 0)
(message "Reprocessing buffer...done"))
(defun decipher-read-alphabet ()
"Build the decipher-alphabet from the alphabet line in the buffer."
(save-excursion
(goto-char (point-min))
(search-forward-regexp "^)")
(move-to-column 27 t)
(setq decipher-alphabet nil)
(let ((plain-char ?z))
(while (>= plain-char ?a)
(backward-char)
(push (cons plain-char (following-char)) decipher-alphabet)
(decf plain-char)))))
(defun decipher-frequency-count ()
"Display the frequency count in the statistics buffer."
(interactive)
(decipher-analyze)
(decipher-display-regexp "^A" "^[A-Z][A-Z]"))
(defun decipher-digram-list ()
"Display the list of digrams in the statistics buffer."
(interactive)
(decipher-analyze)
(decipher-display-regexp "[A-Z][A-Z] +[0-9]" "^$"))
(defun decipher-adjacency-list (cipher-char)
"Display the adjacency list for the letter at point.
The adjacency list shows all letters which come next to CIPHER-CHAR.
An adjacency list (for the letter X) looks like this:
1 1 1 1 1 3 2 1 3 8
X: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z * 11 14 9%
1 1 1 2 1 1 2 5 7
This says that X comes before D once, and after B once. X begins 5
words, and ends 3 words (`*' represents a space). X comes before 8
different letters, after 7 differerent letters, and is next to a total
of 11 different letters. It occurs 14 times, making up 9% of the
ciphertext."
(interactive (list (upcase (following-char))))
(decipher-analyze)
(let (start end)
(save-excursion
(set-buffer (decipher-stats-buffer))
(goto-char (point-min))
(or (re-search-forward (format "^%c: " cipher-char) nil t)
(error "Character `%c' is not used in ciphertext" cipher-char))
(forward-line -1)
(setq start (point))
(forward-line 3)
(setq end (point)))
(decipher-display-range start end)))
(defun decipher-analyze ()
"Perform frequency analysis on the current buffer if necessary."
(cond
((eq major-mode 'decipher-stats-mode))
((eq major-mode 'decipher-mode)
(or (and (bufferp decipher-stats-buffer)
(buffer-name decipher-stats-buffer))
(decipher-analyze-buffer)))
(t (error "This buffer is not in Decipher mode"))))
(defun decipher-display-range (start end)
"Display text between START and END in the statistics buffer.
START and END are positions in the statistics buffer. Makes the
statistics buffer visible and sizes the window to just fit the
displayed text, but leaves the current window selected."
(let ((stats-buffer (decipher-stats-buffer))
(current-window (selected-window))
(pop-up-windows t))
(or (eq (current-buffer) stats-buffer)
(pop-to-buffer stats-buffer))
(goto-char start)
(or (one-window-p t)
(enlarge-window (- (1+ (count-lines start end)) (window-height))))
(recenter 0)
(select-window current-window)))
(defun decipher-display-regexp (start-regexp end-regexp)
"Display text between two regexps in the statistics buffer.
START-REGEXP matches the first line to display.
END-REGEXP matches the line after that which ends the display.
The ending line is included in the display unless it is blank."
(let (start end)
(save-excursion
(set-buffer (decipher-stats-buffer))
(goto-char (point-min))
(re-search-forward start-regexp)
(beginning-of-line)
(setq start (point))
(re-search-forward end-regexp)
(beginning-of-line)
(or (looking-at "^ *$")
(forward-line 1))
(setq end (point)))
(decipher-display-range start end)))
(defun decipher-loop-with-breaks (func)
"Loop through ciphertext, calling FUNC once for each letter & word division.
FUNC is called with no arguments, and its return value is unimportant.
It may examine `decipher-char' to see the current ciphertext
character. `decipher-char' contains either an uppercase letter or a space.
FUNC is called exactly once between words, with `decipher-char' set to
a space.
See `decipher-loop-no-breaks' if you do not care about word divisions."
(let ((decipher-char ?\ )
(decipher--loop-prev-char ?\ ))
(save-excursion
(goto-char (point-min))
(funcall func) (while (search-forward-regexp "^:" nil t)
(while (not (eolp))
(setq decipher-char (upcase (following-char)))
(or (and (>= decipher-char ?A) (<= decipher-char ?Z))
(setq decipher-char ?\ ))
(or (and (equal decipher-char ?\ )
(equal decipher--loop-prev-char ?\ ))
(funcall func))
(setq decipher--loop-prev-char decipher-char)
(forward-char))
(or (equal decipher-char ?\ )
(progn
(setq decipher-char ?\ decipher--loop-prev-char ?\ )
(funcall func)))))))
(defun decipher-loop-no-breaks (func)
"Loop through ciphertext, calling FUNC once for each letter.
FUNC is called with no arguments, and its return value is unimportant.
It may examine `decipher-char' to see the current ciphertext letter.
`decipher-char' contains an uppercase letter.
Punctuation and spacing in the ciphertext are ignored.
See `decipher-loop-with-breaks' if you care about word divisions."
(let (decipher-char)
(save-excursion
(goto-char (point-min))
(while (search-forward-regexp "^:" nil t)
(while (not (eolp))
(setq decipher-char (upcase (following-char)))
(and (>= decipher-char ?A)
(<= decipher-char ?Z)
(funcall func))
(forward-char))))))
(defun decipher-insert-frequency-counts (freq-list total)
"Insert frequency counts in current buffer.
Each element of FREQ-LIST is a list (LETTER FREQ ...).
TOTAL is the total number of letters in the ciphertext."
(let ((i 4) temp-list)
(while (> i 0)
(setq temp-list freq-list)
(while temp-list
(insert (caar temp-list)
(format "%4d%3d%% "
(cadar temp-list)
(/ (* 100 (cadar temp-list)) total)))
(setq temp-list (nthcdr 4 temp-list)))
(insert ?\n)
(setq freq-list (cdr freq-list)
i (1- i)))))
(defun decipher--analyze ()
(setq decipher--digram (format "%c%c" decipher--prev-char decipher-char))
(incf (cdr (or (assoc decipher--digram decipher--digram-list)
(car (push (cons decipher--digram 0)
decipher--digram-list)))))
(and (>= decipher--prev-char ?A)
(incf (aref (aref decipher--before (- decipher--prev-char ?A))
(if (equal decipher-char ?\ )
26
(- decipher-char ?A)))))
(and (>= decipher-char ?A)
(incf (aref decipher--freqs (- decipher-char ?A)))
(incf (aref (aref decipher--after (- decipher-char ?A))
(if (equal decipher--prev-char ?\ )
26
(- decipher--prev-char ?A)))))
(setq decipher--prev-char decipher-char))
(defun decipher--digram-counts (counts)
"Generate the counts for an adjacency list."
(let ((total 0))
(concat
(mapconcat (lambda (x)
(cond ((> x 99) (incf total) "XX")
((> x 0) (incf total) (format "%2d" x))
(t " ")))
counts
"")
(format "%4d" (if (> (aref counts 26) 0)
(1- total) total)))))
(defun decipher--digram-total (before-count after-count)
"Count the number of different letters a letter appears next to."
(let ((total 0)
(i 26))
(while (>= (decf i) 0)
(if (or (> (aref before-count i) 0)
(> (aref after-count i) 0))
(incf total)))
total))
(defun decipher-analyze-buffer ()
"Perform frequency analysis and store results in statistics buffer.
Creates the statistics buffer if it doesn't exist."
(let ((decipher--prev-char (if decipher-ignore-spaces ?\ ?\*))
(decipher--before (make-vector 26 nil))
(decipher--after (make-vector 26 nil))
(decipher--freqs (make-vector 26 0))
(total-chars 0)
decipher--digram decipher--digram-list freq-list)
(message "Scanning buffer...")
(let ((i 26))
(while (>= (decf i) 0)
(aset decipher--before i (make-vector 27 0))
(aset decipher--after i (make-vector 27 0))))
(if decipher-ignore-spaces
(progn
(decipher-loop-no-breaks 'decipher--analyze)
(let ((i 26))
(while (>= (decf i) 0)
(aset (aref decipher--after i) 26 0))))
(decipher-loop-with-breaks 'decipher--analyze))
(message "Processing results...")
(setcdr (last decipher--digram-list 2) nil) (setq decipher--digram-list (sort (sort decipher--digram-list
(lambda (a b) (string< (car a) (car b))))
(lambda (a b) (> (cdr a) (cdr b)))))
(let ((i 26))
(while (>= (decf i) 0)
(setq freq-list
(cons (list (+ i ?A)
(aref decipher--freqs i)
(decipher--digram-total (aref decipher--before i)
(aref decipher--after i)))
freq-list)
total-chars (+ total-chars (aref decipher--freqs i)))))
(save-excursion
(set-buffer (decipher-stats-buffer t))
(or (eq major-mode 'decipher-stats-mode)
(error "Buffer %s is not in Decipher-Stats mode" (buffer-name)))
(setq buffer-read-only nil)
(erase-buffer)
(decipher-insert-frequency-counts freq-list total-chars)
(insert ?\n)
(setq freq-list (sort freq-list
(lambda (a b) (> (second a) (second b)))))
(decipher-insert-frequency-counts freq-list total-chars)
(insert ?\n (mapconcat (lambda (a) (char-to-string (car a)))
freq-list nil)
"\n\n")
(let* ((rows (floor (+ (length decipher--digram-list) 9) 10))
(i rows)
temp-list)
(while (> i 0)
(setq temp-list decipher--digram-list)
(while temp-list
(insert (caar temp-list)
(format "%3d "
(cdar temp-list)))
(setq temp-list (nthcdr rows temp-list)))
(delete-horizontal-space)
(insert ?\n)
(setq decipher--digram-list (cdr decipher--digram-list)
i (1- i))))
(setq freq-list (sort freq-list
(lambda (a b) (> (third a) (third b)))))
(let ((temp-list freq-list)
entry i)
(while (setq entry (pop temp-list))
(if (equal 0 (second entry))
nil (setq i (- (car entry) ?A))
(insert ?\n " "
(decipher--digram-counts (aref decipher--before i)) ?\n
(car entry)
": A B C D E F G H I J K L M N O P Q R S T U V W X Y Z *"
(format "%4d %4d %3d%%\n "
(third entry) (second entry)
(/ (* 100 (second entry)) total-chars))
(decipher--digram-counts (aref decipher--after i)) ?\n))))
(setq buffer-read-only t)
(set-buffer-modified-p nil)
))
(message nil))
(defun decipher-stats-mode ()
"Major mode for displaying ciphertext statistics."
(interactive)
(kill-all-local-variables)
(setq buffer-read-only t
buffer-undo-list t case-fold-search nil indent-tabs-mode nil major-mode 'decipher-stats-mode
mode-name "Decipher-Stats")
(use-local-map decipher-stats-mode-map)
(run-hooks 'decipher-stats-mode-hook))
(put 'decipher-stats-mode 'mode-class 'special)
(defun decipher-display-stats-buffer ()
"Make the statistics buffer visible, but do not select it."
(let ((stats-buffer (decipher-stats-buffer))
(current-window (selected-window)))
(or (eq (current-buffer) stats-buffer)
(progn
(pop-to-buffer stats-buffer)
(select-window current-window)))))
(defun decipher-stats-buffer (&optional create)
"Return the buffer used for decipher statistics.
If CREATE is non-nil, create the buffer if it doesn't exist.
This is guaranteed to return a buffer in Decipher-Stats mode;
if it can't, it signals an error."
(cond
((eq major-mode 'decipher-stats-mode)
(current-buffer))
((and (bufferp decipher-stats-buffer)
(buffer-name decipher-stats-buffer))
(or (save-excursion
(set-buffer decipher-stats-buffer)
(eq major-mode 'decipher-stats-mode))
(error "Buffer %s is not in Decipher-Stats mode"
(buffer-name decipher-stats-buffer)))
decipher-stats-buffer)
(create
(let ((stats-name (concat "*" (buffer-name) "*")))
(setq decipher-stats-buffer
(if (eq 'decipher-stats-mode
(cdr-safe (assoc 'major-mode
(buffer-local-variables
(get-buffer stats-name)))))
(get-buffer stats-name)
(generate-new-buffer stats-name))))
(save-excursion
(set-buffer decipher-stats-buffer)
(decipher-stats-mode))
decipher-stats-buffer)
(t (error "No statistics buffer"))))
(provide 'decipher)