--- /dev/null
+
+/*] Copyright (c) 2009-2010, Charles McGarvey [**************************
+**] All rights reserved.
+*
+* vi:ts=4 sw=4 tw=75
+*
+* Distributable under the terms and conditions of the 2-clause BSD license;
+* see the file COPYING for a complete text of the license.
+*
+**************************************************************************/
+
+#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 is not meant to obscure the division
+ * between C++ and Lua but rather to clarify it and make it more
+ * manageable. 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 <iostream>
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <lua.hpp>
+
+
+namespace moof {
+
+
+class script;
+typedef boost::shared_ptr<script> script_ptr;
+
+
+class script
+{
+public:
+
+ typedef boost::function<int(script&)> function;
+
+ enum status
+ {
+ success = 0,
+ yielding = LUA_YIELD,
+ runtime_error = LUA_ERRRUN,
+ syntax_error = LUA_ERRSYNTAX,
+ memory_error = LUA_ERRMEM,
+ handler_error = LUA_ERRERR,
+ file_error = LUA_ERRFILE
+ };
+
+ enum pseudoindex
+ {
+ registry_index = LUA_REGISTRYINDEX,
+ environment_index = LUA_ENVIRONINDEX,
+ globals_index = LUA_GLOBALSINDEX
+ };
+
+ /**
+ * This is the most prominent abstraction on top of the standard Lua
+ * API. A slot object represents a value on the stack. More
+ * specifically, it represents a position on the stack. The
+ * distinction is only important when objects are moved around on the
+ * stack or if the slot represents a negative index on the stack (the
+ * value of which will change as things are pushed onto and popped from
+ * the stack).
+ */
+
+ struct slot
+ {
+ /**
+ * You have direct access to the index of the value on the stack
+ * being represented.
+ */
+
+ int index;
+
+
+ enum type
+ {
+ none = LUA_TNONE,
+ nil = LUA_TNIL,
+ boolean = LUA_TBOOLEAN,
+ light_data = LUA_TLIGHTUSERDATA,
+ number = LUA_TNUMBER,
+ string = LUA_TSTRING,
+ table = LUA_TTABLE,
+ function = LUA_TFUNCTION,
+ data = LUA_TUSERDATA,
+ thread = LUA_TTHREAD
+ };
+
+
+ slot(const class script& s, int i = 0) :
+ index(i),
+ script_(const_cast<class script&>(s)) {}
+
+ /**
+ * A copied value presently points to the same value, except the
+ * real index is used. That means that if a value that refers to a
+ * frame referenced from the top of the stack will have its
+ * normalized index copied into the new value object.
+ */
+
+ //slot(const slot& copy) :
+ //index(copy.positiveIndex()),
+ //script_(copy.script_) {}
+
+
+ // check the type of the value
+ bool is_boolean() const
+ { return (bool)lua_isboolean(script_.state_, index); }
+ bool is_imported_function() const
+ { return (bool)lua_iscfunction(script_.state_, index); }
+ bool is_function() const
+ { return (bool)lua_isfunction(script_.state_, index); }
+ bool is_nil() const
+ { return (bool)lua_isnil(script_.state_, index); }
+ bool is_none() const
+ { return (bool)lua_isnone(script_.state_, index); }
+ bool is_none_or_nil() const
+ { return (bool)lua_isnoneornil(script_.state_, index); }
+ bool is_number() const
+ { return (bool)lua_isnumber(script_.state_, index); }
+ bool is_string() const
+ { return (bool)lua_isstring(script_.state_, index); }
+ bool is_table() const
+ { return (bool)lua_istable(script_.state_, index); }
+ bool is_thread() const
+ { return (bool)lua_isthread(script_.state_, index); }
+ bool is_data() const
+ { return (bool)lua_isuserdata(script_.state_, index); }
+ bool is_light_data() const
+ { return (bool)lua_islightuserdata(script_.state_, index); }
+
+ /**
+ * Check the value and throw an error if its the wrong type.
+ * There's a little caveat: This method never returns because it
+ * does a long jump. Consequently, constructed C++ objects which
+ * exist on the stack between the current frame and some lua
+ * function will not be destructed. That's not a problem for
+ * objects that only exist on the stack, but any objects that
+ * allocate memory on the heap (such as containers or strings) will
+ * leak. Therefore, you should only call this method after
+ * cleaning up such objects. The best thing to do for defining
+ * functions is to simply check all the parameters at the get-go
+ * before any C++ objects are even constructed.
+ */
+
+ void require_type(type t) const
+ {
+ if (t != type())
+ {
+ luaL_typerror(script_.state_, index,
+ lua_typename(script_.state_, t));
+ }
+ }
+
+ void raise(const char* error)
+ {
+ luaL_argerror(script_.state_, index, error);
+ }
+
+
+ slot& require_boolean()
+ {
+ if (!is_boolean())
+ {
+ luaL_typerror(script_.state_, index, "boolean");
+ }
+ return *this;
+ }
+ slot& require_number()
+ {
+ if (!is_number())
+ {
+ luaL_typerror(script_.state_, index, "number");
+ }
+ return *this;
+ }
+ slot& require_string()
+ {
+ if (!is_string())
+ {
+ luaL_typerror(script_.state_, index, "string");
+ }
+ return *this;
+ }
+ slot& require_table()
+ {
+ if (!is_table())
+ {
+ luaL_typerror(script_.state_, index, "table");
+ }
+ return *this;
+ }
+ slot& require_function()
+ {
+ if (!is_function())
+ {
+ luaL_typerror(script_.state_, index, "function");
+ }
+ return *this;
+ }
+ slot& require_data()
+ {
+ if (!is_data())
+ {
+ luaL_typerror(script_.state_, index, "data");
+ }
+ return *this;
+ }
+ slot& require_nil()
+ {
+ if (!is_nil())
+ {
+ luaL_typerror(script_.state_, index, "nil");
+ }
+ return *this;
+ }
+ slot& require_thread()
+ {
+ if (!is_thread())
+ {
+ luaL_typerror(script_.state_, index, "thread");
+ }
+ return *this;
+ }
+
+
+ /**
+ * Get the type of the value.
+ */
+
+ enum type type() const
+ {
+ return (enum type)lua_type(script_.state_, index);
+ }
+
+ /**
+ * Get the name of the type of the value as a string.
+ */
+
+ std::string type_name() const
+ {
+ return std::string(luaL_typename(script_.state_, index));
+ }
+
+
+ /**
+ * Get the length of the value according to the definition given by
+ * Lua.
+ */
+
+ size_t length() const
+ {
+ return lua_objlen(script_.state_, index);
+ }
+
+ int positiveIndex() const
+ {
+ if (index < 0) return index + lua_gettop(script_.state_) + 1;
+ else return index;
+ }
+
+
+ /**
+ * Get a pointer value (for userdata, tables, threads, and
+ * functions).
+ */
+
+ const void* id() const
+ {
+ return lua_topointer(script_.state_, index);
+ }
+
+ bool is_identical(const slot& rhs) const
+ {
+ return &script_ == &(rhs.script_) && index == rhs.index;
+ }
+
+ operator bool () const
+ {
+ return !is_none();
+ }
+
+
+ bool operator == (const slot& rhs) const
+ {
+ return (bool)lua_equal(script_.state_, index, rhs.index);
+ }
+ bool operator != (const slot& rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ bool operator < (const slot& rhs) const
+ {
+ return (bool)lua_lessthan(script_.state_, index, rhs.index);
+ }
+ bool operator <= (const slot& rhs) const
+ {
+ return *this < rhs || *this == rhs;
+ }
+ bool operator > (const slot& rhs) const
+ {
+ return !(*this <= rhs);
+ }
+ bool operator >= (const slot& rhs) const
+ {
+ return !(*this < rhs);
+ }
+
+
+ /**
+ * Convert the underlying value to a C++ type.
+ */
+
+ template <class T>
+ bool get(T& value) const
+ {
+ if (is_number())
+ {
+ value = (T)lua_tointeger(script_.state_, index);
+ return true;
+ }
+ return false;
+ }
+
+ bool get(float& value) const
+ {
+ if (is_number())
+ {
+ value = (float)lua_tonumber(script_.state_, index);
+ return true;
+ }
+ return false;
+ }
+ bool get(double& value) const
+ {
+ if (is_number())
+ {
+ value = (double)lua_tonumber(script_.state_, index);
+ return true;
+ }
+ return false;
+ }
+
+ bool get(bool& value) const
+ {
+ if (is_boolean())
+ {
+ value = (bool)lua_toboolean(script_.state_, index);
+ return true;
+ }
+ return false;
+ }
+
+ bool get(const char*& value, size_t& size) const
+ {
+ if (is_string())
+ {
+ value = lua_tolstring(script_.state_, index, &size);
+ return true;
+ }
+ return false;
+ }
+
+ bool get(std::string& value) const
+ {
+ const char* str;
+ size_t size;
+ if (get(str, size))
+ {
+ value.assign(str, size);
+ return true;
+ }
+ return false;
+ }
+
+ bool get(void*& value) const
+ {
+ if (is_data())
+ {
+ value = lua_touserdata(script_.state_, index);
+ return true;
+ }
+ return false;
+ }
+
+ template <class T>
+ bool get(std::vector<T>& array) const
+ {
+ if (!is_table()) return false;
+
+ array.clear();
+
+ slot value = script_[-1];
+ int realIndex = positiveIndex();
+
+ bool done = false;
+ for (int i = 1; !done; ++i)
+ {
+ lua_rawgeti(script_.state_, realIndex, i);
+
+ T v;
+ if (value.get(v)) array.push_back(v);
+ else done = true;
+
+ script_.pop();
+ }
+
+ return true;
+ }
+
+ template <class T>
+ bool get(std::map<std::string,T>& dictionary) const
+ {
+ if (!is_table()) return false;
+
+ dictionary.clear();
+
+ slot key = script_[-2];
+ slot value = script_[-1];
+ int realIndex = positiveIndex();
+
+ script_.push_nil();
+ while (lua_next(script_.state_, realIndex) != 0)
+ {
+ std::string k;
+ if (!key.is_number() && key.get(k))
+ {
+ T v;
+ if (value.get(v)) dictionary[k] = v;
+ }
+ script_.pop();
+ }
+ script_.pop();
+
+ return true;
+ }
+
+ /**
+ * Get the value of a field from the table.
+ */
+
+ template <class T, class V>
+ bool get(T& value, V field) const
+ {
+ bool ret = push_field(field).get(value);
+ script_.pop();
+ return ret;
+ }
+
+
+ template <class T, class V>
+ void set_field(T field, V value)
+ {
+ script_.push(field);
+ script_.push(value);
+ set_field();
+ }
+
+ void set_field()
+ {
+ lua_settable(script_.state_, index);
+ }
+
+
+ template <class T>
+ void set_field(const std::string& field, T value)
+ {
+ set_field(field.c_str(), value);
+ }
+ template <class T>
+ void set_field(const char* field, T value)
+ {
+ script_.push(value);
+ lua_setfield(script_.state_, index, field);
+ }
+
+
+ /**
+ * This set method, as opposed to the others, sets the value of the
+ * actual slot. The others set table values.
+ */
+ template <class T>
+ void set(T value)
+ {
+ script_.push(value);
+ replace();
+ }
+
+ void set()
+ {
+ replace();
+ }
+
+
+ /**
+ * Replace this value with the value at the top of the stack.
+ */
+
+ void replace()
+ {
+ lua_replace(script_.state_, index);
+ }
+
+ void remove()
+ {
+ lua_remove(script_.state_, index);
+ }
+
+ void pop()
+ {
+ // removes this slot, taking with it everything above it
+ script_.pop(script_.stack_size() - index + 1);
+ }
+
+ /**
+ * Inserts the top-most value on the stack at position index,
+ * shifting other values as needed.
+ */
+
+ void insert_top_here()
+ {
+ lua_insert(script_.state_, index);
+ }
+
+
+ /**
+ * Copy the value and push the copy to the stack.
+ */
+
+ slot push_copy() const
+ {
+ lua_pushvalue(script_.state_, index);
+ return script_.top();
+ }
+
+ slot push_metatable() const
+ {
+ lua_getmetatable(script_.state_, index);
+ return script_.top();
+ }
+
+ slot push_environment() const
+ {
+ lua_getfenv(script_.state_, index);
+ return script_.top();
+ }
+
+
+ slot push_field() const
+ {
+ lua_gettable(script_.state_, index);
+ return script_.top();
+ }
+
+ template <class T>
+ slot push_field(T index) const
+ {
+ script_.push(index);
+ return push_field();
+ }
+
+ slot push_field(const std::string& name) const
+ {
+ return push_field(name.c_str());
+ }
+ slot push_field(const char* name) const
+ {
+ lua_getfield(script_.state_, index, name);
+ return script_.top();
+ }
+
+
+ class script& script()
+ {
+ return script_;
+ }
+
+ const class script& script() const
+ {
+ return script_;
+ }
+
+
+ private:
+
+ class script& script_;
+ };
+
+
+ script() :
+ state_(0)
+ {
+ reset();
+ }
+
+ ~script()
+ {
+ destroy();
+ }
+
+
+ static script_ptr alloc()
+ {
+ return script_ptr(new script);
+ }
+
+ void reset()
+ {
+ if (state_) destroy();
+ state_ = luaL_newstate();
+ registry().set_field("Script_hh_Object", (void*)this);
+ }
+
+
+ void import_standard_libraries()
+ {
+ luaL_openlibs(state_);
+ }
+
+ void import_base_library()
+ {
+ lua_pushcfunction(state_, luaopen_base);
+ push(LUA_COLIBNAME);
+ call(1, 0);
+ }
+
+ void import_package_library()
+ {
+ lua_pushcfunction(state_, luaopen_package);
+ push(LUA_LOADLIBNAME);
+ call(1, 0);
+ }
+
+ void import_string_library()
+ {
+ lua_pushcfunction(state_, luaopen_string);
+ push(LUA_STRLIBNAME);
+ call(1, 0);
+ }
+
+ void import_table_library()
+ {
+ lua_pushcfunction(state_, luaopen_table);
+ push(LUA_TABLIBNAME);
+ call(1, 0);
+ }
+
+ void import_math_library()
+ {
+ lua_pushcfunction(state_, luaopen_math);
+ push(LUA_MATHLIBNAME);
+ call(1, 0);
+ }
+
+ void import_io_library()
+ {
+ lua_pushcfunction(state_, luaopen_io);
+ push(LUA_IOLIBNAME);
+ call(1, 0);
+ }
+
+ void import_os_library()
+ {
+ lua_pushcfunction(state_, luaopen_os);
+ push(LUA_OSLIBNAME);
+ call(1, 0);
+ }
+
+ void import_debug_library()
+ {
+ lua_pushcfunction(state_, luaopen_debug);
+ push(LUA_DBLIBNAME);
+ call(1, 0);
+ }
+
+
+ void import_function(const std::string& name, const function& function)
+ {
+ push(function);
+ lua_setglobal(state_, name.c_str());
+ }
+
+ status do_string(const std::string& commands)
+ {
+ return status(luaL_dostring(state_, commands.c_str()));
+ }
+
+ status do_file(const std::string& file)
+ {
+ return status(luaL_dofile(state_, file.c_str()));
+ }
+
+
+ /**
+ * Thread-handling methods.
+ */
+
+ script push_new_thread()
+ {
+ return script(state_);
+ }
+
+ void push_thread()
+ {
+ 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 is_main_thread() const
+ {
+ return is_main_thread_;
+ }
+
+
+ /**
+ * Throw an error with the value at the top of the stack. This method
+ * never returns because it does a long jump. Consequently,
+ * constructed C++ objects which exist on the stack between the
+ * current frame and some lua function will not be destructed. That's
+ * not a problem for objects that only exist on the stack, but any
+ * objects that allocate memory on the heap (such as containers or
+ * strings) will leak. Therefore, you should only call this method
+ * after cleaning up such objects.
+ */
+
+ void raise()
+ {
+ lua_error(state_);
+ }
+
+
+ /**
+ * Get significant values.
+ */
+
+ slot globals() const
+ {
+ return slot(*this, globals_index);
+ }
+
+ slot registry() const
+ {
+ return slot(*this, registry_index);
+ }
+
+ slot environment() const
+ {
+ return slot(*this, environment_index);
+ }
+
+ slot top() const
+ {
+ return slot(*this, stack_size());
+ }
+
+
+ /**
+ * Set the size of the stack.
+ * \param size The stack size.
+ */
+ void stack_size(int size)
+ {
+ lua_settop(state_, size);
+ }
+
+ /**
+ * Get the size of the stack; this is also the index of the top-most
+ * value.
+ * \return The stack size.
+ */
+ int stack_size() const
+ {
+ return lua_gettop(state_);
+ }
+
+ /**
+ * Clear the stack, setting its size to zero.
+ */
+ void clear_stack()
+ {
+ stack_size(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 slots on the stack.
+ */
+ void concatenate(int n = 2)
+ {
+ lua_concat(state_, n);
+ }
+
+
+ /**
+ * Push some values onto the stack.
+ */
+ template <class T>
+ slot push(T value)
+ {
+ lua_pushinteger(state_, lua_Integer(value));
+ return top();
+ }
+
+ slot push(bool value)
+ {
+ lua_pushboolean(state_, int(value));
+ return top();
+ }
+
+ slot push(float value)
+ {
+ lua_pushnumber(state_, (lua_Number)value);
+ return top();
+ }
+ slot push(double value)
+ {
+ lua_pushnumber(state_, (lua_Number)value);
+ return top();
+ }
+
+ slot push(const std::string& value)
+ {
+ lua_pushlstring(state_, value.c_str(), value.length());
+ return top();
+ }
+ slot push(const char* value)
+ {
+ lua_pushstring(state_, value);
+ return top();
+ }
+ slot push(const char* value, size_t length)
+ {
+ lua_pushlstring(state_, value, length);
+ return top();
+ }
+
+ slot push(const function& function)
+ {
+ functions_.push_back(function);
+ lua_pushlightuserdata(state_, (void*)&functions_.back());
+ lua_pushcclosure(state_, dispatch_call, 1);
+ return top();
+ }
+
+ slot push(void* data)
+ {
+ lua_pushlightuserdata(state_, data);
+ return top();
+ }
+
+ slot push_nil()
+ {
+ lua_pushnil(state_);
+ return top();
+ }
+
+ slot push_from_thread(script& thread, int n)
+ {
+ lua_xmove(thread.state_, state_, n);
+ return top();
+ }
+
+ slot push_code(const std::string& file, status& result)
+ {
+ result = status(luaL_loadfile(state_, file.c_str()));
+ return top();
+ }
+
+ slot push_code(const std::string& name,
+ const char* buffer,
+ size_t size,
+ status& result)
+ {
+ result = status(luaL_loadbuffer(state_,
+ buffer, size, name.c_str()));
+ return top();
+ }
+
+ slot push_new_data(void*& data, size_t size)
+ {
+ data = lua_newuserdata(state_, size);
+ return top();
+ }
+
+ slot push_new_table(int narr = 0, int nrec = 0)
+ {
+ lua_createtable(state_, narr, nrec);
+ return top();
+ }
+
+
+ /**
+ * 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 = 0, 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 = 1)
+ {
+ lua_pop(state_, n);
+ }
+
+
+ /**
+ * Index into the stack to get a slot.
+ */
+
+ slot operator [] (int index) const
+ {
+ return slot(*this, index);
+ }
+
+
+ /**
+ * Control over the garbage collection process.
+ */
+
+ void collect_all()
+ {
+ lua_gc(state_, LUA_GCCOLLECT, 0);
+ }
+
+ void stop_collector()
+ {
+ lua_gc(state_, LUA_GCSTOP, 0);
+ }
+
+ void restart_collector()
+ {
+ lua_gc(state_, LUA_GCRESTART, 0);
+ }
+
+ int memory_used() const
+ {
+ // in kilobytes
+ return lua_gc(state_, LUA_GCCOUNT, 0);
+ }
+
+ void collect(int step)
+ {
+ lua_gc(state_, LUA_GCSTEP, step);
+ }
+
+ void tune_collector(int pause, int step)
+ {
+ lua_gc(state_, LUA_GCSETPAUSE, pause);
+ lua_gc(state_, LUA_GCSETSTEPMUL, step);
+ }
+
+
+private:
+
+ script(lua_State* state) :
+ state_(lua_newthread(state)),
+ is_main_thread_(false) {}
+
+ static int dispatch_call(lua_State* state)
+ {
+ const function* function = (const script::function*)lua_touserdata(state,
+ lua_upvalueindex(1));
+
+ lua_getfield(state, LUA_REGISTRYINDEX, "Script_hh_Object");
+ script* script = (moof::script*)lua_touserdata(state, -1);
+ lua_pop(state, 1);
+
+ return (*function)(*script);
+ }
+
+ void destroy()
+ {
+ if (is_main_thread_) lua_close(state_);
+ }
+
+ lua_State* state_;
+ bool is_main_thread_;
+ std::list<function> functions_;
+};
+
+
+inline std::ostream& operator << (std::ostream& stream,
+ const script::slot& slot)
+{
+ std::string str;
+ bool boolean;
+
+ if (slot.get(str))
+ {
+ stream << str;
+ }
+ else if (slot.get(boolean))
+ {
+ if (boolean) stream << "true";
+ else stream << "false";
+ }
+ else if (slot.is_nil())
+ {
+ stream << "nil";
+ }
+ else
+ {
+ stream << slot.type_name() << " (" << slot.id() << ")";
+ }
+
+ return stream;
+}
+
+
+} // namespace moof
+
+#endif // _MOOF_SCRIPT_HH_
+