--- /dev/null
+
+/*******************************************************************************
+
+ Copyright (c) 2009, Charles McGarvey
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*******************************************************************************/
+
+#ifndef _MOOF_SCRIPT_HH_
+#define _MOOF_SCRIPT_HH_
+
+/**
+ * @file Script.hh
+ * A thin wrapper over Lua. This is not meant as a complicated binding package
+ * between C++ and Lua. It does not try to make the boundary invisible. It
+ * does not hide the concept of the Lua stack, but rather provides that
+ * mechanism with a certain level of abstraction while also providing a cleaner,
+ * more consistent API.
+ */
+
+#include <list>
+#include <string>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <lua.hpp>
+
+#include <Moof/Exception.hh>
+
+
+namespace Mf {
+
+
+class Script;
+typedef boost::shared_ptr<Script> ScriptP;
+
+
+struct Script
+{
+ typedef boost::function<int(Script&)> Function;
+
+ enum TYPE
+ {
+ NONE = LUA_TNONE,
+ NIL = LUA_TNIL,
+ BOOLEAN = LUA_TBOOLEAN,
+ LIGHTUSERDATA = LUA_TLIGHTUSERDATA,
+ NUMBER = LUA_TNUMBER,
+ STRING = LUA_TSTRING,
+ TABLE = LUA_TTABLE,
+ FUNCTION = LUA_TFUNCTION,
+ USERDATA = LUA_TUSERDATA,
+ THREAD = LUA_TTHREAD
+ };
+
+ enum STATUS
+ {
+ SUCCESS = 0,
+ YIELD = LUA_YIELD,
+ RUNTIME_ERROR = LUA_ERRRUN,
+ SYNTAX_ERROR = LUA_ERRSYNTAX,
+ MEMORY_ERROR = LUA_ERRMEM,
+ HANDLER_ERROR = LUA_ERRERR,
+ FILE_ERROR = LUA_ERRFILE
+ };
+
+ enum PSEUDO_INDEX
+ {
+ REGISTRY = LUA_REGISTRYINDEX,
+ ENVIRONMENT = LUA_ENVIRONINDEX,
+ GLOBALS = LUA_GLOBALSINDEX
+ };
+
+ /**
+ * This is the most noticeable abstraction on top of the standard Lua API.
+ * A Value object represents a value on the stack. More specifically, it
+ * represents a position on the stack. The distinction is only important
+ * when values are moved around on the stack or if the Value represents a
+ * negative index on the stack (the value of which will change as things are
+ * pushed onto and popped from the stack).
+ */
+
+ struct Value
+ {
+ /**
+ * You have direct access to the index of the value on the stack being
+ * represented.
+ */
+
+ int index;
+
+
+ /**
+ * A default-constructed Value is invalid until a valid Value is
+ * assigned to it. The only method that should be called on such a
+ * Value is isValid(), otherwise chaos may ensue. In this case, the
+ * Value will be invalid even if index is manually changed to a valid
+ * index. You have to index the script itself to get a valid Value.
+ */
+ Value() :
+ index(0),
+ state(0) {}
+
+ Value(lua_State* s, int i) :
+ index(i),
+ state(s) {}
+
+ // check the type of the value
+ bool isBoolean() const { return (bool)lua_isboolean(state, index); }
+ bool isFunction() const { return (bool)lua_isfunction(state, index); }
+ bool isNil() const { return (bool)lua_isnil(state, index); }
+ bool isNone() const { return (bool)lua_isnone(state, index); }
+ bool isValid() const { return state != 0 && !isNone(); }
+ bool isNoneOrNil() const { return (bool)lua_isnoneornil(state, index); }
+ bool isNumber() const { return (bool)lua_isnumber(state, index); }
+ bool isString() const { return (bool)lua_isstring(state, index); }
+ bool isTable() const { return (bool)lua_istable(state, index); }
+ bool isThread() const { return (bool)lua_isthread(state, index); }
+ bool isData() const { return (bool)lua_isuserdata(state, index); }
+ bool isLightData() const { return (bool)lua_islightuserdata(state, index); }
+
+ /**
+ * Get the type of the value.
+ */
+
+ TYPE getType() const
+ {
+ return (TYPE)lua_type(state, index);
+ }
+
+ /**
+ * Get the name of the type of the value as a string.
+ */
+
+ std::string getTypeName() const
+ {
+ return std::string(lua_typename(state, (int)getType()));
+ }
+
+ /**
+ * Get a pointer value (for userdata, tables, threads, and functions).
+ */
+
+ const void* getIdentifier() const
+ {
+ return lua_topointer(state, index);
+ }
+
+
+ bool operator == (const Value& rhs) const
+ {
+ return (bool)lua_equal(state, index, rhs.index);
+ }
+ bool operator != (const Value& rhs) const
+ {
+ return !(*this == rhs);
+ }
+ bool operator < (const Value& rhs) const
+ {
+ return (bool)lua_lessthan(state, index, rhs.index);
+ }
+ bool operator <= (const Value& rhs) const
+ {
+ return *this < rhs || *this == rhs;
+ }
+ bool operator > (const Value& rhs) const
+ {
+ return !(*this <= rhs);
+ }
+ bool operator >= (const Value& rhs) const
+ {
+ return !(*this < rhs);
+ }
+ operator bool () const
+ {
+ return (bool)lua_toboolean(state, index);
+ }
+
+ Value& operator = (const Value& rhs)
+ {
+ rhs.pushCopy();
+ replaceWithTop();
+ return *this;
+ }
+
+
+ /**
+ * Get the length of the value according to the definition given by Lua.
+ */
+
+ size_t getLength() const
+ {
+ return lua_objlen(state, index);
+ }
+
+
+ /**
+ * Convert the underlying value to a C++ type.
+ */
+
+ template <typename T>
+ void get(T& value) const
+ {
+ value = (T)lua_tointeger(state, index);
+ }
+
+ void get(bool& value) const
+ {
+ value = (bool)lua_toboolean(state, index);
+ }
+
+ void get(float& value) const
+ {
+ value = (float)lua_tonumber(state, index);
+ }
+ void get(double& value) const
+ {
+ value = (double)lua_tonumber(state, index);
+ }
+
+ void get(std::string& value) const
+ {
+ size_t size;
+ const char* str = lua_tolstring(state, index, &size);
+ value.assign(str, size);
+ }
+
+
+ void set(std::string& value)
+ {
+ }
+
+ //template <typename T>
+ //void get(const std::string& field, T& value) const
+ //{
+ ////lua_getfield(state_, field.c_str());
+ //pushField(field);
+ //get(-1, value);
+ //lua_pop(state_, 1);
+ //}
+
+
+ /**
+ * Copy the value and push the copy to the stack.
+ */
+
+ void pushCopy() const
+ {
+ lua_pushvalue(state, index);
+ }
+
+ /**
+ * Replace this value with the value at the top of the stack.
+ */
+
+ void replaceWithTop()
+ {
+ lua_replace(state, index);
+ }
+
+ void remove()
+ {
+ lua_remove(state, index);
+ }
+
+ /**
+ * Inserts the top-most value on the stack at position index, shifting other
+ * values as needed.
+ */
+
+ void insertTopHere()
+ {
+ lua_insert(state, index);
+ }
+
+
+ void pushMetatable() const
+ {
+ lua_getmetatable(state, index);
+ }
+
+ void pushField() const
+ {
+ lua_gettable(state, index);
+ }
+
+ void pushField(const std::string& name) const
+ {
+ lua_getfield(state, index, name.c_str());
+ }
+
+
+ private:
+
+ lua_State* state;
+ };
+
+
+ Script() :
+ state_(luaL_newstate())
+ {
+ lua_pushlightuserdata(state_, this);
+ lua_setfield(state_, LUA_REGISTRYINDEX, "_script_obj");
+ }
+
+ ~Script()
+ {
+ if (isMainThread_) lua_close(state_);
+ }
+
+
+ static ScriptP alloc()
+ {
+ return ScriptP(new Script);
+ }
+
+
+ void importStandardLibraries()
+ {
+ luaL_openlibs(state_);
+ }
+
+ void importFunction(const std::string& name, const Function& function)
+ {
+ push(function);
+ lua_setglobal(state_, name.c_str());
+ }
+
+
+ STATUS doString(const std::string& commands)
+ {
+ return (STATUS)luaL_dostring(state_, commands.c_str());
+ }
+
+ STATUS doFile(const std::string& file)
+ {
+ return (STATUS)luaL_dofile(state_, file.c_str());
+ }
+
+
+ /**
+ * Thread-handling methods.
+ */
+
+ Script pushNewThread()
+ {
+ return Script(state_);
+ }
+
+ void pushThread()
+ {
+ lua_pushthread(state_);
+ }
+
+ STATUS resume(int nargs)
+ {
+ return (STATUS)lua_resume(state_, nargs);
+ }
+
+ STATUS getStatus() const
+ {
+ return (STATUS)lua_status(state_);
+ }
+
+ int yield(int results)
+ {
+ return lua_yield(state_, results);
+ }
+
+ bool isMainThread() const
+ {
+ return isMainThread_;
+ }
+
+
+ /**
+ * Get significant values.
+ */
+
+ Value getGlobalTable() const
+ {
+ return Value(state_, GLOBALS);
+ }
+
+ Value getRegistryTable() const
+ {
+ return Value(state_, REGISTRY);
+ }
+
+ Value getEnvironmentTable() const
+ {
+ return Value(state_, ENVIRONMENT);
+ }
+
+ Value getTop() const
+ {
+ return Value(state_, lua_gettop(state_));
+ }
+
+ /**
+ * Get the size of the stack; this is also the index of the top-most value.
+ */
+
+ int getSize() const
+ {
+ return lua_gettop(state_);
+ }
+
+ void setSize(int size)
+ {
+ lua_settop(state_, size);
+ }
+
+ void clear()
+ {
+ setSize(0);
+ }
+
+
+ /**
+ * Makes sure there is at least extra more places on the stack. Returns
+ * false if space couldn't be created. Just like with the regular Lua API,
+ * you are responsible to make sure the stack is big enough to hold whatever
+ * you want to push on it. This is usually only an issue if you're pushing
+ * stuff in a loop.
+ */
+
+ bool checkStack(int extra)
+ {
+ return (bool)lua_checkstack(state_, extra);
+ }
+
+
+ /**
+ * Concatenates the top-most n values on the stack.
+ */
+
+ void concat(int n)
+ {
+ lua_concat(state_, n);
+ }
+
+
+ /**
+ * Push some values onto the stack.
+ */
+
+ template <typename T>
+ void push(T value)
+ {
+ lua_pushinteger(state_, lua_Integer(value));
+ }
+
+ void push(bool value)
+ {
+ lua_pushboolean(state_, int(value));
+ }
+
+ void push(float value)
+ {
+ lua_pushnumber(state_, (lua_Number)value);
+ }
+ void push(double value)
+ {
+ lua_pushnumber(state_, (lua_Number)value);
+ }
+
+ void push(const std::string& value)
+ {
+ lua_pushlstring(state_, value.c_str(), value.length());
+ }
+ void push(const char* value, size_t length)
+ {
+ lua_pushlstring(state_, value, length);
+ }
+
+ void push(const Function& function)
+ {
+ functions_.push_back(function);
+
+ lua_pushlightuserdata(state_, (void*)&functions_.back());
+ lua_pushcclosure(state_, dispatchCall, 1);
+ }
+
+ void push(void* data)
+ {
+ lua_pushlightuserdata(state_, data);
+ }
+
+ void pushNil()
+ {
+ lua_pushnil(state_);
+ }
+
+ void pushFromThread(Script& thread, int n)
+ {
+ lua_xmove(thread.state_, state_, n);
+ }
+
+ STATUS pushCode(const std::string& filename)
+ {
+ return (STATUS)luaL_loadfile(state_, filename.c_str());
+ }
+
+ STATUS pushCode(const std::string& name, const char* buffer, size_t size)
+ {
+ return (STATUS)luaL_loadbuffer(state_, buffer, size, name.c_str());
+ }
+
+ void* pushNewData(size_t size)
+ {
+ return lua_newuserdata(state_, size);
+ }
+
+ void pushNewTable()
+ {
+ lua_newtable(state_);
+ }
+
+
+ /**
+ * Call a function on the stack. The correct procedure is to push a
+ * function onto the stack followed by nargs arguments. This method will
+ * pop them off upon return, leaving up to nresults return values (default
+ * is any number of return values, depending on the callee).
+ */
+
+ STATUS call(int nargs, int nresults = LUA_MULTRET)
+ {
+ return (STATUS)lua_pcall(state_, nargs, nresults, 0);
+ }
+
+
+ /**
+ * Pops n values from the top of the stack.
+ */
+
+ void pop(int n)
+ {
+ lua_pop(state_, n);
+ }
+
+
+ /**
+ * Index into the stack to get a Value.
+ */
+
+ Value operator [] (int index) const
+ {
+ return Value(state_, index);
+ }
+
+
+ /**
+ * Getting and setting fields of a table.
+ */
+
+ void get(const std::string& field, int index = GLOBALS) const
+ {
+ lua_getfield(state_, index, field.c_str());
+ }
+
+ void set(const std::string& field, int index = GLOBALS)
+ {
+ lua_setfield(state_, index, field.c_str());
+ }
+
+
+ /**
+ * Control over the garbage collection process.
+ */
+
+ void collectAll()
+ {
+ lua_gc(state_, LUA_GCCOLLECT, 0);
+ }
+
+ void stopCollector()
+ {
+ lua_gc(state_, LUA_GCSTOP, 0);
+ }
+
+ void restartCollector()
+ {
+ lua_gc(state_, LUA_GCRESTART, 0);
+ }
+
+ int getUsedMemory() const
+ {
+ // in kilobytes
+ return lua_gc(state_, LUA_GCCOUNT, 0);
+ }
+
+ void collectStep(int step)
+ {
+ lua_gc(state_, LUA_GCSTEP, step);
+ }
+
+ void tuneCollector(int pause, int step)
+ {
+ lua_gc(state_, LUA_GCSETPAUSE, pause);
+ lua_gc(state_, LUA_GCSETSTEPMUL, step);
+ }
+
+
+
+ struct Exception : public Mf::Exception
+ {
+ explicit Exception(unsigned error) :
+ Mf::Exception(error) {}
+
+ void raise()
+ {
+ throw *this;
+ }
+ };
+
+
+private:
+
+ Script(lua_State* state) :
+ state_(lua_newthread(state)),
+ isMainThread_(false) {}
+
+ static int dispatchCall(lua_State* state)
+ {
+ const Function* function = (const Function*)lua_touserdata(state,
+ lua_upvalueindex(1));
+
+ lua_getfield(state, LUA_REGISTRYINDEX, "_script_obj");
+ Script* script = (Script*)lua_touserdata(state, -1);
+ lua_pop(state, 1);
+
+ return (*function)(*script);
+ }
+
+ lua_State* state_;
+ bool isMainThread_;
+ std::list<Function> functions_;
+};
+
+
+} // namespace Mf
+
+#endif // _MOOF_SCRIPT_HH_
+
+/** vim: set ts=4 sw=4 tw=80: *************************************************/
+