Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
#!/usr/bin/env lua51
-- dednat5.lua - a TeX preprocessor to typeset trees and diagrams
-- This is a rewrite of dednat4.
-- Status: this is a very preliminary version, don't expect it
-- to work...

-- See:
-- (find-es "dednat" "dednat5")



-- Reescrevendo o dednat4:
--   prefixos pra encurtar tudo:
--   A: abbreviations
--   D: diagrams
--   T: trees
--   F: Forth

-- See: (find-dn4ex "edrxmain.tex")
-- and: (find-dn4ex "edrxmain41.tex")
-- and: (find-dn4ex "edrxmain41a.tex")
-- Written by: Eduardo Ochs <eduardoochs@gmail.com>
-- Current revision: 2010apr21
-- For the full history etc see some text that I haven't written yet.
-- See: http://angg.twu.net/dednat4.html
-- See: http://angg.twu.net/dednat4/
-- License: GPL
--
-- «.prefixes-and-ptables»	(to "prefixes-and-ptables")
-- «.heads»			(to "heads")

-- «.dednat4dir»	(to "dednat4dir")
-- «.edrxlib»		(to "edrxlib")
-- «.compat.lua»	(to "compat.lua")
-- «.string-methods»	(to "string-methods")
--
-- «.heads»		(to "heads")
-- «.processfile»	(to "processfile")
-- «.abbrevs»		(to "abbrevs")

-- Terminology and main ideas
-- ==========================
-- A dictionary       is a table whose keys are all strings.
-- A prefix_table     is a dictionary in which "partial matches" point to 0.
--   prefix_tables    are used for two things:
--     line_heads,    where the expansions are functions,
--     abbrevs,       where the expansions are strings.
-- expand             is the function that expands all abbrevs in a string.
-- 
-- Each tree_line     is split into tree_words.
-- Each tree_word     may be a tree_node, a tree_bar, or a tree_name, or garbage.
-- Each tree_bar      is split into a tree_bar_char and a tree_bar_comment.
-- tree_dict          is a dictionary that maps "tree word names", like "tw:20:2"
--                    (the second tree word in line 20 of the .tex) to structures.
-- If a tree_word     is a tree_bar we get the list of tree_nodes above it.
-- If a tree_word     is a tree_name we look for the tree_word above its "^".
-- If a tree_word     is a tree_node we look for a tree_bar above it.
-- tree_nodes_between is the low-level function used to find "tree_words above".
-- tree_output        is called at each tree_name. It may be:
--   tree_output_pt   for Paul Taylor's version,
--   tree_output_tat  for Makoto Tatsuta's package,
--   tree_output_buss for Sam Buss's package (currently unimplemented).
-- tree_output_body   is like tree_output, but without the "\def" header.
-- 
-- Each diag_line     is a sequence of words, to be executed one by one.
-- Each diag_word     is executed by running the correspondent function in _D,
--                    or by pushing the diag_node with that name into the stack.
--   diag_this_word   is the variable that holds the word that we will execute.
--   diag_this_col    is the "column pointer"; it is used and modified by words
--                    that take over the parser to read and process immediate data.
-- 
-- Each stack         has methods push, pop, pick, pock.
-- 
-- For running the diag language we have several stacks:
--   diag_nodes,      for nodes declared in 2D, plus maybe others;
--   diag_arrows,     that are used by diag_output (in enddiagram);
--   diag_contexts,   for the (( ))s.
-- diag_node_names    is a dictionary that lets us refer to nodes by their 2D names.
--                    
-- diag_output        produces a 
-- 
-- TO DO: abbrevs with arguments.






eval = function (str) return assert(loadstring(str))() end




--%%%%%
--%
--% «prefixes-and-ptables»  (to ".prefixes-and-ptables")
--%
--%%%%%

-- Tests:
-- (find-dn4 "dednat5-shadow.lua" "expansion")

-- Internal functions with ugly names:
prefix_longest = function (str, j, ptable)
    local bestk, expansion
    for k=j,#str do
      local candidate = string.sub(str, j, k)
      if ptable[candidate] == nil then return bestk, expansion end
      if ptable[candidate] ~= 0   then
        bestk, expansion = k, ptable[candidate]
      end
    end
    return bestj, expansion
  end
prefix_fixed_then_longest = function (str, i, ptable)
    for j=i,#str do
      local k, expansion = prefix_longest(str, j, ptable)
      if k then return j, k, expansion end
    end
  end
expand_all = function (str, i, ptable, expansions)
    local j, k, expansion = prefix_fixed_then_longest(str, i, ptable)
    if j then
      tinsert(expansions, string.sub(str, i, j-1))
      tinsert(expansions, expansion)
      return expand_all(str, k+1, ptable, expansions)
    else
      tinsert(expansions, string.sub(str, i))
      return expansions
    end
  end
ptable_add = function (ptable, key, expansion)
    ptable[key] = expansion
    for len=#key-1,1,-1 do
      local subkey = string.sub(key, 1, len)
      if ptable[subkey] == nil then
        ptable[subkey] = 0
      else
        break
      end
    end
  end

-- Call as: ptable_adds(ptable, key1, exp1, key2, exp2, ...)
ptable_adds = function (ptable, ...)
    local args = pack(...)
    for i=1,#args,2 do
      ptable_add(ptable, args[i], args[i+1])
    end
  end
