X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=builtin%2Fcommon%2Fmisc_helpers.lua;h=deeba788ea49ae551e62c6b7f550b0556f1479c7;hb=47aca6f6d164347de950a46b11e1774445cdb9e3;hp=38a7b1879d43395d86294f14682bb5cea5efb95a;hpb=c4359ff65cd8e4e754442b9f2ef7051a8eaa4241;p=minetest.git diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 38a7b1879..deeba788e 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -1,88 +1,185 @@ -- Minetest: builtin/misc_helpers.lua -------------------------------------------------------------------------------- -function basic_dump2(o) - if type(o) == "number" then +function basic_dump(o) + local tp = type(o) + if tp == "number" then return tostring(o) - elseif type(o) == "string" then + elseif tp == "string" then return string.format("%q", o) - elseif type(o) == "boolean" then + elseif tp == "boolean" then return tostring(o) - elseif type(o) == "function" then - return "" - elseif type(o) == "userdata" then - return "" - elseif type(o) == "nil" then + elseif tp == "nil" then return "nil" + -- Uncomment for full function dumping support. + -- Not currently enabled because bytecode isn't very human-readable and + -- dump's output is intended for humans. + --elseif tp == "function" then + -- return string.format("loadstring(%q)", string.dump(o)) else - error("cannot dump a " .. type(o)) - return nil + return string.format("<%s>", tp) + end +end + +local keywords = { + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, -- Lua 5.2 + ["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 function is_valid_identifier(str) + if not str:find("^[a-zA-Z_][a-zA-Z0-9_]*$") or keywords[str] then + return false end + return true end -------------------------------------------------------------------------------- +-- Dumps values in a line-per-value format. +-- For example, {test = {"Testing..."}} becomes: +-- _["test"] = {} +-- _["test"][1] = "Testing..." +-- This handles tables as keys and circular references properly. +-- It also handles multiple references well, writing the table only once. +-- The dumped argument is internal-only. + function dump2(o, name, dumped) name = name or "_" + -- "dumped" is used to keep track of serialized tables to handle + -- multiple references and circular tables properly. + -- It only contains tables as keys. The value is the name that + -- the table has in the dump, eg: + -- {x = {"y"}} -> dumped[{"y"}] = '_["x"]' dumped = dumped or {} - io.write(name, " = ") - if type(o) == "number" or type(o) == "string" or type(o) == "boolean" - or type(o) == "function" or type(o) == "nil" - or type(o) == "userdata" then - io.write(basic_dump2(o), "\n") - elseif type(o) == "table" then - if dumped[o] then - io.write(dumped[o], "\n") - else - dumped[o] = name - io.write("{}\n") -- new table - for k,v in pairs(o) do - local fieldname = string.format("%s[%s]", name, basic_dump2(k)) - dump2(v, fieldname, dumped) + if type(o) ~= "table" then + return string.format("%s = %s\n", name, basic_dump(o)) + end + if dumped[o] then + return string.format("%s = %s\n", name, dumped[o]) + end + dumped[o] = name + -- This contains a list of strings to be concatenated later (because + -- Lua is slow at individual concatenation). + local t = {} + for k, v in pairs(o) do + local keyStr + if type(k) == "table" then + if dumped[k] then + keyStr = dumped[k] + else + -- Key tables don't have a name, so use one of + -- the form _G["table: 0xFFFFFFF"] + keyStr = string.format("_G[%q]", tostring(k)) + -- Dump key table + table.insert(t, dump2(k, keyStr, dumped)) end + else + keyStr = basic_dump(k) end - else - error("cannot dump a " .. type(o)) - return nil + local vname = string.format("%s[%s]", name, keyStr) + table.insert(t, dump2(v, vname, dumped)) end + return string.format("%s = {}\n%s", name, table.concat(t)) end -------------------------------------------------------------------------------- -function dump(o, dumped) - dumped = dumped or {} - if type(o) == "number" then - return tostring(o) - elseif type(o) == "string" then - return string.format("%q", o) - elseif type(o) == "table" then - if dumped[o] then - return "" - end - dumped[o] = true - local t = {} - for k,v in pairs(o) do - t[#t+1] = "[" .. dump(k, dumped) .. "] = " .. dump(v, dumped) +-- This dumps values in a one-statement format. +-- For example, {test = {"Testing..."}} becomes: +-- [[{ +-- test = { +-- "Testing..." +-- } +-- }]] +-- This supports tables as keys, but not circular references. +-- It performs poorly with multiple references as it writes out the full +-- table each time. +-- The indent field specifies a indentation string, it defaults to a tab. +-- Use the empty string to disable indentation. +-- The dumped and level arguments are internal-only. + +function dump(o, indent, nested, level) + if type(o) ~= "table" then + return basic_dump(o) + end + -- Contains table -> true/nil of currently nested tables + nested = nested or {} + if nested[o] then + return "" + end + nested[o] = true + indent = indent or "\t" + level = level or 1 + local t = {} + local dumped_indexes = {} + for i, v in ipairs(o) do + table.insert(t, dump(v, indent, nested, level + 1)) + dumped_indexes[i] = true + end + for k, v in pairs(o) do + if not dumped_indexes[k] then + if type(k) ~= "string" or not is_valid_identifier(k) then + k = "["..dump(k, indent, nested, level + 1).."]" + end + v = dump(v, indent, nested, level + 1) + table.insert(t, k.." = "..v) end - return "{" .. table.concat(t, ", ") .. "}" - elseif type(o) == "boolean" then - return tostring(o) - elseif type(o) == "function" then - return "" - elseif type(o) == "userdata" then - return "" - elseif type(o) == "nil" then - return "nil" - else - error("cannot dump a " .. type(o)) - return nil end + nested[o] = nil + if indent ~= "" then + local indent_str = "\n"..string.rep(indent, level) + local end_indent_str = "\n"..string.rep(indent, level - 1) + return string.format("{%s%s%s}", + indent_str, + table.concat(t, ","..indent_str), + end_indent_str) + end + return "{"..table.concat(t, ", ").."}" end -------------------------------------------------------------------------------- -function string:split(sep) - local sep, fields = sep or ",", {} - local pattern = string.format("([^%s]+)", sep) - self:gsub(pattern, function(c) fields[#fields+1] = c end) - return fields +-- Localize functions to avoid table lookups (better performance). +local table_insert = table.insert +local str_sub, str_find = string.sub, string.find +function string.split(str, delim, include_empty, max_splits, sep_is_pattern) + delim = delim or "," + max_splits = max_splits or -1 + local items = {} + local pos, len, seplen = 1, #str, #delim + local plain = not sep_is_pattern + max_splits = max_splits + 1 + repeat + local np, npe = str_find(str, delim, pos, plain) + np, npe = (np or (len+1)), (npe or (len+1)) + if (not np) or (max_splits == 1) then + np = len + 1 + npe = np + end + local s = str_sub(str, pos, np - 1) + if include_empty or (s ~= "") then + max_splits = max_splits - 1 + table_insert(items, s) + end + pos = npe + 1 + until (max_splits == 0) or (pos > (len + 1)) + return items end -------------------------------------------------------------------------------- @@ -115,61 +212,72 @@ function math.hypot(x, y) return x * math.sqrt(1 + t * t) end +-------------------------------------------------------------------------------- +function math.sign(x, tolerance) + tolerance = tolerance or 0 + if x > tolerance then + return 1 + elseif x < -tolerance then + return -1 + end + return 0 +end + -------------------------------------------------------------------------------- function get_last_folder(text,count) local parts = text:split(DIR_DELIM) - + if count == nil then return parts[#parts] end - + local retval = "" for i=1,count,1 do retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM end - + return retval end -------------------------------------------------------------------------------- function cleanup_path(temppath) - + local parts = temppath:split("-") - temppath = "" + temppath = "" for i=1,#parts,1 do if temppath ~= "" then temppath = temppath .. "_" end temppath = temppath .. parts[i] end - + parts = temppath:split(".") - temppath = "" + temppath = "" for i=1,#parts,1 do if temppath ~= "" then temppath = temppath .. "_" end temppath = temppath .. parts[i] end - + parts = temppath:split("'") - temppath = "" + temppath = "" for i=1,#parts,1 do if temppath ~= "" then temppath = temppath .. "" end temppath = temppath .. parts[i] end - + parts = temppath:split(" ") - temppath = "" + temppath = "" for i=1,#parts,1 do if temppath ~= "" then temppath = temppath end temppath = temppath .. parts[i] end - + return temppath end @@ -189,7 +297,7 @@ function core.splittext(text,charlimit) local retval = {} local current_idx = 1 - + local start,stop = string.find(text," ",current_idx) local nl_start,nl_stop = string.find(text,"\n",current_idx) local gotnewline = false @@ -204,30 +312,30 @@ function core.splittext(text,charlimit) table.insert(retval,last_line) last_line = "" end - + if last_line ~= "" then last_line = last_line .. " " end - + last_line = last_line .. string.sub(text,current_idx,stop -1) - + if gotnewline then table.insert(retval,last_line) last_line = "" gotnewline = false end current_idx = stop+1 - + start,stop = string.find(text," ",current_idx) nl_start,nl_stop = string.find(text,"\n",current_idx) - + if nl_start ~= nil and (start == nil or nl_start < start) then start = nl_start stop = nl_stop gotnewline = true end end - + --add last part of text if string.len(last_line) + (string.len(text) - current_idx) > charlimit then table.insert(retval,last_line) @@ -236,7 +344,7 @@ function core.splittext(text,charlimit) last_line = last_line .. " " .. string.sub(text,current_idx) table.insert(retval,last_line) end - + return retval end @@ -355,7 +463,8 @@ function core.explode_table_event(evt) local t = parts[1]:trim() local r = tonumber(parts[2]:trim()) local c = tonumber(parts[3]:trim()) - if type(r) == "number" and type(c) == "number" and t ~= "INV" then + if type(r) == "number" and type(c) == "number" + and t ~= "INV" then return {type=t, row=r, column=c} end end @@ -378,8 +487,68 @@ function core.explode_textlist_event(evt) return {type="INV", index=0} end -function core.pos_to_string(pos) - return "(" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")" +-------------------------------------------------------------------------------- +function core.explode_scrollbar_event(evt) + local retval = core.explode_textlist_event(evt) + + retval.value = retval.index + retval.index = nil + + return retval +end + +-------------------------------------------------------------------------------- +function core.pos_to_string(pos, decimal_places) + local x = pos.x + local y = pos.y + local z = pos.z + if decimal_places ~= nil then + x = string.format("%." .. decimal_places .. "f", x) + y = string.format("%." .. decimal_places .. "f", y) + z = string.format("%." .. decimal_places .. "f", z) + end + return "(" .. x .. "," .. y .. "," .. z .. ")" +end + +-------------------------------------------------------------------------------- +function core.string_to_pos(value) + if value == nil then + return nil + end + + local p = {} + p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + local p = {} + p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + return nil +end + +assert(core.string_to_pos("10.0, 5, -2").x == 10) +assert(core.string_to_pos("( 10.0, 5, -2)").z == -2) +assert(core.string_to_pos("asd, 5, -2)") == nil) + +-------------------------------------------------------------------------------- +function table.copy(t, seen) + local n = {} + seen = seen or {} + seen[t] = n + for k, v in pairs(t) do + n[type(k) ~= "table" and k or seen[k] or table.copy(k, seen)] = + type(v) ~= "table" and v or seen[v] or table.copy(v, seen) + end + return n end -------------------------------------------------------------------------------- @@ -388,29 +557,31 @@ end if INIT == "mainmenu" then function core.get_game(index) local games = game.get_games() - + if index > 0 and index <= #games then return games[index] end - + return nil end - + function fgettext(text, ...) text = core.gettext(text) local arg = {n=select('#', ...), ...} if arg.n >= 1 then -- Insert positional parameters ($1, $2, ...) - result = '' - pos = 1 + local result = '' + local pos = 1 while pos <= text:len() do - newpos = text:find('[$]', pos) + local newpos = text:find('[$]', pos) if newpos == nil then result = result .. text:sub(pos) pos = text:len() + 1 else - paramindex = tonumber(text:sub(newpos+1, newpos+1)) - result = result .. text:sub(pos, newpos-1) .. tostring(arg[paramindex]) + local paramindex = + tonumber(text:sub(newpos+1, newpos+1)) + result = result .. text:sub(pos, newpos-1) + .. tostring(arg[paramindex]) pos = newpos + 2 end end