local function _parse(str, init) local pos = init if string.find(str, "^%(", pos) then pos = pos + 1 local list = {}; while true do if string.find(str, "^%)", pos) then pos = pos + 1 return list, pos end local child, new_pos = _parse(str, pos) if not child then error(string.format("expected ')', '(', or atom at index %d", pos)) end table.insert(list, child) pos = new_pos end else local pos = init local len_str = string.match(str, "^%d+", pos) if not len_str then error(string.format("expected '(' or atom at index %d", pos)) end local len = tonumber(len_str) pos = pos+#len_str if not string.find(str, "^:", pos) then error(string.format("expected ':' at index %d", pos)) end pos = pos + 1 local value = string.sub(str, pos, pos + len - 1) if #value < len then error(string.format("unexpected end of string at index %d", pos)) end pos = pos + len return value, pos end end local function parse(str) local value, pos = _parse(str, 1) if pos <= #str then error(string.format("extra characters at index %d", pos)) end return value end local function _serialize(tokens, obj) if type(obj) == "table" then table.insert(tokens, "(") for _, item in ipairs(obj) do _serialize(tokens, item) end table.insert(tokens, ")") else local s = tostring(obj) table.insert(tokens, tostring(#s)) table.insert(tokens, ":") table.insert(tokens, s) end end local function serialize(obj) local tokens = {} _serialize(tokens, obj) return table.concat(tokens) end return {parse = parse, serialize = serialize}