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

-- another experiment
-- needs upgrading
-- needs checking
-- todo: multilingual

local format, byte = string.format, string.byte
local insert, remove, concat = table.insert, table.remove, table.concat
local unpack, type = unpack or table.unpack, type

local catcodenumbers = catcodes.numbers

local ctxcatcodes    = catcodenumbers.ctxcatcodes
local vrbcatcodes    = catcodenumbers.vrbcatcodes

local context        = context
local contextsprint  = context.sprint

local trace_define   = false  trackers.register("context.define", function(v) trace_define = v end)

interfaces           = interfaces or { }
local implement      = interfaces.implement
local estart         = interfaces.elements.start
local estop          = interfaces.elements.stop

if CONTEXTLMTXMODE > 0 then

    local scanners  = tokens.scanners
    local shortcuts = tokens.shortcuts

    local scanpeek  = scanners.peek
    local scankey   = scanners.key
    local scanvalue = scanners.value
    local scanskip  = scanners.skip

    local open      = byte('[')
    local close     = byte(']')
    local equal     = byte('=')
    local comma     = byte(',')

    function scanhash(scanners)
        if scanpeek() == open then
            local data = { }
            scanskip()
            while true do
                local c = scanpeek()
                if c == comma then
                    scanskip()
                elseif c == close then
                    scanskip()
                    break
                else
                    local key = scankey(equal)
                    if key then
                        if scanpeek() == equal then
                            scanskip()
                            if scanners then
                                local scanner = scanners[key]
                                if scanner then
                                    data[key] = scanner()
                                else
                                    data[key] = scanvalue(comma,close) or ""
                                end
                            else
                                data[key] = scanvalue(comma,close) or ""
                            end
                        else
                            break
                        end
                    else
                        break
                    end
                end
            end
            return data
        end
    end

    function scanarray()
        if scanpeek() == open then
            local data = { }
            local d = 0
            scanskip()
            while true do
                local c = scanpeek()
                if c == comma then
                    scanskip()
                elseif c == close then
                    scanskip()
                    break
                else
                    local v = scanvalue(comma,close) or ""
                    d = d + 1
                    data[d] = v
                end
            end
            return data
        end
    end

    shortcuts.scanhash  = scanhash
    shortcuts.scanarray = scanarray

    scanners.hash  = scanhash
    scanners.array = scanarray

    local function remap(arguments)
        -- backward compatibility
        if type(arguments) == "table" then
            for i=1,#arguments do
                local a = arguments[i]
                if type(a) == "table" then
                    local t = a[2]
                    arguments[i] = t == "list" and "array" or t
                end
            end
            return arguments
        end
    end

    function interfaces.definecommand(name,specification) -- name is optional
        if type(name) == "table" then
            specification = name
            name = specification.name
        end
        if name and specification then
            local environment = specification.environment
            local arguments   = remap(specification.arguments)
            if environment then
                local starter = specification.starter
                local stopper = specification.stopper
                if starter and stopper then
                    implement {
                        name      = estart .. name,
                        arguments = arguments,
                        public    = true,
                        protected = true,
                        actions   = starter,
                    }
                    implement {
                        name      = estop .. name,
                        public    = true,
                        protected = true,
                        actions   = stopper,
                    }
                else
                    -- message
                end
            end
            if not environment or environment == "both" then
                local macro = specification.macro
                if macro then
                    implement {
                        name      = name,
                        arguments = arguments,
                        public    = true,
                        protected = true,
                        actions   = macro,
                    }
                else
                    -- message
                end
            end
        else
            -- message
        end
    end


