if not modules then modules = { } end modules ['toks-scn'] = {
    version   = 1.001,
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- Writing this kind of code (and completing the newtoken code base) is fun. I did
-- so with the brilliant film music from The Girl with the Dragon Tattoo running in a
-- loop in the background (three cd's by Trent Reznor and Atticus Ross). An alien
-- feeling helps with alien code.

-- todo: more \let's at the tex end

local type, next, tostring, tonumber = type, next, tostring, tonumber

local formatters     = string.formatters
local concat         = table.concat

local scanners       = tokens.scanners
local tokenbits      = tokens.bits

local scanstring     = scanners.string
local scanargument   = scanners.argument
local scandelimited  = scanners.delimited  -- lmtx
local scanverbatim   = scanners.verbatim
local scantokenlist  = scanners.tokenlist
local scantoks       = scanners.toks
local scaninteger    = scanners.integer
local scancardinal   = scanners.cardinal
local scannumber     = scanners.number
local scankeyword    = scanners.keyword
local scankeywordcs  = scanners.keywordcs
local scanword       = scanners.word
local scanletters    = scanners.letters
local scankey        = scanners.key
local scancode       = scanners.code
local scanboolean    = scanners.boolean
local scandimen      = scanners.dimen
local scanglue       = scanners.glue
local scangluevalues = scanners.gluevalues
local scangluespec   = scanners.gluespec
local scancsname     = scanners.csname

local todimen        = number.todimen
local toboolean      = toboolean

local lpegmatch      = lpeg.match
local p_unquoted     = lpeg.Cs(lpeg.patterns.unquoted)

local trace_compile  = false  trackers.register("tokens.compile", function(v) trace_compile = v end)
local report_compile = logs.reporter("tokens","compile")
local report_scan    = logs.reporter("tokens","scan")

local open  = tokenbits.open
local close = tokenbits.close

local function scanopen()
    while true do
        local c = scancode(open)
        if c == 123 then
            return true
     -- elseif c ~= 32 then
        elseif not c then
            return
        end
    end
end

local function scanclose()
    while true do
        local c = scancode(close)
        if c == 125 then
            return true
     -- elseif c ~= 32 then
        elseif not c then
            return
        end
    end
end

scanners.scanopen  = scanopen
scanners.scanclose = scanclose

local function scanlist()
    local wrapped = scanopen()
    local list    = { }
    local size    = 0
    while true do
        local entry = scanstring()
        if entry then
            size = size + 1
            list[size] = entry
        else
            break
        end
    end
    if wrapped then
        scanclose()
    end
    return list
end

local function scanconditional()
    local kw = scanword()
    if kw == "true" then
        return true
    end
    if kw == "false" then
        return false
    end
    local c = scaninteger()
    if c then
        return c == 0 -- with a conditional 0=true
    end
    return nil
end

local function scantable(t,data)
    if not data then
        data = { }
    end
    if t then
        local wrapped = scanopen()
        while true do
            local key = scanword(true)
            if key then
                local get = t[key]
                if get then
                    data[key] = get()
                else
                    -- catch all we can get
                end
            else
                break
            end
        end
        if wrapped then
            scanclose()
        end
    end
    return data
end

function tokens.constant(s)
    if type(s) == "string" then
        return "'" .. s .. "'"
    else
        return s
    end
end

scanners.list        = scanlist
scanners.table       = scantable
scanners.conditional = scanconditional

function scanners.whd()
    local width, height, depth
    while true do
        if scankeyword("width") then
            width = scandimen()
        elseif scankeyword("height") then
            height = scandimen()
        elseif scankeyword("depth") then
            depth = scandimen()
        else
            break
        end
    end
    if width or height or depth then
        return width or 0, height or 0, depth or 0
    else
        -- we inherit
    end
end

-- begin lmtx

local l = utf.byte("[")
local r = utf.byte("]")

local function scanbracketed()
    local s = scandelimited(l, r)
    if s then
        return s
    else
        local readstate = status.getreadstate()
        report_scan("missing argument in line %i of %a", readstate.linenumber, readstate.filename)
        return ""
    end
end

local function scanoptional()
    return scandelimited(l, r) or ""
end

local function scanbracketedasis()
    return scandelimited(l, r, false)
end

local function scanargumentasis()
    return scanargument(false)
end

scanners.bracketed     = scanbracketed
scanners.optional      = scanoptional
scanners.bracketedasis = scanbracketedasis
scanners.argumentasis  = scanargumentasis

-- end lmtx

local shortcuts = {
    tokens            = tokens,
    bits              = tokenbits,
    open              = open,
    close             = close,
    scanners          = scanners,
    scanstring        = scanstring,
    scanargument      = scanargument,
    scanverbatim      = scanverbatim,
    scantokenlist     = scantokenlist,
    scantoks          = scantoks,
    scaninteger       = scaninteger,
    scancardinal      = scancardinal,
    scannumber        = scannumber,
    scantable         = scantable, -- not directly useable
    scankeyword       = scankeyword,
    scankeywordcs     = scankeywordcs,
    scanword          = scanword,
    scanletters       = scanletters,
 -- scankey           = scankey,
    scancode          = scancode,
    scanboolean       = scanboolean,
    scanglue          = scanglue, -- list
    scangluespec      = scangluespec,
    scangluevalues    = scangluevalues,
    scandimen         = scandimen,
    scandimension     = scandimen,
    scanbox           = scanners.box,
    scanhbox          = scanners.hbox,
    scanvbox          = scanners.vbox,
    scanvtop          = scanners.vtop,
    scanconditional   = scanconditional,
    scanopen          = scanopen,
    scanclose         = scanclose,
    scanlist          = scanlist,
    scancsname        = scancsname,
    todimen           = todimen,
    tonumber          = tonumber,
    tostring          = tostring,
    toboolean         = toboolean,
    inspect           = inspect,
    report            = report_scan,
    -- lmtx
    scandelimited     = scandelimited, -- not directly useable
    scanbracketed     = scanbracketed,
    scanoptional      = scanoptional,
    scanbracketedasis = scanbracketedasis,
    scanargumentasis  = scanargumentasis,
}

tokens.shortcuts = shortcuts

local load = load
local dump = string.dump

local function loadstripped(code)
     return load(code,nil,nil,shortcuts)
  -- return load(dump(load(code),true),nil,nil,shortcuts)
end

tokens.converters = {
    tonumber  = "tonumber",
    tostring  = "tostring",
    toboolean = "toboolean",
    todimen   = "todimen",
    toglue    = "todimen",
}

-- We could just pickup a keyword but then we really need to make sure that no number
-- follows it when that is the assignment and adding an optional = defeats the gain
-- in speed. Currently we have sources with no spaces (\startcontextdefinitioncode
-- ...) so it fails there.
--
-- Another drawback is that we then need to use { } instead of ending with \relax (as
-- we can do now) but that is no big deal. It's just that I then need to check the TeX
-- end. More pain than gain and a bit risky too. Using scanletters works better, but
-- the gain is only some 10 percent but if we don't have keywords with numbers it might
-- make sense in the end, some day.

local f_if       = formatters[    "  if scankeywordcs('%s') then data['%s'] = scan%s()"]
local f_elseif   = formatters["  elseif scankeywordcs('%s') then data['%s'] = scan%s()"]

----- f_if_x     = formatters[    "  if not data['%s'] and scankeywordcs('%s') then data['%s'] = scan%s()"]
----- f_elseif_x = formatters["  elseif not data['%s'] and scankeywordcs('%s') then data['%s'] = scan%s()"]

local f_local    = formatters["local scan%s = scanners.%s"]
local f_scan     = formatters["scan%s()"]
local f_shortcut = formatters["local %s = scanners.converters.%s"]

local f_if_c     = formatters[    "  if scankeywordcs('%s') then data['%s'] = %s(scan%s())"]
local f_elseif_c = formatters["  elseif scankeywordcs('%s') then data['%s'] = %s(scan%s())"]
local f_scan_c   = formatters["%s(scan%s())"]

-- see above

local f_any      = formatters["  else local key = scanword(true) if key then data[key] = scan%s() else break end end"]
local f_any_c    = formatters["  else local key = scanword(true) if key then data[key] = %s(scan%s()) else break end end"]
local s_done     = "  else break end"

local f_any_all  = formatters["  local key = scanword(true) if key then data[key] = scan%s() else break end"]
local f_any_all_c= formatters["  local key = scanword(true) if key then data[key] = %s(scan%s()) else break end"]

local f_table    = formatters["%\nt\nreturn function()\n  local data = { }\n%s\n  return %s\nend\n"]
local f_sequence = formatters["%\nt\n%\nt\n%\nt\nreturn function()\n    return %s\nend\n"]
local f_singular = formatters["%\nt\n%\nt\n\nreturn function(%s)\n    return %s\nend\n"]
local f_simple   = formatters["%\nt\nreturn function()\n    return %s\nend\n"]
local f_string   = formatters["%q"]
local f_action_f = formatters["action%s(%s)"]
local f_action_s = formatters["local action%s = tokens._action[%s]"]
local f_nested   = formatters["local function scan%s()\n  local data = { }\n%s\n  return data\nend\n"]

local f_check = formatters[ [[
  local wrapped = scanopen()
  while true do
    ]] .. "%\nt\n" .. [[
    %s
  end
  if wrapped then
    scanclose()
  end
]] ]

-- using these shortcuts saves temporary small tables (okay, it looks uglier)

local presets = {
    ["1 string" ] = { "string" },
    ["2 strings"] = { "string", "string" },
    ["3 strings"] = { "string", "string", "string" },
    ["4 strings"] = { "string", "string", "string", "string" },
    ["5 strings"] = { "string", "string", "string", "string", "string" },
    ["6 strings"] = { "string", "string", "string", "string", "string", "string" },
    ["7 strings"] = { "string", "string", "string", "string", "string", "string", "string" },
    ["8 strings"] = { "string", "string", "string", "string", "string", "string", "string", "string" },

    ["1 argument" ] = { "argument" },
    ["2 arguments"] = { "argument", "argument" },
    ["3 arguments"] = { "argument", "argument", "argument" },
    ["4 arguments"] = { "argument", "argument", "argument", "argument" },
}

tokens.presets = presets

function tokens.compile(specification)
    local f = { }
    local n = 0
    local c = { }
    local t = specification.arguments or specification
    local a = specification.actions or nil
    if type(a) == "function" then
        a = { a }
    end
    local code
    local args
    local function compile(t,nested)
        local done = s_done
        local r = { }
        local m = 0
        for i=1,#t do
            local ti = t[i]
            if ti == "*" and i == 1 then
                done = f_any_all("string")
            else
                local t1 = ti[1]
                local t2 = ti[2] or "string"
                if type(t2) == "table" then
                    n = n + 1
                    f[n] = compile(t2,n)
                    t2 = n
                end
                local t3 = ti[3]
                if type(t3) == "function" then
                    -- todo: also create shortcut
                elseif t3 then
                    c[t3] = f_shortcut(t3,t3)
                    if t1 == "*" then
                        if i == 1 then
                            done = f_any_all_c(t3,t2)
                            break
                        else
                            done = f_any_c(t3,t2)
                        end
                    else
                        m = m + 1
                        r[m] = (m > 1 and f_elseif_c or f_if_c)(t1,t1,t3,t2)
                    end
                else
                    if t1 == "*" then
                        if i == 1 then
                            done = f_any_all(t2)
                            break
                        else
                            done = f_any(t2)
                        end
                    else
                        m = m + 1
                        r[m] = (m > 1 and f_elseif   or f_if  )(t1,t1,t2)
                     -- r[m] = (m > 1 and f_elseif_x or f_if_x)(t1,t1,t1,t2)
                    end
                end
            end
        end
        local c = f_check(r,done)
        if nested then
            return f_nested(nested,c)
        else
            return c
        end
    end
    local p = t and presets[t] -- already done in implement
    if p then
        t = p
    end
    local tt = type(t)
    if tt == "string" then
        if a then
            local s = lpegmatch(p_unquoted,t)
            if s and t ~= s then
                code = t
            else
                code = f_scan(t)
            end
            tokens._action = a
            for i=1,#a do
                code = f_action_f(i,code)
                n    = n + 1
                f[n] = f_action_s(i,i)
            end
            code = f_simple(f,code)
        else
            return scanners[t]
        end
    elseif tt ~= "table" then
        return
    elseif #t == 1 then
        local ti = t[1]
        if type(ti) == "table" then
            ti = compile(ti)
            code = "data"
            if a then
                tokens._action = a
                for i=1,#a do
                    code = f_action_f(i,code)
                    n    = n + 1
                    f[n] = f_action_s(i,i)
                end
            end
            code = f_table(f,ti,code)
        elseif a then
            code = f_scan(ti)
            tokens._action = a
            for i=1,#a do
                code = f_action_f(i,code)
                n    = n + 1
                f[n] = f_action_s(i,i)
            end
            code = f_simple(f,code)
        else
            return scanners[ti]
        end
    elseif #t == 0 then
        if specification.usage == "value" then
            code = "b"
            args = "_,b"
        else
            code = ""
            args = ""
        end
        if a then
            tokens._action = a
            for i=1,#a do
                code = f_action_f(i,code)
                n    = n + 1
                f[n] = f_action_s(i,i)
            end
        end
        code = f_singular(c,f,args,code)
    else
        local r = { }
        local p = { }
        local m = 0
        for i=1,#t do
            local ti = t[i]
            local tt = type(ti)
            if tt == "table" then
                if ti[1] == "_constant_" then
                    local v = ti[2]
                    if type(v) == "string" then
                        r[i] = f_string(v)
                    else
                        r[i] = tostring(v)
                    end
                else
                    m = m + 1
                    p[m] = compile(ti,100+m)
                    r[i] = f_scan(100+m)
                end
            elseif tt == "number" then
                r[i] = tostring(ti)
            elseif tt == "boolean" then
                r[i] = tostring(ti)
            else
                local s = lpegmatch(p_unquoted,ti)
                if s and ti ~= s then
                    r[i] = ti -- a string, given as "'foo'" or '"foo"'
                elseif scanners[ti] then
                    r[i] = f_scan(ti)
                else
                    report_compile("unknown scanner %a",ti)
                    r[i] = ti
                end
            end
        end
        code = concat(r,",")
        if a then
            tokens._action = a
            for i=1,#a do
                code = f_action_f(i,code)
                n    = n + 1
                f[n] = f_action_s(i,i)
            end
        end
        code = f_sequence(c,f,p,code)
    end
    if not code then
        return
    end
    if trace_compile then
        report_compile("code: %s",code)
    end
    local code, message = loadstripped(code)
    if code then
        code = code() -- sets action
    else
        report_compile("error in code: %s",code)
        report_compile("error message: %s",message)
    end
    if a then
        tokens._action = nil
    end
    if code then
        return code
    end
end

-- local fetch = tokens.compile {
--     "string",
--     "string",
--     {
--         { "data",    "string" },
--         { "tab",     "string" },
--         { "method",  "string" },
--         { "foo", {
--             { "method", "integer" },
--             { "compact", "number" },
--             { "nature" },
--             { "*" }, -- any key
--         } },
--         { "compact", "string", "tonumber" },
--         { "nature",  "boolean" },
--         { "escape",  "string" },
--         { "escape"  },
--     },
--     "boolean",
-- }
--
-- os.exit()
