cipher-ops.scm 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. ;;;
  2. ;;; This is a small library containing useful procedures for
  3. ;;; pen-and-paper mono- and polyalphabetic ciphers.
  4. ;;;
  5. ;;; NOTICE: sanitize-chars and restore-punctuation work on
  6. ;;; lists of chars and not strings. This helps performance
  7. ;;; by cutting back on conversions. Simply convert before and
  8. ;;; after using the necessary procedure.
  9. ;;;
  10. ;;; Copyright 2016 Jason K. MacDuffie
  11. ;;; License: GPLv3+
  12. ;;;
  13. (define (letter? c)
  14. ;; Is c an ASCII letter?
  15. (define n (char->integer c))
  16. (and (< 64 n 123)
  17. (not (< 90 n 97))))
  18. (define (letter->integer c)
  19. ;; Encode letters as numbers
  20. ;; A -> 0, Z -> 26
  21. (- (char->integer (char-upcase c))
  22. 65))
  23. (define (integer->letter n)
  24. ;; Inverse of letter->integer
  25. (integer->char (+ n 65)))
  26. (define (letter+ a b)
  27. ;; Add letters according to the tabula recta.
  28. (integer->letter (modulo (+ (letter->integer a)
  29. (letter->integer b))
  30. 26)))
  31. (define (letter- a b)
  32. ;; Subtract letters according to the tabula recta.
  33. (integer->letter (modulo (- (letter->integer a)
  34. (letter->integer b))
  35. 26)))
  36. (define (sanitize-chars s)
  37. ;; Remove punctuation and upcase a list of characters.
  38. (let loop ((in s)
  39. (out '()))
  40. (if (null? in)
  41. (reverse out)
  42. (loop (cdr in)
  43. (if (letter? (car in))
  44. (cons (char-upcase (car in)) out)
  45. out)))))
  46. (define (restore-punctuation original modified)
  47. ;; Add punctuation back to a list of characters.
  48. (let loop ((in-original original)
  49. (in-modified modified)
  50. (out '()))
  51. (if (null? in-original)
  52. (reverse out)
  53. (if (letter? (car in-original))
  54. (loop (cdr in-original)
  55. (cdr in-modified)
  56. (cons (if (char-upper-case? (car in-original))
  57. (car in-modified)
  58. (char-downcase (car in-modified)))
  59. out))
  60. (loop (cdr in-original)
  61. in-modified
  62. (cons (car in-original) out))))))
  63. (define (apply-cipher ciph restore? s-in . rest-in)
  64. ;; Higher level procedure that accepts and returns strings
  65. (define sl (string->list s-in))
  66. (define s (sanitize-chars sl))
  67. (define rest (map (lambda (s)
  68. (if (string? s)
  69. (sanitize-chars (string->list s))
  70. s))
  71. rest-in))
  72. (let ((result (apply ciph s rest)))
  73. (list->string (if restore?
  74. (restore-punctuation sl result)
  75. result))))
  76. (define print-letters
  77. (case-lambda
  78. ((s) (print-letters s #\space))
  79. ((s pad)
  80. ;; Space out the letters of s by groups of 5, and
  81. ;; pad the end by repeating a character.
  82. (let loop ((l (string->list s))
  83. (out '())
  84. (i 0))
  85. (if (null? l)
  86. (if (< i 5)
  87. (loop '()
  88. (cons pad out)
  89. (+ i 1))
  90. (apply string (reverse out)))
  91. (cond
  92. ((< i 5)
  93. (loop (cdr l)
  94. (cons (car l) out)
  95. (+ i 1)))
  96. (else
  97. (loop l
  98. (cons #\space out)
  99. 0))))))))
  100. (define (runkey-encipher pt-in key-in)
  101. ;; Simplest polyalphabetic cipher. Potentially useful
  102. ;; as the base of other polyalphabetic ciphers.
  103. (map letter+ pt-in key-in))
  104. (define (runkey-decipher ct-in key-in)
  105. ;; Inverse of runkey-encipher
  106. (map letter- ct-in key-in))