Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   http://anggtwu.net/LUA/Und2D2.lua.html
--   http://anggtwu.net/LUA/Und2D2.lua
--          (find-angg "LUA/Und2D2.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
--
-- (defun u1 () (interactive) (find-angg "LUA/Und2D1.lua"))
-- (defun u2 () (interactive) (find-angg "LUA/Und2D2.lua"))

-- «.u8split»		(to "u8split")
-- «.u8split-tests»	(to "u8split-tests")
-- «.Grid»		(to "Grid")
-- «.Grid-tests»	(to "Grid-tests")
-- «.GRange»		(to "GRange")
-- «.GRange-tests»	(to "GRange-tests")
-- «.GrUD»		(to "GrUD")
-- «.GrUD-test1»	(to "GrUD-test1")
-- «.GrUD-test2»	(to "GrUD-test2")


--         ___            _ _ _   
--  _   _ ( _ ) ___ _ __ | (_) |_ 
-- | | | |/ _ \/ __| '_ \| | | __|
-- | |_| | (_) \__ \ |_) | | | |_ 
--  \__,_|\___/|___/ .__/|_|_|\__|
--                 |_|            
--
-- «u8split»  (to ".u8split")
-- (find-angg "LUA/lua50init.lua" "strlen8")
-- (find-angg "LUA/lua50init.lua" "u8c_to_l1")
u8split = function (str, pat)
    local pat = pat or ".[\128-\191]*"
    local result = HTable {}
    for s in str:gmatch(pat) do table.insert(result, s) end
    return result
  end

-- «u8split-tests»  (to ".u8split-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Und2D2.lua"
foo = "a·b"
= foo
= #foo
= u8split(foo)
= u8split(foo, ".")

--]==]



