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

local P, V, S, R, C, Cs, Cmt, Carg = lpeg.P, lpeg.V, lpeg.S, lpeg.R, lpeg.C, lpeg.Cs, lpeg.Cmt, lpeg.Carg
local lpegmatch, patterns = lpeg.match, lpeg.patterns

local insert, remove = table.insert, table.remove
local rep, sub = string.rep, string.sub
local setmetatable = setmetatable

local pushtarget, poptarget = logs.pushtarget, logs.poptarget

local report_macros = logs.reporter("interface","macros")

local stack, top, n, hashes = { }, nil, 0, { }

local function set(s)
    if top then
        n = n + 1
        if n > 9 then
            report_macros("number of arguments > 9, ignoring %s",s)
        else
            local ns = #stack
            local h = hashes[ns]
            if not h then
                h = rep("#",ns)
                hashes[ns] = h
            end
            m = h .. n
            top[s] = m
            return m
        end
    end
end

local function get(s)
    if not top then
        report_macros("keeping #%s, no stack",s)
        return "#" .. s -- can be lua
    end
    local m = top[s]
    if m then
        return m
    else
        report_macros("keeping #%s, not on stack",s)
        return "#" .. s -- quite likely an error
    end
end

local function push()
    top = { }
    n = 0
    local s = stack[#stack]
    if s then
        setmetatable(top,{ __index = s })
    end
    insert(stack,top)
end

local function pop()
    top = remove(stack)
end

local leftbrace   = P("{")   -- will be in patterns
local rightbrace  = P("}")
local escape      = P("\\")

local space       = patterns.space
local spaces      = space^1
local newline     = patterns.newline
local nobrace     = 1 - leftbrace - rightbrace

local longleft       = leftbrace  -- P("(")
local longright      = rightbrace -- P(")")
local nolong         = 1 - longleft - longright

local name           = R("AZ","az")^1
local csname         = (R("AZ","az") + S("@?!_"))^1
local longname       = (longleft/"") * (nolong^1) * (longright/"")
local variable       = P("#") * Cs(name + longname)
local escapedname    = escape * csname
local definer        = escape * (P("def") + P("egx") * P("def"))                  -- tex
local setter         = escape * P("set") * (P("u")^-1 * P("egx")^-1) * P("value") -- context specific
---                  + escape * P("install") * (1-P("handler"))^1 * P("handler")  -- context specific
local startcode      = P("\\starttexdefinition")                                  -- context specific
local stopcode       = P("\\stoptexdefinition")                                   -- context specific
local anything       = patterns.anything
local always         = patterns.alwaysmatched

-- The comment nilling can become an option but it nicely compensates the Lua
-- parsing here with less parsing at the TeX end. We keep lines so the errors
-- get reported all right, but comments are never seen there anyway. We keep
-- comment that starts inline as it can be something special with a % (at some
-- point we can do that as well, esp if we never use \% or `% somewhere
-- unpredictable). We need to skip comments anyway. Hm, too tricky, this
-- stripping as we can have Lua code etc.

local commenttoken   = P("%")
local crorlf         = S("\n\r")
local commentline    = commenttoken * ((Carg(1) * C((1-crorlf)^0))/function(strip,s) return strip and "" or s end)
local commentline    = commenttoken * ((1-crorlf)^0)
local leadingcomment = (commentline * crorlf^1)^1
local furthercomment = (crorlf^1 * commentline)^1

local pushlocal      = always   / push
local poplocal       = always   / pop
local declaration    = variable / set
local identifier     = variable / get

local argument       = leftbrace * ((identifier + (1-rightbrace))^0) * rightbrace

local function matcherror(str,pos)
    report_macros("runaway definition at: %s",sub(str,pos-30,pos))
end

local grammar = { "converter",
    texcode     = pushlocal
                * startcode
                * spaces
                * name
                * spaces
                * (declaration + furthercomment + (1 - newline - space))^0
                * V("texbody")
                * stopcode
                * poplocal,
    texbody     = (   V("definition")
                    + identifier
                    + V("braced")
                    + (1 - stopcode)
                  )^0,
    definition  = pushlocal
                * definer
                * escapedname
                * (declaration + furthercomment + commentline + (1-leftbrace))^0
                * V("braced")
                * poplocal,
    setcode     = pushlocal
                * setter
                * argument
                * (declaration + furthercomment + commentline + (1-leftbrace))^0
                * V("braced")
                * poplocal,
    braced      = leftbrace
                * (   V("definition")
                    + identifier
                    + V("setcode")
                    + V("texcode")
                    + V("braced")
                    + furthercomment
                    + nobrace
                  )^0
             -- * rightbrace^-1, -- the -1 catches errors
                * (rightbrace + Cmt(always,matcherror)),

    pattern     = leadingcomment
                + V("definition")
                + V("setcode")
                + V("texcode")
                + furthercomment
                + anything,

    converter   = V("pattern")^1,
}

local parser = Cs(grammar)

local checker = P("%") * (1 - newline - P("macros"))^0
              * P("macros") * space^0 * P("=") * space^0 * C(patterns.letter^1)

-- maybe namespace

local macros = { } resolvers.macros = macros

function macros.preprocessed(str,strip)
    return lpegmatch(parser,str,1,strip)
end

function macros.convertfile(oldname,newname) -- beware, no testing on oldname == newname
    local data = resolvers.loadtexfile(oldname)
    data = interfaces.preprocessed(data) or ""
    io.savedata(newname,data)
end

function macros.version(data)
    return lpegmatch(checker,data)
end

function macros.processmkvi(str,filename)
    if (filename and file.suffix(filename) == "mkvi") or lpegmatch(checker,str) == "mkvi" then
        local result = lpegmatch(parser,str,1,true) or str
        pushtarget("log")
        report_macros("processed file '%s', delta %s",filename,#str-#result)
        poptarget("log")
        return result
    else
        return str
    end
end

if resolvers.schemes then

    local function handler(protocol,name,cachename)
        local hashed = url.hashed(name)
        local path = hashed.path
        if path and path ~= "" then
            local str = resolvers.loadtexfile(path)
            if file.suffix(path) == "mkvi" or lpegmatch(checker,str) == "mkvi" then
                -- already done automatically
                io.savedata(cachename,str)
            else
                local result = lpegmatch(parser,str,1,true) or str
                pushtarget("log")
                report_macros("processed scheme '%s', delta %s",filename,#str-#result)
                poptarget("log")
                io.savedata(cachename,result)
            end
        end
        return cachename
    end

    resolvers.schemes.install('mkvi',handler,1) -- this will cache !

    utilities.sequencers.appendaction(resolvers.openers.helpers.textfileactions,"system","resolvers.macros.processmkvi")
 -- utilities.sequencers.disableaction(resolvers.openers.helpers.textfileactions,"resolvers.macros.processmkvi")

end

--~ print(macros.preprocessed([[\def\bla#bla{bla#{bla}}]]))
--~ print(macros.preprocessed([[\def\bla#bla{#{bla}bla}]]))
--~ print(macros.preprocessed([[\def\blä#{blá}{blà:#{blá}}]]))
--~ print(macros.preprocessed([[\def\blä#bla{blà:#bla}]]))
--~ print(macros.preprocessed([[\setvalue{xx}#bla{blà:#bla}]]))
--~ print(macros.preprocessed([[\def\foo#bar{\setvalue{xx#bar}{#bar}}]]))
--~ print(macros.preprocessed([[\def\bla#bla{bla:#{bla}}]]))
--~ print(macros.preprocessed([[\def\bla_bla#bla{bla:#bla}]]))
--~ print(macros.preprocessed([[\def\test#oeps{test:#oeps}]]))
--~ print(macros.preprocessed([[\def\test_oeps#oeps{test:#oeps}]]))
--~ print(macros.preprocessed([[\def\test#oeps{test:#{oeps}}]]))
--~ print(macros.preprocessed([[\def\test#{oeps:1}{test:#{oeps:1}}]]))
--~ print(macros.preprocessed([[\def\test#{oeps}{test:#oeps}]]))
--~ print(macros.preprocessed([[\def\test#{oeps}{test:#oeps \halign{##\cr #oeps\cr}]]))
--~ print(macros.preprocessed([[\def\test#{oeps}{test:#oeps \halign{##\cr #oeps\cr}}]]))
--~ print(macros.preprocessed([[% test
--~ \def\test#oeps{#oeps} % {test}
--~ % test
--~
--~ % test
--~ two
--~ %test]]))
--~ print(macros.preprocessed([[
--~ \def\scrn_button_make_normal#namespace#current#currentparameter#text%
--~   {\ctxlua{structures.references.injectcurrentset(nil,nil)}%
--~ %    \hbox attr \referenceattribute \lastreferenceattribute {\localframed[#namespace:#current]{#text}}}
--~    \hbox attr \referenceattribute \lastreferenceattribute {\directlocalframed[#namespace:#current]{#text}}}
--~ ]]))
--~
--~ print(macros.preprocessed([[
--~ \def\definefoo[#name]%
--~  {\setvalue{start#name}{\dostartfoo{#name}}}
--~ \def\dostartfoo#name%
--~   {\def\noexpand\next#content\expandafter\noexpand\csname stop#name\endcsname{#name : #content}%
--~   \next}
--~ \def\dostartfoo#name%
--~  {\normalexpanded{\def\noexpand\next#content\expandafter\noexpand\csname stop#name\endcsname}{#name : #content}%
--~   \next}
--~ ]]))
