Log In  

Hi everyone,

My new project uses massive tables of graphics metadata, so I've written a new table serializer inspired by this pull request on @BenWiley4000's pico8-table-string to get the job done. Hopefully someone else finds this useful, too!

It uses less characters to store your table as a string than pico8-table-string does (to take up less character / compressed space), but at the cost of 14 more tokens to deserialize, and possibly with less reliability. (It will break if the table contains a string a certain character sequence, see below.) You should also run the output through something like Zep's escape_binary_string before saving to code.

Supported:

  • string/number/boolean values
  • key/value pairs
  • consecutive indexed values starting from 1

Not supported:

  • 0-indexed values
  • non-consecutive indexed values
  • strings containing the ascii sequence \3\120\23

Writing parsers is not one of my strengths, so pull requests on the GitHub repo are open and appreciated!

serialize

---serialize lua table to string
--@param {table} tbl
--  input table
--
--@returns {string}
--  serialized table
function tbl_serialize(tbl)
    local function encode_value(value)
        --ascii unit separator
        --delimits number
        local value_token = "\31"

        --ascii acknowledge
        --delimits true boolean
        local bool_true = "\6"

        --ascii negative acknowledge
        --delimits false boolean
        local bool_false = "\21"

        --ascii start of text
        --delimits start of string
        local str_token = "\2"

        --end of string sequence
        --ascii end of text + x
        --ascii end of transmission block
        local str_end_token = "\3x\23"

        --asci group separator
        --delimits start of table
        local tbl_token = "\29"

        if type(value) == "boolean" then
            return value and bool_true or bool_false

        elseif type(value) == "string" then
            --check for
            --ascii control chars
            for i = 1, #value do
                if ord(sub(value, i, i)) < 32 then
                    --delimit binary string
                    return str_token .. value .. str_end_token
                end
            end

            --encode regular string
            return value_token .. value

        elseif type(value) == "table" then
            return tbl_token .. tbl_serialize(value)
        end

        return value_token .. value
    end

    local str = ""

    --ascii record separator
    --delimits table key
    local key_token = "\30"

    --ascii end of medium
    --delimits end of table
    local tbl_end_token = "\25"

    --get indexed values
    for _, v in ipairs(tbl) do
        str = str .. encode_value(v)
    end

    --get keyed values
    for k, v in pairs(tbl) do
        if type(k) ~= "number" then
            str = str .. key_token .. k .. encode_value(v)
        end
    end

    str = str .. tbl_end_token

    return str
end

---validate output
--@usage
--  output = tbl_serialize(my_table)
--
--  tbl_serialize_validate(
--      my_table,
--      tbl_deserialize(output)
--  )
--
--@param {table} tbl1
--  input table
--
--@param {table} tbl2
--  output table
function tbl_serialize_validate(tbl1, tbl2)
    for k, v in pairs(tbl1) do
        if type(v) == "table" then
            tbl_serialize_validate(tbl2[k], v)
        else
            assert(
                tbl2[k] == v,
                tostr(k) .. ": " .. tostr(v) .. ": " .. tostr(tbl2[k])
            )
        end
    end
end

deserialize

---deserialize table
--@param {string} str
--  serialized table string
--
--@returns {table}
--  deserialized table
function tbl_deserialize(str)
    ---get encoded value
    --@param {string} str
    --  serialized table string
    --
    --@param {integer} i
    --  position of
    --  current delimiter
    --  in serialized table string
    local function get_value(str, i)
        local
            char,
            i_plus1
            =
            sub(str, i, i),
            i + 1

        --table
        if char == "\29" then
            local
                tbl,
                j
                =
                tbl_deserialize(
                    sub(str, i_plus1)
                )

            return tbl, i + j

        --binary string
        elseif char == "\2" then
            for j = i_plus1, #str do
                if sub(str, j, j + 2) == "\3x\23" then
                    return sub(str, i_plus1, j - 1), j + 2
                end
            end

        --bool true
        elseif char == "\6" then
            return true, i

        --bool false
        elseif char == "\21" then
            return false, i
        end

        --number, string,
        --or table key
        --("\30" or "\31")
        for j = i_plus1, #str do
            if ord(sub(str, j, j)) < 32 then
                local value = sub(str, i_plus1, j - 1)

                return tonum(value) or value, j - 1
            end
        end
    end

    local
        tbl,
        i
        =
        {},
        1

    --loop start
    ::parse::

    local char = sub(str, i, i)

    --end of table
    if char == "\25" then
        return tbl, i

    --table key
    elseif char == "\30" then
        local key, j = get_value(str, i)
        local value, k = get_value(str, j + 1)

        tbl[key] = value

        i = k + 1

    --value
    elseif ord(char) < 32 then
        local value, j = get_value(str, i)

        add(tbl, value)

        i = j + 1
    end

    --loop end
    goto parse

    return tbl
end

usage

--example table
tbl1 = {
    true,
    false,
    3,
    "four",
    "binarystring\31\30\6\21\\2\3\23\4\0",
    foo = "bar",
    {
        true,
        false,
        3,
        "four",
        "binarystring\31\30\6\21\\2\3\23\4\0",
        foo = "bar",
        {
            true,
            false,
            3,
            "four",
            "binarystring\31\30\6\21\\2\3\23\4\0",
            foo = "bar"
        }
    }
}

--serialize
str = tbl_serialize(tbl1)

--deserialize
tbl2 = tbl_deserialize(str)

--validate
tbl_serialize_validate(tbl1, tbl2)

--output
printh(escape_binary_str(str), "@clip")

P#112705 2022-06-04 00:46


[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-28 15:30:38 | 0.007s | Q:7