Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
#!/usr/bin/env lua51
-- dednat4.lua - a TeX preprocessor to typeset trees and diagrams
-- See: (find-dn4ex "edrxmain.tex")
-- and: (find-dn4ex "edrxmain41.tex")
--      (find-dn4ex "edrxmain41a.tex")
--      (find-dn4 "Makefile" "dednat41")
-- Written by: Eduardo Ochs <eduardoochs@gmail.com>
-- Current revision: 2008sep13
-- 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
--
-- «.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")
-- «.standardabbrevs»	(to "standardabbrevs")
-- «.abbrev-head»	(to "abbrev-head")
-- «.tex-head»		(to "tex-head")
-- «.dntfiles»		(to "dntfiles")
-- «.undefs»		(to "undefs")
-- «.process»		(to "process")
-- «.lua-head»		(to "lua-head")
-- «.tree-lib»		(to "tree-lib")
-- «.tree-out»		(to "tree-out")
-- «.tex_tree_function»	(to "tex_tree_function")
-- «.tree-head»		(to "tree-head")
-- «.diag-out»		(to "diag-out")
-- «.diagram»		(to "diagram")
-- «.enddiagram»	(to "enddiagram")
-- «.diag-2d»		(to "diag-2d")
-- «.diag-forth»	(to "diag-forth")
-- «.diag-forth-parser»	(to "diag-forth-parser")
-- «.diag-words»	(to "diag-words")
-- «.diag-arrows»	(to "diag-arrows")
-- «.lplacement»	(to "lplacement")
-- «.diag-head»		(to "diag-head")
-- «.main»		(to "main")

-- Some data structures and variables:

-- For heads:
-- A "head" is a table with these fields:
--   head.headstr     a prefix string, e.g. "%:"; set by registerhead
--   head.beforefirst = function (linestr) or nil
--   head.aftereach   = function (linestr)
--   head.afterlast   = function () or nil
-- The table "heads" is an array of headstr -> head. 

-- For processfile:
-- sourcefname is the file name of the file being processed.
-- linen       is the current line number.
-- linestr     is the text of the current line.
-- flines      is an array of linestrs (i.e., flines[linen] is a linestr).

-- For abbrevs:
-- abbrevs   is a table; abbrevs[str] is the expansion of str, or nil if none.
-- isprefix  is a table, generated from abbrevs; isprefix[str] is a boolean.

-- For trees:
-- A "tline" is a table with these fields:
--   tline.linen  an integer
--   tline.text   a  string
--   tline.segs   a  list of "seg"s
-- The table "tlines" is an array of linen -> tline.
-- It only has entries for the "%:" lines.
-- A "seg" is a table with these fields:
--   seg.linen  an integer
--   seg.segn   an integer - for any seg s we have tlines[s.linen][s.segn]==s
--   seg.lcol   an integer
--   seg.rcol   an integer
--   seg.text   a  string

-- For Forth:
-- ...

-- For diagrams:
-- ...



-- (find-dn4 "examples/edrxmain41.tex")
-- (find-angg "LUA/lua50init.lua" "load_dednat4")

-- «dednat4dir»  (to ".dednat4dir")
-- Put the directory where this script is at the
-- front of the list of paths used by "require".
-- (find-blogme3file "blogme3.lua" "fnamedirectory")
fnamedirectory    = function (fname) return fname:match"^(.*/)[^/]*$"  end
fnamenondirectory = function (fname) return fname:match     "([^/]*)$" end
dednat4dir        = dednat4dir or (arg and fnamedirectory(arg[0])) or ""

if dednat4dir ~= "" then package.path = dednat4dir.."?.lua;"..package.path end

-- «edrxlib»  (to ".edrxlib")
-- require "lua50init"             -- (find-angg "LUA/lua50init.lua")
require "edrxlib"                  -- (find-dn4 "edrxlib.lua")
-- Or: dofile(dednat4dir.."edrxlib.lua")


-- (find-luamanualw3m "#pdf-error")
-- (find-luamanualw3m "#pdf-os.exit")
-- errprintf = function (...) printf(...); os.exit(1) end
errprintf = function (...) error(format(...), 1) end


-- «compat.lua»  (to ".compat.lua")
-- (find-angg "LUA/lua50init.lua" "compat")
-- (find-angg "LUA/lua50init.lua" "string-methods")
-- (find-angg "LUA/lua50init.lua" "untabify")
-- (find-angg "LUA/lua50init.lua" "readfile")
-- (find-angg "LUA/lua50init.lua" "writefile")
-- (find-angg "LUA/lua50init.lua" "splitlines")




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

heads = {}

registerhead = function (headstr, head)
    head.headstr = headstr
    heads[headstr] = head
  end
registerhead("", {})

headfor = function (linestr)
    if linestr then
      return heads[string.sub(linestr, 1, 3)] or
             heads[string.sub(linestr, 1, 2)] or
             heads[string.sub(linestr, 1, 1)] or
             heads[""]
    end
  end



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

