# HG changeset patch # User David Demelier # Date 1385547986 -3600 # Node ID 1c2788f9f55f0b15e73c598358430b7ee6cf866b # Parent 5f75779cc7eb3229cd962d892db513f2e9da4fb3 Luae added diff -r 5f75779cc7eb -r 1c2788f9f55f C++/Lua/LuaSocket.cpp --- a/C++/Lua/LuaSocket.cpp Wed Nov 27 10:53:20 2013 +0100 +++ b/C++/Lua/LuaSocket.cpp Wed Nov 27 11:26:26 2013 +0100 @@ -1,12 +1,12 @@ /* - * LuaSocket.cpp -- portable Lua socket wrappers - * - * Copyright (c) 2013, David Demelier - * + * LuaSocket.cpp -- Lua bindings for Sockets + * + * Copyright (c) 2013 David Demelier + * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. - * + * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR @@ -16,70 +16,396 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#define SC_TYPE "Socket" -#define ADDR_TYPE "SocketAddress" +#include +#include +#include +#include +#include -#define GET_SC(L, idx) *static_cast(luaL_checkudata(L, idx, SC_TYPE)) -#define GET_ADDR(L, idx) *static_cast(luaL_checkudata(L, idx, ADDR_TYPE)) +#include +#include +#include -#include -#include - +#include "Luae.h" #include "LuaSocket.h" -#include "Socket.h" +#define SOCKET_TYPE "Socket" +#define ADDRESS_TYPE "SocketAddress" +#define LISTENER_TYPE "SocketListener" + +namespace irccd { + +namespace { + +/* --------------------------------------------------------- + * Enumerations + * --------------------------------------------------------- */ + +typedef std::unordered_map EnumMap; + +EnumMap createSockFamilies() +{ + EnumMap map; -static int l_new(lua_State *L) + map["Inet"] = AF_INET; + map["Inet6"] = AF_INET6; + +#if !defined(_WIN32) + map["Unix"] = AF_UNIX; +#endif + + return map; +} + +EnumMap createSockTypes() +{ + EnumMap map; + + map["Stream"] = SOCK_STREAM; + map["Datagram"] = SOCK_DGRAM; + + return map; +} + +EnumMap createSockProtocols() { - int domain = luaL_checkinteger(L, 1); - int type = luaL_checkinteger(L, 2); - int protocol = 0; - Socket *s, **ptr; + EnumMap map; + + map["Tcp"] = IPPROTO_TCP; + map["Udp"] = IPPROTO_UDP; + map["IPv4"] = IPPROTO_IP; + map["IPv6"] = IPPROTO_IPV6; + + return map; +} + +EnumMap sockFamilies = createSockFamilies(); +EnumMap sockTypes = createSockTypes(); +EnumMap sockProtocols = createSockProtocols(); + +/* --------------------------------------------------------- + * Socket options + * --------------------------------------------------------- */ + +/* + * Windows setsockopt() says that options which takes a bool may use + * the bool type. On some Unix systems (including FreeBSD), passing + * a bool to SO_REUSEADDR for example, will result in a EINVAL. + */ +#if defined(_WIN32) + +typedef bool OptionBool; +typedef int OptionInteger; + +#else + +typedef int OptionBool; +typedef int OptionInteger; + +#endif + +enum class ArgType +{ + Invalid, + Boolean, + Integer +}; + +struct Option +{ + int m_level; + int m_optname; + ArgType m_argtype; + + Option() + : m_level(0) + , m_optname(0) + , m_argtype(ArgType::Invalid) + { + } + + Option(int level, int optname, ArgType type) + : m_level(level) + , m_optname(optname) + , m_argtype(type) + { + } +}; + +typedef std::unordered_map +> OptionMap; + +/* + * Map here the socket options from the C side to Lua. It's very + * close to the standard API. + * + * This map is used to retrieve which argument we must pass to the + * Socket::set function. It also setup the enumerations to be bound + * as tables. + */ +OptionMap mapOfOptions() +{ + OptionMap map; + + /* + * Standard sockets options SOL_SOCKET + */ - if (lua_gettop(L) >= 3) - protocol = luaL_checkinteger(L, 3); +#if defined(SO_REUSEADDR) + map["socket"]["reuse-address"] = Option(SOL_SOCKET, + SO_REUSEADDR, + ArgType::Boolean); +#endif +#if defined(SO_BROADCAST) + map["socket"]["broadcast"] = Option(SOL_SOCKET, + SO_BROADCAST, + ArgType::Boolean); +#endif +#if defined(SO_DEBUG) + map["socket"]["debug"] = Option(SOL_SOCKET, + SO_DEBUG, + ArgType::Boolean); +#endif +#if defined(SO_KEEPALIVE) + map["socket"]["keep-alive"] = Option(SOL_SOCKET, + SO_KEEPALIVE, + ArgType::Boolean); +#endif +#if defined(SO_RCVBUF) + map["socket"]["receive-buffer"] = Option(SOL_SOCKET, + SO_RCVBUF, + ArgType::Integer); +#endif + + /* + * TCP socket options + */ + +#if defined(TCP_NODELAY) + map["tcp"]["no-delay"] = Option(IPPROTO_TCP, + TCP_NODELAY, + ArgType::Boolean); +#endif + + /* + * IPv6 options + */ + +#if defined(IPV6_V6ONLY) + map["ipv6"]["v6only"] = Option(IPPROTO_IPV6, + IPV6_V6ONLY, + ArgType::Boolean); +#endif + + return map; +} + +OptionMap options = mapOfOptions(); + +/* --------------------------------------------------------- + * Socket functions + * --------------------------------------------------------- */ + +void mapToTable(lua_State *L, + const EnumMap &map, + int index, + const std::string &name) +{ + lua_createtable(L, 0, 0); + + for (auto p : map) { + lua_pushinteger(L, p.second); + lua_setfield(L, -2, p.first.c_str()); + } + + if (index < 0) + -- index; - try - { - s = new Socket(domain, type, protocol); + lua_setfield(L, index, name.c_str()); +} + +int genericReceive(lua_State *L, bool udp) +{ + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + int requested = luaL_checkinteger(L, 2); + SocketAddress *sa; + int nret; + char *data; + size_t nbread; + + /* + * This parameter only needed for UDP sockets + */ + if (udp) + sa = Luae::toType(L, 2, ADDRESS_TYPE); + + /* + * Allocate a temporarly buffer for receiveing the data. + */ + data = static_cast(std::malloc(requested)); + if (data == nullptr) { + lua_pushnil(L); + lua_pushstring(L, std::strerror(errno)); + + return 2; } - catch (SocketError error) - { + + try { + if (!udp) + nbread = s->recv(data, requested); + else + nbread = s->recvfrom(data, requested, *sa); + + lua_pushlstring(L, data, nbread); + + nret = 1; + } catch (SocketError error) { + lua_pushnil(L); + lua_pushstring(L, error.what()); + + nret = 2; + } + + std::free(data); + + return nret; +} + +int genericSend(lua_State *L, bool udp) +{ + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + const char *msg = luaL_checkstring(L, 2); + SocketAddress *sa; + long nbsent; + + /* + * This parameter only needed for UDP sockets + */ + if (udp) + sa = Luae::toType(L, 3, ADDRESS_TYPE); + + try { + if (!udp) + nbsent = s->send(msg, strlen(msg)); + else + nbsent = s->sendto(msg, strlen(msg), *sa); + } catch (SocketError error) { lua_pushnil(L); lua_pushstring(L, error.what()); return 2; } - ptr = static_cast(lua_newuserdata(L, sizeof (void *))); - *ptr = s; - luaL_setmetatable(L, SC_TYPE); + lua_pushnumber(L, nbsent); + + return 1; +} + +int socketNew(lua_State *L) +{ + int domain; + int type = SOCK_STREAM; + int protocol = 0; + + // Domain is the only one mandatory + domain = luaL_checkinteger(L, 1); + + if (lua_gettop(L) >= 2) + type = luaL_checkinteger(L, 2); + if (lua_gettop(L) >= 3) + protocol = luaL_checkinteger(L, 3); + + try { + new (L, SOCKET_TYPE) Socket(domain, type, protocol); + } catch (SocketError error) { + lua_pushnil(L); + lua_pushstring(L, error.what()); + + return 2; + } + + return 1; +} + +int socketBlockMode(lua_State *L) +{ + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + bool mode = lua_toboolean(L, 2); + + try { + s->blockMode(mode); + } catch (SocketError error) { + lua_pushnil(L); + lua_pushstring(L, error.what()); + + return 2; + } + + lua_pushboolean(L, true); return 1; } -static int l_blockMode(lua_State *L) +int socketBind(lua_State *L) { - Socket *s = GET_SC(L, 1); - bool mode = lua_toboolean(L, 2); + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + SocketAddress *a = Luae::toType(L, 2, ADDRESS_TYPE); + + /* + * Get nil + error message for chained expression like: + * s:bind(address.bindInet { port = 80, family = 1 }) + */ + if (lua_type(L, 1) == LUA_TNIL && lua_type(L, 2) == LUA_TSTRING) { + lua_pushnil(L); + lua_pushvalue(L, 2); + + return 2; + } - s->blockMode(mode); - + try { + s->bind(*a); + } catch (SocketError error) { + lua_pushnil(L); + lua_pushstring(L, error.what()); + + return 2; + } + + lua_pushboolean(L, true); + + return 1; +} + +int socketClose(lua_State *L) +{ + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + + s->close(); + return 0; } -static int l_bind(lua_State *L) +int socketConnect(lua_State *L) { - Socket *s = GET_SC(L, 1); - SocketAddress *a = GET_ADDR(L, 2); + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + SocketAddress *a = Luae::toType(L, 2, ADDRESS_TYPE); - try - { - s->bind(*a); + /* + * Get nil + error message for chained expression like: + * s:bind(address.bindInet { port = 80, family = 1 }) + */ + if (lua_type(L, 1) == LUA_TNIL && lua_type(L, 2) == LUA_TSTRING) { + lua_pushnil(L); + lua_pushvalue(L, 2); + + return 2; } - catch (SocketError error) - { - lua_pushboolean(L, false); + + try { + s->connect(*a); + } catch (SocketError error) { + lua_pushnil(L); lua_pushstring(L, error.what()); return 2; @@ -90,18 +416,39 @@ return 1; } -static int l_connect(lua_State *L) +int socketAccept(lua_State *L) { - Socket *s = GET_SC(L, 1); - SocketAddress *a = GET_ADDR(L, 2); + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + Socket client; + SocketAddress info; + + try { + client = s->accept(info); + new (L, SOCKET_TYPE) Socket(client); + new (L, ADDRESS_TYPE) SocketAddress(info); + } catch (SocketError error) { + lua_pushnil(L); + lua_pushnil(L); + lua_pushstring(L, error.what()); - try - { - s->connect(*a); + return 3; } - catch (SocketError error) - { - lua_pushboolean(L, false); + + return 2; +} + +int socketListen(lua_State *L) +{ + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + int max = 64; + + if (lua_gettop(L) >= 2) + max = luaL_checkinteger(L, 2); + + try { + s->listen(max); + } catch (SocketError error) { + lua_pushnil(L); lua_pushstring(L, error.what()); return 2; @@ -112,81 +459,377 @@ return 1; } -static int l_accept(lua_State *L) +int socketReceive(lua_State *L) +{ + return genericReceive(L, false); +} + +int socketReceiveFrom(lua_State *L) +{ + return genericReceive(L, true); +} + +int socketSend(lua_State *L) +{ + return genericSend(L, false); +} + +int socketSendTo(lua_State *L) +{ + return genericSend(L, true); +} + +int socketSet(lua_State *L) { - Socket *s = GET_SC(L, 1); - Socket *c, **ptr; + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + const char *lvl = luaL_checkstring(L, 2); + const char *nm = luaL_checkstring(L, 3); + int nret; + + try { + OptionBool bvalue; + OptionInteger ivalue; + void *ptr = nullptr; + size_t size; + + auto o = options.at(lvl).at(nm); + + switch (o.m_argtype) { + case ArgType::Boolean: + bvalue = lua_toboolean(L, 4); + ptr = static_cast(&bvalue); + size = sizeof (OptionBool); + break; + case ArgType::Integer: + ivalue = luaL_checkinteger(L, 4); + ptr = static_cast(&ivalue); + size = sizeof (OptionInteger); + break; + default: + break; + } + + if (ptr != nullptr) + s->set(o.m_level, o.m_optname, ptr, size); + lua_pushboolean(L, true); + + nret = 1; + } catch (std::out_of_range) { + lua_pushnil(L); + lua_pushstring(L, "invalid level or option name"); + + nret = 2; + } catch (SocketError error) { + lua_pushnil(L); + lua_pushstring(L, error.what()); + + nret = 2; + } + + return nret; +} + +int sockEq(lua_State *L) +{ + Socket *s1 = Luae::toType(L, 1, SOCKET_TYPE); + Socket *s2 = Luae::toType(L, 2, SOCKET_TYPE); + + lua_pushboolean(L, *s1 == *s2); + + return 1; +} - try - { - c = new Socket(s->accept()); +int sockToString(lua_State *L) +{ + Socket *s = Luae::toType(L, 1, SOCKET_TYPE); + + lua_pushfstring(L, "socket %d", s->getType()); + + return 1; +} + +int sockGc(lua_State *L) +{ + Luae::toType(L, 1, SOCKET_TYPE)->~Socket(); + + return 0; +} + +const luaL_Reg sockFunctions[] = { + { "new", socketNew }, + { nullptr, nullptr } +}; + +const luaL_Reg sockMethods[] = { + { "blockMode", socketBlockMode }, + { "bind", socketBind }, + { "close", socketClose }, + { "connect", socketConnect }, + { "accept", socketAccept }, + { "listen", socketListen }, + { "receive", socketReceive }, + { "receiveFrom", socketReceiveFrom }, + { "send", socketSend }, + { "sendTo", socketSendTo }, + { "set", socketSet }, + { nullptr, nullptr } +}; + +const luaL_Reg sockMeta[] = { + { "__eq", sockEq }, + { "__tostring", sockToString }, + { "__gc", sockGc }, + { nullptr, nullptr } +}; + +/* --------------------------------------------------------- + * Socket address functions + * --------------------------------------------------------- */ + +int addrConnectInet(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + + std::string host = Luae::requireField(L, 1, "host"); + int port = Luae::requireField(L, 1, "port"); + int family = Luae::requireField(L, 1, "family"); + + try { + new (L, ADDRESS_TYPE) ConnectAddressIP(host, port, family); + } catch (SocketError error) { + lua_pushnil(L); + lua_pushstring(L, error.what()); + + return 2; } - catch (SocketError error) - { - lua_pushboolean(L, false); + + return 1; +} + +int addrBindInet(lua_State *L) +{ + std::string address = "*"; + int port, family; + + luaL_checktype(L, 1, LUA_TTABLE); + + // Mandatory fields + port = Luae::requireField(L, 1, "port"); + family = Luae::requireField(L, 1, "family"); + + // Optional fields + if (Luae::typeField(L, 1, "address") == LUA_TSTRING) + address = Luae::requireField(L, 1, "address"); + + try { + new (L, ADDRESS_TYPE) BindAddressIP(address, port, family); + } catch (SocketError error) { + lua_pushnil(L); lua_pushstring(L, error.what()); return 2; } - ptr = static_cast(lua_newuserdata(L, sizeof (void *))); - *ptr = c; - luaL_setmetatable(L, ADDR_TYPE); + return 1; +} + +#if !defined(_WIN32) + +int addrUnix(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + bool rem = false; + + if (lua_gettop(L) >= 2) + rem = lua_toboolean(L, 2); + + new (L, ADDRESS_TYPE) AddressUnix(path, rem); + + return 1; +} + +#endif + +int addrToString(lua_State *L) +{ + SocketAddress *sa = Luae::toType(L, 1, ADDRESS_TYPE); + + lua_pushfstring(L, "address of length %d", sa->length()); return 1; } -static int l_listen(lua_State *L) +int addrGc(lua_State *L) { - Socket *s = GET_SC(L, 1); - int max = luaL_checkinteger(L, 2); + Luae::toType(L, 1, ADDRESS_TYPE)->~SocketAddress(); + + return 0; +} - try - { - s->listen(max); - } - catch (SocketError error) - { - lua_pushboolean(L, false); - lua_pushstring(L, error.what()); +const luaL_Reg addrFunctions[] = { + { "connectInet", addrConnectInet }, + { "bindInet", addrBindInet }, +#if !defined(_WIN32) + { "unix", addrUnix }, +#endif + { nullptr, nullptr } +}; - return 2; - } +const luaL_Reg addrMeta[] = { + { "__tostring", addrToString }, + { "__gc", addrGc }, + { nullptr, nullptr } +}; - lua_pushboolean(L, true); +/* --------------------------------------------------------- + * Socket listener functions + * --------------------------------------------------------- */ + +int listenerNew(lua_State *L) +{ + new (L, LISTENER_TYPE) SocketListener(); return 1; } -static int l_recv(lua_State *L) +int listenerAdd(lua_State *L) +{ + SocketListener *l = Luae::toType(L, 1, LISTENER_TYPE); + Socket *s = Luae::toType(L, 2, SOCKET_TYPE); + + l->add(*s); + + return 0; +} + +int listenerRemove(lua_State *L) +{ + SocketListener *l = Luae::toType(L, 1, LISTENER_TYPE); + Socket *s = Luae::toType(L, 2, SOCKET_TYPE); + + l->remove(*s); + + return 0; +} + +int listenerClear(lua_State *L) { - Socket *s = GET_SC(L, 1); - unsigned len = luaL_checkinteger(L, 2); - char *data; + Luae::toType(L, 1, LISTENER_TYPE)->clear(); + + return 0; +} + +int listenerSelect(lua_State *L) +{ + SocketListener *l = Luae::toType(L, 1, LISTENER_TYPE); + int seconds = 0, ms = 0, nret; + + if (lua_gettop(L) >= 2) + seconds = luaL_checkinteger(L, 2); + if (lua_gettop(L) >= 3) + ms = luaL_checkinteger(L, 3); - if ((data = (char *)malloc(len)) == NULL) - { + try { + Socket selected = l->select(seconds, ms); + new (L, SOCKET_TYPE) Socket(selected); + + nret = 1; + } catch (SocketError error) { lua_pushnil(L); - lua_pushstring(L, ) + lua_pushstring(L, error.what()); + + nret = 2; + } catch (SocketTimeout timeout) { + lua_pushnil(L); + lua_pushstring(L, timeout.what()); + + nret = 2; } + return nret; +} + +int listenerToStr(lua_State *L) +{ + SocketListener *l = Luae::toType(L, 1, LISTENER_TYPE); + + lua_pushfstring(L, "listener of %d clients", l->size()); + + return 1; +} + +int listenerGc(lua_State *L) +{ + Luae::toType(L, 1, LISTENER_TYPE)->~SocketListener(); + + return 0; +} + +const luaL_Reg listenerFunctions[] = { + { "new", listenerNew }, + { nullptr, nullptr } +}; + +const luaL_Reg listenerMethods[] = { + { "add", listenerAdd }, + { "remove", listenerRemove }, + { "clear", listenerClear }, + { "select", listenerSelect }, + { nullptr, nullptr } +}; + +const luaL_Reg listenerMeta[] = { + { "__tostring", listenerToStr }, + { "__gc", listenerGc }, + { nullptr, nullptr } +}; } -static const luaL_Reg functions[] = { - { "new", l_new }, - { nullptr, nullptr } -}; +int luaopen_socket(lua_State *L) +{ + // Socket functions + luaL_newlib(L, sockFunctions); -static const luaL_Reg methods[] = { - { "blockMode", l_blockMode }, - { "bind", l_bind }, - { "connect", l_connect }, - { nullptr, nullptr } -}; + // Map families, types + mapToTable(L, sockFamilies, -1, "family"); + mapToTable(L, sockTypes, -1, "type"); + mapToTable(L, sockProtocols, -1, "protocol"); -extern "C" int luaopen_socket(lua_State *L) -{ + // Create Socket type + luaL_newmetatable(L, SOCKET_TYPE); + luaL_setfuncs(L, sockMeta, 0); + luaL_newlib(L, sockMethods); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); return 1; } + +int luaopen_socket_address(lua_State *L) +{ + luaL_newlib(L, addrFunctions); + + // Create SocketAddress type + luaL_newmetatable(L, ADDRESS_TYPE); + luaL_setfuncs(L, addrMeta, 0); + lua_pop(L, 1); + + return 1; +} + +int luaopen_socket_listener(lua_State *L) +{ + luaL_newlib(L, listenerFunctions); + + // Create the SocketListener type + luaL_newmetatable(L, LISTENER_TYPE); + luaL_setfuncs(L, listenerMeta, 0); + luaL_newlib(L, listenerMethods); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); + + return 1; +} + +} // !irccd diff -r 5f75779cc7eb -r 1c2788f9f55f C++/Lua/LuaSocket.h --- a/C++/Lua/LuaSocket.h Wed Nov 27 10:53:20 2013 +0100 +++ b/C++/Lua/LuaSocket.h Wed Nov 27 11:26:26 2013 +0100 @@ -1,7 +1,7 @@ /* - * LuaSocket.h -- portable Lua socket wrappers + * LuaSocket.h -- Lua bindings for sockets * - * Copyright (c) 2013, David Demelier + * Copyright (c) 2013 David Demelier * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,6 +16,18 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef _LUA_SOCKET_H_ +#define _LUA_SOCKET_H_ + #include -extern "C" int luaopen_socket(lua_State *L); +namespace irccd +{ + +int luaopen_socket(lua_State *L); +int luaopen_socket_address(lua_State *L); +int luaopen_socket_listener(lua_State *L); + +} // !irccd + +#endif // !_LUA_SOCKET_H_ diff -r 5f75779cc7eb -r 1c2788f9f55f Luae.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Luae.cpp Wed Nov 27 11:26:26 2013 +0100 @@ -0,0 +1,282 @@ +/* + * Lua.cpp -- Lua helpers and such + * + * Copyright (c) 2013 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "Luae.h" + +namespace irccd { + +LuaState::LuaState() +{ + m_state = Ptr(luaL_newstate()); +} + +LuaState::LuaState(lua_State *L) +{ + m_state = Ptr(L); +} + +LuaState::LuaState(LuaState &&state) +{ + m_state = std::move(state.m_state); +} + +LuaState &LuaState::operator=(LuaState &&state) +{ + m_state = std::move(state.m_state); + + return *this; +} + +LuaState::operator lua_State*() +{ + return m_state.get(); +} + +LuaValue LuaValue::copy(lua_State *L, int index) +{ + LuaValue v; + + v.type = lua_type(L, index); + + switch (v.type) { + case LUA_TBOOLEAN: + v.boolean = lua_toboolean(L, index); + break; + case LUA_TNUMBER: + v.number = lua_tonumber(L, index); + break; + case LUA_TSTRING: + v.str = lua_tostring(L, index); + break; + case LUA_TTABLE: + { + LuaValue k; + + if (index < 0) + -- index; + + lua_pushnil(L); + while (lua_next(L, index)) { + v.table.push_back(std::make_pair(copy(L, -2), copy(L, -1))); + lua_pop(L, 1); + } + + break; + } + default: + v.type = LUA_TNIL; + break; + } + + return v; +} + +void LuaValue::push(lua_State *L, const LuaValue &value) +{ + switch (value.type) { + case LUA_TBOOLEAN: + lua_pushboolean(L, value.boolean); + break; + case LUA_TSTRING: + lua_pushlstring(L, value.str.c_str(), value.str.size()); + break; + case LUA_TNUMBER: + lua_pushnumber(L, value.number); + break; + case LUA_TTABLE: + { + lua_createtable(L, 0, 0); + + for (auto p : value.table) { + LuaValue::push(L, p.first); + LuaValue::push(L, p.second); + + lua_settable(L, -3); + } + break; + } + default: + lua_pushnil(L); + break; + } +} + +LuaValue::LuaValue() + : type(LUA_TNIL) +{ +} + +template <> +bool Luae::getField(lua_State *L, int idx, const std::string &name) +{ + bool value = false; + + lua_getfield(L, idx, name.c_str()); + if (lua_type(L, -1) == LUA_TBOOLEAN) + value = lua_toboolean(L, -1) == 1; + lua_pop(L, 1); + + return value; +} + +template <> +double Luae::getField(lua_State *L, int idx, const std::string &name) +{ + double value = 0; + + lua_getfield(L, idx, name.c_str()); + if (lua_type(L, -1) == LUA_TNUMBER) + value = lua_tonumber(L, -1); + lua_pop(L, 1); + + return value; +} + +template <> +int Luae::getField(lua_State *L, int idx, const std::string &name) +{ + int value = 0; + + lua_getfield(L, idx, name.c_str()); + if (lua_type(L, -1) == LUA_TNUMBER) + value = lua_tointeger(L, -1); + lua_pop(L, 1); + + return value; +} + +template <> +std::string Luae::getField(lua_State *L, int idx, const std::string &name) +{ + std::string value; + + lua_getfield(L, idx, name.c_str()); + if (lua_type(L, -1) == LUA_TSTRING) + value = lua_tostring(L, -1); + lua_pop(L, 1); + + return value; +} + +int Luae::typeField(lua_State *L, int idx, const std::string &name) +{ + int type; + + LUA_STACK_CHECKBEGIN(L); + + lua_getfield(L, idx, name.c_str()); + type = lua_type(L, -1); + lua_pop(L, 1); + + LUA_STACK_CHECKEQUALS(L); + + return type; +} + +void Luae::preload(lua_State *L, const std::string &name, lua_CFunction func) +{ + LUA_STACK_CHECKBEGIN(L); + + lua_getglobal(L, "package"); + lua_getfield(L, -1, "preload"); + lua_pushcfunction(L, func); + lua_setfield(L, -2, name.c_str()); + lua_pop(L, 2); + + LUA_STACK_CHECKEQUALS(L); +} + +void Luae::readTable(lua_State *L, int idx, ReadFunction func) +{ + LUA_STACK_CHECKBEGIN(L); + + lua_pushnil(L); + + if (idx < 0) + --idx; + + while (lua_next(L, idx)) { + func(L, lua_type(L, -2), lua_type(L, -1)); + lua_pop(L, 1); + } + + LUA_STACK_CHECKEQUALS(L); +} + +int Luae::referenceField(lua_State *L, int idx, int type, const std::string &name) +{ + int ref = LUA_REFNIL; + + lua_getfield(L, idx, name.c_str()); + + if (lua_type(L, -1) == type) { + lua_pushvalue(L, -1); + ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + + lua_pop(L, 1); + + return ref; +} + +void Luae::require(lua_State *L, const std::string &name, lua_CFunction func, bool global) +{ + LUA_STACK_CHECKBEGIN(L); + + luaL_requiref(L, name.c_str(), func, global); + lua_pop(L, 1); + + LUA_STACK_CHECKEQUALS(L); +} + +void Luae::initRegistry(lua_State *L) +{ + lua_createtable(L, 0, 0); + lua_createtable(L, 0, 1); + lua_pushstring(L, "v"); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L, -2); + lua_setfield(L, LUA_REGISTRYINDEX, "refs"); +} + +} // !irccd + +void * operator new(size_t size, lua_State *L) +{ + return lua_newuserdata(L, size); +} + +void * operator new(size_t size, lua_State *L, const char *metaname) +{ + void *object; + + object = lua_newuserdata(L, size); + luaL_setmetatable(L, metaname); + + return object; +} + +void operator delete(void *, lua_State *) +{ +} + +void operator delete(void *, lua_State *L, const char *) +{ + lua_pushnil(L); + lua_setmetatable(L, -2); +} diff -r 5f75779cc7eb -r 1c2788f9f55f Luae.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Luae.h Wed Nov 27 11:26:26 2013 +0100 @@ -0,0 +1,361 @@ +/* + * Lua.h -- Lua helpers and such + * + * Copyright (c) 2013 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LUA_H_ +#define _LUA_H_ + +#include +#include +#include +#include +#include + +#include + +namespace irccd { + +/** + * @class LuaState + * @brief Wrapper for lua_State + * + * This class automatically create a new Lua state and add implicit + * cast operator plus RAII destruction. + */ +class LuaState { +private: + struct Deleter { + void operator()(lua_State *L) + { + lua_close(L); + } + }; + + using Ptr = std::unique_ptr; + + Ptr m_state; + +public: + LuaState(const LuaState &) = delete; + LuaState &operator=(const LuaState &) = delete; + + /** + * Default constructor. Create a new state. + */ + LuaState(); + + /** + * Use the already created state. + * + * @param L the state to use + */ + LuaState(lua_State *L); + + /** + * Move constructor. + * + * @param state the Lua state to move + */ + LuaState(LuaState &&state); + + /** + * Move assignment operator. + * + * @param state the Lua state to move + */ + LuaState &operator=(LuaState &&state); + + /** + * Implicit cast operator for convenient usage to C Lua API. + * + * @return the state as lua_State * + */ + operator lua_State*(); +}; + +/** + * @class LuaValue + * @brief A fake variant for Lua values + * + * This class is primarly used for copying Lua values without checking + * the types, useful to pass data. + */ +class LuaValue { +private: + union { + lua_Number number; + bool boolean; + }; + + int type; + std::string str; + std::vector> table; + +public: + /** + * Dump a value at the specific index. + * + * @param L the Lua state + * @param index the value + * @return a tree of values + */ + static LuaValue copy(lua_State *L, int index); + + /** + * Push a value to a state. + * + * @param L the Lua state + * @param value the value to push + */ + static void push(lua_State *L, const LuaValue &value); + + /** + * Default constructor (type nil) + */ + LuaValue(); +}; + +/** + * @class Luae + * @brief Add lot of convenience for Lua + * + * This class adds lot of functions for Lua and C++. + */ +class Luae +{ +public: + using ReadFunction = std::function; + + /** + * Get a field of a specific type from a table. Specialized for the + * types: int, double, bool and string. + * + * @param L the Lua state + * @param idx the table index + * @param name the field name + * @return the converted type. + */ + template + static T getField(lua_State *L, int idx, const std::string &name); + + /** + * Require a field from a table. + * + * @param L the Lua state + * @param idx the table index + * @param name the field name + * @return the value or call luaL_error + */ + template + static T requireField(lua_State *L, int idx, const std::string &name) + { + lua_getfield(L, idx, name.c_str()); + + if (lua_type(L, -1) == LUA_TNIL) + luaL_error(L, "missing field `%s'", name.c_str()); + // NOT REACHED + + lua_pop(L, 1); + + return getField(L, idx, name); + } + + /** + * Check a table field. + * + * @param L the Lua state + * @param idx the table index + * @param name the field name + * @return the type + */ + static int typeField(lua_State *L, int idx, const std::string &name); + + /** + * Read a table, the function func is called for each element in the + * table. Parameter tkey is the Lua type of the key, parameter tvalue is + * the Lua type of the value. The key is available at index -2 and the + * value at index -1. + * + * Do not pop anything within the function. + * + * @param L the Lua state + * @param idx the table index + * @param func the function to call + */ + static void readTable(lua_State *L, int idx, ReadFunction func); + + /** + * Preload a library, it will be added to package.preload so the + * user can successfully call require "name". In order to work, you need + * to open luaopen_package and luaopen_base first. + * + * @param L the Lua state + * @param name the module name + * @param func the opening library + * @see require + */ + static void preload(lua_State *L, + const std::string &name, + lua_CFunction func); + + /** + * Reference a field from a table at the index. The reference is created in + * the registry only if type matches. + * + * @param L the Lua state + * @param idx the table index + * @param type the type requested + * @param name the field name + * @return the reference or LUA_REFNIL on problem + */ + static int referenceField(lua_State *L, + int idx, + int type, + const std::string &name); + + /** + * Load a library just like it was loaded with require. + * + * @param L the Lua state + * @param name the module name + * @param func the function + * @param global store as global + */ + static void require(lua_State *L, + const std::string &name, + lua_CFunction func, + bool global); + + /** + * Initialize the registry for shared objects. + * + * @param L the Lua state + */ + static void initRegistry(lua_State *L); + + /** + * Push a shared object to Lua, it also push it to the "refs" + * table with __mode = "v". That is if we need to push the object + * again we use the same reference so Lua get always the same + * userdata and gain the following benefits: + * + * 1. The user can use the userdata as table key + * 2. A performance gain thanks to less allocations + * + * @param L the Lua state + * @param o the object to push + * @param name the object metatable name + */ + template + static void pushShared(lua_State *L, + std::shared_ptr o, + const std::string &name) + { + lua_getfield(L, LUA_REGISTRYINDEX, "refs"); + assert(lua_type(L, -1) == LUA_TTABLE); + + lua_rawgetp(L, -1, o.get()); + + if (lua_type(L, -1) == LUA_TNIL) { + lua_pop(L, 1); + + new (L, name.c_str()) std::shared_ptr(o); + + lua_pushvalue(L, -1); + lua_rawsetp(L, -3, o.get()); + } + + lua_replace(L, -2); + } + + /** + * Get an object from Lua that was previously push with pushShared. + * + * @param L the Lua state + * @param index the object index + * @param meta the object metatable name + * @return the object + */ + template + static std::shared_ptr getShared(lua_State *L, int index, const char *meta) + { + using Ptr = std::shared_ptr; + + Ptr *ptr = static_cast(luaL_checkudata(L, index, meta)); + + return *ptr; + } + + /** + * Convert a new placement made object, without testing if its a real + * object. + * + * @param L the Lua state + * @param idx the object index + * @return the converted object + */ + template + static T toType(lua_State *L, int idx) + { + return reinterpret_cast(lua_touserdata(L, idx)); + } + + /** + * Convert a class created with new placement. + * + * @param L the Lua state + * @param idx the value index + * @param metaname the metatable name + * @return the converted object + */ + template + static T toType(lua_State *L, int idx, const char *metaname) + { + return reinterpret_cast(luaL_checkudata(L, idx, metaname)); + } +}; + +#if !defined(NDEBUG) + +#define LUA_STACK_CHECKBEGIN(L) \ + int __topstack = lua_gettop((L)) + +#define LUA_STACK_CHECKEQUALS(L) \ + assert(lua_gettop((L)) == __topstack) + +#define LUA_STACK_CHECKEND(L, cond) \ + assert(lua_gettop((L)) cond == __topstack) + +#else + +#define LUA_STACK_CHECKBEGIN(L) +#define LUA_STACK_CHECKEQUALS(L) +#define LUA_STACK_CHECKEND(L, cond) + +#endif + +} // !irccd + +void *operator new(size_t size, lua_State *L); + +void *operator new(size_t size, lua_State *L, const char *metaname); + +void operator delete(void *ptr, lua_State *L); + +void operator delete(void *ptr, lua_State *L, const char *metaname); + +#endif // !_LUA_H_