util-sta.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['util-sta'] = {
    version   = 1.001,
    comment   = "companion to util-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

local insert, remove, fastcopy, concat = table.insert, table.remove, table.fastcopy, table.concat
local format = string.format
local select, tostring = select, tostring

local trace_stacker = false  trackers.register("stacker.resolve", function(v) trace_stacker = v end)

local stacker = stacker or { }

utilities.stacker = stacker

local function start(s,t,first,last)
    if s.mode == "switch" then
        local n = tostring(t[last])
        if trace_stacker then
            s.report("start: %s",n)
        end
        return n
    else
        local r = { }
        for i=first,last do
            r[#r+1] = tostring(t[i])
        end
        local n = concat(r," ")
        if trace_stacker then
            s.report("start: %s",n)
        end
        return n
    end
end

local function stop(s,t,first,last)
    if s.mode == "switch" then
        local n = tostring(false)
        if trace_stacker then
            s.report("stop: %s",n)
        end
        return n
    else
        local r = { }
        for i=last,first,-1 do
            r[#r+1] = tostring(false)
        end
        local n = concat(r," ")
        if trace_stacker then
            s.report("stop: %s",n)
        end
        return n
    end
end

local function change(s,t1,first1,last1,t2,first2,last2)
    if s.mode == "switch" then
        local n = tostring(t2[last2])
        if trace_stacker then
            s.report("change: %s",n)
        end
        return n
    else
        local r = { }
        for i=last1,first1,-1 do
            r[#r+1] = tostring(false)
        end
        local n = concat(r," ")
        for i=first2,last2 do
            r[#r+1] = tostring(t2[i])
        end
        if trace_stacker then
            s.report("change: %s",n)
        end
        return n
    end
end

function stacker.new(name)

    -- to be sped up, mmaybe foo:bar syntax here but then quite some access

    local report = logs.reporter("stacker",name or nil)

    local s

    local stack = { }
    local list  = { }
    local ids   = { }
    local hash  = { }

    local hashing = true

    local function push(...)
        -- todo check if more than 1 argument
        for i=1,select("#",...) do
            insert(stack,(select(i,...))) -- watch the ()
        end
        if hashing then
            local c = concat(stack,"|")
            local n = hash[c]
            if not n then
                n = #list+1
                hash[c] = n
                list[n] = fastcopy(stack)
            end
            insert(ids,n)
            return n
        else
            local n = #list+1
            list[n] = fastcopy(stack)
            insert(ids,n)
            return n
        end
    end

    local function pop()
        remove(stack)
        remove(ids)
        return ids[#ids] or s.unset or -1
    end

    local function clean()
        if #stack == 0 then
            if trace_stacker then
                s.report("%s list entries, %s stack entries",#list,#stack)
            end
        end
    end

    local tops   = { }
    local top    = nil
    local switch = nil

    local function resolve_reset(mode)
        if #tops > 0 then
            report("resetting %s left-over states of %a",#tops,name)
        end
        tops   = { }
        top    = nil
        switch = nil
    end

    local function resolve_begin(mode)
        if mode then
            switch = mode == "switch"
        else
            switch = s.mode == "switch"
        end
        top = { switch = switch }
        insert(tops,top)
    end

    local function resolve_step(ti) -- keep track of changes outside function !
        -- todo: optimize for n=1 etc
        if not top then
         -- report("messed op stacker %a",name)
            return
        end
        local result = nil
        local noftop = #top
        if ti > 0 then
            local current = list[ti]
            if current then
                local noflist = #current
                local nofsame = 0
                if noflist > noftop then
                    for i=1,noflist do
                        if current[i] == top[i] then
                            nofsame = i
                        else
                            break
                        end
                    end
                else
                    for i=1,noflist do
                        if current[i] == top[i] then
                            nofsame = i
                        else
                            break
                        end
                    end
                end
                local plus = nofsame + 1
                if plus <= noftop then
                    if plus <= noflist then
                        if switch then
                            result = s.change(s,top,plus,noftop,current,nofsame,noflist)
                        else
                            result = s.change(s,top,plus,noftop,current,plus,noflist)
                        end
                    else
                        if switch then
                            result = s.change(s,top,plus,noftop,current,nofsame,noflist)
                        else
                            result = s.stop(s,top,plus,noftop)
                        end
                    end
                elseif plus <= noflist then
                    if switch then
                        result = s.start(s,current,nofsame,noflist)
                    else
                        result = s.start(s,current,plus,noflist)
                    end
                end
                top = current
            else
                if 1 <= noftop then
                    result = s.stop(s,top,1,noftop)
                end
                top = { }
            end
            return result
        else
            if 1 <= noftop then
                result = s.stop(s,top,1,noftop)
            end
            top = { }
            return result
        end
    end

    local function resolve_end()
     -- resolve_step(s.unset)
        if #tops > 0 then -- was #top brrr
            local result = s.stop(s,top,1,#top)
            remove(tops)
            top = tops[#tops]
            switch = top and top.switch
            return result
        end
    end

    local function resolve(t)
        resolve_begin()
        for i=1,#t do
            resolve_step(t[i])
        end
        resolve_end()
    end

    s = {
        name          = name or "unknown",
        unset         = -1,
        report        = report,
        start         = start,
        stop          = stop,
        change        = change,
        push          = push,
        pop           = pop,
        clean         = clean,
        resolve       = resolve,
        resolve_begin = resolve_begin,
        resolve_step  = resolve_step,
        resolve_end   = resolve_end,
        resolve_reset = resolve_reset,
    }

    return s -- we can overload functions

end

-- local s = utilities.stacker.new("demo")
--
-- local unset = s.unset
-- local push  = s.push
-- local pop   = s.pop
--
-- local t = {
--     unset,
--     unset,
--     push("a"),     -- a
--     push("b","c"), -- a b c
--     pop(),         -- a b
--     push("d"),     -- a b d
--     pop(),         -- a b
--     unset,
--     pop(),         -- a
--     pop(),         -- b
--     unset,
--     unset,
-- }
--
-- s.resolve(t)

-- demostacker = utilities.stacker.new("demos")
--
-- local whatever = {
--     one     = "1 0 0 RG 1 0 0 rg",
--     two     = "1 1 0 RG 1 1 0 rg",
--     [false] = "0 G 0 g",
-- }
--
-- local concat = table.concat
--
-- local pageliteral = nodes.pool.pageliteral
--
-- function demostacker.start(s,t,first,last)
--     local n = whatever[t[last]]
--  -- s.report("start: %s",n)
--     return pageliteral(n)
-- end
--
-- function demostacker.stop(s,t,first,last)
--     local n = whatever[false]
--  -- s.report("stop: %s",n)
--     return pageliteral(n)
-- end
--
-- function demostacker.change(s,t1,first1,last1,t2,first2,last2)
--     local n = whatever[t2[last2]]
--  -- s.report("change: %s",n)
--     return pageliteral(n)
-- end
--
-- demostacker.mode = "switch"
--
-- local whatever = {
--     one     = "/OC /test1 BDC",
--     two     = "/OC /test2 BDC",
--     [false] = "EMC",
-- }
--
-- demostacker = utilities.stacker.new("demos")
--
-- function demostacker.start(s,t,first,last)
--     local r = { }
--     for i=first,last do
--         r[#r+1] = whatever[t[i]]
--     end
--  -- s.report("start: %s",concat(r," "))
--     return pageliteral(concat(r," "))
-- end
--
-- function demostacker.stop(s,t,first,last)
--     local r = { }
--     for i=last,first,-1 do
--         r[#r+1] = whatever[false]
--     end
--  -- s.report("stop: %s",concat(r," "))
--     return pageliteral(concat(r," "))
-- end
--
-- function demostacker.change(s,t1,first1,last1,t2,first2,last2)
--     local r = { }
--     for i=last1,first1,-1 do
--         r[#r+1] = whatever[false]
--     end
--     for i=first2,last2 do
--         r[#r+1] = whatever[t2[i]]
--     end
--  -- s.report("change: %s",concat(r," "))
--     return pageliteral(concat(r," "))
-- end
--
-- demostacker.mode = "stack"