processfile = function (fname, ismetatex)
    local _sourcefname, _linen, _linestr, _head, _flines, _tlines =
           sourcefname,  linen,  linestr,  head,  flines,  tlines
    sourcefname = fname
    linen = 1
    flines = splitlines(readfile(fname))
    tlines = {}
    flines.ismetatex = ismetatex
    while 1 do
      linestr = flines[linen]
      if not linestr then
	break
      end
      head = headfor(linestr)
      local beforefirst = head.beforefirst or function () end
      local aftereach   = head.aftereach   or function () end
      if head.afterlast then
	beforefirst(linestr)
	aftereach(linestr)
	while headfor(flines[linen+1]) == head do
	  linen = linen + 1
	  linestr = flines[linen]
	  aftereach(linestr);
	end
	head.afterlast()
      else
	aftereach(linestr)
      end
      linen = linen + 1
    end
    local tmpflines = flines
     sourcefname,  linen,  linestr,  head,  flines,  tlines =
    _sourcefname, _linen, _linestr, _head, _flines, _tlines
    return tmpflines
  end


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

isprefix = {}
abbrevs = {}
addabbrev = function (abbrev, expansion)
    for i = 1,string.len(abbrev)-1 do
      isprefix[string.sub(abbrev, 1, i)] = true
    end
    abbrevs[abbrev] = expansion
  end

unabbrev = function (str)
    local len, newstr, i = string.len(str), "", 1
    local j, teststr, longest
    while i<=len do
      longest = nil     -- the longest substring starting at i that is an abbrev
      for j=i,len do
        teststr = string.sub(str, i, j)
        if abbrevs[teststr] then longest = teststr
        else
          if not isprefix[teststr] then break end
        end
      end
      if longest then                        -- if str[i] starts an abbrev then
        newstr = newstr .. abbrevs[longest]  -- add the expansion of the abbrev
        i = i + string.len(longest)
      else
        newstr = newstr .. string.sub(str, i, i)   -- else add str[i]
        i = i + 1
      end
    end
    return newstr
  end

addabbrevs = function (...)
    for i=1,getn(arg),2 do
      addabbrev(arg[i], arg[i+1])
    end
  end

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


--%%%%
--%
--% «abbrev-head»  (to ".abbrev-head")
--%
--%%%%

abbrevheadcode1 = function ()
    local _, __, abbrev, expansion = string.find(linestr, "^%%:*(.-)*(.-)*")
    addabbrev(abbrev, expansion)
  end
registerhead("%:*", {
  aftereach = abbrevheadcode1
})


--%%%%
--%
--% «tex-head»  (to ".tex-head")
--%
--%%%%

outtexlines = {}
texheadunabbrev = function (str)
    return string.gsub(linestr, "\\abr(%b{})", function (s)
        return unabbrev(string.sub(s, 2, -2))
      end)
  end
texheadcode1 = function (linestr)
    if flines.ismetatex then
      outtexlines[linen] = texheadunabbrev(linestr)
    end
  end
registerhead("", {
  aftereach = texheadcode1
})


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

dntfile = io.stdout
dntprint = function (str)
    dntfile:write(str, "\n")
  end

-- «undefs»  (to ".undefs")
undefs = ""
remembertoundefine = function (dedordiag, name)
    undefs = undefs.."  \\undef"..dedordiag.."{"..name.."}\n"
  end

setdntfile = function ()
    if dntfile and dntfile ~= io.stdout then io.close(dntfile) end
    if dntn then dntextrasuffix = "-" .. dntn end
    dntfname = stemfname .. ".dnt" .. (dntextrasuffix or "")
    dntfile = assert(io.open(dntfname, "w+"))
    dntprint(undefs)
    undefs = ""
  end

bumpdntfile = function ()
    dntn = (dntn or 0) + 1
    setdntfile()
    outtexlines[linen] = "\\input "..dntfname
  end


--%%%%
--%
--% «lua-head»  (to ".lua-head")
--%
--%%%%

luaheadcode0 = function ()
    luacode = ""
    luacodestartlinen = linen
  end
luaheadcode1 = function (linestr)
    luacode = luacode .. string.sub(linestr, 3) .. "\n"
  end
luaheadcode2 = function ()
    local chunkname = "\"%L\" chunk starting at line "..luacodestartlinen
    assert(loadstring(luacode, chunkname))()
  end
registerhead("%L", {
  beforefirst = luaheadcode0,
  aftereach   = luaheadcode1,
  afterlast   = luaheadcode2
})


--%%%%
--%
--% «tree-lib»  (to ".tree-lib")
--%
--%%%%

dolinenumbers = "eev"
optionalhyperlink = function (pre, linen, post)
    if dolinenumbers == "eev" then
     return format("%s(find-fline \"%s\" %d)%s", pre, sourcefname, linen, post)
    elseif dolinenumbers then
      return format("%sfile %s line %d%s", pre, sourcefname, linen, post)
    else
      return ""
    end
  end

barcharandtext = function (barseg)
    local _, __, text
    _, __, text = string.find(barseg.text, "^-+(.*)")
    if text then return "-",text end
    _, __, text = string.find(barseg.text, "^=+(.*)")
    if text then return "=",text end
    _, __, text = string.find(barseg.text, "^:+(.*)")
    if text then return ":",text end
    errprintf("Bad bar at line %d, col %d: %s\n", barseg.linen, barseg.lcol,
      barseg.text)
  end

relativeplacement = function (upperseg, lowerseg)
    if upperseg.rcol <= lowerseg.lcol then return "L" end
    if upperseg.lcol >= lowerseg.rcol then return "R" end
    return "I"
  end

nextseg = function (seg) return tlines[seg.linen].segs[seg.segn+1] end

firstsegabove = function (lowerseg, nlines)
    local upperseg
    upperseg = tlines[lowerseg.linen - (nlines or 1)] and
               tlines[lowerseg.linen - (nlines or 1)].segs[1]
    while upperseg and relativeplacement(upperseg, lowerseg)=="L" do
        upperseg = nextseg(upperseg)
      end
    if upperseg and relativeplacement(upperseg, lowerseg)=="I" then
      return upperseg
    end
  end

nextsegabove = function (upperseg, lowerseg)
    local nextupperseg = nextseg(upperseg)
    return nextupperseg and
      relativeplacement(nextupperseg, lowerseg)=="I" and nextupperseg
  end

stuffabovenode = function (lowerseg)
    local barseg, firstupperseg, upperseg, n
    barseg = firstsegabove(lowerseg)
    if not barseg then return -1 end
    firstupperseg = firstsegabove(barseg)
    if not firstupperseg then return 0, barseg end
    n = 1
    upperseg = firstupperseg
    while 1 do
      upperseg = nextsegabove(upperseg, barseg)
      if upperseg then n = n+1 else return n, barseg, firstupperseg end
    end
  end


--%%%%
--%
--% «tree-out»  (to ".tree-out")
--%
--%%%%

mathstrut = mathstrut or "\\mathstrut "

tex_node_tatsuta = function (indent, lowerseg)
    local n, barseg, upperseg = stuffabovenode(lowerseg)
    if not barseg then
      return indent..mathstrut..unabbrev(lowerseg.text)
    end
    local barchar, bartext = barcharandtext(barseg)
    -- PP(barseg, barchar, bartext)
    local rulemodifier =
      (barchar=="=" and "=" or (barchar==":" and "*" or "")) ..
      (bartext=="" and "" or "[{"..unabbrev(bartext).."}]")
    local newindent = indent.." "
    local uppertex
    if n==0 then
      return format("%s\\infer%s{ %s%s }{ }", indent, rulemodifier,
          mathstrut, unabbrev(lowerseg.text))
    else
      uppertex = tex_node_tatsuta(newindent, upperseg)
      for i=2,n do
        upperseg = nextseg(upperseg)
        uppertex = uppertex .. " &\n" .. tex_node_tatsuta(newindent, upperseg)
      end
    end
    return format("%s\\infer%s{ %s%s }{\n%s }",
        indent, rulemodifier,
        mathstrut, unabbrev(lowerseg.text),
        uppertex)
  end

tex_tree_tatsuta = function (treetagseg, treelabel, treerootseg)
    return format("\\defded{%s}{%s\n%s }\n",
                  treelabel, optionalhyperlink("    % ", treetagseg.linen, ""),
                  tex_node_tatsuta(" ", treerootseg))
  end

tex_node_paultaylor = function (indent, lowerseg)
    local n, barseg, upperseg = stuffabovenode(lowerseg)
    if not barseg then
      return unabbrev(lowerseg.text)
    end
    local barchar, bartext = barcharandtext(barseg)
    local justifies = (barchar=="=" and "\\Justifies" or
                       (barchar==":" and "\\leadsto" or "\\justifies"))
    local using = (bartext=="" and "" or "\\using "..unabbrev(bartext).." ")
    local newindent = indent.."   "
    local uppertex, segtex, istree, previstree
    if n==0 then
      return "\\[ "..using..justifies.."\n"..
             indent..unabbrev(lowerseg.text).." \\]", "tree"
    else
      uppertex, istree = tex_node_paultaylor(newindent, upperseg)
      for i=2,n do
        upperseg = nextseg(upperseg)
        previstree = istree
	segtex, istree = tex_node_paultaylor(newindent, upperseg)
        if previstree or istree then quad = "" else quad = " \\quad" end
        uppertex = uppertex..quad.."\n"..
                   newindent..segtex
      end
    end
    return "\\[ "..uppertex.." "..using..justifies.."\n"..
           indent..unabbrev(lowerseg.text).." \\]", "tree"
  end

tex_tree_paultaylor = function (treetagseg, treelabel, treerootseg)
  return "\\defded{"..treelabel.."}{"..
         optionalhyperlink("    % ", treetagseg.linen, "")..
         "\n  \\begin{prooftree}\n  "..
            tex_node_paultaylor("  ", treerootseg)..
         "\n  \\end{prooftree}}"
end


-- «tex_tree_function»  (to ".tex_tree_function")
-- The default is to use Makoto Tatsuta's "proof.sty":
tex_tree_function = tex_tree_tatsuta

-- To change the default to Paul Taylor's package, run this:
--   tex_tree_function = tex_tree_paultaylor
-- (possibly in a "%L" line in a .tex file).

