aboutsummaryrefslogtreecommitdiff
path: root/serialize.lua
diff options
context:
space:
mode:
authororwell96 <orwell@bleipb.de>2020-12-16 16:33:41 +0100
committerorwell96 <orwell@bleipb.de>2021-01-12 13:27:00 +0100
commit544f8babb5421ed181371c3fcb49478571fab9f4 (patch)
tree00739c28b06c1a8356ef876a59a610f81f59cabd /serialize.lua
downloadadvtrains-544f8babb5421ed181371c3fcb49478571fab9f4.tar.gz
advtrains-544f8babb5421ed181371c3fcb49478571fab9f4.tar.bz2
advtrains-544f8babb5421ed181371c3fcb49478571fab9f4.zip
Implement basic serialization and file opening
Diffstat (limited to 'serialize.lua')
-rw-r--r--serialize.lua260
1 files changed, 260 insertions, 0 deletions
diff --git a/serialize.lua b/serialize.lua
new file mode 100644
index 0000000..8ffd917
--- /dev/null
+++ b/serialize.lua
@@ -0,0 +1,260 @@
+-- serialize.lua
+-- Lua-conformant library file that has no minetest dependencies
+-- Contains the serialization and deserialization routines
+
+--[[
+
+Structure of entry:
+[keytype][key]:[valuetype][val]
+Types:
+ B - bool
+ -> 0=false, 1=true
+ S - string
+ -> see below
+ N - number
+ -> thing compatible with tonumber()
+Table:
+[keytype][key]:T
+... content is nested in table until the matching
+E
+
+example:
+LUA_SER v=1 {
+Skey:Svalue key = "value",
+N1:Seins [1] = "eins",
+B1:T [true] = {
+Sa:Sb a = "b",
+Sc:B0 c = false,
+E }
+E }
+
+String representations:
+In strings the following characters are escaped by &
+'&' -> '&&'
+(line break) -> '&n'
+(CR) -> '&r' --> required?
+':' -> '&:'
+All other characters are unchanged as they bear no special meaning.
+]]
+
+local write_table, literal_to_string, escape_chars, table_is_empty
+
+function table_is_empty(t)
+ for _,_ in pairs(t) do
+ return false
+ end
+ return true
+end
+
+function write_table(t, file, config)
+ local ks, vs, writeit, istable
+ for key, value in pairs(t) do
+ ks = value_to_string(key, false)
+ writeit = true
+ istable = type(value)=="table"
+
+ if istable then
+ vs = "T"
+ if config.skip_empty_tables then
+ writeit = not table_is_empty(value)
+ end
+ else
+ vs = value_to_string(value, true)
+ end
+
+ if writeit then
+ file:write(ks..":"..vs.."\n")
+
+ if istable then
+ write_table(value, file, config)
+ file:write("E\n")
+ end
+ end
+ end
+end
+
+function value_to_string(t)
+ if type(t)=="table" then
+ error("Can not serialize a table in the key position!")
+ elseif type(t)=="boolean" then
+ if t then
+ return "B1"
+ else
+ return "B0"
+ end
+ elseif type(t)=="number" then
+ return "N"..t
+ elseif type(t)=="string" then
+ return "S"..escape_chars(t)
+ else
+ error("Can not serialize '"..type(t).."' type!")
+ end
+ return str
+end
+
+function escape_chars(str)
+ local rstr = string.gsub(str, "&", "&&")
+ rstr = string.gsub(rstr, ":", "&:")
+ rstr = string.gsub(rstr, "\n", "&n")
+ return rstr
+end
+
+------
+
+local read_table, string_to_value, unescape_chars
+
+function read_table(t, file)
+ local line, ks, vs, kv, vv, vt
+ while true do
+ line = file:read("*l")
+ if not line then
+ error("Unexpected EOF or read error!")
+ end
+
+ if line=="E" then
+ -- done with this table
+ return
+ end
+ ks, vs = string.match(line, "^(.+[^&]):(.+)$")
+ if not ks or not vs then
+ error("Unable to parse line: '"..line.."'!")
+ end
+ kv = string_to_value(ks)
+ vv, vt = string_to_value(vs, true)
+ if vt then
+ read_table(vv, file)
+ end
+ -- put read value in table
+ t[kv] = vv
+ end
+end
+
+-- returns: value, is_table
+function string_to_value(str, table_allow)
+ local first = string.sub(str, 1,1)
+ local rest = string.sub(str, 2)
+ if first=="T" then
+ if table_allow then
+ return {}, true
+ else
+ error("Table not allowed in key component!")
+ end
+ elseif first=="N" then
+ local num = tonumber(rest)
+ if num then
+ return num
+ else
+ error("Unable to parse number: '"..rest.."'!")
+ end
+ elseif first=="B" then
+ if rest=="0" then
+ return false
+ elseif rest=="1" then
+ return true
+ else
+ error("Unable to parse boolean: '"..rest.."'!")
+ end
+ elseif first=="S" then
+ return unescape_chars(rest)
+ else
+ error("Unknown literal type '"..first.."' for literal '"..str.."'!")
+ end
+end
+
+function unescape_chars(str) --TODO
+ local rstr = string.gsub(str, "&:", ":")
+ rstr = string.gsub(rstr, "&n", "\n")
+ rstr = string.gsub(rstr, "&&", "&")
+ return rstr
+end
+
+------
+
+--[[
+config = {
+ skip_empty_tables = false -- if true, does not store empty tables
+ -- On next read, keys that mapped to empty tables resolve to nil
+}
+]]
+
+-- Writes the passed table into the passed file descriptor, and closes the file afterwards
+local function write_to_fd(root_table, file, config)
+ file:write("LUA_SER v=1\n")
+ write_table(root_table, file, config)
+ file:write("E\nEND_SER\n")
+ file:close()
+end
+
+-- Reads the file contents from the passed file descriptor and returns the table on success
+-- Throws errors when something is wrong.
+-- config: see above
+local function read_from_fd(file)
+ local first_line = file:read("*l")
+ if first_line ~= "LUA_SER v=1" then
+ error("Expected header, got '"..first_line.."' instead!")
+ end
+ local t = {}
+ read_table(t, file)
+ local last_line = file:read("*l")
+ file:close()
+ if last_line ~= "END_SER" then
+ error("Missing END_SER, got '"..last_line.."' instead!")
+ end
+ return t
+end
+
+-- Opens the passed filename and serializes root_table into it
+-- config: see above
+function write_to_file(root_table, filename, config)
+ -- try opening the file
+ local file, err = io.open(filename, "w")
+ if not file then
+ error("Failed opening file '"..filename.."' for write: "..err)
+ end
+
+ write_to_fd(root_table, file, config)
+ return true
+end
+
+-- Opens the passed filename, and returns its deserialized contents
+function read_from_file(filename)
+ -- try opening the file
+ local file, err = io.open(filename, "r")
+ if not file then
+ error("Failed opening file '"..filename.."' for read: "..err)
+ end
+
+ return read_from_fd(file)
+end
+
+--[[ simple unit test
+local testtable = {
+ key = "value",
+ [1] = "eins",
+ [true] = {
+ a = "b",
+ c = false,
+ },
+ ["es:cape1"] = "foo:bar",
+ ["es&ca\npe2"] = "baz&bam\nbim",
+ ["es&&ca&\npe3"] = "baz&&bam&\nbim",
+ ["es&:cape4"] = "foo\n:bar"
+}
+local config = {}
+--write_to_file(testtable, "test_out", config)
+local t = read_from_file("test_out")
+write_to_file(t, "test_out_2", config)
+local t2 = read_from_file("test_out_2")
+write_to_file(t2, "test_out_3", config)
+
+-- test_out_2 and test_out_3 should be equal
+
+--]]
+
+
+return {
+ read_from_fd = read_from_fd,
+ write_to_fd = write_to_fd,
+ read_from_file = read_from_file,
+ write_to_file = write_to_file,
+}