Last active
January 18, 2019 14:52
-
-
Save tocuto/a369074324f8b86c6f833fbf6f4e5113 to your computer and use it in GitHub Desktop.
STable: Turns tables to string and viceversa
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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