-- To do: add support for Sam Buss's "bussproofs.sty".
-- See: http://www.phil.cam.ac.uk/teaching_staff/Smith/LaTeX/nd.html




--%%%%
--%
--% «tree-head»  (to ".tree-head")
--%
--%%%%

tlines = {}
treetagsegs = {}

splitintosegs = function (tline)
    local col, nsegs, _, __, spaces, text = 1, 0
    tline.segs = {}
    while 1 do
      _, __, spaces, text = string.find(tline.text, "^( *)([^ ]*)", col)
      if text and text ~= "" then
        nsegs = nsegs + 1
        local nspaces, nchars = string.len(spaces), string.len(text)
        tline.segs[nsegs] = {linen=tline.linen, segn=nsegs,
          lcol=col+nspaces, rcol=col+nspaces+nchars, text=text}
        col = col + nspaces + nchars
      else
        break
      end
    end
  end

processtreelabelandroot = function (labelseg, treelabel, treerootseg)
    dntprint(tex_tree_function(seg, treelabel, treerootseg))
    remembertoundefine("ded", treelabel) -- this should be elsewhere,
    -- but I don't use undefinings anymore, so that doesn't matter
  end

processtreetags = function (tline)
    seg = tline.segs[1]
    while seg do
      if string.sub(seg.text, 1, 1)=="^" then
        local treelabel = string.sub(seg.text, 2)
        if treetagsegs[treelabel] then
          errprintf("Tree redefined: tree %s, lines %d and %d\n",
            treelabel, tline.linen, treetagsegs[treelabel].linen)
        end
        local treerootseg = firstsegabove(seg, 2)
        if not treerootseg then
          errprintf("No root seg: line %d, tree %s\n", tline.linen, treelabel)
        end
        -- Now we have both the "^label" seg and the root seg of the tree...
        -- Note that treelabel holds just the "label" thing, without the "^".
        -- dntprint(tex_tree_function(seg, treelabel, treerootseg))
        processtreelabelandroot(seg, treelabel, treerootseg)
      end
      seg = nextseg(seg)
    end
  end

treeheadcode1 = function ()
    -- tlines[linen] = {linen=linen, text=untabify(string.sub(linestr, 3), 2)}
    tlines[linen] = {linen=linen, text=untabify("  "..string.sub(linestr, 3))}
    splitintosegs(tlines[linen])
    processtreetags(tlines[linen])
  end

registerhead("%:", {
  aftereach = treeheadcode1
})



--%%%%
--%
--% «diag-out»  (to ".diag-out")
--%
--%%%%

-- The data structures for nodes and arrows:
--
-- nodes	an array of "node"s
-- node.noden	an integer such that nodes[node.noden]==node
-- node.TeX	a string (in TeX). It becomes an Ni in \morphism...[N1`N2;L]
-- node.tex	a string, in TeX with abbreviations, or nil
-- node.tag	a string; in general we have nodes[node.tag]==node
-- node.x	an integer (given in Tk pixels)
-- node.y	an integer (given in Tk pixels)
--
-- arrows	an array of "arrow"s
-- arrow.arrown	an integer such that arrows[arrow.arrown]==arrow
-- arrow.from	an index: nodes[arrow.from] is the starting node
-- arrow.to	an index: nodes[arrow.to] is the ending node
-- arrow.shape	a string, e.g. "|->", or nil; nil means "->"
-- arrow.Label	a string (in TeX). It becomes the L in \morphism...[N1`N2;L]
-- arrow.label	a string, in TeX with abbreviations, or nil
-- arrow.placement	a string, e.g. "a" for "label |a|bove arrow", or nil
-- arrow.slide	a string, e.g. "10pt", or nil
-- arrow.curve	a string, e.g. "^2em", or nil
-- arrow.special	a function or nil
-- arrow.lplacement	a string, e.g. "^<>(0.4)", or nil

dxyorigx = 100
dxyorigy = 100
dxyscale = 15
realx = function (x) return dxyscale*(x-dxyorigx) end
realy = function (y) return -dxyscale*(y-dxyorigy) end

nodes = {}
arrows = {}

storenode = function (node)
    tinsert(nodes, node)
    node.noden = getn(nodes)
    if node.tag and not nodes[node.tag] then nodes[node.tag] = node end
    return node
  end

storearrow = function (arrow)
    tinsert(arrows, arrow)
    arrow.arrown = getn(arrows)
    return arrow
  end

