diff options
Diffstat (limited to '')
-rw-r--r-- | src/Bindings/LuaJson.cpp | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/Bindings/LuaJson.cpp b/src/Bindings/LuaJson.cpp new file mode 100644 index 000000000..4fa16273c --- /dev/null +++ b/src/Bindings/LuaJson.cpp @@ -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); +} + + + + |