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")

1



[Please log in to post a comment]