arrowtoTeX = function (arrow, ignorespecial)
    if arrow.special and not ignorespecial then
      return arrow.special(arrow)
    end
    local node1, node2 = nodes[arrow.from], nodes[arrow.to]
    local x1, y1 = realx(node1.x), realy(node1.y)
    local x2, y2 = realx(node2.x), realy(node2.y)
    local dx, dy = x2-x1, y2-y1
    local label
    if     arrow.Label then label = arrow.Label
    elseif arrow.label then label = unabbrev(arrow.label)
                       else label = ""
    end
    -- 2006aug02: a hack: explicit label placement.
    -- When lplacement is not nil (say, "^<>(0.4)") write the label at
    -- the right of lplacement, intead of writing it in "[{%s}`{%s};{HERE}]".
    -- Warning: when we use lplacement the letter in arrow.placement is ignored.
    -- See: (to "lplacement")
    local lplacement
    if arrow.lplacement then
      lplacement = format("%s{%s}", arrow.lplacement, label)
    end
    local p = arrow.placement and "|"..arrow.placement.."|" or ""
    local slide, curve, sh
    if arrow.slide then slide = "@<"..arrow.slide..">" end
    if arrow.curve then curve = "@/"..arrow.curve.."/" end
    if arrow.slide or arrow.curve or arrow.lplacement then -- 2006aug02
      sh = format("/{@{%s}%s%s%s}/", (arrow.shape or "->"),
		  (lplacement or ""), (slide or ""), (curve or ""))
    else
      sh = "/"..(arrow.shape or "->").."/"
    end
    return format("\\morphism(%d,%d)%s%s<%d,%d>[{%s}`{%s};%s]",
                  x1, y1, p, sh, dx, dy,
                  node1.TeX, node2.TeX,
		  lplacement and "" or format("{%s}", label))
  end



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

diagram = function (str)
    diagramname = str
    diagramstartlinen = linen
    dxyorigx = 100
    dxyorigy = 100
    dxyscale = 15
    nodes = {}
    arrows = {}
    xs = {}
    prevy = nil
  end

diagramtoTeX = function ()
    for i=1,getn(nodes) do
      local node = nodes[i]
      if not node.tex then node.tex = node.tag end
      if not node.TeX then node.TeX = unabbrev(node.tex) end
    end
    local bigstr = ""
    for i=1,getn(arrows) do
      bigstr = bigstr.."  "..arrowtoTeX(arrows[i]).."\n"
    end
    return bigstr
  end

-- (find-angg "dednat/dednat3.lua" "processing.a.file" "Tree redefined")
-- (find-angg "LATEX/edrx.sty" "ded")
-- TO DO: support \place, warn at redefinitions
-- «enddiagram»  (to ".enddiagram")

enddiagram = function ()
    local diagramdef = format("\\defdiag{%s}{%s\n%s}",
        diagramname, optionalhyperlink("    % ", diagramstartlinen, ""),
	diagramtoTeX())
    dntprint(diagramdef)
    remembertoundefine("diag", diagramname)
  end



--%%%%
--%
--% «diag-2d»  (to ".diag-2d")
--%
--%%%%

-- The only special data structure used in the 2D support is the array
-- "xs", of cols->Tkcols; only the first position of each x spec is
-- stored in it. Example: if
--   "100  140  "   this xspecstr and then
--   "  +10     "   this xspecstr are fed to dxy2Dx, then we'll have
--    0123456789    xs[0]=100, xs[2]=110, xs[5]=140.

xs = {}
prevy = nil

dxy2Dgetword = function (str, pos)
    if not str or string.len(str)<=pos then return end
    local _, endpos, spaces, word = string.find(str, "^( *)([^ ]+)", pos+1)
    if not word then return end
    local wordpos = pos + string.len(spaces)
    return endpos, wordpos, word
  end

dxy2Dx = function (xspecstr, pos)
    local wordpos, word
    pos = pos or 0
    while 1 do
      pos, wordpos, word = dxy2Dgetword(xspecstr, pos)
      if not word then break end
      local _, __, sign, n = string.find(word, "^([-+]?)([0-9.]+)$")
      if n then		-- words that are not like nn or +nn are ignored
        if sign=="" then
          xs[wordpos] = tonumber(n)
        else		-- sign=="+": add n to the previous x
          local prevx
          for j=wordpos-1,0,-1 do
	    if xs[j] then prevx=xs[j]; break end
          end
          if not prevx then
            error("line "..linen.." col "..pos..": no prevx")
          end
          xs[wordpos] = prevx+tonumber(sign..n)
        end
      end
    end
  end

dxy2D = function (str, pos, insteadofstorenode)
    local wordpos, yword, y
    pos, wordpos, yword = dxy2Dgetword(str, pos or 0)
    if not yword then return end	-- blank lines are ignored
    local _, __, sign, n = string.find(yword, "^(%+?)([0-9.]+)$")
    if n then		-- lines not starting with a y spec are ignored
      if sign=="" then
        y = tonumber(n)
      else
        if not prevy then error("line "..linen.." col "..pos..": no prevy") end
        y = tonumber(n)+prevy
      end
      prevy = y
      while 1 do
        pos, wordpos, word = dxy2Dgetword(str, pos)
        if not word then break end
	for i=wordpos,wordpos+string.len(word)-1 do
          if xs[i] then
            (insteadofstorenode or storenode)({tag=word, x=xs[i], y=y})
            break
          end		-- words without an x are ignored
        end
      end
    end
  end

--[[
dxy2Dx("   100 140 +20"); PP(xs)
dxy2Dx("    +10      +3"); PP(xs)
 dxy2D("55  a    b^ool ", 0, P)
 dxy2D("+5 a^F  ign  h ", 0, P)
--]]



--%%%%
--%
--% «diag-forth»  (to ".diag-forth")
--%
--%%%%

