]> Dogcows Code - chaz/yoink/blobdiff - src/moof/script.hh
the massive refactoring effort
[chaz/yoink] / src / moof / script.hh
diff --git a/src/moof/script.hh b/src/moof/script.hh
new file mode 100644 (file)
index 0000000..85973dd
--- /dev/null
@@ -0,0 +1,1063 @@
+
+/*]  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_
+
This page took 0.037933 seconds and 4 git commands to generate.