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

-- todo: insert_after

local format = string.format

local trace_migrations = false trackers.register("nodes.migrations", function(v) trace_migrations = v end)

local report_nodes = logs.reporter("nodes","migrations")

local attributes    = attributes
local nodes         = nodes
local enableaction  = nodes.tasks.enableaction

local nuts          = nodes.nuts
local tonut         = nuts.tonut

local getnext       = nuts.getnext
local getid         = nuts.getid
local getlist       = nuts.getlist
----- getattr       = nuts.getattr
local getprop       = nuts.getprop

----- setattr       = nuts.setattr
local setprop       = nuts.setprop
local setlink       = nuts.setlink
local setlist       = nuts.setlist
local setprev       = nuts.setprev
local setnext       = nuts.setnext
local setboth       = nuts.setboth

local remove_node   = nuts.remove

local nodecodes     = nodes.nodecodes
local hlist_code    = nodecodes.hlist
local vlist_code    = nodecodes.vlist
local insert_code   = nodecodes.ins
local mark_code     = nodecodes.mark

local a_migrated    = attributes.private("migrated")

local migrate_inserts, migrate_marks, inserts_too

local t_inserts, t_marks, t_sweeps = 0, 0, 0

local function locate(head,first,last,ni,nm)
    local current = head
    while current do
        local id = getid(current)
        if id == vlist_code or id == hlist_code then
            local list = getlist(current)
            if list then
                list, first, last, ni, nm = locate(list,first,last,ni,nm)
                setlist(current,list)
            end
            current = getnext(current)
        elseif migrate_inserts and id == insert_code then
            local insert
            head, current, insert = remove_node(head,current)
            if first then
                setnext(insert)
                setlink(last,insert)
            else
                setboth(insert)
                first = insert
            end
            last = insert
            ni = ni + 1
        elseif migrate_marks and id == mark_code then
            local mark
            head, current, mark = remove_node(head,current)
            if first then
                setnext(mark)
                setlink(last,mark)
            else
                setboth(mark)
                first = mark
            end
            last = mark
            nm = nm + 1
        else
            current = getnext(current)
        end
    end
    return head, first, last, ni, nm
end

function nodes.handlers.migrate(head,where)
    if head then
        if trace_migrations then
            report_nodes("migration sweep %a",where)
        end
        local current = head
        while current do
            local id = getid(current)
            -- inserts_too is a temp hack, we should only do them when it concerns
            -- newly placed (flushed) inserts

            -- todo: getprop / setprop

         -- if id == vlist_code or id == hlist_code or (inserts_too and id == insert_code) and not getattr(current,a_migrated) then
            if id == vlist_code or id == hlist_code or (inserts_too and id == insert_code) and not getprop(current,"migrated") then
             -- setattr(current,a_migrated,1)
                setprop(current,"migrated",true)
                t_sweeps = t_sweeps + 1
                local h = getlist(current)
                local first, last, ni, nm
                while h do
                    local id = getid(h)
                    if id == vlist_code or id == hlist_code then
                        h, first, last, ni, nm = locate(h,first,last,0,0)
                    end
                    h = getnext(h)
                end
                if first then
                    t_inserts = t_inserts + ni
                    t_marks   = t_marks   + nm
                    if trace_migrations and (ni > 0 or nm > 0) then
                        report_nodes("sweep %a, container %a, %s inserts and %s marks migrated outwards during %a",
                            t_sweeps,nodecodes[id],ni,nm,where)
                    end
                    -- inserts after head, use insert_after
                    local n = getnext(current)
                    if n then
                        setlink(last,n)
                    end
                    setlink(current,first)
                    current = last
                end
            end
            current = getnext(current)
        end
        return head
    end
end

-- for the moment this way, this will disappear

experiments.register("marks.migrate", function(v)
    if v then
        enableaction("mvlbuilders", "nodes.handlers.migrate")
    end
    migrate_marks = v
end)

experiments.register("inserts.migrate", function(v)
    if v then
        enableaction("mvlbuilders", "nodes.handlers.migrate")
    end
    migrate_inserts = v
end)

experiments.register("inserts.migrate.nested", function(v)
    if v then
        enableaction("mvlbuilders", "nodes.handlers.migrate")
    end
    inserts_too = v
end)

statistics.register("node migrations", function()
    if trace_migrations and t_sweeps > 0 then
        return format("%s sweeps, %s inserts moved, %s marks moved",t_sweeps,t_inserts,t_marks)
    end
end)