2011-07-16 10:29:46 -06:00
|
|
|
/* vim: set et sw=3 tw=0 fo=croqlaw cino=t0:
|
|
|
|
*
|
|
|
|
* Luaxx, the C++ Lua wrapper library.
|
|
|
|
* Copyright (c) 2006-2008 Matthew Nicholson
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to
|
|
|
|
* deal in the Software without restriction, including without limitation the
|
|
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
|
* IN THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef LUAXX_H
|
|
|
|
#define LUAXX_H
|
|
|
|
|
|
|
|
#define lua_Integer_long 1
|
|
|
|
#define lua_Integer_int 1
|
|
|
|
|
2012-03-26 09:48:24 -06:00
|
|
|
|
2011-08-08 17:46:32 -06:00
|
|
|
#include "lua.h"
|
|
|
|
#include "lauxlib.h"
|
|
|
|
#include "lualib.h"
|
2012-03-26 09:48:24 -06:00
|
|
|
|
|
|
|
|
2011-07-16 10:29:46 -06:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
#include <new>
|
|
|
|
#include <exception>
|
|
|
|
|
|
|
|
/** @file
|
|
|
|
* Luaxx header file.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @mainpage Luaxx
|
|
|
|
*
|
|
|
|
* Luaxx is a thin wrapper around the Lua C API. The wrapper adds some
|
|
|
|
* convenience functions and integrates well with modern C++.
|
|
|
|
*
|
|
|
|
* Luaxx is not designed like toLua, instead Luaxx is more of a 1 to 1
|
|
|
|
* logical mapping of the lua API in C++. For example: in C you would write
|
|
|
|
* 'lua_pushnumber(L, 3)', in C++ with Luaxx you would write
|
|
|
|
* 'L.push(3)'.
|
|
|
|
*
|
|
|
|
* Every thing is contained in the 'lua' namespace and exceptions are thrown
|
|
|
|
* when a lua API function returns an error. Most of the functionality is
|
|
|
|
* contained in the lua::state class, which can be passed directly to lua C API
|
|
|
|
* functions (the compiler will automatically use the internal lua_State
|
|
|
|
* pointer). See the documentation for that class for more information.
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace lua
|
|
|
|
{
|
|
|
|
void StackDump(lua_State *L);
|
|
|
|
/** A generic lua exception.
|
|
|
|
*/
|
|
|
|
class exception : public std::exception {
|
|
|
|
public:
|
|
|
|
/// Constructor.
|
|
|
|
exception() : std::exception() { }
|
|
|
|
/// Constructor.
|
|
|
|
explicit exception(const char* desc) : std::exception(), description(desc) { }
|
|
|
|
virtual ~exception() throw() { }
|
|
|
|
/** Get a description of the error.
|
|
|
|
* @returns a C-string describing the error
|
|
|
|
*/
|
|
|
|
virtual const char* what() const throw() {
|
|
|
|
return description.c_str();
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
std::string description;
|
|
|
|
};
|
|
|
|
|
|
|
|
/** A lua runtime error.
|
|
|
|
* This is thrown when there was an error executing some lua code.
|
|
|
|
* @note This is not an std::runtime error.
|
|
|
|
*/
|
|
|
|
class runtime_error : public exception {
|
|
|
|
public:
|
|
|
|
/// Constructor.
|
|
|
|
runtime_error() : exception() { }
|
|
|
|
/// Constructor.
|
|
|
|
explicit runtime_error(const char* desc) : exception(desc) { }
|
|
|
|
virtual ~runtime_error() throw() { }
|
|
|
|
};
|
|
|
|
|
|
|
|
/** A syntax error.
|
|
|
|
*/
|
|
|
|
class syntax_error : public exception {
|
|
|
|
public:
|
|
|
|
/// Constructor.
|
|
|
|
syntax_error() : exception() { }
|
|
|
|
/// Constructor.
|
|
|
|
explicit syntax_error(const char* desc) : exception(desc) { }
|
|
|
|
virtual ~syntax_error() throw() { }
|
|
|
|
};
|
|
|
|
|
|
|
|
/** An error loading a lua file.
|
|
|
|
* This is thrown when a call to lua::loadfile failed because the file could
|
|
|
|
* not be opened or read.
|
|
|
|
*/
|
|
|
|
class file_error : public exception {
|
|
|
|
public:
|
|
|
|
/// Constructor.
|
|
|
|
file_error() : exception() { }
|
|
|
|
/// Constructor.
|
|
|
|
explicit file_error(const char* desc) : exception(desc) { }
|
|
|
|
virtual ~file_error() throw() { }
|
|
|
|
};
|
|
|
|
|
|
|
|
/** A memory allocation error.
|
|
|
|
*/
|
|
|
|
class bad_alloc : public exception, std::bad_alloc {
|
|
|
|
public:
|
|
|
|
/// Constructor.
|
|
|
|
bad_alloc() : lua::exception(), std::bad_alloc() { }
|
|
|
|
/// Constructor.
|
|
|
|
explicit bad_alloc(const char* desc) : lua::exception(desc), std::bad_alloc() { }
|
|
|
|
virtual ~bad_alloc() throw() { }
|
|
|
|
};
|
|
|
|
|
|
|
|
/** An error converting a lua type.
|
|
|
|
*/
|
|
|
|
class bad_conversion : public exception {
|
|
|
|
public:
|
|
|
|
/// Constructor.
|
|
|
|
bad_conversion() : exception() { }
|
|
|
|
/// Constructor.
|
|
|
|
explicit bad_conversion(const char* desc) : exception(desc) { }
|
|
|
|
virtual ~bad_conversion() throw() { }
|
|
|
|
};
|
|
|
|
|
|
|
|
/// A Lua table (this class does not have any data).
|
|
|
|
class table { };
|
|
|
|
/// A Lua nil (this class does not have any data).
|
|
|
|
class nil { };
|
|
|
|
/// A lua function (not a cfunction).
|
|
|
|
class function { };
|
|
|
|
/// A lua userdatum
|
|
|
|
class userdata { };
|
|
|
|
/// A lua light userdatum
|
|
|
|
class lightuserdata { };
|
|
|
|
|
|
|
|
typedef lua_CFunction cfunction; ///< A cfunction on the lua statck
|
|
|
|
typedef lua_Integer integer; ///< The default lua integer type
|
|
|
|
typedef lua_Number number; ///< The default lua number type
|
|
|
|
typedef lua_Reader reader; ///< The type of function used by lua_load
|
|
|
|
const int multiret = LUA_MULTRET; ///< LUA_MULTIRET
|
|
|
|
|
|
|
|
/** This is the Luaxx equivalent of lua_State.
|
|
|
|
* The functions provided by this class, closely resemble those of the Lua C
|
|
|
|
* API.
|
|
|
|
*/
|
|
|
|
void StackDump(lua_State *L);
|
|
|
|
class state {
|
|
|
|
public:
|
|
|
|
state();
|
|
|
|
state(lua_State* L);
|
|
|
|
state(const state& t)
|
|
|
|
{
|
|
|
|
managed=false;
|
|
|
|
L=t.L;
|
|
|
|
}
|
|
|
|
state& operator = (const state& t);
|
|
|
|
~state();
|
|
|
|
|
|
|
|
operator lua_State*();
|
|
|
|
|
|
|
|
state& push();
|
|
|
|
state& push(nil);
|
|
|
|
state& push(bool boolean);
|
|
|
|
template<typename T> state& push(T number);
|
|
|
|
state& push(const char* s, size_t length);
|
|
|
|
state& push(const char* s);
|
|
|
|
state& push(const std::string& s);
|
|
|
|
state& push(cfunction f);
|
|
|
|
state& push(table);
|
|
|
|
state& push(void* p);
|
|
|
|
template<typename T> state& pushlightuserdata(T p);
|
|
|
|
|
|
|
|
template<typename T> state& to(T& number, int index = -1);
|
|
|
|
template<typename T> state& touserdata(T& p, int index = -1);
|
|
|
|
|
|
|
|
template<typename T> T as(T default_value, int index = -1);
|
|
|
|
template<typename T> T as(int index = -1);
|
|
|
|
template<typename T> T vpop()
|
|
|
|
{
|
|
|
|
T ret;
|
|
|
|
ret=as<T>();
|
|
|
|
pop();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
template<typename T> bool is(int index = -1);
|
|
|
|
|
|
|
|
state& check(int narg);
|
|
|
|
#ifndef lua_Integer_int
|
|
|
|
state& check(int& i, int narg);
|
|
|
|
#endif
|
|
|
|
state& check(integer& i, int narg);
|
|
|
|
#ifndef lua_Integer_long
|
|
|
|
state& check(long& l, int narg);
|
|
|
|
#endif
|
|
|
|
state& check(std::string& s, int narg);
|
|
|
|
state& check(number& n, int narg);
|
|
|
|
|
|
|
|
template<typename msg_t> void error(msg_t message);
|
|
|
|
#if 0
|
|
|
|
template<> void error(const std::string& message);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
state& pcall(int nargs = 0, int nresults = 0, int on_error = 0);
|
|
|
|
state& call(int nargs = 0, int nresults = 0);
|
|
|
|
|
|
|
|
state& checkstack(int size);
|
|
|
|
state& settop(int index);
|
|
|
|
int gettop();
|
|
|
|
int size();
|
|
|
|
bool empty();
|
|
|
|
|
|
|
|
state& insert(int index);
|
|
|
|
state& replace(int index);
|
|
|
|
state& remove(int index);
|
|
|
|
state& pop(int elements = 1);
|
|
|
|
|
|
|
|
state& pushvalue(int index);
|
|
|
|
|
|
|
|
state& newtable();
|
|
|
|
bool newmetatable(const std::string& tname);
|
|
|
|
template<typename userdata_t> userdata_t* newuserdata();
|
|
|
|
void* newuserdata(size_t nbytes);
|
|
|
|
|
|
|
|
state& gettable(int index = -2);
|
|
|
|
state& getfield(const std::string& k, int index = -1);
|
|
|
|
state& settable(int index = -3);
|
|
|
|
state& setfield(const std::string& k, int index = -2);
|
|
|
|
state& getmetatable(const std::string& tname);
|
|
|
|
bool getmetatable(int index);
|
|
|
|
|
|
|
|
bool next(int index = -2);
|
|
|
|
|
|
|
|
state& getglobal(const std::string& name);
|
|
|
|
state& setglobal(const std::string& name);
|
|
|
|
|
|
|
|
state& loadfile(const std::string& filename);
|
|
|
|
state& loadstring(const std::string& s);
|
|
|
|
|
|
|
|
template<typename iterator_t> state& load(iterator_t begin, iterator_t end);
|
|
|
|
|
|
|
|
size_t objlen(int index = -1);
|
|
|
|
|
|
|
|
private:
|
|
|
|
lua_State* L;
|
|
|
|
bool managed;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int throw_error(int code);
|
|
|
|
};
|
|
|
|
|
|
|
|
// template functions
|
|
|
|
|
|
|
|
/** Push a number onto the stack.
|
|
|
|
* @tparam T the numeric type to push (should be automatically determined,
|
|
|
|
* if there is no specialization for the desired type, lua_pushnumber() will
|
|
|
|
* be used, and may fail)
|
|
|
|
* @param number the number to push
|
|
|
|
* @returns a reference to this lua::state
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
state& state::push(T number) {
|
|
|
|
lua_pushnumber(L, number);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Push a light userdatum on to the stack.
|
|
|
|
* @tparam T the type of data to push (should be automatically determined)
|
|
|
|
* @param p the pointer to push
|
|
|
|
* @returns a reference to this lua::state
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
state& state::pushlightuserdata(T p) {
|
|
|
|
lua_pushlightuserdata(L, p);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Check if the given index is of the given type (defaults to using
|
|
|
|
* lua_isnumber()).
|
|
|
|
* @tparam T the type to check for (the default, if no specializations
|
|
|
|
* match, does lua_isnumber())
|
|
|
|
* @param index the index to check
|
|
|
|
* @note the default version (used if no specialization is matched) will
|
|
|
|
* check if the given value is a number
|
|
|
|
* @returns whether the value at the given index is a nil
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
bool state::is(int index) {
|
|
|
|
return lua_isnumber(L, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the value at index as the given numeric type.
|
|
|
|
* @tparam T they numeric type to static_cast<T>() the numeric value on the
|
|
|
|
* stack to
|
|
|
|
* @param number where to store the value
|
|
|
|
* @param index the index to get
|
|
|
|
* @note This function does \em not pop the value from the stack.
|
|
|
|
* @todo Instead of throwing an exception here, we may just return an
|
|
|
|
* error code.
|
|
|
|
* @throws lua::bad_conversion if the value on the stack could not be
|
|
|
|
* converted to the indicated type
|
|
|
|
* @returns a reference to this lua::state
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
state& state::to(T& number, int index) {
|
|
|
|
if (lua_isnumber(L, index))
|
|
|
|
number = static_cast<T>(lua_tonumber(L, index));
|
|
|
|
else
|
|
|
|
throw bad_conversion("Cannot convert non 'number' value to number");
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the value at index as (light) userdata.
|
|
|
|
* @tparam T the type of data pointed to (pointer is returned as
|
|
|
|
* reinterpret_cast<T>())
|
|
|
|
* @param p the pointer to store the value in
|
|
|
|
* @param index the index to get
|
|
|
|
* @note This function does \em not pop the value from the stack.
|
|
|
|
* @todo Instead of throwing an exception here, we may just return an
|
|
|
|
* error code.
|
|
|
|
* @throws lua::bad_conversion if the value on the stack could not be
|
|
|
|
* converted to the indicated type
|
|
|
|
* @returns a reference to this lua::state
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
state& state::touserdata(T& p, int index) {
|
|
|
|
if (lua_isuserdata(L, index))
|
|
|
|
p = reinterpret_cast<T>(lua_touserdata(L, index));
|
|
|
|
else
|
|
|
|
throw bad_conversion("Cannot convert non 'userdata' or 'lightuserdata' value to userdata");
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the value at index as the given type.
|
|
|
|
* @tparam T the type to retrieve the value on the stack as (the default
|
|
|
|
* template function uses lua_tonumber(), specializations may cause
|
|
|
|
* different behavior)
|
|
|
|
* @param default_value this value is returned if the conversion fails
|
|
|
|
* @param index the index to get
|
|
|
|
* @note This function does \em not pop the value from the stack.
|
|
|
|
* @returns the indicated value from the stack or the default value if
|
|
|
|
* the conversion fails
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
T state::as(T default_value, int index) {
|
|
|
|
if (lua_isnumber(L, index))
|
|
|
|
return static_cast<T>(lua_tonumber(L, index));
|
|
|
|
else
|
|
|
|
return default_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the value at index as the given type.
|
|
|
|
* @tparam T the expected type of the value
|
|
|
|
* @param index the index to get
|
|
|
|
*
|
|
|
|
* @note This function does \em not pop the value from the stack.
|
|
|
|
* @note The default version of this function uses lua_tonumber() but
|
|
|
|
* specializations may cause different behavior.
|
|
|
|
*
|
|
|
|
* @todo Instead of throwing an exception here, we may just return an
|
|
|
|
* error code.
|
|
|
|
*
|
|
|
|
* @throws lua::bad_conversion if the value on the stack could not be
|
|
|
|
* converted to the indicated type
|
|
|
|
*
|
|
|
|
* This function will return the value on the stack as the given type. If
|
|
|
|
* the value is not of the given type <em>no conversion will be
|
|
|
|
* performed</em> and lua::bad_conversion will be thrown. There are some
|
|
|
|
* exceptions to this rule, for example, numbers will be converted to
|
|
|
|
* strings and vice-versa (conversion is only performed if the matching
|
|
|
|
* lua_is*() function returns true). The state::to() function should be
|
|
|
|
* used to perform automatic conversion.
|
|
|
|
*
|
|
|
|
* @returns the indicated value as the given type if possible
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
T state::as(int index) {
|
|
|
|
if (lua_isnumber(L, index))
|
|
|
|
return static_cast<T>(lua_tonumber(L, index));
|
|
|
|
else
|
|
|
|
throw bad_conversion("Cannot convert non 'number' value to number");
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Create a new userdatum on the stack.
|
|
|
|
* @tparam userdata_t the type of the userdata (will be passed to sizeof())
|
|
|
|
*
|
|
|
|
* This function creates a new userdatum on the stack the size of
|
|
|
|
* userdata_t and return a pointer to it.
|
|
|
|
* @returns a pointer to the new userdatum
|
|
|
|
*/
|
|
|
|
template<typename userdata_t>
|
|
|
|
userdata_t* state::newuserdata() {
|
|
|
|
return reinterpret_cast<userdata_t*>(lua_newuserdata(L, sizeof(userdata_t)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Generate a Lua error.
|
|
|
|
* @tparam msg_t the type of error message data (should be automatically
|
|
|
|
* determined)
|
|
|
|
* @param message the error message/value
|
|
|
|
* @note This function is used to raise errors from lua::cfunctions.
|
|
|
|
* @note This function never returns, instead it throws an exception
|
|
|
|
* caught by the intepreter.
|
|
|
|
*/
|
|
|
|
template<typename msg_t>
|
|
|
|
void state::error(msg_t message) {
|
|
|
|
push(message);
|
|
|
|
lua_error(L);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Load a sequence of data as a Lua chunk.
|
|
|
|
* @tparam iterator_t the type of iterator to use (should be automatically
|
|
|
|
* determined)
|
|
|
|
* @param begin an iterator to the start of the sequence
|
|
|
|
* @param end an iterator to the end of the sequence (one past the
|
|
|
|
* end)
|
|
|
|
*
|
|
|
|
* This function takes a sequence of data and attempts to convert it
|
|
|
|
* into a Lua chunk. The type of data passed must be able to be
|
|
|
|
* converted into an 8-bit char.
|
|
|
|
*
|
|
|
|
* @note This function should automatically detect if the data is text
|
|
|
|
* or binary.
|
|
|
|
*
|
|
|
|
* @returns a reference to this lua::state
|
|
|
|
*/
|
|
|
|
template<typename iterator_t>
|
|
|
|
state& state::load(iterator_t begin, iterator_t end) {
|
|
|
|
// convert the data to characters
|
|
|
|
std::vector<char> chunk(begin, end);
|
|
|
|
|
|
|
|
// Here we use the address of the first element of our vector.
|
|
|
|
// This works because the data in std::vectors is contiguous.
|
|
|
|
throw_error(luaL_loadbuffer(L, &(*chunk.begin()), chunk.size(), NULL));
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// template specializations
|
|
|
|
template<> state& state::to(bool& boolean, int index);
|
|
|
|
template<> state& state::to(std::string& string, int index);
|
|
|
|
|
|
|
|
template<> bool state::as(bool default_value, int index);
|
|
|
|
template<> std::string state::as(std::string default_value, int index);
|
|
|
|
template<> bool state::as(int index);
|
|
|
|
template<> std::string state::as(int index);
|
|
|
|
|
|
|
|
template<> bool state::is<nil>(int index);
|
|
|
|
template<> bool state::is<bool>(int index);
|
|
|
|
template<> bool state::is<std::string>(int index);
|
|
|
|
template<> bool state::is<table>(int index);
|
|
|
|
template<> bool state::is<cfunction>(int index);
|
|
|
|
template<> bool state::is<function>(int index);
|
|
|
|
template<> bool state::is<userdata>(int index);
|
|
|
|
template<> bool state::is<lightuserdata>(int index);
|
|
|
|
|
|
|
|
|
|
|
|
// inline functions
|
|
|
|
|
|
|
|
/** Convert a lua::state to a lua_State*.
|
|
|
|
* This operator allows lua::state to behave like a lua_State
|
|
|
|
* pointer.
|
|
|
|
*
|
|
|
|
* @note This should be used as a last result to interoperate with C
|
|
|
|
* code. This may be removed in future versions of Luaxx.
|
|
|
|
*/
|
|
|
|
inline state::operator lua_State*() {
|
|
|
|
return L;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Throws exceptions for error return codes.
|
|
|
|
* @param code the return code
|
|
|
|
*
|
|
|
|
* This function throws an exception based on the error it was passed.
|
|
|
|
* If it is passed a 0 it will not throw anything.
|
|
|
|
*
|
|
|
|
* @todo In the future this function may check an exception mask
|
|
|
|
* before throwing an error.
|
|
|
|
*
|
|
|
|
* @returns the code it was passed
|
|
|
|
*/
|
|
|
|
inline int state::throw_error(int code) {
|
|
|
|
std::string error;
|
|
|
|
|
|
|
|
// below, we package lua errors into exceptions
|
|
|
|
switch (code) {
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case LUA_ERRSYNTAX:
|
|
|
|
to(error).pop();
|
|
|
|
throw syntax_error(error.c_str());
|
|
|
|
case LUA_ERRMEM:
|
|
|
|
to(error).pop();
|
|
|
|
throw bad_alloc(error.c_str());
|
|
|
|
case LUA_ERRRUN:
|
|
|
|
to(error).pop();
|
|
|
|
throw runtime_error(error.c_str());
|
|
|
|
case LUA_ERRFILE:
|
|
|
|
to(error).pop();
|
|
|
|
throw file_error(error.c_str());
|
|
|
|
default:
|
|
|
|
to(error).pop();
|
|
|
|
throw exception(error.c_str());
|
|
|
|
}
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|