Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   http://angg.twu.net/LUA/Tos.lua.html
--   http://angg.twu.net/LUA/Tos.lua
--           (find-angg "LUA/Tos.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
-- Version: 2023nov26
-- Public domain.
--
-- A class that lets me build lots of tostring-like functions.
-- This "is" a standalone version, with comments and tests, of:
--   (find-angg "LUA/lua50init.lua" "Tos")
-- but I haven't tested its standaloneness in ages...
--
-- See also: (find-es "lua5" "Tos")
--           (find-es "lua5" "Tos-2021")
--           (find-angg "LUA/tos.lua")
--           (find-angg "LUA/Tos2.lua")

-- «.Tos»		(to "Tos")
-- «.tos0-and-tosp»	(to "tos0-and-tosp")
-- «.mytostring»	(to "mytostring")
-- «.PP»		(to "PP")
-- «.HTable-and-VTable»	(to "HTable-and-VTable")
-- «.tests»		(to "tests")


-- From: (find-angg "LUA/lua50init.lua" "eoo")
--       (find-dn6 "eoo.lua")
--
Class = {
    type   = "Class",
    __call = function (class, o) return setmetatable(o, class) end,
  }
setmetatable(Class, Class)

-- From: (find-angg "LUA/lua50init.lua" "compat")
--       (find-angg "LUA/lua50init.lua" "pack-and-unpack")
--       (find-angg "LUA/lua50init.lua" "printf")
--       (find-angg "LUA/lua50init.lua" "sorted")
--       (find-angg "LUA/lua50init.lua" "map")
--
format = string.format
write  = io.write
pack   = table.pack or function (...) return {n=select("#", ...), ...} end
printf = function (...) write(format(...)) end
--
sorted = function (tbl, lt) table.sort(tbl, lt); return tbl end
map = function (f, arr, n)
    local brr = {}
    for i=1,(n or #arr) do table.insert(brr, (f(arr[i]))) end
    return brr
  end
mapconcat = function (f, tbl, sep, n)
    return table.concat(map(f, tbl, n), sep)
  end

rawtostring = function (o)
    if type(o) == "table" then
      local mt = getmetatable(o); setmetatable(o, nil)
      local rawtos = tostring(o); setmetatable(o, mt)
      return rawtos
    end
    return tostring(o)
  end
rawtostring_comp = function (o1, o2)
    local t1, t2 = type(o1), type(o2)
    if t1 == t2 then
      if t1 == "number" then return o1 < o2 end
      if t1 == "string" then return o1 < o2 end
      return rawtostring(o1) < rawtostring(o2)  -- fast
    else
      return t1 < t2   -- numbers before strings before tables, etc
    end
  end




-- «Tos»  (to ".Tos")
-- Also here: (find-angg "LUA/lua50init.lua" "Tos")
--
Tos = Class {
  type    = "Tos",
  __index = {
    --
    -- Basic methods:
    --   o: object (of any type) to string
    --   ov: like o, but vertical in a simplistic way
    --   t: table to string
    --    t0: table to string, low level
    --   kvs: listofkeyvaluepairs to string
    --   kv: keyvaluepair to string
    --   k: key to string
    --
    o = function (tos, o, a,sep,b,emp)
        local ty = type(o)
        if ty=="number" then return tostring(o) end
        if ty=="string" then return format("%q", o) end
        if ty=="table"  then return tos:t(o, a,sep,b,emp) end
        return "<"..tostring(o)..">"
      end,
    ov = function (tos, o, a,sep,b,emp)
        return tos:o(o, "{ ", ",\n  ", "\n}", "{}")
      end,
    t = function (tos, T, a,sep,b,emp)
        return tos:t0(T, a,sep,b,emp)
      end,
    t0 = function (tos, T, a,sep,b,emp)
        local tableisempty = (next(T) == nil)
        if tableisempty and emp then return emp end
        local body = tos:kvs(tos:getsortedkvs(T), sep)
        return (a or "{")..body..(b or "}")
      end,
    --
    kvs = function (tos, ps, sep)
        local tos_p = function (p) return tos:kv(p) end
        return mapconcat(tos_p, ps, sep or ", ")
      end,
    kv = function (tos, p) return tos:k(p.key).."="..tos:o(p.val) end,
    k = function (tos, k) return tos:o(k) end,
    --
    -- t0 uses this to sort the key-value pairs of a table.
    getsortedkvs = function (tos, T)
        return sorted(tos:getkvs(T), tos.comparekvs)
      end,
    getkvs = function (tos, T)
        local kvs = {}
        for k,v in pairs(T) do table.insert(kvs, {key=k, val=v}) end
	return kvs
      end,
    comparekvs = function (kv1, kv2)  -- not a method!
        local k1, k2 = kv1.key,  kv2.key
        return rawtostring_comp(k1, k2)
        local t1, t2 = type(k1), type(k2)
        -- if t1 == t2 then
        --   if t1 == "number" then return k1 < k2 end
        --   if t1 == "string" then return k1 < k2 end
        --   return rawtostring(k1) < rawtostring(k2)  -- fast
        -- else
        --   return t1 < t2   -- numbers before strings before tables, etc
        -- end
      end,
    --
    -- return a tostring-like function
    f = function (tos, a,sep,b,emp)
        return function (o) return tos:o(o, a,sep,b,emp) end
      end,
    --
    -- An alternative to t.
    -- When tos is an object of the class Tos
    --  and foo is an object of the class Foo we have this:
    --   tos:t (foo)  -->     "{...}"
    --   tos:tp(foo)  --> "Foo:{...}"
    --   
    tp = function (tos, T, a,sep,b,emp)  -- experimental
        local mt = getmetatable(T)
        local typename = mt and mt.type
	local prefix = typename and (typename..":") or "" 
        return prefix..tos:t0(T, a,sep,b,emp)
      end,
  },
}

-- «tos0-and-tosp»  (to ".tos0-and-tosp")
-- Two objects of the class Tos.
-- "tos0" uses all the default methods,
-- "tosp" overrides ":t" with ":tp" to display class names.
tos0 = Tos({})
tosp = Tos({t = Tos.__index.tp})

-- «mytostring»  (to ".mytostring")
-- Basic tostring-ish functions.
-- To override them, redefine these functions.
mytostring      = function (o) return tos0:o (o) end
mytostringv     = function (o) return tos0:ov(o) end
mytostringp     = function (o) return tosp:o (o) end
mytostringpv    = function (o) return tosp:ov(o) end

mytabletostring = function (o) return tos0:ov(o) end -- old name

-- «PP»  (to ".PP")
-- Basic pretty-printing functions.
PPV = function (o) print(mytabletostring(o)); return o end
PP  = function (...) return PP_(mytostring, ...) end
PP_ = function (tos, ...)
    local args = pack(...)
    for i=1,args.n do printf(" %s", tos(args[i])) end
    print()
    return ...
  end

-- «HTable-and-VTable»  (to ".HTable-and-VTable")
-- See: (find-angg "LUA/lua50init.lua" "HTable-and-VTable")


-- «tests»  (to ".tests")
-- (getenv "LUA_INIT")
-- (setenv "LUA_INIT" nil)
-- (setenv (concat "@" (ee-expand "~/LUA/lua50init.lua"))
--
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Tos.lua"

Aaa = Class {
  type = "Aaa",
  __tostring = mytostring,
  __index = {
  },
}
Bbb = Class {
  type = "Bbb",
  __tostring = mytostringpv,
  __index = {
  },
}

A = Aaa {1, 2, 3}
B = Bbb {10, 20, 30}
C = {30, A, B, 40}

PPV(A)
PP(A, B)
= A
= B

= C
= mytostring(C)
= mytostringv(C)
= mytostringp(C)
= mytostringpv(C)

--]]




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