commit bc410afa0c38432b519a75cc08c17e2d0921b39c Author: Fierelier Date: Sat Aug 10 00:35:03 2024 +0000 Initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6a6f3d8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024 + +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. diff --git a/cmd/autorun.lua b/cmd/autorun.lua new file mode 100644 index 0000000..29a3d60 --- /dev/null +++ b/cmd/autorun.lua @@ -0,0 +1,4 @@ +local function cmd() +end + +return cmd diff --git a/cmd/entry_add.lua b/cmd/entry_add.lua new file mode 100644 index 0000000..3d3dbd9 --- /dev/null +++ b/cmd/entry_add.lua @@ -0,0 +1,36 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + local entry = {} + entry.tag = "entry" + entry.attrs = {} + local found = true + while found do + found = false + entry.attrs.id = env.makeUUID() + for i,e in pairs(entries.children) do + if e.attrs.id == entry.attrs.id then + found = true + end + end + end + if args[2] == nil then + print("No name given") + os.exit(1) + end + entry.attrs.name = args[2] + entry.attrs.category = "None" + entry.attrs.date = env.stringDate(os.date("*t",os.time())) + entry.children = {} + entry.children[1] = {} + entry.children[1].tag = "history" + entry.children[1].attrs = {} + entry.children[1].children = {} + entry.children[1].parent = entry + + entry.parent = entries + table.insert(entries.children,entry) + env.changed = true + print(entry.attrs.id) +end + +return cmd diff --git a/cmd/entry_backup.lua b/cmd/entry_backup.lua new file mode 100644 index 0000000..c6fd637 --- /dev/null +++ b/cmd/entry_backup.lua @@ -0,0 +1,35 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,entry in pairs(entries.children) do + if entry.attrs.id == args[2] then + local history = env.xmlFindTags(entry,"history")[1] + local hentry = {} + hentry.tag = "entry" + hentry.attrs = {} + for i,v in pairs(entry.attrs) do + hentry.attrs[i] = v + end + hentry.children = {} + hentry.parent = history + + for i,field in pairs(env.xmlFindTags(entry,"field")) do + local hfield = {} + hfield.tag = "field" + hfield.attrs = {} + hfield.children = {} + hfield.parent = hfields + for i,v in pairs(field.attrs) do + hfield.attrs[i] = v + end + hfield.children[1] = {} + hfield.children[1].text = field.children[1].text + table.insert(hentry.children,hfield) + end + + table.insert(history.children,hentry) + env.changed = true + end + end +end + +return cmd diff --git a/cmd/entry_list.lua b/cmd/entry_list.lua new file mode 100644 index 0000000..80c2f17 --- /dev/null +++ b/cmd/entry_list.lua @@ -0,0 +1,11 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + entries = env.xmlFindTags(entries,"entry") + for i,entry in pairs(entries) do + if (entry.attrs.removed ~= "true") then + print(entry.attrs.id .. " " ..entry.attrs.date.. " " ..entry.attrs.name) + end + end +end + +return cmd diff --git a/cmd/entry_remove.lua b/cmd/entry_remove.lua new file mode 100644 index 0000000..07df072 --- /dev/null +++ b/cmd/entry_remove.lua @@ -0,0 +1,11 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,entry in pairs(entries.children) do + if entry.attrs.id == args[2] then + entry.attrs.removed = "true" + env.changed = true + end + end +end + +return cmd diff --git a/cmd/entry_restore.lua b/cmd/entry_restore.lua new file mode 100644 index 0000000..029711a --- /dev/null +++ b/cmd/entry_restore.lua @@ -0,0 +1,11 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,entry in pairs(entries.children) do + if entry.attrs.id == args[2] then + entry.attrs.removed = nil + env.changed = true + end + end +end + +return cmd diff --git a/cmd/exit.lua b/cmd/exit.lua new file mode 100644 index 0000000..a431c4a --- /dev/null +++ b/cmd/exit.lua @@ -0,0 +1,4 @@ +local function cmd() + os.exit(0) +end +return cmd diff --git a/cmd/field_get.lua b/cmd/field_get.lua new file mode 100644 index 0000000..39cb2b4 --- /dev/null +++ b/cmd/field_get.lua @@ -0,0 +1,15 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,v in pairs(entries.children) do + if v.attrs.id == args[2] then + local fields = env.xmlFindTags(v,"field") + for i,v in pairs(fields) do + if v.attrs.name == args[3] then + print(v.children[1].text) + end + end + end + end +end + +return cmd diff --git a/cmd/field_get_type.lua b/cmd/field_get_type.lua new file mode 100644 index 0000000..aebc4f4 --- /dev/null +++ b/cmd/field_get_type.lua @@ -0,0 +1,15 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,v in pairs(entries.children) do + if v.attrs.id == args[2] then + local fields = env.xmlFindTags(v,"field") + for i,v in pairs(fields) do + if v.attrs.name == args[3] then + print(v.attrs.type) + end + end + end + end +end + +return cmd diff --git a/cmd/field_list.lua b/cmd/field_list.lua new file mode 100644 index 0000000..7d716ea --- /dev/null +++ b/cmd/field_list.lua @@ -0,0 +1,13 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,v in pairs(entries.children) do + if v.attrs.id == args[2] and not v.attrs.deleted then + local fields = env.xmlFindTags(v,"field") + for i,v in pairs(fields) do + print(v.attrs.name) + end + end + end +end + +return cmd diff --git a/cmd/field_remove.lua b/cmd/field_remove.lua new file mode 100644 index 0000000..71bda90 --- /dev/null +++ b/cmd/field_remove.lua @@ -0,0 +1,18 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,entry in pairs(entries.children) do + if entry.attrs.id == args[2] then + for i,field in pairs(entry.children) do + if field.tag == "field" then + if field.attrs.name == args[3] then + table.remove(entry.children,i) + entry.attrs.date = env.stringDate(os.date("*t",os.time())) + env.changed = true + end + end + end + end + end +end + +return cmd diff --git a/cmd/field_set.lua b/cmd/field_set.lua new file mode 100644 index 0000000..554435a --- /dev/null +++ b/cmd/field_set.lua @@ -0,0 +1,36 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,entry in pairs(entries.children) do + if entry.attrs.id == args[2] then + local fields = env.xmlFindTags(entry,"field") + local field = false + for i,v in pairs(fields) do + if v.attrs.name == args[3] then + field = v + end + end + + if field == false then + field = {} + field.tag = "field" + field.attrs = {} + field.attrs.type = "text" + field.attrs.name = args[3] + if field.attrs.name == "password" then + field.attrs.type = "text-secret" + end + field.parent = fields + field.children = {} + field.children[1] = {} + table.insert(entry.children,field) + end + + if field.children[1] == nil then field.children[1] = {} end + field.children[1].text = args[4] + entry.attrs.date = env.stringDate(os.date("*t",os.time())) + env.changed = true + end + end +end + +return cmd diff --git a/cmd/field_set_type.lua b/cmd/field_set_type.lua new file mode 100644 index 0000000..f5015a4 --- /dev/null +++ b/cmd/field_set_type.lua @@ -0,0 +1,17 @@ +local function cmd(args) + local entries = env.xmlFindTags(env.db,"entries")[1] + for i,entry in pairs(entries.children) do + if entry.attrs.id == args[2] then + local fields = env.xmlFindTags(entry,"field") + for i,v in pairs(fields) do + if v.attrs.name == args[3] then + v.attrs.type = args[4] + entry.attrs.date = env.stringDate(os.date("*t",os.time())) + env.changed = true + end + end + end + end +end + +return cmd diff --git a/cmd/open.lua b/cmd/open.lua new file mode 100644 index 0000000..fe2a955 --- /dev/null +++ b/cmd/open.lua @@ -0,0 +1,11 @@ +local function cmd(args) + env.db = {} + collectgarbage("collect") + local parseFile = require('xmllpegparser').parseFile + env.file = args[2] + local doc,err = parseFile(env.file) + env.db = doc + env.changed = false +end + +return cmd diff --git a/cmd/save.lua b/cmd/save.lua new file mode 100644 index 0000000..f24f627 --- /dev/null +++ b/cmd/save.lua @@ -0,0 +1,9 @@ +local function cmd(arg) + local toString = require('xmllpegparser').tostring + local s = toString(env.db,"\t") + local fh = io.open(env.file,"w") + fh:write(s) + fh:close() + env.changed = false +end +return cmd diff --git a/lib/lulpeg.lua b/lib/lulpeg.lua new file mode 100644 index 0000000..daaf499 --- /dev/null +++ b/lib/lulpeg.lua @@ -0,0 +1,2976 @@ +-- LuLPeg, a pure Lua port of LPeg, Roberto Ierusalimschy's +-- Parsing Expression Grammars library. +-- +-- Copyright (C) Pierre-Yves Gerardy. +-- Released under the Romantic WTF Public License (cf. the LICENSE +-- file or the end of this file, whichever is present). +-- +-- See http://www.inf.puc-rio.br/~roberto/lpeg/ for the original. +-- +-- The re.lua module and the test suite (tests/lpeg.*.*.tests.lua) +-- are part of the original LPeg distribution. +local _ENV, loaded, packages, release, require_ + = _ENV or _G, {}, {}, true, require + +local function require(...) + local lib = ... + + -- is it a private file? + if loaded[lib] then + return loaded[lib] + elseif packages[lib] then + loaded[lib] = packages[lib](lib) + return loaded[lib] + else + return require_(lib) + end +end + +--============================================================================= +do local _ENV = _ENV +packages['API'] = function (...) + +local assert, error, ipairs, pairs, pcall, print + , require, select, tonumber, tostring, type + = assert, error, ipairs, pairs, pcall, print + , require, select, tonumber, tostring, type +local t, u = require"table", require"util" +local _ENV = u.noglobals() --------------------------------------------------- +local t_concat = t.concat +local checkstring, copy, fold, load, map_fold, map_foldr, setify, t_pack, t_unpack + = u.checkstring, u.copy, u.fold, u.load, u.map_fold, u.map_foldr, u.setify, u.pack, u.unpack +local +function charset_error(index, charset) + error("Character at position ".. index + 1 + .." is not a valid "..charset.." one.", + 2) +end +return function(Builder, LL) -- module wrapper ------------------------------- +local cs = Builder.charset +local constructors, LL_ispattern + = Builder.constructors, LL.ispattern +local truept, falsept, Cppt + = constructors.constant.truept + , constructors.constant.falsept + , constructors.constant.Cppt +local split_int, validate + = cs.split_int, cs.validate +local Range, Set, S_union, S_tostring + = Builder.Range, Builder.set.new + , Builder.set.union, Builder.set.tostring +local factorize_choice, factorize_lookahead, factorize_sequence, factorize_unm +local +function makechar(c) + return constructors.aux("char", c) +end +local +function LL_P (...) + local v, n = (...), select('#', ...) + if n == 0 then error"bad argument #1 to 'P' (value expected)" end + local typ = type(v) + if LL_ispattern(v) then + return v + elseif typ == "function" then + return + LL.Cmt("", v) + elseif typ == "string" then + local success, index = validate(v) + if not success then + charset_error(index, cs.name) + end + if v == "" then return truept end + return + map_foldr(split_int(v), makechar, Builder.sequence) + elseif typ == "table" then + local g = copy(v) + if g[1] == nil then error("grammar has no initial rule") end + if not LL_ispattern(g[1]) then g[1] = LL.V(g[1]) end + return + constructors.none("grammar", g) + elseif typ == "boolean" then + return v and truept or falsept + elseif typ == "number" then + if v == 0 then + return truept + elseif v > 0 then + return + constructors.aux("any", v) + else + return + - constructors.aux("any", -v) + end + else + error("bad argument #1 to 'P' (lpeg-pattern expected, got "..typ..")") + end +end +LL.P = LL_P +local +function LL_S (set) + if set == "" then + return + falsept + else + local success + set = checkstring(set, "S") + return + constructors.aux("set", Set(split_int(set)), set) + end +end +LL.S = LL_S +local +function LL_R (...) + if select('#', ...) == 0 then + return LL_P(false) + else + local range = Range(1,0)--Set("") + for _, r in ipairs{...} do + r = checkstring(r, "R") + assert(#r == 2, "bad argument #1 to 'R' (range must have two characters)") + range = S_union ( range, Range(t_unpack(split_int(r))) ) + end + return + constructors.aux("set", range) + end +end +LL.R = LL_R +local +function LL_V (name) + assert(name ~= nil) + return + constructors.aux("ref", name) +end +LL.V = LL_V +do + local one = setify{"set", "range", "one", "char"} + local zero = setify{"true", "false", "lookahead", "unm"} + local forbidden = setify{ + "Carg", "Cb", "C", "Cf", + "Cg", "Cs", "Ct", "/zero", + "Clb", "Cmt", "Cc", "Cp", + "div_string", "div_number", "div_table", "div_function", + "at least", "at most", "behind" + } + local function fixedlen(pt, gram, cycle) + local typ = pt.pkind + if forbidden[typ] then return false + elseif one[typ] then return 1 + elseif zero[typ] then return 0 + elseif typ == "string" then return #pt.as_is + elseif typ == "any" then return pt.aux + elseif typ == "choice" then + local l1, l2 = fixedlen(pt[1], gram, cycle), fixedlen(pt[2], gram, cycle) + return (l1 == l2) and l1 + elseif typ == "sequence" then + local l1, l2 = fixedlen(pt[1], gram, cycle), fixedlen(pt[2], gram, cycle) + return l1 and l2 and l1 + l2 + elseif typ == "grammar" then + if pt.aux[1].pkind == "ref" then + return fixedlen(pt.aux[pt.aux[1].aux], pt.aux, {}) + else + return fixedlen(pt.aux[1], pt.aux, {}) + end + elseif typ == "ref" then + if cycle[pt] then return false end + cycle[pt] = true + return fixedlen(gram[pt.aux], gram, cycle) + else + print(typ,"is not handled by fixedlen()") + end + end + function LL.B (pt) + pt = LL_P(pt) + local len = fixedlen(pt) + assert(len, "A 'behind' pattern takes a fixed length pattern as argument.") + if len >= 260 then error("Subpattern too long in 'behind' pattern constructor.") end + return + constructors.both("behind", pt, len) + end +end +local function nameify(a, b) + return ('%s:%s'):format(a.id, b.id) +end +local +function choice (a, b) + local name = nameify(a, b) + local ch = Builder.ptcache.choice[name] + if not ch then + ch = factorize_choice(a, b) or constructors.binary("choice", a, b) + Builder.ptcache.choice[name] = ch + end + return ch +end +function LL.__add (a, b) + return + choice(LL_P(a), LL_P(b)) +end +local +function sequence (a, b) + local name = nameify(a, b) + local seq = Builder.ptcache.sequence[name] + if not seq then + seq = factorize_sequence(a, b) or constructors.binary("sequence", a, b) + Builder.ptcache.sequence[name] = seq + end + return seq +end +Builder.sequence = sequence +function LL.__mul (a, b) + return + sequence(LL_P(a), LL_P(b)) +end +local +function LL_lookahead (pt) + if pt == truept + or pt == falsept + or pt.pkind == "unm" + or pt.pkind == "lookahead" + then + return pt + end + return + constructors.subpt("lookahead", pt) +end +LL.__len = LL_lookahead +LL.L = LL_lookahead +local +function LL_unm(pt) + return + factorize_unm(pt) + or constructors.subpt("unm", pt) +end +LL.__unm = LL_unm +local +function LL_sub (a, b) + a, b = LL_P(a), LL_P(b) + return LL_unm(b) * a +end +LL.__sub = LL_sub +local +function LL_repeat (pt, n) + local success + success, n = pcall(tonumber, n) + assert(success and type(n) == "number", + "Invalid type encountered at right side of '^'.") + return constructors.both(( n < 0 and "at most" or "at least" ), pt, n) +end +LL.__pow = LL_repeat +for _, cap in pairs{"C", "Cs", "Ct"} do + LL[cap] = function(pt) + pt = LL_P(pt) + return + constructors.subpt(cap, pt) + end +end +LL["Cb"] = function(aux) + return + constructors.aux("Cb", aux) +end +LL["Carg"] = function(aux) + assert(type(aux)=="number", "Number expected as parameter to Carg capture.") + assert( 0 < aux and aux <= 200, "Argument out of bounds in Carg capture.") + return + constructors.aux("Carg", aux) +end +local +function LL_Cp () + return Cppt +end +LL.Cp = LL_Cp +local +function LL_Cc (...) + return + constructors.none("Cc", t_pack(...)) +end +LL.Cc = LL_Cc +for _, cap in pairs{"Cf", "Cmt"} do + local msg = "Function expected in "..cap.." capture" + LL[cap] = function(pt, aux) + assert(type(aux) == "function", msg) + pt = LL_P(pt) + return + constructors.both(cap, pt, aux) + end +end +local +function LL_Cg (pt, tag) + pt = LL_P(pt) + if tag ~= nil then + return + constructors.both("Clb", pt, tag) + else + return + constructors.subpt("Cg", pt) + end +end +LL.Cg = LL_Cg +local valid_slash_type = setify{"string", "number", "table", "function"} +local +function LL_slash (pt, aux) + if LL_ispattern(aux) then + error"The right side of a '/' capture cannot be a pattern." + elseif not valid_slash_type[type(aux)] then + error("The right side of a '/' capture must be of type " + .."string, number, table or function.") + end + local name + if aux == 0 then + name = "/zero" + else + name = "div_"..type(aux) + end + return + constructors.both(name, pt, aux) +end +LL.__div = LL_slash +if Builder.proxymt then + for k, v in pairs(LL) do + if k:match"^__" then + Builder.proxymt[k] = v + end + end +else + LL.__index = LL +end +local factorizer + = Builder.factorizer(Builder, LL) +factorize_choice, factorize_lookahead, factorize_sequence, factorize_unm = +factorizer.choice, factorizer.lookahead, factorizer.sequence, factorizer.unm +end -- module wrapper -------------------------------------------------------- + +end +end +--============================================================================= +do local _ENV = _ENV +packages['analyzer'] = function (...) + +local u = require"util" +local nop, weakkey = u.nop, u.weakkey +local hasVcache, hasCmtcache , lengthcache + = weakkey{}, weakkey{}, weakkey{} +return { + hasV = nop, + hasCmt = nop, + length = nop, + hasCapture = nop +} + +end +end +--============================================================================= +do local _ENV = _ENV +packages['charsets'] = function (...) + +local s, t, u = require"string", require"table", require"util" +local _ENV = u.noglobals() ---------------------------------------------------- +local copy = u.copy +local s_char, s_sub, s_byte, t_concat, t_insert + = s.char, s.sub, s.byte, t.concat, t.insert +local +function utf8_offset (byte) + if byte < 128 then return 0, byte + elseif byte < 192 then + error("Byte values between 0x80 to 0xBF cannot start a multibyte sequence") + elseif byte < 224 then return 1, byte - 192 + elseif byte < 240 then return 2, byte - 224 + elseif byte < 248 then return 3, byte - 240 + elseif byte < 252 then return 4, byte - 248 + elseif byte < 254 then return 5, byte - 252 + else + error("Byte values between 0xFE and OxFF cannot start a multibyte sequence") + end +end +local +function utf8_validate (subject, start, finish) + start = start or 1 + finish = finish or #subject + local offset, char + = 0 + for i = start,finish do + local b = s_byte(subject,i) + if offset == 0 then + char = i + success, offset = pcall(utf8_offset, b) + if not success then return false, char - 1 end + else + if not (127 < b and b < 192) then + return false, char - 1 + end + offset = offset -1 + end + end + if offset ~= 0 then return nil, char - 1 end -- Incomplete input. + return true, finish +end +local +function utf8_next_int (subject, i) + i = i and i+1 or 1 + if i > #subject then return end + local c = s_byte(subject, i) + local offset, val = utf8_offset(c) + for i = i+1, i+offset do + c = s_byte(subject, i) + val = val * 64 + (c-128) + end + return i + offset, i, val +end +local +function utf8_next_char (subject, i) + i = i and i+1 or 1 + if i > #subject then return end + local offset = utf8_offset(s_byte(subject,i)) + return i + offset, i, s_sub(subject, i, i + offset) +end +local +function utf8_split_int (subject) + local chars = {} + for _, _, c in utf8_next_int, subject do + t_insert(chars,c) + end + return chars +end +local +function utf8_split_char (subject) + local chars = {} + for _, _, c in utf8_next_char, subject do + t_insert(chars,c) + end + return chars +end +local +function utf8_get_int(subject, i) + if i > #subject then return end + local c = s_byte(subject, i) + local offset, val = utf8_offset(c) + for i = i+1, i+offset do + c = s_byte(subject, i) + val = val * 64 + ( c - 128 ) + end + return val, i + offset + 1 +end +local +function split_generator (get) + if not get then return end + return function(subject) + local res = {} + local o, i = true + while o do + o,i = get(subject, i) + res[#res] = o + end + return res + end +end +local +function merge_generator (char) + if not char then return end + return function(ary) + local res = {} + for i = 1, #ary do + t_insert(res,char(ary[i])) + end + return t_concat(res) + end +end +local +function utf8_get_int2 (subject, i) + local byte, b5, b4, b3, b2, b1 = s_byte(subject, i) + if byte < 128 then return byte, i + 1 + elseif byte < 192 then + error("Byte values between 0x80 to 0xBF cannot start a multibyte sequence") + elseif byte < 224 then + return (byte - 192)*64 + s_byte(subject, i+1), i+2 + elseif byte < 240 then + b2, b1 = s_byte(subject, i+1, i+2) + return (byte-224)*4096 + b2%64*64 + b1%64, i+3 + elseif byte < 248 then + b3, b2, b1 = s_byte(subject, i+1, i+2, 1+3) + return (byte-240)*262144 + b3%64*4096 + b2%64*64 + b1%64, i+4 + elseif byte < 252 then + b4, b3, b2, b1 = s_byte(subject, i+1, i+2, 1+3, i+4) + return (byte-248)*16777216 + b4%64*262144 + b3%64*4096 + b2%64*64 + b1%64, i+5 + elseif byte < 254 then + b5, b4, b3, b2, b1 = s_byte(subject, i+1, i+2, 1+3, i+4, i+5) + return (byte-252)*1073741824 + b5%64*16777216 + b4%64*262144 + b3%64*4096 + b2%64*64 + b1%64, i+6 + else + error("Byte values between 0xFE and OxFF cannot start a multibyte sequence") + end +end +local +function utf8_get_char(subject, i) + if i > #subject then return end + local offset = utf8_offset(s_byte(subject,i)) + return s_sub(subject, i, i + offset), i + offset + 1 +end +local +function utf8_char(c) + if c < 128 then + return s_char(c) + elseif c < 2048 then + return s_char(192 + c/64, 128 + c%64) + elseif c < 55296 or 57343 < c and c < 65536 then + return s_char(224 + c/4096, 128 + c/64%64, 128 + c%64) + elseif c < 2097152 then + return s_char(240 + c/262144, 128 + c/4096%64, 128 + c/64%64, 128 + c%64) + elseif c < 67108864 then + return s_char(248 + c/16777216, 128 + c/262144%64, 128 + c/4096%64, 128 + c/64%64, 128 + c%64) + elseif c < 2147483648 then + return s_char( 252 + c/1073741824, + 128 + c/16777216%64, 128 + c/262144%64, 128 + c/4096%64, 128 + c/64%64, 128 + c%64) + end + error("Bad Unicode code point: "..c..".") +end +local +function binary_validate (subject, start, finish) + start = start or 1 + finish = finish or #subject + return true, finish +end +local +function binary_next_int (subject, i) + i = i and i+1 or 1 + if i >= #subject then return end + return i, i, s_sub(subject, i, i) +end +local +function binary_next_char (subject, i) + i = i and i+1 or 1 + if i > #subject then return end + return i, i, s_byte(subject,i) +end +local +function binary_split_int (subject) + local chars = {} + for i = 1, #subject do + t_insert(chars, s_byte(subject,i)) + end + return chars +end +local +function binary_split_char (subject) + local chars = {} + for i = 1, #subject do + t_insert(chars, s_sub(subject,i,i)) + end + return chars +end +local +function binary_get_int(subject, i) + return s_byte(subject, i), i + 1 +end +local +function binary_get_char(subject, i) + return s_sub(subject, i, i), i + 1 +end +local charsets = { + binary = { + name = "binary", + binary = true, + validate = binary_validate, + split_char = binary_split_char, + split_int = binary_split_int, + next_char = binary_next_char, + next_int = binary_next_int, + get_char = binary_get_char, + get_int = binary_get_int, + tochar = s_char + }, + ["UTF-8"] = { + name = "UTF-8", + validate = utf8_validate, + split_char = utf8_split_char, + split_int = utf8_split_int, + next_char = utf8_next_char, + next_int = utf8_next_int, + get_char = utf8_get_char, + get_int = utf8_get_int + } +} +return function (Builder) + local cs = Builder.options.charset or "binary" + if charsets[cs] then + Builder.charset = copy(charsets[cs]) + Builder.binary_split_int = binary_split_int + else + error("NYI: custom charsets") + end +end + +end +end +--============================================================================= +do local _ENV = _ENV +packages['compat'] = function (...) + +local _, debug, jit +_, debug = pcall(require, "debug") +_, jit = pcall(require, "jit") +jit = _ and jit +local compat = { + debug = debug, + lua51 = (_VERSION == "Lua 5.1") and not jit, + lua52 = _VERSION == "Lua 5.2", + luajit = jit and true or false, + jit = jit and jit.status(), + lua52_len = not #setmetatable({},{__len = function()end}), + proxies = pcall(function() + local prox = newproxy(true) + local prox2 = newproxy(prox) + assert (type(getmetatable(prox)) == "table" + and (getmetatable(prox)) == (getmetatable(prox2))) + end), + _goto = not not(loadstring or load)"::R::" +} +return compat + +end +end +--============================================================================= +do local _ENV = _ENV +packages['compiler'] = function (...) +local assert, error, pairs, print, rawset, select, setmetatable, tostring, type + = assert, error, pairs, print, rawset, select, setmetatable, tostring, type +local s, t, u = require"string", require"table", require"util" +local _ENV = u.noglobals() ---------------------------------------------------- +local s_byte, s_sub, t_concat, t_insert, t_remove, t_unpack + = s.byte, s.sub, t.concat, t.insert, t.remove, u.unpack +local load, map, map_all, t_pack + = u.load, u.map, u.map_all, u.pack +local expose = u.expose +return function(Builder, LL) +local evaluate, LL_ispattern = LL.evaluate, LL.ispattern +local charset = Builder.charset +local compilers = {} +local +function compile(pt, ccache) + if not LL_ispattern(pt) then + error("pattern expected") + end + local typ = pt.pkind + if typ == "grammar" then + ccache = {} + elseif typ == "ref" or typ == "choice" or typ == "sequence" then + if not ccache[pt] then + ccache[pt] = compilers[typ](pt, ccache) + end + return ccache[pt] + end + if not pt.compiled then + pt.compiled = compilers[pt.pkind](pt, ccache) + end + return pt.compiled +end +LL.compile = compile +local +function clear_captures(ary, ci) + for i = ci, #ary do ary[i] = nil end +end +local LL_compile, LL_evaluate, LL_P + = LL.compile, LL.evaluate, LL.P +local function computeidex(i, len) + if i == 0 or i == 1 or i == nil then return 1 + elseif type(i) ~= "number" then error"number or nil expected for the stating index" + elseif i > 0 then return i > len and len + 1 or i + else return len + i < 0 and 1 or len + i + 1 + end +end +local function newcaps() + return { + kind = {}, + bounds = {}, + openclose = {}, + aux = -- [[DBG]] dbgcaps + {} + } +end +local +function _match(dbg, pt, sbj, si, ...) + if dbg then ------------- + print("@!!! Match !!!@", pt) + end --------------------- + pt = LL_P(pt) + assert(type(sbj) == "string", "string expected for the match subject") + si = computeidex(si, #sbj) + if dbg then ------------- + print(("-"):rep(30)) + print(pt.pkind) + LL.pprint(pt) + end --------------------- + local matcher = compile(pt, {}) + local caps = newcaps() + local matcher_state = {grammars = {}, args = {n = select('#',...),...}, tags = {}} + local success, final_si, ci = matcher(sbj, si, caps, 1, matcher_state) + if dbg then ------------- + print("!!! Done Matching !!! success: ", success, + "final position", final_si, "final cap index", ci, + "#caps", #caps.openclose) + end---------------------- + if success then + clear_captures(caps.kind, ci) + clear_captures(caps.aux, ci) + if dbg then ------------- + print("trimmed cap index = ", #caps + 1) + LL.cprint(caps, sbj, 1) + end --------------------- + local values, _, vi = LL_evaluate(caps, sbj, 1, 1) + if dbg then ------------- + print("#values", vi) + expose(values) + end --------------------- + if vi == 0 + then return final_si + else return t_unpack(values, 1, vi) end + else + if dbg then print("Failed") end + return nil + end +end +function LL.match(...) + return _match(false, ...) +end +function LL.dmatch(...) + return _match(true, ...) +end +for _, v in pairs{ + "C", "Cf", "Cg", "Cs", "Ct", "Clb", + "div_string", "div_table", "div_number", "div_function" +} do + compilers[v] = load(([=[ + local compile, expose, type, LL = ... + return function (pt, ccache) + local matcher, this_aux = compile(pt.pattern, ccache), pt.aux + return function (sbj, si, caps, ci, state) + local ref_ci = ci + local kind, bounds, openclose, aux + = caps.kind, caps.bounds, caps.openclose, caps.aux + kind [ci] = "XXXX" + bounds [ci] = si + openclose [ci] = 0 + caps.aux [ci] = (this_aux or false) + local success + success, si, ci + = matcher(sbj, si, caps, ci + 1, state) + if success then + if ci == ref_ci + 1 then + caps.openclose[ref_ci] = si + else + kind [ci] = "XXXX" + bounds [ci] = si + openclose [ci] = ref_ci - ci + aux [ci] = this_aux or false + ci = ci + 1 + end + else + ci = ci - 1 + end + return success, si, ci + end + end]=]):gsub("XXXX", v), v.." compiler")(compile, expose, type, LL) +end +compilers["Carg"] = function (pt, ccache) + local n = pt.aux + return function (sbj, si, caps, ci, state) + if state.args.n < n then error("reference to absent argument #"..n) end + caps.kind [ci] = "value" + caps.bounds [ci] = si + if state.args[n] == nil then + caps.openclose [ci] = 1/0 + caps.aux [ci] = 1/0 + else + caps.openclose [ci] = si + caps.aux [ci] = state.args[n] + end + return true, si, ci + 1 + end +end +for _, v in pairs{ + "Cb", "Cc", "Cp" +} do + compilers[v] = load(([=[ + return function (pt, ccache) + local this_aux = pt.aux + return function (sbj, si, caps, ci, state) + caps.kind [ci] = "XXXX" + caps.bounds [ci] = si + caps.openclose [ci] = si + caps.aux [ci] = this_aux or false + return true, si, ci + 1 + end + end]=]):gsub("XXXX", v), v.." compiler")(expose) +end +compilers["/zero"] = function (pt, ccache) + local matcher = compile(pt.pattern, ccache) + return function (sbj, si, caps, ci, state) + local success, nsi = matcher(sbj, si, caps, ci, state) + clear_captures(caps.aux, ci) + return success, nsi, ci + end +end +local function pack_Cmt_caps(i,...) return i, t_pack(...) end +compilers["Cmt"] = function (pt, ccache) + local matcher, func = compile(pt.pattern, ccache), pt.aux + return function (sbj, si, caps, ci, state) + local success, Cmt_si, Cmt_ci = matcher(sbj, si, caps, ci, state) + if not success then + clear_captures(caps.aux, ci) + return false, si, ci + end + local final_si, values + if Cmt_ci == ci then + final_si, values = pack_Cmt_caps( + func(sbj, Cmt_si, s_sub(sbj, si, Cmt_si - 1)) + ) + else + clear_captures(caps.aux, Cmt_ci) + clear_captures(caps.kind, Cmt_ci) + local cps, _, nn = evaluate(caps, sbj, ci) + final_si, values = pack_Cmt_caps( + func(sbj, Cmt_si, t_unpack(cps, 1, nn)) + ) + end + if not final_si then + return false, si, ci + end + if final_si == true then final_si = Cmt_si end + if type(final_si) == "number" + and si <= final_si + and final_si <= #sbj + 1 + then + local kind, bounds, openclose, aux + = caps.kind, caps.bounds, caps.openclose, caps.aux + for i = 1, values.n do + kind [ci] = "value" + bounds [ci] = si + if values[i] == nil then + caps.openclose [ci] = 1/0 + caps.aux [ci] = 1/0 + else + caps.openclose [ci] = final_si + caps.aux [ci] = values[i] + end + ci = ci + 1 + end + elseif type(final_si) == "number" then + error"Index out of bounds returned by match-time capture." + else + error("Match time capture must return a number, a boolean or nil" + .." as first argument, or nothing at all.") + end + return true, final_si, ci + end +end +compilers["string"] = function (pt, ccache) + local S = pt.aux + local N = #S + return function(sbj, si, caps, ci, state) + local in_1 = si - 1 + for i = 1, N do + local c + c = s_byte(sbj,in_1 + i) + if c ~= S[i] then + return false, si, ci + end + end + return true, si + N, ci + end +end +compilers["char"] = function (pt, ccache) + return load(([=[ + local s_byte, s_char = ... + return function(sbj, si, caps, ci, state) + local c, nsi = s_byte(sbj, si), si + 1 + if c ~= __C0__ then + return false, si, ci + end + return true, nsi, ci + end]=]):gsub("__C0__", tostring(pt.aux)))(s_byte, ("").char) +end +local +function truecompiled (sbj, si, caps, ci, state) + return true, si, ci +end +compilers["true"] = function (pt) + return truecompiled +end +local +function falsecompiled (sbj, si, caps, ci, state) + return false, si, ci +end +compilers["false"] = function (pt) + return falsecompiled +end +local +function eoscompiled (sbj, si, caps, ci, state) + return si > #sbj, si, ci +end +compilers["eos"] = function (pt) + return eoscompiled +end +local +function onecompiled (sbj, si, caps, ci, state) + local char, _ = s_byte(sbj, si), si + 1 + if char + then return true, si + 1, ci + else return false, si, ci end +end +compilers["one"] = function (pt) + return onecompiled +end +compilers["any"] = function (pt) + local N = pt.aux + if N == 1 then + return onecompiled + else + N = pt.aux - 1 + return function (sbj, si, caps, ci, state) + local n = si + N + if n <= #sbj then + return true, n + 1, ci + else + return false, si, ci + end + end + end +end +do + local function checkpatterns(g) + for k,v in pairs(g.aux) do + if not LL_ispattern(v) then + error(("rule 'A' is not a pattern"):gsub("A", tostring(k))) + end + end + end + compilers["grammar"] = function (pt, ccache) + checkpatterns(pt) + local gram = map_all(pt.aux, compile, ccache) + local start = gram[1] + return function (sbj, si, caps, ci, state) + t_insert(state.grammars, gram) + local success, nsi, ci = start(sbj, si, caps, ci, state) + t_remove(state.grammars) + return success, nsi, ci + end + end +end +local dummy_acc = {kind={}, bounds={}, openclose={}, aux={}} +compilers["behind"] = function (pt, ccache) + local matcher, N = compile(pt.pattern, ccache), pt.aux + return function (sbj, si, caps, ci, state) + if si <= N then return false, si, ci end + local success = matcher(sbj, si - N, dummy_acc, ci, state) + dummy_acc.aux = {} + return success, si, ci + end +end +compilers["range"] = function (pt) + local ranges = pt.aux + return function (sbj, si, caps, ci, state) + local char, nsi = s_byte(sbj, si), si + 1 + for i = 1, #ranges do + local r = ranges[i] + if char and r[char] + then return true, nsi, ci end + end + return false, si, ci + end +end +compilers["set"] = function (pt) + local s = pt.aux + return function (sbj, si, caps, ci, state) + local char, nsi = s_byte(sbj, si), si + 1 + if s[char] + then return true, nsi, ci + else return false, si, ci end + end +end +compilers["range"] = compilers.set +compilers["ref"] = function (pt, ccache) + local name = pt.aux + local ref + return function (sbj, si, caps, ci, state) + if not ref then + if #state.grammars == 0 then + error(("rule 'XXXX' used outside a grammar"):gsub("XXXX", tostring(name))) + elseif not state.grammars[#state.grammars][name] then + error(("rule 'XXXX' undefined in given grammar"):gsub("XXXX", tostring(name))) + end + ref = state.grammars[#state.grammars][name] + end + local success, nsi, nci = ref(sbj, si, caps, ci, state) + return success, nsi, nci + end +end +local choice_tpl = [=[ + success, si, ci = XXXX(sbj, si, caps, ci, state) + if success then + return true, si, ci + else + end]=] +local function flatten(kind, pt, ccache) + if pt[2].pkind == kind then + return compile(pt[1], ccache), flatten(kind, pt[2], ccache) + else + return compile(pt[1], ccache), compile(pt[2], ccache) + end +end +compilers["choice"] = function (pt, ccache) + local choices = {flatten("choice", pt, ccache)} + local names, chunks = {}, {} + for i = 1, #choices do + local m = "ch"..i + names[#names + 1] = m + chunks[ #names ] = choice_tpl:gsub("XXXX", m) + end + names[#names + 1] = "clear_captures" + choices[ #names ] = clear_captures + local compiled = t_concat{ + "local ", t_concat(names, ", "), [=[ = ... + return function (sbj, si, caps, ci, state) + local aux, success = caps.aux, false + ]=], + t_concat(chunks,"\n"),[=[-- + return false, si, ci + end]=] + } + return load(compiled, "Choice")(t_unpack(choices)) +end +local sequence_tpl = [=[ + success, si, ci = XXXX(sbj, si, caps, ci, state) + if not success then + return false, ref_si, ref_ci + end]=] +compilers["sequence"] = function (pt, ccache) + local sequence = {flatten("sequence", pt, ccache)} + local names, chunks = {}, {} + for i = 1, #sequence do + local m = "seq"..i + names[#names + 1] = m + chunks[ #names ] = sequence_tpl:gsub("XXXX", m) + end + names[#names + 1] = "clear_captures" + sequence[ #names ] = clear_captures + local compiled = t_concat{ + "local ", t_concat(names, ", "), [=[ = ... + return function (sbj, si, caps, ci, state) + local ref_si, ref_ci, success = si, ci + ]=], + t_concat(chunks,"\n"),[=[ + return true, si, ci + end]=] + } + return load(compiled, "Sequence")(t_unpack(sequence)) +end +compilers["at most"] = function (pt, ccache) + local matcher, n = compile(pt.pattern, ccache), pt.aux + n = -n + return function (sbj, si, caps, ci, state) + local success = true + for i = 1, n do + success, si, ci = matcher(sbj, si, caps, ci, state) + if not success then + break + end + end + return true, si, ci + end +end +compilers["at least"] = function (pt, ccache) + local matcher, n = compile(pt.pattern, ccache), pt.aux + if n == 0 then + return function (sbj, si, caps, ci, state) + local last_si, last_ci + while true do + local success + last_si, last_ci = si, ci + success, si, ci = matcher(sbj, si, caps, ci, state) + if not success then + si, ci = last_si, last_ci + break + end + end + return true, si, ci + end + elseif n == 1 then + return function (sbj, si, caps, ci, state) + local last_si, last_ci + local success = true + success, si, ci = matcher(sbj, si, caps, ci, state) + if not success then + return false, si, ci + end + while true do + local success + last_si, last_ci = si, ci + success, si, ci = matcher(sbj, si, caps, ci, state) + if not success then + si, ci = last_si, last_ci + break + end + end + return true, si, ci + end + else + return function (sbj, si, caps, ci, state) + local last_si, last_ci + local success = true + for _ = 1, n do + success, si, ci = matcher(sbj, si, caps, ci, state) + if not success then + return false, si, ci + end + end + while true do + local success + last_si, last_ci = si, ci + success, si, ci = matcher(sbj, si, caps, ci, state) + if not success then + si, ci = last_si, last_ci + break + end + end + return true, si, ci + end + end +end +compilers["unm"] = function (pt, ccache) + if pt.pkind == "any" and pt.aux == 1 then + return eoscompiled + end + local matcher = compile(pt.pattern, ccache) + return function (sbj, si, caps, ci, state) + local success, _, _ = matcher(sbj, si, caps, ci, state) + return not success, si, ci + end +end +compilers["lookahead"] = function (pt, ccache) + local matcher = compile(pt.pattern, ccache) + return function (sbj, si, caps, ci, state) + local success, _, _ = matcher(sbj, si, caps, ci, state) + return success, si, ci + end +end +end + +end +end +--============================================================================= +do local _ENV = _ENV +packages['constructors'] = function (...) + +local getmetatable, ipairs, newproxy, print, setmetatable + = getmetatable, ipairs, newproxy, print, setmetatable +local t, u, compat + = require"table", require"util", require"compat" +local t_concat = t.concat +local copy, getuniqueid, id, map + , weakkey, weakval + = u.copy, u.getuniqueid, u.id, u.map + , u.weakkey, u.weakval +local _ENV = u.noglobals() ---------------------------------------------------- +local patternwith = { + constant = { + "Cp", "true", "false" + }, + aux = { + "string", "any", + "char", "range", "set", + "ref", "sequence", "choice", + "Carg", "Cb" + }, + subpt = { + "unm", "lookahead", "C", "Cf", + "Cg", "Cs", "Ct", "/zero" + }, + both = { + "behind", "at least", "at most", "Clb", "Cmt", + "div_string", "div_number", "div_table", "div_function" + }, + none = "grammar", "Cc" +} +return function(Builder, LL) --- module wrapper. +local S_tostring = Builder.set.tostring +local newpattern, pattmt +local next_pattern_id = 1 +if compat.proxies and not compat.lua52_len then + local proxycache = weakkey{} + local __index_LL = {__index = LL} + local baseproxy = newproxy(true) + pattmt = getmetatable(baseproxy) + Builder.proxymt = pattmt + function pattmt:__index(k) + return proxycache[self][k] + end + function pattmt:__newindex(k, v) + proxycache[self][k] = v + end + function LL.getdirect(p) return proxycache[p] end + function newpattern(cons) + local pt = newproxy(baseproxy) + setmetatable(cons, __index_LL) + proxycache[pt]=cons + pt.id = "__ptid" .. next_pattern_id + next_pattern_id = next_pattern_id + 1 + return pt + end +else + if LL.warnings and not compat.lua52_len then + print("Warning: The `__len` metamethod won't work with patterns, " + .."use `LL.L(pattern)` for lookaheads.") + end + pattmt = LL + function LL.getdirect (p) return p end + function newpattern(pt) + pt.id = "__ptid" .. next_pattern_id + next_pattern_id = next_pattern_id + 1 + return setmetatable(pt,LL) + end +end +Builder.newpattern = newpattern +local +function LL_ispattern(pt) return getmetatable(pt) == pattmt end +LL.ispattern = LL_ispattern +function LL.type(pt) + if LL_ispattern(pt) then + return "pattern" + else + return nil + end +end +local ptcache, meta +local +function resetcache() + ptcache, meta = {}, weakkey{} + Builder.ptcache = ptcache + for _, p in ipairs(patternwith.aux) do + ptcache[p] = weakval{} + end + for _, p in ipairs(patternwith.subpt) do + ptcache[p] = weakval{} + end + for _, p in ipairs(patternwith.both) do + ptcache[p] = {} + end + return ptcache +end +LL.resetptcache = resetcache +resetcache() +local constructors = {} +Builder.constructors = constructors +constructors["constant"] = { + truept = newpattern{ pkind = "true" }, + falsept = newpattern{ pkind = "false" }, + Cppt = newpattern{ pkind = "Cp" } +} +local getauxkey = { + string = function(aux, as_is) return as_is end, + table = copy, + set = function(aux, as_is) + return S_tostring(aux) + end, + range = function(aux, as_is) + return t_concat(as_is, "|") + end, + sequence = function(aux, as_is) + return t_concat(map(getuniqueid, aux),"|") + end +} +getauxkey.choice = getauxkey.sequence +constructors["aux"] = function(typ, aux, as_is) + local cache = ptcache[typ] + local key = (getauxkey[typ] or id)(aux, as_is) + local res_pt = cache[key] + if not res_pt then + res_pt = newpattern{ + pkind = typ, + aux = aux, + as_is = as_is + } + cache[key] = res_pt + end + return res_pt +end +constructors["none"] = function(typ, aux) + return newpattern{ + pkind = typ, + aux = aux + } +end +constructors["subpt"] = function(typ, pt) + local cache = ptcache[typ] + local res_pt = cache[pt.id] + if not res_pt then + res_pt = newpattern{ + pkind = typ, + pattern = pt + } + cache[pt.id] = res_pt + end + return res_pt +end +constructors["both"] = function(typ, pt, aux) + local cache = ptcache[typ][aux] + if not cache then + ptcache[typ][aux] = weakval{} + cache = ptcache[typ][aux] + end + local res_pt = cache[pt.id] + if not res_pt then + res_pt = newpattern{ + pkind = typ, + pattern = pt, + aux = aux, + cache = cache -- needed to keep the cache as long as the pattern exists. + } + cache[pt.id] = res_pt + end + return res_pt +end +constructors["binary"] = function(typ, a, b) + return newpattern{ + a, b; + pkind = typ, + } +end +end -- module wrapper + +end +end +--============================================================================= +do local _ENV = _ENV +packages['datastructures'] = function (...) +local getmetatable, pairs, setmetatable, type + = getmetatable, pairs, setmetatable, type +local m, t , u = require"math", require"table", require"util" +local compat = require"compat" +local ffi if compat.luajit then + ffi = require"ffi" +end +local _ENV = u.noglobals() ---------------------------------------------------- +local extend, load, u_max + = u.extend, u.load, u.max +local m_max, t_concat, t_insert, t_sort + = m.max, t.concat, t.insert, t.sort +local structfor = {} +local byteset_new, isboolset, isbyteset +local byteset_mt = {} +local +function byteset_constructor (upper) + local set = setmetatable(load(t_concat{ + "return{ [0]=false", + (", false"):rep(upper), + " }" + })(), + byteset_mt) + return set +end +if compat.jit then + local struct, boolset_constructor = {v={}} + function byteset_mt.__index(s,i) + if i == nil or i > s.upper then return nil end + return s.v[i] + end + function byteset_mt.__len(s) + return s.upper + end + function byteset_mt.__newindex(s,i,v) + s.v[i] = v + end + boolset_constructor = ffi.metatype('struct { int upper; bool v[?]; }', byteset_mt) + function byteset_new (t) + if type(t) == "number" then + local res = boolset_constructor(t+1) + res.upper = t + return res + end + local upper = u_max(t) + struct.upper = upper + if upper > 255 then error"bool_set overflow" end + local set = boolset_constructor(upper+1) + set.upper = upper + for i = 1, #t do set[t[i]] = true end + return set + end + function isboolset(s) return type(s)=="cdata" and ffi.istype(s, boolset_constructor) end + isbyteset = isboolset +else + function byteset_new (t) + if type(t) == "number" then return byteset_constructor(t) end + local set = byteset_constructor(u_max(t)) + for i = 1, #t do set[t[i]] = true end + return set + end + function isboolset(s) return false end + function isbyteset (s) + return getmetatable(s) == byteset_mt + end +end +local +function byterange_new (low, high) + high = ( low <= high ) and high or -1 + local set = byteset_new(high) + for i = low, high do + set[i] = true + end + return set +end +local tmpa, tmpb ={}, {} +local +function set_if_not_yet (s, dest) + if type(s) == "number" then + dest[s] = true + return dest + else + return s + end +end +local +function clean_ab (a,b) + tmpa[a] = nil + tmpb[b] = nil +end +local +function byteset_union (a ,b) + local upper = m_max( + type(a) == "number" and a or #a, + type(b) == "number" and b or #b + ) + local A, B + = set_if_not_yet(a, tmpa) + , set_if_not_yet(b, tmpb) + local res = byteset_new(upper) + for i = 0, upper do + res[i] = A[i] or B[i] or false + end + clean_ab(a,b) + return res +end +local +function byteset_difference (a, b) + local res = {} + for i = 0, 255 do + res[i] = a[i] and not b[i] + end + return res +end +local +function byteset_tostring (s) + local list = {} + for i = 0, 255 do + list[#list+1] = (s[i] == true) and i or nil + end + return t_concat(list,", ") +end +structfor.binary = { + set ={ + new = byteset_new, + union = byteset_union, + difference = byteset_difference, + tostring = byteset_tostring + }, + Range = byterange_new, + isboolset = isboolset, + isbyteset = isbyteset, + isset = isbyteset +} +local set_mt = {} +local +function set_new (t) + local set = setmetatable({}, set_mt) + for i = 1, #t do set[t[i]] = true end + return set +end +local -- helper for the union code. +function add_elements(a, res) + for k in pairs(a) do res[k] = true end + return res +end +local +function set_union (a, b) + a, b = (type(a) == "number") and set_new{a} or a + , (type(b) == "number") and set_new{b} or b + local res = set_new{} + add_elements(a, res) + add_elements(b, res) + return res +end +local +function set_difference(a, b) + local list = {} + a, b = (type(a) == "number") and set_new{a} or a + , (type(b) == "number") and set_new{b} or b + for el in pairs(a) do + if a[el] and not b[el] then + list[#list+1] = el + end + end + return set_new(list) +end +local +function set_tostring (s) + local list = {} + for el in pairs(s) do + t_insert(list,el) + end + t_sort(list) + return t_concat(list, ",") +end +local +function isset (s) + return (getmetatable(s) == set_mt) +end +local +function range_new (start, finish) + local list = {} + for i = start, finish do + list[#list + 1] = i + end + return set_new(list) +end +structfor.other = { + set = { + new = set_new, + union = set_union, + tostring = set_tostring, + difference = set_difference, + }, + Range = range_new, + isboolset = isboolset, + isbyteset = isbyteset, + isset = isset, + isrange = function(a) return false end +} +return function(Builder, LL) + local cs = (Builder.options or {}).charset or "binary" + if type(cs) == "string" then + cs = (cs == "binary") and "binary" or "other" + else + cs = cs.binary and "binary" or "other" + end + return extend(Builder, structfor[cs]) +end + +end +end +--============================================================================= +do local _ENV = _ENV +packages['evaluator'] = function (...) + +local select, tonumber, tostring, type + = select, tonumber, tostring, type +local s, t, u = require"string", require"table", require"util" +local s_sub, t_concat + = s.sub, t.concat +local t_unpack + = u.unpack +local _ENV = u.noglobals() ---------------------------------------------------- +return function(Builder, LL) -- Decorator wrapper +local eval = {} +local +function insert (caps, sbj, vals, ci, vi) + local openclose, kind = caps.openclose, caps.kind + while kind[ci] and openclose[ci] >= 0 do + ci, vi = eval[kind[ci]](caps, sbj, vals, ci, vi) + end + return ci, vi +end +function eval.C (caps, sbj, vals, ci, vi) + if caps.openclose[ci] > 0 then + vals[vi] = s_sub(sbj, caps.bounds[ci], caps.openclose[ci] - 1) + return ci + 1, vi + 1 + end + vals[vi] = false -- pad it for now + local cj, vj = insert(caps, sbj, vals, ci + 1, vi + 1) + vals[vi] = s_sub(sbj, caps.bounds[ci], caps.bounds[cj] - 1) + return cj + 1, vj +end +local +function lookback (caps, label, ci) + local aux, openclose, kind= caps.aux, caps.openclose, caps.kind + repeat + ci = ci - 1 + local auxv, oc = aux[ci], openclose[ci] + if oc < 0 then ci = ci + oc end + if oc ~= 0 and kind[ci] == "Clb" and label == auxv then + return ci + end + until ci == 1 + label = type(label) == "string" and "'"..label.."'" or tostring(label) + error("back reference "..label.." not found") +end +function eval.Cb (caps, sbj, vals, ci, vi) + local Cb_ci = lookback(caps, caps.aux[ci], ci) + Cb_ci, vi = eval.Cg(caps, sbj, vals, Cb_ci, vi) + return ci + 1, vi +end +function eval.Cc (caps, sbj, vals, ci, vi) + local these_values = caps.aux[ci] + for i = 1, these_values.n do + vi, vals[vi] = vi + 1, these_values[i] + end + return ci + 1, vi +end +eval["Cf"] = function() error("NYI: Cf") end +function eval.Cf (caps, sbj, vals, ci, vi) + if caps.openclose[ci] > 0 then + error"No First Value" + end + local func, Cf_vals, Cf_vi = caps.aux[ci], {} + ci = ci + 1 + ci, Cf_vi = eval[caps.kind[ci]](caps, sbj, Cf_vals, ci, 1) + if Cf_vi == 1 then + error"No first value" + end + local result = Cf_vals[1] + while caps.kind[ci] and caps.openclose[ci] >= 0 do + ci, Cf_vi = eval[caps.kind[ci]](caps, sbj, Cf_vals, ci, 1) + result = func(result, t_unpack(Cf_vals, 1, Cf_vi - 1)) + end + vals[vi] = result + return ci +1, vi + 1 +end +function eval.Cg (caps, sbj, vals, ci, vi) + if caps.openclose[ci] > 0 then + vals[vi] = s_sub(sbj, caps.bounds[ci], caps.openclose[ci] - 1) + return ci + 1, vi + 1 + end + local cj, vj = insert(caps, sbj, vals, ci + 1, vi) + if vj == vi then + vals[vj] = s_sub(sbj, caps.bounds[ci], caps.bounds[cj] - 1) + vj = vj + 1 + end + return cj + 1, vj +end +function eval.Clb (caps, sbj, vals, ci, vi) + local oc = caps.openclose + if oc[ci] > 0 then + return ci + 1, vi + end + local depth = 0 + repeat + if oc[ci] == 0 then depth = depth + 1 + elseif oc[ci] < 0 then depth = depth - 1 + end + ci = ci + 1 + until depth == 0 + return ci, vi +end +function eval.Cp (caps, sbj, vals, ci, vi) + vals[vi] = caps.bounds[ci] + return ci + 1, vi + 1 +end +function eval.Ct (caps, sbj, vals, ci, vi) + local aux, openclose, kind = caps. aux, caps.openclose, caps.kind + local tbl_vals = {} + vals[vi] = tbl_vals + if openclose[ci] > 0 then + return ci + 1, vi + 1 + end + local tbl_vi, Clb_vals = 1, {} + ci = ci + 1 + while kind[ci] and openclose[ci] >= 0 do + if kind[ci] == "Clb" then + local label, Clb_vi = aux[ci], 1 + ci, Clb_vi = eval.Cg(caps, sbj, Clb_vals, ci, 1) + if Clb_vi ~= 1 then tbl_vals[label] = Clb_vals[1] end + else + ci, tbl_vi = eval[kind[ci]](caps, sbj, tbl_vals, ci, tbl_vi) + end + end + return ci + 1, vi + 1 +end +local inf = 1/0 +function eval.value (caps, sbj, vals, ci, vi) + local val + if caps.aux[ci] ~= inf or caps.openclose[ci] ~= inf + then val = caps.aux[ci] + end + vals[vi] = val + return ci + 1, vi + 1 +end +function eval.Cs (caps, sbj, vals, ci, vi) + if caps.openclose[ci] > 0 then + vals[vi] = s_sub(sbj, caps.bounds[ci], caps.openclose[ci] - 1) + else + local bounds, kind, openclose = caps.bounds, caps.kind, caps.openclose + local start, buffer, Cs_vals, bi, Cs_vi = bounds[ci], {}, {}, 1, 1 + local last + ci = ci + 1 + while openclose[ci] >= 0 do + last = bounds[ci] + buffer[bi] = s_sub(sbj, start, last - 1) + bi = bi + 1 + ci, Cs_vi = eval[kind[ci]](caps, sbj, Cs_vals, ci, 1) + if Cs_vi > 1 then + buffer[bi] = Cs_vals[1] + bi = bi + 1 + start = openclose[ci-1] > 0 and openclose[ci-1] or bounds[ci-1] + else + start = last + end + end + buffer[bi] = s_sub(sbj, start, bounds[ci] - 1) + vals[vi] = t_concat(buffer) + end + return ci + 1, vi + 1 +end +local +function insert_divfunc_results(acc, val_i, ...) + local n = select('#', ...) + for i = 1, n do + val_i, acc[val_i] = val_i + 1, select(i, ...) + end + return val_i +end +function eval.div_function (caps, sbj, vals, ci, vi) + local func = caps.aux[ci] + local params, divF_vi + if caps.openclose[ci] > 0 then + params, divF_vi = {s_sub(sbj, caps.bounds[ci], caps.openclose[ci] - 1)}, 2 + else + params = {} + ci, divF_vi = insert(caps, sbj, params, ci + 1, 1) + end + ci = ci + 1 -- skip the closed or closing node. + vi = insert_divfunc_results(vals, vi, func(t_unpack(params, 1, divF_vi - 1))) + return ci, vi +end +function eval.div_number (caps, sbj, vals, ci, vi) + local this_aux = caps.aux[ci] + local divN_vals, divN_vi + if caps.openclose[ci] > 0 then + divN_vals, divN_vi = {s_sub(sbj, caps.bounds[ci], caps.openclose[ci] - 1)}, 2 + else + divN_vals = {} + ci, divN_vi = insert(caps, sbj, divN_vals, ci + 1, 1) + end + ci = ci + 1 -- skip the closed or closing node. + if this_aux >= divN_vi then error("no capture '"..this_aux.."' in /number capture.") end + vals[vi] = divN_vals[this_aux] + return ci, vi + 1 +end +local function div_str_cap_refs (caps, ci) + local opcl = caps.openclose + local refs = {open=caps.bounds[ci]} + if opcl[ci] > 0 then + refs.close = opcl[ci] + return ci + 1, refs, 0 + end + local first_ci = ci + local depth = 1 + ci = ci + 1 + repeat + local oc = opcl[ci] + if depth == 1 and oc >= 0 then refs[#refs+1] = ci end + if oc == 0 then + depth = depth + 1 + elseif oc < 0 then + depth = depth - 1 + end + ci = ci + 1 + until depth == 0 + refs.close = caps.bounds[ci - 1] + return ci, refs, #refs +end +function eval.div_string (caps, sbj, vals, ci, vi) + local n, refs + local cached + local cached, divS_vals = {}, {} + local the_string = caps.aux[ci] + ci, refs, n = div_str_cap_refs(caps, ci) + vals[vi] = the_string:gsub("%%([%d%%])", function (d) + if d == "%" then return "%" end + d = tonumber(d) + if not cached[d] then + if d > n then + error("no capture at index "..d.." in /string capture.") + end + if d == 0 then + cached[d] = s_sub(sbj, refs.open, refs.close - 1) + else + local _, vi = eval[caps.kind[refs[d]]](caps, sbj, divS_vals, refs[d], 1) + if vi == 1 then error("no values in capture at index"..d.." in /string capture.") end + cached[d] = divS_vals[1] + end + end + return cached[d] + end) + return ci, vi + 1 +end +function eval.div_table (caps, sbj, vals, ci, vi) + local this_aux = caps.aux[ci] + local key + if caps.openclose[ci] > 0 then + key = s_sub(sbj, caps.bounds[ci], caps.openclose[ci] - 1) + else + local divT_vals, _ = {} + ci, _ = insert(caps, sbj, divT_vals, ci + 1, 1) + key = divT_vals[1] + end + ci = ci + 1 + if this_aux[key] then + vals[vi] = this_aux[key] + return ci, vi + 1 + else + return ci, vi + end +end +function LL.evaluate (caps, sbj, ci) + local vals = {} + local _, vi = insert(caps, sbj, vals, ci, 1) + return vals, 1, vi - 1 +end +end -- Decorator wrapper + +end +end +--============================================================================= +do local _ENV = _ENV +packages['factorizer'] = function (...) +local ipairs, pairs, print, setmetatable + = ipairs, pairs, print, setmetatable +local u = require"util" +local id, nop, setify, weakkey + = u.id, u.nop, u.setify, u.weakkey +local _ENV = u.noglobals() ---------------------------------------------------- +local +function process_booleans(a, b, opts) + local id, brk = opts.id, opts.brk + if a == id then return true, b + elseif b == id then return true, a + elseif a == brk then return true, brk + else return false end +end +local unary = setify{ + "unm", "lookahead", "C", "Cf", + "Cg", "Cs", "Ct", "/zero" +} +local unary_aux = setify{ + "behind", "at least", "at most", "Clb", "Cmt", + "div_string", "div_number", "div_table", "div_function" +} +local unifiable = setify{"char", "set", "range"} +local hasCmt; hasCmt = setmetatable({}, {__mode = "k", __index = function(self, pt) + local kind, res = pt.pkind, false + if kind == "Cmt" + or kind == "ref" + then + res = true + elseif unary[kind] or unary_aux[kind] then + res = hasCmt[pt.pattern] + elseif kind == "choice" or kind == "sequence" then + res = hasCmt[pt[1]] or hasCmt[pt[2]] + end + hasCmt[pt] = res + return res +end}) +return function (Builder, LL) -------------------------------------------------- +if Builder.options.factorize == false then + return { + choice = nop, + sequence = nop, + lookahead = nop, + unm = nop + } +end +local constructors, LL_P = Builder.constructors, LL.P +local truept, falsept + = constructors.constant.truept + , constructors.constant.falsept +local --Range, Set, + S_union + = --Builder.Range, Builder.set.new, + Builder.set.union +local mergeable = setify{"char", "set"} +local type2cons = { + ["/zero"] = "__div", + ["div_number"] = "__div", + ["div_string"] = "__div", + ["div_table"] = "__div", + ["div_function"] = "__div", + ["at least"] = "__pow", + ["at most"] = "__pow", + ["Clb"] = "Cg", +} +local +function choice (a, b) + do -- handle the identity/break properties of true and false. + local hasbool, res = process_booleans(a, b, { id = falsept, brk = truept }) + if hasbool then return res end + end + local ka, kb = a.pkind, b.pkind + if a == b and not hasCmt[a] then + return a + elseif ka == "choice" then -- correct associativity without blowing up the stack + local acc, i = {}, 1 + while a.pkind == "choice" do + acc[i], a, i = a[1], a[2], i + 1 + end + acc[i] = a + for j = i, 1, -1 do + b = acc[j] + b + end + return b + elseif mergeable[ka] and mergeable[kb] then + return constructors.aux("set", S_union(a.aux, b.aux)) + elseif mergeable[ka] and kb == "any" and b.aux == 1 + or mergeable[kb] and ka == "any" and a.aux == 1 then + return ka == "any" and a or b + elseif ka == kb then + if (unary[ka] or unary_aux[ka]) and ( a.aux == b.aux ) then + return LL[type2cons[ka] or ka](a.pattern + b.pattern, a.aux) + elseif ( ka == kb ) and ka == "sequence" then + if a[1] == b[1] and not hasCmt[a[1]] then + return a[1] * (a[2] + b[2]) + end + end + end + return false +end +local +function lookahead (pt) + return pt +end +local +function sequence(a, b) + do + local hasbool, res = process_booleans(a, b, { id = truept, brk = falsept }) + if hasbool then return res end + end + local ka, kb = a.pkind, b.pkind + if ka == "sequence" then -- correct associativity without blowing up the stack + local acc, i = {}, 1 + while a.pkind == "sequence" do + acc[i], a, i = a[1], a[2], i + 1 + end + acc[i] = a + for j = i, 1, -1 do + b = acc[j] * b + end + return b + elseif (ka == "one" or ka == "any") and (kb == "one" or kb == "any") then + return LL_P(a.aux + b.aux) + end + return false +end +local +function unm (pt) + if pt == truept then return falsept + elseif pt == falsept then return truept + elseif pt.pkind == "unm" then return #pt.pattern + elseif pt.pkind == "lookahead" then return -pt.pattern + end +end +return { + choice = choice, + lookahead = lookahead, + sequence = sequence, + unm = unm +} +end + +end +end +--============================================================================= +do local _ENV = _ENV +packages['init'] = function (...) + +local getmetatable, setmetatable, pcall + = getmetatable, setmetatable, pcall +local u = require"util" +local copy, map, nop, t_unpack + = u.copy, u.map, u.nop, u.unpack +local API, charsets, compiler, constructors + , datastructures, evaluator, factorizer + , locale, printers, re + = t_unpack(map(require, + { "API", "charsets", "compiler", "constructors" + , "datastructures", "evaluator", "factorizer" + , "locale", "printers", "re" })) +local _, package = pcall(require, "package") +local _ENV = u.noglobals() ---------------------------------------------------- +local VERSION = "0.12" +local LuVERSION = "0.1.0" +local function global(self, env) setmetatable(env,{__index = self}) end +local function register(self, env) + pcall(function() + package.loaded.lpeg = self + package.loaded.re = self.re + end) + if env then + env.lpeg, env.re = self, self.re + end + return self +end +local +function LuLPeg(options) + options = options and copy(options) or {} + local Builder, LL + = { options = options, factorizer = factorizer } + , { new = LuLPeg + , version = function () return VERSION end + , luversion = function () return LuVERSION end + , setmaxstack = nop --Just a stub, for compatibility. + } + LL.util = u + LL.global = global + LL.register = register + ;-- Decorate the LuLPeg object. + charsets(Builder, LL) + datastructures(Builder, LL) + printers(Builder, LL) + constructors(Builder, LL) + API(Builder, LL) + evaluator(Builder, LL) + ;(options.compiler or compiler)(Builder, LL) + locale(Builder, LL) + LL.re = re(Builder, LL) + return LL +end -- LuLPeg +local LL = LuLPeg() +return LL + +end +end +--============================================================================= +do local _ENV = _ENV +packages['locale'] = function (...) + +local extend = require"util".extend +local _ENV = require"util".noglobals() ---------------------------------------- +return function(Builder, LL) -- Module wrapper {------------------------------- +local R, S = LL.R, LL.S +local locale = {} +locale["cntrl"] = R"\0\31" + "\127" +locale["digit"] = R"09" +locale["lower"] = R"az" +locale["print"] = R" ~" -- 0x20 to 0xee +locale["space"] = S" \f\n\r\t\v" -- \f == form feed (for a printer), \v == vtab +locale["upper"] = R"AZ" +locale["alpha"] = locale["lower"] + locale["upper"] +locale["alnum"] = locale["alpha"] + locale["digit"] +locale["graph"] = locale["print"] - locale["space"] +locale["punct"] = locale["graph"] - locale["alnum"] +locale["xdigit"] = locale["digit"] + R"af" + R"AF" +function LL.locale (t) + return extend(t or {}, locale) +end +end -- Module wrapper --------------------------------------------------------} + +end +end +--============================================================================= +do local _ENV = _ENV +packages['match'] = function (...) + +end +end +--============================================================================= +do local _ENV = _ENV +packages['optimizer'] = function (...) +-- Nothing for now. +end +end +--============================================================================= +do local _ENV = _ENV +packages['printers'] = function (...) +return function(Builder, LL) +local ipairs, pairs, print, tostring, type + = ipairs, pairs, print, tostring, type +local s, t, u = require"string", require"table", require"util" +local S_tostring = Builder.set.tostring +local _ENV = u.noglobals() ---------------------------------------------------- +local s_char, s_sub, t_concat + = s.char, s.sub, t.concat +local expose, load, map + = u.expose, u.load, u.map +local escape_index = { + ["\f"] = "\\f", + ["\n"] = "\\n", + ["\r"] = "\\r", + ["\t"] = "\\t", + ["\v"] = "\\v", + ["\127"] = "\\ESC" +} +local function flatten(kind, list) + if list[2].pkind == kind then + return list[1], flatten(kind, list[2]) + else + return list[1], list[2] + end +end +for i = 0, 8 do escape_index[s_char(i)] = "\\"..i end +for i = 14, 31 do escape_index[s_char(i)] = "\\"..i end +local +function escape( str ) + return str:gsub("%c", escape_index) +end +local +function set_repr (set) + return s_char(load("return "..S_tostring(set))()) +end +local printers = {} +local +function LL_pprint (pt, offset, prefix) + return printers[pt.pkind](pt, offset, prefix) +end +function LL.pprint (pt0) + local pt = LL.P(pt0) + print"\nPrint pattern" + LL_pprint(pt, "", "") + print"--- /pprint\n" + return pt0 +end +for k, v in pairs{ + string = [[ "P( \""..escape(pt.as_is).."\" )" ]], + char = [[ "P( \""..escape(to_char(pt.aux)).."\" )"]], + ["true"] = [[ "P( true )" ]], + ["false"] = [[ "P( false )" ]], + eos = [[ "~EOS~" ]], + one = [[ "P( one )" ]], + any = [[ "P( "..pt.aux.." )" ]], + set = [[ "S( "..'"'..escape(set_repr(pt.aux))..'"'.." )" ]], + ["function"] = [[ "P( "..pt.aux.." )" ]], + ref = [[ + "V( ", + (type(pt.aux) == "string" and "\""..pt.aux.."\"") + or tostring(pt.aux) + , " )" + ]], + range = [[ + "R( ", + escape(t_concat(map( + pt.as_is, + function(e) return '"'..e..'"' end) + , ", ")) + ," )" + ]] +} do + printers[k] = load(([==[ + local k, map, t_concat, to_char, escape, set_repr = ... + return function (pt, offset, prefix) + print(t_concat{offset,prefix,XXXX}) + end + ]==]):gsub("XXXX", v), k.." printer")(k, map, t_concat, s_char, escape, set_repr) +end +for k, v in pairs{ + ["behind"] = [[ LL_pprint(pt.pattern, offset, "B ") ]], + ["at least"] = [[ LL_pprint(pt.pattern, offset, pt.aux.." ^ ") ]], + ["at most"] = [[ LL_pprint(pt.pattern, offset, pt.aux.." ^ ") ]], + unm = [[LL_pprint(pt.pattern, offset, "- ")]], + lookahead = [[LL_pprint(pt.pattern, offset, "# ")]], + choice = [[ + print(offset..prefix.."+") + local ch, i = {}, 1 + while pt.pkind == "choice" do + ch[i], pt, i = pt[1], pt[2], i + 1 + end + ch[i] = pt + map(ch, LL_pprint, offset.." :", "") + ]], + sequence = [=[ + print(offset..prefix.."*") + local acc, p2 = {} + offset = offset .. " |" + while true do + if pt.pkind ~= "sequence" then -- last element + if pt.pkind == "char" then + acc[#acc + 1] = pt.aux + print(offset..'P( "'..s.char(u.unpack(acc))..'" )') + else + if #acc ~= 0 then + print(offset..'P( "'..s.char(u.unpack(acc))..'" )') + end + LL_pprint(pt, offset, "") + end + break + elseif pt[1].pkind == "char" then + acc[#acc + 1] = pt[1].aux + elseif #acc ~= 0 then + print(offset..'P( "'..s.char(u.unpack(acc))..'" )') + acc = {} + LL_pprint(pt[1], offset, "") + else + LL_pprint(pt[1], offset, "") + end + pt = pt[2] + end + ]=], + grammar = [[ + print(offset..prefix.."Grammar") + for k, pt in pairs(pt.aux) do + local prefix = ( type(k)~="string" + and tostring(k) + or "\""..k.."\"" ) + LL_pprint(pt, offset.." ", prefix .. " = ") + end + ]] +} do + printers[k] = load(([[ + local map, LL_pprint, pkind, s, u, flatten = ... + return function (pt, offset, prefix) + XXXX + end + ]]):gsub("XXXX", v), k.." printer")(map, LL_pprint, type, s, u, flatten) +end +for _, cap in pairs{"C", "Cs", "Ct"} do + printers[cap] = function (pt, offset, prefix) + print(offset..prefix..cap) + LL_pprint(pt.pattern, offset.." ", "") + end +end +for _, cap in pairs{"Cg", "Clb", "Cf", "Cmt", "div_number", "/zero", "div_function", "div_table"} do + printers[cap] = function (pt, offset, prefix) + print(offset..prefix..cap.." "..tostring(pt.aux or "")) + LL_pprint(pt.pattern, offset.." ", "") + end +end +printers["div_string"] = function (pt, offset, prefix) + print(offset..prefix..'/string "'..tostring(pt.aux or "")..'"') + LL_pprint(pt.pattern, offset.." ", "") +end +for _, cap in pairs{"Carg", "Cp"} do + printers[cap] = function (pt, offset, prefix) + print(offset..prefix..cap.."( "..tostring(pt.aux).." )") + end +end +printers["Cb"] = function (pt, offset, prefix) + print(offset..prefix.."Cb( \""..pt.aux.."\" )") +end +printers["Cc"] = function (pt, offset, prefix) + print(offset..prefix.."Cc(" ..t_concat(map(pt.aux, tostring),", ").." )") +end +local cprinters = {} +local padding = " " +local function padnum(n) + n = tostring(n) + n = n .."."..((" "):rep(4 - #n)) + return n +end +local function _cprint(caps, ci, indent, sbj, n) + local openclose, kind = caps.openclose, caps.kind + indent = indent or 0 + while kind[ci] and openclose[ci] >= 0 do + if caps.openclose[ci] > 0 then + print(t_concat({ + padnum(n), + padding:rep(indent), + caps.kind[ci], + ": start = ", tostring(caps.bounds[ci]), + " finish = ", tostring(caps.openclose[ci]), + caps.aux[ci] and " aux = " or "", + caps.aux[ci] and ( + type(caps.aux[ci]) == "string" + and '"'..tostring(caps.aux[ci])..'"' + or tostring(caps.aux[ci]) + ) or "", + " \t", s_sub(sbj, caps.bounds[ci], caps.openclose[ci] - 1) + })) + if type(caps.aux[ci]) == "table" then expose(caps.aux[ci]) end + else + local kind = caps.kind[ci] + local start = caps.bounds[ci] + print(t_concat({ + padnum(n), + padding:rep(indent), kind, + ": start = ", start, + caps.aux[ci] and " aux = " or "", + caps.aux[ci] and ( + type(caps.aux[ci]) == "string" + and '"'..tostring(caps.aux[ci])..'"' + or tostring(caps.aux[ci]) + ) or "" + })) + ci, n = _cprint(caps, ci + 1, indent + 1, sbj, n + 1) + print(t_concat({ + padnum(n), + padding:rep(indent), + "/", kind, + " finish = ", tostring(caps.bounds[ci]), + " \t", s_sub(sbj, start, (caps.bounds[ci] or 1) - 1) + })) + end + n = n + 1 + ci = ci + 1 + end + return ci, n +end +function LL.cprint (caps, ci, sbj) + ci = ci or 1 + print"\nCapture Printer:\n================" + _cprint(caps, ci, 0, sbj, 1) + print"================\n/Cprinter\n" +end +return { pprint = LL.pprint,cprint = LL.cprint } +end -- module wrapper --------------------------------------------------------- + +end +end +--============================================================================= +do local _ENV = _ENV +packages['re'] = function (...) + +return function(Builder, LL) +local tonumber, type, print, error = tonumber, type, print, error +local setmetatable = setmetatable +local m = LL +local mm = m +local mt = getmetatable(mm.P(0)) +local version = _VERSION +if version == "Lua 5.2" then _ENV = nil end +local any = m.P(1) +local Predef = { nl = m.P"\n" } +local mem +local fmem +local gmem +local function updatelocale () + mm.locale(Predef) + Predef.a = Predef.alpha + Predef.c = Predef.cntrl + Predef.d = Predef.digit + Predef.g = Predef.graph + Predef.l = Predef.lower + Predef.p = Predef.punct + Predef.s = Predef.space + Predef.u = Predef.upper + Predef.w = Predef.alnum + Predef.x = Predef.xdigit + Predef.A = any - Predef.a + Predef.C = any - Predef.c + Predef.D = any - Predef.d + Predef.G = any - Predef.g + Predef.L = any - Predef.l + Predef.P = any - Predef.p + Predef.S = any - Predef.s + Predef.U = any - Predef.u + Predef.W = any - Predef.w + Predef.X = any - Predef.x + mem = {} -- restart memoization + fmem = {} + gmem = {} + local mt = {__mode = "v"} + setmetatable(mem, mt) + setmetatable(fmem, mt) + setmetatable(gmem, mt) +end +updatelocale() +local function getdef (id, defs) + local c = defs and defs[id] + if not c then error("undefined name: " .. id) end + return c +end +local function patt_error (s, i) + local msg = (#s < i + 20) and s:sub(i) + or s:sub(i,i+20) .. "..." + msg = ("pattern error near '%s'"):format(msg) + error(msg, 2) +end +local function mult (p, n) + local np = mm.P(true) + while n >= 1 do + if n%2 >= 1 then np = np * p end + p = p * p + n = n/2 + end + return np +end +local function equalcap (s, i, c) + if type(c) ~= "string" then return nil end + local e = #c + i + if s:sub(i, e - 1) == c then return e else return nil end +end +local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 +local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0 +local arrow = S * "<-" +local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1 +name = m.C(name) +local Def = name * m.Carg(1) +local num = m.C(m.R"09"^1) * S / tonumber +local String = "'" * m.C((any - "'")^0) * "'" + + '"' * m.C((any - '"')^0) * '"' +local defined = "%" * Def / function (c,Defs) + local cat = Defs and Defs[c] or Predef[c] + if not cat then error ("name '" .. c .. "' undefined") end + return cat +end +local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R +local item = defined + Range + m.C(any) +local Class = + "[" + * (m.C(m.P"^"^-1)) -- optional complement symbol + * m.Cf(item * (item - "]")^0, mt.__add) / + function (c, p) return c == "^" and any - p or p end + * "]" +local function adddef (t, k, exp) + if t[k] then + error("'"..k.."' already defined as a rule") + else + t[k] = exp + end + return t +end +local function firstdef (n, r) return adddef({n}, n, r) end +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end +local exp = m.P{ "Exp", + Exp = S * ( m.V"Grammar" + + m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) ); + Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul) + * (m.L(seq_follow) + patt_error); + Prefix = "&" * S * m.V"Prefix" / mt.__len + + "!" * S * m.V"Prefix" / mt.__unm + + m.V"Suffix"; + Suffix = m.Cf(m.V"Primary" * S * + ( ( m.P"+" * m.Cc(1, mt.__pow) + + m.P"*" * m.Cc(0, mt.__pow) + + m.P"?" * m.Cc(-1, mt.__pow) + + "^" * ( m.Cg(num * m.Cc(mult)) + + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow)) + ) + + "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div)) + + m.P"{}" * m.Cc(nil, m.Ct) + + m.Cg(Def / getdef * m.Cc(mt.__div)) + ) + + "=>" * S * m.Cg(Def / getdef * m.Cc(m.Cmt)) + ) * S + )^0, function (a,b,f) return f(a,b) end ); + Primary = "(" * m.V"Exp" * ")" + + String / mm.P + + Class + + defined + + "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" / + function (n, p) return mm.Cg(p, n) end + + "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + + m.P"{}" / mm.Cp + + "{~" * m.V"Exp" * "~}" / mm.Cs + + "{|" * m.V"Exp" * "|}" / mm.Ct + + "{" * m.V"Exp" * "}" / mm.C + + m.P"." * m.Cc(any) + + (name * -arrow + "<" * name * ">") * m.Cb("G") / NT; + Definition = name * arrow * m.V"Exp"; + Grammar = m.Cg(m.Cc(true), "G") * + m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0, + adddef) / mm.P +} +local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error) +local function compile (p, defs) + if mm.type(p) == "pattern" then return p end -- already compiled + local cp = pattern:match(p, 1, defs) + if not cp then error("incorrect pattern", 3) end + return cp +end +local function match (s, p, i) + local cp = mem[p] + if not cp then + cp = compile(p) + mem[p] = cp + end + return cp:match(s, i or 1) +end +local function find (s, p, i) + local cp = fmem[p] + if not cp then + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } + fmem[p] = cp + end + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end +end +local function gsub (s, p, rep) + local g = gmem[p] or {} -- ensure gmem[p] is not collected while here + gmem[p] = g + local cp = g[rep] + if not cp then + cp = compile(p) + cp = mm.Cs((cp / rep + 1)^0) + g[rep] = cp + end + return cp:match(s) +end +local re = { + compile = compile, + match = match, + find = find, + gsub = gsub, + updatelocale = updatelocale, +} +return re +end +end +end +--============================================================================= +do local _ENV = _ENV +packages['util'] = function (...) + +local getmetatable, setmetatable, load, loadstring, next + , pairs, pcall, print, rawget, rawset, select, tostring + , type, unpack + = getmetatable, setmetatable, load, loadstring, next + , pairs, pcall, print, rawget, rawset, select, tostring + , type, unpack +local m, s, t = require"math", require"string", require"table" +local m_max, s_match, s_gsub, t_concat, t_insert + = m.max, s.match, s.gsub, t.concat, t.insert +local compat = require"compat" +local +function nop () end +local noglobals, getglobal, setglobal if pcall and not compat.lua52 and not release then + local function errR (_,i) + error("illegal global read: " .. tostring(i), 2) + end + local function errW (_,i, v) + error("illegal global write: " .. tostring(i)..": "..tostring(v), 2) + end + local env = setmetatable({}, { __index=errR, __newindex=errW }) + noglobals = function() + pcall(setfenv, 3, env) + end + function getglobal(k) rawget(env, k) end + function setglobal(k, v) rawset(env, k, v) end +else + noglobals = nop +end +local _ENV = noglobals() ------------------------------------------------------ +local util = { + nop = nop, + noglobals = noglobals, + getglobal = getglobal, + setglobal = setglobal +} +util.unpack = t.unpack or unpack +util.pack = t.pack or function(...) return { n = select('#', ...), ... } end +if compat.lua51 then + local old_load = load + function util.load (ld, source, mode, env) + local fun + if type (ld) == 'string' then + fun = loadstring (ld) + else + fun = old_load (ld, source) + end + if env then + setfenv (fun, env) + end + return fun + end +else + util.load = load +end +if compat.luajit and compat.jit then + function util.max (ary) + local max = 0 + for i = 1, #ary do + max = m_max(max,ary[i]) + end + return max + end +elseif compat.luajit then + local t_unpack = util.unpack + function util.max (ary) + local len = #ary + if len <=30 or len > 10240 then + local max = 0 + for i = 1, #ary do + local j = ary[i] + if j > max then max = j end + end + return max + else + return m_max(t_unpack(ary)) + end + end +else + local t_unpack = util.unpack + local safe_len = 1000 + function util.max(array) + local len = #array + if len == 0 then return -1 end -- FIXME: shouldn't this be `return -1`? + local off = 1 + local off_end = safe_len + local max = array[1] -- seed max. + repeat + if off_end > len then off_end = len end + local seg_max = m_max(t_unpack(array, off, off_end)) + if seg_max > max then + max = seg_max + end + off = off + safe_len + off_end = off_end + safe_len + until off >= len + return max + end +end +local +function setmode(t,mode) + local mt = getmetatable(t) or {} + if mt.__mode then + error("The mode has already been set on table "..tostring(t)..".") + end + mt.__mode = mode + return setmetatable(t, mt) +end +util.setmode = setmode +function util.weakboth (t) + return setmode(t,"kv") +end +function util.weakkey (t) + return setmode(t,"k") +end +function util.weakval (t) + return setmode(t,"v") +end +function util.strip_mt (t) + return setmetatable(t, nil) +end +local getuniqueid +do + local N, index = 0, {} + function getuniqueid(v) + if not index[v] then + N = N + 1 + index[v] = N + end + return index[v] + end +end +util.getuniqueid = getuniqueid +do + local counter = 0 + function util.gensym () + counter = counter + 1 + return "___SYM_"..counter + end +end +function util.passprint (...) print(...) return ... end +local val_to_str_, key_to_str, table_tostring, cdata_to_str, t_cache +local multiplier = 2 +local +function val_to_string (v, indent) + indent = indent or 0 + t_cache = {} -- upvalue. + local acc = {} + val_to_str_(v, acc, indent, indent) + local res = t_concat(acc, "") + return res +end +util.val_to_str = val_to_string +function val_to_str_ ( v, acc, indent, str_indent ) + str_indent = str_indent or 1 + if "string" == type( v ) then + v = s_gsub( v, "\n", "\n" .. (" "):rep( indent * multiplier + str_indent ) ) + if s_match( s_gsub( v,"[^'\"]",""), '^"+$' ) then + acc[#acc+1] = t_concat{ "'", "", v, "'" } + else + acc[#acc+1] = t_concat{'"', s_gsub(v,'"', '\\"' ), '"' } + end + elseif "cdata" == type( v ) then + cdata_to_str( v, acc, indent ) + elseif "table" == type(v) then + if t_cache[v] then + acc[#acc+1] = t_cache[v] + else + t_cache[v] = tostring( v ) + table_tostring( v, acc, indent ) + end + else + acc[#acc+1] = tostring( v ) + end +end +function key_to_str ( k, acc, indent ) + if "string" == type( k ) and s_match( k, "^[_%a][_%a%d]*$" ) then + acc[#acc+1] = s_gsub( k, "\n", (" "):rep( indent * multiplier + 1 ) .. "\n" ) + else + acc[#acc+1] = "[ " + val_to_str_( k, acc, indent ) + acc[#acc+1] = " ]" + end +end +function cdata_to_str(v, acc, indent) + acc[#acc+1] = ( " " ):rep( indent * multiplier ) + acc[#acc+1] = "[" + print(#acc) + for i = 0, #v do + if i % 16 == 0 and i ~= 0 then + acc[#acc+1] = "\n" + acc[#acc+1] = (" "):rep(indent * multiplier + 2) + end + acc[#acc+1] = v[i] and 1 or 0 + acc[#acc+1] = i ~= #v and ", " or "" + end + print(#acc, acc[1], acc[2]) + acc[#acc+1] = "]" +end +function table_tostring ( tbl, acc, indent ) + acc[#acc+1] = t_cache[tbl] + acc[#acc+1] = "{\n" + for k, v in pairs( tbl ) do + local str_indent = 1 + acc[#acc+1] = (" "):rep((indent + 1) * multiplier) + key_to_str( k, acc, indent + 1) + if acc[#acc] == " ]" + and acc[#acc - 2] == "[ " + then str_indent = 8 + #acc[#acc - 1] + end + acc[#acc+1] = " = " + val_to_str_( v, acc, indent + 1, str_indent) + acc[#acc+1] = "\n" + end + acc[#acc+1] = ( " " ):rep( indent * multiplier ) + acc[#acc+1] = "}" +end +function util.expose(v) print(val_to_string(v)) return v end +function util.map (ary, func, ...) + if type(ary) == "function" then ary, func = func, ary end + local res = {} + for i = 1,#ary do + res[i] = func(ary[i], ...) + end + return res +end +function util.selfmap (ary, func, ...) + if type(ary) == "function" then ary, func = func, ary end + for i = 1,#ary do + ary[i] = func(ary[i], ...) + end + return ary +end +local +function map_all (tbl, func, ...) + if type(tbl) == "function" then tbl, func = func, tbl end + local res = {} + for k, v in next, tbl do + res[k]=func(v, ...) + end + return res +end +util.map_all = map_all +local +function fold (ary, func, acc) + local i0 = 1 + if not acc then + acc = ary[1] + i0 = 2 + end + for i = i0, #ary do + acc = func(acc,ary[i]) + end + return acc +end +util.fold = fold +local +function foldr (ary, func, acc) + local offset = 0 + if not acc then + acc = ary[#ary] + offset = 1 + end + for i = #ary - offset, 1 , -1 do + acc = func(ary[i], acc) + end + return acc +end +util.foldr = foldr +local +function map_fold(ary, mfunc, ffunc, acc) + local i0 = 1 + if not acc then + acc = mfunc(ary[1]) + i0 = 2 + end + for i = i0, #ary do + acc = ffunc(acc,mfunc(ary[i])) + end + return acc +end +util.map_fold = map_fold +local +function map_foldr(ary, mfunc, ffunc, acc) + local offset = 0 + if not acc then + acc = mfunc(ary[#acc]) + offset = 1 + end + for i = #ary - offset, 1 , -1 do + acc = ffunc(mfunc(ary[i], acc)) + end + return acc +end +util.map_foldr = map_fold +function util.zip(a1, a2) + local res, len = {}, m_max(#a1,#a2) + for i = 1,len do + res[i] = {a1[i], a2[i]} + end + return res +end +function util.zip_all(t1, t2) + local res = {} + for k,v in pairs(t1) do + res[k] = {v, t2[k]} + end + for k,v in pairs(t2) do + if res[k] == nil then + res[k] = {t1[k], v} + end + end + return res +end +function util.filter(ary,func) + local res = {} + for i = 1,#ary do + if func(ary[i]) then + t_insert(res, ary[i]) + end + end +end +local +function id (...) return ... end +util.id = id +local function AND (a,b) return a and b end +local function OR (a,b) return a or b end +function util.copy (tbl) return map_all(tbl, id) end +function util.all (ary, mfunc) + if mfunc then + return map_fold(ary, mfunc, AND) + else + return fold(ary, AND) + end +end +function util.any (ary, mfunc) + if mfunc then + return map_fold(ary, mfunc, OR) + else + return fold(ary, OR) + end +end +function util.get(field) + return function(tbl) return tbl[field] end +end +function util.lt(ref) + return function(val) return val < ref end +end +function util.compose(f,g) + return function(...) return f(g(...)) end +end +function util.extend (destination, ...) + for i = 1, select('#', ...) do + for k,v in pairs((select(i, ...))) do + destination[k] = v + end + end + return destination +end +function util.setify (t) + local set = {} + for i = 1, #t do + set[t[i]]=true + end + return set +end +function util.arrayify (...) return {...} end +local +function _checkstrhelper(s) + return s.."" +end +function util.checkstring(s, func) + local success, str = pcall(_checkstrhelper, s) + if not success then + if func == nil then func = "?" end + error("bad argument to '" + ..tostring(func) + .."' (string expected, got " + ..type(s) + ..")", + 2) + end + return str +end +return util + +end +end +return require"init" + + + +-- The Romantic WTF public license. +-- -------------------------------- +-- a.k.a. version "<3" or simply v3 +-- +-- +-- Dear user, +-- +-- The LuLPeg library +-- +-- \ +-- '.,__ +-- \ / +-- '/,__ +-- / +-- / +-- / +-- has been / released +-- ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ +-- under the Romantic WTF Public License. +-- ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~`,´ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ +-- I hereby grant you an irrevocable license to +-- ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ +-- do what the gentle caress you want to +-- ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ +-- with this lovely +-- ~ ~ ~ ~ ~ ~ ~ ~ +-- / library... +-- / ~ ~ ~ ~ +-- / Love, +-- # / ',' +-- ####### · +-- ##### +-- ### +-- # +-- +-- -- Pierre-Yves +-- +-- +-- +-- P.S.: Even though I poured my heart into this work, +-- I _cannot_ provide any warranty regarding +-- its fitness for _any_ purpose. You +-- acknowledge that I will not be held liable +-- for any damage its use could incur. +-- +-- ----------------------------------------------------------------------------- +-- +-- LuLPeg, Copyright (C) 2013 Pierre-Yves Gérardy. +-- +-- The `re` module and lpeg.*.*.test.lua, +-- Copyright (C) 2013 Lua.org, PUC-Rio. +-- +-- 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. + diff --git a/lib/xmllpegparser.lua b/lib/xmllpegparser.lua new file mode 100644 index 0000000..c01caa0 --- /dev/null +++ b/lib/xmllpegparser.lua @@ -0,0 +1,820 @@ +-- from https://github.com/jonathanpoelen/lua-xmllpegparser + +local lpeg = require'lulpeg' +local lpeg1_0 = type(lpeg.version) == 'function' -- version is a string since 1.1 +local S = lpeg.S +local C = lpeg.C +local R = lpeg.R +local Ct = lpeg.Ct +local Cg = lpeg.Cg +local Cs = lpeg.Cs +local P = lpeg.P +local I = lpeg.Cp() +local Cc = lpeg.Cc +local Ce = Cc() + +local Space = S' \n\t' +local Space0 = Space^0 +local Space1 = Space^1 +local String = (S"'" * (1-S"'")^0 * S"'") + (S'"' * (1-S'"')^0 * S'"') +local CString = (S"'" * C((1-S"'")^0) * S"'") + (S'"' * C((1-S'"')^0) * S'"') +local Name = ((R('az','AZ') + S'_') * (R('az','AZ') + S'_-:' + R'09')^0) +local CName = C(Name) +local Attr = ( Name * Space0 * '=' * Space0 * String ) +local CAttr = Cg(CName * Space0 * '=' * Space0 * CString) +local XMLComment = '')^0 * '-->' +local CXMLComment = '')^0 * '-->' +local Entity = ('') +local CEntity = Cg('') + +local noop = function()end + +local mt = {__call = function(_, ...) return _.parse(...) end} + +local addI = function(x) return I * x end +local identityFn = function(x) return x end + +-- force a no captured value with a query function +local unsafeCall = function(patt, func) + return patt / function(...) func(...) end +end + +local safeCall = function(patt, func) + return patt / func +end + +local _parser = function(v, safeVisitor) + local call = safeVisitor == true and safeCall or unsafeCall + local mark = (v.withpos and addI or identityFn) + + local Comment = v.comment and call(CXMLComment, v.comment) or XMLComment + local Comments = Space0 * (Comment * Space0)^0 + + local hasAttr = v.accuattr or (v.accuattr ~= false and (v.tag or v.proc)) + local CAttrs = hasAttr and ( + lpeg1_0 -- Cf is deprecated in 1.1 + and lpeg.Cf(Ct'' * (Space1 * CAttr)^0, v.accuattr or rawset) * Space0 + or Ct'' * (Space1 * CAttr % (v.accuattr or rawset))^0 * Space0 + ) + local Attrs = (Space1 * Attr)^0 * Space0 + local ProcAttrs = (v.accuattr or (hasAttr and v.proc)) and CAttrs or Attrs + local TagAttrs = (v.accuattr or (hasAttr and v.tag )) and CAttrs or Attrs + + local Preproc = v.proc and + (Comments * call(mark('', v.proc))^0 or + (Comments * '' )^0 + + local Entities = v.entity and + (Comments * call(Cg(mark(CEntity)), v.entity))^0 or + (Comments * Entity )^0 + + local DoctypeEnt = Space0 * (P'>' + '[' * Entities * Comments * ']' * Space0 * '>') + local Doctype = v.doctype and + Comments * (call(mark('' * call(Ce, v.open) + '/>' or + P'>' + '/>' + + local Close = v.close and + '' or + '' + + local Text = v.text and + call(mark(C((Space0 * (1-S" \n\t<")^1)^1)), v.text) or + ((Space0 * (1-S" \n\t<")^1)^1) + + local Cdata = (v.cdata or v.text) and + '')^0) * ']]>'), v.cdata or v.text) or + '')^0) * ']]>' + + local G = Preproc * Doctype * (Space0 * (Tag * Open + Close + Comment + Cdata + Text))^0 * Space0 * I + + local init, finish = (v.init or noop), (v.finish or noop) + + return function(s, ...) + local err + local pos = init(...) + pos = G:match(s, pos) + if #s >= pos then + err = 'parse error at position ' .. tostring(pos) + end + + local doc, verr = finish(err, pos, s) + return doc, (verr == nil and err or verr) + end +end + + +local mkparser = function(pf) + local p + p = setmetatable({ + parse = pf, + parseFile = function(filename, ...) + local f, err = io.open(filename) + if f then + local content = f:read'*a' + f:close() + return p.parse(content, ...), nil + end + return f, err + end, + }, mt) + return p +end + +--! Create a parser. +--! @param[in] visitor table : see mkVisitor() +--! @param[in] safeVisitor boolean : when true, optimizes the parser. +--! Should only be used if all visitor functions (except init and finish) return nil +--! @return Parser +--! @code +--! @code +--! -- all values are optional +--! visitor = { +--! withpos = boolean, -- indicates if pos parameter exists in function parameter (except `finish`) +--! init = function(...), -- called before parsing, returns the position of the beginning of match or nil +--! finish = function(err, pos, xmlstring), -- called after parsing, returns (doc, err) or nil +--! proc = function(pos, name, attrs) or function(name, attrs), -- for `` +--! entity = function(entityName, entityValue), +--! doctype = function(pos, name, ident, pubident, dtd) or function(name, ident, pubident, dtd), -- called after all entity() +--! accuattr = function(table, entityName, entityValue), +--! -- `table` is an accumulator that will be transmitted to tag.attrs. +--! -- Set to `false` for disable this function. +--! -- If `nil` and `tag` is `not nil`, a default accumalator is used. +--! -- If `false`, the accumulator is disabled. +--! -- (`tag(pos, name, accuattr(accuattr({}, attr1, value1), attr2, value2)`) +--! tag = function(name, attrs), -- for a new tag (`` or ``) +--! open = function(), -- only for a open node (`` not ``), called after `tag`. +--! close = function(name), +--! text = function(text), +--! cdata = function(text), -- or `text` if nil +--! comment = function(str), +--! } +--! +--! parser = { +--! __call = --[[call parse]] +--! parse = function(str, --[[visitorInitArgs]]...), +--! parseFile = function(filename, --[[visitorInitArgs]]...), +--! } +--! @endcode +local function parser(visitor, safeVisitor) + return mkparser(_parser(visitor, safeVisitor)) +end + +--! Returns the default entity table. +--! @return table +local function defaultEntityTable() + return { quot='"', apos='\'', lt='<', gt='>', amp='&', tab='\t', nbsp=' ', } +end + +local DeclEntity = P'&' * C((1-P';')^1) * P';' + +--! Returns an LPeg expression that can replace entities. +--! @code +--! p = mkReplaceEntities(defaultEntityTable()) +--! str = 'a & b' +--! str = p:match(str) +--! assert(str == 'a & b') +--! @endcode +local function mkReplaceEntities(repl) + return Cs((DeclEntity / repl + 1)^0) +end + +--! @param[in] s string +--! @param[in] entities table : with entity name as key and value as replacement +--! @return string +local function replaceEntities(s, entities) + return s:gsub('&([^;]+);', entities) +end + +--! Add entities to resultEntities from the document entity table. +--! Create new table when resultEntities is nil. +--! @param[in] docEntities table +--! @param[in,out] resultEntities table|nil +--! @return resultEntities or a new table when nil +local function createEntityTable(docEntities, resultEntities) + local entities = resultEntities or defaultEntityTable() + for _,e in pairs(docEntities) do + e.value = replaceEntities(e.value, entities) + entities[e.name] = e.value + end + return entities +end + +--! Create a visitor. +--! If `not defaultEntities` and `evalEntities` then `defaultEntities = defaultEntityTable()`.\ +--! If `withoutPosition`, then `pos` parameter does not exist for the visitor functions except for `finish`. +--! @param[in] evalEntities boolean +--! @param[in] defaultEntities boolean|table|function +--! @param[in] withoutPosition boolean +--! @return visitor table and true for safeVisitor (see parser()) +local function mkVisitor(evalEntities, defaultEntities, withoutPosition) + local root, elem, doc, bad, SubEntity, accuattr, doctype, text, badclose + local mkDefaultEntities = defaultEntities and ( + type(defaultEntities) == 'table' and function() + local t = {} + for k,e in pairs(defaultEntities) do + t[k] = e + end + return t + end + or defaultEntities + ) or defaultEntityTable + + if evalEntities then + accuattr = function(a,k,v) + a[k] = SubEntity:match(v) + return a + end + + doctype = withoutPosition and function(name, ident, pubident, dtd) + doc.doctype = {name=name, ident=ident, pubident=pubident, dtd=dtd} + doc.tentities = createEntityTable(doc.entities, mkDefaultEntities()) + SubEntity = mkReplaceEntities(doc.tentities) + end or function(pos, name, ident, pubident, dtd) + doc.doctype = {name=name, ident=ident, pubident=pubident, dtd=dtd, pos=pos} + doc.tentities = createEntityTable(doc.entities, mkDefaultEntities()) + SubEntity = mkReplaceEntities(doc.tentities) + end + + text = withoutPosition and function(str) + elem.children[#elem.children+1] = {parent=elem, text=SubEntity:match(str)} + end or function(pos, str) + elem.children[#elem.children+1] = {parent=elem, text=SubEntity:match(str), pos=pos} + end + else + -- accuattr = noop + + doctype = withoutPosition and function(name, ident, pubident, dtd) + doc.doctype = {name=name, ident=ident, pubident=pubident, dtd=dtd} + end or function(pos, name, ident, pubident, dtd) + doc.doctype = {name=name, ident=ident, pubident=pubident, dtd=dtd, pos=pos} + end + + text = withoutPosition and function(str) + elem.children[#elem.children+1] = {parent=elem, text=str} + end or function(pos, str) + elem.children[#elem.children+1] = {parent=elem, text=str, pos=pos} + end + end + + local pushCloseError = function(tagname, pos) + local errElem = withoutPosition + and {tag=tagname, children={}} + or {tag=tagname, children={}, pos=pos-2} + bad.children[#bad.children+1] = errElem + badclose = badclose or errElem + elem = elem or bad + end + + return { + withpos=not withoutPosition, + accuattr=accuattr, + doctype=doctype, + text=text, + + cdata = withoutPosition and function(str) + elem.children[#elem.children+1] = {parent=elem, text=str, cdata=true} + end or function(pos, str) + elem.children[#elem.children+1] = {parent=elem, text=str, cdata=true, pos=pos-9} + end, + + init=function() + bad = {children={}} + root = {children={}} + doc = {preprocessor={}, entities={}, children=root.children} + elem = root + badclose = nil + if evalEntities then + SubEntity = mkReplaceEntities(mkDefaultEntities()) + end + end, + + finish=function(err, pos) + if badclose then + doc.bad = bad + err = (err and err .. ' ' or '') + .. 'No matching opening tag for ' .. tostring(badclose.tag) + .. (badclose.pos and ' at position ' .. tostring(badclose.pos) or '') + elseif root ~= elem then + err = (err and err .. ' ' or '') + .. 'No matching closing tag for ' .. tostring(elem.tag) + .. (elem.pos and ' at position ' .. tostring(elem.pos) or '') + end + + doc.lastpos = pos + if err then + doc.error = err + end + return doc, err + end, + + proc=withoutPosition and function(name, attrs) + doc.preprocessor[#doc.preprocessor+1] = {tag=name, attrs=attrs} + end or function(pos, name, attrs) + doc.preprocessor[#doc.preprocessor+1] = {tag=name, attrs=attrs, pos=pos} + end, + + entity=withoutPosition and function(k, v) + doc.entities[#doc.entities+1] = {name=k, value=v} + end or function(pos, k, v) + doc.entities[#doc.entities+1] = {name=k, value=v, pos=pos} + end, + + tag=withoutPosition and function(name, attrs) + elem.children[#elem.children+1] = {tag=name, attrs=attrs, parent=elem, children={}} + end or function(pos, name, attrs) + elem.children[#elem.children+1] = {tag=name, attrs=attrs, parent=elem, children={}, pos=pos-1} + end, + + open=function() + elem = elem.children[#elem.children] + end, + + close=withoutPosition and function(tagname) + local currentTag = elem.tag + elem = elem.parent + if elem and currentTag == tagname then + return + end + pushCloseError(tagname) + end or function(pos, tagname) + local currentTag = elem.tag + elem = elem.parent + if elem and currentTag == tagname then + return + end + pushCloseError(tagname, pos) + end, + }, true -- safeVisitor +end + +--! Create a parser whose visitor is built on the first call. +--! @param[in] visitorCreator function +--! @return Parser +local function lazyParser(visitorCreator) + local p + p = mkparser(function(...) + p.parse = _parser(visitorCreator()) + return p.parse(...) + end) + return p, true +end + +--! @{ +--! Document structure for default parser: +--! @code +--! -- pos member = index of string. Only when visitor.withPos == true +--! document = { +--! children = { +--! { pos=number, parent=table or nil, text=string[, cdata=true] } or +--! { pos=number, parent=table or nil, tag=string, attrs={ { name=string, value=string }, ... }, children={ ... } }, +--! ... +--! }, +--! bad = { children={ ... } } -- when a closed node has no match +--! preprocessor = { { pos=number, tag=string, attrs={ { name=string, value=string }, ... } }, +--! doctype = { pos=number, name=string, ident=string, pubident=string or nil, dtd=string or nil }, -- if there is a doctype +--! error = string, -- if error +--! lastpos = number, -- last known position of parse() +--! entities = { { pos=number, name=string, value=string }, ... }, +--! tentities = { name=value, ... } -- only if subEntities = true +--! } +--! @endcode + +-- The default parser used by parse(str, false) +local treeParser = lazyParser(function() return mkVisitor() end) +-- The default parser used by parse(str, true) +local treeParserWithReplacedEntities = lazyParser(function() return mkVisitor(true) end) +-- Parser without `pos` parameter +local treeParserWithoutPos = lazyParser(function() return mkVisitor(nil,nil,true) end) +-- Parser without `pos` parameter +local treeParserWithoutPosWithReplacedEntities = lazyParser(function() return mkVisitor(true,nil,true) end) +--! @} + +local _defaultParser, _defaultParserWithReplacedEntities = treeParser, treeParserWithReplacedEntities + +--! @param[in] b boolean|nil : when false, sets parsers that do not take a position as default parsers. +--! @return old defaultParser and defaultParserWithReplacedEntities +local function enableWithoutPosParser(b) + local r1, r2 = _defaultParser, _defaultParserWithReplacedEntities + if b == nil or b == true then + _defaultParser, _defaultParserWithReplacedEntities = treeParserWithoutPos, treeParserWithoutPosWithReplacedEntities + else + _defaultParser, _defaultParserWithReplacedEntities = treeParser, treeParserWithReplacedEntities + end + return r1, r2 +end + +--! Sets default parsers for without and with entity replacement. +--! @param[in] p table|nil : Use treeParser when p is nil +--! @param[in] pWithReplacedEntities table|boolean|nil : +--! Use treeParserWithReplacedEntities when pWithReplacedEntities is nil +--! @return old defaultParser and defaultParserWithReplacedEntities +local function setDefaultParsers(p, pWithReplacedEntities) + local r1, r2 = _defaultParser, _defaultParserWithReplacedEntities + _defaultParser = p or treeParser + if pWithReplacedEntities == true then + _defaultParserWithReplacedEntities = _defaultParser + elseif pWithReplacedEntities == false then + _defaultParserWithReplacedEntities = treeParserWithReplacedEntities + else + _defaultParserWithReplacedEntities = pWithReplacedEntities or treeParserWithReplacedEntities + end + return r1, r2 +end + +--! Returns a parser. +--! @param[in] visitorOrEvalEntities table|bool|nil : +--! When visitorOrEvalEntities is a boolean or nil, +--! a default parser is returned (see \c setDefaultParsers()). +--! Otherwise visitorOrEvalEntities is returned. +--! @return Parser +local getParser = function(visitorOrEvalEntities) + return (not visitorOrEvalEntities and _defaultParser) or + (visitorOrEvalEntities == true and _defaultParserWithReplacedEntities) or + parser(visitorOrEvalEntities) +end + +--! Returns a tuple `document table, (string error or nil)`. See `visitor.finish`. +--! @param[in] s string : xml data +--! @param[in,out] visitorOrEvalEntities table|bool|nil : see \c getParser() +--! @param[in,out] ... argument for visitor.init() +--! @return table +local function parse(xmlstring, visitorOrEvalEntities, ...) + return getParser(visitorOrEvalEntities).parse(xmlstring, ...) +end + +--! Return a tuple `document table, error file`. +--! @param filename[in] string +--! @param[in,out] visitorOrEvalEntities table|bool|nil : see \c getParser() +--! @param[in,out] ... argument for visitor.init() +--! @return table +local function parseFile(filename, visitorOrEvalEntities, ...) + return getParser(visitorOrEvalEntities).parseFile(filename, ...) +end + + +local function flatAttrCmp(a, b) + return a[1] < b[1] +end + +local tinsert = table.insert +local tremove = table.remove + +local function insertAttrs(t, it, escapeAttr) + for name,value in it do + tinsert(t, ' ') + tinsert(t, name) + tinsert(t, '="') + tinsert(t, escapeAttr(value)) + tinsert(t, '"') + end +end + +local function toStringComputeIndent(tindent, lvl, indentationText) + local prefix = tindent[lvl] + if not prefix then + prefix = tindent[lvl - 1] .. indentationText + tindent[lvl] = prefix + end + return prefix +end + +local function escapeComment(s) + s = s:gsub('--', '—') + return s +end + +local function escapeAttribute(s) + s = s:gsub('<', '<'):gsub('"', '"') + return s +end + +local function escapeAttributeAndAmp(s) + s = s:gsub('&', '&'):gsub('<', '<'):gsub('"', '"') + return s +end + +local function escapeCDATA(s) + s = s:gsub(']]>', ']]>]]> +--! - \b comment: text between +--! @return string +local function documentToString(tdoc, indentationText, params) + local escapeFns = params and params.escapes + -- luacheck: push ignore 431 + local escapeAttr = escapeFns and escapeFns.attr or identityFn + local escapeText = escapeFns and escapeFns.text or identityFn + local escapeCDATA = escapeFns and escapeFns.cdata or identityFn + local escapeComment = escapeFns and escapeFns.comment or identityFn + -- luacheck: pop + local inlineTextLengthMax = params and params.inlineTextLengthMax or 9999999 + local shortEmptyElements = not params or params.shortEmptyElements == nil or params.shortEmptyElements + + local attrIter + if not params or params.stableAttributes == nil or params.stableAttributes == true then + attrIter = function(attrs) + local flatAttrs = {} + for attr,value in pairs(attrs) do + tinsert(flatAttrs, {attr,value}) + end + + table.sort(flatAttrs, flatAttrCmp) + + local idx = 0 + return function() -- simplified iterator since used only once + idx = idx + 1 + local t = flatAttrs[idx] + if t then + return t[1], t[2] + end + end, flatAttrs, nil + end + elseif params.stableAttributes == false then + attrIter = identityFn + else + attrIter = params.stableAttributes + end + + local strs = {} + + local proc = tdoc.preprocessor + if proc then + for _, e in pairs(proc) do + tinsert(strs, '') + end + end + + local prefix = indentationText and '\n' or '' + local tindent = {prefix} + + indentationText = indentationText or '' + + local doctype = tdoc.doctype + if doctype then + if proc then + tinsert(strs, prefix) + end + + tinsert(strs, '') + end + + tinsert(strs, '[') + if tdoc.entities then + for _,t in pairs(tdoc.entities) do + addEntity(t.name, t.value) + end + else + for name,value in pairs(tdoc.tentities) do + addEntity(name, value:gsub('%', '%')) + end + end + tinsert(strs, prefix) + tinsert(strs, ']') + end + + tinsert(strs, '>') + end + + local elems = tdoc.children + if elems and elems[1] then + local emptyTable = {} + + local lvl = 1 + local depths = {} + + local i = 1 + local e, e2, tag, children, node + + local function loop() + --::loop:: + + e = elems[i] + tag = e.tag + + -- tag + if tag then + tinsert(strs, prefix) + tinsert(strs, '<') + tinsert(strs, tag) + insertAttrs(strs, attrIter(e.attrs), escapeAttr) + + children = e.children or emptyTable + + -- has at least 2 children or a tag as child + if children[2] or (children[1] and children[1].tag) then + tinsert(strs, '>') + + tinsert(depths, {elems, i}) + i = 0 + elems = children + lvl = lvl + 1 + prefix = toStringComputeIndent(tindent, lvl, indentationText) + + -- only has one text as child + elseif children[1] and children[1].text then + tinsert(strs, '>') + e2 = children[1] + -- CDATA + if e2.cdata then + tinsert(strs, toStringComputeIndent(tindent, lvl+1, indentationText)) + tinsert(strs, '') + tinsert(strs, prefix) + -- inline text + elseif #e2.text <= inlineTextLengthMax then + tinsert(strs, escapeText(e2.text)) + -- text + else + tinsert(strs, toStringComputeIndent(tindent, lvl+1, indentationText)) + tinsert(strs, escapeText(e2.text)) + tinsert(strs, prefix) + end + tinsert(strs, '') + + -- empty short tag + elseif shortEmptyElements then + tinsert(strs, '/>') + + -- empty tag + else + tinsert(strs, '>') + end + + -- text + elseif e.text then + -- CDATA + if e.cdata then + tinsert(strs, prefix) + tinsert(strs, '') + else + tinsert(strs, prefix) + tinsert(strs, escapeText(e.text)) + end + + -- comment + elseif e.comment then + tinsert(strs, prefix) + tinsert(strs, '') + end + + i = i + 1 + e = elems[i] + + -- close parent + while not e do + node = tremove(depths) + if not node then + return table.concat(strs, '') + end + elems = node[1] + i = node[2] + lvl = lvl - 1 + prefix = tindent[lvl] + tinsert(strs, prefix) + tinsert(strs, '') + i = i + 1 + e = elems[i] + end + + --goto loop + end + while true do + local rtn = loop() + if rtn then return rtn end + end + end + + return table.concat(strs, '') +end + +return { + defaultEntityTable = defaultEntityTable, + mkReplaceEntities = mkReplaceEntities, + replaceEntities = replaceEntities, + createEntityTable = createEntityTable, + mkVisitor = mkVisitor, + lazyParser = lazyParser, + treeParser = treeParser, + treeParserWithReplacedEntities = treeParserWithReplacedEntities, + treeParserWithoutPos = treeParserWithoutPos, + treeParserWithoutPosWithReplacedEntities = treeParserWithoutPosWithReplacedEntities, + enableWithoutPosParser = enableWithoutPosParser, + setDefaultParsers = setDefaultParsers, + parser = parser, + parse = parse, + parseFile = parseFile, + tostring = documentToString, + escapeFunctions = escapeFunctions, + escapeComment = escapeComment, + escapeAttribute = escapeAttribute, + escapeAttributeAndAmp = escapeAttributeAndAmp, + escapeCDATA = escapeCDATA, + escapeText = escapeText, + escapeTextAndAmp = escapeTextAndAmp, +} diff --git a/prpw b/prpw new file mode 100755 index 0000000..7fb8c1d --- /dev/null +++ b/prpw @@ -0,0 +1,107 @@ +#!/usr/bin/env lua +local basepath = (debug.getinfo(1, "S").source:sub(2):match("(.*[/\\])") or "./"):sub(1,-2) +env = {} + +local function main(arg) + package.path = basepath .. "/lib/?.lua;" .. basepath .. "/lib/?/main.lua;" .. package.path + math.randomseed(os.time()) + env.run({"autorun"}) + + if #arg > 0 then + env.run({"open",arg[1]}) + table.remove(arg,1) + env.run(arg) + if env.changed == true then + env.run({"save"}) + end + env.run({"exit"}) + end + + while true do + env.loop() + end +end + +function env.loop() + local command = env.stringSplit(io.read()," ",3,"\\") + env.run(command) + print("") +end + +function env.run(command) + local func = loadfile(basepath .. "/cmd/" ..command[1].. ".lua") + func = func() + env.rtn = func(command) +end + +function env.xmlFindTags(xml,tag) + local rtn = {} + for i,v in ipairs(xml["children"]) do + if v.tag == tag then + table.insert(rtn,v) + end + end + return rtn +end + +function env.stringDate(d) + return d.year .. "." ..env.padNumber(d.month,2).. "." ..env.padNumber(d.day,2).. "_" ..env.padNumber(d.hour,2).. ":" ..env.padNumber(d.min,2).. ":" ..env.padNumber(d.sec,2) +end + +function env.makeUUID() + local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and math.random(0, 0x0f) or math.random(8, 0x0b) + return string.format('%x', v) + end) +end + +function env.stringSplit(str, sep, limit, esc) + local length = string.len(str) + local index = 1 + local c + local escaped = false + local entry = "" + local rtn = {} + local rtnLength = 0 + + while index <= length do + c = string.sub(str,index,index) + if escaped == false then + if esc ~= nil then + if c == esc then + escaped = true + else + if c == sep then + if limit > -1 and rtnLength < limit then + table.insert(rtn,entry) + entry = "" + rtnLength = rtnLength + 1 + else + entry = entry .. c + end + else + entry = entry .. c + end + end + end + else + entry = entry .. c + end + index = index + 1 + end + table.insert(rtn,entry) + return rtn +end + +function env.padNumber(nr,len) + nr = tostring(nr) + local curLen = string.len(nr) + while curLen < len do + nr = "0" .. nr + curLen = curLen + 1 + end + return nr +end + +main(arg)