Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   http://anggtwu.net/LUA/Dednat7.lua.html
--   http://anggtwu.net/LUA/Dednat7.lua
--          (find-angg "LUA/Dednat7.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
--
-- (defun e () (interactive) (find-angg "LUA/Dednat7.lua"))
--
-- The core of dednat6, rewritten as a small number of classes with
-- nice __tostring metamethods and nice test blocks. Based on:
--   (find-angg "LATEX/dednat6/minimalcore.lua")
--   (find-dn6file "minimalcore.lua")

-- «.Head»		(to "Head")
-- «.Head-tests»	(to "Head-tests")
-- «.Heads»		(to "Heads")
-- «.Heads-tests»	(to "Heads-tests")
-- «.AllTeXFiles»	(to "AllTeXFiles")
-- «.AllTeXFiles-tests»	(to "AllTeXFiles-tests")
-- «.TeXFile»		(to "TeXFile")
-- «.TeXFile-tests»	(to "TeXFile-tests")
-- «.TexLines»		(to "TexLines")
-- «.TexLines-tests»	(to "TexLines-tests")
-- «.Block»		(to "Block")
-- «.Block-tests»	(to "Block-tests")
-- «.Blocks»		(to "Blocks")
-- «.Blocks-tests»	(to "Blocks-tests")



--  _   _                _ 
-- | | | | ___  __ _  __| |
-- | |_| |/ _ \/ _` |/ _` |
-- |  _  |  __/ (_| | (_| |
-- |_| |_|\___|\__,_|\__,_|
--                         
-- Typical usage: when we do something like this
--   heads:registerhead "%L" {name="lua", action=function () end}
-- this adds a new "Head" object to "heads".
-- See: (to "Heads")
--      (to "Heads-tests")
--      (find-dn6 "heads6.lua" "abbrev-head")
--      (find-dn6 "heads6.lua" "lua-head")
--
-- «Head»  (to ".Head")

Head = Class {
  type    = "Head",
  __tostring = function (head) return mytostringv(head) end,
  __index = {
  },
}

-- «Head-tests»  (to ".Head-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Dednat7.lua"
= Head {name = "foo", action = function () end}

--]]




--  _   _                _     
-- | | | | ___  __ _  __| |___ 
-- | |_| |/ _ \/ _` |/ _` / __|
-- |  _  |  __/ (_| | (_| \__ \
-- |_| |_|\___|\__,_|\__,_|___/
--
-- Typical usage:
--   heads = Heads {}
--   heads:registerhead "%L" {name="lua",  action=function () end}
--   heads:registerhead "%:" {name="tree", action=function () end}
-- See: (find-dn6 "heads6.lua" "registerhead")
--
-- «Heads»  (to ".Heads")

Heads = Class {
  type    = "Heads",
  __tostring = function (heads)
      return "Registered heads: "..mapconcat(mytostring, sortedkeys(heads), ", ")
    end,
  __index = {
    headstrfor = function (heads, li)
        local p = function (len)
            local s = li:sub(1, len)
            return heads[s] and s
          end
        return p(5) or p(4) or p(3) or p(2) or p(1) or p(0)
      end,
    headfor = function (heads, li)
        local headstr = heads:headstrfor(li)
        return headstr and heads[headstr]
      end,
    registerhead = function (heads, headstr)
        return function (tbl)
            tbl.headstr = headstr
            heads[headstr] = Head(tbl)
            return heads
          end
      end,
  },
}

-- «Heads-tests»  (to ".Heads-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Dednat7.lua"
heads = Heads {}
= heads
heads["%L"]  = "foo"
heads["%LL"] = "bar"
heads["%D"]  = 42
= heads
= heads:headstrfor "%LL bla"
= heads:headstrfor "%L  bla"
= heads:headstrfor "blop"
= heads:headfor    "%LL bla"
= heads:headfor    "%L  bla"
= heads:headfor    "blop"

heads:registerhead "%P" {
  name = "foo",
  action = function () end
}

= heads
= heads["%P"]
= heads:headstrfor "%P bla"
= heads:headfor    "%P bla"

--]]





--     _    _ _ _____   __  _______ _ _           
--    / \  | | |_   _|__\ \/ /  ___(_) | ___  ___ 
--   / _ \ | | | | |/ _ \\  /| |_  | | |/ _ \/ __|
--  / ___ \| | | | |  __//  \|  _| | | |  __/\__ \
-- /_/   \_\_|_| |_|\___/_/\_\_|   |_|_|\___||___/
--                                                
-- «AllTeXFiles»  (to ".AllTeXFiles")

AllTeXFiles = Class {
  type    = "AllTeXFiles",
  __tostring = function (atf)
      if #keys(atf) == 0 then return "(no TeXFiles objects registered)" end
      local f = function (k) return format("%q -> %d lines", k, #atf[k]) end
      local ks = sorted(keys(atf))
      return mapconcat(f, ks, "\n")
    end,
  __index = {
  },
}

alltexfiles = AllTeXFiles {}

-- «AllTeXFiles-tests»  (to ".AllTeXFiles-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Dednat7.lua"
= alltexfiles
alltexfiles["a.tex"]  =        {"a", "b", "c", "d", "e"}
alltexfiles["bb.tex"] = VTable {"a", "b", "c", texname="bb.tex"}
= alltexfiles
= alltexfiles["a.tex"]
= alltexfiles["bb.tex"]

--]==]




--  _____   __  _______ _ _      
-- |_   _|__\ \/ /  ___(_) | ___ 
--   | |/ _ \\  /| |_  | | |/ _ \
--   | |  __//  \|  _| | | |  __/
--   |_|\___/_/\_\_|   |_|_|\___|
--                               
-- A TeXFile object is a .tex file split into lines and with a
-- "texname" field. A texfile object also has a "blocks" field,
-- that is initially empty.
--
-- Based on: (find-dn6 "minimalcore.lua" "heads")
-- «TeXFile»  (to ".TeXFile")

TeXFile = Class {
  type = "TeXFile",
  read = function (texname)
      return TeXFile.from(texname, ee_readfile(texname))
    end,
  from = function (texname, lines)
      return TeXFile.from0(texname):setlines(lines):register()
    end,
  from0 = function (texname)
      return TeXFile {texname=texname, blocks=Blocks{}}
    end,
  __tostring = function (tf)
      return format("TeXFile: %q\n%s\n%s",
                    tf.texname,
                    tf:linestostring(),
                    tostring(tf.blocks))
    end,
  __index = {
    linetostring  = function (tf,k) return format("%3d: %s", k, tf[k]) end,
    linestostring = function (tf,i,j)
        i,j = i or 1, j or #tf
        local f = function (k) return tf:linetostring(k) end
        return mapconcat(f, seq(i,j), "\n")
      end,
    register = function (tf)
        alltexfiles[tf.texname] = tf
        return tf
      end,
    setlines = function (tf, lines)
        if type(lines) == "string" then
          lines = splitlines(unixnewlines(lines))
        end
        for i=1,#lines do tf[i] = lines[i] end
        return tf
      end,
    --
    headstr = function (tf,k) return heads:headstrfor(tf[k]) end,
    head    = function (tf,k) return heads:headfor(tf[k]) end,
    getj    = function (tf,i)
        local j,headstr = i,tf:headstr(i)
        while j<#tf and tf:headstr(j+1)==headstr do j = j+1 end
        return j
      end,
    buildblock = function (tf,i)
        return Block.from(i, tf:getj(i), tf.texname, tf:headstr(i))
      end,
    --
    hasblocks    = function (tf) return #tf.blocks > 0 end,
    lastblock    = function (tf) return tf.blocks[#tf.blocks] end,
    lastj        = function (tf) return tf:lastblock().j end,
    anotherblock = function (tf)
        if #tf == 0 then return end
        if tf:hasblocks() and tf:lastj() == #tf then return end
        local i  = tf:hasblocks() and tf:lastj()+1 or 1
        local bl = tf:buildblock(i)
        table.insert(tf.blocks, bl)
        return bl
      end,
    --
    lastj_or_0   = function (tf) return tf:hasblocks() and tf:lastj() or 0 end,
    processlast  = function (tf) return tf:lastblock():doaction() end,
    processnext  = function (tf,verbose)
        if tf:anotherblock() then
          if verbose then print(tf:lastblock())end
          tf:processlast()
        end
      end,
    processuntil = function (tf,puline,verbose)
        if puline > #tf then puline = #tf end   -- to avoid a loop
        while tf:lastj_or_0() < puline do tf:processnext(verbose) end
      end,
  },
}

-- «TeXFile-tests»  (to ".TeXFile-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Dednat7.lua"
lis = VTable {"1", "2", "%L print(3,", "%L 4)", "5", "6"}
tf  = TeXFile.from("a.tex", lis)
= alltexfiles
= alltexfiles["a.tex"]

heads = Heads {}
heads:registerhead "%:" {name="tree", action=function () end}
heads:registerhead "%L" {
    name   = "lua",
    action = function ()
        thisbody = thisblock:untabifys(3, "", "\n")
        print(thisbody)   -- global
      end,
  }
= heads

= tf:anotherblock()
= tf:anotherblock()
= tf:anotherblock()
= tf:anotherblock()
= tf
= tf.blocks
= tf.blocks[2]
= tf.blocks[2]:untabifys()
= tf.blocks[2]:untabifys(nil, nil, "\n")
= tf.blocks[2]:untabifys(3,   nil, "\n")
= tf.blocks[2]:untabifys(3,   "",  "\n")
= tf.blocks[2]:untabifys(3,   "---", "\n")

= tf.blocks[1]:head()
= tf.blocks[2]:head()
= tf.blocks[1]:action()
= tf.blocks[2]:action()
= tf.blocks[1]:doaction()
= tf.blocks[2]:doaction()

tf.blocks = Blocks {}  -- reset
= tf
= tf:lastj_or_0()
tf:processnext("verbose")
tf:processnext("verbose")
tf:processnext("verbose")

tf.blocks = Blocks {}  -- reset
tf:processuntil(200, "verbose")

--]==]





--  ____  _            _    
-- | __ )| | ___   ___| | __
-- |  _ \| |/ _ \ / __| |/ /
-- | |_) | | (_) | (__|   < 
-- |____/|_|\___/ \___|_|\_\
--                          
-- «Block»  (to ".Block")
--
Block = Class {
  type = "Block",
  from = function (i,j,texname,headstr)
      return Block {i=i, j=j, texname=texname, headstr=headstr}
    end,
  __tostring = function (bl) return bl:tostring() end,
  __index = {
    ijtostring   = function (bl) return bl.i.."--"..bl.j end,
    luachunkname = function (bl) return bl.texname..":%L:"..bl.i.."-"..bl.j end,
    tostring     = function (bl) return
        format("%7s in %s: %s", bl:ijtostring(), bl.texname, mytostring(bl.headstr))
      end,
    --
    texlines = function (bl)    return alltexfiles[bl.texname] end,
    line     = function (bl, k) return bl:texlines()[k] end,
    head     = function (bl)    return bl.headstr and heads[bl.headstr] end,
    action   = function (bl)    return bl:head()  and bl:head().action  end,
    doaction = function (bl)
        thisblock = bl              -- set a global
        local action = bl:action()
        if action then return action() end
      end,
    --
    untabify = function (bl, k, cut, newprefix)
        local line = untabify8(bl:line(k))
        if cut then line = line:sub(cut+1) end
        if newprefix then line = newprefix..line end
        return line
      end,
    untabifys = function (bl, cut, newprefix, sep)
        local f = function (k) return bl:untabify(k, cut, newprefix) end
        local o = map(f, seq(bl.i, bl.j))
        if sep then o = table.concat(o, sep) end
        return o
      end,
    --
    -- Old, recycle:
    hyperlink = function (bl)
        return format("In the \"%s\"-block in lines %d--%d",
                      lastheadblock.heads, lastheadblock.i, lastheadblock.j)
      end,
  },
}

-- «Block-tests»  (to ".Block-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Dednat7.lua"
= Block.from(4,6, "a.tex", "%L")
= Block.from(4,6, "b.tex")

--]]



--  ____  _            _        
-- | __ )| | ___   ___| | _____ 
-- |  _ \| |/ _ \ / __| |/ / __|
-- | |_) | | (_) | (__|   <\__ \
-- |____/|_|\___/ \___|_|\_\___/
--                              
-- «Blocks»  (to ".Blocks")

Blocks = Class {
  type    = "Blocks",
  __tostring = function (bls)
      return "Blocks:\n"..mapconcat(tostring, bls, "\n")
    end,
  __index = {
  },
}

-- «Blocks-tests»  (to ".Blocks-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Dednat7.lua"
bls = Blocks {
  Block.from(1,4, "a.tex"),
  Block.from(5,6, "a.tex", "%L"),
  Block.from(7,8, "a.tex"),
}
= bls

--]]



-- Old:

texfile0 = function (fname)
    texlines = TexLines.read(fname)
    tf = texlines:toblock()
  end
texfile = function (fname)
    texfile0(fname..".tex")
  end

pu = function (puline) tf:processuntil(puline or tex.inputlineno) end









-- (defun e () (interactive) (find-angg "LUA/DedNat7.lua"))


-- Local Variables:
-- coding:  utf-8-unix
-- End: