Mercurial > hg > orthanc
changeset 997:1b1d51e9f1a2 lua-scripting
return Json from Lua
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 03 Jul 2014 18:12:50 +0200 |
parents | cf52f3bcb2b3 |
children | 4136fab6a639 |
files | Core/Lua/LuaFunctionCall.cpp Core/Lua/LuaFunctionCall.h OrthancServer/ServerContext.cpp UnitTestsSources/LuaTests.cpp |
diffstat | 4 files changed, 212 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/Core/Lua/LuaFunctionCall.cpp Thu Jul 03 16:27:16 2014 +0200 +++ b/Core/Lua/LuaFunctionCall.cpp Thu Jul 03 18:12:50 2014 +0200 @@ -34,7 +34,9 @@ #include "LuaFunctionCall.h" #include <cassert> - +#include <stdio.h> +#include <boost/lexical_cast.hpp> +#include <glog/logging.h> namespace Orthanc { @@ -80,7 +82,7 @@ lua_pushnumber(context_.lua_, value); } - void LuaFunctionCall::PushJSON(const Json::Value& value) + void LuaFunctionCall::PushJson(const Json::Value& value) { CheckAlreadyExecuted(); @@ -119,7 +121,7 @@ lua_pushnumber(context_.lua_, i + 1); // Push the value of the cell - PushJSON(value[i]); + PushJson(value[i]); // Stores the pair in the table lua_rawset(context_.lua_, -3); @@ -138,7 +140,7 @@ lua_pushstring(context_.lua_, it->c_str()); // Push the value of the cell - PushJSON(value[*it]); + PushJson(value[*it]); // Stores the pair in the table lua_rawset(context_.lua_, -3); @@ -150,7 +152,7 @@ } } - void LuaFunctionCall::Execute(int numOutputs) + void LuaFunctionCall::ExecuteInternal(int numOutputs) { CheckAlreadyExecuted(); @@ -177,13 +179,8 @@ bool LuaFunctionCall::ExecutePredicate() { - Execute(1); - - if (lua_gettop(context_.lua_) == 0) - { - throw LuaException("No output was provided by the function"); - } - + ExecuteInternal(1); + if (!lua_isboolean(context_.lua_, 1)) { throw LuaException("The function is not a predicate (only true/false outputs allowed)"); @@ -191,4 +188,95 @@ return lua_toboolean(context_.lua_, 1) != 0; } + + + static void PopJson(Json::Value& result, + lua_State* lua, + int top) + { + if (lua_istable(lua, top)) + { + Json::Value tmp = Json::objectValue; + bool isArray = true; + size_t size = 0; + + // http://stackoverflow.com/a/6142700/881731 + + // Push another reference to the table on top of the stack (so we know + // where it is, and this function can work for negative, positive and + // pseudo indices + lua_pushvalue(lua, top); + // stack now contains: -1 => table + lua_pushnil(lua); + // stack now contains: -1 => nil; -2 => table + while (lua_next(lua, -2)) + { + // stack now contains: -1 => value; -2 => key; -3 => table + // copy the key so that lua_tostring does not modify the original + lua_pushvalue(lua, -2); + // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table + std::string key(lua_tostring(lua, -1)); + Json::Value v; + PopJson(v, lua, -2); + + tmp[key] = v; + + size += 1; + try + { + if (boost::lexical_cast<size_t>(key) != size) + { + isArray = false; + } + } + catch (boost::bad_lexical_cast&) + { + isArray = false; + } + + // pop value + copy of key, leaving original key + lua_pop(lua, 2); + // stack now contains: -1 => key; -2 => table + } + // stack now contains: -1 => table (when lua_next returns 0 it pops the key + // but does not push anything.) + // Pop table + lua_pop(lua, 1); + + // Stack is now the same as it was on entry to this function + + if (isArray) + { + result = Json::arrayValue; + for (size_t i = 0; i < size; i++) + { + result.append(tmp[boost::lexical_cast<std::string>(i + 1)]); + } + } + else + { + result = tmp; + } + } + else if (lua_isnumber(lua, top)) + { + result = static_cast<float>(lua_tonumber(lua, top)); + } + else if (lua_isstring(lua, top)) + { + result = std::string(lua_tostring(lua, top)); + } + else + { + LOG(WARNING) << "Unsupported Lua type when returning Json"; + result = Json::nullValue; + } + } + + + void LuaFunctionCall::ExecuteToJson(Json::Value& result) + { + ExecuteInternal(1); + PopJson(result, context_.lua_, lua_gettop(context_.lua_)); + } }
--- a/Core/Lua/LuaFunctionCall.h Thu Jul 03 16:27:16 2014 +0200 +++ b/Core/Lua/LuaFunctionCall.h Thu Jul 03 18:12:50 2014 +0200 @@ -46,6 +46,8 @@ void CheckAlreadyExecuted(); + void ExecuteInternal(int numOutputs); + public: LuaFunctionCall(LuaContext& context, const char* functionName); @@ -58,10 +60,15 @@ void PushDouble(double value); - void PushJSON(const Json::Value& value); + void PushJson(const Json::Value& value); - void Execute(int numOutputs = 0); + void Execute() + { + ExecuteInternal(0); + } bool ExecutePredicate(); + + void ExecuteToJson(Json::Value& result); }; }
--- a/OrthancServer/ServerContext.cpp Thu Jul 03 16:27:16 2014 +0200 +++ b/OrthancServer/ServerContext.cpp Thu Jul 03 18:12:50 2014 +0200 @@ -104,7 +104,7 @@ SimplifyTags(simplified, dicomJson); LuaFunctionCall call(locker.GetLua(), RECEIVED_INSTANCE_FILTER); - call.PushJSON(simplified); + call.PushJson(simplified); call.PushString(remoteAet); if (!call.ExecutePredicate())
--- a/UnitTestsSources/LuaTests.cpp Thu Jul 03 16:27:16 2014 +0200 +++ b/UnitTestsSources/LuaTests.cpp Thu Jul 03 18:12:50 2014 +0200 @@ -35,6 +35,8 @@ #include "../Core/Lua/LuaFunctionCall.h" +#include <boost/lexical_cast.hpp> + TEST(Lua, Json) { @@ -65,13 +67,13 @@ { Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushJSON(v); + f.PushJson(v); f.Execute(); } { Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); + f.PushJson(o); ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); } @@ -79,7 +81,7 @@ { Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); + f.PushJson(o); ASSERT_FALSE(f.ExecutePredicate()); } @@ -87,7 +89,7 @@ { Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); + f.PushJson(o); ASSERT_TRUE(f.ExecutePredicate()); } } @@ -134,3 +136,99 @@ f.Execute(); } } + + +TEST(Lua, ReturnJson) +{ + Json::Value b = Json::objectValue; + b["a"] = 42; + b["b"] = 44; + b["c"] = 43; + + Json::Value c = Json::arrayValue; + c.append("test3"); + c.append("test1"); + c.append("test2"); + + Json::Value a = Json::objectValue; + a["Hello"] = "World"; + a["List"] = Json::arrayValue; + a["List"].append(b); + a["List"].append(c); + + Orthanc::LuaContext lua; + + // This is the identity function (it simply returns its input) + lua.Execute("function identity(a) return a end"); + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + f.PushJson("hello"); + Json::Value v; + f.ExecuteToJson(v); + ASSERT_EQ("hello", v.asString()); + } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + f.PushJson(42.25); + Json::Value v; + f.ExecuteToJson(v); + ASSERT_FLOAT_EQ(42.25f, v.asFloat()); + } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + Json::Value vv = Json::arrayValue; + f.PushJson(vv); + Json::Value v; + f.ExecuteToJson(v); + ASSERT_EQ(Json::arrayValue, v.type()); + } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + Json::Value vv = Json::objectValue; + f.PushJson(vv); + Json::Value v; + f.ExecuteToJson(v); + // Lua does not make the distinction between empty lists and empty objects + ASSERT_EQ(Json::arrayValue, v.type()); + } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + f.PushJson(b); + Json::Value v; + f.ExecuteToJson(v); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_FLOAT_EQ(42.0f, v["a"].asFloat()); + ASSERT_FLOAT_EQ(44.0f, v["b"].asFloat()); + ASSERT_FLOAT_EQ(43.0f, v["c"].asFloat()); + } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + f.PushJson(c); + Json::Value v; + f.ExecuteToJson(v); + ASSERT_EQ(Json::arrayValue, v.type()); + ASSERT_EQ("test3", v[0].asString()); + ASSERT_EQ("test1", v[1].asString()); + ASSERT_EQ("test2", v[2].asString()); + } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + f.PushJson(a); + Json::Value v; + f.ExecuteToJson(v); + ASSERT_EQ("World", v["Hello"].asString()); + ASSERT_EQ(42, v["List"][0]["a"].asInt()); + ASSERT_EQ(44, v["List"][0]["b"].asInt()); + ASSERT_EQ(43, v["List"][0]["c"].asInt()); + ASSERT_EQ("test3", v["List"][1][0].asString()); + ASSERT_EQ("test1", v["List"][1][1].asString()); + ASSERT_EQ("test2", v["List"][1][2].asString()); + } +}