else

    _clmh_ = utilities.parsers.settings_to_hash
    _clma_ = utilities.parsers.settings_to_array

    local starters, stoppers, macros, stack = { }, { }, { }, { }

    local checkers = {
        [0] = "",
        "\\dosingleempty",
        "\\dodoubleempty",
        "\\dotripleempty",
        "\\doquadrupleempty",
        "\\doquintupleempty",
        "\\dosixtupleempty",
    }

    function _clmm_(name,...)
        macros[name](...)
    end

    function _clmb_(name,...)
        local sn = stack[name]
        insert(sn,{...})
        starters[name](...)
    end

    function _clme_(name)
        local sn = stack[name]
        local sv = remove(sn)
        if sv then
            stoppers[name](unpack(sv))
        else
            -- nesting error
        end
    end

    _clmn_ = tonumber

    local estart = interfaces.elements.start
    local estop  = interfaces.elements.stop

    -- this is a bit old definition ... needs to be modernized

    function interfaces.definecommand(name,specification) -- name is optional
        if type(name) == "table" then
            specification = name
            name = specification.name
        end
        if name and specification then
            local arguments = specification.arguments
            local na = (arguments and #arguments) or 0
            local environment = specification.environment
            if na == 0 then
                if environment then
                    contextsprint(ctxcatcodes,"\\setuvalue{",estart,name,"}{\\ctxlua{_clmb_('",name,"')}}")
                    contextsprint(ctxcatcodes,"\\setuvalue{",estop, name,"}{\\ctxlua{_clme_('",name,"')}}")
                end
                if not environment or environment == "both" then
                    contextsprint(ctxcatcodes,"\\setuvalue{",       name,"}{\\ctxlua{_clmm_('",name,"')}}")
                end
            else
                -- we could flush immediate but tracing is bad then
                stack[name] = { }
                local opt      = 0
                local done     = false
                local snippets = { } -- we can reuse it
                local mkivdo   = "\\mkivdo" .. name -- maybe clddo
                snippets[#snippets+1] = "\\def"
                snippets[#snippets+1] = mkivdo
                for i=1,na do
                    local a = arguments[i]
                    local variant = a[1]
                    if variant == "option" then
                        snippets[#snippets+1] = "[#"
                        snippets[#snippets+1] = i
                        snippets[#snippets+1] = "]"
                        if not done then
                            opt = opt + 1
                        end
                    else
                        done = true -- no more optional checking after this
                        snippets[#snippets+1] = "#"
                        snippets[#snippets+1] = i
                    end
                end
                if environment then
                    snippets[#snippets+1] = "{\\ctxlua{_clmb_('"
                    snippets[#snippets+1] = name
                    snippets[#snippets+1] = "'"
                else
                    snippets[#snippets+1] = "{\\ctxlua{_clmm_('"
                    snippets[#snippets+1] = name
                    snippets[#snippets+1] = "'"
                end
                for i=1,na do
                    local a = arguments[i]
                    local variant = a[2]
                    if variant == "list" then
                        snippets[#snippets+1] = ",_clma_([==[#"
                        snippets[#snippets+1] = i
                        snippets[#snippets+1] = "]==])"
                    elseif variant == "hash" then
                        snippets[#snippets+1] = ",_clmh_([==[#"
                        snippets[#snippets+1] = i
                        snippets[#snippets+1] = "]==])"
                    elseif variant == "number" then
                        snippets[#snippets+1] = ",_clmn_([==[#"
                        snippets[#snippets+1] = i
                        snippets[#snippets+1] = "]==])"
                    else
                        snippets[#snippets+1] = ",[==[#"
                        snippets[#snippets+1] = i
                        snippets[#snippets+1] = "]==]"
                    end
                end
                snippets[#snippets+1] = ")}}"
                contextsprint(ctxcatcodes,unpack(snippets))
                if environment then
                    -- needs checking
                    contextsprint(ctxcatcodes,"\\setuvalue{",estart,name,"}{",checkers[opt],mkivdo,"}")
                    contextsprint(ctxcatcodes,"\\setuvalue{",estop, name,"}{\\ctxlua{_clme_('",name,"')}}")
                end
                if not environment or environment == "both" then
                    contextsprint(ctxcatcodes,"\\setuvalue{",       name,"}{",checkers[opt],mkivdo,"}")
                end
            end
            if environment then
                starters[name] = specification.starter
                stoppers[name] = specification.stopper
            else
                macros[name] = specification.macro
            end
        end
    end

end

function interfaces.tolist(t)
    local r = { }
    for i=1,#t do
        r[i] = t[i]
    end
    local n = #r
    for k,v in table.sortedhash(t) do
        if type(k) ~= "number" then
            n = n + 1
            r[n] = k .. "=" .. v
        end
    end
    return concat(r,", ")
end

-- \startluacode
-- function test(opt_1, opt_2, arg_1)
--     context.startnarrower()
--     context("options 1: %s",interfaces.tolist(opt_1))
--     context.par()
--     context("options 2: %s",interfaces.tolist(opt_2))
--     context.par()
--     context("argument 1: %s",arg_1)
--     context.stopnarrower()
-- end
--
-- interfaces.definecommand {
--     name = "test",
--     arguments = {
--         { "option", "list" },
--         { "option", "hash" },
--         { "content", "string" },
--     },
--     macro = test,
-- }
-- \stopluacode
--
-- test: \test[1][a=3]{whatever}
--
-- \startluacode
-- local function startmore(opt_1)
--     context.startnarrower()
--     context("start more, options: %s",interfaces.tolist(opt_1))
--     context.startnarrower()
-- end
--
-- local function stopmore(opt_1)
--     context.stopnarrower()
--     context("stop more, options: %s",interfaces.tolist(opt_1))
--     context.stopnarrower()
-- end
--
-- interfaces.definecommand ( "more", {
--     environment = true,
--     arguments = {
--         { "option", "list" },
--     },
--     starter = startmore,
--     stopper = stopmore,
-- } )
-- \stopluacode
--
-- more: \startmore[1] one \startmore[2] two \stopmore one \stopmore
--
-- More modern (no need for option or content):
--
-- \startluacode
-- interfaces.definecommand {
--     name = "test",
--     arguments = {
--         "array", -- or list
--         "hash",
--         "string",
--         "number",
--     },
--     macro = test,
-- }
-- \stopluacode
--