[Orxonox-commit 1943] r6660 - in code/branches/gamestate: data/lua src/libraries/core

rgrieder at orxonox.net rgrieder at orxonox.net
Wed Mar 31 00:04:30 CEST 2010


Author: rgrieder
Date: 2010-03-31 00:04:30 +0200 (Wed, 31 Mar 2010)
New Revision: 6660

Modified:
   code/branches/gamestate/data/lua/Debugger.lua
   code/branches/gamestate/data/lua/LuaStateInit.lua
   code/branches/gamestate/src/libraries/core/LuaState.cc
   code/branches/gamestate/src/libraries/core/LuaState.h
Log:
Improved Lua error handling: Clearer messages and debugger hook.
So, if the IOConsole is not running and a runtime error occurs, the lua debugger automatically gets started.

Modified: code/branches/gamestate/data/lua/Debugger.lua
===================================================================
--- code/branches/gamestate/data/lua/Debugger.lua	2010-03-30 14:34:01 UTC (rev 6659)
+++ code/branches/gamestate/data/lua/Debugger.lua	2010-03-30 22:04:30 UTC (rev 6660)
@@ -432,7 +432,7 @@
   end
 
   -- Transform line endings to \n
-  text :gsub("\r\n", "\n") -- Windows to Unix
+  text:gsub("\r\n", "\n") -- Windows to Unix
   text:gsub("\r", "\n")   -- Mac to Unix
   if text[-1] ~= "\n" then
       text = text.."\n"

Modified: code/branches/gamestate/data/lua/LuaStateInit.lua
===================================================================
--- code/branches/gamestate/data/lua/LuaStateInit.lua	2010-03-30 14:34:01 UTC (rev 6659)
+++ code/branches/gamestate/data/lua/LuaStateInit.lua	2010-03-30 22:04:30 UTC (rev 6660)
@@ -16,7 +16,7 @@
 original_dofile = dofile
 dofile = function(filename)
   luaState:doFile(filename)
-  -- Required because the C++ function cannot return whatever might be on the stack
+  -- Required because if the file returns a table, it cannot be passed through the C++ function
   return LuaStateReturnValue -- C-injected global variable
 end
 doFile = dofile
@@ -25,7 +25,7 @@
 -- to a function provided to the LuaState constructor (in C++)
 include = function(filename)
   luaState:includeFile(filename)
-  -- Required because the C++ function cannot return whatever might be on the stack
+  -- Required because if the file returns a table, it cannot be passed through the C++ function
   return LuaStateReturnValue -- C-injected global variable
 end
 
@@ -43,18 +43,55 @@
   if not _LOADED then
     _LOADED = {}
   end
-  if not _LOADED[moduleName] then
+  if _LOADED[moduleName] == nil then
     -- save old value
     local _REQUIREDNAME_OLD = _REQUIREDNAME
     _REQUIREDNAME = moduleName
     luaState:doFile(moduleName .. ".lua")
-    _LOADED[moduleName] = LuaStateReturnValue
+    -- LuaStateReturnValue is required because if the file returns a table,
+    -- it cannot be passed through the C++ function
+    if LuaStateReturnValue == nil then -- C-injected global variable
+        LuaStateReturnValue = true
+    end
+    _LOADED[moduleName] = LuaStateReturnValue -- This entry must never be nil
     -- restore old value
     _REQUIREDNAME = _REQUIREDNAME_OLD
   end
   return _LOADED[moduleName]
 end
 
