--[[
LPEGLJ
lpprint.lua
Tree, code and debug print function (only for debuging)
Copyright (C) 2014 Rostislav Sacek.
based on LPeg v1.0 - PEG pattern matching for Lua
Lua.org & PUC-Rio  written by Roberto Ierusalimschy
http://www.inf.puc-rio.br/~roberto/lpeg/

** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
--]]

local ffi = require"ffi"
local band, rshift, lshift = bit.band, bit.rshift, bit.lshift

ffi.cdef[[
  int isprint ( int c );
]]

local RuleLR = 0x10000
local Ruleused = 0x20000

-- {======================================================
-- Printing patterns (for debugging)
-- =======================================================

local TChar = 0
local TSet = 1
local TAny = 2 -- standard PEG elements
local TTrue = 3
local TFalse = 4
local TRep = 5
local TSeq = 6
local TChoice = 7
local TNot = 8
local TAnd = 9
local TCall = 10
local TOpenCall = 11
local TRule = 12 -- sib1 is rule's pattern, sib2 is 'next' rule
local TGrammar = 13 -- sib1 is initial (and first) rule
local TBehind = 14 -- match behind
local TCapture = 15 -- regular capture
local TRunTime = 16 -- run-time capture

local IAny = 0 -- if no char, fail
local IChar = 1 -- if char != aux, fail
local ISet = 2 -- if char not in val, fail
local ITestAny = 3 -- in no char, jump to 'offset'
local ITestChar = 4 -- if char != aux, jump to 'offset'
local ITestSet = 5 -- if char not in val, jump to 'offset'
local ISpan = 6 -- read a span of chars in val
local IBehind = 7 -- walk back 'aux' characters (fail if not possible)
local IRet = 8 -- return from a rule
local IEnd = 9 -- end of pattern
local IChoice = 10 -- stack a choice; next fail will jump to 'offset'
local IJmp = 11 -- jump to 'offset'
local ICall = 12 -- call rule at 'offset'
local IOpenCall = 13 -- call rule number 'offset' (must be closed to a ICall)
local ICommit = 14 -- pop choice and jump to 'offset'
local IPartialCommit = 15 -- update top choice to current position and jump
local IBackCommit = 16 -- "fails" but jump to its own 'offset'
local IFailTwice = 17 -- pop one choice and then fail
local IFail = 18 -- go back to saved state on choice and jump to saved offset
local IGiveup = 19 -- internal use
local IFullCapture = 20 -- complete capture of last 'off' chars
local IOpenCapture = 21 -- start a capture
local ICloseCapture = 22
local ICloseRunTime = 23

local Cclose = 0
local Cposition = 1
local Cconst = 2
local Cbackref = 3
local Carg = 4
local Csimple = 5
local Ctable = 6
local Cfunction = 7
local Cquery = 8
local Cstring = 9
local Cnum = 10
local Csubst = 11
local Cfold = 12
local Cruntime = 13
local Cgroup = 14


-- number of siblings for each tree
local numsiblings = {
    [TRep] = 1,
    [TSeq] = 2,
    [TChoice] = 2,
    [TNot] = 1,
    [TAnd] = 1,
    [TRule] = 2,
    [TGrammar] = 1,
    [TBehind] = 1,
    [TCapture] = 1,
    [TRunTime] = 1,
}
local names = {
    [IAny] = "any",
    [IChar] = "char",
    [ISet] = "set",
    [ITestAny] = "testany",
    [ITestChar] = "testchar",
    [ITestSet] = "testset",
    [ISpan] = "span",
    [IBehind] = "behind",
    [IRet] = "ret",
    [IEnd] = "end",
    [IChoice] = "choice",
    [IJmp] = "jmp",
    [ICall] = "call",
    [IOpenCall] = "open_call",
    [ICommit] = "commit",
    [IPartialCommit] = "partial_commit",
    [IBackCommit] = "back_commit",
    [IFailTwice] = "failtwice",
    [IFail] = "fail",
    [IGiveup] = "giveup",
    [IFullCapture] = "fullcapture",
    [IOpenCapture] = "opencapture",
    [ICloseCapture] = "closecapture",
    [ICloseRunTime] = "closeruntime"
}

local function printcharset(st)
    io.write("[");
    local i = 0
    while i <= 255 do
        local first = i;
        while band(st[rshift(i, 5)], lshift(1, band(i, 31))) ~= 0 and i <= 255 do
            i = i + 1
        end
        if i - 1 == first then -- unary range?
            io.write(("(%02x)"):format(first))
        elseif i - 1 > first then -- non-empty range?
            io.write(("(%02x-%02x)"):format(first, i - 1))
        end
        i = i + 1
    end
    io.write("]")
end

local modes = {
    [Cclose] = "close",
    [Cposition] = "position",
    [Cconst] = "constant",
    [Cbackref] = "backref",
    [Carg] = "argument",
    [Csimple] = "simple",
    [Ctable] = "table",
    [Cfunction] = "function",
    [Cquery] = "query",
    [Cstring] = "string",
    [Cnum] = "num",
    [Csubst] = "substitution",
    [Cfold] = "fold",
    [Cruntime] = "runtime",
    [Cgroup] = "group"
}

local function printcapkind(kind)
    io.write(("%s"):format(modes[kind]))
end

local function printjmp(p, index)
    io.write(("-> %d"):format(index + p[index].offset))
end

local function printrulename(p, index, rulenames)
    if rulenames and rulenames[index + p[index].offset] then
        io.write(' ', rulenames[index + p[index].offset])
    end
end

local function printinst(p, index, valuetable, rulenames)
    local code = p[index].code
    if rulenames and rulenames[index] then
        io.write(rulenames[index], '\n')
    end
    io.write(("%04d: %s "):format(index, names[code]))
    if code == IChar then
        io.write(("'%s'"):format(string.char(p[index].val)))
    elseif code == ITestChar then
        io.write(("'%s'"):format(string.char(p[index].val)))
        printjmp(p, index)
        printrulename(p, index, rulenames)
    elseif code == IFullCapture then
        printcapkind(band(p[index].val, 0x0f));
        io.write((" (size = %d)  (idx = %s)"):format(band(rshift(p[index].val, 4), 0xF), tostring(valuetable[p[index].offset])))
    elseif code == IOpenCapture then
        printcapkind(band(p[index].val, 0x0f))
        io.write((" (idx = %s)"):format(tostring(valuetable[p[index].offset])))
    elseif code == ISet then
        printcharset(valuetable[p[index].val]);
    elseif code == ITestSet then
        printcharset(valuetable[p[index].val])
        printjmp(p, index);
        printrulename(p, index, rulenames)
    elseif code == ISpan then
        printcharset(valuetable[p[index].val]);
    elseif code == IOpenCall then
        io.write(("-> %d"):format(p[index].offset))
    elseif code == IBehind then
        io.write(("%d"):format(p[index].val))
    elseif code == IJmp or code == ICall or code == ICommit or code == IChoice or
            code == IPartialCommit or code == IBackCommit or code == ITestAny then
        printjmp(p, index);
        if (code == ICall or code == IJmp) and p[index].aux > 0 then
            io.write(' ', valuetable[p[index].aux])
        else
            printrulename(p, index, rulenames)
        end
    end
    io.write("\n")
end


local function printpatt(p, valuetable)
    local ruleNames = {}
    for i = 0, p.size - 1 do
        local code = p.p[i].code
        if (code == ICall or code == IJmp) and p.p[i].aux > 0 then
            local index = i + p.p[i].offset
            ruleNames[index] = valuetable[p.p[i].aux]
        end
    end
    for i = 0, p.size - 1 do
        printinst(p.p, i, valuetable, ruleNames)
    end
end


local function printcap(cap, index, valuetable)
    printcapkind(cap[index].kind)
    io.write((" (idx: %s - size: %d) -> %d\n"):format(valuetable[cap[index].idx], cap[index].siz, cap[index].s))
end


local function printcaplist(cap, limit, valuetable)
    io.write(">======\n")
    local index = 0
    while cap[index].s and index < limit do
        printcap(cap, index, valuetable)
        index = index + 1
    end
    io.write("=======\n")
end

-- ======================================================



-- {======================================================
-- Printing trees (for debugging)
-- =======================================================

local tagnames = {
    [TChar] = "char",
    [TSet] = "set",
    [TAny] = "any",
    [TTrue] = "true",
    [TFalse] = "false",
    [TRep] = "rep",
    [TSeq] = "seq",
    [TChoice] = "choice",
    [TNot] = "not",
    [TAnd] = "and",
    [TCall] = "call",
    [TOpenCall] = "opencall",
    [TRule] = "rule",
    [TGrammar] = "grammar",
    [TBehind] = "behind",
    [TCapture] = "capture",
    [TRunTime] = "run-time"
}


local function printtree(tree, ident, index, valuetable)
    for i = 1, ident do
        io.write(" ")
    end
    local tag = tree[index].tag
    io.write(("%s"):format(tagnames[tag]))
    if tag == TChar then
        local c = tree[index].val
        if ffi.C.isprint(c) then
            io.write((" '%c'\n"):format(c))
        else
            io.write((" (%02X)\n"):format(c))
        end
    elseif tag == TSet then
        printcharset(valuetable[tree[index].val]);
        io.write("\n")
    elseif tag == TOpenCall or tag == TCall then
        io.write((" key: %s\n"):format(tostring(valuetable[tree[index].val])))
    elseif tag == TBehind then
        io.write((" %d\n"):format(tree[index].val))
        printtree(tree, ident + 2, index + 1, valuetable);
    elseif tag == TCapture then
        io.write((" cap: %s   n: %s\n"):format(modes[bit.band(tree[index].cap, 0xffff)], valuetable[tree[index].val]))
        printtree(tree, ident + 2, index + 1, valuetable);
    elseif tag == TRule then
        local extra = bit.band(tree[index].cap, RuleLR) == RuleLR and ' left recursive' or ''
        extra = extra .. (bit.band(tree[index].cap, Ruleused) ~= Ruleused and ' not used' or '')
        io.write((" n: %d  key: %s%s\n"):format(bit.band(tree[index].cap, 0xffff) - 1, valuetable[tree[index].val], extra))
        printtree(tree, ident + 2, index + 1, valuetable);
        -- do not print next rule as a sibling
    elseif tag == TGrammar then
        local ruleindex = index + 1
        io.write((" %d\n"):format(tree[index].val)) -- number of rules
        for i = 1, tree[index].val do
            printtree(tree, ident + 2, ruleindex, valuetable);
            ruleindex = ruleindex + tree[ruleindex].ps
        end
        assert(tree[ruleindex].tag == TTrue); -- sentinel
    else
        local sibs = numsiblings[tree[index].tag] or 0
        io.write("\n")
        if sibs >= 1 then
            printtree(tree, ident + 2, index + 1, valuetable);
            if sibs >= 2 then
                printtree(tree, ident + 2, index + tree[index].ps, valuetable)
            end
        end
    end
end

-- }====================================================== */

return {
    printtree = printtree,
    printpatt = printpatt,
    printcaplist = printcaplist,
    printinst = printinst
}