diff options
Diffstat (limited to 'script-beta/utility.lua')
-rw-r--r-- | script-beta/utility.lua | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/script-beta/utility.lua b/script-beta/utility.lua new file mode 100644 index 00000000..c9defebc --- /dev/null +++ b/script-beta/utility.lua @@ -0,0 +1,452 @@ +local tableSort = table.sort +local stringRep = string.rep +local tableConcat = table.concat +local tostring = tostring +local type = type +local pairs = pairs +local ipairs = ipairs +local next = next +local rawset = rawset +local move = table.move +local setmetatable = setmetatable +local mathType = math.type +local mathCeil = math.ceil +local getmetatable = getmetatable +local mathAbs = math.abs +local ioOpen = io.open + +_ENV = nil + +local function formatNumber(n) + local str = ('%.10f'):format(n) + str = str:gsub('%.?0*$', '') + return str +end + +local function isInteger(n) + if mathType then + return mathType(n) == 'integer' + else + return type(n) == 'number' and n % 1 == 0 + end +end + +local TAB = setmetatable({}, { __index = function (self, n) + self[n] = stringRep(' ', n) + return self[n] +end}) + +local RESERVED = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +local m = {} + +--- 打印表的结构 +---@param tbl table +---@param option table {optional = 'self'} +---@return string +function m.dump(tbl, option) + if not option then + option = {} + end + if type(tbl) ~= 'table' then + return ('%s'):format(tbl) + end + local lines = {} + local mark = {} + lines[#lines+1] = '{' + local function unpack(tbl, tab) + mark[tbl] = (mark[tbl] or 0) + 1 + local keys = {} + local keymap = {} + local integerFormat = '[%d]' + local alignment = 0 + if #tbl >= 10 then + local width = #tostring(#tbl) + integerFormat = ('[%%0%dd]'):format(mathCeil(width)) + end + for key in pairs(tbl) do + if type(key) == 'string' then + if not key:match('^[%a_][%w_]*$') + or RESERVED[key] + or option['longStringKey'] + then + keymap[key] = ('[%q]'):format(key) + else + keymap[key] = ('%s'):format(key) + end + elseif isInteger(key) then + keymap[key] = integerFormat:format(key) + else + keymap[key] = ('["<%s>"]'):format(tostring(key)) + end + keys[#keys+1] = key + if option['alignment'] then + if #keymap[key] > alignment then + alignment = #keymap[key] + end + end + end + local mt = getmetatable(tbl) + if not mt or not mt.__pairs then + if option['sorter'] then + option['sorter'](keys, keymap) + else + tableSort(keys, function (a, b) + return keymap[a] < keymap[b] + end) + end + end + for _, key in ipairs(keys) do + local keyWord = keymap[key] + if option['noArrayKey'] + and isInteger(key) + and key <= #tbl + then + keyWord = '' + else + if #keyWord < alignment then + keyWord = keyWord .. (' '):rep(alignment - #keyWord) .. ' = ' + else + keyWord = keyWord .. ' = ' + end + end + local value = tbl[key] + local tp = type(value) + if option['format'] and option['format'][key] then + lines[#lines+1] = ('%s%s%s,'):format(TAB[tab+1], keyWord, option['format'][key](value, unpack, tab+1)) + elseif tp == 'table' then + if mark[value] and mark[value] > 0 then + lines[#lines+1] = ('%s%s%s,'):format(TAB[tab+1], keyWord, option['loop'] or '"<Loop>"') + else + lines[#lines+1] = ('%s%s{'):format(TAB[tab+1], keyWord) + unpack(value, tab+1) + lines[#lines+1] = ('%s},'):format(TAB[tab+1]) + end + elseif tp == 'string' then + lines[#lines+1] = ('%s%s%q,'):format(TAB[tab+1], keyWord, value) + elseif tp == 'number' then + lines[#lines+1] = ('%s%s%s,'):format(TAB[tab+1], keyWord, (option['number'] or formatNumber)(value)) + elseif tp == 'nil' then + else + lines[#lines+1] = ('%s%s%s,'):format(TAB[tab+1], keyWord, tostring(value)) + end + end + mark[tbl] = mark[tbl] - 1 + end + unpack(tbl, 0) + lines[#lines+1] = '}' + return tableConcat(lines, '\r\n') +end + +--- 递归判断A与B是否相等 +---@param a any +---@param b any +---@return boolean +function m.equal(a, b) + local tp1 = type(a) + local tp2 = type(b) + if tp1 ~= tp2 then + return false + end + if tp1 == 'table' then + local mark = {} + for k, v in pairs(a) do + mark[k] = true + local res = m.equal(v, b[k]) + if not res then + return false + end + end + for k in pairs(b) do + if not mark[k] then + return false + end + end + return true + elseif tp1 == 'number' then + return mathAbs(a - b) <= 1e-10 + else + return a == b + end +end + +local function sortTable(tbl) + if not tbl then + tbl = {} + end + local mt = {} + local keys = {} + local mark = {} + local n = 0 + for key in next, tbl do + n=n+1;keys[n] = key + mark[key] = true + end + tableSort(keys) + function mt:__newindex(key, value) + rawset(self, key, value) + n=n+1;keys[n] = key + mark[key] = true + if type(value) == 'table' then + sortTable(value) + end + end + function mt:__pairs() + local list = {} + local m = 0 + for key in next, self do + if not mark[key] then + m=m+1;list[m] = key + end + end + if m > 0 then + move(keys, 1, n, m+1) + tableSort(list) + for i = 1, m do + local key = list[i] + keys[i] = key + mark[key] = true + end + n = n + m + end + local i = 0 + return function () + i = i + 1 + local key = keys[i] + return key, self[key] + end + end + + return setmetatable(tbl, mt) +end + +--- 创建一个有序表 +---@param tbl table {optional = 'self'} +---@return table +function m.container(tbl) + return sortTable(tbl) +end + +--- 读取文件 +---@param path string +function m.loadFile(path) + local f, e = ioOpen(path, 'rb') + if not f then + return nil, e + end + if f:read(3) ~= '\xEF\xBB\xBF' then + f:seek("set") + end + local buf = f:read 'a' + f:close() + return buf +end + +--- 写入文件 +---@param path string +---@param content string +function m.saveFile(path, content) + local f, e = ioOpen(path, "wb") + + if f then + f:write(content) + f:close() + return true + else + return false, e + end +end + +--- 计数器 +---@param init integer {optional = 'after'} +---@param step integer {optional = 'after'} +---@return fun():integer +function m.counter(init, step) + if not step then + step = 1 + end + local current = init and (init - 1) or 0 + return function () + current = current + step + return current + end +end + +--- 排序后遍历 +---@param t table +function m.sortPairs(t) + local keys = {} + for k in pairs(t) do + keys[#keys+1] = k + end + tableSort(keys) + local i = 0 + return function () + i = i + 1 + local k = keys[i] + return k, t[k] + end +end + +--- 深拷贝(不处理元表) +---@param source table +---@param target table {optional = 'self'} +function m.deepCopy(source, target) + local mark = {} + local function copy(a, b) + if type(a) ~= 'table' then + return a + end + if mark[a] then + return mark[a] + end + if not b then + b = {} + end + mark[a] = b + for k, v in pairs(a) do + b[copy(k)] = copy(v) + end + return b + end + return copy(source, target) +end + +--- 序列化 +function m.unpack(t) + local result = {} + local tid = 0 + local cache = {} + local function unpack(o) + local id = cache[o] + if not id then + tid = tid + 1 + id = tid + cache[o] = tid + if type(o) == 'table' then + local new = {} + result[tid] = new + for k, v in next, o do + new[unpack(k)] = unpack(v) + end + else + result[id] = o + end + end + return id + end + unpack(t) + return result +end + +--- 反序列化 +function m.pack(t) + local cache = {} + local function pack(id) + local o = cache[id] + if o then + return o + end + o = t[id] + if type(o) == 'table' then + local new = {} + cache[id] = new + for k, v in next, o do + new[pack(k)] = pack(v) + end + return new + else + cache[id] = o + return o + end + end + return pack(1) +end + +--- defer +local deferMT = { __close = function (self) self[1]() end } +function m.defer(callback) + return setmetatable({ callback }, deferMT) +end + +local esc = { + ["'"] = [[\']], + ['"'] = [[\"]], + ['\r'] = [[\r]], + ['\n'] = '\\\n', +} + +function m.viewString(str, quo) + if not quo then + if not str:find("'", 1, true) and str:find('"', 1, true) then + quo = "'" + else + quo = '"' + end + end + if quo == "'" then + return quo .. str:gsub([=[['\r\n]]=], esc) .. quo + elseif quo == '"' then + return quo .. str:gsub([=[["\r\n]]=], esc) .. quo + else + if str:find '\r' then + return m.viewString(str) + end + local eqnum = #quo - 2 + local fsymb = ']' .. ('='):rep(eqnum) .. ']' + if not str:find(fsymb, 1, true) then + return quo .. str .. fsymb + end + for i = 0, 10 do + local fsymb = ']' .. ('='):rep(i) .. ']' + if not str:find(fsymb, 1, true) then + local ssymb = '[' .. ('='):rep(i) .. '[' + return ssymb .. str .. fsymb + end + end + return m.viewString(str) + end +end + +function m.viewLiteral(v) + local tp = type(v) + if tp == 'nil' then + return 'nil' + elseif tp == 'string' then + return m.viewString(v) + elseif tp == 'boolean' then + return tostring(v) + elseif tp == 'number' then + if isInteger(v) then + return tostring(v) + else + return formatNumber(v) + end + end + return nil +end + +return m |