+
+-- Include command line debugger for lua 5.1
+-- Note: It doesn't work if the IOConsole was started. Then we replace pause() with a warning
+if _VERSION ~= "Lua 5.0"  and not luaState:usingIOConsole() then
+  require("Debugger")
+else
+  -- Fallback pause function
+  pause = function()
+    logMessage(2, [["Warning: debug() called in Lua, but Debugger is not active.
+Do you have the IOConsole disabled and are you using Lua version 5.1?"]])
+  end
+end
+
+-- General error handler that gets called whenever an error happens at runtime
+errorHandler = function(err)
+  -- Display the error message
+  if type(err) == "string" then
+    logMessage(1, "Lua runtime error: "..err)
+  end
+
+  -- Start debugger if possible
+  if _LOADED and _LOADED["Debugger"] ~= nil then
+    pause()
+  else
+    -- Fallback: print stack trace
+    debug.traceback(nil, "", 2)
+  end
+  return err -- Hello Lua debugger user! Please type 'set 2' to get to the
+             -- actual position in the stack where the error occurred
+end
+
+
 -- Convenience function for console commands
 orxonox.execute = function(command)
   orxonox.CommandExecutor:execute(command)
@@ -70,8 +107,3 @@
 orxonox.tconfig = function(section, entry, value)
   return orxonox.SettingsConfigFile:getInstance():tconfig(section, entry, value)
 end
-
--- Include command line debugger for lua 5.1
-if _VERSION ~= "Lua 5.0" then
-  require("Debugger")
-end

Modified: code/branches/gamestate/src/libraries/core/LuaState.cc
===================================================================
--- code/branches/gamestate/src/libraries/core/LuaState.cc	2010-03-30 14:34:01 UTC (rev 6659)
+++ code/branches/gamestate/src/libraries/core/LuaState.cc	2010-03-30 22:04:30 UTC (rev 6660)
@@ -36,6 +36,9 @@
 }
 
 #include "util/Debug.h"
+#include "util/Exception.h"
+#include "util/ScopeGuard.h"
+#include "IOConsole.h"
 #include "Resource.h"
 #include "ToluaBindCore.h"
 
@@ -53,6 +56,7 @@
     {
         // Create new lua state and configure it
         luaState_ = lua_open();
+        Loki::ScopeGuard luaStateGuard = Loki::MakeGuard(&lua_close, luaState_);
 #if LUA_VERSION_NUM == 501
         luaL_openlibs(luaState_);
 #else
@@ -77,7 +81,10 @@
         lua_setglobal(luaState_, "luaState");
 
         // Parse init script
-        this->doFile("LuaStateInit.lua");
+        if (!this->doFile("LuaStateInit.lua"))
+            ThrowException(InitialisationFailed, "Running LuaStateInit.lua failed");
+
+        luaStateGuard.Dismiss();
     }
 
     LuaState::~LuaState()
@@ -95,16 +102,19 @@
         return sourceInfo;
     }
 
-    void LuaState::includeFile(const std::string& filename)
+    bool LuaState::includeFile(const std::string& filename)
     {
         shared_ptr<ResourceInfo> sourceInfo = this->getFileInfo(filename);
         if (sourceInfo != NULL)
-            this->includeString(Resource::open(sourceInfo)->getAsString(), sourceInfo);
+            return this->includeString(Resource::open(sourceInfo)->getAsString(), sourceInfo);
         else
-            COUT(2) << "LuaState: Cannot include file '" << filename << "'." << std::endl;
+        {
+            COUT(2) << "LuaState: Cannot include file '" << filename << "' (not found)." << std::endl;
+            return false;
+        }
     }
 
-    void LuaState::includeString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo)
+    bool LuaState::includeString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo)
     {
         // Parse string with provided include parser (otherwise don't preparse at all)
         std::string luaInput;
@@ -120,7 +130,7 @@
             this->sourceCodeMap_[sourceFileInfo->filename] = code;
         }
 
-        this->doString(luaInput, sourceFileInfo);
+        bool returnValue = this->doString(luaInput, sourceFileInfo);
 
         if (sourceFileInfo != NULL)
         {
@@ -128,18 +138,23 @@
             if (sourceFileInfo != NULL)
                 this->sourceCodeMap_.erase(sourceFileInfo->filename);
         }
+
+        return returnValue;
     }
 
-    void LuaState::doFile(const std::string& filename)
+    bool LuaState::doFile(const std::string& filename)
     {
         shared_ptr<ResourceInfo> sourceInfo = this->getFileInfo(filename);
         if (sourceInfo != NULL)
-            this->doString(Resource::open(sourceInfo)->getAsString(), sourceInfo);
+            return this->doString(Resource::open(sourceInfo)->getAsString(), sourceInfo);
         else
-            COUT(2) << "LuaState: Cannot do file '" << filename << "'." << std::endl;
+        {
+            COUT(2) << "LuaState: Cannot do file '" << filename << "' (not found)." << std::endl;
+            return false;
+        }
     }
 
-    void LuaState::doString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo)
+    bool LuaState::doString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo)
     {
         // Save the old source file info
         shared_ptr<ResourceInfo> oldSourceFileInfo = sourceFileInfo_;
@@ -160,39 +175,71 @@
             chunkname = code;
         }
 
-        int error = 0;
+        // Push custom error handler that uses the debugger
+        int errorHandler = 1;
+        lua_getglobal(this->luaState_, "errorHandler");
+        if (lua_isnil(this->luaState_, -1))
+        {
+            lua_pop(this->luaState_, 1);
+            errorHandler = 0;
+        }
+
 #if LUA_VERSION_NUM != 501
         LoadS ls;
         ls.s = code.c_str();
         ls.size = code.size();
-        error = lua_load(luaState_, &orxonox::LuaState::lua_Chunkreader, &ls, chunkname.c_str());
+        int error = lua_load(luaState_, &orxonox::LuaState::lua_Chunkreader, &ls, chunkname.c_str());
 #else
-        error = luaL_loadbuffer(luaState_, code.c_str(), code.size(), chunkname.c_str());
+        int error = luaL_loadbuffer(luaState_, code.c_str(), code.size(), chunkname.c_str());
 #endif
 
-        // execute the chunk
+        switch (error)
+        {
+        case LUA_ERRSYNTAX: // Syntax error
+            COUT(1) << "Lua syntax error: " << lua_tostring(luaState_, -1) << std::endl;
+            break;
+        case LUA_ERRMEM:    // Memory allocation error
+            COUT(1) << "Lua memory allocation error: Consult your dentist immediately!" << std::endl;
+            lua_pop(luaState_, 1);
+            break;
+        }
+
         if (error == 0)
-            error = lua_pcall(luaState_, 0, 1, 0);
-        if (error != 0)
         {
-            std::string origin;
-            if (sourceFileInfo != NULL)
-                origin = " originating from " + sourceFileInfo_->filename;
-            COUT(1) << "Error in Lua-script" << origin << ": " << lua_tostring(luaState_, -1) << std::endl;
-            // return value is true (not nil!)
-            lua_pushboolean(luaState_, 1);
+            // Execute the chunk in protected mode with an error handler function (stack index)
+            error = lua_pcall(luaState_, 0, 1, errorHandler);
+
+            switch (error)
+            {
+            case LUA_ERRRUN: // Runtime error
+                // Remove error string from stack (we already display the error in the
+                // 'errorHandler' Lua function in LuaStateInit.lua)
+                lua_pop(luaState_, 1);
+                break;
+            case LUA_ERRERR: // Error in the error handler
+                COUT(1) << "Lua error in error handler: " << lua_tostring(luaState_, -1) << std::endl;
+                break;
+            case LUA_ERRMEM: // Memory allocation error
+                COUT(1) << "Lua memory allocation error: Consult your dentist immediately!" << std::endl;
+                lua_pop(luaState_, 1);
+                break;
+            }
         }
-        if (lua_isnil(luaState_, -1))
+
+        if (error != 0)
         {
-            // Nil return values cause problems
-            lua_pop(luaState_, 1);
-            lua_pushboolean(luaState_, 1);
+            // Push a nil return value
+            lua_pushnil(luaState_);
         }
-        // Push return value because it will get lost since the return value of this function is void
+
+        // Set return value to a global variable because we cannot return a table in this function
+        // here. It would work for numbers, pointers and strings, but certainly not for Lua tables.
         lua_setglobal(luaState_, "LuaStateReturnValue");
 
         // Load the old info again
         sourceFileInfo_ = oldSourceFileInfo;
+
+        return (error == 0);
     }
 
     void LuaState::luaPrint(const std::string& str)
@@ -229,6 +276,11 @@
             return Resource::open(info)->getAsString();
     }
 
+    bool LuaState::usingIOConsole() const
+    {
+        return IOConsole::exists();
+    }
+
 #if LUA_VERSION_NUM != 501
     const char * LuaState::lua_Chunkreader(lua_State *L, void *data, size_t *size)
     {

Modified: code/branches/gamestate/src/libraries/core/LuaState.h
===================================================================
--- code/branches/gamestate/src/libraries/core/LuaState.h	2010-03-30 14:34:01 UTC (rev 6659)
+++ code/branches/gamestate/src/libraries/core/LuaState.h	2010-03-30 22:04:30 UTC (rev 6660)
@@ -70,11 +70,11 @@
         LuaState();
         ~LuaState();
 
-        void doFile(const std::string& filename); // tolua_export
-        void doString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo = shared_ptr<ResourceInfo>());
+        bool doFile(const std::string& filename); // tolua_export
+        bool doString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo = shared_ptr<ResourceInfo>());
 
-        void includeFile(const std::string& filename); // tolua_export
-        void includeString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo = shared_ptr<ResourceInfo>());
+        bool includeFile(const std::string& filename); // tolua_export
+        bool includeString(const std::string& code, const shared_ptr<ResourceInfo>& sourceFileInfo = shared_ptr<ResourceInfo>());
 
         void luaPrint(const std::string& str); // tolua_export
         void luaLog(unsigned int level, const std::string& message); // tolua_export
@@ -91,6 +91,8 @@
         const shared_ptr<ResourceInfo>& getDefaultResourceInfo() { return this->sourceFileInfo_; }
 
         Functor* createLuaFunctor(const std::string& code) { return new LuaFunctor(code, this); } // tolua_export
+        //! Tells about whether IOConsole was activated. The Lua debugger only works with a normal console.
+        bool usingIOConsole() const; // tolua_export
 
         static bool addToluaInterface(int (*function)(lua_State*), const std::string& name);
         static bool removeToluaInterface(const std::string& name);




More information about the Orxonox-commit mailing list