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

-- use lpeg matchers

local find, match = string.find, string.match
local insert, remove = table.insert, table.remove

local attributes, nodes = attributes, nodes

local set_attributes      = nodes.setattributes
local traverse_nodes      = node.traverse

local nodecodes           = nodes.nodecodes

local math_noad_code      = nodecodes.noad           -- attr nucleus sub sup
local math_accent_code    = nodecodes.accent         -- attr nucleus sub sup accent
local math_radical_code   = nodecodes.radical        -- attr nucleus sub sup left degree
local math_fraction_code  = nodecodes.fraction       -- attr nucleus sub sup left right
local math_box_code       = nodecodes.subbox         -- attr list
local math_sub_code       = nodecodes.submlist       -- attr list
local math_char_code      = nodecodes.mathchar       -- attr fam char
local math_textchar_code  = nodecodes.mathtextchar   -- attr fam char
local math_delim_code     = nodecodes.delim          -- attr small_fam small_char large_fam large_char
local math_style_code     = nodecodes.style          -- attr style
local math_choice_code    = nodecodes.choice         -- attr display text script scriptscript
local math_fence_code     = nodecodes.fence          -- attr subtype

local hlist_code          = nodecodes.hlist
local vlist_code          = nodecodes.vlist
local glyph_code          = nodecodes.glyph
local glue_code           = nodecodes.glue

local a_tagged            = attributes.private('tagged')
local a_exportstatus      = attributes.private('exportstatus')
local a_mathcategory      = attributes.private('mathcategory')
local a_mathmode          = attributes.private('mathmode')

local tags                = structures.tags

local start_tagged        = tags.start
local restart_tagged      = tags.restart
local stop_tagged         = tags.stop
local taglist             = tags.taglist

local chardata            = characters.data

local getmathcode         = tex.getmathcode
local mathcodes           = mathematics.codes
local ordinary_code       = mathcodes.ordinary
local variable_code       = mathcodes.variable

local process

local function processsubsup(start)
    -- At some point we might need to add an attribute signaling the
    -- super- and subscripts because TeX and MathML use a different
    -- order.
    local nucleus, sup, sub = start.nucleus, start.sup, start.sub
    if sub then
        if sup then
            start[a_tagged] = start_tagged("msubsup")
            process(nucleus)
            process(sub)
            process(sup)
            stop_tagged()
        else
            start[a_tagged] = start_tagged("msub")
            process(nucleus)
            process(sub)
            stop_tagged()
        end
    elseif sup then
        start[a_tagged] = start_tagged("msup")
        process(nucleus)
        process(sup)
        stop_tagged()
    else
        process(nucleus)
    end
end

-- todo: check function here and keep attribute the same

-- todo: variants -> original

local actionstack = { }

