├── Makefile ├── README.md ├── lmarshal.c └── test.lua /Makefile: -------------------------------------------------------------------------------- 1 | # makefile for marshal library for Lua 2 | 3 | # change these to reflect your Lua installation 4 | LUA= /usr/local/bin/lua 5 | LUAINC= /usr/local/include 6 | LUALIB= $(LUA) 7 | LUABIN= /usr/local/bin 8 | 9 | WARN= -ansi -pedantic -Wall 10 | INCS= -I$(LUAINC) 11 | 12 | CFLAGS=-O3 $(INCS) 13 | LDFLAGS= 14 | 15 | OS_NAME=$(shell uname -s) 16 | MH_NAME=$(shell uname -m) 17 | 18 | ifeq ($(OS_NAME), Darwin) 19 | CFLAGS+=-bundle -undefined dynamic_lookup 20 | else ifeq ($(OS_NAME), Linux) 21 | CFLAGS+=-shared -fPIC 22 | endif 23 | 24 | MYNAME= marshal 25 | MYLIB= l$(MYNAME) 26 | T= $(MYNAME).so 27 | OBJS= $(MYLIB).o 28 | TEST= test.lua 29 | 30 | all: test 31 | 32 | test: $T 33 | $(LUABIN)/lua $(TEST) 34 | 35 | o: $(MYLIB).o 36 | 37 | so: $T 38 | 39 | $T: $(OBJS) 40 | $(CC) $(CFLAGS) $(WARN) -o $@ $(OBJS) 41 | 42 | clean: 43 | rm -f $(OBJS) $T 44 | 45 | doc: 46 | @echo "$(MYNAME) library:" 47 | @fgrep '/**' $(MYLIB).c | cut -f2 -d/ | tr -d '*' | sort | column 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fast serialization for Lua 2 | ========================== 3 | 4 | local marshal = require "marshal" 5 | 6 | Provides: 7 | --------- 8 | 9 | * s = marshal.encode(v[, constants]) - serializes a value to a byte stream 10 | * t = marshal.decode(s[, constants]) - deserializes a byte stream to a value 11 | * t = marshal.clone(orig[, constants]) - deep clone a value (deep for tables and functions) 12 | 13 | Features: 14 | --------- 15 | 16 | Serializes tables, which may contain cycles, Lua functions with upvalues and basic data types. 17 | 18 | All functions take an optional constants table which, if encountered during serialization, 19 | are simply referenced from the constants table passed during deserialization. For example: 20 | 21 | ```Lua 22 | local orig = { answer = 42, print = print } 23 | local pack = marshal.encode(orig, { print }) 24 | local copy = marshal.decode(pack, { print }) 25 | assert(copy.print == print) 26 | ``` 27 | 28 | Hooks 29 | ----- 30 | 31 | A hook is provided for influencing serializing behaviour via the `__persist` metamethod. 32 | The `__persist` metamethod is expected to return a closure which is called during 33 | deserialization. The return value of the closure is taken as the final decoded result. 34 | 35 | This is useful for serializing both userdata and for use with object-oriented Lua, 36 | since metatables are not serialized. 37 | 38 | For example: 39 | 40 | ```Lua 41 | local Point = { } 42 | function Point:new(x, y) 43 | self.__index = self 44 | return setmetatable({ x = x, y = y }, self) 45 | end 46 | function Point:move(x, y) 47 | self.x = x 48 | self.y = y 49 | end 50 | function Point:__persist() 51 | local x = self.x 52 | local y = self.y 53 | return function() 54 | -- do NOT refer to self in this scope 55 | return setmetatable({ x = x, y = y }, Point) 56 | end 57 | end 58 | ``` 59 | The above shows a way to persist an "instance" of Point (if you're thinking 60 | in OO terms). In this case `Point` itself will be included in the encoded chunk 61 | because it's referenced as an upvalue of the returned closure. 62 | 63 | The `__persist` hook may *NOT* refer to the receiver (i.e. `self` 64 | in the example) because this will cause deep recursion when upvalues 65 | are serialized. 66 | 67 | Limitations: 68 | ------------ 69 | 70 | Coroutines are not serialized. Userdata doesn't serialize either 71 | however support for userdata the `__persist` metatable hook can be used. 72 | 73 | Metatables and function environments are not serialized. 74 | 75 | Attempt to serialize C functions, threads and userdata without a `__persist` hook 76 | raises an exception. 77 | 78 | Serialized code is not portable. 79 | 80 | -------------------------------------------------------------------------------- /lmarshal.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lmarshal.c 3 | * A Lua library for serializing and deserializing Lua values 4 | * Richard Hundt 5 | * 6 | * License: MIT 7 | * 8 | * Copyright (c) 2010 Richard Hundt 9 | * 10 | * Permission is hereby granted, free of charge, to any person 11 | * obtaining a copy of this software and associated documentation 12 | * files (the "Software"), to deal in the Software without 13 | * restriction, including without limitation the rights to use, 14 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the 16 | * Software is furnished to do so, subject to the following 17 | * conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be 20 | * included in all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 | * OTHER DEALINGS IN THE SOFTWARE. 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include "lua.h" 37 | #include "lualib.h" 38 | #include "lauxlib.h" 39 | 40 | 41 | #define MAR_TREF 1 42 | #define MAR_TVAL 2 43 | #define MAR_TUSR 3 44 | 45 | #define MAR_CHR 1 46 | #define MAR_I32 4 47 | #define MAR_I64 8 48 | 49 | #define MAR_MAGIC 0x8e 50 | #define SEEN_IDX 3 51 | 52 | typedef struct mar_Buffer { 53 | size_t size; 54 | size_t seek; 55 | size_t head; 56 | char* data; 57 | } mar_Buffer; 58 | 59 | static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx); 60 | static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx); 61 | 62 | static void buf_init(lua_State *L, mar_Buffer *buf) 63 | { 64 | buf->size = 128; 65 | buf->seek = 0; 66 | buf->head = 0; 67 | if (!(buf->data = malloc(buf->size))) luaL_error(L, "Out of memory!"); 68 | } 69 | 70 | static void buf_done(lua_State* L, mar_Buffer *buf) 71 | { 72 | free(buf->data); 73 | } 74 | 75 | static int buf_write(lua_State* L, const char* str, size_t len, mar_Buffer *buf) 76 | { 77 | if (len > UINT32_MAX) luaL_error(L, "buffer too long"); 78 | if (buf->size - buf->head < len) { 79 | size_t new_size = buf->size << 1; 80 | size_t cur_head = buf->head; 81 | while (new_size - cur_head <= len) { 82 | new_size = new_size << 1; 83 | } 84 | if (!(buf->data = realloc(buf->data, new_size))) { 85 | luaL_error(L, "Out of memory!"); 86 | } 87 | buf->size = new_size; 88 | } 89 | memcpy(&buf->data[buf->head], str, len); 90 | buf->head += len; 91 | return 0; 92 | } 93 | 94 | static const char* buf_read(lua_State *L, mar_Buffer *buf, size_t *len) 95 | { 96 | if (buf->seek < buf->head) { 97 | buf->seek = buf->head; 98 | *len = buf->seek; 99 | return buf->data; 100 | } 101 | *len = 0; 102 | return NULL; 103 | } 104 | 105 | static void mar_encode_value(lua_State *L, mar_Buffer *buf, int val, size_t *idx) 106 | { 107 | size_t l; 108 | int val_type = lua_type(L, val); 109 | lua_pushvalue(L, val); 110 | 111 | buf_write(L, (void*)&val_type, MAR_CHR, buf); 112 | switch (val_type) { 113 | case LUA_TBOOLEAN: { 114 | int int_val = lua_toboolean(L, -1); 115 | buf_write(L, (void*)&int_val, MAR_CHR, buf); 116 | break; 117 | } 118 | case LUA_TSTRING: { 119 | const char *str_val = lua_tolstring(L, -1, &l); 120 | buf_write(L, (void*)&l, MAR_I32, buf); 121 | buf_write(L, str_val, l, buf); 122 | break; 123 | } 124 | case LUA_TNUMBER: { 125 | lua_Number num_val = lua_tonumber(L, -1); 126 | buf_write(L, (void*)&num_val, MAR_I64, buf); 127 | break; 128 | } 129 | case LUA_TTABLE: { 130 | int tag, ref; 131 | lua_pushvalue(L, -1); 132 | lua_rawget(L, SEEN_IDX); 133 | if (!lua_isnil(L, -1)) { 134 | ref = lua_tointeger(L, -1); 135 | tag = MAR_TREF; 136 | buf_write(L, (void*)&tag, MAR_CHR, buf); 137 | buf_write(L, (void*)&ref, MAR_I32, buf); 138 | lua_pop(L, 1); 139 | } 140 | else { 141 | mar_Buffer rec_buf; 142 | lua_pop(L, 1); /* pop nil */ 143 | if (luaL_getmetafield(L, -1, "__persist")) { 144 | tag = MAR_TUSR; 145 | 146 | lua_pushvalue(L, -2); /* self */ 147 | lua_call(L, 1, 1); 148 | if (!lua_isfunction(L, -1)) { 149 | luaL_error(L, "__persist must return a function"); 150 | } 151 | 152 | lua_remove(L, -2); /* __persist */ 153 | 154 | lua_newtable(L); 155 | lua_pushvalue(L, -2); /* callback */ 156 | lua_rawseti(L, -2, 1); 157 | 158 | buf_init(L, &rec_buf); 159 | mar_encode_table(L, &rec_buf, idx); 160 | 161 | buf_write(L, (void*)&tag, MAR_CHR, buf); 162 | buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); 163 | buf_write(L, rec_buf.data, rec_buf.head, buf); 164 | buf_done(L, &rec_buf); 165 | lua_pop(L, 1); 166 | } 167 | else { 168 | tag = MAR_TVAL; 169 | 170 | lua_pushvalue(L, -1); 171 | lua_pushinteger(L, (*idx)++); 172 | lua_rawset(L, SEEN_IDX); 173 | 174 | lua_pushvalue(L, -1); 175 | buf_init(L, &rec_buf); 176 | mar_encode_table(L, &rec_buf, idx); 177 | lua_pop(L, 1); 178 | 179 | buf_write(L, (void*)&tag, MAR_CHR, buf); 180 | buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); 181 | buf_write(L, rec_buf.data,rec_buf.head, buf); 182 | buf_done(L, &rec_buf); 183 | } 184 | } 185 | break; 186 | } 187 | case LUA_TFUNCTION: { 188 | int tag, ref; 189 | lua_pushvalue(L, -1); 190 | lua_rawget(L, SEEN_IDX); 191 | if (!lua_isnil(L, -1)) { 192 | ref = lua_tointeger(L, -1); 193 | tag = MAR_TREF; 194 | buf_write(L, (void*)&tag, MAR_CHR, buf); 195 | buf_write(L, (void*)&ref, MAR_I32, buf); 196 | lua_pop(L, 1); 197 | } 198 | else { 199 | mar_Buffer rec_buf; 200 | int i; 201 | lua_Debug ar; 202 | lua_pop(L, 1); /* pop nil */ 203 | 204 | lua_pushvalue(L, -1); 205 | lua_getinfo(L, ">nuS", &ar); 206 | if (ar.what[0] != 'L') { 207 | luaL_error(L, "attempt to persist a C function '%s'", ar.name); 208 | } 209 | tag = MAR_TVAL; 210 | lua_pushvalue(L, -1); 211 | lua_pushinteger(L, (*idx)++); 212 | lua_rawset(L, SEEN_IDX); 213 | 214 | lua_pushvalue(L, -1); 215 | buf_init(L, &rec_buf); 216 | lua_dump(L, (lua_Writer)buf_write, &rec_buf); 217 | 218 | buf_write(L, (void*)&tag, MAR_CHR, buf); 219 | buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); 220 | buf_write(L, rec_buf.data, rec_buf.head, buf); 221 | buf_done(L, &rec_buf); 222 | lua_pop(L, 1); 223 | 224 | lua_newtable(L); 225 | for (i=1; i <= ar.nups; i++) { 226 | lua_getupvalue(L, -2, i); 227 | lua_rawseti(L, -2, i); 228 | } 229 | 230 | buf_init(L, &rec_buf); 231 | mar_encode_table(L, &rec_buf, idx); 232 | 233 | buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); 234 | buf_write(L, rec_buf.data, rec_buf.head, buf); 235 | buf_done(L, &rec_buf); 236 | lua_pop(L, 1); 237 | } 238 | 239 | break; 240 | } 241 | case LUA_TUSERDATA: { 242 | int tag, ref; 243 | lua_pushvalue(L, -1); 244 | lua_rawget(L, SEEN_IDX); 245 | if (!lua_isnil(L, -1)) { 246 | ref = lua_tointeger(L, -1); 247 | tag = MAR_TREF; 248 | buf_write(L, (void*)&tag, MAR_CHR, buf); 249 | buf_write(L, (void*)&ref, MAR_I32, buf); 250 | lua_pop(L, 1); 251 | } 252 | else { 253 | mar_Buffer rec_buf; 254 | lua_pop(L, 1); /* pop nil */ 255 | if (luaL_getmetafield(L, -1, "__persist")) { 256 | tag = MAR_TUSR; 257 | 258 | lua_pushvalue(L, -2); 259 | lua_pushinteger(L, (*idx)++); 260 | lua_rawset(L, SEEN_IDX); 261 | 262 | lua_pushvalue(L, -2); 263 | lua_call(L, 1, 1); 264 | if (!lua_isfunction(L, -1)) { 265 | luaL_error(L, "__persist must return a function"); 266 | } 267 | lua_newtable(L); 268 | lua_pushvalue(L, -2); 269 | lua_rawseti(L, -2, 1); 270 | lua_remove(L, -2); 271 | 272 | buf_init(L, &rec_buf); 273 | mar_encode_table(L, &rec_buf, idx); 274 | 275 | buf_write(L, (void*)&tag, MAR_CHR, buf); 276 | buf_write(L, (void*)&rec_buf.head, MAR_I32, buf); 277 | buf_write(L, rec_buf.data, rec_buf.head, buf); 278 | buf_done(L, &rec_buf); 279 | } 280 | else { 281 | luaL_error(L, "attempt to encode userdata (no __persist hook)"); 282 | } 283 | lua_pop(L, 1); 284 | } 285 | break; 286 | } 287 | case LUA_TNIL: break; 288 | default: 289 | luaL_error(L, "invalid value type (%s)", lua_typename(L, val_type)); 290 | } 291 | lua_pop(L, 1); 292 | } 293 | 294 | static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx) 295 | { 296 | lua_pushnil(L); 297 | while (lua_next(L, -2) != 0) { 298 | mar_encode_value(L, buf, -2, idx); 299 | mar_encode_value(L, buf, -1, idx); 300 | lua_pop(L, 1); 301 | } 302 | return 1; 303 | } 304 | 305 | #define mar_incr_ptr(l) \ 306 | if (((*p)-buf)+(l) > len) luaL_error(L, "bad code"); (*p) += (l); 307 | 308 | #define mar_next_len(l,T) \ 309 | if (((*p)-buf)+sizeof(T) > len) luaL_error(L, "bad code"); \ 310 | l = *(T*)*p; (*p) += sizeof(T); 311 | 312 | static void mar_decode_value 313 | (lua_State *L, const char *buf, size_t len, const char **p, size_t *idx) 314 | { 315 | size_t l; 316 | char val_type = **p; 317 | mar_incr_ptr(MAR_CHR); 318 | switch (val_type) { 319 | case LUA_TBOOLEAN: 320 | lua_pushboolean(L, *(char*)*p); 321 | mar_incr_ptr(MAR_CHR); 322 | break; 323 | case LUA_TNUMBER: 324 | lua_pushnumber(L, *(lua_Number*)*p); 325 | mar_incr_ptr(MAR_I64); 326 | break; 327 | case LUA_TSTRING: 328 | mar_next_len(l, uint32_t); 329 | lua_pushlstring(L, *p, l); 330 | mar_incr_ptr(l); 331 | break; 332 | case LUA_TTABLE: { 333 | char tag = *(char*)*p; 334 | mar_incr_ptr(MAR_CHR); 335 | if (tag == MAR_TREF) { 336 | int ref; 337 | mar_next_len(ref, int); 338 | lua_rawgeti(L, SEEN_IDX, ref); 339 | } 340 | else if (tag == MAR_TVAL) { 341 | mar_next_len(l, uint32_t); 342 | lua_newtable(L); 343 | lua_pushvalue(L, -1); 344 | lua_rawseti(L, SEEN_IDX, (*idx)++); 345 | mar_decode_table(L, *p, l, idx); 346 | mar_incr_ptr(l); 347 | } 348 | else if (tag == MAR_TUSR) { 349 | mar_next_len(l, uint32_t); 350 | lua_newtable(L); 351 | mar_decode_table(L, *p, l, idx); 352 | lua_rawgeti(L, -1, 1); 353 | lua_call(L, 0, 1); 354 | lua_remove(L, -2); 355 | lua_pushvalue(L, -1); 356 | lua_rawseti(L, SEEN_IDX, (*idx)++); 357 | mar_incr_ptr(l); 358 | } 359 | else { 360 | luaL_error(L, "bad encoded data"); 361 | } 362 | break; 363 | } 364 | case LUA_TFUNCTION: { 365 | size_t nups; 366 | int i; 367 | mar_Buffer dec_buf; 368 | char tag = *(char*)*p; 369 | mar_incr_ptr(1); 370 | if (tag == MAR_TREF) { 371 | int ref; 372 | mar_next_len(ref, int); 373 | lua_rawgeti(L, SEEN_IDX, ref); 374 | } 375 | else { 376 | mar_next_len(l, uint32_t); 377 | dec_buf.data = (char*)*p; 378 | dec_buf.size = l; 379 | dec_buf.head = l; 380 | dec_buf.seek = 0; 381 | lua_load(L, (lua_Reader)buf_read, &dec_buf, "=marshal"); 382 | mar_incr_ptr(l); 383 | 384 | lua_pushvalue(L, -1); 385 | lua_rawseti(L, SEEN_IDX, (*idx)++); 386 | 387 | mar_next_len(l, uint32_t); 388 | lua_newtable(L); 389 | mar_decode_table(L, *p, l, idx); 390 | nups = lua_objlen(L, -1); 391 | for (i=1; i <= nups; i++) { 392 | lua_rawgeti(L, -1, i); 393 | lua_setupvalue(L, -3, i); 394 | } 395 | lua_pop(L, 1); 396 | mar_incr_ptr(l); 397 | } 398 | break; 399 | } 400 | case LUA_TUSERDATA: { 401 | char tag = *(char*)*p; 402 | mar_incr_ptr(MAR_CHR); 403 | if (tag == MAR_TREF) { 404 | int ref; 405 | mar_next_len(ref, int); 406 | lua_rawgeti(L, SEEN_IDX, ref); 407 | } 408 | else if (tag == MAR_TUSR) { 409 | mar_next_len(l, uint32_t); 410 | lua_newtable(L); 411 | mar_decode_table(L, *p, l, idx); 412 | lua_rawgeti(L, -1, 1); 413 | lua_call(L, 0, 1); 414 | lua_remove(L, -2); 415 | lua_pushvalue(L, -1); 416 | lua_rawseti(L, SEEN_IDX, (*idx)++); 417 | mar_incr_ptr(l); 418 | } 419 | else { /* tag == MAR_TVAL */ 420 | lua_pushnil(L); 421 | } 422 | break; 423 | } 424 | case LUA_TNIL: 425 | case LUA_TTHREAD: 426 | lua_pushnil(L); 427 | break; 428 | default: 429 | luaL_error(L, "bad code"); 430 | } 431 | } 432 | 433 | static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx) 434 | { 435 | const char* p; 436 | p = buf; 437 | while (p - buf < len) { 438 | mar_decode_value(L, buf, len, &p, idx); 439 | mar_decode_value(L, buf, len, &p, idx); 440 | lua_settable(L, -3); 441 | } 442 | return 1; 443 | } 444 | 445 | static int mar_encode(lua_State* L) 446 | { 447 | const unsigned char m = MAR_MAGIC; 448 | size_t idx, len; 449 | mar_Buffer buf; 450 | 451 | if (lua_isnone(L, 1)) { 452 | lua_pushnil(L); 453 | } 454 | if (lua_isnoneornil(L, 2)) { 455 | lua_newtable(L); 456 | } 457 | else if (!lua_istable(L, 2)) { 458 | luaL_error(L, "bad argument #2 to encode (expected table)"); 459 | } 460 | lua_settop(L, 2); 461 | 462 | len = lua_objlen(L, 2); 463 | lua_newtable(L); 464 | for (idx = 1; idx <= len; idx++) { 465 | lua_rawgeti(L, 2, idx); 466 | if (lua_isnil(L, -1)) { 467 | lua_pop(L, 1); 468 | continue; 469 | } 470 | lua_pushinteger(L, idx); 471 | lua_rawset(L, SEEN_IDX); 472 | } 473 | lua_pushvalue(L, 1); 474 | 475 | buf_init(L, &buf); 476 | buf_write(L, (void*)&m, 1, &buf); 477 | 478 | mar_encode_value(L, &buf, -1, &idx); 479 | 480 | lua_pop(L, 1); 481 | 482 | lua_pushlstring(L, buf.data, buf.head); 483 | 484 | buf_done(L, &buf); 485 | 486 | lua_remove(L, SEEN_IDX); 487 | 488 | return 1; 489 | } 490 | 491 | static int mar_decode(lua_State* L) 492 | { 493 | size_t l, idx, len; 494 | const char *p; 495 | const char *s = luaL_checklstring(L, 1, &l); 496 | 497 | if (l < 1) luaL_error(L, "bad header"); 498 | if (*(unsigned char *)s++ != MAR_MAGIC) luaL_error(L, "bad magic"); 499 | l -= 1; 500 | 501 | if (lua_isnoneornil(L, 2)) { 502 | lua_newtable(L); 503 | } 504 | else if (!lua_istable(L, 2)) { 505 | luaL_error(L, "bad argument #2 to decode (expected table)"); 506 | } 507 | lua_settop(L, 2); 508 | 509 | len = lua_objlen(L, 2); 510 | lua_newtable(L); 511 | for (idx = 1; idx <= len; idx++) { 512 | lua_rawgeti(L, 2, idx); 513 | lua_rawseti(L, SEEN_IDX, idx); 514 | } 515 | 516 | p = s; 517 | mar_decode_value(L, s, l, &p, &idx); 518 | 519 | lua_remove(L, SEEN_IDX); 520 | lua_remove(L, 2); 521 | 522 | return 1; 523 | } 524 | 525 | static int mar_clone(lua_State* L) 526 | { 527 | mar_encode(L); 528 | lua_replace(L, 1); 529 | mar_decode(L); 530 | return 1; 531 | } 532 | 533 | static const luaL_reg R[] = 534 | { 535 | {"encode", mar_encode}, 536 | {"decode", mar_decode}, 537 | {"clone", mar_clone}, 538 | {NULL, NULL} 539 | }; 540 | 541 | int luaopen_marshal(lua_State *L) 542 | { 543 | lua_newtable(L); 544 | luaL_register(L, NULL, R); 545 | return 1; 546 | } 547 | 548 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | local marshal = require "marshal" 2 | 3 | local k = { "tkey" } 4 | local a = { "a", "b", "c", [k] = "tval" } 5 | local s = assert(marshal.encode(a)) 6 | --print(string.format("%q", s)) 7 | local t = marshal.decode(s) 8 | --print(t) 9 | table.foreach(t, print) 10 | assert(t[1] == "a") 11 | assert(t[2] == "b") 12 | assert(t[3] == "c") 13 | ---[==[ 14 | assert(#t == 3) 15 | local _k = next(t, #t) 16 | assert(type(_k) == "table") 17 | assert(_k[1] == "tkey") 18 | assert(t[_k] == "tval") 19 | 20 | local o = { } 21 | o.__index = o 22 | local s = marshal.encode(o) 23 | local t = marshal.decode(s) 24 | assert(type(t) == 'table') 25 | assert(t.__index == t) 26 | 27 | local up = 69 28 | local s = marshal.encode({ answer = 42, funky = function() return up end }) 29 | local t = marshal.decode(s) 30 | assert(t.answer == 42) 31 | assert(type(t.funky) == "function") 32 | assert(t.funky() == up) 33 | 34 | local t = { answer = 42 } 35 | local c = { "cycle" } 36 | c.this = c 37 | t.here = c 38 | local s = marshal.encode(t) 39 | local u = marshal.decode(s) 40 | assert(u.answer == 42) 41 | assert(type(u.here) == "table") 42 | assert(u.here == u.here.this) 43 | assert(u.here[1] == "cycle") 44 | 45 | local o = { x = 11, y = 22 } 46 | local seen_hook 47 | setmetatable(o, { 48 | __persist = function(o) 49 | local x = o.x 50 | local y = o.y 51 | seen_hook = true 52 | local mt = getmetatable(o) 53 | local print = print 54 | return function() 55 | local o = { } 56 | o.x = x 57 | o.y = y 58 | print("constant table: 'print'") 59 | return setmetatable(o, mt) 60 | end 61 | end 62 | }) 63 | 64 | local s = marshal.encode(o, { print }) 65 | assert(seen_hook) 66 | local p = marshal.decode(s, { print }) 67 | assert(p ~= o) 68 | assert(p.x == o.x) 69 | assert(p.y == o.y) 70 | assert(getmetatable(p)) 71 | assert(type(getmetatable(p).__persist) == "function") 72 | 73 | local o = { 42 } 74 | local a = { o, o, o } 75 | local s = marshal.encode(a) 76 | local t = marshal.decode(s) 77 | assert(type(t[1]) == "table") 78 | assert(t[1] == t[2]) 79 | assert(t[2] == t[3]) 80 | 81 | local u = { 42 } 82 | local f = function() return u end 83 | local a = { f, f, u, f } 84 | local s = marshal.encode(a) 85 | local t = marshal.decode(s) 86 | assert(type(t[1]) == "function") 87 | assert(t[1] == t[2]) 88 | assert(t[2] == t[4]) 89 | assert(type(t[1]()) == "table") 90 | assert(type(t[3]) == "table") 91 | assert(t[1]() == t[3]) 92 | 93 | local u = function() return 42 end 94 | local f = function() return u end 95 | local a = { f, f, f, u } 96 | local s = marshal.encode(a) 97 | local t = marshal.decode(s) 98 | assert(type(t[1]) == "function") 99 | assert(t[1] == t[2]) 100 | assert(t[2] == t[3]) 101 | assert(type(t[1]()) == "function") 102 | assert(type(t[4]) == "function") 103 | assert(t[1]() == t[4]) 104 | 105 | local u = newproxy() 106 | debug.setmetatable(u, { 107 | __persist = function() 108 | return function() 109 | return newproxy() 110 | end 111 | end 112 | }) 113 | 114 | local s = marshal.encode{u} 115 | local t = marshal.decode(s) 116 | assert(type(t[1]) == "userdata") 117 | 118 | local t1 = { 1, 'a', b = 'b' } 119 | table.foreach(t1, print) 120 | local t2 = marshal.clone(t1) 121 | print('---') 122 | table.foreach(t1, print) 123 | print('---') 124 | table.foreach(t2, print) 125 | assert(t1[1] == t2[1]) 126 | assert(t1[2] == t2[2]) 127 | assert(t1.b == t2.b) 128 | 129 | local t1 = marshal.clone({ }) 130 | 131 | local answer = 42 132 | local f1 = function() 133 | return "answer: "..answer 134 | end 135 | local s1 = marshal.encode(f1) 136 | local f2 = marshal.decode(s1) 137 | assert(f2() == 'answer: 42') 138 | 139 | assert(marshal.decode(marshal.encode()) == nil) 140 | assert(marshal.decode(marshal.encode(nil)) == nil) 141 | 142 | local s1 = marshal.encode(pt) 143 | local p2 = marshal.decode(s1) 144 | print(string.format('%q',s1)) 145 | 146 | print "OK" 147 | 148 | --[[ micro-bench (~4.2 seconds on my laptop) 149 | local t = { a='a', b='b', c='c', d='d', hop='jump', skip='foo', answer=42 } 150 | local s = marshal.encode(t) 151 | for i=1, 1000000 do 152 | s = marshal.encode(t) 153 | t = marshal.decode(s) 154 | end 155 | --]] 156 | --]==] 157 | 158 | --------------------------------------------------------------------------------