1
0

Added a Json parser and serializer to Lua API.

This commit is contained in:
Mattes D 2015-12-23 11:08:39 +01:00
parent 7b65ee4096
commit 122d824a8a
8 changed files with 500 additions and 48 deletions

View File

@ -1372,53 +1372,6 @@ local Item5 = cItem(E_ITEM_DIAMOND_CHESTPLATE, 1, 0, "thorns=1;unbreaking=3");
}, },
}, -- cItem }, -- cItem
cObjective =
{
Desc = [[
This class represents a single scoreboard objective.
]],
Functions =
{
AddScore = { Params = "string, number", Return = "Score", Notes = "Adds a value to the score of the specified player and returns the new value." },
GetDisplayName = { Params = "", Return = "string", Notes = "Returns the display name of the objective. This name will be shown to the connected players." },
GetName = { Params = "", Return = "string", Notes = "Returns the internal name of the objective." },
GetScore = { Params = "string", Return = "Score", Notes = "Returns the score of the specified player." },
GetType = { Params = "", Return = "eType", Notes = "Returns the type of the objective. (i.e what is being tracked)" },
Reset = { Params = "", Return = "", Notes = "Resets the scores of the tracked players." },
ResetScore = { Params = "string", Return = "", Notes = "Reset the score of the specified player." },
SetDisplayName = { Params = "string", Return = "", Notes = "Sets the display name of the objective." },
SetScore = { Params = "string, Score", Return = "", Notes = "Sets the score of the specified player." },
SubScore = { Params = "string, number", Return = "Score", Notes = "Subtracts a value from the score of the specified player and returns the new value." },
},
Constants =
{
otAchievement = { Notes = "" },
otDeathCount = { Notes = "" },
otDummy = { Notes = "" },
otHealth = { Notes = "" },
otPlayerKillCount = { Notes = "" },
otStat = { Notes = "" },
otStatBlockMine = { Notes = "" },
otStatEntityKill = { Notes = "" },
otStatEntityKilledBy = { Notes = "" },
otStatItemBreak = { Notes = "" },
otStatItemCraft = { Notes = "" },
otStatItemUse = { Notes = "" },
otTotalKillCount = { Notes = "" },
},
}, -- cObjective
cPainting =
{
Desc = "This class represents a painting in the world. These paintings are special and different from Vanilla in that they can be critical-hit.",
Functions =
{
GetDirection = { Params = "", Return = "number", Notes = "Returns the direction the painting faces. Directions: ZP - 0, ZM - 2, XM - 1, XP - 3. Note that these are not the BLOCK_FACE constants." },
GetName = { Params = "", Return = "string", Notes = "Returns the name of the painting" },
},
}, -- cPainting
cItemGrid = cItemGrid =
{ {
Desc = [[This class represents a 2D array of items. It is used as the underlying storage and API for all cases that use a grid of items: Desc = [[This class represents a 2D array of items. It is used as the underlying storage and API for all cases that use a grid of items:
@ -1556,6 +1509,65 @@ end
}, },
}, -- cItems }, -- cItems
cJson =
{
Desc = [[
Exposes the Json parser and serializer available in the server. Plugins can parse Json strings into
Lua tables, and serialize Lua tables into Json strings easily.
]],
Functions =
{
Parse = { Params = "string", Return = "table", Notes = "Parses the Json in the input string into a Lua table. Returns nil and detailed error message if parsing fails." },
Serialize = { Params = "table, [options]", Return = "string", Notes = "Serializes the input table into a Json string. The options table, if present, is used to adjust the formatting of the serialized string, see below for details." },
},
AdditionalInfo =
{
{
Header = "Serializer options",
Contents = [[
The "options" parameter given to the cJson:Serialize() function is a dictionary-table of "option
name" -> "option value". The serializer warns if any unknown options are used; the following
options are recognized:</p>
<ul>
<li><b>commentStyle</b> - either "All" or "None", specifies whether comments are written to the
output. Currently unused since comments cannot be represented in a Lua table</li>
<li><b>indentation</b> - the string that is repeated for each level of indentation of the output.
If empty, the Json is compressed (without linebreaks).</li>
<li><b>enableYAMLCompatibility</b> - bool manipulating the whitespace around the colons.</li>
<li><b>dropNullPlaceholders</b> - bool specifying whether null placeholders should be dropped
from the output</li>
</ul>
]],
},
{
Header = "Code example: Parsing a Json string",
Contents = [==[
The following code, adapted from the Debuggers plugin, parses a simple Json string and verifies
the results:
<pre class="prettyprint lang-lua">
local t1 = cJson:Parse([[{"a": 1, "b": "2", "c": [3, "4", 5]}]])
assert(t1.a == 1)
assert(t1.b == "2")
assert(t1.c[1] == 3)
assert(t1.c[2] == "4")
assert(t1.c[3] == 5)
</pre>
]==],
},
{
Header = "Code example: Serializing into a Json string",
Contents = [==[
The following code, adapted from the Debuggers plugin, serializes a simple Lua table into a
string, using custom indentation:
<pre class="prettyprint lang-lua">
local s1 = cJson:Serialize({a = 1, b = "2", c = {3, "4", 5}}, {indentation = " "})
LOG("Serialization result: " .. (s1 or "<nil>"))
</pre>
]==],
},
},
}, -- cJson
cLuaWindow = cLuaWindow =
{ {
Desc = [[This class is used by plugins wishing to display a custom window to the player, unrelated to block entities or entities near the player. The window can be of any type and have any contents that the plugin defines. Callbacks for when the player modifies the window contents and when the player closes the window can be set. Desc = [[This class is used by plugins wishing to display a custom window to the player, unrelated to block entities or entities near the player. The window can be of any type and have any contents that the plugin defines. Callbacks for when the player modifies the window contents and when the player closes the window can be set.
@ -1813,6 +1825,52 @@ a_Player:OpenWindow(Window);
Inherits = "cPawn", Inherits = "cPawn",
}, -- cMonster }, -- cMonster
cObjective =
{
Desc = [[
This class represents a single scoreboard objective.
]],
Functions =
{
AddScore = { Params = "string, number", Return = "Score", Notes = "Adds a value to the score of the specified player and returns the new value." },
GetDisplayName = { Params = "", Return = "string", Notes = "Returns the display name of the objective. This name will be shown to the connected players." },
GetName = { Params = "", Return = "string", Notes = "Returns the internal name of the objective." },
GetScore = { Params = "string", Return = "Score", Notes = "Returns the score of the specified player." },
GetType = { Params = "", Return = "eType", Notes = "Returns the type of the objective. (i.e what is being tracked)" },
Reset = { Params = "", Return = "", Notes = "Resets the scores of the tracked players." },
ResetScore = { Params = "string", Return = "", Notes = "Reset the score of the specified player." },
SetDisplayName = { Params = "string", Return = "", Notes = "Sets the display name of the objective." },
SetScore = { Params = "string, Score", Return = "", Notes = "Sets the score of the specified player." },
SubScore = { Params = "string, number", Return = "Score", Notes = "Subtracts a value from the score of the specified player and returns the new value." },
},
Constants =
{
otAchievement = { Notes = "" },
otDeathCount = { Notes = "" },
otDummy = { Notes = "" },
otHealth = { Notes = "" },
otPlayerKillCount = { Notes = "" },
otStat = { Notes = "" },
otStatBlockMine = { Notes = "" },
otStatEntityKill = { Notes = "" },
otStatEntityKilledBy = { Notes = "" },
otStatItemBreak = { Notes = "" },
otStatItemCraft = { Notes = "" },
otStatItemUse = { Notes = "" },
otTotalKillCount = { Notes = "" },
},
}, -- cObjective
cPainting =
{
Desc = "This class represents a painting in the world. These paintings are special and different from Vanilla in that they can be critical-hit.",
Functions =
{
GetDirection = { Params = "", Return = "number", Notes = "Returns the direction the painting faces. Directions: ZP - 0, ZM - 2, XM - 1, XP - 3. Note that these are not the BLOCK_FACE constants." },
GetName = { Params = "", Return = "string", Notes = "Returns the name of the painting" },
},
}, -- cPainting
cPawn = cPawn =
{ {
Desc = [[cPawn is a controllable pawn object, controlled by either AI or a player. cPawn inherits all functions and members of {{cEntity}} Desc = [[cPawn is a controllable pawn object, controlled by either AI or a player. cPawn inherits all functions and members of {{cEntity}}

View File

@ -1921,6 +1921,34 @@ end
function HandleConsoleTestJson(a_Split, a_EntireCmd)
LOG("Testing Json parsing...")
local t1 = cJson:Parse([[{"a": 1, "b": "2", "c": [3, "4", 5] }]])
assert(t1.a == 1)
assert(t1.b == "2")
assert(t1.c[1] == 3)
assert(t1.c[2] == "4")
assert(t1.c[3] == 5)
local t2, msg = cJson:Parse([[{"some": invalid, json}]])
assert(t2 == nil)
assert(type(msg) == "string")
LOG("Error message returned: " .. msg)
LOG("Json parsing test succeeded")
LOG("Testing Json serializing...")
local s1 = cJson:Serialize({a = 1, b = "2", c = {3, "4", 5}}, {indentation = " "})
LOG("Serialization result: " .. (s1 or "<nil>"))
LOG("Json serializing test succeeded")
return true
end
function HandleConsoleTestTracer(a_Split, a_EntireCmd) function HandleConsoleTestTracer(a_Split, a_EntireCmd)
-- Check required params: -- Check required params:
if not(a_Split[7]) then if not(a_Split[7]) then

View File

@ -242,11 +242,17 @@ g_PluginInfo =
HelpString = "Tests the world scheduling", HelpString = "Tests the world scheduling",
}, },
["testjson"] =
{
Handler = HandleConsoleTestJson,
HelpString = "Tests the cJson parser and serializer",
},
["testtracer"] = ["testtracer"] =
{ {
Handler = HandleConsoleTestTracer, Handler = HandleConsoleTestTracer,
HelpString = "Tests the cLineBlockTracer", HelpString = "Tests the cLineBlockTracer",
} },
}, -- ConsoleCommands }, -- ConsoleCommands
} -- g_PluginInfo } -- g_PluginInfo

View File

@ -8,6 +8,7 @@ SET (SRCS
Bindings.cpp Bindings.cpp
DeprecatedBindings.cpp DeprecatedBindings.cpp
LuaChunkStay.cpp LuaChunkStay.cpp
LuaJson.cpp
LuaNameLookup.cpp LuaNameLookup.cpp
LuaServerHandle.cpp LuaServerHandle.cpp
LuaState.cpp LuaState.cpp
@ -30,6 +31,7 @@ SET (HDRS
DeprecatedBindings.h DeprecatedBindings.h
LuaChunkStay.h LuaChunkStay.h
LuaFunctions.h LuaFunctions.h
LuaJson.h
LuaNameLookup.h LuaNameLookup.h
LuaServerHandle.h LuaServerHandle.h
LuaState.h LuaState.h

315
src/Bindings/LuaJson.cpp Normal file
View File

@ -0,0 +1,315 @@
// LuaJson.cpp
// Implements the Json exposure bindings to Lua
#include "Globals.h"
#include <sstream>
#include "LuaJson.h"
#include "LuaState.h"
#include "tolua++/include/tolua++.h"
#include "json/json.h"
// fwd:
static void PushJsonValue(const Json::Value & a_Value, cLuaState & a_LuaState);
static Json::Value JsonSerializeValue(cLuaState & a_LuaState);
/** Pushes the specified Json array as a table on top of the specified Lua state.
Assumes that a_Value is an array. */
static void PushJsonArray(const Json::Value & a_Value, cLuaState & a_LuaState)
{
// Create the appropriately-sized Lua table:
lua_createtable(a_LuaState, static_cast<int>(a_Value.size()), 0);
// Insert each value to the appropriate index (1-based):
int idx = 1;
for (const auto & v: a_Value)
{
// Include Json null values in the array - it will have holes, but indices will stay the same
PushJsonValue(v, a_LuaState);
lua_rawseti(a_LuaState, -2, idx);
idx += 1;
} // for v: a_Value[]
}
/** Pushes the specified Json object as a table on top of the specified Lua state.
Assumes that a_Value is an object. */
static void PushJsonObject(const Json::Value & a_Value, cLuaState & a_LuaState)
{
// Create the appropriately-sized Lua table:
lua_createtable(a_LuaState, 0, static_cast<int>(a_Value.size()));
// Json::Value has no means of iterating over children with their names included.
// We need to iterate over names and "find" them in the object again:
auto names = a_Value.getMemberNames();
for (const auto & n: names)
{
auto v = a_Value[n];
if (v.isNull())
{
// Skip null values
continue;
}
// Set the value in Lua's table:
a_LuaState.Push(n);
PushJsonValue(v, a_LuaState);
lua_rawset(a_LuaState, -3);
} // for itr - a_Value[]
}
/** Pushes the specified Json value as an appropriate type on top of the specified Lua state. */
void PushJsonValue(const Json::Value & a_Value, cLuaState & a_LuaState)
{
switch (a_Value.type())
{
case Json::nullValue:
{
a_LuaState.PushNil();
break;
}
case Json::intValue:
case Json::uintValue:
case Json::realValue:
{
a_LuaState.Push(static_cast<lua_Number>(a_Value.asDouble()));
break;
}
case Json::booleanValue:
{
a_LuaState.Push(a_Value.asBool());
break;
}
case Json::stringValue:
{
a_LuaState.Push(a_Value.asString());
break;
}
case Json::arrayValue:
{
PushJsonArray(a_Value, a_LuaState);
break;
}
case Json::objectValue:
{
PushJsonObject(a_Value, a_LuaState);
break;
}
} // switch (v.type())
}
/** Serializes the Lua table at the top of the specified Lua state's stack into a Json value.
Lets jsoncpp decides whether to serialize into an object or an array. */
static Json::Value JsonSerializeTable(cLuaState & a_LuaState)
{
Json::Value res;
lua_pushnil(a_LuaState);
while (lua_next(a_LuaState, -2) != 0)
{
if (lua_type(a_LuaState, -2) == LUA_TNUMBER)
{
int idx;
a_LuaState.GetStackValue(-2, idx);
res[idx - 1] = JsonSerializeValue(a_LuaState);
}
else
{
AString name;
if (a_LuaState.GetStackValue(-2, name))
{
res[name] = JsonSerializeValue(a_LuaState);
}
}
lua_pop(a_LuaState, 1);
}
return res;
}
/** Serializes the Lua value at the top of the specified Lua state into a Json value. */
static Json::Value JsonSerializeValue(cLuaState & a_LuaState)
{
switch (lua_type(a_LuaState, -1))
{
case LUA_TNUMBER:
{
lua_Number v;
a_LuaState.GetStackValue(-1, v);
return Json::Value(v);
}
case LUA_TSTRING:
{
AString v;
a_LuaState.GetStackValue(-1, v);
return Json::Value(v);
}
case LUA_TTABLE:
{
return JsonSerializeTable(a_LuaState);
}
default:
{
LOGD("Attempting to serialize an unhandled Lua value type: %d", lua_type(a_LuaState, -1));
return Json::Value(Json::nullValue);
}
}
}
static int tolua_cJson_Parse(lua_State * a_LuaState)
{
// Function signature:
// cJson:Parse("string") -> table
// Check the param types:
cLuaState L(a_LuaState);
if (
!L.CheckParamUserTable(1, "cJson") ||
!L.CheckParamString(2) ||
!L.CheckParamEnd(3)
)
{
return 0;
}
// Get the input string:
AString input;
if (!L.GetStackValue(2, input))
{
LOGWARNING("cJson:Parse(): Cannot read input string");
L.LogStackTrace();
return 0;
}
// Parse the string:
Json::Value root;
Json::Reader reader;
if (!reader.parse(input, root, false))
{
L.PushNil();
L.Push(Printf("Parsing Json failed: %s", reader.getFormattedErrorMessages().c_str()));
return 2;
}
// Push the Json value onto Lua stack:
PushJsonValue(root, L);
return 1;
}
static int tolua_cJson_Serialize(lua_State * a_LuaState)
{
// Function signature:
// cJson:Serialize({table}, [{option1 = value1, option2 = value2}]) -> string
// Check the param types:
cLuaState L(a_LuaState);
if (
!L.CheckParamUserTable(1, "cJson") ||
!L.CheckParamTable(2) ||
!L.CheckParamEnd(4)
)
{
return 0;
}
// Push the table to the top of the Lua stack, and call the serializing function:
lua_pushvalue(L, 2);
Json::Value root = JsonSerializeValue(L);
lua_pop(L, 1);
// Create the writer, with all properties (optional param 3) applied to it:
Json::StreamWriterBuilder builder;
if (lua_istable(L, 3))
{
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
if (lua_type(L, -2) == LUA_TSTRING)
{
AString propName, propValue;
if (L.GetStackValues(-2, propName, propValue))
{
builder[propName] = propValue;
}
}
lua_pop(L, 1);
}
// Check for invalid settings:
Json::Value invalid;
if (!builder.validate(&invalid))
{
LOGINFO("cJson:Serialize(): detected invalid settings:");
for (const auto & n: invalid.getMemberNames())
{
LOGINFO(" \"%s\" (\"%s\")", n.c_str(), invalid[n].asCString());
}
}
}
auto writer(builder.newStreamWriter());
// Serialize the string and push it as the return value:
std::stringstream ss;
writer->write(root, &ss);
L.Push(ss.str());
return 1;
}
void cLuaJson::Bind(cLuaState & a_LuaState)
{
tolua_beginmodule(a_LuaState, nullptr);
// Create the cJson API class:
tolua_usertype(a_LuaState, "cJson");
tolua_cclass(a_LuaState, "cJson", "cJson", "", nullptr);
// Fill in the functions (alpha-sorted):
tolua_beginmodule(a_LuaState, "cJson");
tolua_function(a_LuaState, "Parse", tolua_cJson_Parse);
tolua_function(a_LuaState, "Serialize", tolua_cJson_Serialize);
tolua_endmodule(a_LuaState);
tolua_endmodule(a_LuaState);
}

32
src/Bindings/LuaJson.h Normal file
View File

@ -0,0 +1,32 @@
// LuaJson.h
// Declares the Json exposure bindings to Lua
#pragma once
// fwd:
class cLuaState;
class cLuaJson
{
public:
/** Registers the Json library in the specified Lua state. */
static void Bind(cLuaState & a_LuaState);
};

View File

@ -16,6 +16,7 @@ extern "C"
#include "Bindings.h" #include "Bindings.h"
#include "ManualBindings.h" #include "ManualBindings.h"
#include "DeprecatedBindings.h" #include "DeprecatedBindings.h"
#include "LuaJson.h"
#include "../Entities/Entity.h" #include "../Entities/Entity.h"
#include "../BlockEntities/BlockEntity.h" #include "../BlockEntities/BlockEntity.h"
@ -180,6 +181,7 @@ void cLuaState::RegisterAPILibs(void)
tolua_AllToLua_open(m_LuaState); tolua_AllToLua_open(m_LuaState);
cManualBindings::Bind(m_LuaState); cManualBindings::Bind(m_LuaState);
DeprecatedBindings::Bind(m_LuaState); DeprecatedBindings::Bind(m_LuaState);
cLuaJson::Bind(*this);
luaopen_lsqlite3(m_LuaState); luaopen_lsqlite3(m_LuaState);
luaopen_lxp(m_LuaState); luaopen_lxp(m_LuaState);
} }

View File

@ -9,6 +9,7 @@
#include "SelfTests.h" #include "SelfTests.h"
#include "Bindings.h" #include "Bindings.h"
#include "Bindings/DeprecatedBindings.h" #include "Bindings/DeprecatedBindings.h"
#include "Bindings/LuaJson.h"
#include "Bindings/ManualBindings.h" #include "Bindings/ManualBindings.h"
#include "BlockEntities/BlockEntity.h" #include "BlockEntities/BlockEntity.h"
#include "Blocks/BlockHandler.h" #include "Blocks/BlockHandler.h"
@ -49,6 +50,14 @@ void DeprecatedBindings::Bind(lua_State * a_LuaState)
void cLuaJson::Bind(cLuaState & a_LuaState)
{
}
int tolua_AllToLua_open(lua_State * a_LuaState) int tolua_AllToLua_open(lua_State * a_LuaState)
{ {
return 0; return 0;