--   ____      _     _ 
--  / ___|_ __(_) __| |
-- | |  _| '__| |/ _` |
-- | |_| | |  | | (_| |
--  \____|_|  |_|\__,_|
--                     
-- «Grid»  (to ".Grid")

Grid = Class {
  type = "Grid",
  from = function (lines)
      if type(lines) == "string" then lines = splitlines(lines) end
      lines = VTable(lines)
      local wd = 0
      local ht = #lines
      local grid = VTable {}
      for y=1,ht do
        local linechars = u8split(lines[y])
        wd = max(wd, #linechars)
        grid[y] = linechars
      end
      return Grid {lines=lines, wd=wd, ht=ht, grid=grid}
    end,
  __index = {
    yx  = function (gr,y,x) return gr.grid[y][x] or "" end,
    yxx = function (gr,y,x0,x1)
        local f = function (x) return gr:yx(y,x) end
        return mapconcat(f, seq(x0,x1))
      end,
  },
}

-- «Grid-tests»  (to ".Grid-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Und2D2.lua"
gr0 = Grid.from "abc\nd\nefgh"
= gr0
= gr0.grid
= gr0.wd
= gr0.ht
= gr0.lines
PP(gr0:yx(1,1))
PP(gr0:yx(1,3))
PP(gr0:yx(1,5))
PP(gr0:yxx(3,2,3))

--]==]

-- (find-angg "LUA/Pratt1.lua" "Range")


--   ____ ____                        
--  / ___|  _ \ __ _ _ __   __ _  ___ 
-- | |  _| |_) / _` | '_ \ / _` |/ _ \
-- | |_| |  _ < (_| | | | | (_| |  __/
--  \____|_| \_\__,_|_| |_|\__, |\___|
--                         |___/      
-- «GRange»  (to ".GRange")

GRange = Class {
  type = "GRange",
  from = function (y,x0,x1,...)
      return GRange {y=y, x0=x0, x1=x1, ...}
    end,
  __tostring = function (gra)
      local y,x0,x1 = gra.y,gra.x0,gra.x1
      local s = y..":"
      if x0==x1 then s=s..x0 else s=s..x0.."-"..x1 end
      for i=1,#gra do s=s.." "..mytostring(gra[i]) end
      return s
    end,
  __index = {
  },
}

-- «GRange-tests»  (to ".GRange-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Und2D2.lua"
= GRange.from(2,3,4)
= GRange.from(2,5,5)
= GRange.from(2,5,5,6,"6")
gras = 

--]]




--   ____      _   _ ____  
--  / ___|_ __| | | |  _ \ 
-- | |  _| '__| | | | | | |
-- | |_| | |  | |_| | |_| |
--  \____|_|   \___/|____/ 
--                         
-- «GrUD»  (to ".GrUD")
-- Grid with underbrace data

GrUD = Class {
  type    = "GrUD",
  from = function (lines)
      local grid = Grid.from(lines)
      local gras = VTable {}
      local top  = HTable {}
      for x=1,grid.wd do table.insert(gras, GRange.from(1,x,x,"topc")) end
      for x=1,grid.wd do table.insert(top,  x) end
      return GrUD {grid=grid, gras=gras, top=top}
    end,
  __tostring = function (grud) return grud:tostring() end,
  __index = {
    yxx     = function (grud,y,x0,x1) return grud.grid:yxx(y,x0,x1) end,
    gra     = function (grud,k) return grud.gras[k] end,
    kind    = function (grud,k) return grud.gras[k][1] end,
    kyxx    = function (grud,k) local gra = grud:gra(k); return gra.y,gra.x0,gra.x1 end,
    kyxxstr = function (grud,k) return grud:yxx(grud:kyxx(k)) end,
    ktostring = function (grud,k)
        local k_   = "k="..k..":"
        local gra_ = tostring(grud.gras[k])
        local s_   = mytostring(grud:kyxxstr(k))
        return format("%-5s %-30s %s", k_, gra_, s_)
      end,
    tostring = function (grud,i,j)
        local f = function (k) return grud:ktostring(k) end
        return mapconcat(f, seq(i or grud.grid.wd+1, j or #grud.gras), "\n")
      end,
    --
    settop = function (grud,k,x0,x1) for x=x0,x1 do grud.top[x] = k end end,
    addgra = function (grud, ...) table.insert(grud.gras, GRange.from(...)) end,
    packtop = function (grud,x0,x1)
        grud:addgra(1,x0,x1, "top")
        local k = #grud.gras
        grud:settop(k, x0,x1)
        return k
      end,
    packtops = function (grud,x0,x1)
        local oldst,st = nil,"beg"
        local x2,x3
        local open   = function (x) x2=x; x3=x end
        local extend = function (x) x3=x end
        local close  = function () grud:packtop(x2,x3) end
        for x=x0,x1 do
          oldst,st = st,grud.gras[grud.top[x]][1]
          if oldst ~= "topc" and st == "topc" then open(x) end
          if oldst == "topc" and st == "topc" then extend(x) end
          if oldst == "topc" and st ~= "topc" then close() end
        end
        oldst,st = st,"end"
        if oldst == "topc" then close() end
      end,
    getks = function (grud,x0,x1)
        local ks = VTable {}
        local lastk = function () return ks[#ks] end
        local addk = function (k) if k and k ~= lastk() then table.insert(ks, k) end end
        for x=x0,x1 do addk(grud.top[x]) end
        return ks
      end,
    --
    addconcat = function (grud,x0,x1)
        grud:packtops(x0,x1)
        local ks = grud:getks(x0, x1)
        grud:addgra(1, x0,x1, "concat", unpack(ks))  -- use y=1 (fake)
        local k = #grud.gras
        grud:settop(k, x0,x1)
        return k
      end,
    addunder = function (grud,y,x0,x1)  -- run only after an addconcat
        grud:addgra(y,x0,x1, "under")
        return #grud.gras
      end,
    addunderbrace = function (grud,y,x0,x1)
        grud:addgra(y,x0,x1, "underbrace",
                    grud:addconcat(x0,x1),
                    grud:addunder(y,x0,x1))
        local k = #grud.gras
        grud:settop(k, x0,x1)
        return k
      end,
    addlastconcat = function (grud)
        grud:addconcat(1, grud.grid.wd)
        return #grud.gras
      end,
    --
    addallbars_at_y = function (grud, y, verbose)
        local bars = grud.grid.lines[y]
        for x0,x1r in bars:gmatch("()%S+()") do
          local x1 = x1r-1
          if verbose then PP(y, x0, x1) end
          grud:addunderbrace(y+1, x0,x1)
        end
      end,
    addallbars = function (grud, verbose)
        for y=2,grud.grid.ht,2 do grud:addallbars_at_y(y, verbose) end
        grud:addlastconcat(grud)
        return grud
      end,
    --
    totex_top   = function (grud,k) return grud:kyxxstr(k) end,
    totex_under = function (grud,k) return grud:kyxxstr(k) end,
    totex_underbrace = function (grud,k)
        local gra   = grud:gra(k)
        local over  = grud:totex(gra[2])
        local under = grud:totex(gra[3])
        return format("\\und{%s}{%s}", over, under)
      end,
    totex_concat = function (grud,k)
        local gra = grud:gra(k)
        local part = function (i) return grud:totex(gra[i]) end
        return mapconcat(part, seq(2,#gra))
      end,
    totex = function (grud, k)
        k = k or #grud.gras
        local m = "totex_"..grud:kind(k)
        return grud[m](grud, k)
      end,
    --
    textsfstr = function (grud,str) return format("\\textsf{%s}", str) end,
    totex_top_textsf   = function (grud,k) return grud:textsfstr(grud:kyxxstr(k)) end,
    totex_under_textsf = function (grud,k) return grud:textsfstr(grud:kyxxstr(k)) end,
    totex_underbrace_textsf = function (grud,k)
        local gra   = grud:gra(k)
        local over  = grud:totex_textsf(gra[2])
        local under = grud:totex_textsf(gra[3])
        return format("\\und{%s}{%s}", over, under)
      end,
    totex_concat_textsf = function (grud,k)
        local gra = grud:gra(k)
        local part = function (i) return grud:totex_textsf(gra[i]) end
        return mapconcat(part, seq(2,#gra))
      end,
    totex_textsf = function (grud, k)
        k = k or #grud.gras
        local m = "totex_"..grud:kind(k).."_textsf"
        return grud[m](grud, k)
      end,
  },
}


-- «GrUD-test1»  (to ".GrUD-test1")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Und2D2.lua"
bigstr = [=[
abcdefghij
  --  --
  kl  mn
  -------
  opqrstu
]=]

grud = GrUD.from(bigstr)
= grud
= grud:yxx(1,2,3)    -- bc
= grud:gra(4)        -- 1:4 "topc"
= grud:kyxx(4)       -- 1 4 4
= grud:kyxxstr(4)    -- d
= grud:ktostring(4)  -- k=4: 1:4 "topc"   "d"
= grud:tostring(1)
= grud.grid.lines
= grud.top           -- {1=1, 2=2, 3=3,  4=4,  5=5, 6=6, 7=7,  8=8,  9=9, 10=10}
= grud:packtop(3,4)  -- 11
= grud:packtop(7,8)  -- 12
= grud.top           -- {1=1, 2=2, 3=11, 4=11, 5=5, 6=6, 7=12, 8=12, 9=9, 10=10}
= grud:packtops(2,9) 
= grud.top
= grud
= grud:getks(2,6)
= grud:getks(2,7)
= grud:getks(1,9)

= GrUD.from("a\nbc\ndefg"):tostring()
= GrUD.from("a\nbc\ndefg"):tostring(1)
= GrUD.from("a\nbc\ndefg"):tostring(1,5)

grud = GrUD.from("a\nbc\ndefg")

--]==]


-- «GrUD-test2»  (to ".GrUD-test2")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Und2D2.lua"
bigstr = [=[
abcdefghij
  --  --
  kl  mn
  -------
  opqrstu
]=]

  grud = GrUD.from(bigstr)
  grud:addunderbrace(3,3,4)
= grud
  grud:addunderbrace(3,7,8)
= grud
  grud:addunderbrace(5,3,9)
= grud
  grud:addlastconcat()
= grud

  grud = GrUD.from(bigstr)
  grud:addallbars("verbose")
= grud
= grud:kind(11)

= grud:totex(11)
= grud:totex(26)
= grud:totex()
= grud:totex_textsf()

= GrUD.from(bigstr):addallbars():totex()
= GrUD.from(bigstr):addallbars():totex_textsf()

--]==]







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