Skip to content

Instantly share code, notes, and snippets.

@tocuto
Last active January 18, 2019 14:52
Show Gist options
  • Save tocuto/a369074324f8b86c6f833fbf6f4e5113 to your computer and use it in GitHub Desktop.
Save tocuto/a369074324f8b86c6f833fbf6f4e5113 to your computer and use it in GitHub Desktop.
STable: Turns tables to string and viceversa
--[[
STable - By Tocutoeltuco
Allows:
- String indexes
- Numeric indexes
- String values
- Numeric values
- Boolean values
- Table values (backreference/circular tables allowed! (_G._G = _G))
tableToString( tbl )
-- Parameters:
-- -- tbl (table) (required)
-- Returns:
-- -- string (encoded table)
stringToTable( str )
-- Parameters:
-- -- str (string) (required)
-- Returns:
-- -- table (decoded string)
]]
do
-- [[ Optimizations ]]
-- These variables are here to make lua just load one variable, instead of loading 2
-- I know, it won't make the script a lot times faster but on big tables (en/de)coding
-- this will make it faster than if we call string.char instead of string_char
local string_char = string.char
local string_byte = string.byte
local string_sub = string.sub
local string_gsub = string.gsub
local string_gmatch = string.gmatch
local bitwise_module = bit or bit32
local bitwise_and = bitwise_module.band
local bitwise_rshift = bitwise_module.rshift
local bitwise_lshift = bitwise_module.lshift
-- [[ Optimizations ]]
-- [[ Private functions ]]
-- These functions are here to make some repeated tasks, such as turning a number into bytes
local function intToBytes(int) -- Turns a number into bytes
local bytes = string_char(bitwise_and(int, 255))
-- Bitwise AND with 255 so we get just the last byte of the number
while bitwise_rshift(int, 8) > 0 do -- While there are unread bytes
int = bitwise_rshift(int, 8) -- Delete the last one
bytes = string_char(bitwise_and(int, 255)) .. bytes -- Add the next to the start of the string
end
return bytes
end
local function bytesToInt(bytes) -- Turns bytes into numbers
local result = 0
local bytesLength = #bytes
for index = 1, bytesLength do -- For every byte
result = result + bitwise_lshift(string_byte(string_sub(bytes, index, index)), (bytesLength - index) * 8)
-- string_byte(string_sub(bytes, index, index)): We get the current byte
-- We do the bitwise lshift operation (bytesLength - index) bytes so we are sure
-- that we are adding the byte value depending on its position.
-- Example:
-- -- 0xFF00 != 0xFF
end
return result
end
local function intToLength(int)
-- This function is here because the intToBytes will NEVER return the same quantity of bytes
-- So we are sure that we can read a byte and we know the intToBytes length
local bytes = intToBytes(int)
return string_char(#bytes) .. bytes
end
local function getStringValue(str) -- Gets the value of a string (intToLength(#string) .. string)
local valueLengthSize = string_byte(string_sub(str, 1, 1))
-- Gets the length of the intToBytes
local valueLength = bytesToInt(string_sub(string_sub(str, 2, 1 + valueLengthSize))
-- Gets the length of the string
local value = string_sub(str, 2 + valueLengthSize, 1 + valueLengthSize + valueLength)
-- Gets the string
local str = string_sub(str, 2 + valueLengthSize + valueLength)
-- Deletes this value from the string so the function can be called again and return the next value
return value, str
end
local function getBackreferenceTables(tbl, path, references, backreference)
for index, value in next, tbl do -- Lookup for circular/backreference tables (_G._G = _G)
if type(value) == "table" then
local referenceId = string_gsub(tostring(value), "table: ", "", 1)
-- Gets the table id
local parsedIndex = string_gsub(string_gsub(tostring(index), "&", "&0"), "%.", "&1")
-- Replaces some values of the index so you can name the table index whatever you want
-- and you are sure that it won't break ({["test.abc&"] = true})
local indexType = ({["string"] = "1", ["number"] = "2"})[type(index)]
-- Marks the index type (string/number)
if references[referenceId] then -- Checks if the table has already been defined (same id!)
local newPath = path .. "." .. indexType .. parsedIndex
backreference = backreference .. intToLength(#references[referenceId][1]) .. references[referenceId][1] .. intToLength(#newPath) .. newPath
-- Adds to the backreference variable the original table path and the backreference path
else
local newPath = path .. (#path == 0 and "" or ".") .. indexType .. parsedIndex
references[referenceId] = {newPath, false}
-- Marks the table as already defined
references, backreference = getBackreferenceTables(value, newPath, references, backreference)
-- Calls the function again to check if there is any backreference in the subtable
end
end
end
return references, backreference
end
-- [[ Private functions ]]
-- [[ Public functions ]]
function tableToString(tbl, references)
local _result = ""
local isTheFirstCall = false
local boolTransform = ({[false] = 0, [true] = 1})
local backreference
if references == nil then
-- Checks if it is the first call to the function
isTheFirstCall = true
local tblId = string_gsub(tostring(tbl), "table: ", "", 1)
-- Saves the original table id so we know that if there is any other table with the same id, it is a backreference
references, backreference = getBackreferenceTables(tbl, "", {[tblId] = {"", true}}, "")
-- Checks for backreference tables
end
for index, value in next, tbl do
local indtype, valtype = type(index), type(value)
assert(
indtype == "string" or indtype == "number",
"Unsupported index type at some point in a table: " .. indtype
) -- Checks if the index/value are supported values
assert(
valtype == "string" or valtype == "number" or valtype == "boolean" or valtype == "table",
"Unsupported value type at some point in a table: " .. indtype
)
local result = string_char(
(
{["string"] = 0, ["number"] = 1}
)[indtype]) .. string_char(
(
{["string"] = 0, ["number"] = 1, ["boolean"] = 2, ["table"] = 3}
)[valtype]
) -- Adds the index and the value type to the result string
index = tostring(index) -- Turns the index into string, we need it as string!
result = result .. intToLength(#index) .. index
-- Adds the index to the string so it can be read by getStringValue
if valtype == "string" or valtype == "number" then
-- If the value is a string or a number, we can do the same we did with the index!
value = tostring(value)
result = result .. intToLength(#value) .. value
elseif valtype == "boolean" then
-- If it is a boolean, we turn the value into 1 or 0 with
-- the boolTransform table which is defined at the start of the function
result = result .. intToLength(1) .. boolTransform[value]
elseif valtype == "table" then
-- If it is a table, we check if it was not defined before
local referenceId = string_gsub(tostring(value), "table: ", "", 1)
if not references[referenceId][2] then
-- If it isn't, we mark it as defined so we don't get back to define it!
references[referenceId][2] = true
value = tableToString(value, references)
-- Calls the function again so we turn the table into string
result = result .. intToLength(#value) .. value
-- Adds it to the result string so we can read it by getStringValue
else
-- If it is, we just ignore it, because it is already defined in the backreference variable!
result = ""
end
end
_result = _result .. result
end
if isTheFirstCall then
-- If this is the first call, we have ended all the tableToString transformation!
-- We return the result table, and with a separator (0xFF) we add the backreference variable
-- so they can be added by the stringToTable function later!
return _result .. string_char(255) .. backreference
end
return _result
end
function stringToTable(str)
local result, backreference, typeTransform = {}, false, {["1"] = tostring, ["2"] = tonumber}
while #str > 0 do
local indexType = string_byte(string_sub(str, 1, 1))
-- Gets the first byte. It can be two possible values:
-- -- An index type (<255)
-- -- The separator between the table definition and the backreference definition
if indexType == 255 then
-- If it is the separator, we define that we are in the
-- backreference definition mode and start again the loop
backreference = true
str = string_sub(str, 2)
elseif not backreference then
-- If we are not in the backreference definition mode,
-- we get the value type
local valueType = string_byte(string_sub(str, 2, 2))
str = string_sub(str, 3)
index, str = getStringValue(str)
value, str = getStringValue(str)
-- We read the index and the value from the string
if indexType == 1 then
-- If the index is a number, we turn it into a number!
index = tonumber(index)
end
if valueType == 1 then
-- If the value is a number, we turn it into a number!
value = tonumber(value)
elseif valueType == 2 then
-- If the value is a boolean, we turn it into a boolean!
value = value == "1"
elseif valueType == 3 then
-- If the value is a table, we call again the function
-- to turn the value into a string
value = stringToTable(value)
end
result[index] = value
-- And we set the table index to that value
else -- Otherwise, if we are in the backreference definition mode
indexReference, str = getStringValue(str)
copy, str = getStringValue(str)
-- We read where is the backreference definition
-- and were we need to copy it!
local reference = result
if indexReference ~= "" then
for piece in string_gmatch(indexReference, "[^%.]+") do
-- We split the string into pieces (splitted by dots)
local parsedIndex = string_gsub(string_gsub(string_sub(piece, 2), "&1", "."), "&0", "&")
-- We decode the string so you can call your indexes whatever you want ({["test.abc&"] = true})
reference = reference[typeTransform[string_sub(piece, 1, 1)](parsedIndex)]
-- And we navigate to that index
end
end
local copyPath, copyIn = result, ""
if copy ~= "" then
local path, index = {}, 0
for piece in string_gmatch(copy, "[^%.]+") do
index = index + 1
path[index] = piece
-- We split the string into pieces (splitted by dots)
end
for index = 1, #path - 1 do
if path[index] ~= "" then
local parsedIndex = string_gsub(string_gsub(string_sub(path[index], 2), "&1", "."), "&0", "&")
-- We decode the string
copyPath = copyPath[typeTransform[string_sub(path[index], 1, 1)](parsedIndex)]
-- And we navigate to that index
end
end
local parsedIndex = string_gsub(string_gsub(string_sub(path[#path], 2), "&1", "."), "&0", "&")
-- We decode the string
copyIn = typeTransform[string_sub(path[#path], 1, 1)](parsedIndex)
-- And we get the index to copy in
end
copyPath[copyIn] = reference
-- And we copy!
end
end
return result
end
-- [[ Public functions ]]
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment