Module:Grind/util
From Fallen London Wiki (Staging)
Documentation for this module may be created at Module:Grind/util/doc
local p = {}
-- == Conversion utilities ==
-- Float to string conversion.
function p.f2s(f)
if f == f + 1 then
-- either inf or -inf
return '' .. f
end
if ('' .. f):find('%.') == nil then
-- an integer
return '' .. f
end
if math.abs(f) >= 1 then
return string.format('%.2f', f)
elseif math.abs(f) >= 0.01 then
return string.format('%.4f', f)
else
return string.format('%.8f', f)
end
end
-- String to boolean conversion.
-- Recognised boolean strings:
-- * yes/no
-- * true/false
-- * on/off
-- * 1/0
function p.s2b(s, fallback)
fallback = fallback or false
if type(s) ~= 'string' then
return fallback
end
local yes = {'yes', 'true', 'on', '1'}
local no = {'no', 'false', 'off', '0'}
for _, v in ipairs(yes) do
if s:lower() == v then
return true
end
end
for _, v in ipairs(no) do
if s:lower() == v then
return false
end
end
return fallback
end
-- Boolean to number conversion.
function p.b2n(b)
return b and 1 or 0
end
-- == General parsing utilities ==
-- Returns the next position and the next character.
function p.advance(s, pos)
return pos + 1, s:sub(pos + 1, pos + 1)
end
-- Matching token finder.
-- Supported token pairs:
-- * ()
-- * ""
-- Escape sequences in strings are taken into account.
-- `initial_pos` is the location of the starting token.
-- Location of the matching token is returned.
function p.find_match(s, initial_pos)
local pos = initial_pos
local context = {s:sub(pos, pos)}
local last_escape = nil
while #context > 0 do
pos = pos + 1
if pos > #s then
return nil
end
local last = context[#context]
local current_char = s:sub(pos, pos)
if current_char == '\\' and last_escape ~= pos - 1 then
last_escape = pos
end
if current_char == '"' then
if last == '"' and last_escape ~= pos - 1 then
table.remove(context)
elseif last ~= '"' then
table.insert(context, '"')
end
end
if last == '(' and current_char == '(' then
table.insert(context, '(')
end
if last == '(' and current_char == ')' then
table.remove(context)
end
end
return pos
end
-- Whitespace stripper.
-- Starting and ending whitespace characters are removed.
function p.strip_whitespaces(str)
local s = '' .. str
while s:sub(1, 1):match('%s') do
s = s:sub(2)
end
while s:sub(-1, -1):match('%s') do
s = s:sub(1, -2)
end
return s
end
-- Whitespace skipping.
-- Finds the first non-whitespace character starting with `initial_pos`.
-- Its position is returned.
-- `#s + 1` is returned if no such character is found.
function p.skip_whitespaces(s, initial_pos)
local pos = initial_pos
while s:sub(pos, pos):match('%s') do
pos = pos + 1
end
return pos
end
-- Range parsing.
-- The following formats are supported:
-- * `X` -> exact value (for non-negative X);
-- * `X-Y` -> exact range (X, Y may be negative);
-- * `X-` -> range from X to positive infinity;
-- * `-Y` -> range from 0 to Y (for non-negative Y);
-- * `-` -> range from 0 to positive infinity.
-- Returns a tuple of range boundaries.
-- Returns nil, nil is parsing fails.
function p.parse_range(range)
if type(range) ~= 'string' then
return nil, nil
end
local _, n = range:gsub('%-', '')
if n == 0 then
-- exact value
local exact = tonumber(range)
return exact, exact
elseif n == 1 then
-- non-negative range; could be a negative number, but this case is not supported
local min, max = range:gmatch('(%d*)%-(%d*)')()
if min ~= nil and max ~= nil then
min = tonumber(min) or 0
max = tonumber(max) or 1/0
return min, max
end
return nil, nil
elseif n == 2 then
-- one number is negative; assuming the first one is
local min, max = range:gmatch('%-(%d+)%-(%d*)')()
if min ~= nil and max ~= nil then
min = -tonumber(min)
max = tonumber(max) or 1/0
return min, max
end
return nil, nil
elseif n == 3 then
-- both numbers are negative
local min, max = range:gmatch('%-(%d+)%-%-(%d+)')()
if min ~= nil and max ~= nil then
min = -tonumber(min)
max = -tonumber(max)
return min, max
end
return nil, nil
else
-- ???
return nil, nil
end
end
-- == Misc. ==
-- Checks if two objects are equal.
-- Objects are considered to be equal iff
-- * they are non-table structures that are equal
-- OR all of the following conditions are true:
-- * they are both tables;
-- * values corresponding to the keys defined in t1 and t2 are equal.
function p.tables_equal(t1, t2)
if type(t1) ~= type(t2) then
return false
end
if type(t1) ~= 'table' then
return t1 == t2
end
local keys = {}
for k, _ in pairs(t1) do
keys[k] = true
end
for k, _ in pairs(t2) do
keys[k] = true
end
for k, _ in pairs(keys) do
if not p.tables_equal(t1[k], t2[k]) then
return false
end
end
return true
end
-- Multiplies a range by a constant.
function p.multiply_range(range, m)
local rmin, rmax = p.parse_range(range)
if rmin == nil or rmax == nil then
return range
end
if rmax ~= rmax + 1 then
return (rmin * m) .. '-' .. (rmax * m)
else
return (rmin * m) .. '-'
end
end
function p.normalise_input(name)
if name:sub(-5) == ':GRON' then
return name:sub(1, -6), 'gron'
end
if name:sub(-8) == ':Boolean' then
return name:sub(1, -9), 'boolean'
end
if name:sub(-7) == ':Number' then
return name:sub(1, -8), 'number'
end
if name:sub(-7) == ':String' then
return name:sub(1, -8), 'string'
end
return name, 'unknown'
end
function p.input_suffix(itype)
if itype == 'gron' then
return ':GRON'
elseif itype == 'boolean' then
return ':Boolean'
elseif itype == 'number' then
return ':Number'
elseif itype == 'string' then
return ':String'
else
return ''
end
end
-- Packs the effect and the action cost into a table.
function p.complete_effect(a, effect)
return {
a=a,
effect=effect
}
end
return p