-- (find-angg "LFORTH/kernel.lua")
-- (find-angg "LFORTH/kernel.lua" "getword")
-- (find-es "lua5" "0-based")
-- lua50e 'P(string.find("012345678", "^(23456)", 1+2))	--> 1+2 7 "23456"'

ds = {}
dspush = function (v) tinsert(ds, 1, v); return v end
dspop  = function () return tremove(ds, 1) end

depths = {}
depthspush = function (v) tinsert(depths, 1, v); return v end
depthspop  = function () return tremove(depths, 1) end

forths = forths or {}
forths["drop"] = dspop
forths["swap"] = function () ds[2], ds[1] = ds[1], ds[2] end
forths["(("] = function () depthspush(getn(ds)) end
forths["))"] = function ()
    if not depths[1] then
      error("line "..linen.." col "..p.wordpos..": missing `))'")
    end
    for i=getn(ds)-1,depthspop(),-1 do tremove(ds, 1) end
  end
forths["@"] = function ()
    dspush(ds[table.getn(ds) - depths[1] - getwordasluaexpr()])
  end

pushtag = function (tag) dspush(assert(nodes[tag], tag..": no such node")) end
pusharrow = function (shape)
    dspush(storearrow {from=ds[2].noden, to=ds[1].noden, shape=shape})
  end

dof = function (word)
    if forths[word] then forths[word]() 
    elseif nodes[word] then dspush(nodes[word])		-- diagxy-specific
    else
      printf("At file %q, line %s (%q):\n",
	     sourcefname or "?", linen or "?", linestr or "??")
      error("No such word: "..word)
    end
  end



--%%%%%
--%
--% «diag-forth-parser»  (to ".diag-forth-parser")
--%
--%%%%%

-- Todo: rewrite this. Possible inspirations for the new version:
-- (find-miniforth "miniforth5.lua")
-- (find-blogme3 "blogme3.lua")
-- (find-blogme3 "brackets.lua")

p = {text=str, pos=0, word=nil, wordpos=nil, stuffpos=nil}

getword = function ()
    local _, __, afterspaces, word, afterword, newline =
      string.find(p.text, "^ *()([^ \n]*)()(\n?)", p.pos+1)
    -- PP(_, __, afterspaces, word, afterword, newline)
    if word ~= "" then
      p.stuffpos, p.pos = afterspaces-1, afterword-1
      return word
    elseif newline ~= "" then
      p.stuffpos, p.pos = afterword-1, afterword
      return newline
    end
    p.pos = nil
  end
getrestofline = function ()
    local _, __, stuffpos, stuff, newpos =
      string.find(p.text, "^ ?()([^\n]*)()", p.pos+1)
    p.stuffpos, p.pos = stuffpos-1, newpos-1
    return stuff
  end
getuntilquote = function ()
    local _, __, stuff = string.find(p.text, "^ (.-)\"", p.pos+1)
    if not _ then error("line "..linen..": no closing quote") end
    p.stuffpos, p.pos = p.pos+1, __
    return stuff
  end

asluaexpr = function (str)
    return assert(loadstring("return "..str))()
  end
getwordasluaexpr = function () return asluaexpr(getword()) end

dofs = function (str, pos)
    local oldp = p
    p = {text=str, pos=(pos or 0)}
    while 1 do
      p.word = getword()
      if not p.word then p = oldp; return end
      p.wordpos = p.stuffpos
      dof(p.word)
    end
  end

forths["\n"] = function () end
forths["#"] = getrestofline
macro = function (str) return function () dofs(str) end end


--%%%%
--%
--% «diag-words»  (to ".diag-words")
--%
--%%%%

-- Note that "drop", "((", and "))" have already been defined.

forths["diagram"] = function ()
    diagram(getword() or error("Missing diagram name"))
  end
forths["enddiagram"] = enddiagram

forths["2Dx"] = function ()
    getrestofline()
    dxy2Dx(p.text, p.stuffpos)
  end
forths["2D"] = function ()
    getrestofline()
    dxy2D(p.text, p.stuffpos)
  end



--%%%%
--%
--% «diag-arrows»  (to ".diag-arrows")
--%
--%%%%

settex = function (tag, str) nodes[tag].tex = str end
setTeX = function (tag, str) nodes[tag].TeX = str end
setp = function (tag, str) nodes[tag].placement = str end
setslide = function (tag, str) nodes[tag].slide = str end
setcurve = function (tag, str) nodes[tag].curve = str end

forths[".tex="]   = function () ds[1].tex = getword() end
forths[".TeX="]   = function () ds[1].TeX = getword() end
forths[".TeX=\""] = function () ds[1].TeX = getuntilquote() end
forths[".p="]     = function () ds[1].placement = getword() end
forths[".slide="] = function () ds[1].slide = getword() end
forths[".curve="] = function () ds[1].curve = getword() end

forths[".label="]   = function () ds[1].label = getword() end
forths[".label=\""] = function () ds[1].label = getuntilquote() end

forths[".plabel="] = function ()
    ds[1].placement = getword()
    ds[1].label = getword()
  end

