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());
+  }
+}