One of the nice things about using Lua is the ability to create nested tables. Here is an example:
The example above creates a single table (mobs) with sub-tables (kobold and worm) and inside those some other sub-tables again (treasure and attacks).
Such tables are all very well in Lua, but how do you save them from one MUSHclient session to another? The least tedious way is to write a recursive serializer, an example of which is below. This is based on chapter 12.1.2 of the Lua manual, however it has been adapted to generate a string rather than writing to a file.
Most of the work is done in the "save" function, which recursively saves variables. To package the results into a string, we initially save to a table (out) which is then concatenated into a single string, with carriage-return, linefeeds between each line.
Example of saving a table
Assuming we have the "mobs" table shown at the top, all we have to do is this to save it:
After doing that, we can edit the "mobs" variable in MUSHclient:
The serialize function will handle shared sub-tables, or even tables that refer to themselves. It does that by keeping track of which tables have been created, and not creating them again.
Example of loading a table
Once we have the table saved (which we might do in a plugin's OnPluginSaveState function), then we need to be able to restore the table next time we want to use it.
Serializing local variables
The serialize function has an optional second argument, which is the contents of the variable. If the variable is a global variable this is not needed, as using the global variable of the supplied name is the default. However if you want to serialize a local variable then you would need to specify its value as well as its name.
eg.
In the example above "test" is a variable that is not in the global namespace, and thus its name and value need to be supplied to the serialize function.
mobs = {} -- create mobs table
mobs.kobold = {
name = 'killer',
hp = 22,
gold = 5,
location = 'city square',
treasure = { "sword", "gold", "helmet" } -- sub table
}
-- and another one ...
mobs.worm = {
name = 'gordon',
hp = 4,
gold = 15,
location = 'underground',
treasure = { "food", "knife" },
attacks = { "bite", "poison" }
}
The example above creates a single table (mobs) with sub-tables (kobold and worm) and inside those some other sub-tables again (treasure and attacks).
Such tables are all very well in Lua, but how do you save them from one MUSHclient session to another? The least tedious way is to write a recursive serializer, an example of which is below. This is based on chapter 12.1.2 of the Lua manual, however it has been adapted to generate a string rather than writing to a file.
-- ----------------------------------------------------------
-- serializer
-- See "Programming In Lua" chapter 12.1.2.
-- Also see forum thread:
-- http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=4960
-- ----------------------------------------------------------
function basicSerialize (o)
if type(o) == "number" or type(o) == "boolean" then
return tostring(o)
else -- assume it is a string
return string.format("%q", o)
end
end -- basicSerialize
--
-- Lua keywords might look OK to not be quoted as keys but must be.
-- So, we make a list of them.
--
lua_reserved_words = {}
for _, v in {
"and", "break", "do", "else", "elseif", "end", "false",
"for", "function", "if", "in", "local", "nil", "not", "or",
"repeat", "return", "then", "true", "until", "while"
} do lua_reserved_words [v] = true end
-- ----------------------------------------------------------
-- save one variable (calls itself recursively)
-- ----------------------------------------------------------
function save (name, value, out, indent, saved)
saved = saved or {} -- initial value
indent = indent or 0 -- start indenting at zero cols
local iname = string.rep (" ", indent) .. name -- indented name
if type(value) == "number" or
type(value) == "string" or
type(value) == "boolean" then
table.insert (out, iname .. " = " .. basicSerialize(value))
elseif type(value) == "table" then
if saved[value] then -- value already saved?
table.insert (out, iname .. " = " .. saved[value]) -- use its previous name
else
saved[value] = name -- save name for next time
table.insert (out, iname .. " = {}") -- create a new table
for k,v in pairs(value) do -- save its fields
local fieldname
if type (k) == "string"
and string.find (k, "^[_%a][_%a%d]*$")
and not lua_reserved_words [k] then
fieldname = string.format("%s.%s", name, k)
else
fieldname = string.format("%s[%s]", name,
basicSerialize(k))
end
save(fieldname, v, out, indent + 2, saved)
end
end
else
error("cannot save a " .. type(value))
end
end -- save
-- ----------------------------------------------------------
-- Serialize a variable or nested set of tables:
-- ----------------------------------------------------------
--[[
Example of use:
SetVariable ("mobs", serialize ("mobs")) --> serialize mobs table
loadstring (GetVariable ("mobs")) () --> restore mobs table
--]]
function serialize (what, v)
v = v or _G [what] -- default to "what" in global namespace
assert (type (what) == "string",
"Argument to serialize should be the *name* of a variable")
assert (v, "Variable '" .. what .. "' does not exist")
local out = {} -- output to this table
save (what, v, out) -- do serialization
return table.concat (out, "\r\n") -- turn into a string
end -- serialize
Most of the work is done in the "save" function, which recursively saves variables. To package the results into a string, we initially save to a table (out) which is then concatenated into a single string, with carriage-return, linefeeds between each line.
Example of saving a table
Assuming we have the "mobs" table shown at the top, all we have to do is this to save it:
SetVariable ("mobs", serialize ("mobs"))
After doing that, we can edit the "mobs" variable in MUSHclient:
mobs = {}
mobs.worm = {}
mobs.worm.attacks = {}
mobs.worm.attacks[1] = "bite"
mobs.worm.attacks[2] = "poison"
mobs.worm.treasure = {}
mobs.worm.treasure[1] = "food"
mobs.worm.treasure[2] = "knife"
mobs.worm.name = "gordon"
mobs.worm.gold = 15
mobs.worm.location = "underground"
mobs.worm.hp = 4
mobs.kobold = {}
mobs.kobold.treasure = {}
mobs.kobold.treasure[1] = "sword"
mobs.kobold.treasure[2] = "gold"
mobs.kobold.treasure[3] = "helmet"
mobs.kobold.name = "killer"
mobs.kobold.gold = 5
mobs.kobold.location = "city square"
mobs.kobold.hp = 22
The serialize function will handle shared sub-tables, or even tables that refer to themselves. It does that by keeping track of which tables have been created, and not creating them again.
Example of loading a table
Once we have the table saved (which we might do in a plugin's OnPluginSaveState function), then we need to be able to restore the table next time we want to use it.
loadstring (GetVariable ("mobs"))
Serializing local variables
The serialize function has an optional second argument, which is the contents of the variable. If the variable is a global variable this is not needed, as using the global variable of the supplied name is the default. However if you want to serialize a local variable then you would need to specify its value as well as its name.
eg.
do
local test = { "a", "b", "c" }
print (serialize ("test", test))
end
In the example above "test" is a variable that is not in the global namespace, and thus its name and value need to be supplied to the serialize function.