forths["->"] = function () pusharrow("->") end
forths["=>"] = function () pusharrow("=>") end
forths[".>"] = function () pusharrow(".>") end
forths[":>"] = function () pusharrow(":>") end
forths["|.>"] = function () pusharrow("|.>") end
forths["-->"] = function () pusharrow("-->") end
forths["==>"] = function () pusharrow("==>") end
forths["|->"] = function () pusharrow("|->") end
forths["|-->"] = function () pusharrow("|-->") end
forths["`->"] = function () pusharrow("^{ (}->") end
forths[">->"] = function () pusharrow(" >->") end
forths["->>"] = function () pusharrow("->>") end
forths["|->>"] = function () pusharrow("|->>") end
forths["<->"] = function () pusharrow("<->") end

forths["<-"] = function () pusharrow("<-") end
forths["<="] = function () pusharrow("<=") end
forths["<."] = function () pusharrow("<.") end
forths["<:"] = function () pusharrow("<:") end
forths["<.|"] = function () pusharrow("<.|") end
forths["<--"] = function () pusharrow("<--") end
forths["<=="] = function () pusharrow("<==") end
forths["<-|"] = function () pusharrow("<-|") end
forths["<--|"] = function () pusharrow("<--|") end
forths["<-'"] = function () pusharrow("<-^{) }") end
forths["<-<"] = function () pusharrow("<-< ") end
forths["<<-"] = function () pusharrow("<<-") end
forths["<<-|"] = function () pusharrow("<<-|") end

forths["="] = function () pusharrow("=") end
forths["-"] = function () pusharrow("-") end
forths["--"] = function () pusharrow("--") end
forths["."] = function () pusharrow(".") end

