170 lines
4.2 KiB
Lua
170 lines
4.2 KiB
Lua
-- Module by 1waffle1 and boatbomber, optimized and fixed by iiau
|
|
-- https://devforum.roblox.com/t/text-compression/163637/37
|
|
|
|
local dictionary = {} -- key to len
|
|
|
|
do -- populate dictionary
|
|
local length = 0
|
|
for i = 32, 127 do
|
|
if i ~= 34 and i ~= 92 then
|
|
local c = string.char(i)
|
|
dictionary[c] = length
|
|
dictionary[length] = c
|
|
length = length + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local escapemap_126, escapemap_127 = {}, {}
|
|
local unescapemap_126, unescapemap_127 = {}, {}
|
|
|
|
local blacklisted_126 = { 34, 92 }
|
|
for i = 126, 180 do
|
|
table.insert(blacklisted_126, i)
|
|
end
|
|
|
|
do -- Populate escape map
|
|
-- represents the numbers 0-31, 34, 92, 126, 127 (36 characters)
|
|
-- and 128-180 (52 characters)
|
|
-- https://devforum.roblox.com/t/text-compression/163637/5
|
|
for i = 0, 31 + #blacklisted_126 do
|
|
local b = blacklisted_126[i - 31]
|
|
local s = i + 32
|
|
|
|
-- Note: 126 and 127 are magic numbers
|
|
local c = string.char(b or i)
|
|
local e = string.char(s + (s >= 34 and 1 or 0) + (s >= 91 and 1 or 0))
|
|
|
|
escapemap_126[c] = e
|
|
unescapemap_126[e] = c
|
|
end
|
|
|
|
for i = 1, 255 - 180 do
|
|
local c = string.char(i + 180)
|
|
local s = i + 34
|
|
local e = string.char(s + (s >= 92 and 1 or 0))
|
|
|
|
escapemap_127[c] = e
|
|
unescapemap_127[e] = c
|
|
end
|
|
end
|
|
|
|
local function escape(s)
|
|
-- escape the control characters 0-31, double quote 34, backslash 92, Tilde 126, and DEL 127 (36 chars)
|
|
-- escape characters 128-180 (53 chars)
|
|
return string.gsub(string.gsub(s, '[%c"\\\126-\180]', function(c)
|
|
return "\126" .. escapemap_126[c]
|
|
end), '[\181-\255]', function(c)
|
|
return "\127" .. escapemap_127[c]
|
|
end)
|
|
end
|
|
local function unescape(s)
|
|
return string.gsub(string.gsub(s, "\127(.)", function(e)
|
|
return unescapemap_127[e]
|
|
end), "\126(.)", function(e)
|
|
return unescapemap_126[e]
|
|
end)
|
|
end
|
|
|
|
local b93Cache = {}
|
|
local function tobase93(n)
|
|
local value = b93Cache[n]
|
|
if value then
|
|
return value
|
|
end
|
|
|
|
local c = n
|
|
value = ""
|
|
repeat
|
|
local remainder = n % 93
|
|
value = dictionary[remainder] .. value
|
|
n = (n - remainder) / 93
|
|
until n == 0
|
|
|
|
b93Cache[c] = value
|
|
return value
|
|
end
|
|
|
|
local b10Cache = {}
|
|
local function tobase10(value)
|
|
local n = b10Cache[value]
|
|
if n then
|
|
return n
|
|
end
|
|
|
|
n = 0
|
|
for i = 1, #value do
|
|
n = n + math.pow(93, i - 1) * dictionary[string.sub(value, -i, -i)]
|
|
end
|
|
|
|
b10Cache[value] = n
|
|
return n
|
|
end
|
|
|
|
local function compress(text)
|
|
assert(type(text) == "string", "bad argument #1 to 'compress' (string expected, got " .. typeof(text) .. ")")
|
|
local dictionaryCopy = table.clone(dictionary)
|
|
local key, sequence, size = "", {}, #dictionary
|
|
|
|
local width, spans, span = 1, {}, 0
|
|
local function listkey(k)
|
|
local value = tobase93(dictionaryCopy[k])
|
|
local valueLength = #value
|
|
if valueLength > width then
|
|
width, span, spans[width] = valueLength, 0, span
|
|
end
|
|
table.insert(sequence, string.rep(" ", width - valueLength) .. value)
|
|
span += 1
|
|
end
|
|
text = escape(text)
|
|
for i = 1, #text do
|
|
local c = string.sub(text, i, i)
|
|
local new = key .. c
|
|
if dictionaryCopy[new] then
|
|
key = new
|
|
else
|
|
listkey(key)
|
|
key = c
|
|
size += 1
|
|
dictionaryCopy[new] = size
|
|
dictionaryCopy[size] = new
|
|
end
|
|
end
|
|
listkey(key)
|
|
spans[width] = span
|
|
return table.concat(spans, ",") .. "|" .. table.concat(sequence)
|
|
end
|
|
|
|
local function decompress(text)
|
|
assert(type(text) == "string", "bad argument #1 to 'decompress' (string expected, got " .. typeof(text) .. ")")
|
|
local dictionaryCopy = table.clone(dictionary)
|
|
local sequence, spans, content = {}, string.match(text, "(.-)|(.*)")
|
|
local groups, start = {}, 1
|
|
for span in string.gmatch(spans, "%d+") do
|
|
local width = #groups + 1
|
|
groups[width] = string.sub(content, start, start + span * width - 1)
|
|
start = start + span * width
|
|
end
|
|
local previous
|
|
|
|
for width, group in ipairs(groups) do
|
|
for value in string.gmatch(group, string.rep(".", width)) do
|
|
local entry = dictionaryCopy[tobase10(value)]
|
|
if previous then
|
|
if entry then
|
|
table.insert(dictionaryCopy, previous .. string.sub(entry, 1, 1))
|
|
else
|
|
entry = previous .. string.sub(previous, 1, 1)
|
|
table.insert(dictionaryCopy, entry)
|
|
end
|
|
table.insert(sequence, entry)
|
|
else
|
|
sequence[1] = entry
|
|
end
|
|
previous = entry
|
|
end
|
|
end
|
|
return unescape(table.concat(sequence))
|
|
end
|
|
|
|
return { compress = compress, decompress = decompress } |