-- Call as: ptable_adds(ptable, key1, key2, ...)
ptable_dels = function (ptable, ...)
    for _,key in ipairs({...}) do
      ptable[key] = 0
    end
  end

-- Interface functions with nice names:
expansion_table = {}
expansion_adds = function (...) ptable_adds(expansion_table, ...) end
expansion_add  = expansion_adds
expansion_del  = function (...) ptable_dels(expansion_table, ...) end
expand = function (str)
    return table.concat(expand_all(str, 1, expansion_table, {}))
  end




--%%%%%
--%
--% «heads»  (to ".heads")
--%
--%%%%%

-- Tests:
-- (find-dn4 "dednat5-shadow.lua" "heads")

head_table = {}
head_add = function (...) ptable_adds(head_table, ...) end
head_add("", "NONE")
head_for = function (str)
    local k, expansion = prefix_longest(str, 1, head_table)
    if k then
      return string.sub(str, 1, k), string.sub(str, k+1), expansion
    end
    return "", str, head_table[""]
  end

-- process_blocks is less than dednat[45]'s "processfile".
-- (find-dn4 "dednat5.lua" "processfile")
process_block = function ()
    linestr = flines[linen]
    prefix, rest, head = head_for(linestr)
    local beforefirst = head.beforefirst or function () end
    local aftereach   = head.aftereach   or function () end
    local afterlast   = head.afterlast   or function () end
    if head.afterlast then
      beforefirst()
      aftereach(rest)
      while flines[linen+1] and head_for(flines[linen+1]) == prefix do
	linen = linen + 1
	linestr = flines[linen]
        prefix, rest, head = head_for(linestr)
	aftereach(rest);
      end
      afterlast()
    else
      aftereach(linestr)
    end
    linen = linen + 1
  end

process_blocks = function ()
    while flines[linen] do process_block() end
  end







--%%%%
--%
--% «abbrevs»  (to ".abbrevs")
--%
--%%%%

abbrevs = {}
abbrev_add = function (abbrev, expansion)
    for i=1,#abbrev-1 do
      local s = abbrev:sub(1,i)
      abbrevs[s] = abbrevs[s] or 0  -- make s a prefix (if it is not an abbrev)
    end
    abbrevs[abbrev] = expansion
  end
abbrev_longest = function (str, i)
    local bestj, abbrev, expansion
    for j=i,#str do
      local teststr = str:sub(i, j)
      local a = abbrevs[teststr]   -- is teststr a prefix, or an abbrev?
      if a == nil then break end   -- it is neither: break the loop
      if a ~= 0   then             -- it's an abbrev: save j and a and continue
        bestj, abbrev, expansion = j, teststr, a
      end
      -- if a == 0 then PP(i, j, teststr, "is a prefix and not an abbrev") end
    end
    return bestj, abbrev, expansion
  end
abbrev_expand1 = function (str, i)
    for j=i,#str do
      local k, abbrev, expansion = abbrev_longest(str, j)
      if k then
        local literal = str:sub(i, j-1)  -- the literal part before the abbrev
        return j, k, literal, abbrev, expansion
      end
      -- PP(i, j, str:sub(j), "does not start with an abbrev")
    end
  end
unabbrev = function (str)
    local result = {}
    local i = 1
    while i <= #str do
      local j, k, literal, abbrev, expansion = abbrev_expand1(str, i)
      if j then
        table.insert(result, literal)
        table.insert(result, expansion)
        i = k + 1
      else
        table.insert(result, str:sub(i))
        break
      end
    end
    -- PP(result)
    return table.concat(result)
  end
addabbrevs = function (...)
    local arg = {...}
    for i=1,#arg,2 do
      abbrev_add(arg[i], arg[i+1])
    end
  end

--[[
-- Tests:

* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
abbrevs = {}
abbrev_add("cde", "CCDDEE")
PP(abbrevs)
  --> {"c"=0, "cd"=0, "cde"="CCDDEE"}
= abbrev_expand1("abcdefg", 1)
  --> 3  5  ab  cde  CCDDEE
= abbrev_expand1("abcdefg", 2)
  --> 3  5  b  cde  CCDDEE
= unabbrev("abcdefg")
  --> abCCDDEEfg
= unabbrev("abcdefg_cd_cde__")
  --> abCCDDEEfg_cd_CCDDEE__
abbrev_add("c", "**")
= unabbrev("abcdefg_cd_cde__")
  --> abCCDDEEfg_**d_CCDDEE__
abbrevs = {}
addabbrevs("cde", "CCDDEE", "c", "**")
PP(abbrevs)
  --> {"c"="**", "cd"=0, "cde"="CCDDEE"}

--]]

-- «standardabbrevs»  (to ".standardabbrevs")
-- (find-dn4ex "edrx08.sty")
standardabbrevs = function ()
    addabbrevs(
      "->^", "\\ton ",   "`->", "\\ito ", "-.>", "\\tnto ",
      "=>",  "\\funto ", "<->", "\\bij ", "->",  "\\to ",
      "|-",  "\\vdash ", "|->", "\\mto ", "\"",  " ")
  end

-- Local Variables:
-- coding: raw-text-unix
-- End: