Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   https://anggtwu.net/LUA/Comprehensions2.lua.html
--   https://anggtwu.net/LUA/Comprehensions2.lua
--           (find-angg "LUA/Comprehensions2.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
--
-- (defun e () (interactive) (find-angg "LUA/Comprehensions2.lua"))
-- (defun o () (interactive) (find-angg "LUA/Comprehensions1.lua"))
-- (defun oe () (interactive) (find-2a '(o) '(e)))

-- «.MTree»				(to "MTree")
--   «.colnames»			(to "colnames")
--   «.syntree»				(to "syntree")
--   «.tex»				(to "tex")
--   «.add»				(to "add")
--   «.collect»				(to "collect")
-- «.Comprehension»			(to "Comprehension")
--   «.Gen-Filt-Expr»			(to "Gen-Filt-Expr")
--   «.Function»			(to "Function")
--   «.runcmds»				(to "runcmds")
-- «.Comprehension-tests»		(to "Comprehension-tests")
-- «.Comprehension-runcmds-tests»	(to "Comprehension-runcmds-tests")


--  __  __ _____              
-- |  \/  |_   _| __ ___  ___ 
-- | |\/| | | || '__/ _ \/ _ \
-- | |  | | | || | |  __/  __/
-- |_|  |_| |_||_|  \___|\___|
--                            
-- «MTree»  (to ".MTree")

MTree = Class {
  type = "MTree",
  new  = function (o) return MTree {[0]=o} end,
  from = function (o,...)   -- very dwimmy, for building MTrees by hand for tests
      if #{...}>0 then o = {[0]=o,...} end
      if  type(o) == "number" then o = tostring(o) end
      if  type(o) == "string" then return MTree {[0]=o} end
      if otype(o) == "MTree"  then return o end  -- Mtrees are returned unchanged
      local T = MTree {[0]=o[0]}        -- other tables are converted recursively
      for _,leaf in ipairs(o) do table.insert(T, MTree.from(leaf)) end
      return T
    end,
  __tostring = function (T) return T:tostring() end,        -- colnames + syntree
  __index = {
    --
    -- «colnames»  (to ".colnames")
    setcolnames      = function (T,cols) T.colnames = cols; return T end,
    colnames_syntree = function (T) return T:colnames_rect(" | ", "") end,
    colnames_tex     = function (T) return T:colnames_rect(" & ", " \\\\\\HLine") end,
    colnames_rect    = function (T,sep,post)
        if not T.colnames then return Rect {} end
        local line = table.concat(T.colnames, sep)..post
        return Rect {line}
      end,
    --
    -- «syntree»  (to ".syntree")
    torect   = function (T) return T:colnames_syntree() / SynTree.from(T):torect() end,
    tostring = function (T) return T:torect():tostring() end,
    --
    -- «tex»  (to ".tex")
    totexrect1 = function (T)
        if #T==0 then return Rect {T[0].." \\\\"} end  -- if we're on a leaf
        local rect = Rect {}
        for _,leaf in ipairs(T) do rect = rect / leaf:totexrect1() end
        if T[0] then rect = tostring(T[0])..(" & "*rect) end
        return rect
      end,
    totexrect2 = function (T) return T:colnames_tex() / T:totexrect1() end,
    totexrect3 = function (T)
        return Rect {"\\comprehensionbox{"}
               / ("  "..T:totexrect2())
               / "  }"
      end,
    totexrect = function (T) return T:totexrect3() end,
    totex     = function (T) return T:totexrect():tostring() end,
    --
    -- «add»  (to ".add")
    -- Methods that add elements and subtrees to an MTree.
    -- These methods are very tricky! See MTree_test{1,2,3} below.
    add = function (T,o)
        local subTree = MTree.new(o)
        table.insert(T,subTree)
        return subTree
      end,
    addstop  = function (T) T:add("\\Stop") end,
    addfalse = function (T) T:add("\\False"):addstop() end,
    addtrue  = function (T) return T:add("\\True") end,
    addstopifempty = function (T) if #T == 0 then T:addstop() end end,
    --
    -- «collect»  (to ".collect")
    collect         = function (T) return T:collectintoset() end,
    collectintoset  = function (T) return Set.from(T:collectintolist()) end,
    collectintolist = function (T) return T:collectinto(VTable{}) end,
    collectinto     = function (T,L)
        if #T==0
        then if T[0] and T[0] ~= "\\Stop" then table.insert(L,T[0]) end
        else for _,subTree in ipairs(T) do subTree:collectinto(L) end
        end
        return L
      end,
  },
}

--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Comprehensions2.lua"
tr = MTree.from

T = MTree.from   {MTree.from(3,34,35), 4}
T = MTree.from(2, MTree.from(3, 34,
                                35),
                  4)
= T
PPP(T)
T.colnames = {"foo", "bar"}
= T
= T:totexrect1()
= T:totexrect2()
= T:totexrect3()

--]]