process = function(start) -- we cannot use the processor as we have no finalizers (yet)
    while start do
        local id = start.id
        if id == math_char_code then
            local char = start.char
            -- check for code
            local a = start[a_mathcategory]
            if a then
                a = { detail = a }
            end
            local code = getmathcode(char)
            if code then
                code = code[1]
            end
            local tag
            if code == ordinary_code or code == variable_code then
                local ch = chardata[char]
                local mc = ch and ch.mathclass
                if mc == "number" then
                    tag = "mn"
                elseif mc == "variable" or not mc then -- variable is default
                    tag = "mi"
                else
                    tag = "mo"
                end
            else
                tag = "mo"
            end
            start[a_tagged] = start_tagged(tag,a)
            stop_tagged()
            break -- okay?
        elseif id == math_textchar_code then
            -- check for code
            local a = start[a_mathcategory]
            if a then
                start[a_tagged] = start_tagged("ms",{ detail = a })
            else
                start[a_tagged] = start_tagged("ms")
            end
            stop_tagged()
            break
        elseif id == math_delim_code then
            -- check for code
            start[a_tagged] = start_tagged("mo")
            stop_tagged()
            break
        elseif id == math_style_code then
            -- has a next
        elseif id == math_noad_code then
            processsubsup(start)
        elseif id == math_box_code or id == hlist_code or id == vlist_code then
            -- keep an eye on math_box_code and see what ends up in there
            local attr = start[a_tagged]
            local last = attr and taglist[attr]
            if last and find(last[#last],"formulacaption[:%-]") then
                -- leave alone, will nicely move to the outer level
            else
                local text = start_tagged("mtext")
                start[a_tagged] = text
                local list = start.list
                if not list then
                    -- empty list
                elseif not attr then
                    -- box comes from strange place
                    set_attributes(list,a_tagged,text)
                else
                    -- Beware, the first node in list is the actual list so we definitely
                    -- need to nest. This approach is a hack, maybe I'll make a proper
                    -- nesting feature to deal with this at another level. Here we just
                    -- fake structure by enforcing the inner one.
                    local tagdata = taglist[attr]
                    local common = #tagdata + 1
                    local function runner(list) -- quite inefficient
                        local cache = { } -- we can have nested unboxed mess so best local to runner
                        for n in traverse_nodes(list) do
                            local id = n.id
                            local aa = n[a_tagged]
                            if aa then
                                local ac = cache[aa]
                                if not ac then
                                    local tagdata = taglist[aa]
                                    local extra = #tagdata
                                    if common <= extra then
                                        for i=common,extra do
                                            ac = restart_tagged(tagdata[i]) -- can be made faster
                                        end
                                        for i=common,extra do
                                            stop_tagged() -- can be made faster
                                        end
                                    else
                                        ac = text
                                    end
                                    cache[aa] = ac
                                end
                                n[a_tagged] = ac
                            else
                                n[a_tagged] = text
                            end
                            if id == hlist_code or id == vlist_code then
                                runner(n.list)
                            end
                        end
                    end
                    runner(list)
                end
                stop_tagged()
            end
        elseif id == math_sub_code then
            local list = start.list
            if list then
                local attr = start[a_tagged]
                local last = attr and taglist[attr]
                local action = last and match(last[#last],"maction:(.-)%-")
                if action and action ~= "" then
                    if actionstack[#actionstack] == action then
                        start[a_tagged] = start_tagged("mrow")
                        process(list)
                        stop_tagged()
                    else
                        insert(actionstack,action)
                        start[a_tagged] = start_tagged("mrow",{ detail = action })
                        process(list)
                        stop_tagged()
                        remove(actionstack)
                    end
                else
                    start[a_tagged] = start_tagged("mrow")
                    process(list)
                    stop_tagged()
                end
            end
        elseif id == math_fraction_code then
            local num, denom, left, right = start.num, start.denom, start.left, start.right
            if left then
               left[a_tagged] = start_tagged("mo")
               process(left)
               stop_tagged()
            end
            start[a_tagged] = start_tagged("mfrac")
            process(num)
            process(denom)
            stop_tagged()
            if right then
                right[a_tagged] = start_tagged("mo")
                process(right)
                stop_tagged()
            end
        elseif id == math_choice_code then
            local display, text, script, scriptscript = start.display, start.text, start.script, start.scriptscript
            if display then
                process(display)
            end
            if text then
                process(text)
            end
            if script then
                process(script)
            end
            if scriptscript then
                process(scriptscript)
            end
        elseif id == math_fence_code then
            local delim = start.delim
            local subtype = start.subtype
            if subtype == 1 then
                -- left
                start[a_tagged] = start_tagged("mfenced")
                if delim then
                    start[a_tagged] = start_tagged("mleft")
                    process(delim)
                    stop_tagged()
                end
            elseif subtype == 2 then
                -- middle
                if delim then
                    start[a_tagged] = start_tagged("mmiddle")
                    process(delim)
                    stop_tagged()
                end
            elseif subtype == 3 then
                if delim then
                    start[a_tagged] = start_tagged("mright")
                    process(delim)
                    stop_tagged()
                end
                stop_tagged()
            else
                -- can't happen
            end
        elseif id == math_radical_code then
            local left, degree = start.left, start.degree
            if left then
                start_tagged("")
                process(left) -- root symbol, ignored
                stop_tagged()
            end
            if degree then -- not good enough, can be empty mlist
                start[a_tagged] = start_tagged("mroot")
                processsubsup(start)
                process(degree)
                stop_tagged()
            else
                start[a_tagged] = start_tagged("msqrt")
                processsubsup(start)
                stop_tagged()
            end
        elseif id == math_accent_code then
            local accent, bot_accent = start.accent, start.bot_accent
            if bot_accent then
                if accent then
                    start[a_tagged] = start_tagged("munderover",{ detail = "accent" })
                    processsubsup(start)
                    process(bot_accent)
                    process(accent)
                    stop_tagged()
                else
                    start[a_tagged] = start_tagged("munder",{ detail = "accent" })
                    processsubsup(start)
                    process(bot_accent)
                    stop_tagged()
                end
            elseif accent then
                start[a_tagged] = start_tagged("mover",{ detail = "accent" })
                processsubsup(start)
                process(accent)
                stop_tagged()
            else
                processsubsup(start)
            end
        elseif id == glue_code then
            start[a_tagged] = start_tagged("mspace")
            stop_tagged()
        else
            start[a_tagged] = start_tagged("merror", { detail = nodecodes[i] })
            stop_tagged()
        end
        start = start.next
    end
end

function noads.handlers.tags(head,style,penalties)
    local v_math = start_tagged("math")
    local v_mrow = start_tagged("mrow")
    local v_mode = head[a_mathmode]
    head[a_tagged] = v_math
    head[a_tagged] = v_mrow
    tags.setattributehash(v_math,"mode",v_mode == 1 and "display" or "inline")
    process(head)
    stop_tagged()
    stop_tagged()
    return true
end
