diff options
Diffstat (limited to 'src/script/lua_api/marshall.c')
-rw-r--r-- | src/script/lua_api/marshall.c | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/src/script/lua_api/marshall.c b/src/script/lua_api/marshall.c new file mode 100644 index 000000000..ef70566cb --- /dev/null +++ b/src/script/lua_api/marshall.c @@ -0,0 +1,551 @@ +/* +* lmarshal.c +* A Lua library for serializing and deserializing Lua values +* Richard Hundt <richardhundt@gmail.com> +* +* License: MIT +* +* Copyright (c) 2010 Richard Hundt +* +* 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. +*/ + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + + +#define MAR_TREF 1 +#define MAR_TVAL 2 +#define MAR_TUSR 3 + +#define MAR_CHR 1 +#define MAR_I32 4 +#define MAR_I64 8 + +#define MAR_MAGIC 0x8e +#define SEEN_IDX 3 + +typedef struct mar_Buffer { + size_t size; + size_t seek; + size_t head; + char* data; +} mar_Buffer; + +static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx); +static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx); + +static void buf_init(lua_State *L, mar_Buffer *buf) +{ + buf->size = 128; + buf->seek = 0; + buf->head = 0; + if (!(buf->data = malloc(buf->size))) luaL_error(L, "Out of memory!"); +} + +static void buf_done(lua_State* L, mar_Buffer *buf) +{ + free(buf->data); +} + +static int buf_write(lua_State* L, const char* str, size_t len, mar_Buffer *buf) +{ + if (len > UINT32_MAX) luaL_error(L, "buffer too long"); + if (buf->size - buf->head < len) { + size_t new_size = buf->size << 1; + size_t cur_head = buf->head; + while (new_size - cur_head <= len) { + new_size = new_size << 1; + } + if (!(buf->data = realloc(buf->data, new_size))) { + luaL_error(L, "Out of memory!"); + } + buf->size = new_size; + } + memcpy(&buf->data[buf->head], str, len); + buf->head += len; + return 0; +} + +static const char* buf_read(lua_State *L, mar_Buffer *buf, size_t *len) +{ + if (buf->seek < buf->head) { + buf->seek = buf->head; + *len = buf->seek; + return buf->data; + } + *len = 0; + return NULL; +} + +static void mar_encode_value(lua_State *L, mar_Buffer *buf, int val, size_t *idx) +{ + size_t l; + int val_type = lua_type(L, val); + lua_pushvalue(L, val); + + buf_write(L, (void*)&val_type, MAR_CHR, buf); + switch (val_type) { + case LUA_TBOOLEAN: { + int int_val = lua_toboolean(L, -1); + buf_write(L, (void*)&int_val, MAR_CHR, buf); + break; + } + case LUA_TSTRING: { + const char *str_val = lua_tolstring(L, -1, &l); + buf_write(L, (void*)&l, MAR_I32, buf); + buf_write(L, str_val, l, buf); + break; + } + case LUA_TNUMBER: { + lua_Number num_val = lua_tonumber(L, -1); + buf_write(L, (void*)&num_val, MAR_I64, buf); + break; + } + case LUA_TTABLE: { + int tag, ref; + lua_pushvalue(L, -1); + lua_rawget(L, SEEN_IDX); + if (!lua_isnil(L, -1)) { + ref = lua_tointeger(L, -1); + tag = MAR_TREF; + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&ref, MAR_I32, buf); + lua_pop(L, 1); + } + else { + mar_Buffer rec_buf; + lua_pop(L, 1); /* pop nil */ + if (luaL_getmetafield(L, -1, "__persist")) { + tag = MAR_TUSR; + + lua_pushvalue(L, -2); /* self */ + lua_call(L, 1, 1); + if (!lua_isfunction(L, -1)) { + luaL_error(L, "__persist must return a function"); + } + + lua_remove(L, -2); /* __persist */ + + lua_newtable(L); + lua_pushvalue(L, -2); /* callback */ + lua_rawseti(L, -2, 1); + + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + lua_pop(L, 1); + } + else { + tag = MAR_TVAL; + + lua_pushvalue(L, -1); + lua_pushinteger(L, (*idx)++); + lua_rawset(L, SEEN_IDX); + + lua_pushvalue(L, -1); + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + lua_pop(L, 1); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data,rec_buf.head, buf); + buf_done(L, &rec_buf); + } + } + break; + } + case LUA_TFUNCTION: { + int tag, ref; + lua_pushvalue(L, -1); + lua_rawget(L, SEEN_IDX); + if (!lua_isnil(L, -1)) { + ref = lua_tointeger(L, -1); + tag = MAR_TREF; + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&ref, MAR_I32, buf); + lua_pop(L, 1); + } + else { + mar_Buffer rec_buf; + int i; + lua_Debug ar; + lua_pop(L, 1); /* pop nil */ + + lua_pushvalue(L, -1); + lua_getinfo(L, ">nuS", &ar); + if (ar.what[0] != 'L') { + luaL_error(L, "attempt to persist a C function '%s'", ar.name); + } + tag = MAR_TVAL; + lua_pushvalue(L, -1); + lua_pushinteger(L, (*idx)++); + lua_rawset(L, SEEN_IDX); + + lua_pushvalue(L, -1); + buf_init(L, &rec_buf); + lua_dump(L, (lua_Writer)buf_write, &rec_buf); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + lua_pop(L, 1); + + lua_newtable(L); + for (i=1; i <= ar.nups; i++) { + lua_getupvalue(L, -2, i); + lua_rawseti(L, -2, i); + } + + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + lua_pop(L, 1); + } + + break; + } + case LUA_TUSERDATA: { + int tag, ref; + lua_pushvalue(L, -1); + lua_rawget(L, SEEN_IDX); + if (!lua_isnil(L, -1)) { + ref = lua_tointeger(L, -1); + tag = MAR_TREF; + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&ref, MAR_I32, buf); + lua_pop(L, 1); + } + else { + mar_Buffer rec_buf; + lua_pop(L, 1); /* pop nil */ + if (luaL_getmetafield(L, -1, "__persist")) { + tag = MAR_TUSR; + + lua_pushvalue(L, -2); + lua_pushinteger(L, (*idx)++); + lua_rawset(L, SEEN_IDX); + + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + if (!lua_isfunction(L, -1)) { + luaL_error(L, "__persist must return a function"); + } + lua_newtable(L); + lua_pushvalue(L, -2); + lua_rawseti(L, -2, 1); + lua_remove(L, -2); + + buf_init(L, &rec_buf); + mar_encode_table(L, &rec_buf, idx); + + buf_write(L, (void*)&tag, MAR_CHR, buf); + buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); + buf_write(L, rec_buf.data, rec_buf.head, buf); + buf_done(L, &rec_buf); + } + else { + luaL_error(L, "attempt to encode userdata (no __persist hook)"); + } + lua_pop(L, 1); + } + break; + } + case LUA_TNIL: break; + default: + luaL_error(L, "invalid value type (%s)", lua_typename(L, val_type)); + } + lua_pop(L, 1); +} + +static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx) +{ + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + mar_encode_value(L, buf, -2, idx); + mar_encode_value(L, buf, -1, idx); + lua_pop(L, 1); + } + return 1; +} + +#define mar_incr_ptr(l) \ + if (((*p)-buf)+(l) > len) luaL_error(L, "bad code"); (*p) += (l); + +#define mar_next_len(l,T) \ + if (((*p)-buf)+sizeof(T) > len) luaL_error(L, "bad code"); \ + l = *(T*)*p; (*p) += sizeof(T); + +static void mar_decode_value + (lua_State *L, const char *buf, size_t len, const char **p, size_t *idx) +{ + size_t l; + char val_type = **p; + mar_incr_ptr(MAR_CHR); + switch (val_type) { + case LUA_TBOOLEAN: + lua_pushboolean(L, *(char*)*p); + mar_incr_ptr(MAR_CHR); + break; + case LUA_TNUMBER: + lua_pushnumber(L, *(lua_Number*)*p); + mar_incr_ptr(MAR_I64); + break; + case LUA_TSTRING: + mar_next_len(l, uint32_t); + lua_pushlstring(L, *p, l); + mar_incr_ptr(l); + break; + case LUA_TTABLE: { + char tag = *(char*)*p; + mar_incr_ptr(MAR_CHR); + if (tag == MAR_TREF) { + int ref; + mar_next_len(ref, int); + lua_rawgeti(L, SEEN_IDX, ref); + } + else if (tag == MAR_TVAL) { + mar_next_len(l, uint32_t); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + mar_decode_table(L, *p, l, idx); + mar_incr_ptr(l); + } + else if (tag == MAR_TUSR) { + mar_next_len(l, uint32_t); + lua_newtable(L); + mar_decode_table(L, *p, l, idx); + lua_rawgeti(L, -1, 1); + lua_call(L, 0, 1); + lua_remove(L, -2); + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + mar_incr_ptr(l); + } + else { + luaL_error(L, "bad encoded data"); + } + break; + } + case LUA_TFUNCTION: { + size_t nups; + int i; + mar_Buffer dec_buf; + char tag = *(char*)*p; + mar_incr_ptr(1); + if (tag == MAR_TREF) { + int ref; + mar_next_len(ref, int); + lua_rawgeti(L, SEEN_IDX, ref); + } + else { + mar_next_len(l, uint32_t); + dec_buf.data = (char*)*p; + dec_buf.size = l; + dec_buf.head = l; + dec_buf.seek = 0; + lua_load(L, (lua_Reader)buf_read, &dec_buf, "=marshal"); + mar_incr_ptr(l); + + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + + mar_next_len(l, uint32_t); + lua_newtable(L); + mar_decode_table(L, *p, l, idx); + nups = lua_objlen(L, -1); + for (i=1; i <= nups; i++) { + lua_rawgeti(L, -1, i); + lua_setupvalue(L, -3, i); + } + lua_pop(L, 1); + mar_incr_ptr(l); + } + break; + } + case LUA_TUSERDATA: { + char tag = *(char*)*p; + mar_incr_ptr(MAR_CHR); + if (tag == MAR_TREF) { + int ref; + mar_next_len(ref, int); + lua_rawgeti(L, SEEN_IDX, ref); + } + else if (tag == MAR_TUSR) { + mar_next_len(l, uint32_t); + lua_newtable(L); + mar_decode_table(L, *p, l, idx); + lua_rawgeti(L, -1, 1); + lua_call(L, 0, 1); + lua_remove(L, -2); + lua_pushvalue(L, -1); + lua_rawseti(L, SEEN_IDX, (*idx)++); + mar_incr_ptr(l); + } + else { /* tag == MAR_TVAL */ + lua_pushnil(L); + } + break; + } + case LUA_TNIL: + case LUA_TTHREAD: + lua_pushnil(L); + break; + default: + luaL_error(L, "bad code"); + } +} + +static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx) +{ + const char* p; + p = buf; + while (p - buf < len) { + mar_decode_value(L, buf, len, &p, idx); + mar_decode_value(L, buf, len, &p, idx); + lua_settable(L, -3); + } + return 1; +} + +static int mar_encode(lua_State* L) +{ + const unsigned char m = MAR_MAGIC; + size_t idx, len; + mar_Buffer buf; + + if (lua_isnone(L, 1)) { + lua_pushnil(L); + } + if (lua_isnoneornil(L, 2)) { + lua_newtable(L); + } + else if (!lua_istable(L, 2)) { + luaL_error(L, "bad argument #2 to encode (expected table)"); + } + lua_settop(L, 2); + + len = lua_objlen(L, 2); + lua_newtable(L); + for (idx = 1; idx <= len; idx++) { + lua_rawgeti(L, 2, idx); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + continue; + } + lua_pushinteger(L, idx); + lua_rawset(L, SEEN_IDX); + } + lua_pushvalue(L, 1); + + buf_init(L, &buf); + buf_write(L, (void*)&m, 1, &buf); + + mar_encode_value(L, &buf, -1, &idx); + + lua_pop(L, 1); + + lua_pushlstring(L, buf.data, buf.head); + + buf_done(L, &buf); + + lua_remove(L, SEEN_IDX); + + return 1; +} + +static int mar_decode(lua_State* L) +{ + size_t l, idx, len; + const char *p; + const char *s = luaL_checklstring(L, 1, &l); + + if (l < 1) luaL_error(L, "bad header"); + if (*(unsigned char *)s++ != MAR_MAGIC) luaL_error(L, "bad magic"); + l -= 1; + + if (lua_isnoneornil(L, 2)) { + lua_newtable(L); + } + else if (!lua_istable(L, 2)) { + luaL_error(L, "bad argument #2 to decode (expected table)"); + } + lua_settop(L, 2); + + len = lua_objlen(L, 2); + lua_newtable(L); + for (idx = 1; idx <= len; idx++) { + lua_rawgeti(L, 2, idx); + lua_rawseti(L, SEEN_IDX, idx); + } + + p = s; + mar_decode_value(L, s, l, &p, &idx); + + lua_remove(L, SEEN_IDX); + lua_remove(L, 2); + + return 1; +} + +static int mar_clone(lua_State* L) +{ + mar_encode(L); + lua_replace(L, 1); + mar_decode(L); + return 1; +} + +static const luaL_reg R[] = +{ + {"encode", mar_encode}, + {"decode", mar_decode}, + {"clone", mar_clone}, + {NULL, NULL} +}; + +int luaopen_marshal(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, "marshal", R); + return 1; +} + + + + + |