|
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: