-- Basic functionality that should be standard in Lua -- Table / List -- Whether a table contains no keys function table.empty(t) return next(t) == nil end -- Apply a function to each key in a table function table.map(f, ...) local ts = { ... } local u = {} for k, _ in pairs(ts[1]) do local args = {} for j = 1, #ts do args[j] = ts[j][k] end u[k] = f(unpack(args)) end return u end function table.mapk(f, ...) local ts = { ... } local u = {} for k, _ in pairs(ts[1]) do local args = {} for j = 1, #ts do args[j] = ts[j][k] end u[k] = f(k, unpack(args)) end return u end function table.map_list(f, ...) local ts = { ... } local u = {} for i = 1, #ts[1] do local args = {} for j = 1, #ts do args[j] = ts[j][i] end u[i] = f(unpack(args)) end return u end function table.mapi_list(f, ...) local ts = { ... } local u = {} for i = 1, #ts[1] do local args = {} for j = 1, #ts do args[j] = ts[j][i] end u[i] = f(i, unpack(args)) end return u end -- Swap keys/values function table.swap(t) local u = {} for k, v in pairs(t) do u[v] = k end return u end -- Reverse a list function table.reverse(l) local m = {} for i = 1, #l do m[#l - i + 1] = l[i] end return m end -- Whether a table is a list/array (has only monotonic integer keys) function table.islist(t) local n = 0 for i, _ in pairs(t) do if type(i) ~= 'number' or i % 1 ~= 0 then return false end n = n + 1 end return n == #t end -- Append contents of other tables to first table function table.append(t, ...) local a = { ... } for _, u in ipairs(a) do for _, v in ipairs(u) do table.insert(t, v) end end return t end -- Create a new table containing all keys from any number of tables -- latter tables in the arg list override prior ones -- overlaps, NOT appends, integer keys function table.join(...) local ts = { ... } local w = {} for _, t in ipairs(ts) do for k, v in pairs(t) do w[k] = v end end return w end -- Whether a table contains a certain value in any key function table.contains(t, s) for _, v in pairs(t) do if v == s then return true end end return false end function table.contains_list(t, s) for _, v in ipairs(t) do if v == s then return true end end return false end -- Copy a table to another table function table.copy(t) local u = {} for k, v in pairs(t) do u[k] = v end return u end function table.copy_list(l) local m = {} for i, v in ipairs(l) do m[i] = v end return m end -- Sort a table in a new copy function table.sortcopy(t, f) local u = table.copy_list(t) table.sort(u, f) return u end -- Remove a value from a table function table.removevalue(t, r) local rem = {} for k, v in pairs(t) do if v == r then table.insert(rem, k) end end for _, k in ipairs(rem) do t[k] = nil end end function table.removevalue_list(t, r) for i = #t, 1, -1 do if t[i] == r then table.remove(t, i) end end end -- Export tables into formatted executable strings local function tabs(tabLevel) return (' '):rep(tabLevel) end local valueToString local function tableToString(t, tabLevel, seen) if type(t) ~= 'table' or (getmetatable(t) and getmetatable(t).__tostring) then return tostring(t) elseif table.islist(t) then if #t == 0 then return '{}' else local strs = {} local containsTables = false for _, v in ipairs(t) do if type(v) == 'table' then containsTables = true end table.insert(strs, valueToString(v, tabLevel + 1, seen) .. ',') end if containsTables or #t > 3 then return '{\n' .. tabs(tabLevel + 1) .. table.concat(strs, '\n' .. tabs(tabLevel + 1)) .. '\n' .. tabs(tabLevel) .. '}' else return '{ ' .. table.concat(strs, ' ') .. ' }' end end else local containsNonStringKeys = false for k, v in pairs(t) do if type(k) ~= 'string' or k:find('[^a-zA-Z0-9_]') then containsNonStringKeys = true elseif type(k) == 'table' then error('table.tostring: table contains a table as key, cannot serialize') end end local strs = {} if containsNonStringKeys then for k, v in pairs(t) do table.insert(strs, '\n' .. tabs(tabLevel + 1) .. '[' .. valueToString(k, tabLevel + 1, seen) .. '] = ' .. valueToString(v, tabLevel + 1, seen) .. ',') end else for k, v in pairs(t) do table.insert(strs, '\n' .. tabs(tabLevel + 1) .. k .. ' = ' .. valueToString(v, tabLevel + 1, seen) .. ',') end end return '{' .. table.concat(strs) .. '\n' .. tabs(tabLevel) .. '}' end end valueToString = function(v, tabLevel, seen) local t = type(v) if t == 'table' then if seen[v] then return 'nil --[[ already seen: ' .. tostring(v) .. ' ]]' else seen[v] = true return tableToString(v, tabLevel, seen) end elseif t == 'string' then return '\'' .. string.escape(v) .. '\'' elseif t == 'number' or t == 'boolean' then return tostring(v) else --error('table.tostring: table contains a '..t..' value, cannot serialize') return 'nil --[[ ' .. tostring(v) .. ' ]]' end end function table.tostring(t) return tableToString(t, 0, {}) end -- String -- Split string into table by separator -- or by chars if no separator given -- if regex is not true, sep is treated as a regex pattern function string.split(str, sep, noregex) if type(str) ~= 'string' then error('string.split: argument #1: expected string, got ' .. type(str), 2) end if sep == nil or sep == '' then local t = {} local ns = #str for x = 1, ns do table.insert(t, str:sub(x, x)) end return t elseif type(sep) == 'string' then local t = {} if #str > 0 then local first = 1 while true do local last, newfirst = str:find(sep, first, noregex) if not last then break end table.insert(t, str:sub(first, last - 1)) first = newfirst + 1 end table.insert(t, str:sub(first, #str)) end return t else error( 'string.split: argument #2: expected string or nil, got ' .. type(sep), 2) end end -- Split string to a list of char bytes function string.bytes(s) local b = {} for i = 1, #s do local c = s:sub(i, i) table.insert(b, c:byte()) end return b end -- Trim leading and trailing whitespace function string.trim(s, ws) ws = ws or ' \t\r\n' return s:gsub('^[' .. ws .. ']+', ''):gsub('[' .. ws .. ']+$', '') .. '' end -- String slicing and searching using [] operator local str_meta = getmetatable('') local str_meta_index_old = str_meta.__index function str_meta.__index(s, k) if type(k) == 'string' then return str_meta_index_old[k] elseif type(k) == 'number' then if k < 0 then k = #s + k + 1 end return string.sub(s, k, k) elseif type(k) == 'table' then local a = k[1] < 0 and (#s + k[1] + 1) or k[1] local b = k[2] < 0 and (#s + k[2] + 1) or k[2] return string.sub(s, a, b) end end -- String iterator function string.chars(s) local i = 0 return function() i = i + 1 if i <= #s then return s:sub(i, i) else return nil end end end -- Escape sequences local defaultEscapes = { ['\\'] = '\\\\', ['\''] = '\\\'', ['\"'] = '\\\"', ['\t'] = '\\t', ['\r'] = '\\r', ['\n'] = '\\n', ['\0'] = '\\0', } function string.escape(s, escapes) escapes = escapes or defaultEscapes local t = {} for i = 1, #s do local c = s:sub(i, i) table.insert(t, escapes[c] or c) end return table.concat(t) end local defaultEscapeChar = '\\' local defaultUnescapes = { ['\\'] = '\\', ['\''] = '\'', ['\"'] = '\"', ['t'] = '\t', ['r'] = '\r', ['n'] = '\n', ['0'] = '\0', } function string.unescape(s, escapeChar, unescapes) escapeChar = escapeChar or defaultEscapeChar unescapes = unescapes or defaultUnescapes local t = {} local inEscape = false for i = 1, #s do local c = s:sub(i, i) if inEscape then table.insert(t, unescapes[c] or error('string.unescape: invalid escape sequence: \'' .. escapeChar .. c .. '\'')) elseif c == escapeChar then inEscape = true else table.insert(t, c) end end return table.concat(t) end -- IO io = io or {} -- Read entire file at once, return nil,err if access failed function io.readall(filename) local fi, err = io.open(filename, 'rb') if not fi then return nil, err end local s = fi:read("*a") fi:close() return s end -- Write data to file all at once, return true if success / false,err if failure function io.writeall(filename, data) local fi, err = io.open(filename, 'wb') if not fi then return false, err end fi:write(data) fi:close() return true, nil end -- Math -- Round function math.round(x) return math.floor(x + 0.5) end -- Mod that accounts for floating point inaccuracy function math.mod(a, b) local m = a % b if m == 0 or math.abs(m) < 1e-15 or math.abs(m - b) < 1e-15 then return 0 else return m end end -- Clamp value between min and max function math.clamp(v, n, x) return math.min(x, math.max(v, n)) end print(' Executed std.lua')