summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Gesang <pgesang@ix.urz.uni-heidelberg.de>2012-02-21 10:42:08 +0100
committerPhilipp Gesang <pgesang@ix.urz.uni-heidelberg.de>2012-02-21 10:42:08 +0100
commit7477a9e3c3e955307558d7ed0ae47d68d24d156d (patch)
treef104156d547001b92ac43204ada8f56c16fd01bf
downloadenigma-7477a9e3c3e955307558d7ed0ae47d68d24d156d.tar.gz
initial commit: Plain module functional
-rw-r--r--.hgignore43
-rw-r--r--COPYING22
-rw-r--r--tex/context/third/enigma/enigma.lua1237
-rw-r--r--tex/latex/enigma/enigma.sty3
-rw-r--r--tex/plain/enigma/enigma.tex83
5 files changed, 1388 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..e360e23
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,43 @@
+syntax:glob
+*.swp
+*.pdf
+*.aux
+*.bbl
+*.log
+*.url
+*.toc
+*.ind
+*.out
+*.dvi
+*.blg
+*.4ct
+*.idv
+*.html
+*.css
+*.4tc
+*.lg
+*.xref
+*.idx
+*.tmp
+*.djvu
+*.ps
+*.make
+*.d
+*.fls
+*-blx.bib
+*.temp
+*.latexmain
+*.snm
+*.nav
+*.vrb
+*.top
+*.tuc
+*.swo
+*.tuo
+*.ted
+*.bib
+./testing/*
+./tmp.tex
+*–rst_temporary*
+./tmp/*
+./test/*
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..3e802a1
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,22 @@
+Copyright 2011 Philipp Gesang. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/tex/context/third/enigma/enigma.lua b/tex/context/third/enigma/enigma.lua
new file mode 100644
index 0000000..4514ca7
--- /dev/null
+++ b/tex/context/third/enigma/enigma.lua
@@ -0,0 +1,1237 @@
+#!/usr/bin/env texlua
+------------------------------------------------------------------------
+-- FILE: enigma.lua
+-- USAGE: Call via interface from within a TeX session.
+-- DESCRIPTION: Enigma logic.
+-- REQUIREMENTS: LuaTeX capable format (Luaplain, ConTeXt).
+-- AUTHOR: Philipp Gesang (Phg), <megas.kapaneus@gmail.com>
+-- VERSION: hg tip
+-- CREATED: 2012-02-19 21:44:22+0100
+------------------------------------------------------------------------
+--
+
+--[[ichd--
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Prerequisites]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startparagraph
+First of all, we generate local copies of all those library functions
+that are expected to be referenced frequently.
+The format-independent stuff comes first; it consists of functions from
+the
+\identifier{io},
+\identifier{lpeg},
+\identifier{math},
+\identifier{string},
+\identifier{table}, and
+\identifier{unicode}
+libraries.
+\stopparagraph
+--ichd]]--
+local iowrite = io.write
+local mathrandom = math.random
+local next = next
+local nodecopy = node.copy
+local nodeid = node.id
+local nodeinsert_before = node.insert_before
+local nodenew = node.new
+local noderemove = node.remove
+local nodetraverse = node.traverse
+local stringfind = string.find
+local stringformat = string.format
+local stringlower = string.lower
+local stringsub = string.sub
+local stringupper = string.upper
+local tableconcat = table.concat
+local tonumber = tonumber
+local utf8byte = unicode.utf8.byte
+local utf8char = unicode.utf8.char
+local utf8len = unicode.utf8.len
+local utf8sub = unicode.utf8.sub
+local utfcharacters = string.utfcharacters
+
+local glyph_node = nodeid"glyph"
+local glue_node = nodeid"glue"
+
+--[[ichd
+\startparagraph
+The initialization of the module relies heavily on parsers generated by
+\type{LPEG}.
+\stopparagraph
+--ichd]]--
+
+local lpeg = require "lpeg"
+
+local C, Cb, Cc, Cf, Cg,
+ Cmt, Cp, Cs, Ct
+ = lpeg.C, lpeg.Cb, lpeg.Cc, lpeg.Cf, lpeg.Cg,
+ lpeg.Cmt, lpeg.Cp, lpeg.Cs, lpeg.Ct
+
+local P, R, S, V, lpegmatch
+ = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.match
+
+--local B = lpeg.version() == "0.10" and lpeg.B or nil
+
+--[[ichd
+\startparagraph
+By default the output to \type{stdout} will be zero. The verbosity level
+can be adjusted in order to alleviate debugging.
+\stopparagraph
+--ichd]]--
+local verbose_level = 42
+--local verbose_level = 0
+
+--[[ichd
+\startparagraph
+Historically, Enigma-encoded messages were restricted to a size of 250
+characters. With sufficient verbosity we will indicate whether this
+limit has been exceeded during the \TEX\ run.
+\stopparagraph
+--ichd]]--
+local max_msg_length = 250
+--[[ichd
+\stopsection
+--ichd]]--
+
+
+--[[ichd--
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Globals]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startparagraph
+The following mappings are used all over the place as we convert back
+and forth between the characters (unicode) and their numerical
+representation.
+\stopparagraph
+--ichd]]--
+
+local value_to_letter -- { [int] -> chr }
+local letter_to_value -- { [chr] -> int }
+local alpha_sorted -- string, length 26
+local raw_rotor_wiring -- { string0, .. string5, }
+local notches -- { [int] -> int } // rotor num -> notch pos
+local reflector_wiring -- { { [int] -> int }, ... } // symmetrical
+do
+ value_to_letter = {
+ "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",
+ }
+
+ letter_to_value = {
+ a = 01, b = 02, c = 03, d = 04, e = 05, f = 06, g = 07, h = 08,
+ i = 09, j = 10, k = 11, l = 12, m = 13, n = 14, o = 15, p = 16,
+ q = 17, r = 18, s = 19, t = 20, u = 21, v = 22, w = 23, x = 24,
+ y = 25, z = 26,
+ }
+
+ --[[
+ Nice: http://www.ellsbury.com/ultraenigmawirings.htm
+ Wirings are created from strings at runtime.
+ ]]--
+ alpha_sorted = "abcdefghijklmnopqrstuvwxyz"
+ raw_rotor_wiring = {
+ [0] = alpha_sorted,
+ "ekmflgdqvzntowyhxuspaibrcj",
+ "ajdksiruxblhwtmcqgznpyfvoe",
+ "bdfhjlcprtxvznyeiwgakmusqo",
+ "esovpzjayquirhxlnftgkdcmwb",
+ "vzbrgityupsdnhlxawmjqofeck",
+ }
+
+--[[ichd--
+\startparagraph
+Notches are assigned to rotors according to the Royal Army
+mnemonic.
+\stopparagraph
+--ichd]]--
+ notches = { }
+ do
+ local raw_notches = "rfwkannnn"
+ --local raw_notches = "qevjz"
+ local n = 1
+ for chr in utfcharacters(raw_notches) do
+ local pos = stringfind(alpha_sorted, chr)
+ notches[n] = pos - 1
+ n = n + 1
+ end
+ end
+
+--[[ichd--
+\placetable[here][]{The three reflectors and their substitution
+ rules.}{%
+ \starttabular[|r|l|]
+ \NC UKW a \NC AE BJ CM DZ FL GY HX IV KW NR OQ PU ST \NC \NR
+ \NC UKW b \NC AY BR CU DH EQ FS GL IP JX KN MO TZ VW \NC \NR
+ \NC UKW c \NC AF BV CP DJ EI GO HY KR LZ MX NW QT SU \NC \NR
+ \stoptabular
+}
+--ichd]]--
+
+ reflector_wiring = { }
+ local raw_ukw = {
+ { a = "e", b = "j", c = "m", d = "z", f = "l", g = "y", h = "x",
+ i = "v", k = "w", n = "r", o = "q", p = "u", s = "t", },
+ { a = "y", b = "r", c = "u", d = "h", e = "q", f = "s", g = "l",
+ i = "p", j = "x", k = "n", m = "o", t = "z", v = "w", },
+ { a = "f", b = "v", c = "p", d = "j", e = "i", g = "o", h = "y",
+ k = "r", l = "z", m = "x", n = "w", q = "t", s = "u", },
+ }
+ for i=1, #raw_ukw do
+ local new_wiring = { }
+ local current_ukw = raw_ukw[i]
+ for from, to in next, current_ukw do
+ from = letter_to_value[from]
+ to = letter_to_value[to]
+ new_wiring[from] = to
+ new_wiring[to] = from
+ end
+ reflector_wiring[i] = new_wiring
+ end
+end
+
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Pretty printing for debug purposes]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startparagraph
+The functions below allow for formatting of the terminal output; they
+have no effect on the workings of the enigma simulator.
+\stopparagraph
+--ichd]]--
+
+local emit
+local pprint_ciphertext
+local pprint_encoding
+local pprint_encoding_scheme
+local pprint_init
+local pprint_machine_step
+local pprint_new_machine
+local pprint_rotor
+local pprint_rotor_scheme
+local pprint_step
+do
+ local eol = "\n"
+
+ local colorstring_template = "\027[%d;1m%s\027[0m"
+ local colorize = function (s, color)
+ color = color and color < 38 and color > 29 and color or 31
+ return stringformat(colorstring_template,
+ color,
+ s)
+ end
+
+ local underline = function (s)
+ return stringformat("\027[4;37m%s\027[0m", s)
+ end
+
+ local s_steps = [[Total characters encoded: ]]
+ local f_warnsteps = [[ (%d over permitted maximum)]]
+ pprint_machine_step = function (n)
+ local sn
+ if n > max_msg_length then
+ sn = colorize(n, 31) .. stringformat(f_warnsteps,
+ n - max_msg_length)
+ else
+ sn = colorize(n, 37)
+ end
+ emit(1, s_steps .. sn .. ".")
+ end
+ local rotorstate = "[s \027[1;37m%s\027[0m n\027[1;37m%2d\027[0m]> "
+ pprint_rotor = function (rotor)
+ local visible = rotor.state % 26 + 1
+ local w, n = rotor.wiring, (rotor.notch - visible) % 26 + 1
+ local tmp = { }
+ for i=1, 26 do
+ local which = (i + rotor.state - 1) % 26 + 1
+ local chr = value_to_letter[rotor.wiring.from[which]]
+ if i == n then -- highlight positions of notches
+ tmp[i] = colorize(stringupper(chr), 32)
+ --elseif chr == value_to_letter[visible] then
+ ---- highlight the character in window
+ -- tmp[i] = colorize(chr, 33)
+ else
+ tmp[i] = chr
+ end
+ end
+ emit(3, stringformat(rotorstate,
+ stringupper(value_to_letter[visible]),
+ n)
+ .. tableconcat(tmp))
+ end
+
+ local rotor_scheme = underline"[rot not]"
+ .. " "
+ .. underline(alpha_sorted)
+ pprint_rotor_scheme = function ()
+ emit(3, rotor_scheme)
+ end
+
+ local s_encoding_scheme = eol
+ .. [[in > 1 => 2 => 3 > UKW > 3 => 2 => 1]]
+ pprint_encoding_scheme = function ()
+ emit(2, underline(s_encoding_scheme))
+ end
+ local s_step = " => "
+ local stepcolor = 36
+ local finalcolor = 32
+ pprint_encoding = function (steps)
+ local nsteps, result = #steps, { }
+ for i=0, nsteps-1 do
+ result[i+1] = colorize(value_to_letter[steps[i]], stepcolor)
+ .. s_step
+ end
+ result[nsteps+1] = colorize(value_to_letter[steps[nsteps]],
+ finalcolor)
+ emit(2, tableconcat(result))
+ end
+
+ local init_announcement = colorize([[Initial position of rotors: ]],
+ 37)
+ pprint_init = function (init)
+ local result = ""
+ result = value_to_letter[init[1]] .. " "
+ .. value_to_letter[init[2]] .. " "
+ .. value_to_letter[init[3]]
+ emit(1, init_announcement .. colorize(stringupper(result), 34))
+ end
+
+ local machine_announcement =
+ [[Enigma machine initialized with the following settings.]] .. eol
+ local s_ukw = colorize(" Reflector:", 37)
+ local s_pb = colorize("Plugboard setting:", 37)
+ local s_ring = colorize(" Ring positions:", 37)
+ local empty_plugboard = colorize(" ——", 34)
+ pprint_new_machine = function (m)
+ local result = { eol }
+ result[#result+1] = underline(machine_announcement)
+ result[#result+1] = s_ukw
+ .. " "
+ .. colorize(
+ stringupper(value_to_letter[m.reflector]),
+ 34
+ )
+ local rings = ""
+ for i=1, 3 do
+ local this = m.ring[i]
+ rings = rings
+ .. " "
+ .. colorize(stringupper(value_to_letter[this + 1]), 34)
+ end
+ result[#result+1] = s_ring .. rings
+ if m.__raw.plugboard then
+ local tpb, pb = m.__raw.plugboard, ""
+ for i=1, #tpb do
+ pb = pb .. " " .. colorize(tpb[i], 34)
+ end
+ result[#result+1] = s_pb .. pb
+ else
+ result[#result+1] = s_pb .. empty_plugboard
+ end
+ result[#result+1] = ""
+ emit(1, tableconcat(result, eol))
+ pprint_rotor_scheme()
+ for i=1, 3 do
+ result[#result+1] = pprint_rotor(m.rotors[i])
+ end
+ emit(1, "")
+ end
+
+ local step_template = colorize([[Step № ]], 37)
+ local chr_template = colorize([[ —— Input ]], 37)
+ local pbchr_template = colorize([[ → ]], 37)
+ pprint_step = function (n, chr, pb_chr)
+ emit(2, eol
+ .. step_template
+ .. colorize(n, 34)
+ .. chr_template
+ .. colorize(stringupper(value_to_letter[chr]), 34)
+ .. pbchr_template
+ .. colorize(stringupper(value_to_letter[pb_chr]), 34)
+ .. eol)
+ end
+
+ -- Split the strings into lines, group them in bunches of five etc.
+ local tw = 30
+ local pprint_textblock = function (s)
+ local len = utf8len(s)
+ local position = 1 -- position in string
+ local nline = 5 -- width of current line
+ local out = utf8sub(s, position, position+4)
+ repeat
+ position = position + 5
+ nline = nline + 6
+ if nline > tw then
+ out = out .. eol .. utf8sub(s, position, position+4)
+ nline = 1
+ else
+ out = out .. " " .. utf8sub(s, position, position+4)
+ end
+ until position > len
+ return out
+ end
+
+ local intext = colorize([[Input text:]], 37)
+ local outtext = colorize([[Output text:]], 37)
+ pprint_ciphertext = function (input, output, upper_p)
+ if upper_p then
+ input = stringupper(input)
+ output = stringupper(output)
+ end
+ emit(1, eol
+ .. intext
+ .. eol
+ .. pprint_textblock(input)
+ .. eol .. eol
+ .. outtext
+ .. eol
+ .. pprint_textblock(output))
+ end
+
+--[[ichd
+\startparagraph
+Main stdout verbosity wrapper function. Checks if the global verbosity
+setting exceeds the specified threshold, and only then pushes the
+output.
+\stopparagraph
+--ichd]]--
+ emit = function (v, str)
+ if str and v and verbose_level >= v then
+ iowrite(str .. eol)
+ end
+ return 0
+ end
+end
+
+local new
+do
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Rotation]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startparagraph
+The following function \luafunction{do_rotate} increments the rotational
+state of a single rotor. There are two tests for notches:
+\startitemize[n]
+ \item whether it’s at the current character, and
+ \item whether it’s at the next character.
+\stopitemize
+The latter is an essential prerequisite for double-stepping.
+\stopparagraph
+--ichd]]--
+ local do_rotate = function (rotor)
+ rotor.state = rotor.state % 26 + 1
+ return rotor,
+ rotor.state == rotor.notch,
+ rotor.state + 1 == rotor.notch
+ end
+
+--[[ichd--
+\startparagraph
+The \luafunction{rotate} function takes care of rotor ({\em Walze})
+movement. This entails incrementing the next rotor whenever the notch
+has been reached and covers the corner case {\em double stepping}.
+\stopparagraph
+--ichd]]--
+ local rotate = function (machine)
+ local rotors = machine.rotors
+ local rc, rb, ra = rotors[1], rotors[2], rotors[3]
+
+ ra, nxt = do_rotate(ra)
+ if nxt or machine.double_step then
+ rb, nxt, nxxt = do_rotate(rb)
+ if nxt then
+ rc = do_rotate(rc)
+ end
+ if nxxt then
+ --- weird: home.comcast.net/~dhhamer/downloads/rotors1.pdf
+ machine.double_step = true
+ else
+ machine.double_step = false
+ end
+ end
+ machine.rotors = { rc, rb, ra }
+ end
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Input Preprocessing]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startparagraph
+Internally, we will use lowercase strings as they are a lot more
+readable than uppercase. Lowercasing happens prior to any further
+dealings with input. After the encoding or decoding has been
+accomplished, there will be an optional (re-)uppercasing.
+\stopparagraph
+
+\startparagraph
+Substitutions are applied onto the lowercased input. You might want
+to avoid some of these, above all the rules for numbers, because they
+translate single digits only. The solution is to write out numbers above
+ten.
+\stopparagraph
+--ichd]]--
+
+ local pp_substitutions = {
+ -- Umlauts are resolved.
+ ["ö"] = "oe",
+ ["ä"] = "ae",
+ ["ü"] = "ue",
+ ["ß"] = "ss",
+ -- WTF?
+ ["ch"] = "q",
+ ["ck"] = "q",
+ -- Punctuation -> “x”
+ [","] = "x",
+ ["."] = "x",
+ [";"] = "x",
+ [":"] = "x",
+ ["/"] = "x",
+ ["’"] = "x",
+ ["‘"] = "x",
+ ["„"] = "x",
+ ["“"] = "x",
+ ["“"] = "x",
+ ["-"] = "x",
+ ["–"] = "x",
+ ["—"] = "x",
+ ["!"] = "x",
+ ["?"] = "x",
+ ["‽"] = "x",
+ ["("] = "x",
+ [")"] = "x",
+ ["["] = "x",
+ ["]"] = "x",
+ ["<"] = "x",
+ [">"] = "x",
+ -- Spaces are omitted.
+ [" "] = "",
+ ["\n"] = "",
+ ["\t"] = "",
+ ["\v"] = "",
+ -- Numbers are resolved.
+ ["0"] = "null",
+ ["1"] = "eins",
+ ["2"] = "zwei",
+ ["3"] = "drei",
+ ["4"] = "vier",
+ ["5"] = "fünf",
+ ["6"] = "sechs",
+ ["7"] = "sieben",
+ ["8"] = "acht",
+ ["9"] = "neun",
+ }
+
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[
+ title={Main function chain to be applied to single characters},
+]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\startparagraph
+As far as the Enigma is concerned, there is no difference between
+encoding and decoding. Thus, we need only one function
+(\luafunction{encode_char}) to achieve the complete functionality.
+However, within every encoding step, characters will be wired
+differently in at least one of the rotors according to its rotational
+state. Rotation is simulated by adding the \identifier{state} field of
+each rotor to the letter value (its position on the ingoing end).
+\stopparagraph
+\placetable[here][table:dirs]{Directional terminology}{%
+ \starttabulate[|l|r|l|]
+ \NC boolean \NC direction \NC meaning \NC \AR
+ \NC true \NC “from” \NC right to left \NC \AR
+ \NC false \NC “to” \NC left to right \NC \AR
+ \stoptabulate%
+}
+\startparagraph
+The function \luafunction{do_do_encode_char} returns the character
+substitution for one rotor. As a letter passes through each rotor twice,
+the argument \identifier{direction} determines which way the
+substitution is applied.
+\stopparagraph
+--ichd]]--
+ local do_do_encode_char = function (char, rotor, direction)
+ local rw = rotor.wiring
+ local rs = rotor.state
+ local result = char
+ if direction then -- from
+ result = (result + rs - 1) % 26 + 1
+ result = rw.from[result]
+ result = (result - rs - 1) % 26 + 1
+ else -- to
+ result = (result + rs - 1) % 26 + 1
+ result = rw.to[result]
+ result = (result - rs - 1) % 26 + 1
+ end
+ return result
+ end
+
+--[[ichd
+\startparagraph
+Behind the plugboard, every character undergoes seven substitutions: two
+for each rotor plus the central one through the reflector. The function
+\luafunction{do_encode_char}, although it returns the final result only,
+keeps every intermediary step inside a table for debugging purposes.
+This may look inefficient but is actually a great advantage whenever
+something goes wrong.
+\stopparagraph
+--ichd]]--
+ --- ra -> rb -> rc -> ukw -> rc -> rb -> ra
+ local do_encode_char = function (rotors, reflector, char)
+ local rc, rb, ra = rotors[1], rotors[2], rotors[3]
+ local steps = { [0] = char }
+ --
+ steps[1] = do_do_encode_char(steps[0], ra, true)
+ steps[2] = do_do_encode_char(steps[1], rb, true)
+ steps[3] = do_do_encode_char(steps[2], rc, true)
+ steps[4] = reflector_wiring[reflector][steps[3]]
+ steps[5] = do_do_encode_char(steps[4], rc, false)
+ steps[6] = do_do_encode_char(steps[5], rb, false)
+ steps[7] = do_do_encode_char(steps[6], ra, false)
+ pprint_encoding_scheme()
+ pprint_encoding(steps)
+ return steps[7]
+ end
+
+--[[ichd
+\startparagraph
+Before an input character is passed on to the actual encoding routing,
+the function \luafunction{encode_char} matches it agains the latin
+alphabet. Characters that fail this check are, at the moment, returned
+as they were.
+\TODO{Make behaviour of \luafunction{encode_char} in case of invalid
+input configurable.}
+Also, the counter of encoded characters is incremented at this stage and
+some pretty printer hooks reside here.
+\stopparagraph
+
+\startparagraph
+\luafunction{encode_char} contributes only one element of the encoding
+procedure: the plugboard ({\em Steckerbrett}).
+Like the rotors described above, a character passed through this
+device twice; the plugboard marks the beginning and end of every step.
+For debugging purposes, the first substitution is stored in a separate
+local variable, \identifier{pb_char}.
+\stopparagraph
+--ichd]]--
+ local valid_char_p = letter_to_value
+
+ local encode_char = function (machine, char)
+ machine.step = machine.step + 1
+ machine:rotate()
+ local pb = machine.plugboard
+ --if valid_char_p[char] == nil then -- skip unwanted characters
+ -- return char
+ --end
+ char = letter_to_value[char]
+ local pb_char = pb[char] -- first plugboard substitution
+ pprint_step(machine.step, char, pb_char)
+ pprint_rotor_scheme()
+ pprint_rotor(machine.rotors[1])
+ pprint_rotor(machine.rotors[2])
+ pprint_rotor(machine.rotors[3])
+ char = do_encode_char(machine.rotors,
+ machine.reflector,
+ pb_char)
+ return value_to_letter[pb[char]] -- second plugboard substitution
+ end
+
+ local get_random_pattern = function ()
+ local a, b, c = mathrandom(1,26), mathrandom(1,26), mathrandom(1,26)
+ return value_to_letter[a]
+ .. value_to_letter[b]
+ .. value_to_letter[c]
+ end
+
+ local pattern_to_state = function (pat)
+ return {
+ letter_to_value[stringsub(pat, 1, 1)],
+ letter_to_value[stringsub(pat, 2, 2)],
+ letter_to_value[stringsub(pat, 3, 3)],
+ }
+ end
+
+ local set_state = function (machine, state)
+ local rotors = machine.rotors
+ for i=1, 3 do
+ rotors[i].state = state[i] - 1
+ end
+ end
+
+--[[ichd
+\startparagraph
+As the actual encoding proceeds character-wise, the processing of entire
+strings needs to be managed externally. This is where
+\luafunction{encode_string} comes into play: It handles iteration and
+extraction of successive characters from the sequence.
+\TODO{Make \luafunction{encode_string} preprocess characters.}
+\stopparagraph
+--ichd]]--
+ local encode_string = function (machine, str) --, pattern)
+ local init_state = pattern_to_state(pattern or get_random_pattern())
+ pprint_init(init_state)
+ machine:set_state(init_state)
+ local result = { }
+ str = stringlower(str)
+ local n = 1 -- OPTIONAL could lookup machine.step instead
+ for char in utfcharacters(str) do
+ result[n] = machine:encode_char(char)
+ n = n + 1
+ end
+ return tableconcat(result)
+ end
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd--
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Initialization string parser]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\placetable[here][]{Initialization strings}{%
+ \bTABLE
+ \bTR
+ \bTD Reflector \eTD
+ \bTD[nc=3] Rotor \eTD
+ \bTD Initial rotor \eTD
+ \bTD Plugboard wiring \eTD
+ \eTR
+ \eTR
+ \bTR
+ \bTD in slot \eTD \bTD[nc=3] setting \eTD
+ \eTR
+ \bTR
+ \bTD \eTD
+ \bTD 1 \eTD\bTD 2 \eTD\bTD 3 \eTD
+ \bTD 1 \eTD\bTD 2 \eTD\bTD 3 \eTD
+ \bTD 1 \eTD\bTD 2 \eTD\bTD 3 \eTD\bTD 4 \eTD\bTD 5 \eTD
+ \bTD 6 \eTD\bTD 7 \eTD\bTD 8 \eTD\bTD 9 \eTD\bTD 10 \eTD
+ \eTR
+ \bTR
+ \bTD B \eTD
+ \bTD I \eTD\bTD IV \eTD\bTD III \eTD
+ \bTD 16 \eTD\bTD 26 \eTD\bTD 08 \eTD
+ \bTD AD \eTD\bTD CN \eTD\bTD ET \eTD
+ \bTD FL \eTD\bTD GI \eTD\bTD JV \eTD
+ \bTD KZ \eTD\bTD PU \eTD\bTD QY \eTD
+ \bTD WX \eTD
+ \eTR
+ \eTABLE
+}
+--ichd]]--
+ local roman_digits = {
+ i = 1, I = 1,
+ ii = 2, II = 2,
+ iii = 3, III = 3,
+ iv = 4, IV = 4,
+ v = 5, V = 5,
+ }
+
+ local p_init = P{
+ "init",
+ init = Ct(V"do_init"),
+ do_init = V"reflector" * V"whitespace"
+ * V"rotors" * V"whitespace"
+ * V"ring"
+ * (V"whitespace" * V"plugboard")^-1
+ ,
+ reflector = Cg(C(R("ac","AC")) / stringlower, "reflector"),
+
+ rotors = Cg(Ct(V"rotor" * V"whitespace"
+ * V"rotor" * V"whitespace"
+ * V"rotor"),
+ "rotors")
+ ,
+ rotor = Cs(V"roman_five" / roman_digits
+ + V"roman_four" / roman_digits
+ + V"roman_three" / roman_digits
+ + V"roman_two" / roman_digits
+ + V"roman_one" / roman_digits)
+ ,
+ roman_one = P"I" + P"i",
+ roman_two = P"II" + P"ii",
+ roman_three = P"III" + P"iii",
+ roman_four = P"IV" + P"iv",
+ roman_five = P"V" + P"v",
+
+ ring = Cg(Ct(V"double_digit" * V"whitespace"
+ * V"double_digit" * V"whitespace"
+ * V"double_digit"),
+ "ring")
+ ,
+ double_digit = C(R"02" * R"09"),
+
+ plugboard = Cg(V"do_plugboard", "plugboard"),
+ --- no need to enforce exactly ten substitutions
+ --do_plugboard = Ct(V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination" * V"whitespace"
+ -- * V"letter_combination")
+ do_plugboard = Ct(V"letter_combination"
+ * (V"whitespace" * V"letter_combination")^0)
+ ,
+ letter_combination = C(R("az", "AZ") * R("az", "AZ")),
+
+ whitespace = S" \n\t\v"^1,
+ }
+
+
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd--
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Initialization routines]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\startparagraph
+The plugboard is implemented as a pair of hash tables.
+\stopparagraph
+--ichd]]--
+ local get_plugboard_substitution = function (p)
+ -- Plugboard wirings are symmetrical, thus we have one table for each
+ -- direction.
+ local tmp, result = { }, { }
+ for _, str in next, p do
+ local one, two = stringlower(stringsub(str, 1, 1)),
+ stringlower(stringsub(str, 2))
+ tmp[one] = two
+ tmp[two] = one
+ end
+ local n_letters = 26
+
+ local lv = letter_to_value
+ for n=1, n_letters do
+ local letter = value_to_letter[n]
+ local sub = tmp[letter] or letter
+ -- Map each char either to the plugboard substitution or itself.
+ result[lv[letter]] = lv[sub or letter]
+ end
+ return result
+ end
+
+--[[ichd--
+\startparagraph
+Initialization of the rotors requires some precautions to be taken.
+The most obvious of which is adjusting the displacement of its wiring by
+the ring setting.
+\stopparagraph
+\startparagraph
+Another important task is to store the notch position in order for it
+to be retrievable by the rotation subroutine at a later point.
+\stopparagraph
+\startparagraph
+The actual bidirectional mapping is implemented as a pair of tables.
+The initial order of letters, before the ring shift is applied, is
+alphabetical on the input (right, “from”) side and, on the output (left,
+“to”) side taken by the hard wired correspondence as specified in the
+rotor wirings above.
+NB the descriptions in terms of “output” and “input” directions is
+misleading in so far as during any encoding step the electricity will
+pass through every rotor in both ways.
+Hence, the “input” (right, from) direction literally applies only to the
+first half of the encoding process between plugboard and reflector.
+\stopparagraph
+\startparagraph
+The function \luafunction{do_get_rotor} creates a single rotor
+instance and populates it with character mappings. The \identifier{from}
+and \identifier{to} subfields of its \identifier{wiring} field represent the
+wiring in the respective directions.
+This initital wiring was specified in the corresponding
+\identifier{raw_rotor_wiring} table; the ringshift is added modulo the
+alphabet size in order to get the correctly initialized rotor.
+\stopparagraph
+--ichd]]--
+ local do_get_rotor = function (raw, notch, ringshift)
+ local rotor = {
+ wiring = {
+ from = { },
+ to = { },
+ },
+ state = 0,
+ notch = notch,
+ }
+ local w = rotor.wiring
+ for from=1, 26 do
+ local to = letter_to_value[stringsub(raw, from, from)]
+ --- The shift needs to be added in both directions.
+ to = (to + ringshift - 1) % 26 + 1
+ from = (from + ringshift - 1) % 26 + 1
+ rotor.wiring.from[from] = to
+ rotor.wiring.to [to ] = from
+ end
+ --table.print(rotor, "rotor")
+ return rotor
+ end
+
+--[[ichd--
+\startparagraph
+Rotors are initialized sequentially accordings to the initialization
+request.
+The function \luafunction{get_rotors} walks over the list of
+initialization instructions and calls \luafunction{do_get_rotor} for the
+actual generation of the rotor table. Each rotor generation request
+consists of three elements:
+\stopparagraph
+\startitemize[n]
+ \item the choice of rotor (one of five),
+ \item the notch position of said rotor, and
+ \item the ring shift.
+\stopitemize
+--ichd]]--
+ local get_rotors = function (rotors, ring)
+ local s, r = { }, { }
+ for n=1, 3 do
+ local nr = tonumber(rotors[n])
+ local ni = tonumber(ring[n]) - 1 -- “1” means shift of zero
+ r[n] = do_get_rotor(raw_rotor_wiring[nr], notches[nr], ni)
+ s[n] = ni
+ end
+ return r, s
+ end
+
+ local decode_char = encode_char -- hooray for involutory ciphers
+
+ local encode_general = function (machine, chr)
+ local replacement = pp_substitutions[chr] or valid_char_p[chr] and chr
+ if not replacement then return false end
+ if utf8len(replacement) == 1 then
+ return encode_char(machine, chr)
+ end
+ local result = { }
+ for chr in next, utfcharacters(replacement) do
+ result[#result+1] = encode_char(machine, chr)
+ end
+ return result
+ end
+
+ local process_message_key
+ local alpha = R"az"
+ local alpha_dec = alpha / letter_to_value
+ local whitespace = S" \n\t\v"
+ local mkeypattern = Ct(alpha_dec * alpha_dec * alpha_dec)
+ * whitespace^0
+ * C(alpha * alpha *alpha)
+ process_message_key = function (machine, message_key)
+ message_key = stringlower(message_key)
+ local init, three = lpegmatch(mkeypattern, message_key)
+ -- to be implemented
+ end
+
+ local decode_string = function (machine, str, message_key)
+ machine.kenngruppe, str = stringsub(str, 3, 5), stringsub(str, 6)
+ machine:process_message_key(message_key)
+ local decoded = encode_string(machine, str)
+ return decoded
+ end
+
+ local testoptions = {
+ size = 42,
+
+ }
+ local generate_header = function (options)
+ end
+
+ local processed_chars = function (machine)
+ pprint_machine_step(machine.step)
+ end
+ new = function (setup_string, pattern)
+ local raw_settings = lpegmatch(p_init, setup_string)
+ local rotors, ring =
+ get_rotors(raw_settings.rotors, raw_settings.ring)
+ local plugboard = raw_settings.plugboard
+ and get_plugboard_substitution(raw_settings.plugboard)
+ or get_plugboard_substitution{ }
+ local machine = {
+ step = 0, -- n characters encoded
+ init = {
+ rotors = raw_settings.rotors,
+ ring = raw_settings.ring
+ },
+ rotors = rotors,
+ ring = ring,
+ state = init_state,
+ ---> a>1, b>2, c>3
+ reflector = letter_to_value[raw_settings.reflector],
+ plugboard = plugboard,
+ --- functionality
+ rotate = rotate,
+ --process_message_key = process_message_key,
+ encode_string = encode_string,
+ encode_char = encode_char,
+ encode = encode_general,
+ decode_string = decode_string,
+ decode_char = decode_char,
+ set_state = set_state,
+ processed_chars = processed_chars,
+ --- <badcodingstyle>
+ __raw = raw_settings -- hackish but occasionally useful
+ --- </badcodingstyle>
+ }
+ local init_state = pattern_to_state(pattern or get_random_pattern())
+ pprint_init(init_state)
+ machine:set_state(init_state)
+
+ --table.print(machine.rotors)
+ pprint_new_machine(machine)
+ return machine
+ end
+end
+
+--[[ichd--
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Format Dependent Code]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\startparagraph
+Exported functionality will be collected in the table
+\identifier{enigma}.
+\stopparagraph
+--ichd]]--
+
+local enigma = { }
+
+--[[ichd
+\startparagraph
+Afaict, \LATEX\ for \LUATEX\ still lacks a globally accepted namespacing
+convention. This is more than bad, but we’ll have to cope with that. For
+this reason we brazenly introduce \identifier{packagedata} (in analogy
+to \CONTEXT’s \identifier{thirddata}) table as a package namespace
+proposal. If this module is called from a \LATEX\ or plain session, the
+table \identifier{packagedata} will already have been created so we will
+identify the format according to its presence or absence, respectively.
+\stopparagraph
+--ichd]]--
+
+if packagedata then -- latex or plain
+ packagedata.enigma = enigma
+elseif thirddata then -- context
+ packagedata.enigma = enigma
+else -- external call, mtx-script or whatever
+ _G.enigma = enigma
+end
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd--
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\startsection[title=Setup Argument Handling]
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\startparagraph
+As the module is intended to work both with the Plain and \LATEX\
+formats as well as \CONTEXT, we can’t rely on format dependent setups.
+Hence the need for an argument parser. Should be more efficient anyways
+as all the functionality resides in Lua.
+\stopparagraph
+--ichd]]--
+
+local p_args = P{
+ "args",
+ args = Cf(Ct"" * (V"kv_pair" + V"emptyline")^0, rawset),
+ kv_pair = Cg(V"key"
+ * V"separator"
+ * (V"value" * V"final"
+ + V"empty"))
+ * V"rest_of_line"^-1
+ ,
+ key = V"whitespace"^0 * C(V"key_char"^1),
+ key_char = (1 - V"whitespace" - V"eol" - V"equals")^1,
+ separator = V"whitespace"^0 * V"equals" * V"whitespace"^0,
+ empty = V"whitespace"^0 * V"comma" * V"rest_of_line"^-1
+ * Cc(false)
+ ,
+ value = C((V"balanced" + (1 - V"final"))^1),
+ final = V"whitespace"^0 * V"comma" + V"rest_of_string",
+ rest_of_string = V"whitespace"^0 * V"eol_comment"^-1 * V"eol"^0 * V"eof",
+ rest_of_line = V"whitespace"^0 * V"eol_comment"^-1 * V"eol",
+ eol_comment = V"comment_string" * (1 - (V"eol" + V"eof"))^0,
+ comment_string = V"lua_comment" + V"TeX_comment",
+ TeX_comment = V"percent",
+ lua_comment = V"double_dash",
+ emptyline = V"rest_of_line",
+
+ balanced = V"balanced_brk" + V"balanced_brc",
+ balanced_brk = V"lbrk" * (V"balanced" + (1 - V"rbrk"))^0 * V"rbrk",
+ balanced_brc = V"lbrc" * (V"balanced" + (1 - V"rbrc"))^0 * V"rbrc",
+
+ -- Terminals
+ eol = P"\n\r" + P"\r\n" + P"\n" + P"\r", -- users do strange things
+ eof = -P(1),
+ whitespace = S" \t\v",
+ equals = P"=",
+ dot = P".",
+ comma = P",",
+ dash = P"-", double_dash = V"dash" * V"dash",
+ percent = P"%",
+ lbrk = P"[", rbrk = P"]",
+ lbrc = P"{", rbrc = P"}",
+}
+
+
+--[[ichd
+\startparagraph
+In the next step we process the arguments, check the input for sanity
+etc. The function \luafunction{parse_args} will test whether a value has
+a sanitizer routine and, if so, apply it to its value.
+\stopparagraph
+--ichd]]--
+
+do
+ local boolean_synonyms = {
+ ["1"] = true,
+ doit = true,
+ indeed = true,
+ ok = true,
+ ["⊤"] = true,
+ ["true"] = true,
+ yes = true,
+ }
+ local toboolean = function (value) return boolean_synonyms[value] or false end
+ local alpha = R("az", "AZ")
+ local digit = R"09"
+ local space = S" \t\v"
+ local ans = alpha + digit + space
+ local p_ans = Cs((ans + (1 - ans / ""))^1)
+ local alphanum_or_space = function (str)
+ if type(str) ~= "string" then return "" end
+ return lpegmatch(p_ans, str)
+ end
+
+ local sanitizers = {
+ other_chars = toboolean,
+ day_key = alphanum_or_space,
+ }
+ enigma.parse_args = function (raw)
+ local args = lpegmatch(p_args, raw)
+ for k, v in next, args do
+ local f = sanitizers[k]
+ args[k] = f(v)
+ end
+ return args
+ end
+end
+
+--[[ichd
+\stopsection
+--ichd]]--
+
+--[[ichd
+\startsection[title=Callback]
+\startparagraph
+This is the interface to \TEX.
+\stopparagraph
+--ichd]]--
+
+enigma.new_callback = function (machine)
+ enigma.current_machine = machine
+ return function (head)
+ for n in nodetraverse(head) do
+ --print(node, node.id)
+ if n.id == glyph_node then
+ local chr = utf8char(n.char)
+ local replacement = machine:encode(chr)
+ if replacement == false then
+ --noderemove(head, n)
+ elseif type(replacement) == "string" then
+ local insertion = nodecopy(n)
+ insertion.char = utf8byte(replacement)
+ nodeinsert_before(head, n, insertion)
+ print(n.char, insertion.char)
+ end
+ noderemove(head, n)
+ elseif n.id == glue_node then
+ -- spaces are dropped
+ noderemove(head, n)
+ end
+ end
+ return head
+ end
+end
+
+--local teststring = [[B I II III 01 01 01]]
+enigma.new_machine = function (args, pattern)
+ local machine = new(args.day_key, pattern)
+ return machine
+end --stub
+
+--[[ichd
+\stopsection
+--ichd]]--
+------------------------------------------------------------------------
+--enigma.testit = function(args)
+-- print""
+-- print">>>>>>>>>>>>>>>>>>>>>>>>>>"
+-- --for i,j in next, _G do print(i,j) end
+-- print">>>>>>>>>>>>>>>>>>>>>>>>>>"
+-- --print(table.print)
+--end
+
+
+--local teststring = [[B I IV III 16 26 08 AD CN ET FL GI JV KZ PU QY WX]]
+--local teststring = [[B I II III 01 01 01 AD CN ET FL GI JV KZ PU QY WX]]
+local teststring = [[B I II III 01 01 01]]
+--local teststring = [[B I II III 01 01 02]]
+--local teststring = [[B I II III 02 02 02]]
+--local teststring = [[B I IV III 16 26 08 AD CN ET FL GI JV KZ PU QY WX]]
+--local teststring = [[B I IV III 16 26 08]]
+--local teststring = [[B I IV III 01 01 02]]
+
+--local testtext = [[
+--DASOB ERKOM MANDO DERWE HRMAQ TGIBT BEKAN NTXAA CHENX AACHE
+--NXIST GERET TETXD URQGE BUEND ELTEN EINSA TZDER HILFS KRAEF
+--TEKON NTEDI EBEDR OHUNG ABGEW ENDET UNDDI ERETT UNGDE RSTAD
+--TGEGE NXEIN SXAQT XNULL XNULL XUHRS IQERG ESTEL LTWER DENX
+--]]
+--
+--local testtext2 = [[
+--XYOWN LJPQH SVDWC LYXZQ FXHIU VWDJO BJNZX RCWEO TVNJC IONTF
+--QNSXW ISXKH JDAGD JVAKU KVMJA JHSZQ QJHZO IAVZO WMSCK ASRDN
+--XKKSR FHCXC MPJGX YIJCC KISYY SHETX VVOVD QLZYT NJXNU WKZRX
+--UJFXM BDIBR VMJKR HTCUJ QPTEE IYNYN JBEAQ JCLMU ODFWM ARQCF
+--OBWN
+--]]
+
+--local ea = environment.argument
+--local main = function ()
+-- --local init_setting = { 1, 2, 3 }
+-- local machine = new(teststring)
+--
+-- local plaintext = ea"s"
+-- --local plaintext = testtext2
+-- --local message_key = [[QWE EWG]]
+-- --local ciphertext = machine:encode_string(plaintext, "rtz")
+-- local ciphertext = machine:encode_string(plaintext, "aaa")
+-- --local cyphertext = machine:encode_string(plaintext)
+-- --local cyphertext = machine:decode_string(plaintext, message_key)
+-- pprint_ciphertext(plaintext, ciphertext, true)
+--end
+
+--return main()
+
+-- vim:ft=lua:sw=2:ts=2:tw=72
diff --git a/tex/latex/enigma/enigma.sty b/tex/latex/enigma/enigma.sty
new file mode 100644
index 0000000..eee2cb5
--- /dev/null
+++ b/tex/latex/enigma/enigma.sty
@@ -0,0 +1,3 @@
+\ProvidesPackage{enigma}[2012-02-20 21:16:13+0100 enigma]
+\input{enigma}
+% vim:ft=tex:sw=2:ts=2:expandtab:tw=72
diff --git a/tex/plain/enigma/enigma.tex b/tex/plain/enigma/enigma.tex
new file mode 100644
index 0000000..6827878
--- /dev/null
+++ b/tex/plain/enigma/enigma.tex
@@ -0,0 +1,83 @@
+\input{luatexbase.sty}
+%D \startsection[title=Prerequisites]
+%D \startparagraph
+%D Package loading and the namespacing issue are commented on in
+%D \identifier{enigma.lua}.
+%D \stopparagraph
+\directlua{%
+ packagedata = packagedata or { }
+ dofile(kpse.find_file"enigma.lua")
+}
+
+%D \startparagraph
+%D First, create somthing like \CONTEXT’s asciimode. We found
+%D \texmacro{\newluatexcatcodetable} in \identifier{luacode.sty} and it
+%D seems to get the job done.
+%D \stopparagraph
+\newluatexcatcodetable \enigmasetupcatcodes
+\bgroup
+ \def\escapecatcode {0}
+ \def\begingroupcatcode {1}
+ \def\endgroupcatcode {2}
+ \def\spacecatcode {10}
+ \def\lettercatcode {11}
+ \setluatexcatcodetable\enigmasetupcatcodes {
+ \catcode`\^^I = \spacecatcode % tab
+ \catcode`\ = \spacecatcode
+ \catcode`\{ = \begingroupcatcode
+ \catcode`\} = \endgroupcatcode
+ \catcode`\^^L = \lettercatcode % form feed
+ \catcode`\^^M = \lettercatcode % eol
+ }
+\egroup
+%D \stopsection
+
+%D \startsection[title=Setups]
+%D \startparagraph
+%D Once the proper catcodes are in place, the setup macro
+%D \texmacro{do-setup_enigma} doesn’t to anything besides passing stuff
+%D through to Lua.
+%D \stoppparagraph
+\def\do_setup_enigma#1{%
+ \directlua{
+ local enigma = packagedata.enigma
+ local current_args = enigma.parse_args([====[\detokenize{#1}]====])
+ enigma.current = enigma.new_callback(enigma.new_machine(current_args, "aaa"))
+ }%
+ \egroup%
+}
+
+%D The module setup \texmacro{setupenigma} expects key=value, notation.
+%D All the logic is at the Lua end, not much to see here …
+\def\setupenigma{%
+ \bgroup
+ \luatexcatcodetable \enigmasetupcatcodes
+ \do_setup_enigma%
+}
+%D \stopsection
+
+%D \startsection[title=Encoding Macros]
+%D \startparagraph
+%D The environment of \texmacro{\startenigma} and \texmacro{\stopenigma}
+%D allow enabling of Enigma encoding in different parts of the document.
+%D \stopparagraph
+
+\def\startenigma{%
+ \bgroup%
+ \directlua{%
+ if packagedata.enigma and packagedata.enigma.current then
+ luatexbase.add_to_callback("pre_linebreak_filter",
+ packagedata.enigma.current,
+ "enigma")
+ end
+ }%
+}
+
+\def\stopenigma{%
+ \directlua{luatexbase.remove_from_callback("pre_linebreak_filter", "enigma")
+ packagedata.enigma.current_machine:processed_chars()}%
+ \egroup%
+}
+%D \stopsection
+
+% vim:ft=tex:sw=2:ts=2:expandtab:tw=72