    if not modules then modules = { } end modules ['node-nut'] = {
    version   = 1.001,
    comment   = "companion to node-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- Here starts some more experimental code that Luigi and I use in a next stage of
-- exploring and testing potential speedups in the engines. This code is not meant
-- for users and can change (or be removed) any moment. During the experiments I'll
-- do my best to keep the code as fast as possible by using two codebases. See
-- about-fast.pdf for some more info about impacts. Although key based access has
-- more charm, function based is somewhat faster and has more potential for future
-- speedups.

-- This next iteration is flagged direct because we avoid user data which has a price
-- in allocation and metatable tagging. Although in this stage we pass numbers around
-- future versions might use light user data, so never depend on what direct function
-- return. Using the direct approach had some speed advantages but you loose the key
-- based access. The speed gain is only measurable in cases with lots of access. For
-- instance when typesettign arabic with advanced fonts, we're talking of many millions
-- of function calls and there we can get a 30\% or more speedup. On average complex
-- \CONTEXT\ runs the gain can be 10\% to 15\% percent. Because mixing the two models
-- (here we call then nodes and nuts) is not possible you need to cast either way which
-- has a penalty. Also, error messages in nuts mode are less clear and \LUATEX\ will
-- often simply abort when you make mistakes of mix the models. So, development (at least
-- in \CONTEXT) can be done in node mode and not in nuts mode. Only robust code will
-- be turned nuts afterwards and quite likely not all code. The official \LUATEX\ api
-- to nodes is userdata!
--
-- Listening to 'lunatic soul' at the same time helped wrapping my mind around the mixed
-- usage of both models. Just for the record: the potential of the direct approach only
-- became clear after experimenting for weeks and partly adapting code. It is one of those
-- (sub)projects where you afterwards wonder if it was worth the trouble, but users that
-- rely on lots of complex functionality and font support will probably notice the speedup.
--
--                                luatex                    luajittex
-- -------------    -----    --------------------     ---------------------------------
-- name             pages     old   new       pct      old         new              pct
-- -------------    -----    --------------------     ---------------------------------
-- fonts-mkiv         166     9.3   7.7/7.4  17.2      7.4 (37.5)  5.9/5.7 (55.6)  20.3
-- about               60     3.3   2.7/2.6  20.4      2.5 (39.5)  2.1     (57.0)  23.4
-- arabic-001          61    25.3  15.8      18.2     15.3 (46.7)  6.8     (54.7)  16.0
-- torture-001        300    21.4  11.4      24.2     13.9 (35.0)  6.3     (44.7)  22.2
--
-- so:
--
-- - we run around 20% faster on documents of average complexity and gain more when
--   dealing with scripts like arabic and such
-- - luajittex benefits a bit more so a luajittex job can (in principle) now be much
--   faster
-- - if we reason backwards, and take luajittex as norm we get 1:2:3 on some jobs for
--   luajittex direct:luatex direct:luatex normal i.e. we can be 3 times faster
-- - keep in mind that these are tex/lua runs so the real gain at the lua end is much
--   larger
--
-- Because we can fake direct mode a little bit by using the fast getfield and setfield
-- at the cost of wrapped getid and alike, we still are running quite ok. As we could gain
-- some 5% with fast mode, we can sacrifice some on wrappers when we use a few fast core
-- functions. This means that simulated direct mode runs font-mkiv in 9.1 seconds (we could
-- get down to 8.7 seconds in fast mode) and that we can migrate slowely to direct mode.
--
-- The following measurements are from 2013-07-05 after adapting some 47 files to nuts. Keep
-- in mind that the old binary can fake a fast getfield and setfield but that the other
-- getters are wrapped functions. The more we have, the slower it gets.
--
--                                           fonts   about   arabic
-- old mingw, indexed plus some functions :   8.9     3.2     20.3
-- old mingw, fake functions              :   9.9     3.5     27.4
-- new mingw, node functions              :   9.0     3.1     20.8
-- new mingw, indexed plus some functions :   8.6     3.1     19.6
-- new mingw, direct functions            :   7.5     2.6     14.4
--
-- \starttext \dorecurse{1000}{test\page} \stoptext :
--
-- luatex    560 pps
-- luajittex 600 pps
--
-- \setupbodyfont[pagella]
--
-- \edef\zapf{\cldcontext{context(io.loaddata(resolvers.findfile("zapf.tex")))}}
--
-- \starttext \dorecurse{1000}{\zapf\par} \stoptext
--
-- luatex    3.9 sec / 54 pps
-- luajittex 2.3 sec / 93 pps

local type, rawget = type, rawget

local nodes      = nodes
local direct     = node.direct

local fastcopy   = table.fastcopy

local nodecodes  = nodes.nodecodes
local hlist_code = nodecodes.hlist
local vlist_code = nodecodes.vlist
local glyph_code = nodecodes.glyph

local nuts       = nodes.nuts or { }
nodes.nuts       = nuts

nodes.isnode     = direct.isnode   or function() return true  end
nodes.isdirect   = direct.isdirect or function() return false end
nodes.isnut      = nodes.isdirect

-- casters

local tonode     = direct.tonode   or function(n) return n end
local tonut      = direct.todirect or function(n) return n end

nuts.tonode      = tonode
nuts.tonut       = tonut

nodes.tonode     = tonode
nodes.tonut      = tonut

-- helpers

local nuts                   = nodes.nuts

nuts.checkdiscretionaries    = direct.check_discretionaries
nuts.copy                    = direct.copy
nuts.copynode                = direct.copy
nuts.copyonly                = direct.copy_only or direct.copy
nuts.copylist                = direct.copy_list
nuts.count                   = direct.count
nuts.currentattribute        = direct.current_attr
nuts.currentattr             = direct.current_attr
nuts.delete                  = direct.delete
nuts.dimensions              = direct.dimensions
nuts.endofmath               = direct.end_of_math
nuts.findattribute           = direct.find_attribute
nuts.firstglyph              = direct.first_glyph
nuts.flattendiscretionaries  = direct.flatten_discretionaries
nuts.flush                   = direct.flush_node
nuts.flushlist               = direct.flush_list
nuts.flushnode               = direct.flush_node
nuts.free                    = direct.free
nuts.getsynctexfields        = direct.get_synctex_fields
nuts.hasattribute            = direct.has_attribute
nuts.hasfield                = direct.has_field
nuts.hasglyph                = direct.has_glyph or direct.first_glyph
nuts.hpack                   = direct.hpack
nuts.insertafter             = direct.insert_after
nuts.insertbefore            = direct.insert_before
nuts.isdirect                = direct.is_direct
nuts.isnode                  = direct.is_node
nuts.isnut                   = direct.is_direct
nuts.kerning                 = direct.kerning
nuts.hyphenating             = direct.hyphenating
nuts.lastnode                = direct.last_node
nuts.length                  = direct.length
nuts.ligaturing              = direct.ligaturing
nuts.new                     = direct.new
nuts.protectglyph            = direct.protect_glyph
nuts.protectglyphs           = direct.protect_glyphs
nuts.protrusionskippable     = direct.protrusion_skippable
nuts.rangedimensions         = direct.rangedimensions
nuts.setattribute            = direct.set_attribute
nuts.setsynctexfields        = direct.set_synctex_fields
nuts.slide                   = direct.slide
nuts.tail                    = direct.tail
nuts.tostring                = direct.tostring
nuts.traverse                = direct.traverse
nuts.traversechar            = direct.traverse_char
nuts.traverseglyph           = direct.traverse_glyph
nuts.traverseid              = direct.traverse_id
nuts.traverselist            = direct.traverse_list
nuts.unprotectglyph          = direct.unprotect_glyph
nuts.unprotectglyphs         = direct.unprotect_glyphs
nuts.unsetattribute          = direct.unset_attribute
nuts.unsetattribute          = direct.unset_attribute
nuts.usedlist                = direct.usedlist
nuts.usesfont                = direct.uses_font
nuts.vpack                   = direct.vpack
nuts.write                   = direct.write
nuts.mlisttohlist            = direct.mlist_to_hlist
nuts.hasdimensions           = direct.has_dimensions
nuts.startofpar              = direct.start_of_par
nuts.migrate                 = direct.migrate

if not nuts.mlisttohlist then

    local n_mlisttohlist = node.mlist_to_hlist

    function nuts.mlisttohlist(head,...)
        if head then
            local head = n_mlisttohlist(tonode(head),...)
            if head then
                return tonut(head)
            end
        end
    end

end

if not nuts.hasdimensions then

    local getwhd = direct.getwhd

    function nuts.hasdimensions(n)
        local wd, ht, dp = getwhd(n)
        return wd ~= 0 or (ht + dp) ~= 0
    end

end

local getfield        = direct.getfield
local setfield        = direct.setfield

nuts.getfield         = getfield
nuts.setfield         = setfield

nuts.getnext          = direct.getnext
nuts.setnext          = direct.setnext

nuts.getid            = direct.getid

nuts.getprev          = direct.getprev
nuts.setprev          = direct.setprev

local getattribute    = direct.get_attribute
local setattribute    = direct.set_attribute
local unsetattribute  = direct.unset_attribute

nuts.getattr          = getattribute
nuts.setattr          = setattribute
nuts.takeattr         = unsetattribute -- ?

nuts.getattribute     = getattribute
nuts.setattribute     = setattribute
nuts.unsetattribute   = unsetattribute -- ?

nuts.iszeroglue       = direct.is_zero_glue
nuts.effectiveglue    = direct.effective_glue

nuts.getglue          = direct.getglue
nuts.setglue          = direct.setglue
nuts.getboxglue       = direct.getglue
nuts.setboxglue       = direct.setglue

nuts.getdisc          = direct.getdisc
nuts.setdisc          = direct.setdisc
nuts.getdiscretionary = direct.getdisc
nuts.setdiscretionary = direct.setdisc

nuts.getpre           = direct.getpre
nuts.setpre           = direct.setpre
nuts.getpost          = direct.getpost
nuts.setpost          = direct.setpost
nuts.getreplace       = direct.getreplace
nuts.setreplace       = direct.setreplace

local getdata         = direct.getdata
local setdata         = direct.setdata

nuts.getdata          = getdata
nuts.setdata          = setdata
nuts.getvalue         = getdata
nuts.setvalue         = setdata

nuts.getexpansion     = direct.getexpansion
nuts.setexpansion     = direct.setexpansion

nuts.getwhd           = direct.getwhd
nuts.setwhd           = direct.setwhd
nuts.getwidth         = direct.getwidth
nuts.setwidth         = direct.setwidth
nuts.getheight        = direct.getheight
nuts.setheight        = direct.setheight
nuts.getdepth         = direct.getdepth
nuts.setdepth         = direct.setdepth
nuts.getshift         = direct.getshift
nuts.setshift         = direct.setshift
nuts.gettotal         = direct.gettotal

-- lmtx compatibility

nuts.getorientation   = direct.getorientation or function() end
nuts.setorientation   = direct.setorientation or function() end

nuts.getglyphdata     = direct.getglyphdata or               getattribute
nuts.setglyphdata     = direct.setglyphdata or function(n,d) setattribute(n,0,d) end

nuts.getruledata      = direct.getglyphdata and getdata or function(n)   return getfield(n,"transform")   end
nuts.setruledata      = direct.setglyphdata and setdata or function(n,d) return setfield(n,"transform",d) end

-- maybe some day: [g|s]etglyphoptions and then use attribute for mkiv / generic but not now

nuts.getoptions       = direct.getoptions or function() return 0 end
nuts.setoptions       = direct.setoptions or function() end

-- so far

nuts.getnucleus       = direct.getnucleus
nuts.setnucleus       = direct.setnucleus
nuts.getsup           = direct.getsup
nuts.setsup           = direct.setsup
nuts.getsub           = direct.getsub
nuts.setsub           = direct.setsub
nuts.getsuppre        = direct.getsuppre
nuts.setsuppre        = direct.setsuppre
nuts.getsubpre        = direct.getsubpre
nuts.setsubpre        = direct.setsubpre

nuts.getchar          = direct.getchar
nuts.setchar          = direct.setchar
nuts.getfont          = direct.getfont
nuts.setfont          = direct.setfont
nuts.getfam           = direct.getfam
nuts.setfam           = direct.setfam

nuts.getboth          = direct.getboth
nuts.setboth          = direct.setboth
nuts.setlink          = direct.setlink
nuts.exchange         = direct.exchange
nuts.reverse          = direct.reverse
nuts.setsplit         = direct.setsplit

nuts.getlist          = direct.getlist -- only hlist and vlist !
nuts.setlist          = direct.setlist
nuts.getleader        = direct.getleader
nuts.setleader        = direct.setleader
nuts.getcomponents    = direct.getcomponents
nuts.setcomponents    = direct.setcomponents

nuts.getsubtype       = direct.getsubtype
nuts.setsubtype       = direct.setsubtype

nuts.getlang          = direct.getlang
nuts.setlang          = direct.setlang
nuts.getlanguage      = direct.getlang
nuts.setlanguage      = direct.setlang

nuts.getattrlist      = direct.getattributelist
nuts.setattrlist      = direct.setattributelist
nuts.getattributelist = direct.getattributelist
nuts.setattributelist = direct.setattributelist

nuts.getoffsets       = direct.getoffsets
nuts.setoffsets       = direct.setoffsets

nuts.getkern          = direct.getkern
nuts.setkern          = direct.setkern

nuts.getdir           = direct.getdir
nuts.setdir           = direct.setdir

nuts.getdirection     = direct.getdirection
nuts.setdirection     = direct.setdirection

nuts.getpenalty       = direct.getpenalty
nuts.setpenalty       = direct.setpenalty

nuts.getbox           = direct.getbox
nuts.setbox           = direct.setbox

nuts.ischar           = direct.is_char
nuts.isglyph          = direct.is_glyph

local d_remove_node   = direct.remove
local d_flushnode     = direct.flush_node
local d_getnext       = direct.getnext
local d_getprev       = direct.getprev
local d_getid         = direct.getid
local d_getlist       = direct.getlist
local d_find_tail     = direct.tail
local d_insertafter   = direct.insert_after
local d_insertbefore  = direct.insert_before
local d_slide         = direct.slide
local d_traverse      = direct.traverse
local d_setlink       = direct.setlink
local d_setboth       = direct.setboth
local d_getboth       = direct.getboth

local remove = function(head,current,free_too)
    if current then
        local h, c = d_remove_node(head,current)
        if free_too then
            d_flushnode(current)
            return h, c
        else
            d_setboth(current)
            return h, c, current
        end
    end
    return head, current
end

-- for now

if not nuts.startofpar then

    local parcodes      = nodes.parcodes
    local hmodepar_code = parcodes.vmode_par
    local vmodepar_code = parcodes.hmode_par

    local getsubtype    = nuts.getsubtype

    function nuts.startofpar(n)
        local s = getsubtype(n)
        return s == hmodepar_code or s == vmodepar_code
    end

end

-- for now

if not nuts.exchange then

    local d_getprev = direct.getprev
    local d_getnext = direct.getnext
    local d_setlink = direct.setlink

    function nuts.exchange(head,first,second)
        if first then
            if not second then
                second = d_getnext(first)
            end
            if second then
                d_setlink(d_getprev(first),second,first,d_getnext(second))
                if first == head then
                    return second
                end
            end
        end
        return head
    end

end

-- for now

if not nuts.getpre then

    local d_getdisc  = direct.getdisc
    local d_setfield = direct.setfield

    function nuts.getpre    (n) local h, _, _, t, _, _ = d_getdisc(n,true) return h, t end
    function nuts.getpost   (n) local _, h, _, _, t, _ = d_getdisc(n,true) return h, t end
    function nuts.getreplace(n) local _, _, h, _, _, t = d_getdisc(n,true) return h, t end

    function nuts.setpre    (n,h) d_setfield(n,"pre",    h) end
    function nuts.setpost   (n,h) d_setfield(n,"post",   h) end
    function nuts.setreplace(n,h) d_setfield(n,"replace",h) end

end

if not nuts.gettotal then

    local d_getheight = direct.getheight
    local d_getdepth  = direct.getdepth

    function nuts.gettotal(n)
        return (d_getheight(n) or 0) + (d_getdepth(n) or 0)
    end

end

-- alias

nuts.getsurround = nuts.getkern
nuts.setsurround = nuts.setkern

nuts.remove = remove

function nuts.delete(head,current)
    return remove(head,current,true)
end

function nuts.replace(head,current,new) -- no head returned if false
    if not new then
        head, current, new = false, head, current
    end
    local prev, next = d_getboth(current)
    if prev or next then
        d_setlink(prev,new,next)
    end
    if head then
        if head == current then
            head = new
        end
        d_flushnode(current)
        return head, new
    else
        d_flushnode(current)
        return new
    end
end

local function countall(stack,flat)
    local n = 0
    while stack do
        local id = d_getid(stack)
        if not flat and id == hlist_code or id == vlist_code then
            local list = d_getlist(stack)
            if list then
                n = n + 1 + countall(list) -- self counts too
            else
                n = n + 1
            end
        else
            n = n + 1
        end
        stack = d_getnext(stack)
    end
    return n
end

nuts.countall = countall

function nodes.countall(stack,flat)
    return countall(tonut(stack),flat)
end

function nuts.append(head,current,...)
    for i=1,select("#",...) do
        head, current = d_insertafter(head,current,(select(i,...)))
    end
    return head, current
end

function nuts.prepend(head,current,...)
    for i=1,select("#",...) do
        head, current = d_insertbefore(head,current,(select(i,...)))
    end
    return head, current
end

function nuts.linked(...) -- slides !
    local head, last
    for i=1,select("#",...) do
        local next = select(i,...)
        if next then
            if head then
                d_setlink(last,next)
            else
                head = next
            end
            last = d_find_tail(next) -- we could skip the last one
        end
    end
    return head
end

function nuts.concat(list) -- consider tail instead of slide
    local head, tail
    for i=1,#list do
        local li = list[i]
        if li then
            if head then
                d_setlink(tail,li)
            else
                head = li
            end
            tail = d_slide(li)
        end
    end
    return head, tail
end

function nuts.reference(n)
    return n or "<none>"
end

-- quick and dirty tracing of nuts

-- for k, v in next, nuts do
--     if string.find(k,"box") then
--         nuts[k] = function(...) print(k,...) return v(...) end
--     end
-- end

function nodes.vianuts (f) return function(n,...) return tonode(f(tonut (n),...)) end end
function nodes.vianodes(f) return function(n,...) return tonut (f(tonode(n),...)) end end

nuts.vianuts  = nodes.vianuts
nuts.vianodes = nodes.vianodes

function nodes.insertlistafter(h,c,n)
    local t = n_tail(n)
    if c then
        local cn = n_getnext(c)
        if cn then
            -- no setboth here yet
            n_setfield(t,"next",cn)
            n_setfield(cn,"prev",t)
        else
            n_setfield(t,"next",nil)
        end
        n_setfield(c,"next",n)
        n_setfield(n,"prev",c)
        return h, n
    end
    return n, t
end

function nuts.insertlistafter(h,c,n)
    local t = d_tail(n)
    if c then
        local cn = d_getnext(c)
        if cn then
            d_setlink(t,cn)
        else
            d_setnext(t)
        end
        d_setlink(c,n)
        return h, n
    end
    return n, t
end

-- test code only

-- collectranges and mix

local report = logs.reporter("sliding")

local function message(detail,head,current,previous)
    report("error: %s, current: %s:%s, previous: %s:%s, list: %s, text: %s",
        detail,
        nodecodes[d_getid(current)],
        current,
        nodecodes[d_getid(previous)],
        previous,
        nodes.idstostring(head),
        nodes.listtoutf(head)
    )
    utilities.debugger.showtraceback(report)
end

local function warn()
    report()
    report("warning: the slide tracer is enabled")
    report()
    warn = false
end

local function tracedslide(head)
    if head then
        if warn then
            warn()
        end
        local next = d_getnext(head)
        if next then
            local prev = head
            for n in d_traverse(next) do
                local p = d_getprev(n)
                if not p then
                    message("unset",head,n,prev)
                 -- break
                elseif p ~= prev then
                    message("wrong",head,n,prev)
                 -- break
                end
                prev = n
            end
        end
        return d_slide(head)
    end
end

local function nestedtracedslide(head,level) -- no sliding !
    if head then
        if warn then
            warn()
        end
        local id = d_getid(head)
        local next = d_getnext(head)
        if next then
            report("%whead:%s",level or 0,nodecodes[id])
            local prev = head
            for n in d_traverse(next) do
                local p = d_getprev(n)
                if not p then
                    message("unset",head,n,prev)
                 -- break
                elseif p ~= prev then
                    message("wrong",head,n,prev)
                 -- break
                end
                prev = n
                local id = d_getid(n)
                if id == hlist_code or id == vlist_code then
                    nestedtracedslide(d_getlist(n),(level or 0) + 1)
                end
            end
        elseif id == hlist_code or id == vlist_code then
            report("%wlist:%s",level or 0,nodecodes[id])
            nestedtracedslide(d_getlist(head),(level or 0) + 1)
        end
     -- return d_slide(head)
    end
end

local function untracedslide(head)
    if head then
        if warn then
            warn()
        end
        local next = d_getnext(head)
        if next then
            local prev = head
            for n in d_traverse(next) do
                local p = d_getprev(n)
                if not p then
                    return "unset", d_getid(n)
                elseif p ~= prev then
                    return "wrong", d_getid(n)
                end
                prev = n
            end
        end
        return d_slide(head)
    end
end

nuts.tracedslide       = tracedslide
nuts.untracedslide     = untracedslide
nuts.nestedtracedslide = nestedtracedslide

-- this might move

local propertydata = direct.get_properties_table(true)

local getattr = nuts.getattr
local setattr = nuts.setattr

nodes.properties = {
    data = propertydata,
}

if direct.set_properties_mode then
    direct.set_properties_mode(true,true)  -- create metatable, slower but needed for font-otj.lua (unless we use an intermediate table)
    function direct.set_properties_mode() end
end

-- experimental code with respect to copying attributes has been removed
-- as it doesn't pay of (most attributes are only accessed once anyway)

nuts.getprop = function(n,k)
    local p = propertydata[n]
    if p then
        if k then
            return p[k]
        else
            return p
        end
    end
end

nuts.rawprop = function(n,k)
    local p = rawget(propertydata,n)
    if p then
        if k then
            return p[k]
        else
            return p
        end
    end
end

nuts.setprop = function(n,k,v)
    local p = propertydata[n]
    if p then
        p[k] = v
    else
        propertydata[n] = { [k] = v }
    end
end

nuts.theprop = function(n)
    local p = propertydata[n]
    if not p then
        p = { }
        propertydata[n] = p
    end
    return p
end

local getstate = direct.getstate
local setstate = direct.setstate

if not setstate or not getstate then

    setstate = function(n,v)
        local p = propertydata[n]
        if p then
            p.state = v
        else
            propertydata[n] = { state = v }
        end
    end

    getstate = function(n,v)
        local p = propertydata[n]
        if p then
            if v then
                return p.state == v
            else
                return p.state
            end
        else
            return nil
        end
    end
end

nuts.setstate = setstate
nuts.getstate = getstate

local getscript = direct.getscript or function(n,v) end -- elsewhere
local setscript = direct.setscript or function(n,v) end -- elsewhere

nuts.setscript = setscript
nuts.getscript = getscript

function nuts.isdone(n,k)
    local p = propertydata[n]
    if not p then
        propertydata[n] = { [k] = true }
        return false
    end
    local v = p[k]
    if v == nil then
        propertydata[n] = { [k] = true }
        return false
    end
    return v
end

function nuts.copy_properties(source,target,what)
    local newprops = propertydata[source]
    if not newprops then
        -- nothing to copy
        return
    end
    if what then
        -- copy one category
        newprops = rawget(source,what)
        if newprops then
            newprops = fastcopy(newprops)
            local p = rawget(propertydata,target)
            if p then
                p[what] = newprops
            else
                propertydata[target] = {
                    [what] = newprops,
                }
            end
        end
    else
        -- copy all properties
        newprops = fastcopy(newprops)
        propertydata[target] = newprops
    end
    return newprops -- for checking
end