-- «lplacement»  (to ".lplacement")
-- 2008apr19: a low-level way to specify label placement.
-- Example:
--   A B -> .PLABEL= ^<>(0.5) f
-- See: (find-diagxypage 23)
-- See: (find-diagxytext "4.3    Empty placement and moving labels")
--
-- The first char of the "lplacement" argument has to be either "^" or
-- "_", to specify if the label is to go "above" or "below" the arrow
-- [this bypasses diagxy's tricks to convert a/b/l/r/m into ^ or _],
-- and the rest specifies what to use as reference points (the centers
-- of the nodes or the extremities of the arrows) and where between
-- the reference points the label is to be placed.
--
-- Simulating the "m" placement with .PLABEL= seems to be possible but
-- hard - it would require tricks involving "\hole", I guess.
--
forths[".PLABEL="] = function ()
    ds[1].lplacement = getword()
    ds[1].label = getword()
  end



--%%%%
--%
--% «diag-head»  (to ".diag-head")
--%
--%%%%

-- (find-angg "dednat/dednat3.lua" "luahead")
-- Tell dednat3 to treat "%D" lines as strings to be dofs'd.

diagheadcode1 = function (linestr)
    -- dofs(untabify(linestr), 2)
    dofs(untabify("  "..string.sub(linestr, 3)))
  end

if registerhead then
  registerhead("%D", {
    aftereach = diagheadcode1
  })
end




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

removesuffix = function (suffix, str)
    if strsub(str, -strlen(suffix)) == suffix then
      return strsub(str, 1, -strlen(suffix)-1)
    end
  end
processtex = function (fname)
    -- stemfname = removesuffix(".tex", fname)
    -- 2008mar25:
    stemfname = removesuffix(".tex", fname) or removesuffix(".sty", fname)
    if not stemfname then error("Filename must end with .tex") end
    setdntfile()
    processfile(fname)
    io.close(dntfile)
  end
process = processfile

-- The support for ".metatex" files - in which some of the source
-- files could contain abbreviations - is obsolete! Don't use!

processmetatex = function (fname)
    stemfname = removesuffix(".metatex", fname)
    setdntfile()
    outtexfname = stemfname .. ".tex"
    local flines = processfile(fname, "metatex")
    io.close(dntfile)
    outtexfile = assert(io.open(outtexfname, "w+"))
    for i=1,getn(flines) do
      outtexfile:write(outtexlines[i] or "%", "\n")
    end
    io.close(outtexfile)
  end

processtexormetatex = function (fname)
    if removesuffix(".tex", fname) then
      processtex(fname)
    elseif removesuffix(".metatex", fname) then
      processmetatex(fname)
    else
      error("Filename must end with .tex or .metatex")
    end
  end



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

-- (find-es "lua5" "setvbuf")
io.stdout:setvbuf("no")

if arg then
  if arg[1] then
    if arg[1] == "-e" then
      assert(loadstring(arg[2]))()
    else
      processtex(arg[1])
    end
  else
    error("Usage: "..arg[0].." filename.tex")
  end
else
  -- no "arg" means that this is being loaded by "-e",
  -- probably by something like this:
  --   lua51 -e 'dofile "dednat4.lua"' -i
  -- so we have to prepare for entering interactive mode...
  -- (find-angg "LUA/lua50init.lua" "load_dednat4")
  -- interactivemode = function ()
    print("Loaded: " .. dednat4dir .. "dednat4.lua")
    -- dofile(dednat4dir .. "dednat4.lua")
    A  = function (abbrev, expansion) addabbrev(abbrev, expansion) end
    D  = function (linestr) dofs(untabify(linestr)) end
    DX = function (linestr) dxy2Dx(untabify(linestr)) end
    D2 = function (linestr) dxy2D(untabify(linestr)) end
end


-- To do: change the above "main" code to something that uses options
-- in a clearer way, like this:
--   dednat4 -o foo.dnt -ot foo.tex -i foo.metatex
--   dednat4 -o bar.dnt             -i bar.tex

-- Code borrowed from blogme for inspiration:

-- do
--   local i = 1
--   local infname, outfname
--   while i <= arg.n do
--     local a, b = arg[i], arg[i+1]
--     if     a == "-o" then outfname = b; i = i+2
--     elseif a == "-i" then blogme_test(b, outfname); i = i+2
--     elseif a == "-p" then relativepathto_prefix = b; i = i+2
--     elseif a == "-e" then assert(loadstring(b))(); i = i+2
--     else print("Unrecognized option: " .. a); os.exit(1)
--     end
--   end
-- end




--%%%%
--%
--% «diag-extensions»
--%
--%%%%

-- (find-angg "dednat/dednat2.lua" "tatsuta_do_node")
-- (find-lua50ref "Precedence")

-- (find-es "tex" "ptproof")



--[[
storenode {TeX="a", tag="a", x=100, y=100}
storenode {TeX="b", tag="b", x=140, y=100}
PP(nodes)
storearrow {from="a", to="b", shape="|->",
            slide="5pt", label="up", placement="a"}
storearrow {from="a", to="b", shape=".>"}
print(arrowtoTeX(arrows[1]))
print(arrowtoTeX(arrows[2]))
--]]



--[[
diagram "miniadj"
dxy2Dx "      100   140 "
dxy2D  " 140 a^L <= a   "
dxy2D  "      -     -   "
dxy2D  "      |     |   "
dxy2D  "      v     v   "
dxy2D  " 100  b => b^R  "
PP("nodes =", nodes)
pushtag "a^L"; pushtag "a"; dof "<="
pushtag "a^L"; pushtag "b"; dof "|->"
pushtag "a"; pushtag "b^R"; dof "|->"
pushtag "b"; pushtag "b^R"; dof "=>"
PP("arrows =", arrows)
enddiagram()
--]]
--[[
dofs "diagram miniadj"
dofs "2Dx      100   140 "
dofs "2D  140 a^L <= a   "
dofs "2D       -     -   "
dofs "2D       |     |   "
dofs "2D       v     v   "
dofs "2D  100  b => b^R  "
dofs "a^L a <=   a^L b |->   a b^R |->   b b^R =>"
dofs "enddiagram"
--]]

--[[
#*
rm -Rv /tmp/dn4/
mkdir  /tmp/dn4/
cd     /tmp/dn4/

cat > dntest.metatex <<'%%%'
bla
%L standardabbrevs()
%:
%:     -
%:     c  d
%:     ====?
%:  a  a->b
%:  -------
%:     b
%:
%:  ^tree1
%:
%D diagram miniadj
%D 2Dx     100   140
%D 2D 140 a^L <= a
%D 2D      -     -
%D 2D      | <-> |
%D 2D      v     v
%D 2D 100  b => b^R
%D a (( a^L => drop b |-> drop b^R => )) b^R |->
%D enddiagram
$\abr{a-.>(b<->c)}$
%%%

~/dednat4/dednat4.lua dntest.metatex

#*
# (find-fline "/tmp/dn4/")
# (find-fline "/tmp/dn4/dntest.tex")
# (find-fline "/tmp/dn4/dntest.dnt")
# (find-angg "LATEX/edrx.sty" "diag")
# (find-es "xypic" "diagxydemo0")

#*
mkdir  /tmp/dn4/
cd     /tmp/dn4/
unzip -a -o $S/ftp/ftp.math.mcgill.ca/pub/barr/diagxy.zip
cp $S/http/www.ctan.org/tex-archive/macros/latex/contrib/proof/proof.sty .
cp ~/dednat4/demodefs.tex .

#*
mkdir  /tmp/dn4/
cd     /tmp/dn4/
cp ~/dednat4/demodefs.tex .

cat > main.tex <<'%%%'
\documentclass[oneside]{book}

\usepackage{proof}
\usepackage{amsmath}
\usepackage{amssymb}
%
\input diagxy
\xyoption{curve}
%
\input demodefs.tex

\begin{document}
\input dntest.dnt
\input dntest.tex
$$\ded{tree1} \qquad \diag{miniadj}$$
\end{document}
%%%

latex main.tex && xdvi main.dvi &

#*

# (find-fline "/tmp/dn4/")
# (find-fline "/oldfs/7/pandahome/edrx/LATEX/")



]]

--[[
P(untabify("34\t\t01234567\t0123\t", 3))
P("3456701234567012345670123456701234567")
--]]

-- (find-vtutil4 "vtfontlib.lua")
-- (find-lua50ref "")
-- (find-angg "LUA/lua50init.lua")

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