Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   http://anggtwu.net/LUA/Tos3.lua.html
--   http://anggtwu.net/LUA/Tos3.lua
--          (find-angg "LUA/Tos3.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
--
-- This is a rewrite of this class,
--   (find-angg "LUA/lua50init.lua" "Tos")
--   (find-angg "LUA/Tos.lua")
-- that can print nested tables with nested indentation,
-- can serialize tables, and do lots of other nice things.
-- The default is still to print tables linearly.
--
-- (defun e () (interactive) (find-angg "LUA/Tos3.lua"))
-- (defun o () (interactive) (find-angg "LUA/Tos2.lua"))


-- «.MakePrint»		(to "MakePrint")
-- «.MakePrint-tests»	(to "MakePrint-tests")
-- «.TosConcat»		(to "TosConcat")
-- «.TosConcat-tests»	(to "TosConcat-tests")
-- «.Tos»		(to "Tos")
-- «.Tos-tests»		(to "Tos-tests")
-- «.variants»		(to "variants")
-- «.variants-tests»	(to "variants-tests")
-- «.mytostring»	(to "mytostring")
-- «.PP»		(to "PP")
-- «.PP-tests»		(to "PP-tests")



--  __  __       _        ____       _       _   
-- |  \/  | __ _| | _____|  _ \ _ __(_)_ __ | |_ 
-- | |\/| |/ _` | |/ / _ \ |_) | '__| | '_ \| __|
-- | |  | | (_| |   <  __/  __/| |  | | | | | |_ 
-- |_|  |_|\__,_|_|\_\___|_|   |_|  |_|_| |_|\__|
--                                               
-- «MakePrint»  (to ".MakePrint")
-- See: (find-angg "LUA/lua50init.lua" "PP" "PP_ =")

MakePrint = Class {
  type    = "MakePrint",
  with    = function (...) return MakePrint{}:with(...) end,
  __index = {
    with   = function (mp,...) mp.o = pack(...); mp.orig = mp.o; return mp end,
    concat = function (mp,sep) mp.o = table.concat(mp.o, sep); return mp end,
    mapn   = function (mp,f)   mp.o = map(f, mp.o, mp.o.n); return mp end,
    map    = function (mp,f)   mp.o = map(f, mp.o); return mp end,
    pre    = function (mp,p)   return mp:map(function (s) return p..s end) end,
    print  = function (mp)     print(mp.o); return mp end,
    ret    = function (mp)     return myunpack(mp.orig) end,
    --
    fold   = function (mp,f)   mp.o = foldl1(f, mp.o); return mp end,
    _ddot  = function (a,b)    return a.."  "..b end,
    ddot   = function (mp)     return mp:fold(mp._ddot) end,
  },
}

-- «MakePrint-tests»  (to ".MakePrint-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Tos3.lua"
PP2 = function (...)
    return MakePrint.with(...):mapn(mytostring):pre(" "):concat():print():ret()
  end
  PP2(nil, {20,30}, nil)
= PP2(nil, {20,30}, nil)

require "Tree1"       -- (find-angg "LUA/Tree1.lua")
PPT = function (...)
    return MakePrint.with(...):mapn(treetor):ddot():print():ret()
  end
  PPT({1, 2, {3, 4}}, {22, 33}, true, "foo")
= PPT({1, 2, {3, 4}}, {22, 33}, true, "foo")

--]]



--  _____          ____                      _   
-- |_   _|__  ___ / ___|___  _ __   ___ __ _| |_ 
--   | |/ _ \/ __| |   / _ \| '_ \ / __/ _` | __|
--   | | (_) \__ \ |__| (_) | | | | (_| (_| | |_ 
--   |_|\___/|___/\____\___/|_| |_|\___\__,_|\__|
--                                               
-- This class implements several ways of concatenating lists of
-- sorted "key-value string"s. The most basic way works like this,
--   {"a=b", "c=d"} -> "{a=b, c=d}"
-- and the more advanced ways use newlines and indentation in
-- different ways depending on the level.
--
-- «TosConcat»  (to ".TosConcat")

TosConcat = Class {
  type    = "TosConcat",
  __call     = function (tc, strs) return tc:concat(strs) end,
  __tostring = function (tc)
      local q = function (s) return format("%q", s) end
      local f = function (s) return (q(s):gsub("\\\n", "\\n")) end
      return "  abcz_:   "..mapconcat(f, {tc:abcz_()}, "    ")
    end,
  __index = {
    --
    -- Basic:
    abcz   = function (tc) return tc.a, tc.b, tc.c, tc.z end,
    abcz_  = function (tc) return tc.a, tc.b, tc.c, tc.z, tc._ end,
    concat = function (tc, strs)
        local a,b,c,z = tc:abcz()
        if #strs == 0 then return z end
        return a..table.concat(strs, b)..c
      end,
    indent = function (tc) return tc end,
    --
    -- With expansion:
    e       = function (tc, str) return (str:gsub("_", tc._)) end,
    abcz1   = function (tc) return tc:e(tc.a),tc:e(tc.b),tc:e(tc.c),tc:e(tc.z) end,
    indent1 = function (tc) tc = copy(tc); tc._ = tc._ .. " "; return tc end,
  },
}

-- Basic TosConcat'ers:
tosc0    = TosConcat {a="{",     b=", ",    c=   "}", z="{}"}
toscv    = TosConcat {a="{",     b=",\n ",  c= "\n}", z="{}", 
  indent = function (tc) return tosc0 end,
}

-- TosConcat'ers with (recursive) indentation:
toscind1 = TosConcat {a="{\n_ ", b=",\n_ ", c="\n_}", z="{}", _="",
  abcz   = TosConcat.__index.abcz1,
  indent = TosConcat.__index.indent1,
}
toscind2 = TosConcat {a="{\n_ ", b=",\n_ ", c=   "}", z="{}", _="",
  abcz   = TosConcat.__index.abcz1,
  indent = TosConcat.__index.indent1,
}

-- «TosConcat-tests»  (to ".TosConcat-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Tos3.lua"

= toscv
= toscv:indent()
= tosc0
= tosc0:indent()

= toscind1
= toscind1:indent()
= toscind1:indent():indent()

= toscind2
= toscind2:indent()
= toscind2:indent():indent()
= toscind2

test = function (sometc)
    tc = sometc
    tci = tc:indent()
    tcii = tci:indent()
    print(tc {"a=b", "c=" .. tci {"d=e", "f=" .. tcii {"g=h", "i=j"}}})
  end

test(tosc0)
test(toscv)
test(toscind1)
test(toscind2)

--]==]




--  _____         
-- |_   _|__  ___ 
--   | |/ _ \/ __|
--   | | (_) \__ \
--   |_|\___/|___/
--
-- Convert objets "Tos"trings.
--                
-- «Tos»  (to ".Tos")

Tos = Class {
  type    = "Tos",
  __index = {
    --
    -- Basic methods to convert things to strings:
    --   n:     number to string
    --   s:     string to string - adds quotes
    --   f0:    function to string - low level, like <function: 0x...>
    --   f1:    function to string - high level, uses PrintFunction
    --   f:     function to string - uses PrintFunction by default
    --   raw:   like this: true
    --   rawa:  raw with angle brackets - like this: <true>
    --   other: defaults to rawa
    --   ob:    object (basic, i.e., non-table) to string
    --
    n  = function (tos, n) return tostring(n) end,
    s  = function (tos, s) return format("%q", s) end,
    f1 = function (tos, f) return PrintFunction.tostring(f) end,
    f0 = function (tos, f) return "<"..tostring(f)..">" end,
    f  = function (tos, f) return tos:f1(f) end,
    --
    raw   = function (tos, o) return rawtostring(o) end,
    rawa  = function (tos, o) return "<"..rawtostring(o)..">" end,
    other = function (tos, o) return "<"..tostring(o)..">" end,
    --
    ob = function (tos, o)
        local ty = type(o)
        if ty=="number"   then return tos:n(o) end
        if ty=="string"   then return tos:s(o) end
        if ty=="function" then return tos:f(o) end
        if ty=="table"    then return nil end      -- see :o()
        return tos:other(o)
      end,
    --
    -- Low-level recursive methods to convert things to strings:
    --   k:      key   - the default uses tos:o() for simplicity
    --   v:      value - the default uses tos:o() for simplicity
    --   kvp:    key-value pair - {key="a", val=42} -> "a"=42
    --   lskvss: list of sorted "key-value string"s - calls :concat(), see below
    --
    k      = function (tos, k)   return tos:o(k) end,
    v      = function (tos, v)   return tos:o(v) end,
    kvp    = function (tos, kvp) return tos:k(kvp.key).."="..tos:v(kvp.val) end,
    lskvss = function (tos, lskvss) return tos:concat(lskvss) end, -- see below
    --
    -- Methods that use a TosConcat object to concatenate lists of
    -- sorted "key-value string"s. In the default case our output
    -- is linear, like this, "{a=b, c={d=e, f=g}}", and :indent()
    -- is almost a no-op - except for some copying.
    --
    tosconcat = tosc0,
    concat    = function (tos, strs) return tos.tosconcat(strs) end,
    indent    = function (tos)
        tos = copy(tos)
        tos.tosconcat = tos.tosconcat:indent()
        return tos
      end,
    --
    -- The low-level side of converting a table to a list of sorted
    -- key-value pairs and to a list of sorted key-value strings.
    --
    _comparekvs = function (kv1, kv2)  -- a function, not a method!
        local key1,key2 = kv1.key, kv2.key
        return rawtostring_comp(key1, key2)
      end,
    tolskvps = function (tos, T)
        local lkvps = {}
        for k,v in pairs(T) do table.insert(lkvps, {key=k, val=v}) end
        local lskvps = sorted(lkvps, tos._comparekvs)
        return lskvps
      end, 
    tolskvss = function (tos, T)
        local lskvps = tos:tolskvps(T)
        local f = function (kvp) return tos:kvp(kvp) end
        local lskvss = map(f, lskvps)
        return lskvss
      end,
    --
    -- High-level recursive methods to convert things to strings:
    --   t0: table to string
    --   t:  table to string, maybe with add-ons
    --   o:  object (including tables) to string
    --
    -- Note that in a case like this
    --   A = {a=b, c={d=e, f=g}}
    -- we use two diffent ":concat()s"...
    --
    --   first:  tos:indent():concat(        {"d=e", "f=g")
    --   then:   tos         :concat({"a=b", "c={d=e, f=g}")
    --
    t0 = function (tos, T) return tos:concat(tos:indent():tolskvss(T)) end,
    t  = function (tos, T) return tos:t0(T) end,
    o  = function (tos, o) return tos:ob(o) or tos:t(o) end,
  },
}


-- «Tos-tests»  (to ".Tos-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Tos3.lua"
A = { 2, "foo",   {3, 4},   VTable{5, 6} }
A = { 2, "foo", a={3, 4}, b=VTable{5, 6} }
tos30 = Tos {}
= tos30:o(42)
= tos30:o(true)
= tos30:o(nil)
= tos30:kvp({key=2, val=3})
= tos30:o({4,5})
= tos30:o(split)
= tos30:o(A)

= tos30:concat {"a=b", "c=d"}

tos3v = Tos { tosconcat=toscv }
= tos3v:o(A)

AA = {a=1, b={c=2, d={3, 4}}}
= tosv:o(AA)

--]==]



-- __     __         _             _       
-- \ \   / /_ _ _ __(_) __ _ _ __ | |_ ___ 
--  \ \ / / _` | '__| |/ _` | '_ \| __/ __|
--   \ V / (_| | |  | | (_| | | | | |_\__ \
--    \_/ \__,_|_|  |_|\__,_|_| |_|\__|___/
--
-- Sometimes we need to print tables in a format that be read back.
-- For example, we have:
--
--   PP  ({10, "20", a="b"})   --> {1=10, 2="20", "a"="b"}   (bad)
--   PPPI({10, "20", a="b"})   --> {[1]=10, [2]="20", a="b"} (good)
--
-- This is called "serialization", and it needs to print keys inside
-- square brackets; see :kb() below. Also, :tp() is a variant of :t()
-- that print the "class" of a table as a prefix, like this:
--
--   PPPI(VTable {10, 20})     --> VTable {[1]=10, [2]=20}
--
-- This only works for tables tables that are "objects" of "classes"
-- created with eoo.lua.
--
-- «variants»  (to ".variants")
--
table.addentries(Tos.__index, {
    --
    -- :kb()    is like k(), but with square brackets
    -- :tp()    is like :t(), but with a class prefix
    -- :tcols() is an alternative to (the outermost) t
    --
    isname = function (tos, o)
        return type(o) == "string" and o:match"^[%a_][%a%d_]*$"
      end,
    kb = function (tos, k)
        return tos:isname(k) and k or "["..tos:o(k).."]"
      end,
    --
    classsep = ":",
    tp0 = function (tos, T)
        local mt = getmetatable(T)
        local typename = mt and mt.type
	local prefix = typename and (typename .. tos.classsep) or ""
        return prefix..tos:t0(T)
      end,
    tp = function (tos, T) return tos:tp0(T) end,
    --
    tcolfmt = " %-14s %s",
    tcolk   = function (tos, k) return tostring(k)..":" end,
    tcol    = function (tos, kvp)
        local kstr,vstr = tos:tcolk(kvp.key),tos:o(kvp.val)
        return format(tos.tcolfmt, kstr, vstr)
      end,
    tcols   = function (tos, T)
        local lskvps = tos:tolskvps(T)   -- list of sorted key-value pairs
        local f = function (kvp) return tos:tcol(kvp) end
        return mapconcat(f, lskvps, "\n")
      end,

  })

-- «variants-tests»  (to ".variants-tests")
--[[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Tos3.lua"
A = {10, "20", a="b"}
A = {10, "20", a="b", VTable {42, 99}}
PP  (A)
PPPI(A)

--]]



-- «mytostring»  (to ".mytostring")
-- «PP»          (to ".PP")
-- (find-angg "LUA/lua50init.lua" "mytostring")
-- (find-angg "LUA/lua50init.lua" "PP")
-- (find-angg "LUA/lua50init.lua" "PP" "PP_")

tos0         = Tos {}
tosv         = Tos { tosconcat=toscv }
tospv        = Tos { tosconcat=toscv, t=Tos.__index.tp }
tosp         = Tos {                  t=Tos.__index.tp }
mytostring   = function (o) return tos0:o(o)  end
mytostringv  = function (o) return tosv:o(o)  end
mytostringp  = function (o) return tosp:o(o)  end
mytostringvp = function (o) return tospv:o(o) end
mytostringpv = function (o) return tospv:o(o) end
PPPV         = function (o) print(mytostringpv(o)); return o end
PPP          = function (o) print(mytostringp (o)); return o end
PPV          = function (o) print(mytostringv (o)); return o end
PP           = function (...) return PP_(mytostring, ...) end

tosi         = Tos { tosconcat=toscind1, k=Tos.__index.kb }
tosip        = Tos { tosconcat=toscind1, k=Tos.__index.kb,
                     t=Tos.__index.tp,   classsep=" " }
mytostringi  = function (o) return tosi:o(o)  end
mytostringip = function (o) return tosip:o(o) end
PPI          = function (o) print(mytostringi(o));  return o end
PPIP         = function (o) print(mytostringip(o)); return o end
PPPI         = function (o) print(mytostringip(o)); return o end

PPC          = function (T) print(tos0:tcols(T)) end

-- «PP-tests»  (to ".PP-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "Tos3.lua"

AA = {a=1, b={c=2, d={3, "4"}}, VTable {5,6}}
= AA
PP(AA)
PPV(AA)
PPI(AA)
PPPI(AA)
PPP(AA)

-- PPC(Tos)
PPC(Tos.__index)
PPPI(AA)

--]==]


-- (find-angg "LUA/lua50init.lua" "HTable-and-VTable")

HTable = Class {
  type = "HTable",
  __tostring = mytostring,
  __index = {
  },
}
HTableP = Class {
  type = "HTableP",
  __tostring = mytostringp,
  __index = {
  },
}
VTable = Class {
  type = "VTable",
  __tostring = mytostringv,
  __index = {
  },
}
VTableP = Class {
  type = "VTableP",
  __tostring = mytostringvp,
  __index = {
  },
}




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