Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   http://anggtwu.net/LUA/Comprehensions1.lua.html
--   http://anggtwu.net/LUA/Comprehensions1.lua
--          (find-angg "LUA/Comprehensions1.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
-- Date: 2025sep13
--
-- These classes can be used to show how set comprehensions are
-- calculated step-by-step, using trees drawn as LaTeX matrices like
-- the ones in:
--
--   (mpgp 9 "comprehension-tables")
--   (mpga   "comprehension-tables")
--
-- «.tocurly»			(to "tocurly")
-- «.tocurly-tests»		(to "tocurly-tests")
-- «.MTree»			(to "MTree")
-- «.MTree-tests-defs»		(to "MTree-tests-defs")
-- «.MTree-tests»		(to "MTree-tests")
-- «.Filter»			(to "Filter")
-- «.Comprehension»		(to "Comprehension")
-- «.Comprehension-tests»	(to "Comprehension-tests")


-- «tocurly»  (to ".tocurly")
tocurly_prefix = ""
tocurly_prefix = "\\"
tocurly = function (L, p)
    p = p or tocurly_prefix
    return p.."{"..mapconcat(tostring, L, ",")..p.."}"
  end

-- «tocurly-tests»  (to ".tocurly-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Comprehensions1.lua"
= tocurly(seq(2,10,3))
= tocurly(seq(2,10,-1))
  tocurly_prefix = ""
= tocurly(seq(2,10,3))

--]]



--  __  __ _____              
-- |  \/  |_   _| __ ___  ___ 
-- | |\/| | | || '__/ _ \/ _ \
-- | |  | | | || | |  __/  __/
-- |_|  |_| |_||_|  \___|\___|
--                            
-- «MTree»  (to ".MTree")
-- Base on: (mpgp 9 "comprehension-tables")
--          (mpga   "comprehension-tables")
--          (mpga   "comprehension-tables" "Stop")
--
-- MTrees are used to show how set comprehensions
-- can be calculated using trees.
-- The "M" means "LaTeX <M>atrix",
-- and if m is an MTree then m:totex()
-- generates the body of a LaTeX matrix,
-- in a format like this:
--
--   x & 6-x & \{x,...,6-x\} & y & (x,y) \\\hline
--   1 &  5  & \{1,2,3,4,5\} & 1 & (1,1) & 
--     &     &               & 2 & (1,2) & 
--     &     &               & 3 & (1,3) & 
--     &     &               & 4 & (1,4) & 
--     &     &               & 5 & (1,5) & 
--   2 &  4  &   \{2,3,4\}   & 2 & (2,2) & 
--     &     &               & 3 & (2,3) & 
--     &     &               & 4 & (2,4) & 
--   3 &  3  &     \{3\}     & 3 & (3,3) & 
--   4 &  2  &     \{\}      & \Stop & 
--   5 &  1  &     \{\}      & \Stop & 
--
MTree = Class {
  type = "MTree",
  new  = function (o) return MTree {[0]=o} end,
  _    = function (o) return MTree {[0]=o} end,
  from = function (o)
      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(map(MTree.from, o)) -- other tables are converted recursively
      T[0] = o[0]                         -- keep the [0] field
      return T
    end,
  tosyntree  = function (o) return MTree.from(o):tosyntree() end,
  totexrect  = function (o) return MTree.from(o):totexrect() end,
  torect     = function (o) return MTree.from(o):torect()    end,
  totex      = function (o) return MTree.from(o):totex()     end,
  __tostring = function (o) return MTree.from(o):tostring()  end,
  __index = {
    --
    -- Generate the column names, as a Rect.
    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.columnnames then return Rect {} end
        local line = table.concat(T.columnnames, sep)..post
        return Rect {line}
      end,
    --
    -- The default :tostring() method, that uses SynTrees.
    tostring  = function (T) return T:torect():tostring() end,
    torect    = function (T) return T:colnames_syntree() / T:tosyntree():torect() end,
    tosyntree = function (T) return SynTree.from(T) end,
    --
    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,
    --
    --
    -- The :totex() method generates the body of a LaTeX matrix.
    -- The matrix is generated from left to right, recursively.
    -- Only the outer MTree can have a .columnnames field.
    totex       = function (T) return T:totexrect():tostring() end,
    totexrect   = function (T) return T:colnames_tex() / T:totexrect_LR() end,
    totexrect_R = function (T)
        local R = Rect {}
        for _,node in ipairs(T) do
          for _,line in ipairs(MTree.totexrect(node)) do
            table.insert(R, line)
          end
        end
        return R
      end,
    totexrect_L = function (T,topstr,height)
        local Ltop   = format("%s & ", topstr)
        local Lblank = Ltop:gsub("[^&]", " ")
        return Ltop / Rect.rep(Lblank, height-1)
      end,
    totexrect_LR = function (T)
        local topstr = T[0]
        if #T==0 then return Rect {topstr.." \\\\"} end  -- if we're on a leaf
        local R = T:totexrect_R()
        if not topstr then return R end      -- if we're on the first generator
        return T:totexrect_L(topstr,#R)..R   -- other cases
      end,
    --
    -- 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,
    setcolumnnames = function (T,cols) T.columnnames = cols; return T end,
  },
}

--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Comprehensions1.lua"

T = {{[0]=1},
     {[0]=2, {[0]=23},
             {[0]=24}},
     {[0]=5}}
= MTree.from(T)
PPPV(MTree.from(T))

T = MTree.new()
T:add_(1)
do
  local T2 = T:add_(2)
  T2:add_(23)
  T2:add_(24)
end
T:add_(5)
= T
= T:totex()
PPV(T)
T.columnnames = {"x","y"}
= T
= T:totex()

T = MTree.new()
for x=1,2 do
  local T = T:add_(x)
  for y=3,4 do
    local T = T:add_(y)
  end
end
= T
= T:totex()
T.columnnames = {"x","y"}
= T
= T:totex()
PPV(T)

--]]