--   ____                               _                    _             
--  / ___|___  _ __ ___  _ __  _ __ ___| |__   ___ _ __  ___(_) ___  _ __  
-- | |   / _ \| '_ ` _ \| '_ \| '__/ _ \ '_ \ / _ \ '_ \/ __| |/ _ \| '_ \ 
-- | |__| (_) | | | | | | |_) | | |  __/ | | |  __/ | | \__ \ | (_) | | | |
--  \____\___/|_| |_| |_| .__/|_|  \___|_| |_|\___|_| |_|___/_|\___/|_| |_|
--                      |_|                                                
--
-- «Comprehension»  (to ".Comprehension")
Comprehension = Class {
  type = "Comprehension",
  new  = function () return Comprehension {prog = Rect{}} end,
  from = function (names,cmds,verbose)
      local co = Comprehension {prog=Rect{}, names=names, cmds=cmds}
      if names then co.colnames = split(names) end
      co:runcmds(cmds,verbose)
      co:Function()
      return co
    end,
  __tostring = function (co) return co:tostring() end,
  __index = {
    tostring = function (co) return co.prog:tostring() end,
    program  = function (co) return co.prog:tostring() end,
    pre_     = function (co,r1) co.prog = torect(r1) / co.prog; return co end,
    post_    = function (co,r2) co.prog = co.prog / torect(r2); return co end,
    pre      = function (co,fmt,...) return co:pre_ (format(fmt,...)) end,
    post     = function (co,fmt,...) return co:post_(format(fmt,...)) end,
    --
    -- «Gen-Filt-Expr»  (to ".Gen-Filt-Expr")
    var = function (co,str)   -- co:var("_,x in seq(2,5)") returns "x"
        str = bitrim(str)
        str = str:gsub("^_,", "")
        str = str:gsub("^([0-9A-Za-z_]+).*", "%1")
        return str
      end,
    For = function (co,gen)
        co.prog = Rect {format("for %s do", gen)}
                / ("  " .. co.prog)
                / Rect {"end"}
                / Rect {"T:addstopifempty()"}
        return co
      end,
    Filt = function (co,expr)
        local indentedbody = "  " .. (Rect {"local T = T:addtrue()"} / co.prog)
        co.prog = Rect {format("if %s then", expr)}
                / indentedbody
                / Rect {"else T = T:addfalse()"}
                / Rect {"end"} 
        return co
      end,
    Expr = function (co,expr) return co:pre("local T = T:add(%s)", expr) end,
    Gen  = function (co,gen)  return co:Expr(co:var(gen)):For(gen) end,
    --
    -- «Function»  (to ".Function")
    Setcolnames = function (co)
        if not co.colnames then return co end
        local body = mapconcat(mytostring, co.colnames, ", ")
	co.prog = Rect {format("T:setcolnames({%s})", body)} / co.prog
        return co
      end,
    Functionbody = function (co)
        co.prog = Rect {"local T = MTree.new()"}
                / co.prog
                / Rect {"return T"}
        return co
      end,
    Function0 = function (co)
        co.prog = Rect {"function ()"}
                / ("    "..co.prog)
                / Rect {"  end"}
        return co
      end,
    Function = function (co)
        return co:Setcolnames():Functionbody():Function0()
      end,
    run = function (co) return expr(c:program())() end,
    --
    -- «runcmds»  (to ".runcmds")
    runcmd = function (co,cmd,verbose)
        local funname,arg = cmd:match("^(.-):(.*)$")
        if verbose then PP("runcmd:", funname, arg) end
        return co[funname](co, arg)
      end,
    runcmds = function (co,cmds,verbose)
        if verbose then PP("runcmds:", cmds) end
        for _,cmd in ipairs(table.reverse(split(cmds))) do
          co:runcmd(cmd,verbose)
        end
        return co
      end,
  },
}

-- «Comprehension-tests»  (to ".Comprehension-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Comprehensions2.lua"

c = Comprehension.new()
= c:Expr("x+y")
= c:Filt("x+y<5")
= c:Gen("y=2,3")
= c:Gen("x=1,2")

c.colnames = {"x", "y", "x+y<5", "x+y"}
= c:Function()

= c:program()
= c:run()
= c:run():totex()
= c:run():collect()
= c:run():collect():ksc(" ")
= c:run():collectintolist()
= table.concat(c:run():collectintolist(), " ")

--]]






-- «Comprehension-runcmds-tests»  (to ".Comprehension-runcmds-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Comprehensions2.lua"

c = Comprehension.new()
c:runcmd("Expr:x+y",   "verbose")
c:runcmd("Filt:x+y<5", "verbose")
c:runcmd("Gen:y=2,3",  "verbose")
c:runcmd("Gen:x=1,2",  "verbose")
c:Function()
= c:run()

c = Comprehension.new()
c:runcmds("Gen:x=1,2  Gen:y=2,3  Filt:x+y<5  Expr:x+y", "verbose")
c:Function()
= c:program()
= c:run()

c = Comprehension.from(
      "    x          y           x+y<5       x+y",
      "Gen:x=1,2  Gen:y=2,3  Filt:x+y<5  Expr:x+y",
      "verbose")
= c
= c:run()
= c:run():collect():ksc(" ")
= c:run():totex()

--]]