--  __  __ _____                _            _       
-- |  \/  |_   _| __ ___  ___  | |_ ___  ___| |_ ___ 
-- | |\/| | | || '__/ _ \/ _ \ | __/ _ \/ __| __/ __|
-- | |  | | | || | |  __/  __/ | ||  __/\__ \ |_\__ \
-- |_|  |_| |_||_|  \___|\___|  \__\___||___/\__|___/
--                                                   
-- «MTree-tests-defs»  (to ".MTree-tests-defs")
-- These tests were hand-written, but the class `Comprehension',
-- defined below, generates code similar to these examples.

MTree_test0 = function ()
    local T = MTree.new()
    for x=1,2 do
      for y=3,4 do
        for z=5,6 do
          T:add_(format("(%s,%s,%s)", x,y,z))
        end
      end
    end
    return T
  end

MTree_test1 = function ()
    local T_ = MTree.new()
    for x=1,2 do
      local Tx = T:add_(x)
      for y=3,4 do
        local Ty = T:add_(y)
        for z=5,6 do
          local Tx = T:add_(z)
        end
      end
    end
    return T_
  end

-- { x in {1,...,5}, y in {x,...,6-x} ; (x,y) }
--                               \-/
--                               lim
--                        \---------/
--                           range
--
MTree_test2 = function ()
    local T = MTree._()
    for x=1,5 do
      local lim = 6-x
      local range = tocurly(seq(x,lim))
      local T = T:add_(x)       -- also called T
      local T = T:add_(lim)     -- also called T
      local T = T:add_(range)   -- also called T, etc
      for y=x,6-x do
        local T = T:add_(y)
        local T = T:add_(format("(%s,%s)",x,y))
      end
      T:addstopifempty()
    end
    T.columnnames = {"x", "6-x", "\\{x,...,6-x\\}", "y", "(x,y)"}
    return T
  end

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

= MTree_test0()
= MTree_test0():totex()

T3 = {[0]=3, 5, 6}
T4 = {[0]=4, 5, 6}
T1 = {[0]=1, T3, T4}
T2 = {[0]=2, T3, T4}
T0 =       { T1, T2}
T  = MTree.from(T0)
T.columnnames = {"x","y","z"}

= T 
= MTree.from(T0)
= MTree.totex(T0)
= MTree.totex(T)

T = MTree_test1()
= T;
= T:totex();

tocurly_prefix = ""
T = MTree_test2()
= T;
= T:totex();

tocurly_prefix = "\\"
T = MTree_test2()
= T;
= T:totex();

--]]


-- «Filter»  (to ".Filter")

MTree_test3 = function ()
    local T = MTree.new()
    for x=1,5 do
      local lim = 6-x
      local range = tocurly(seq(x,lim))
      local T = T:add_(x)       -- also called T
      local T = T:add_(lim)     -- also called T
      local T = T:add_(range)   -- also called T, etc
      for y=x,6-x do
        local T = T:add_(y)
        local T = T:add_(format("(%s,%s)",x,y))
      end
      T:addstopifempty()
    end
    T.columnnames = {"x", "6-x", "\\{x,...,6-x\\}", "y", "(x,y)"}
    return T
  end

--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Comprehensions1.lua"
= MTree_test3()

--]]






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

Comprehension = Class {
  type = "Comprehension",
  new  = function () return Comprehension {prog = Rect{}} end,
  from = function (cmds)
      return Comprehension.new():runcommands(cmds):Function()
    end,
  __tostring = function (co) return co:tostring() end,
  __index = {
    tostring = function (co) return co:program() 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,
    --
    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,
    --
    -- :Function() is the wrapping method that is executed last.
    -- It uses the internal methods :functionbody_(), that returns
    -- a Rect, and :columnnames_(), that returns a string or nil.
    columnnames_ = function (co)              -- returns a string or nil
        if co.columnnames then
          local body = mapconcat(mytostring, co.columnnames, ", ")
          return format("T.columnnames = {%s}", body)
        end
      end,
    functionbody_ = function (co)             -- returns a Rect
        return Rect {"local T = MTree.new()"}
             / Rect { co:columnnames_() }     -- may be empty
             / co.prog
             / Rect {"return T"}
      end,
    Function = function (co)
        local indentedbody = "    " .. co:functionbody_()
        co.prog = Rect {"function ()"}
                / indentedbody
                / Rect {"  end"}
        return co
      end,
    --
    Top = function (co,...) co.columnnames = {...}; return co 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,
    Column = function (co,expr) return co:pre("local T = T:add_(%s)", expr) 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,
    --
    -- TODO: Expr should be Column + collect...
    --
    -- Obsolete?
    Local = function (co,vardef,var)
         if var then co:Column(var) end
         return co:pre("local %s", vardef)
      end,
    --
    --
    splitcommand = function (co,line)
        return VTable(map(bitrim, split(line, "([^:]+)")))
      end,
    runcommand = function (co,line)
        local parts = co:splitcommand(line)
        co[parts[1]](co, unpack(parts,2))
        return co
      end,
    runcommands = function (co,lines)
        if type(lines) == "string" then
          lines = lines:gsub("|", "\n")
          lines = splitlines(lines)
        end
        for i=#lines,1,-1 do
          local line = lines[i]
          line = line:gsub("%-%-.*", "")
          line = bitrim(line)
          if line ~= "" then co:runcommand(line) end
        end
        return co
      end,
    eval = function (co) return expr(co:program())() end,
  },
}

-- «Comprehension-tests»  (to ".Comprehension-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Comprehensions1.lua"
co = Comprehension.from [=[
  Top:x          :y           :x+y       :x+y<6       :10*x+y
  Gen:x=1,2 | Gen:y=3,4 | Expr:x+y | Filt:x+y<6 | Expr:10*x+y
]=]
= co
= co:eval()
= co:eval():collectintolist()
= co:eval():collectintoset()
= co:eval():collectintoset():ksc(" ")
= co:eval():collect():ksc(" ")
= co:eval():totex()

co = Comprehension.from [=[
  Top:x           :6-x          :\{x,\ldots,6-x\}      :y                             :(x,y)
  Gen:x=1,5 | Expr:6-x | Expr:tocurly(seq(x,6-x)) | Gen:y=x,6-x | Expr:format("(%s,%s)",x,y)
]=]
= co
= co:eval()
= co:eval():collectintolist()
= co:eval():collect():ksc(" ")
= co:eval():totex()

= Comprehension {prog=Rect{"BLA"}, columnnames={"x","y"}}
= Comprehension {prog=Rect{"BLA"}, columnnames={"x","y"}} :Function()
= Comprehension {prog=Rect{"BLA"}}                        :Function()
= Comprehension {prog=Rect{"BLA"}} :Top        ("x","y")  :Function()

co = Comprehension.new()
= co:pre("a:%s","AA"):pre("b:%s","BB"):post("c:%s","CC"):post("d:%s","DD")



-- Old:

cmds = [=[
  Colnames : x : 6-x : \{x,\ldots,6-x\} : y : (x,y) 
  Gen      : x = 1,5
  Local    : lim = 6-x                   : lim
  Local    : range = tocurly(seq(x,lim)) : range
  Gen      : y = x,6-x
  Expr     : format("(%s,%s)",x,y)
]=]

cmds = [=[
  Colnames : x : 6-x : \{x,\ldots,6-x\} : y : (x,y) 
  Gen      : x = 1,5
--Expr     :               6-x
  Expr     : tocurly(seq(x,6-x))
  Gen      : y =         x,6-x
  Expr     : format("(%s,%s)",x,y)
]=]

cmds = [=[
  Gen      : x = 1,4
  Filt     : x <= 2
  Expr     : x*10
]=]

co = Comprehension.from(cmds)
= co
= co:eval()
= co:eval():totex()
= co:eval():collect()

co = Comprehension.from "Gen: x=1,2 | Gen: y=3,4"
= co:eval()

tocurly_prefix = ""
tocurly_prefix = "\\"
= MTree_test2()

co = Comprehension.new()
= co:Column  ('format("(%s,%s)",x,y)')
= co:For     ('y=x,6-x',   'y')
= co:Local   ('range = tocurly(seq(x,lim))', 'range')
= co:Local   ('lim = 6-x', 'lim')
= co:For     ('x=1,5',     'x')
= co:Function()

co = Comprehension.new()
= co:splitcommand "Local  : range = tocurly(seq(x,lim)) : range"
= co:runcommand   "Local  : range = tocurly(seq(x,lim)) : range"

--]==]








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