├── lib └── .keepme ├── obj └── .keepme ├── tmp └── .keepme ├── README ├── include └── .keepme ├── test ├── large_data.lua ├── large_data.luabins ├── util.c ├── util.h ├── test.c ├── test.h ├── test_fwrite_api.c ├── test_write_api.c ├── write_tests.inc ├── test_api.c ├── test_savebuffer.c └── test.lua ├── AUTHORS ├── etc ├── genmakefile.sh ├── toluabins.lua ├── checkfmt.lua ├── tolua.lua ├── dataset.lua ├── benchmark.lua └── Makefile.luabins.template ├── .gitignore ├── src ├── luaheaders.h ├── lualess.c ├── fwrite.h ├── lualess.h ├── fwrite.c ├── luainternals.h ├── write.h ├── luainternals.c ├── write.c ├── savebuffer.h ├── luabins.c ├── luabins.h ├── saveload.h ├── savebuffer.c ├── save.c └── load.c ├── rockspec ├── luabins-0.1.1-1.rockspec ├── luabins-scm-1.rockspec ├── luabins-0.1.1-2.rockspec ├── luabins-scm-2.rockspec ├── luabins-0.2-1.rockspec └── luabins-0.3-1.rockspec ├── TODO ├── HISTORY ├── COPYRIGHT ├── BENCHMARK ├── README.md └── Makefile /lib/.keepme: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /obj/.keepme: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keepme: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /include/.keepme: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/large_data.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agladysh/luabins/HEAD/test/large_data.lua -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Luabins authors: 2 | ---------------- 3 | 4 | Alexander Gladysh 5 | -------------------------------------------------------------------------------- /test/large_data.luabins: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agladysh/luabins/HEAD/test/large_data.luabins -------------------------------------------------------------------------------- /etc/genmakefile.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | lua ~/projects/genmakefile/src/genmakefile.lua Makefile 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .*~ 3 | \#*\# 4 | .\#*\# 5 | 6 | .DS_Store 7 | .project 8 | .settings 9 | 10 | /bin/ 11 | /include/ 12 | /lib/ 13 | /obj/ 14 | /tmp/ 15 | 16 | lua-nucleo 17 | -------------------------------------------------------------------------------- /test/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * util.c 3 | * Luabins test utilities 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include "util.h" 8 | 9 | void fprintbuf(FILE * out, const unsigned char * b, size_t len) 10 | { 11 | size_t i = 0; 12 | for (i = 0; i < len; ++i) 13 | { 14 | fprintf(out, "%02X ", b[i]); 15 | } 16 | fprintf(out, "\n"); 17 | } 18 | -------------------------------------------------------------------------------- /test/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * util.h 3 | * Luabins test utilities 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #ifndef LUABINS_TEST_UTIL_H_INCLUDED_ 8 | #define LUABINS_TEST_UTIL_H_INCLUDED_ 9 | 10 | #include 11 | 12 | void fprintbuf(FILE * out, const unsigned char * b, size_t len); 13 | 14 | #endif /* LUABINS_TEST_UTIL_H_INCLUDED_ */ 15 | -------------------------------------------------------------------------------- /etc/toluabins.lua: -------------------------------------------------------------------------------- 1 | -- Lua to Luabins converter 2 | 3 | package.cpath = "./lib/?.so;"..package.cpath 4 | 5 | local luabins = require 'luabins' 6 | 7 | local filename = select(1, ...) 8 | assert(filename, "Usage: lua toluabins.lua ") 9 | 10 | io.write( 11 | luabins.save( 12 | assert(loadfile(filename))() 13 | ) 14 | ) 15 | 16 | io:flush() 17 | -------------------------------------------------------------------------------- /src/luaheaders.h: -------------------------------------------------------------------------------- 1 | #ifndef LUABINS_LUAHEADERS_H_INCLUDED_ 2 | #define LUABINS_LUAHEADERS_H_INCLUDED_ 3 | 4 | #if defined (__cplusplus) && !defined (LUABINS_LUABUILTASCPP) 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #if defined (__cplusplus) && !defined (LUABINS_LUABUILTASCPP) 11 | } 12 | #endif 13 | 14 | #endif /* LUABINS_LUAHEADERS_H_INCLUDED_ */ 15 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test.c 3 | * Luabins test suite 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include 8 | 9 | #include "test.h" 10 | 11 | int main() 12 | { 13 | #ifdef __cplusplus 14 | printf("luabins C API test compiled as C++\n"); 15 | #else 16 | printf("luabins C API test compiled as plain C\n"); 17 | #endif /* __cplusplus */ 18 | 19 | test_savebuffer(); 20 | test_write_api(); 21 | test_fwrite_api(); 22 | test_api(); 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | /* 2 | * test.h 3 | * Luabins test basics 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #ifndef LUABINS_TEST_H_INCLUDED_ 8 | #define LUABINS_TEST_H_INCLUDED_ 9 | 10 | #define STRINGIZE(s) #s 11 | 12 | #define TEST(name, body) \ 13 | static void name() \ 14 | { \ 15 | printf("---> BEGIN %s\n", STRINGIZE(name)); \ 16 | body \ 17 | printf("---> OK\n"); \ 18 | } 19 | 20 | void test_savebuffer(); 21 | void test_write_api(); 22 | void test_fwrite_api(); 23 | void test_api(); 24 | 25 | #endif /* LUABINS_TEST_H_INCLUDED_ */ 26 | -------------------------------------------------------------------------------- /rockspec/luabins-0.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luabins" 2 | version = "0.1.1-1" 3 | source = { 4 | url = "http://cloud.github.com/downloads/agladysh/luabins/luabins-0.1.1.tar.gz" 5 | } 6 | description = { 7 | summary = "Trivial Lua Binary Serialization Library", 8 | detailed = [[ 9 | Luabins allows to save tuples of primitive Lua types into binary chunks and to load saved data back. 10 | ]], 11 | homepage = "http://github.com/agladysh/luabins", 12 | license = "MIT/X11" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "make" 19 | } 20 | -------------------------------------------------------------------------------- /etc/checkfmt.lua: -------------------------------------------------------------------------------- 1 | --- Luabins format checker 2 | 3 | package.cpath = "./lib/?.so;"..package.cpath 4 | 5 | local luabins = require("luabins") 6 | 7 | local luabins_save, luabins_load = luabins.save, luabins.load 8 | 9 | local filename = select(1, ...) 10 | assert(filename, "Usage: lua checkfmt.lua ") 11 | 12 | local file 13 | if filename == "-" then 14 | file = io.stdin 15 | else 16 | file = assert(io.open(filename, "r")) 17 | end 18 | 19 | assert( 20 | luabins_load( 21 | file:read("*a") 22 | ) 23 | ) 24 | 25 | if file ~= io.stdin then 26 | file:close() 27 | end 28 | file = nil 29 | 30 | print("OK") 31 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | -- Add key-value size-less table format (see luatexts) 2 | -- Add utf-8 char-length string format (see luatexts) 3 | -- Add autotest targets for LuaJIT and LuaJIT 2 x86, x86_64, both for Lua and C. 4 | -- Test on Lua with custom allocator. 5 | -- Use Makefile in rockspec? 6 | -- Enhance "corrupt data" message on load. Need more info on what is wrong. 7 | Ensure every case is covered with tests. 8 | -- Autocompact integers (especially strings -- most of them do not need size_t!) 9 | -- Better cover new exponential growth strategy corner-cases with tests. 10 | -- Shouldn't write and fwrite have common codebase? 11 | -- Document write.h and fwrite.h 12 | -------------------------------------------------------------------------------- /src/lualess.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lualess.h 3 | * Lua-related definitions for lua-less builds (based on Lua manual) 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include 8 | 9 | /* 10 | * lua_Alloc-compatible allocator to use in Lua-less applications 11 | * with lbs_SaveBuffer. Based on sample code from Lua 5.1 manual. 12 | */ 13 | void * lbs_simplealloc( 14 | void * ud, 15 | void * ptr, 16 | size_t osize, 17 | size_t nsize 18 | ) 19 | { 20 | (void) ud; 21 | (void) osize; /* not used */ 22 | 23 | if (nsize == 0) 24 | { 25 | free(ptr); 26 | return NULL; 27 | } 28 | else 29 | { 30 | return realloc(ptr, nsize); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rockspec/luabins-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luabins" 2 | version = "scm-1" 3 | source = { 4 | url = "git://github.com/agladysh/luabins.git" 5 | } 6 | description = { 7 | summary = "Trivial Lua Binary Serialization Library", 8 | detailed = [[ 9 | Luabins allows to save tuples of primitive Lua types into binary chunks and to load saved data back. 10 | ]], 11 | homepage = "http://github.com/agladysh/luabins", 12 | license = "MIT/X11" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | luabins = { 21 | sources = { 22 | "src/load.c", 23 | "src/luabins.c", 24 | "src/luainternals.c", 25 | "src/save.c" 26 | }, 27 | incdirs = { 28 | "src/" 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /etc/tolua.lua: -------------------------------------------------------------------------------- 1 | -- Luabins to Lua converter 2 | -- Requires lua-nucleo (http://github.com/lua-nucleo/lua-nucleo/) 3 | 4 | package.cpath = "./lib/?.so;"..package.cpath 5 | 6 | local luabins = require 'luabins' 7 | 8 | dofile('lua-nucleo/strict.lua') 9 | dofile('lua-nucleo/import.lua') 10 | 11 | local tserialize = import 'lua-nucleo/tserialize.lua' { 'tserialize' } 12 | 13 | local filename = select(1, ...) 14 | assert(filename, "Usage: lua tolua.lua ") 15 | 16 | local file 17 | if filename == "-" then 18 | file = io.stdin 19 | else 20 | file = assert(io.open(filename, "r")) 21 | end 22 | 23 | io.write( 24 | tserialize( 25 | assert( 26 | luabins.load( 27 | file:read("*a") 28 | ) 29 | ) 30 | ) 31 | ) 32 | 33 | io:flush() 34 | 35 | if file ~= io.stdin then 36 | file:close() 37 | end 38 | file = nil 39 | 40 | -------------------------------------------------------------------------------- /rockspec/luabins-0.1.1-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "luabins" 2 | version = "0.1.1-2" 3 | source = { 4 | url = "http://cloud.github.com/downloads/agladysh/luabins/luabins-0.1.1.tar.gz" 5 | } 6 | description = { 7 | summary = "Trivial Lua Binary Serialization Library", 8 | detailed = [[ 9 | Luabins allows to save tuples of primitive Lua types into binary chunks and to load saved data back. 10 | ]], 11 | homepage = "http://github.com/agladysh/luabins", 12 | license = "MIT/X11" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | luabins = { 21 | sources = { 22 | "src/load.c", 23 | "src/luabins.c", 24 | "src/luainternals.c", 25 | "src/save.c" 26 | }, 27 | incdirs = { 28 | "src/" 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/fwrite.h: -------------------------------------------------------------------------------- 1 | /* 2 | * fwrite.h 3 | * Luabins Lua-less write API using FILE * as buffer 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #ifndef LUABINS_FWRITE_H_INCLUDED_ 8 | #define LUABINS_FWRITE_H_INCLUDED_ 9 | 10 | #include "saveload.h" 11 | 12 | #define lbs_fwriteTupleSize(f, tuple_size) \ 13 | fputc((int)(tuple_size), (f)) 14 | 15 | void lbs_fwriteTableHeader( 16 | FILE * f, 17 | int array_size, 18 | int hash_size 19 | ); 20 | 21 | #define lbs_fwriteNil(f) \ 22 | fputc(LUABINS_CNIL, (f)) 23 | 24 | #define lbs_fwriteBoolean(f, value) \ 25 | fputc(((value) == 0) ? LUABINS_CFALSE : LUABINS_CTRUE, (f)) 26 | 27 | void lbs_fwriteNumber(FILE * f, lua_Number value); 28 | 29 | #define lbs_fwriteInteger lbs_fwriteNumber 30 | 31 | void lbs_fwriteString( 32 | FILE * f, 33 | const char * value, 34 | size_t length 35 | ); 36 | 37 | #endif /* LUABINS_FWRITE_H_INCLUDED_ */ 38 | -------------------------------------------------------------------------------- /rockspec/luabins-scm-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "luabins" 2 | version = "scm-2" 3 | source = { 4 | url = "git://github.com/agladysh/luabins.git" 5 | } 6 | description = { 7 | summary = "Trivial Lua Binary Serialization Library", 8 | detailed = [[ 9 | Luabins allows to save tuples of primitive Lua types into binary chunks and to load saved data back. 10 | ]], 11 | homepage = "http://github.com/agladysh/luabins", 12 | license = "MIT/X11" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | luabins = { 21 | sources = { 22 | "src/load.c", 23 | "src/luabins.c", 24 | "src/luainternals.c", 25 | "src/lualess.c", 26 | "src/save.c", 27 | "src/savebuffer.c", 28 | "src/write.c" 29 | }, 30 | incdirs = { 31 | "src/" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lualess.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lualess.h 3 | * Lua-related definitions for lua-less builds (based on Lua manual) 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #ifndef LUABINS_LUALESS_H_INCLUDED_ 8 | #define LUABINS_LUALESS_H_INCLUDED_ 9 | 10 | #include /* ptrdiff_t */ 11 | 12 | /* WARNING: Change these if your luaconf.h has different settings */ 13 | typedef double lua_Number; 14 | typedef ptrdiff_t lua_Integer; 15 | 16 | typedef void * (*lua_Alloc) (void *ud, 17 | void *ptr, 18 | size_t osize, 19 | size_t nsize) 20 | ; 21 | 22 | /* 23 | * lua_Alloc-compatible allocator to use in Lua-less applications 24 | * with lbs_SaveBuffer. Based on sample code from Lua 5.1 manual. 25 | */ 26 | void * lbs_simplealloc( 27 | void * ud, 28 | void * ptr, 29 | size_t osize, 30 | size_t nsize 31 | ); 32 | 33 | #endif /* LUABINS_LUALESS_H_INCLUDED_ */ 34 | -------------------------------------------------------------------------------- /rockspec/luabins-0.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luabins" 2 | version = "0.2-1" 3 | source = { 4 | url = "http://cloud.github.com/downloads/agladysh/luabins/luabins-0.2.tar.gz" 5 | } 6 | description = { 7 | summary = "Trivial Lua Binary Serialization Library", 8 | detailed = [[ 9 | Luabins allows to save tuples of primitive Lua types into binary chunks and to load saved data back. 10 | ]], 11 | homepage = "http://github.com/agladysh/luabins", 12 | license = "MIT/X11" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | luabins = { 21 | sources = { 22 | "src/load.c", 23 | "src/luabins.c", 24 | "src/luainternals.c", 25 | "src/lualess.c", 26 | "src/save.c", 27 | "src/savebuffer.c", 28 | "src/write.c" 29 | }, 30 | incdirs = { 31 | "src/" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rockspec/luabins-0.3-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luabins" 2 | version = "0.3-1" 3 | source = { 4 | url = "http://cloud.github.com/downloads/agladysh/luabins/luabins-0.3.tar.gz" 5 | } 6 | description = { 7 | summary = "Trivial Lua Binary Serialization Library", 8 | detailed = [[ 9 | Luabins allows to save tuples of primitive Lua types into binary chunks and to load saved data back. 10 | ]], 11 | homepage = "http://github.com/agladysh/luabins", 12 | license = "MIT/X11" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | luabins = { 21 | sources = { 22 | "src/load.c", 23 | "src/luabins.c", 24 | "src/luainternals.c", 25 | "src/lualess.c", 26 | "src/save.c", 27 | "src/savebuffer.c", 28 | "src/write.c" 29 | }, 30 | incdirs = { 31 | "src/" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/fwrite.c: -------------------------------------------------------------------------------- 1 | /* 2 | * fwrite.c 3 | * Luabins Lua-less write API using FILE * as buffer 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include "luaheaders.h" 8 | 9 | #include "fwrite.h" 10 | 11 | /* TODO: Note that stream errors are ignored. Handle them better? */ 12 | 13 | void lbs_fwriteTableHeader( 14 | FILE * f, 15 | int array_size, 16 | int hash_size 17 | ) 18 | { 19 | fputc(LUABINS_CTABLE, f); 20 | fwrite( 21 | (const unsigned char *)&array_size, 22 | LUABINS_LINT, 23 | 1, 24 | f 25 | ); 26 | fwrite( 27 | (const unsigned char *)&hash_size, 28 | LUABINS_LINT, 29 | 1, 30 | f 31 | ); 32 | } 33 | 34 | void lbs_fwriteNumber(FILE * f, lua_Number value) 35 | { 36 | fputc(LUABINS_CNUMBER, f); 37 | fwrite((const unsigned char *)&value, LUABINS_LNUMBER, 1, f); 38 | } 39 | 40 | void lbs_fwriteString( 41 | FILE * f, 42 | const char * value, 43 | size_t length 44 | ) 45 | { 46 | fputc(LUABINS_CSTRING, f); 47 | fwrite((const unsigned char *)&length, LUABINS_LSIZET, 1, f); 48 | fwrite((const unsigned char *)value, length, 1, f); 49 | } 50 | -------------------------------------------------------------------------------- /src/luainternals.h: -------------------------------------------------------------------------------- 1 | /* 2 | * luainternals.h 3 | * Code quoted from MIT-licensed Lua 5.1.4 internals 4 | * See copyright notice in lua.h 5 | */ 6 | 7 | #ifndef LUABINS_LUAINTERNALS_H_INCLUDED_ 8 | #define LUABINS_LUAINTERNALS_H_INCLUDED_ 9 | 10 | /* 11 | * BEGIN COPY-PASTE FROM Lua 5.1.4 luaconf.h 12 | * WARNING: If your Lua config differs, fix this! 13 | */ 14 | 15 | #define luai_numeq(a,b) ((a)==(b)) 16 | #define luai_numisnan(a) (!luai_numeq((a), (a))) 17 | 18 | /* 19 | * END COPY-PASTE FROM Lua 5.1.4 luaconf.h 20 | */ 21 | 22 | /* 23 | * BEGIN COPY-PASTE FROM Lua 5.1.4 lobject.h 24 | */ 25 | 26 | int luaO_log2 (unsigned int x); 27 | 28 | #define ceillog2(x) (luaO_log2((x)-1) + 1) 29 | 30 | /* 31 | * END COPY-PASTE FROM Lua 5.1.4 lobject.h 32 | */ 33 | 34 | /* 35 | * BEGIN COPY-PASTE FROM Lua 5.1.4 ltable.c 36 | */ 37 | 38 | /* 39 | ** max size of array part is 2^MAXBITS 40 | */ 41 | #if LUAI_BITSINT > 26 42 | #define MAXBITS 26 43 | #else 44 | #define MAXBITS (LUAI_BITSINT-2) 45 | #endif 46 | 47 | #define MAXASIZE (1 << MAXBITS) 48 | 49 | /* 50 | * END COPY-PASTE FROM Lua 5.1.4 ltable.c 51 | */ 52 | 53 | #endif /* LUABINS_LUAINTERNALS_H_INCLUDED_ */ 54 | -------------------------------------------------------------------------------- /HISTORY: -------------------------------------------------------------------------------- 1 | v0.3 2 | ==== 3 | 4 | Format unification for x86 and x86_64. Bug fixes. 5 | 6 | WARNING: Format is not compatible with 0.2 and below. 7 | Only data saved on x86_64 is affected, though. 8 | Data, saved on x86, should load fine. 9 | 10 | New features: 11 | 12 | -- Format change: unified save format for x86 vs. x86_64. 13 | -- API to save data to FILE * stream without Lua (see fwrite.h). 14 | 15 | Bug fixes: 16 | 17 | -- Load: fixed bug in readbyte, now it checks if we have data before read. 18 | -- Load: fixed Lua C stack overflow bug on large data. 19 | 20 | Misc: 21 | 22 | -- Better module information. 23 | Replaced luabins.VERSION with _VERSION, _DESCRIPTION and _COPYRIGHT. 24 | -- Added some CLI tools, useful for Luabins development (see etc/). 25 | -- Some code cleanup. 26 | 27 | v0.2 28 | ==== 29 | 30 | Lua-less saving. 31 | 32 | -- New, 2x faster luabins.save() (see BENCHMARK). 33 | -- API to save data manually, without Lua (see write.h). 34 | -- Added Luarocks rockspecs (see rockspec/). 35 | -- Fixed Makefile for Ubuntu. 36 | 37 | v0.1.1 38 | ====== 39 | 40 | Bugfix release. 41 | 42 | -- Fixed handling of array holes in Lua tables. 43 | 44 | v0.1 45 | ==== 46 | 47 | Initial release. 48 | -------------------------------------------------------------------------------- /src/write.h: -------------------------------------------------------------------------------- 1 | /* 2 | * write.h 3 | * Luabins Lua-less write API 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #ifndef LUABINS_WRITE_H_INCLUDED_ 8 | #define LUABINS_WRITE_H_INCLUDED_ 9 | 10 | #include "saveload.h" 11 | #include "savebuffer.h" 12 | 13 | #define LUABINS_APPEND ((size_t)-1) 14 | 15 | #define lbs_writeTupleSize(sb, tuple_size) \ 16 | lbsSB_writechar((sb), (tuple_size)) 17 | 18 | int lbs_writeTableHeaderAt( 19 | luabins_SaveBuffer * sb, 20 | size_t offset, /* Pass LUABINS_APPEND to append to the end of buffer */ 21 | int array_size, 22 | int hash_size 23 | ); 24 | 25 | #define lbs_writeTableHeader(sb, array_size, hash_size) \ 26 | lbs_writeTableHeaderAt((sb), LUABINS_APPEND, (array_size), (hash_size)) 27 | 28 | #define lbs_writeNil(sb) \ 29 | lbsSB_writechar((sb), LUABINS_CNIL) 30 | 31 | #define lbs_writeBoolean(sb, value) \ 32 | lbsSB_writechar((sb), ((value) == 0) ? LUABINS_CFALSE : LUABINS_CTRUE) 33 | 34 | int lbs_writeNumber(luabins_SaveBuffer * sb, lua_Number value); 35 | 36 | #define lbs_writeInteger lbs_writeNumber 37 | 38 | int lbs_writeString( 39 | luabins_SaveBuffer * sb, 40 | const char * value, 41 | size_t length 42 | ); 43 | 44 | #endif /* LUABINS_WRITE_H_INCLUDED_ */ 45 | -------------------------------------------------------------------------------- /src/luainternals.c: -------------------------------------------------------------------------------- 1 | /* 2 | * luainternals.c 3 | * Code quoted from MIT-licensed Lua 5.1.4 internals 4 | * See copyright notice in lua.h 5 | */ 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif /* __cplusplus */ 10 | 11 | #include 12 | #include 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif /* __cplusplus */ 17 | 18 | #include "luainternals.h" 19 | 20 | /* 21 | * BEGIN COPY-PASTE FROM Lua 5.1.4 llimits.h 22 | */ 23 | 24 | /* chars used as small naturals (so that `char' is reserved for characters) */ 25 | typedef unsigned char lu_byte; 26 | 27 | /* 28 | * END COPY-PASTE FROM Lua 5.1.4 llimits.h 29 | */ 30 | 31 | /* 32 | * BEGIN COPY-PASTE FROM Lua 5.1.4 lobject.c 33 | */ 34 | 35 | int luaO_log2 (unsigned int x) { 36 | static const lu_byte log_2[256] = { 37 | 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 38 | 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 39 | 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 40 | 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 41 | 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 42 | 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 43 | 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 44 | 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 45 | }; 46 | int l = -1; 47 | while (x >= 256) { l += 8; x >>= 8; } 48 | return l + log_2[x]; 49 | 50 | } 51 | 52 | /* 53 | * END COPY-PASTE FROM Lua 5.1.4 lobject.c 54 | */ 55 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Luabins License 2 | --------------- 3 | 4 | Luabins is licensed under the terms of the MIT license reproduced below. 5 | This means that luabins is free software and can be used for both academic 6 | and commercial purposes at absolutely no cost. 7 | 8 | =============================================================================== 9 | 10 | Copyright (C) 2009-2010 Luabins authors 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in 20 | all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | THE SOFTWARE. 29 | 30 | =============================================================================== 31 | 32 | (end of COPYRIGHT) 33 | -------------------------------------------------------------------------------- /src/write.c: -------------------------------------------------------------------------------- 1 | /* 2 | * write.c 3 | * Luabins Lua-less write API 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include "luaheaders.h" 8 | 9 | #include "write.h" 10 | 11 | int lbs_writeTableHeaderAt( 12 | luabins_SaveBuffer * sb, 13 | size_t offset, /* Pass LUABINS_APPEND to append to the end of buffer */ 14 | int array_size, 15 | int hash_size 16 | ) 17 | { 18 | int result = lbsSB_grow(sb, 1 + LUABINS_LINT + LUABINS_LINT); 19 | if (result == LUABINS_ESUCCESS) 20 | { 21 | /* 22 | * We have to reset offset here in case it was beyond the buffer. 23 | * Otherwise sequental overwrites may break. 24 | */ 25 | 26 | size_t length = lbsSB_length(sb); 27 | if (offset > length) 28 | { 29 | offset = length; 30 | } 31 | 32 | lbsSB_overwritechar(sb, offset, LUABINS_CTABLE); 33 | lbsSB_overwrite( 34 | sb, 35 | offset + 1, 36 | (const unsigned char *)&array_size, 37 | LUABINS_LINT 38 | ); 39 | lbsSB_overwrite( 40 | sb, 41 | offset + 1 + LUABINS_LINT, 42 | (const unsigned char *)&hash_size, 43 | LUABINS_LINT 44 | ); 45 | } 46 | 47 | return result; 48 | } 49 | 50 | int lbs_writeNumber(luabins_SaveBuffer * sb, lua_Number value) 51 | { 52 | int result = lbsSB_grow(sb, 1 + LUABINS_LNUMBER); 53 | if (result == LUABINS_ESUCCESS) 54 | { 55 | lbsSB_writechar(sb, LUABINS_CNUMBER); 56 | lbsSB_write(sb, (const unsigned char *)&value, LUABINS_LNUMBER); 57 | } 58 | return result; 59 | } 60 | 61 | int lbs_writeString( 62 | luabins_SaveBuffer * sb, 63 | const char * value, 64 | size_t length 65 | ) 66 | { 67 | int result = lbsSB_grow(sb, 1 + LUABINS_LSIZET + length); 68 | if (result == LUABINS_ESUCCESS) 69 | { 70 | lbsSB_writechar(sb, LUABINS_CSTRING); 71 | lbsSB_write(sb, (const unsigned char *)&length, LUABINS_LSIZET); 72 | lbsSB_write(sb, (const unsigned char *)value, length); 73 | } 74 | return result; 75 | } 76 | -------------------------------------------------------------------------------- /etc/dataset.lua: -------------------------------------------------------------------------------- 1 | -- Random Luabins dataset generator 2 | 3 | package.cpath = "./lib/?.so;"..package.cpath 4 | 5 | local luabins = require("luabins") 6 | 7 | local luabins_save, luabins_load = luabins.save, luabins.load 8 | 9 | math.randomseed(123456) 10 | 11 | -- TODO: Generalize. Copy-paste from test.lua 12 | local function gen_random_dataset(num, nesting) 13 | num = num or math.random(0, 128) 14 | nesting = nesting or 1 15 | 16 | local gen_str = function() 17 | local t = {} 18 | local n = math.random(0, 1024) 19 | for i = 1, n do 20 | t[i] = string.char(math.random(0, 255)) 21 | end 22 | return table.concat(t) 23 | end 24 | 25 | local gen_bool = function() return math.random() >= 0.5 end 26 | 27 | local gen_nil = function() return nil end 28 | 29 | local generators = 30 | { 31 | gen_nil; 32 | gen_nil; 33 | gen_nil; 34 | gen_bool; 35 | gen_bool; 36 | gen_bool; 37 | function() return math.random() end; 38 | function() return math.random(-10000, 10000) end; 39 | function() return math.random() * math.random(-10000, 10000) end; 40 | gen_str; 41 | gen_str; 42 | gen_str; 43 | function() 44 | if nesting >= 24 then 45 | return nil 46 | end 47 | 48 | local t = {} 49 | local n = math.random(0, 24 - nesting) 50 | for i = 1, n do 51 | local k = gen_random_dataset(1, nesting + 1) 52 | if k == nil then 53 | k = "(nil)" 54 | end 55 | t[ k ] = gen_random_dataset( 56 | 1, 57 | nesting + 1 58 | ) 59 | end 60 | 61 | return t 62 | end; 63 | } 64 | 65 | local t = {} 66 | for i = 1, num do 67 | local n = math.random(1, #generators) 68 | t[i] = generators[n]() 69 | end 70 | return unpack(t, 0, num) 71 | end 72 | 73 | local saved = assert(luabins_save(gen_random_dataset())) 74 | 75 | local filename = select(1, ...) 76 | assert(filename, "Usage: lua dataset.lua ") 77 | 78 | local file 79 | if filename == "-" then 80 | file = io.stdout 81 | else 82 | file = assert(io.open(filename, "w")) 83 | end 84 | 85 | file:write(saved) 86 | 87 | if file ~= io.stdout then 88 | file:close() 89 | end 90 | file = nil 91 | -------------------------------------------------------------------------------- /src/savebuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * savebuffer.h 3 | * Luabins save buffer 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #ifndef LUABINS_SAVEBUFFER_H_INCLUDED_ 8 | #define LUABINS_SAVEBUFFER_H_INCLUDED_ 9 | 10 | typedef struct luabins_SaveBuffer 11 | { 12 | lua_Alloc alloc_fn; 13 | void * alloc_ud; 14 | 15 | unsigned char * buffer; 16 | size_t buf_size; 17 | size_t end; 18 | 19 | } luabins_SaveBuffer; 20 | 21 | void lbsSB_init( 22 | luabins_SaveBuffer * sb, 23 | lua_Alloc alloc_fn, 24 | void * alloc_ud 25 | ); 26 | 27 | /* 28 | * Ensures that there is at least delta size available in buffer. 29 | * New size is aligned by blockSize increments. 30 | * Returns non-zero if resize failed. 31 | * If you pre-sized the buffer, subsequent writes up to the new size 32 | * are guaranteed to not fail. 33 | */ 34 | int lbsSB_grow(luabins_SaveBuffer * sb, size_t delta); 35 | 36 | /* 37 | * Returns non-zero if write failed. 38 | * Allocates buffer as needed. 39 | */ 40 | int lbsSB_write( 41 | luabins_SaveBuffer * sb, 42 | const unsigned char * bytes, 43 | size_t length 44 | ); 45 | 46 | /* 47 | * Returns non-zero if write failed. 48 | * Allocates buffer as needed. 49 | * Convenience function. 50 | */ 51 | int lbsSB_writechar( 52 | luabins_SaveBuffer * sb, 53 | unsigned char byte 54 | ); 55 | 56 | #define lbsSB_length(sb) ( (sb)->end ) 57 | 58 | /* 59 | * If offset is greater than total length, data is appended to the end. 60 | * Returns non-zero if write failed. 61 | * Allocates buffer as needed. 62 | */ 63 | int lbsSB_overwrite( 64 | luabins_SaveBuffer * sb, 65 | size_t offset, 66 | const unsigned char * bytes, 67 | size_t length 68 | ); 69 | 70 | /* 71 | * If offset is greater than total length, data is appended to the end. 72 | * Returns non-zero if write failed. 73 | * Allocates buffer as needed. 74 | * Convenience function. 75 | */ 76 | int lbsSB_overwritechar( 77 | luabins_SaveBuffer * sb, 78 | size_t offset, 79 | unsigned char byte 80 | ); 81 | 82 | /* 83 | * Returns a pointer to the internal buffer with data. 84 | * Note that buffer is NOT zero-terminated. 85 | * Buffer is valid until next operation with the given sb. 86 | */ 87 | const unsigned char * lbsSB_buffer(luabins_SaveBuffer * sb, size_t * length); 88 | 89 | void lbsSB_destroy(luabins_SaveBuffer * sb); 90 | 91 | #endif /* LUABINS_SAVEBUFFER_H_INCLUDED_ */ 92 | -------------------------------------------------------------------------------- /BENCHMARK: -------------------------------------------------------------------------------- 1 | Luabins 0.2 benchmark (see etc/benchmark.lua) results on 2 | 3 | MacBook Pro 2.4 GHz Intel Core Duo 2.66 MB DDR2 SDRAM 4 | OS X 10.6.2 5 | GCC 4.2.1 (Apple Inc. build 5646) (dot 1) 6 | Lua 5.1.4 from MacPorts 7 | Luabins built with default Makefile configuration 8 | 9 | Note that the data used in benchmark is quite trivial. You're advised 10 | to find out if Luabins is "fast enough" for you by yourself. 11 | 12 | Lua 13 | ------------------------------------------------------------------- 14 | name | rel | abs s / iter = us (1e-6 s) / iter 15 | ------------------------------------------------------------------- 16 | luabins_save | 1.0000 | 4.21 / 1000000 = 4.210000 us 17 | luabins_load | 1.2043 | 5.07 / 1000000 = 5.070000 us 18 | loadstring | 4.7435 | 19.97 / 1000000 = 19.970000 us 19 | concat | 10.6413 | 44.80 / 1000000 = 44.800000 us 20 | 21 | =================================================================== 22 | 23 | Luabins 0.1 benchmark (see etc/benchmark.lua) results on 24 | 25 | MacBook Pro 2.4 GHz Intel Core Duo 2.66 MB DDR2 SDRAM 26 | OS X 10.5.6 27 | GCC 4.0.1 (Apple Inc. build 5490) 28 | Lua 5.1.4 from MacPorts 29 | LuaJIT 1.1.3 built from sources with default configuration 30 | Luabins built with default configuration 31 | 32 | Note that the data used in benchmark is quite trivial. You're advised 33 | to find out if Luabins is "fast enough" for you by yourself. 34 | 35 | Lua 36 | ------------------------------------------------------------------- 37 | name | rel | abs s / iter = us (1e-6 s) / iter 38 | ------------------------------------------------------------------- 39 | luabins_load | 1.0000 | 6.34 / 1000000 = 6.340000 us 40 | luabins_save | 1.7256 | 10.94 / 1000000 = 10.940000 us 41 | loadstring | 3.6530 | 23.16 / 1000000 = 23.160000 us 42 | concat | 10.0741 | 63.87 / 1000000 = 63.870000 us 43 | 44 | LuaJIT -O 45 | ------------------------------------------------------------------- 46 | name | rel | abs s / iter = us (1e-6 s) / iter 47 | ------------------------------------------------------------------- 48 | luabins_load | 1.0000 | 5.40 / 1000000 = 5.400000 us 49 | luabins_save | 1.6111 | 8.70 / 1000000 = 8.700000 us 50 | concat | 6.6630 | 35.98 / 1000000 = 35.980000 us 51 | loadstring | 23.6370 | 127.64 / 1000000 = 127.640000 us 52 | -------------------------------------------------------------------------------- /src/luabins.c: -------------------------------------------------------------------------------- 1 | /* 2 | * luabins.c 3 | * Luabins Lua module code 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include "luaheaders.h" 8 | 9 | #include "luabins.h" 10 | #include "saveload.h" 11 | 12 | /* 13 | * On success returns data string. 14 | * On failure returns nil and error message. 15 | */ 16 | static int l_save(lua_State * L) 17 | { 18 | int error = luabins_save(L, 1, lua_gettop(L)); 19 | if (error == 0) 20 | { 21 | return 1; 22 | } 23 | 24 | lua_pushnil(L); 25 | lua_replace(L, -3); /* Put nil before error message on stack */ 26 | return 2; 27 | } 28 | 29 | /* 30 | * On success returns true and loaded data tuple. 31 | * On failure returns nil and error message. 32 | */ 33 | static int l_load(lua_State * L) 34 | { 35 | int count = 0; 36 | int error = 0; 37 | size_t len = 0; 38 | const unsigned char * data = (const unsigned char *)luaL_checklstring( 39 | L, 1, &len 40 | ); 41 | 42 | lua_pushboolean(L, 1); 43 | 44 | error = luabins_load(L, data, len, &count); 45 | if (error == 0) 46 | { 47 | return count + 1; 48 | } 49 | 50 | lua_pushnil(L); 51 | lua_replace(L, -3); /* Put nil before error message on stack */ 52 | 53 | return 2; 54 | } 55 | 56 | /* luabins Lua module API */ 57 | static const struct luaL_reg R[] = 58 | { 59 | { "save", l_save }, 60 | { "load", l_load }, 61 | { NULL, NULL } 62 | }; 63 | 64 | #ifdef __cplusplus 65 | extern "C" { 66 | #endif 67 | 68 | LUALIB_API int luaopen_luabins(lua_State * L) 69 | { 70 | /* 71 | * Compile-time checks for size constants. 72 | * Consult PORTABILITY WARNING in saveload.h before changing constants. 73 | */ 74 | 75 | /* int is too small on your platform, fix LUABINS_LINT */ 76 | luabins_static_assert(sizeof(int) >= LUABINS_LINT); 77 | /* size_t is too small on your platform, fix LUABINS_LSIZET */ 78 | luabins_static_assert(sizeof(size_t) >= LUABINS_LSIZET); 79 | /* unexpected lua_Number size, fix LUABINS_LNUMBER */ 80 | luabins_static_assert(sizeof(lua_Number) == LUABINS_LNUMBER); 81 | 82 | /* 83 | * Register module 84 | */ 85 | luaL_register(L, "luabins", R); 86 | 87 | /* 88 | * Register module information 89 | */ 90 | lua_pushliteral(L, LUABINS_VERSION); 91 | lua_setfield(L, -2, "_VERSION"); 92 | 93 | lua_pushliteral(L, LUABINS_COPYRIGHT); 94 | lua_setfield(L, -2, "_COPYRIGHT"); 95 | 96 | lua_pushliteral(L, LUABINS_DESCRIPTION); 97 | lua_setfield(L, -2, "_DESCRIPTION"); 98 | 99 | return 1; 100 | } 101 | 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | -------------------------------------------------------------------------------- /src/luabins.h: -------------------------------------------------------------------------------- 1 | /* 2 | * luabins.h 3 | * luabins -- Lua Binary Serialization Module 4 | * See copyright notice at the end of this file. 5 | */ 6 | 7 | #ifndef LUABINS_H_INCLUDED_ 8 | #define LUABINS_H_INCLUDED_ 9 | 10 | #define LUABINS_VERSION "Luabins 0.3" 11 | #define LUABINS_COPYRIGHT "Copyright (C) 2009-2010, Luabins authors" 12 | #define LUABINS_DESCRIPTION "Trivial Lua Binary Serialization Library" 13 | 14 | /* Define LUABINS_LUABUILTASCPP if your Lua is built as C++ */ 15 | 16 | /* Can't be more than 255 */ 17 | #define LUABINS_MAXTUPLE (250) 18 | 19 | #define LUABINS_MAXTABLENESTING (250) 20 | 21 | /* 22 | * Save Lua values from given state at given stack index range. 23 | * Lua value is left untouched. Note that empty range is not an error. 24 | * You may save from 0 to LUABINS_MAXTUPLE values. 25 | * Returns 0 on success, pushes saved data as a string on the top of stack. 26 | * Returns non-zero on failure, pushes error message on the top 27 | * of the stack. 28 | */ 29 | int luabins_save(lua_State * L, int index_from, int index_to); 30 | 31 | /* 32 | * Load Lua values from given byte chunk. 33 | * Returns 0 on success, pushes loaded values on stack. 34 | * Sets count to the number of values pushed. 35 | * Note that to have zero loaded items is a valid scenario. 36 | * Returns non-zero on failure, pushes error message on the top 37 | * of the stack. 38 | */ 39 | int luabins_load( 40 | lua_State * L, 41 | const unsigned char * data, 42 | size_t len, 43 | int * count 44 | ); 45 | 46 | /****************************************************************************** 47 | * Copyright (C) 2009-2010 Luabins authors. All rights reserved. 48 | * 49 | * Permission is hereby granted, free of charge, to any person obtaining 50 | * a copy of this software and associated documentation files (the 51 | * "Software"), to deal in the Software without restriction, including 52 | * without limitation the rights to use, copy, modify, merge, publish, 53 | * distribute, sublicense, and/or sell copies of the Software, and to 54 | * permit persons to whom the Software is furnished to do so, subject to 55 | * the following conditions: 56 | * 57 | * The above copyright notice and this permission notice shall be 58 | * included in all copies or substantial portions of the Software. 59 | * 60 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 61 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 62 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 63 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 64 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 65 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 66 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 67 | ******************************************************************************/ 68 | 69 | #endif /* LUABINS_H_INCLUDED_ */ 70 | -------------------------------------------------------------------------------- /etc/benchmark.lua: -------------------------------------------------------------------------------- 1 | -- This benchmark is compatible with luamarca benchmarking system 2 | -- http://github.com/agladysh/luamarca 3 | 4 | package.cpath = "./?.so;"..package.cpath 5 | 6 | local luabins = require("luabins") 7 | 8 | local table_concat = table.concat 9 | local loadstring, assert = loadstring, assert 10 | local pairs, type, tostring = pairs, type, tostring 11 | local luabins_save, luabins_load = luabins.save, luabins.load 12 | 13 | local lua = ([[return { 14 | true, false, 42, "string", 15 | [{ 16 | true, false, 42, "string", 17 | [true] = true, [false] = false, [42] = 42, string = "string" 18 | }] = 19 | { 20 | true, false, 42, "string", 21 | [true] = true, [false] = false, [42] = 42, string = "string" 22 | } 23 | }]]):gsub("[%s\n]+", "") -- Remove spaces for compactness 24 | 25 | local data = assert(loadstring(lua))() 26 | local saved = assert(luabins_save(data)) 27 | 28 | -- Imagine we know exact data structure. 29 | -- We still impose some overhead on table.concat() related 30 | -- stuff, since it is more realistic scenario. 31 | -- Still looks a bit silly. 32 | local concat = function(data) 33 | local buf = {} 34 | local function cat(v) buf[#buf + 1] = tostring(v) return cat end 35 | 36 | -- Find table key 37 | local tablekey, tableval 38 | for k, v in pairs(data) do 39 | if type(k) == "table" then 40 | tablekey, tableval = k, v 41 | break 42 | end 43 | end 44 | 45 | cat 'return{' 46 | 47 | cat (data[1]) ',' 48 | cat (data[2]) ',' 49 | cat (data[3]) ',' 50 | cat '"' (data[4]) '",' 51 | 52 | cat '[{' 53 | 54 | cat (tablekey[1]) ',' 55 | cat (tablekey[2]) ',' 56 | cat (tablekey[3]) ',' 57 | cat '"' (tablekey[4]) '",' 58 | 59 | cat '[' (true) ']=' (tablekey[true]) ',' 60 | cat '[' (false) ']=' (tablekey[false]) ',' 61 | cat '[' (42) ']=' (tablekey[42]) ',' 62 | cat 'string' '=' '"' (tablekey["string"]) '"' 63 | 64 | cat '}]=' 65 | 66 | cat '{' 67 | 68 | cat (tablekey[1]) ',' 69 | cat (tablekey[2]) ',' 70 | cat (tablekey[3]) ',' 71 | cat '"' (tablekey[4]) '",' 72 | 73 | cat '[' (true) ']=' (tablekey[true]) ',' 74 | cat '[' (false) ']=' (tablekey[false]) ',' 75 | cat '[' (42) ']=' (tablekey[42]) ',' 76 | cat 'string' '=' '"' (tablekey["string"]) '"' 77 | 78 | cat '}' 79 | 80 | cat '}' 81 | 82 | return table_concat(buf, '') 83 | end 84 | 85 | -- Sanity check 86 | assert(concat(data) == lua) 87 | 88 | local bench = {} 89 | 90 | bench.concat = function() 91 | assert(concat(data)) 92 | end 93 | 94 | bench.loadstring = function() 95 | assert(loadstring(lua))() 96 | end 97 | 98 | bench.luabins_save = function() 99 | assert(luabins_save(data)) 100 | end 101 | 102 | bench.luabins_load = function() 103 | assert(luabins_load(saved)) 104 | end 105 | 106 | return bench 107 | -------------------------------------------------------------------------------- /test/test_fwrite_api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test_fwrite_api.c 3 | * Luabins Lua-less fwrite API tests 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | /* 8 | * WARNING: This suite is format-specific. Change it when format changes. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "lualess.h" 16 | #include "fwrite.h" 17 | 18 | #include "test.h" 19 | #include "util.h" 20 | 21 | /******************************************************************************/ 22 | 23 | /* 24 | * Note it is different from test_savebuffer variant. 25 | * We're interested in higher level stuff here. 26 | */ 27 | static void check_buffer( 28 | FILE * f, 29 | const char * expected_buf_c, 30 | size_t expected_length 31 | ) 32 | { 33 | const unsigned char * expected_buf = (unsigned char *)expected_buf_c; 34 | unsigned char * actual_buf = NULL; 35 | size_t actual_length = ftell(f); 36 | size_t actually_read = 0; 37 | 38 | fseek(f, 0, SEEK_SET); 39 | 40 | actual_buf = (unsigned char *)malloc(actual_length); 41 | actually_read = fread(actual_buf, actual_length, 1, f); 42 | if (actually_read != 1ul) 43 | { 44 | fprintf( 45 | stderr, 46 | "fread count error: got %lu, expected %lu\n", 47 | actually_read, 1ul 48 | ); 49 | 50 | free(actual_buf); 51 | fclose(f); 52 | exit(1); 53 | } 54 | 55 | fseek(f, actual_length, SEEK_SET); 56 | 57 | if (actual_length != expected_length) 58 | { 59 | fprintf( 60 | stderr, 61 | "length mismatch: got %lu, expected %lu\n", 62 | actual_length, expected_length 63 | ); 64 | fprintf(stderr, "actual:\n"); 65 | fprintbuf(stderr, actual_buf, actual_length); 66 | fprintf(stderr, "expected:\n"); 67 | fprintbuf(stderr, expected_buf, expected_length); 68 | 69 | free(actual_buf); 70 | fclose(f); 71 | exit(1); 72 | } 73 | 74 | if (memcmp(actual_buf, expected_buf, expected_length) != 0) 75 | { 76 | fprintf(stderr, "buffer mismatch\n"); 77 | fprintf(stderr, "actual:\n"); 78 | fprintbuf(stderr, actual_buf, actual_length); 79 | fprintf(stderr, "expected:\n"); 80 | fprintbuf(stderr, expected_buf, expected_length); 81 | 82 | free(actual_buf); 83 | fclose(f); 84 | exit(1); 85 | } 86 | 87 | free(actual_buf); 88 | } 89 | 90 | /******************************************************************************/ 91 | 92 | #define CAT(a, b) a ## b 93 | 94 | #define TEST_NAME(x) CAT(test_fwrite, x) 95 | #define CALL_NAME(x) CAT(lbs_fwrite, x) 96 | #define BUFFER_NAME (f) 97 | #define INIT_BUFFER \ 98 | FILE * f = tmpfile(); 99 | 100 | #define DESTROY_BUFFER \ 101 | fclose(f); 102 | 103 | #define CHECK_BUFFER check_buffer 104 | 105 | #include "write_tests.inc" 106 | 107 | /******************************************************************************/ 108 | 109 | void test_fwrite_api() 110 | { 111 | RUN_GENERATED_TESTS; 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | luabins — Lua Binary Serialization Library 2 | ========================================== 3 | 4 | Allows to save tuples of primitive Lua types into binary chunks 5 | and to load saved data back. 6 | 7 | NB: You may be better off with luatexts: https://github.com/agladysh/luatexts. 8 | 9 | On serialization 10 | ---------------- 11 | 12 | ### Luabins works with 13 | 14 | * `nil` 15 | * `boolean` 16 | * `number` 17 | * `string` 18 | * `table` (see below) 19 | 20 | ### Luabins refuses to save 21 | 22 | * `function` 23 | * `thread` 24 | * `userdata` 25 | 26 | Luabins intentionally does not save or check any meta-information 27 | (versions, endianness etc.) along with data. If needed, it is to be handled 28 | elsewhere. 29 | 30 | ### Table serialization 31 | 32 | 1. Metatatables are ignored. 33 | 2. Table nesting depth should be no more than `LUABINS_MAXTABLENESTING`. 34 | 3. On table save references are not honored. Each encountered reference 35 | becomes independent object on load: 36 | 37 | local t = { 42 } 38 | { t, t } 39 | 40 | becomes 41 | 42 | { { 42 }, { 42 } } 43 | 44 | that is, three separate tables instead of two. 45 | 46 | Lua API 47 | ------- 48 | 49 | * `luabins.save(...)` 50 | 51 | Saves arguments into a binary string. 52 | 53 | * On success returns that string. 54 | * On failure returns nil and error message. 55 | 56 | Example: 57 | 58 | local str = assert(luabins.save(1, "two", { "three", 4 })) 59 | 60 | * `luabins.load(string)` 61 | 62 | Loads a list of values from a binary string. 63 | 64 | * On success returns true and loaded values. 65 | * On failure returns nil and error message. 66 | 67 | Example: 68 | 69 | If you do not know in advance what data is stored inside a binary string, 70 | you may put results into a table: 71 | 72 | local values = { luabins.load(data) } 73 | assert(values[1], values[2]) 74 | 75 | If you know how to handle stored values (for example you're sure they were 76 | generated following some established protocol), you may want to use 77 | something like this function to check `luabins.load()` result: 78 | 79 | function eat_true(t, ...) 80 | assert(t, ...) 81 | return ... 82 | end 83 | 84 | my_value_handler(eat_true(luabins.load(data))) 85 | 86 | C API 87 | ----- 88 | 89 | * `int luabins_save(lua_State * L, int index_from, int index_to)` 90 | 91 | Save Lua values from given state at given stack index range. 92 | Lua value is left untouched. Note that empty range is not an error. 93 | You may save from 0 to `LUABINS_MAXTUPLE` values. 94 | Note only real non-negative indices work. 95 | 96 | * On success returns 0, pushes saved data as a string on the top of stack. 97 | * On failure returns non-zero, pushes error message on the top 98 | of the stack. 99 | 100 | * `int luabins_load(lua_State * L, const unsigned char * data, 101 | size_t len, int *count)` 102 | 103 | Load Lua values from given byte chunk. 104 | 105 | * On success returns 0, pushes loaded values on stack. 106 | Sets count to the number of values pushed. 107 | Note that to have zero loaded items is a valid scenario. 108 | * On failure returns non-zero, pushes error message on the top 109 | of the stack. 110 | 111 | Luabins is still an experimental volatile software. 112 | Please see source code for more documentation. 113 | 114 | See the copyright information in the file named `COPYRIGHT`. 115 | -------------------------------------------------------------------------------- /test/test_write_api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test_write_api.c 3 | * Luabins Lua-less write API tests 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | /* 8 | * WARNING: This suite is format-specific. Change it when format changes. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | /* Should be included first */ 16 | #include "lualess.h" 17 | #include "write.h" 18 | 19 | #include "test.h" 20 | #include "util.h" 21 | 22 | /******************************************************************************/ 23 | 24 | /* 25 | * Note it is different from test_savebuffer variant. 26 | * We're interested in higher level stuff here. 27 | */ 28 | static void check_buffer( 29 | luabins_SaveBuffer * sb, 30 | const char * expected_buf_c, 31 | size_t expected_length 32 | ) 33 | { 34 | const unsigned char * expected_buf = (const unsigned char *)expected_buf_c; 35 | 36 | size_t actual_length = (size_t)-1; 37 | const unsigned char * actual_buf = lbsSB_buffer(sb, &actual_length); 38 | if (actual_length != expected_length) 39 | { 40 | fprintf( 41 | stderr, 42 | "lsbSB_buffer length mismatch: got %lu, expected %lu\n", 43 | actual_length, expected_length 44 | ); 45 | fprintf(stderr, "actual:\n"); 46 | fprintbuf(stderr, actual_buf, actual_length); 47 | fprintf(stderr, "expected:\n"); 48 | fprintbuf(stderr, expected_buf, expected_length); 49 | exit(1); 50 | } 51 | 52 | if (memcmp(actual_buf, expected_buf, expected_length) != 0) 53 | { 54 | fprintf(stderr, "lsbSB_buffer buffer mismatch\n"); 55 | fprintf(stderr, "actual:\n"); 56 | fprintbuf(stderr, actual_buf, actual_length); 57 | fprintf(stderr, "expected:\n"); 58 | fprintbuf(stderr, expected_buf, expected_length); 59 | 60 | exit(1); 61 | } 62 | } 63 | 64 | /******************************************************************************/ 65 | 66 | #define CAT(a, b) a ## b 67 | 68 | #define TEST_NAME(x) CAT(test_write, x) 69 | #define CALL_NAME(x) CAT(lbs_write, x) 70 | #define BUFFER_NAME (&sb) 71 | #define INIT_BUFFER \ 72 | luabins_SaveBuffer sb; \ 73 | lbsSB_init(BUFFER_NAME, lbs_simplealloc, NULL); 74 | 75 | #define DESTROY_BUFFER \ 76 | lbsSB_destroy(BUFFER_NAME); 77 | 78 | #define CHECK_BUFFER check_buffer 79 | 80 | #include "write_tests.inc" 81 | 82 | /******************************************************************************/ 83 | 84 | TEST (test_writeTableHeaderAt, 85 | { 86 | INIT_BUFFER; 87 | 88 | { 89 | unsigned char tuple_size = 0x01; 90 | int array_size = 0x00; 91 | int hash_size = 0x00; 92 | int table_header_pos = 0; 93 | 94 | lbs_writeTupleSize(BUFFER_NAME, tuple_size); 95 | table_header_pos = lbsSB_length(BUFFER_NAME); 96 | lbs_writeTableHeader(BUFFER_NAME, array_size, hash_size); 97 | 98 | CHECK_BUFFER( 99 | &sb, 100 | "\x01" "T" "\x00\x00\x00\x00" "\x00\x00\x00\x00", 101 | 1 + 1 + 4 + 4 102 | ); 103 | 104 | array_size = 0xAB; 105 | hash_size = 0xCD; 106 | 107 | lbs_writeTableHeaderAt( 108 | BUFFER_NAME, 109 | table_header_pos, 110 | array_size, 111 | hash_size 112 | ); 113 | CHECK_BUFFER( 114 | BUFFER_NAME, 115 | "\x01" "T" "\xAB\x00\x00\x00" "\xCD\x00\x00\x00", 116 | 1 + 1 + 4 + 4 117 | ); 118 | } 119 | 120 | DESTROY_BUFFER; 121 | }) 122 | 123 | /******************************************************************************/ 124 | 125 | void test_write_api() 126 | { 127 | RUN_GENERATED_TESTS; 128 | 129 | test_writeTableHeaderAt(); 130 | } 131 | -------------------------------------------------------------------------------- /src/saveload.h: -------------------------------------------------------------------------------- 1 | /* 2 | * saveload.h 3 | * Luabins internal constants and helper macros 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #ifndef LUABINS_SAVELOAD_H_INCLUDED_ 8 | #define LUABINS_SAVELOAD_H_INCLUDED_ 9 | 10 | /* Find minimum of two values */ 11 | #define luabins_min(a, b) \ 12 | ( ((a) < (b)) ? (a) : (b) ) 13 | 14 | /* Find maximum of two values */ 15 | #define luabins_max(a, b) \ 16 | ( ((a) > (b)) ? (a) : (b) ) 17 | 18 | /* Find minimum of three values */ 19 | #define luabins_min3(a, b, c) \ 20 | ( ((a) < (b)) ? luabins_min((a), (c)) : luabins_min((b), (c)) ) 21 | 22 | /* Preprocessor concatenation */ 23 | #define LUABINS_CAT(a, b) a##b 24 | 25 | /* Static assert helper macro */ 26 | #define luabins_static_assert_line(pred, line) \ 27 | typedef char LUABINS_CAT( \ 28 | static_assertion_failed_at_line_, \ 29 | line \ 30 | )[2 * !!(pred) - 1] 31 | 32 | /* Static (compile-time) assert */ 33 | #define luabins_static_assert(pred) \ 34 | luabins_static_assert_line(pred, __LINE__) 35 | 36 | /* Internal error codes */ 37 | #define LUABINS_ESUCCESS (0) 38 | #define LUABINS_EFAILURE (1) 39 | #define LUABINS_EBADTYPE (2) 40 | #define LUABINS_ETOODEEP (3) 41 | #define LUABINS_ENOSTACK (4) 42 | #define LUABINS_EBADDATA (5) 43 | #define LUABINS_ETAILEFT (6) 44 | #define LUABINS_EBADSIZE (7) 45 | #define LUABINS_ETOOLONG (8) 46 | 47 | /* Type bytes */ 48 | #define LUABINS_CNIL '-' /* 0x2D (45) */ 49 | #define LUABINS_CFALSE '0' /* 0x30 (48) */ 50 | #define LUABINS_CTRUE '1' /* 0x31 (49) */ 51 | #define LUABINS_CNUMBER 'N' /* 0x4E (78) */ 52 | #define LUABINS_CSTRING 'S' /* 0x53 (83) */ 53 | #define LUABINS_CTABLE 'T' /* 0x54 (84) */ 54 | 55 | /* 56 | * PORTABILITY WARNING! 57 | * You have to ensure manually that length constants below are the same 58 | * for both code that does the save and code that does the load. 59 | * Also these constants must be actual or code below would break. 60 | * Beware of endianness and lua_Number actual type as well. 61 | * Note also that luabins does not check for overflow on save, 62 | * if your integer does not fit, it would be truncated. 63 | */ 64 | #define LUABINS_LINT (4) 65 | #define LUABINS_LSIZET (4) 66 | #define LUABINS_LNUMBER (8) 67 | 68 | /* 69 | * Derived lengths 70 | * 71 | * WARNING: Change these if format is changed! 72 | */ 73 | 74 | /* One type byte */ 75 | #define LUABINS_LTYPEBYTE (1) 76 | 77 | /* Minimal table: type, array size, hash size, no data */ 78 | #define LUABINS_LMINTABLE (LUABINS_LTYPEBYTE + LUABINS_LINT + LUABINS_LINT) 79 | 80 | /* Minimal number: type, number value */ 81 | #define LUABINS_LMINNUMBER (LUABINS_LTYPEBYTE + LUABINS_LNUMBER) 82 | 83 | /* Minimal string: type, length, no data */ 84 | #define LUABINS_LMINSTRING (LUABINS_LTYPEBYTE + LUABINS_LSIZET) 85 | 86 | /* Minimum large (non-boolean non-nil) value length */ 87 | #define LUABINS_LMINLARGEVALUE \ 88 | ( luabins_min3(LUABINS_LMINTABLE, LUABINS_LMINSTRING, LUABINS_LMINSTRING) ) 89 | 90 | /* 91 | * Lower limit on total table data size is determined as follows: 92 | * -- All entries are always key and value. 93 | * -- Minimum value size is one byte for nil and boolean, 94 | * but that is two keys maximum (nil can'be the key). 95 | * -- All the rest of key types are mimimum of LUABINS_MINLARGEVALUELEN 96 | * bytes (type byte plus data bytes). 97 | * -- All values in the table may be booleans. 98 | * 99 | * WARNING: Change this if format is changed! 100 | * 101 | * Note this formula does NOT take in account 102 | * table header (type byte and array/hash sizes). 103 | */ 104 | #define luabins_min_table_data_size(total_size) \ 105 | ( \ 106 | (total_size > 2) \ 107 | ? ( \ 108 | 2 * (LUABINS_LTYPEBYTE + LUABINS_LTYPEBYTE) \ 109 | + (total_size - 2) * (LUABINS_LMINLARGEVALUE + LUABINS_LTYPEBYTE) \ 110 | ) \ 111 | : (total_size * (LUABINS_LTYPEBYTE + LUABINS_LTYPEBYTE)) \ 112 | ) 113 | 114 | #endif /* LUABINS_SAVELOAD_H_INCLUDED_ */ 115 | -------------------------------------------------------------------------------- /test/write_tests.inc: -------------------------------------------------------------------------------- 1 | /* 2 | * write_tests.inc 3 | * Luabins Lua-less write API 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | /* Note this file intentionally does not have include guards */ 8 | 9 | /* 10 | * Depends on following symbols: 11 | * -- TEST_NAME 12 | * -- CALL_NAME 13 | * -- BUFFER_NAME 14 | * -- INIT_BUFFER 15 | * -- DESTROY_BUFFER 16 | * -- CHECK_BUFFER 17 | * 18 | * Defines symbols 19 | * -- RUN_GENERATED_TESTS 20 | */ 21 | 22 | TEST (TEST_NAME(TupleSize), 23 | { 24 | INIT_BUFFER; 25 | 26 | { 27 | unsigned char tuple_size = 0xAB; 28 | 29 | CALL_NAME(TupleSize)(BUFFER_NAME, tuple_size); 30 | CHECK_BUFFER(BUFFER_NAME, "\xAB", 1); 31 | } 32 | 33 | DESTROY_BUFFER; 34 | }) 35 | 36 | /******************************************************************************/ 37 | 38 | TEST (TEST_NAME(TableHeader), 39 | { 40 | INIT_BUFFER; 41 | 42 | { 43 | int array_size = 0xAB; 44 | int hash_size = 0xCD; 45 | 46 | CALL_NAME(TableHeader)(BUFFER_NAME, array_size, hash_size); 47 | CHECK_BUFFER( 48 | BUFFER_NAME, 49 | "T" "\xAB\x00\x00\x00" "\xCD\x00\x00\x00", 50 | 1 + 4 + 4 51 | ); 52 | } 53 | 54 | DESTROY_BUFFER; 55 | }) 56 | 57 | /******************************************************************************/ 58 | 59 | TEST (TEST_NAME(Nil), 60 | { 61 | INIT_BUFFER; 62 | 63 | { 64 | CALL_NAME(Nil)(BUFFER_NAME); 65 | CHECK_BUFFER(BUFFER_NAME, "-", 1); 66 | } 67 | 68 | DESTROY_BUFFER; 69 | }) 70 | 71 | /******************************************************************************/ 72 | 73 | TEST (TEST_NAME(Boolean), 74 | { 75 | INIT_BUFFER; 76 | 77 | { 78 | CALL_NAME(Boolean)(BUFFER_NAME, 1); 79 | CHECK_BUFFER(BUFFER_NAME, "1", 1); 80 | 81 | CALL_NAME(Boolean)(BUFFER_NAME, 0); 82 | CHECK_BUFFER(BUFFER_NAME, "10", 1 + 1); 83 | } 84 | 85 | DESTROY_BUFFER; 86 | }) 87 | 88 | /******************************************************************************/ 89 | 90 | TEST (TEST_NAME(Number), 91 | { 92 | INIT_BUFFER; 93 | 94 | { 95 | /* Note number is a double */ 96 | CALL_NAME(Number)(BUFFER_NAME, 1.0); 97 | CHECK_BUFFER(BUFFER_NAME, "N" "\x00\x00\x00\x00\x00\x00\xF0\x3F", 1 + 8); 98 | } 99 | 100 | DESTROY_BUFFER; 101 | }) 102 | 103 | /******************************************************************************/ 104 | 105 | TEST (TEST_NAME(Integer), 106 | { 107 | INIT_BUFFER; 108 | 109 | { 110 | /* Note integer is alsow written as a double */ 111 | CALL_NAME(Integer)(BUFFER_NAME, 1); 112 | CHECK_BUFFER(BUFFER_NAME, "N" "\x00\x00\x00\x00\x00\x00\xF0\x3F", 1 + 8); 113 | } 114 | 115 | DESTROY_BUFFER; 116 | }) 117 | 118 | /******************************************************************************/ 119 | 120 | TEST (TEST_NAME(StringEmpty), 121 | { 122 | INIT_BUFFER; 123 | 124 | { 125 | CALL_NAME(String)(BUFFER_NAME, "", 0); 126 | CHECK_BUFFER(BUFFER_NAME, "S" "\x00\x00\x00\x00", 1 + 4); 127 | } 128 | 129 | DESTROY_BUFFER; 130 | }) 131 | 132 | TEST (TEST_NAME(StringSimple), 133 | { 134 | INIT_BUFFER; 135 | 136 | { 137 | CALL_NAME(String)(BUFFER_NAME, "Luabins", 7); 138 | CHECK_BUFFER( 139 | BUFFER_NAME, 140 | "S" "\x07\x00\x00\x00" "Luabins", 141 | 1 + 4 + 7 142 | ); 143 | } 144 | 145 | DESTROY_BUFFER; 146 | }) 147 | 148 | TEST (TEST_NAME(StringEmbeddedZero), 149 | { 150 | INIT_BUFFER; 151 | 152 | { 153 | CALL_NAME(String)(BUFFER_NAME, "Embedded\0Zero", 13); 154 | CHECK_BUFFER( 155 | BUFFER_NAME, 156 | "S" "\x0D\x00\x00\x00" "Embedded\0Zero", 157 | 1 + 4 + 13 158 | ); 159 | } 160 | 161 | DESTROY_BUFFER; 162 | }) 163 | 164 | /******************************************************************************/ 165 | 166 | #define RUN_GENERATED_TESTS \ 167 | TEST_NAME(TupleSize)(); \ 168 | TEST_NAME(TableHeader)(); \ 169 | TEST_NAME(Nil)(); \ 170 | TEST_NAME(Boolean)(); \ 171 | TEST_NAME(Number)(); \ 172 | TEST_NAME(Integer)(); \ 173 | TEST_NAME(StringEmpty)(); \ 174 | TEST_NAME(StringSimple)(); \ 175 | TEST_NAME(StringEmbeddedZero)(); 176 | -------------------------------------------------------------------------------- /src/savebuffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * savebuffer.c 3 | * Luabins save buffer 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include /* memcpy() */ 8 | 9 | #include "luaheaders.h" 10 | 11 | #include "saveload.h" 12 | #include "savebuffer.h" 13 | 14 | #if 0 15 | #define SPAM(a) printf a 16 | #else 17 | #define SPAM(a) (void)0 18 | #endif 19 | 20 | /* Minimum allocation size */ 21 | #define LUABINS_SAVEMINALLOC (256) 22 | 23 | /* TODO: Test this with custom allocator! */ 24 | 25 | void lbsSB_init( 26 | luabins_SaveBuffer * sb, 27 | lua_Alloc alloc_fn, 28 | void * alloc_ud 29 | ) 30 | { 31 | sb->alloc_fn = alloc_fn; 32 | sb->alloc_ud = alloc_ud; 33 | 34 | sb->buffer = NULL; 35 | sb->buf_size = 0UL; 36 | 37 | sb->end = 0UL; 38 | } 39 | 40 | /* 41 | * Ensures that there is at least delta size available in buffer. 42 | * New size is aligned by blockSize increments 43 | * Returns non-zero if resize failed. 44 | * If you pre-sized the buffer, subsequent writes up to the new size 45 | * are guaranteed to not fail. 46 | */ 47 | int lbsSB_grow(luabins_SaveBuffer * sb, size_t delta) 48 | { 49 | size_t needed_size = sb->end + delta; 50 | 51 | if (needed_size > sb->buf_size) 52 | { 53 | /* 54 | * Growth factor x1.5 55 | * Discussion on possible values: 56 | * http://stackoverflow.com/questions/2269063/buffer-growth-strategy 57 | */ 58 | 59 | /* TODO: Handle size_t overflow? */ 60 | 61 | size_t new_size = 0; 62 | 63 | size_t add_size = sb->buf_size / 2; 64 | if (add_size < LUABINS_SAVEMINALLOC) 65 | { 66 | add_size = LUABINS_SAVEMINALLOC; 67 | } 68 | 69 | new_size = sb->buf_size + add_size; 70 | 71 | /* 72 | SPAM(( 73 | "trying %lu + %lu = %lu (needed %lu)\n", 74 | sb->buf_size, 75 | add_size, 76 | new_size, 77 | needed_size 78 | )); 79 | */ 80 | 81 | while (new_size < needed_size) 82 | { 83 | SPAM(("...+%lu not enough, growing more\n", add_size)); 84 | /* Grow exponentially as needed */ 85 | add_size += new_size / 2; 86 | new_size += add_size; 87 | } 88 | 89 | SPAM(( 90 | "growing from %lu to %lu (needed %lu)\n", 91 | sb->buf_size, 92 | new_size, 93 | needed_size 94 | )); 95 | 96 | sb->buffer = (unsigned char *)sb->alloc_fn( 97 | sb->alloc_ud, 98 | sb->buffer, 99 | sb->buf_size, 100 | new_size 101 | ); 102 | if (sb->buffer == NULL) 103 | { 104 | /* TODO: We probably should free the buffer here */ 105 | sb->buf_size = 0UL; 106 | sb->end = 0; 107 | return LUABINS_ETOOLONG; 108 | } 109 | 110 | sb->buf_size = new_size; 111 | } 112 | 113 | return LUABINS_ESUCCESS; 114 | } 115 | 116 | /* 117 | * Returns non-zero if write failed. 118 | * Allocates buffer as needed. 119 | */ 120 | int lbsSB_write( 121 | luabins_SaveBuffer * sb, 122 | const unsigned char * bytes, 123 | size_t length 124 | ) 125 | { 126 | int result = lbsSB_grow(sb, length); 127 | if (result != LUABINS_ESUCCESS) 128 | { 129 | return result; 130 | } 131 | 132 | memcpy(&sb->buffer[sb->end], bytes, length); 133 | sb->end += length; 134 | 135 | return LUABINS_ESUCCESS; 136 | } 137 | 138 | /* 139 | * Returns non-zero if write failed. 140 | * Allocates buffer as needed. 141 | * Convenience function. 142 | */ 143 | int lbsSB_writechar( 144 | luabins_SaveBuffer * sb, 145 | const unsigned char byte 146 | ) 147 | { 148 | /* TODO: Shouldn't this be a macro? */ 149 | int result = lbsSB_grow(sb, 1); 150 | if (result != LUABINS_ESUCCESS) 151 | { 152 | return result; 153 | } 154 | 155 | sb->buffer[sb->end] = byte; 156 | sb->end++; 157 | 158 | return LUABINS_ESUCCESS; 159 | } 160 | 161 | /* 162 | * If offset is greater than total length, data is appended to the end. 163 | * Returns non-zero if write failed. 164 | * Allocates buffer as needed. 165 | */ 166 | int lbsSB_overwrite( 167 | luabins_SaveBuffer * sb, 168 | size_t offset, 169 | const unsigned char * bytes, 170 | size_t length 171 | ) 172 | { 173 | if (offset > sb->end) 174 | { 175 | offset = sb->end; 176 | } 177 | 178 | if (offset + length > sb->end) 179 | { 180 | int result = lbsSB_grow(sb, length); 181 | if (result != LUABINS_ESUCCESS) 182 | { 183 | return result; 184 | } 185 | 186 | sb->end = offset + length; 187 | } 188 | 189 | memcpy(&sb->buffer[offset], bytes, length); 190 | 191 | return LUABINS_ESUCCESS; 192 | } 193 | 194 | /* 195 | * If offset is greater than total length, data is appended to the end. 196 | * Returns non-zero if write failed. 197 | * Allocates buffer as needed. 198 | * Convenience function. 199 | */ 200 | int lbsSB_overwritechar( 201 | luabins_SaveBuffer * sb, 202 | size_t offset, 203 | unsigned char byte 204 | ) 205 | { 206 | if (offset > sb->end) 207 | { 208 | offset = sb->end; 209 | } 210 | 211 | if (offset + 1 > sb->end) 212 | { 213 | int result = lbsSB_grow(sb, 1); 214 | if (result != LUABINS_ESUCCESS) 215 | { 216 | return result; 217 | } 218 | 219 | sb->end = offset + 1; 220 | } 221 | 222 | sb->buffer[offset] = byte; 223 | 224 | return LUABINS_ESUCCESS; 225 | } 226 | 227 | /* 228 | * Returns a pointer to the internal buffer with data. 229 | * Note that buffer is NOT zero-terminated. 230 | * Buffer is valid until next operation with the given sb. 231 | */ 232 | const unsigned char * lbsSB_buffer(luabins_SaveBuffer * sb, size_t * length) 233 | { 234 | if (length != NULL) 235 | { 236 | *length = sb->end; 237 | } 238 | return sb->buffer; 239 | } 240 | 241 | void lbsSB_destroy(luabins_SaveBuffer * sb) 242 | { 243 | if (sb->buffer != NULL) 244 | { 245 | /* Ignoring errors */ 246 | SPAM(("dealloc size %lu\n", sb->buf_size)); 247 | sb->alloc_fn(sb->alloc_ud, sb->buffer, sb->buf_size, 0UL); 248 | sb->buffer = NULL; 249 | sb->buf_size = 0UL; 250 | sb->end = 0UL; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/save.c: -------------------------------------------------------------------------------- 1 | /* 2 | * save.c 3 | * Luabins save code 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include "luaheaders.h" 8 | 9 | #include "luabins.h" 10 | #include "saveload.h" 11 | #include "savebuffer.h" 12 | #include "write.h" 13 | 14 | /* TODO: Test this with custom allocator! */ 15 | 16 | #if 0 17 | #define SPAM(a) printf a 18 | #else 19 | #define SPAM(a) (void)0 20 | #endif 21 | 22 | static int save_value( 23 | lua_State * L, 24 | luabins_SaveBuffer * sb, 25 | int index, 26 | int nesting 27 | ); 28 | 29 | /* Returns 0 on success, non-zero on failure */ 30 | static int save_table( 31 | lua_State * L, 32 | luabins_SaveBuffer * sb, 33 | int index, 34 | int nesting 35 | ) 36 | { 37 | int result = LUABINS_ESUCCESS; 38 | int header_pos = 0; 39 | int total_size = 0; 40 | 41 | if (nesting > LUABINS_MAXTABLENESTING) 42 | { 43 | return LUABINS_ETOODEEP; 44 | } 45 | 46 | /* TODO: Hauling stack for key and value removal 47 | may get too heavy for larger tables. Think out a better way. 48 | */ 49 | 50 | header_pos = lbsSB_length(sb); 51 | result = lbs_writeTableHeader(sb, 0, 0); 52 | 53 | result = lbsSB_grow(sb, LUABINS_LINT + LUABINS_LINT); 54 | if (result == LUABINS_ESUCCESS) 55 | { 56 | lua_checkstack(L, 2); /* Key and value */ 57 | lua_pushnil(L); /* key for lua_next() */ 58 | } 59 | 60 | while (result == LUABINS_ESUCCESS && lua_next(L, index) != 0) 61 | { 62 | int value_pos = lua_gettop(L); /* We need absolute values */ 63 | int key_pos = value_pos - 1; 64 | 65 | /* Save key. */ 66 | result = save_value(L, sb, key_pos, nesting); 67 | 68 | /* Save value. */ 69 | if (result == LUABINS_ESUCCESS) 70 | { 71 | result = save_value(L, sb, value_pos, nesting); 72 | } 73 | 74 | if (result == LUABINS_ESUCCESS) 75 | { 76 | /* Remove value from stack, leave key for the next iteration. */ 77 | lua_pop(L, 1); 78 | ++total_size; 79 | } 80 | } 81 | 82 | if (result == LUABINS_ESUCCESS) 83 | { 84 | /* 85 | Note that if array has holes, lua_objlen() may report 86 | larger than actual array size. So we need to adjust. 87 | 88 | TODO: Note inelegant downsize from size_t to int. 89 | Handle integer overflow here. 90 | */ 91 | int array_size = luabins_min(total_size, (int)lua_objlen(L, index)); 92 | int hash_size = luabins_max(0, total_size - array_size); 93 | 94 | result = lbs_writeTableHeaderAt(sb, header_pos, array_size, hash_size); 95 | } 96 | 97 | return result; 98 | } 99 | 100 | /* Returns 0 on success, non-zero on failure */ 101 | static int save_value( 102 | lua_State * L, 103 | luabins_SaveBuffer * sb, 104 | int index, 105 | int nesting 106 | ) 107 | { 108 | int result = LUABINS_ESUCCESS; 109 | 110 | switch (lua_type(L, index)) 111 | { 112 | case LUA_TNIL: 113 | result = lbs_writeNil(sb); 114 | break; 115 | 116 | case LUA_TBOOLEAN: 117 | result = lbs_writeBoolean(sb, lua_toboolean(L, index)); 118 | break; 119 | 120 | case LUA_TNUMBER: 121 | result = lbs_writeNumber(sb, lua_tonumber(L, index)); 122 | break; 123 | 124 | case LUA_TSTRING: 125 | { 126 | size_t len = 0; 127 | const char * buf = lua_tolstring(L, index, &len); 128 | 129 | result = lbs_writeString(sb, buf, len); 130 | } 131 | break; 132 | 133 | case LUA_TTABLE: 134 | result = save_table(L, sb, index, nesting + 1); 135 | break; 136 | 137 | case LUA_TNONE: 138 | case LUA_TFUNCTION: 139 | case LUA_TTHREAD: 140 | case LUA_TUSERDATA: 141 | default: 142 | result = LUABINS_EBADTYPE; 143 | } 144 | 145 | return result; 146 | } 147 | 148 | int luabins_save(lua_State * L, int index_from, int index_to) 149 | { 150 | unsigned char num_to_save = 0; 151 | int index = index_from; 152 | int base = lua_gettop(L); 153 | luabins_SaveBuffer sb; 154 | 155 | /* 156 | * TODO: If lua_error() would happen below, would leak the buffer. 157 | */ 158 | 159 | if (index_to - index_from > LUABINS_MAXTUPLE) 160 | { 161 | lua_pushliteral(L, "can't save that many items"); 162 | return LUABINS_EFAILURE; 163 | } 164 | 165 | /* Allowing to call luabins_save(L, 1, lua_gettop(L)) 166 | from C function, called from Lua with no arguments 167 | (when lua_gettop() would return 0) 168 | */ 169 | if (index_to < index_from) 170 | { 171 | index_from = 0; 172 | index_to = 0; 173 | num_to_save = 0; 174 | } 175 | else 176 | { 177 | if ( 178 | index_from < 0 || index_from > base || 179 | index_to < 0 || index_to > base 180 | ) 181 | { 182 | lua_pushliteral(L, "can't save: inexistant indices"); 183 | return LUABINS_EFAILURE; 184 | } 185 | 186 | num_to_save = index_to - index_from + 1; 187 | } 188 | 189 | { 190 | void * alloc_ud = NULL; 191 | lua_Alloc alloc_fn = lua_getallocf(L, &alloc_ud); 192 | lbsSB_init(&sb, alloc_fn, alloc_ud); 193 | } 194 | 195 | lbs_writeTupleSize(&sb, num_to_save); 196 | for ( ; index <= index_to; ++index) 197 | { 198 | int result = 0; 199 | 200 | result = save_value(L, &sb, index, 0); 201 | if (result != LUABINS_ESUCCESS) 202 | { 203 | switch (result) 204 | { 205 | case LUABINS_EBADTYPE: 206 | lua_pushliteral(L, "can't save: unsupported type detected"); 207 | break; 208 | 209 | case LUABINS_ETOODEEP: 210 | lua_pushliteral(L, "can't save: nesting is too deep"); 211 | break; 212 | 213 | case LUABINS_ETOOLONG: 214 | lua_pushliteral(L, "can't save: not enough memory"); 215 | break; 216 | 217 | default: /* Should not happen */ 218 | lua_pushliteral(L, "save failed"); 219 | break; 220 | } 221 | 222 | lbsSB_destroy(&sb); 223 | 224 | return result; 225 | } 226 | } 227 | 228 | { 229 | size_t len = 0UL; 230 | const unsigned char * buf = lbsSB_buffer(&sb, &len); 231 | lua_pushlstring(L, (const char *)buf, len); 232 | lbsSB_destroy(&sb); 233 | } 234 | 235 | return LUABINS_ESUCCESS; 236 | } 237 | -------------------------------------------------------------------------------- /etc/Makefile.luabins.template: -------------------------------------------------------------------------------- 1 | @{define:projectname:luabins} 2 | @{define:test-dir:test/} 3 | @{define:source-dir:src/} 4 | @{define:sharedlib:$(LIBDIR)/$(SONAME)} 5 | @{define:staticlib:$(LIBDIR)/$(ANAME)} 6 | @{define-table:release-info: 7 | {suffix=""; 8 | libdir="$(LIBDIR)";objprefix="$(OBJDIR)/"; 9 | cflags=""; 10 | CC="$(CC)"; 11 | LD="$(LD)"; 12 | }; 13 | } 14 | @{define-table:std-info: 15 | {suffix="c89"; 16 | libdir="$(TMPDIR)/c89";objprefix="$(OBJDIR)/c89-"; 17 | cflags="-Werror -Wall -Wextra -pedantic -x c -std=c89"; 18 | CC="$(CC)"; 19 | LD="$(LD)"; 20 | }; 21 | {suffix="c99"; 22 | libdir="$(TMPDIR)/c99";objprefix="$(OBJDIR)/c99-"; 23 | cflags="-Werror -Wall -Wextra -pedantic -x c -std=c99"; 24 | CC="$(CC)"; 25 | LD="$(LD)"; 26 | }; 27 | {suffix="c++98"; 28 | libdir="$(TMPDIR)/c++98";objprefix="$(OBJDIR)/c++98-"; 29 | cflags="-Werror -Wall -Wextra -pedantic -x c++ -std=c++98"; 30 | CC="$(CXX)"; 31 | LD="$(LDXX)"; 32 | }; 33 | } 34 | @{define-dep:dep-objects:dep-template::@{source-dir}} 35 | @{define-dep:test-dep-objects:test-dep-template:-I@{source-dir}:@{test-dir}} 36 | 37 | ## CONFIGURATION ############################################################## 38 | 39 | ifeq ($(shell uname),Darwin) 40 | LUA_DIR := /usr/local 41 | LUA_LIBDIR := $(LUA_DIR)/lib/lua/5.1 42 | LUA_INCDIR := $(LUA_DIR)/include 43 | LUALIB := lua 44 | else 45 | # Assuming Ubuntu 46 | LUA_LIBDIR := /usr/lib 47 | LUA_INCDIR := /usr/include/lua5.1 48 | LUALIB := lua5.1 49 | endif 50 | 51 | PROJECTNAME := @{projectname} 52 | 53 | SONAME := $(PROJECTNAME).so 54 | ANAME := lib$(PROJECTNAME).a 55 | HNAME := $(PROJECTNAME).h 56 | TESTNAME := $(PROJECTNAME)-test 57 | TESTLUA := test.lua 58 | 59 | LUA := lua 60 | CP := cp 61 | RM := rm -f 62 | RMDIR := rm -df 63 | MKDIR := mkdir -p 64 | CC := gcc 65 | LD := gcc 66 | AR := ar rcu 67 | RANLIB := ranlib 68 | ECHO := @echo 69 | TOUCH := touch 70 | 71 | # Needed for tests only 72 | CXX := g++ 73 | LDXX := g++ 74 | 75 | OBJDIR := ./obj 76 | TMPDIR := ./tmp 77 | INCDIR := ./include 78 | LIBDIR := ./lib 79 | 80 | HFILE := $(INCDIR)/$(HNAME) 81 | 82 | CFLAGS += -O2 -Wall -I$(LUA_INCDIR) 83 | LDFLAGS += -L$(LUA_LIBDIR) 84 | 85 | # Tested on OS X and Ubuntu 86 | SOFLAGS := 87 | ifeq ($(shell uname),Darwin) 88 | SOFLAGS += -dynamiclib -undefined dynamic_lookup 89 | else 90 | CFLAGS += -fPIC 91 | SOFLAGS += -shared 92 | LDFLAGS += -ldl 93 | RMDIR := rm -rf 94 | endif 95 | 96 | CFLAGS += $(MYCFLAGS) 97 | LDFLAGS += $(MYLDFLAGS) 98 | 99 | ## MAIN TARGETS ############################################################### 100 | 101 | @{define-table:.PHONY:} 102 | 103 | @{insert:.PHONY:all} 104 | all: @{sharedlib} @{staticlib} $(HFILE) 105 | 106 | @{insert:.PHONY:clean} 107 | clean: cleanlibs cleantest 108 | $(RM) $(HFILE) 109 | 110 | @{insert:.PHONY:install} 111 | install: @{sharedlib} 112 | # Note header and static library are not copied anywhere 113 | $(CP) @{sharedlib} $(LUA_LIBDIR)/$(SONAME) 114 | 115 | $(HFILE): 116 | $(CP) src/$(HNAME) $(HFILE) 117 | 118 | @{--:-------------------------------------------------------------------------} 119 | 120 | @{define:objects-macro: 121 | 122 | @{insert:.PHONY:clean%objects@{suffix}} 123 | clean%objects@{suffix}: 124 | $(RM) % 125 | 126 | % 127 | 128 | } 129 | 130 | @{define:lib-targets: 131 | 132 | @{define-fill:objects:@{fill-template:@{dep-objects}:}} 133 | 134 | @{insert:.PHONY:cleanlibs@{suffix}} 135 | cleanlibs@{suffix}: cleanobjects@{suffix} 136 | $(RM) @{sharedlib} 137 | $(RM) @{staticlib} 138 | 139 | @{sharedlib}: @{objects} 140 | $(MKDIR) @{libdir} 141 | @{LD} -o $@ @{objects} $(LDFLAGS) $(SOFLAGS) 142 | 143 | @{staticlib}: @{objects} 144 | $(MKDIR) @{libdir} 145 | $(AR) $@ @{objects} 146 | $(RANLIB) $@ 147 | 148 | @{--: note implicit objprefix parameter } 149 | # objects@{suffix}: 150 | @{fill-macro:objects-macro:%<>: 151 | prefix="", 152 | objects=@{lua-escape:@{objects}}; 153 | dep=@{lua-escape:@{fill-template:dep-template:}}; 154 | } 155 | 156 | } 157 | 158 | @{define:std-targets: 159 | 160 | ## ----- Begin @{suffix} ----- 161 | 162 | @{define-fill:cflags-orig:@{cflags}} 163 | @{define-fill:cflags:@{cflags} -I@{source-dir}} 164 | 165 | @{define-fill:sharedlib:@{libdir}/$(SONAME)} 166 | @{define-fill:staticlib:@{libdir}/$(ANAME)} 167 | @{define-fill:objects:@{fill-template:@{test-dep-objects}:}} 168 | 169 | @{insert:.PHONY:test@{suffix}} 170 | test@{suffix}: lua-tests@{suffix} c-tests@{suffix} 171 | 172 | @{insert:.PHONY:lua-tests@{suffix}} 173 | lua-tests@{suffix}: @{libdir}/.luatestspassed 174 | 175 | @{insert:.PHONY:c-tests@{suffix}} 176 | c-tests@{suffix}: @{libdir}/.ctestspassed 177 | 178 | @{libdir}/.luatestspassed: @{sharedlib} test/$(TESTLUA) 179 | $(ECHO) "===== Running Lua tests for @{suffix} =====" 180 | @$(LUA) \ 181 | -e "package.cpath='@{sharedlib};'..package.cpath" \ 182 | test/$(TESTLUA) 183 | $(TOUCH) @{libdir}/.luatestspassed 184 | $(ECHO) "===== Lua tests for @{suffix} PASSED =====" 185 | 186 | @{libdir}/.ctestspassed: @{libdir}/$(TESTNAME) test/$(TESTLUA) 187 | $(ECHO) "===== Running C tests for @{suffix} =====" 188 | @{libdir}/$(TESTNAME) 189 | $(TOUCH) @{libdir}/.ctestspassed 190 | $(ECHO) "===== C tests for @{suffix} PASSED =====" 191 | 192 | @{libdir}/$(TESTNAME): @{objects} @{staticlib} 193 | $(MKDIR) @{libdir} 194 | @{LD} -o $@ @{objects} $(LDFLAGS) -lm -l$(LUALIB) -l$(PROJECTNAME) -L@{libdir} 195 | 196 | @{insert:.PHONY:resettest@{suffix}} 197 | resettest@{suffix}: 198 | $(RM) @{libdir}/.luatestspassed 199 | $(RM) @{libdir}/.ctestspassed 200 | 201 | @{insert:.PHONY:cleantest@{suffix}} 202 | cleantest@{suffix}: cleanlibs@{suffix} resettest@{suffix} \ 203 | cleantestobjects@{suffix} 204 | $(RM) @{libdir}/$(TESTNAME) 205 | $(RMDIR) @{libdir} 206 | 207 | @{--: Note implicit objprefix parameter } 208 | # testobjects@{suffix}: 209 | @{fill-macro:objects-macro:%<>: 210 | prefix="test", 211 | objects=@{lua-escape:@{objects}}; 212 | dep=@{lua-escape:@{fill-template:test-dep-template:}}; 213 | } 214 | 215 | @{--: Libraries must use original cflags } 216 | @{fill-template:lib-targets:cflags=@{lua-escape:@{cflags-orig}}} 217 | 218 | } 219 | 220 | ## GENERATED RELEASE TARGETS ################################################## 221 | 222 | @{map-template:release-info:lib-targets} 223 | 224 | ## TEST TARGETS ############################################################### 225 | 226 | @{insert:.PHONY:test} 227 | test:@{map-template:std-info: test@{suffix}} 228 | $(ECHO) "===== TESTS PASSED =====" 229 | 230 | @{insert:.PHONY:resettest} 231 | resettest:@{map-template:std-info: resettest@{suffix}} 232 | 233 | @{insert:.PHONY:cleantest} 234 | cleantest:@{map-template:std-info: cleantest@{suffix}} 235 | 236 | ## GENERATED TEST TARGETS ##################################################### 237 | 238 | @{map-template:std-info:std-targets} 239 | 240 | ## END OF GENERATED TARGETS ################################################### 241 | 242 | .PHONY: @{concat:.PHONY: } 243 | -------------------------------------------------------------------------------- /test/test_api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test_api.c 3 | * Luabins API tests 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif /* __cplusplus */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif /* __cplusplus */ 20 | 21 | #include "luabins.h" 22 | 23 | #define STACKGUARD "-- stack ends here --" 24 | 25 | /* Note this one does not dump values to protect from embedded zeroes. */ 26 | static int dump_lua_stack(lua_State * L, int base) 27 | { 28 | int top = lua_gettop(L); 29 | 30 | if (top == 0) 31 | { 32 | lua_pushliteral(L, "-- stack is empty --"); 33 | } 34 | else 35 | { 36 | int pos = 0; 37 | luaL_Buffer b; 38 | 39 | luaL_buffinit(L, &b); 40 | 41 | for (pos = top; pos > 0; --pos) 42 | { 43 | luaL_addstring(&b, (pos != base) ? "[" : "{"); 44 | lua_pushinteger(L, pos); 45 | luaL_addvalue(&b); 46 | luaL_addstring(&b, (pos != base) ? "] - " : "} -"); 47 | luaL_addstring(&b, luaL_typename(L, pos)); 48 | luaL_addstring(&b, "\n"); 49 | } 50 | 51 | luaL_pushresult(&b); 52 | } 53 | 54 | if (lua_gettop(L) != top + 1) 55 | { 56 | return luaL_error(L, "dumpstack not balanced %d %d", top, lua_gettop(L)); 57 | } 58 | 59 | return 1; 60 | } 61 | 62 | static void fatal(lua_State * L, const char * msg) 63 | { 64 | dump_lua_stack(L, 0); 65 | fprintf(stderr, "%s\nSTACK\n%s", msg, lua_tostring(L, -1)); 66 | lua_pop(L, 1); 67 | fflush(stderr); 68 | exit(1); 69 | } 70 | 71 | static void check(lua_State * L, int base, int extra) 72 | { 73 | int top = lua_gettop(L); 74 | if (top != base + extra) 75 | { 76 | fatal(L, "stack unbalanced"); 77 | } 78 | 79 | lua_pushliteral(L, STACKGUARD); 80 | if (lua_rawequal(L, -1, base) == 0) 81 | { 82 | fatal(L, "stack guard corrupted"); 83 | } 84 | lua_pop(L, 1); 85 | } 86 | 87 | static void checkerr(lua_State * L, int base, const char * err) 88 | { 89 | int top = lua_gettop(L); 90 | if (top != base + 1) 91 | { 92 | fatal(L, "stack unbalanced on error"); 93 | } 94 | 95 | lua_pushliteral(L, STACKGUARD); 96 | if (lua_rawequal(L, -1, base) == 0) 97 | { 98 | fatal(L, "stack guard corrupted"); 99 | } 100 | lua_pop(L, 1); 101 | 102 | lua_pushstring(L, err); 103 | if (lua_rawequal(L, -1, -2) == 0) 104 | { 105 | fprintf(stderr, "actual: '%s'\n", lua_tostring(L, -2)); 106 | fprintf(stderr, "expected: '%s'\n", err); 107 | fatal(L, "error message mismatch"); 108 | } 109 | lua_pop(L, 2); /* Pops error message as well */ 110 | } 111 | 112 | static int push_testdataset(lua_State * L) 113 | { 114 | int base = lua_gettop(L); 115 | 116 | lua_pushnil(L); 117 | lua_pushboolean(L, 0); 118 | lua_pushboolean(L, 1); 119 | lua_pushinteger(L, 42); 120 | lua_pushliteral(L, "luabins"); 121 | 122 | lua_newtable(L); 123 | 124 | if (lua_gettop(L) - base != 6) 125 | { 126 | fatal(L, "push_dataset broken"); 127 | } 128 | 129 | return 6; 130 | } 131 | 132 | static void check_testdataset_on_top(lua_State * L) 133 | { 134 | int base = lua_gettop(L); 135 | 136 | /* TODO: Check table contents */ 137 | if (!lua_istable(L, -1)) 138 | { 139 | fatal(L, "dataset (-1) is not table"); 140 | } 141 | 142 | lua_pushliteral(L, "luabins"); 143 | if (!lua_rawequal(L, -1, -2 - 1)) 144 | { 145 | fatal(L, "dataset (-2) value mismatch"); 146 | } 147 | lua_pop(L, 1); 148 | 149 | lua_pushinteger(L, 42); 150 | if (!lua_rawequal(L, -1, -3 - 1)) 151 | { 152 | fatal(L, "dataset (-3) value mismatch"); 153 | } 154 | lua_pop(L, 1); 155 | 156 | lua_pushboolean(L, 1); 157 | if (!lua_rawequal(L, -1, -4 - 1)) 158 | { 159 | fatal(L, "dataset (-4) value mismatch"); 160 | } 161 | lua_pop(L, 1); 162 | 163 | lua_pushboolean(L, 0); 164 | if (!lua_rawequal(L, -1, -5 - 1)) 165 | { 166 | fatal(L, "dataset (-5) value mismatch"); 167 | } 168 | lua_pop(L, 1); 169 | 170 | lua_pushnil(L); 171 | if (!lua_rawequal(L, -1, -6 - 1)) 172 | { 173 | fatal(L, "dataset (-6) value mismatch"); 174 | } 175 | lua_pop(L, 1); 176 | 177 | if (lua_gettop(L) != base) 178 | { 179 | fatal(L, "check_dataset_on_top broken"); 180 | } 181 | } 182 | 183 | void test_api() 184 | { 185 | int base = 0; 186 | int count = 0; 187 | const unsigned char * str; 188 | size_t length = 0; 189 | 190 | lua_State * L = lua_open(); 191 | luaL_openlibs(L); 192 | 193 | printf("---> BEGIN test_api\n"); 194 | 195 | /* Push stack check value */ 196 | lua_pushliteral(L, STACKGUARD); 197 | base = lua_gettop(L); 198 | 199 | /* Sanity check */ 200 | check(L, base, 0); 201 | 202 | /* Save error: inexistant index */ 203 | if (luabins_save(L, lua_gettop(L) + 1, lua_gettop(L) + 1) == 0) 204 | { 205 | fatal(L, "save should fail"); 206 | } 207 | 208 | checkerr(L, base, "can't save: inexistant indices"); 209 | 210 | if (luabins_save(L, -1, -1) == 0) 211 | { 212 | fatal(L, "save should fail"); 213 | } 214 | 215 | checkerr(L, base, "can't save: inexistant indices"); 216 | 217 | /* Assuming other save errors to be tested in test.lua */ 218 | 219 | /* Trigger load error */ 220 | 221 | if (luabins_load(L, (const unsigned char *)"", 0, &count) == 0) 222 | { 223 | fatal(L, "load should fail"); 224 | } 225 | 226 | checkerr(L, base, "can't load: corrupt data"); 227 | 228 | /* Assuming other load errors to be tested in test.lua */ 229 | 230 | /* Do empty save */ 231 | if (luabins_save(L, base, base - 1) != 0) 232 | { 233 | fatal(L, "empty save failed"); 234 | } 235 | check(L, base, 1); 236 | 237 | str = (const unsigned char *)lua_tolstring(L, -1, &length); 238 | if (str == NULL || length == 0) 239 | { 240 | fatal(L, "bad empty save string"); 241 | } 242 | 243 | /* Load empty save */ 244 | 245 | if (luabins_load(L, str, length, &count) != 0) 246 | { 247 | fatal(L, "empty load failed"); 248 | } 249 | 250 | if (count != 0) 251 | { 252 | fatal(L, "bad empty load count"); 253 | } 254 | 255 | /* Pop saved data string */ 256 | check(L, base, 1); 257 | lua_pop(L, 1); 258 | check(L, base, 0); 259 | 260 | { 261 | /* Save test dataset */ 262 | 263 | int num_items = push_testdataset(L); 264 | check(L, base, num_items); 265 | 266 | if (luabins_save(L, base + 1, base + num_items) != 0) 267 | { 268 | fprintf(stderr, "%s\n", lua_tostring(L, -1)); 269 | fatal(L, "test dataset save failed"); 270 | } 271 | 272 | check(L, base, num_items + 1); 273 | 274 | /* Load test dataset */ 275 | 276 | str = (const unsigned char *)lua_tolstring(L, -1, &length); 277 | if (str == NULL || length == 0) 278 | { 279 | fatal(L, "bad empty save string"); 280 | } 281 | 282 | if (luabins_load(L, str, length, &count) != 0) 283 | { 284 | fprintf(stderr, "%s\n", lua_tostring(L, -1)); 285 | fatal(L, "test dataset load failed"); 286 | } 287 | 288 | if (count != num_items) 289 | { 290 | fatal(L, "wrong test dataset load count"); 291 | } 292 | 293 | check(L, base, num_items + 1 + num_items); 294 | 295 | check_testdataset_on_top(L); /* Check loaded data */ 296 | 297 | lua_pop(L, 1 + num_items); 298 | 299 | check_testdataset_on_top(L); /* Check original data intact */ 300 | 301 | lua_pop(L, num_items); 302 | 303 | check(L, base, 0); 304 | 305 | /* Assuming further tests are done in test.lua */ 306 | } 307 | 308 | lua_close(L); 309 | 310 | printf("---> OK\n"); 311 | } 312 | -------------------------------------------------------------------------------- /src/load.c: -------------------------------------------------------------------------------- 1 | /* 2 | * load.c 3 | * Luabins load code 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | #include 8 | 9 | #include "luaheaders.h" 10 | 11 | #include "luabins.h" 12 | #include "saveload.h" 13 | #include "luainternals.h" 14 | 15 | #if 0 16 | #define XSPAM(a) printf a 17 | #else 18 | #define XSPAM(a) (void)0 19 | #endif 20 | 21 | #if 0 22 | #define SPAM(a) printf a 23 | #else 24 | #define SPAM(a) (void)0 25 | #endif 26 | 27 | typedef struct lbs_LoadState 28 | { 29 | const unsigned char * pos; 30 | size_t unread; 31 | } lbs_LoadState; 32 | 33 | static void lbsLS_init( 34 | lbs_LoadState * ls, 35 | const unsigned char * data, 36 | size_t len 37 | ) 38 | { 39 | ls->pos = (len > 0) ? data : NULL; 40 | ls->unread = len; 41 | } 42 | 43 | #define lbsLS_good(ls) \ 44 | ((ls)->pos != NULL) 45 | 46 | #define lbsLS_unread(ls) \ 47 | ((ls)->unread) 48 | 49 | static unsigned char lbsLS_readbyte(lbs_LoadState * ls) 50 | { 51 | if (lbsLS_good(ls)) 52 | { 53 | if (lbsLS_unread(ls) > 0) 54 | { 55 | const unsigned char b = *ls->pos; 56 | ++ls->pos; 57 | --ls->unread; 58 | return b; 59 | } 60 | else 61 | { 62 | ls->unread = 0; 63 | ls->pos = NULL; 64 | } 65 | } 66 | return 0; 67 | } 68 | 69 | static const unsigned char * lbsLS_eat(lbs_LoadState * ls, size_t len) 70 | { 71 | const unsigned char * result = NULL; 72 | if (lbsLS_good(ls)) 73 | { 74 | if (lbsLS_unread(ls) >= len) 75 | { 76 | XSPAM(("* eat: len %u\n", (int)len)); 77 | result = ls->pos; 78 | ls->pos += len; 79 | ls->unread -= len; 80 | XSPAM(("* eat: done len %u\n", (int)len)); 81 | } 82 | else 83 | { 84 | ls->unread = 0; 85 | ls->pos = NULL; 86 | } 87 | } 88 | return result; 89 | } 90 | 91 | static int lbsLS_readbytes( 92 | lbs_LoadState * ls, 93 | unsigned char * buf, 94 | size_t len 95 | ) 96 | { 97 | const unsigned char * pos = lbsLS_eat(ls, len); 98 | if (pos != NULL) 99 | { 100 | memcpy(buf, pos, len); 101 | return LUABINS_ESUCCESS; 102 | } 103 | SPAM(("load: Failed to read %lu bytes\n", (unsigned long)len)); 104 | return LUABINS_EBADDATA; 105 | } 106 | 107 | static int load_value(lua_State * L, lbs_LoadState * ls); 108 | 109 | static int load_table(lua_State * L, lbs_LoadState * ls) 110 | { 111 | int array_size = 0; 112 | int hash_size = 0; 113 | unsigned int total_size = 0; 114 | 115 | int result = lbsLS_readbytes(ls, (unsigned char *)&array_size, LUABINS_LINT); 116 | if (result == LUABINS_ESUCCESS) 117 | { 118 | result = lbsLS_readbytes(ls, (unsigned char *)&hash_size, LUABINS_LINT); 119 | } 120 | 121 | if (result == LUABINS_ESUCCESS) 122 | { 123 | total_size = array_size + hash_size; 124 | /* 125 | SPAM(( 126 | "LT SIZE CHECK\n" 127 | "* array_size %d limit 0 .. %d\n" 128 | "* hash_size %d limit >0\n" 129 | "* hash_size bytes %d, limit %d\n" 130 | "* unread %u limit >min_size %u (total_size %u)\n", 131 | array_size, MAXASIZE, 132 | hash_size, 133 | ceillog2((unsigned int)hash_size), MAXBITS, 134 | (unsigned int)lbsLS_unread(ls), 135 | (unsigned int)luabins_min_table_data_size(total_size), 136 | (unsigned int)total_size 137 | )); 138 | */ 139 | if ( 140 | array_size < 0 || array_size > MAXASIZE || 141 | hash_size < 0 || 142 | (hash_size > 0 && ceillog2((unsigned int)hash_size) > MAXBITS) || 143 | lbsLS_unread(ls) < luabins_min_table_data_size(total_size) 144 | ) 145 | { 146 | result = LUABINS_EBADSIZE; 147 | } 148 | } 149 | 150 | if (result == LUABINS_ESUCCESS) 151 | { 152 | unsigned int i = 0; 153 | 154 | XSPAM(( 155 | "* load: creating table a:%d + h:%d = %d\n", 156 | array_size, hash_size, total_size 157 | )); 158 | 159 | lua_createtable(L, array_size, hash_size); 160 | 161 | for (i = 0; i < total_size; ++i) 162 | { 163 | int key_type = LUA_TNONE; 164 | 165 | result = load_value(L, ls); /* Load key. */ 166 | if (result != LUABINS_ESUCCESS) 167 | { 168 | break; 169 | } 170 | 171 | /* Table key can't be nil or NaN */ 172 | key_type = lua_type(L, -1); 173 | if (key_type == LUA_TNIL) 174 | { 175 | /* Corrupt data? */ 176 | SPAM(("load: nil as key detected\n")); 177 | result = LUABINS_EBADDATA; 178 | break; 179 | } 180 | 181 | if (key_type == LUA_TNUMBER) 182 | { 183 | lua_Number key = lua_tonumber(L, -1); 184 | if (luai_numisnan(key)) 185 | { 186 | /* Corrupt data? */ 187 | SPAM(("load: NaN as key detected\n")); 188 | result = LUABINS_EBADDATA; 189 | break; 190 | } 191 | } 192 | 193 | result = load_value(L, ls); /* Load value. */ 194 | if (result != LUABINS_ESUCCESS) 195 | { 196 | break; 197 | } 198 | 199 | lua_rawset(L, -3); 200 | } 201 | } 202 | 203 | return result; 204 | } 205 | 206 | static int load_value(lua_State * L, lbs_LoadState * ls) 207 | { 208 | int result = LUABINS_ESUCCESS; 209 | unsigned char type = lbsLS_readbyte(ls); 210 | if (!lbsLS_good(ls)) 211 | { 212 | SPAM(("load: Failed to read value type byte\n")); 213 | return LUABINS_EBADDATA; 214 | } 215 | 216 | XSPAM(("* load: begin load_value\n")); 217 | 218 | luaL_checkstack(L, 1, "load_value"); 219 | 220 | switch (type) 221 | { 222 | case LUABINS_CNIL: 223 | XSPAM(("* load: nil\n")); 224 | lua_pushnil(L); 225 | break; 226 | 227 | case LUABINS_CFALSE: 228 | XSPAM(("* load: false\n")); 229 | lua_pushboolean(L, 0); 230 | break; 231 | 232 | case LUABINS_CTRUE: 233 | XSPAM(("* load: true\n")); 234 | lua_pushboolean(L, 1); 235 | break; 236 | 237 | case LUABINS_CNUMBER: 238 | { 239 | lua_Number value; 240 | 241 | XSPAM(("* load: number\n")); 242 | 243 | result = lbsLS_readbytes(ls, (unsigned char *)&value, LUABINS_LNUMBER); 244 | if (result == LUABINS_ESUCCESS) 245 | { 246 | lua_pushnumber(L, value); 247 | } 248 | } 249 | break; 250 | 251 | case LUABINS_CSTRING: 252 | { 253 | size_t len = 0; 254 | 255 | XSPAM(("* load: string\n")); 256 | 257 | result = lbsLS_readbytes(ls, (unsigned char *)&len, LUABINS_LSIZET); 258 | if (result == LUABINS_ESUCCESS) 259 | { 260 | const unsigned char * pos = lbsLS_eat(ls, len); 261 | 262 | XSPAM(("* load: string size %u\n", (int)len)); 263 | 264 | if (pos != NULL) 265 | { 266 | lua_pushlstring(L, (const char *)pos, len); 267 | } 268 | else 269 | { 270 | result = LUABINS_EBADSIZE; 271 | } 272 | } 273 | } 274 | break; 275 | 276 | case LUABINS_CTABLE: 277 | XSPAM(("* load: table\n")); 278 | result = load_table(L, ls); 279 | break; 280 | 281 | default: 282 | SPAM(("load: Unknown type char 0x%02X found\n", type)); 283 | result = LUABINS_EBADDATA; 284 | break; 285 | } 286 | 287 | XSPAM(("* load: end load_value\n")); 288 | 289 | return result; 290 | } 291 | 292 | int luabins_load( 293 | lua_State * L, 294 | const unsigned char * data, 295 | size_t len, 296 | int * count 297 | ) 298 | { 299 | lbs_LoadState ls; 300 | int result = LUABINS_ESUCCESS; 301 | unsigned char num_items = 0; 302 | int base = 0; 303 | int i = 0; 304 | 305 | base = lua_gettop(L); 306 | 307 | lbsLS_init(&ls, data, len); 308 | num_items = lbsLS_readbyte(&ls); 309 | if (!lbsLS_good(&ls)) 310 | { 311 | SPAM(("load: failed to read num_items byte\n")); 312 | result = LUABINS_EBADDATA; 313 | } 314 | else if (num_items > LUABINS_MAXTUPLE) 315 | { 316 | SPAM(("load: tuple too large: %d\n", (int)num_items)); 317 | result = LUABINS_EBADSIZE; 318 | } 319 | else 320 | { 321 | XSPAM(("* load: tuple size %d\n", (int)num_items)); 322 | for ( 323 | i = 0; 324 | i < num_items && result == LUABINS_ESUCCESS; 325 | ++i 326 | ) 327 | { 328 | XSPAM(("* load: loading tuple item %d\n", i)); 329 | result = load_value(L, &ls); 330 | } 331 | } 332 | 333 | if (result == LUABINS_ESUCCESS && lbsLS_unread(&ls) > 0) 334 | { 335 | SPAM(("load: %lu chars left at tail\n", lbsLS_unread(&ls))); 336 | result = LUABINS_ETAILEFT; 337 | } 338 | 339 | if (result == LUABINS_ESUCCESS) 340 | { 341 | *count = num_items; 342 | } 343 | else 344 | { 345 | lua_settop(L, base); /* Discard intermediate results */ 346 | switch (result) 347 | { 348 | case LUABINS_EBADDATA: 349 | lua_pushliteral(L, "can't load: corrupt data"); 350 | break; 351 | 352 | case LUABINS_EBADSIZE: 353 | lua_pushliteral(L, "can't load: corrupt data, bad size"); 354 | break; 355 | 356 | case LUABINS_ETAILEFT: 357 | lua_pushliteral(L, "can't load: extra data at end"); 358 | break; 359 | 360 | default: /* Should not happen */ 361 | lua_pushliteral(L, "load failed"); 362 | break; 363 | } 364 | } 365 | 366 | return result; 367 | } 368 | -------------------------------------------------------------------------------- /test/test_savebuffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test_savebuffer.c 3 | * Luabins SaveBuffer tests 4 | * See copyright notice in luabins.h 5 | */ 6 | 7 | /* 8 | * TODO: Tests are tuned for old fixed-increment memory allocation strategy. 9 | * Test for exponential growth corner-cases specifically. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "lualess.h" 17 | #include "savebuffer.h" 18 | 19 | #include "test.h" 20 | 21 | /******************************************************************************/ 22 | 23 | static size_t NOT_CHANGED = (size_t)-1; 24 | static void * NOT_CHANGED_PTR = NULL; 25 | 26 | static size_t DUMMY = (size_t)-42; 27 | static void * DUMMY_PTR = NULL; 28 | 29 | static void * g_last_ud = NULL; 30 | static size_t g_last_osize = 0; 31 | 32 | static void reset_alloc_globals() 33 | { 34 | g_last_ud = NOT_CHANGED_PTR; 35 | g_last_osize = NOT_CHANGED; 36 | } 37 | 38 | static void init_globals() 39 | { 40 | NOT_CHANGED_PTR = (void *)&NOT_CHANGED; 41 | DUMMY_PTR = (void *)&DUMMY; 42 | 43 | reset_alloc_globals(); 44 | } 45 | 46 | static void * dummy_alloc( 47 | void * ud, 48 | void * ptr, 49 | size_t osize, 50 | size_t nsize 51 | ) 52 | { 53 | g_last_ud = ud; 54 | g_last_osize = osize; 55 | 56 | if (nsize == 0) 57 | { 58 | free(ptr); 59 | return NULL; 60 | } 61 | else 62 | { 63 | return realloc(ptr, nsize); 64 | } 65 | } 66 | 67 | /******************************************************************************/ 68 | 69 | static void check_alloc(void * expected_ud, size_t expected_osize) 70 | { 71 | if (g_last_ud != expected_ud) 72 | { 73 | fprintf( 74 | stderr, 75 | "userdata mismatch in allocator: got %p, expected %p\n", 76 | g_last_ud, expected_ud 77 | ); 78 | exit(1); 79 | } 80 | 81 | if (g_last_osize != expected_osize) 82 | { 83 | fprintf( 84 | stderr, 85 | "old size mismatch in allocator: got %lu, expected %lu\n", 86 | g_last_osize, expected_osize 87 | ); 88 | exit(1); 89 | } 90 | 91 | reset_alloc_globals(); 92 | } 93 | 94 | static void check_buffer( 95 | luabins_SaveBuffer * sb, 96 | const char * expected_buf_c, 97 | size_t expected_length, 98 | void * expected_ud, 99 | size_t expected_osize 100 | ) 101 | { 102 | const unsigned char * expected_buf = (const unsigned char *)expected_buf_c; 103 | 104 | { 105 | size_t actual_length = lbsSB_length(sb); 106 | if (actual_length != expected_length) 107 | { 108 | fprintf( 109 | stderr, 110 | "lbsSB_length mismatch in allocator: got %lu, expected %lu\n", 111 | actual_length, expected_length 112 | ); 113 | exit(1); 114 | } 115 | } 116 | 117 | { 118 | size_t actual_length = (size_t)-1; 119 | const unsigned char * actual_buf = lbsSB_buffer(sb, &actual_length); 120 | if (actual_length != expected_length) 121 | { 122 | fprintf( 123 | stderr, 124 | "lsbSB_buffer length mismatch in allocator: got %lu, expected %lu\n", 125 | actual_length, expected_length 126 | ); 127 | exit(1); 128 | } 129 | 130 | if (memcmp(actual_buf, expected_buf, expected_length) != 0) 131 | { 132 | fprintf( 133 | stderr, 134 | "lsbSB_buffer buf mismatch in allocator\n" 135 | ); 136 | exit(1); 137 | } 138 | } 139 | 140 | check_alloc(expected_ud, expected_osize); 141 | } 142 | 143 | /******************************************************************************/ 144 | 145 | TEST (test_init_destroy, 146 | { 147 | luabins_SaveBuffer sb; 148 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 149 | 150 | check_buffer(&sb, "", 0, NOT_CHANGED_PTR, NOT_CHANGED); 151 | 152 | lbsSB_destroy(&sb); 153 | check_alloc(NOT_CHANGED_PTR, NOT_CHANGED); 154 | }) 155 | 156 | TEST (test_grow_zero, 157 | { 158 | luabins_SaveBuffer sb; 159 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 160 | 161 | lbsSB_grow(&sb, 0); 162 | check_buffer(&sb, "", 0, NOT_CHANGED_PTR, NOT_CHANGED); 163 | 164 | lbsSB_destroy(&sb); 165 | check_alloc(NOT_CHANGED_PTR, NOT_CHANGED); 166 | }) 167 | 168 | TEST (test_grow_bufsiz, 169 | { 170 | luabins_SaveBuffer sb; 171 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 172 | 173 | lbsSB_grow(&sb, 1024); 174 | check_buffer(&sb, "", 0, DUMMY_PTR, 0); 175 | 176 | lbsSB_destroy(&sb); 177 | check_alloc(DUMMY_PTR, 1344); 178 | }) 179 | 180 | TEST (test_grow_one, 181 | { 182 | luabins_SaveBuffer sb; 183 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 184 | 185 | lbsSB_grow(&sb, 1); 186 | check_buffer(&sb, "", 0, DUMMY_PTR, 0); 187 | 188 | lbsSB_destroy(&sb); 189 | check_alloc(DUMMY_PTR, 256); 190 | }) 191 | 192 | TEST (test_grow_one_grow_one_noop, 193 | { 194 | luabins_SaveBuffer sb; 195 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 196 | 197 | lbsSB_grow(&sb, 1); 198 | check_buffer(&sb, "", 0, DUMMY_PTR, 0); 199 | 200 | lbsSB_grow(&sb, 1); 201 | check_buffer(&sb, "", 0, NOT_CHANGED_PTR, NOT_CHANGED); 202 | 203 | lbsSB_destroy(&sb); 204 | check_alloc(DUMMY_PTR, 256); 205 | }) 206 | 207 | TEST (test_grow_one_grow_bufsiz_noop, 208 | { 209 | luabins_SaveBuffer sb; 210 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 211 | 212 | lbsSB_grow(&sb, 1); 213 | check_buffer(&sb, "", 0, DUMMY_PTR, 0); 214 | 215 | lbsSB_grow(&sb, 255); 216 | check_buffer(&sb, "", 0, NOT_CHANGED_PTR, NOT_CHANGED); 217 | 218 | lbsSB_destroy(&sb); 219 | check_alloc(DUMMY_PTR, 256); 220 | }) 221 | 222 | TEST (test_grow_one_grow_bufsiz_one, 223 | { 224 | luabins_SaveBuffer sb; 225 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 226 | 227 | lbsSB_grow(&sb, 1); 228 | check_buffer(&sb, "", 0, DUMMY_PTR, 0); 229 | 230 | lbsSB_grow(&sb, 257); 231 | check_buffer(&sb, "", 0, DUMMY_PTR, 256); 232 | 233 | lbsSB_destroy(&sb); 234 | check_alloc(DUMMY_PTR, 512); 235 | }) 236 | 237 | /******************************************************************************/ 238 | 239 | TEST (test_write_empty, 240 | { 241 | luabins_SaveBuffer sb; 242 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 243 | 244 | lbsSB_write(&sb, (unsigned char*)"", 0); 245 | check_buffer(&sb, "", 0, NOT_CHANGED_PTR, NOT_CHANGED); 246 | 247 | lbsSB_destroy(&sb); 248 | check_alloc(NOT_CHANGED_PTR, NOT_CHANGED); 249 | }) 250 | 251 | TEST (test_write, 252 | { 253 | luabins_SaveBuffer sb; 254 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 255 | 256 | lbsSB_write(&sb, (unsigned char*)"42", 3); 257 | check_buffer(&sb, "42", 3, DUMMY_PTR, 0); 258 | 259 | lbsSB_destroy(&sb); 260 | check_alloc(DUMMY_PTR, 256); 261 | }) 262 | 263 | TEST (test_write_embedded_zero, 264 | { 265 | luabins_SaveBuffer sb; 266 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 267 | 268 | lbsSB_write(&sb, (unsigned char*)"4\02", 4); 269 | check_buffer(&sb, "4\02", 4, DUMMY_PTR, 0); 270 | 271 | lbsSB_destroy(&sb); 272 | check_alloc(DUMMY_PTR, 256); 273 | }) 274 | 275 | TEST (test_write_write_smallsiz, 276 | { 277 | luabins_SaveBuffer sb; 278 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 279 | 280 | lbsSB_write(&sb, (unsigned char*)"01234567", 8); 281 | check_buffer(&sb, "01234567", 8, DUMMY_PTR, 0); 282 | 283 | lbsSB_write(&sb, (unsigned char*)"01234567", 8); 284 | check_buffer(&sb, "0123456701234567", 8 + 8, NOT_CHANGED_PTR, NOT_CHANGED); 285 | 286 | lbsSB_write(&sb, (unsigned char*)"0123", 4); 287 | check_buffer( 288 | &sb, 289 | "01234567012345670123", 290 | 8 + 8 + 4, 291 | NOT_CHANGED_PTR, 292 | NOT_CHANGED 293 | ); 294 | 295 | lbsSB_write(&sb, (unsigned char*)"0123456789ABCDEF", 16); 296 | check_buffer( 297 | &sb, 298 | "012345670123456701230123456789ABCDEF", 299 | 8 + 8 + 4 + 16, 300 | NOT_CHANGED_PTR, 301 | NOT_CHANGED 302 | ); 303 | 304 | lbsSB_destroy(&sb); 305 | check_alloc(DUMMY_PTR, 256); 306 | }) 307 | 308 | /******************************************************************************/ 309 | 310 | TEST (test_writechar, 311 | { 312 | luabins_SaveBuffer sb; 313 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 314 | 315 | lbsSB_writechar(&sb, 'A'); 316 | check_buffer(&sb, "A", 1, DUMMY_PTR, 0); 317 | 318 | lbsSB_destroy(&sb); 319 | check_alloc(DUMMY_PTR, 256); 320 | }) 321 | 322 | TEST (test_writechar_zero, 323 | { 324 | luabins_SaveBuffer sb; 325 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 326 | 327 | lbsSB_writechar(&sb, '\0'); 328 | check_buffer(&sb, "\0", 1, DUMMY_PTR, 0); 329 | 330 | lbsSB_destroy(&sb); 331 | check_alloc(DUMMY_PTR, 256); 332 | }) 333 | 334 | TEST (test_write_writechar_smallsiz, 335 | { 336 | luabins_SaveBuffer sb; 337 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 338 | 339 | lbsSB_write(&sb, (unsigned char*)"01234567", 8); 340 | check_buffer(&sb, "01234567", 8, DUMMY_PTR, 0); 341 | 342 | lbsSB_writechar(&sb, 'A'); 343 | check_buffer(&sb, "01234567A", 8 + 1, NOT_CHANGED_PTR, NOT_CHANGED); 344 | 345 | lbsSB_destroy(&sb); 346 | check_alloc(DUMMY_PTR, 256); 347 | }) 348 | 349 | /******************************************************************************/ 350 | 351 | TEST (test_overwrite_empty, 352 | { 353 | luabins_SaveBuffer sb; 354 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 355 | 356 | lbsSB_overwrite(&sb, 0, (unsigned char*)"42", 3); 357 | check_buffer(&sb, "42", 3, DUMMY_PTR, 0); 358 | 359 | lbsSB_destroy(&sb); 360 | check_alloc(DUMMY_PTR, 256); 361 | }) 362 | 363 | TEST (test_overwrite_inplace, 364 | { 365 | luabins_SaveBuffer sb; 366 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 367 | 368 | lbsSB_write(&sb, (unsigned char*)"ABCD", 4); 369 | check_buffer(&sb, "ABCD", 4, DUMMY_PTR, 0); 370 | 371 | lbsSB_overwrite(&sb, 1, (unsigned char*)"42", 2); 372 | check_buffer(&sb, "A42D", 4, NOT_CHANGED_PTR, NOT_CHANGED); 373 | 374 | lbsSB_destroy(&sb); 375 | check_alloc(DUMMY_PTR, 256); 376 | }) 377 | 378 | TEST (test_overwrite_overflow, 379 | { 380 | luabins_SaveBuffer sb; 381 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 382 | 383 | lbsSB_write(&sb, (unsigned char*)"ABCD", 4); 384 | check_buffer(&sb, "ABCD", 4, DUMMY_PTR, 0); 385 | 386 | lbsSB_overwrite(&sb, 3, (unsigned char*)"42", 2); 387 | check_buffer(&sb, "ABC42", 5, NOT_CHANGED_PTR, NOT_CHANGED); 388 | 389 | lbsSB_destroy(&sb); 390 | check_alloc(DUMMY_PTR, 256); 391 | }) 392 | 393 | TEST (test_overwrite_overflow_grows, 394 | { 395 | luabins_SaveBuffer sb; 396 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 397 | 398 | lbsSB_write(&sb, (unsigned char*)"012345", 6); 399 | check_buffer(&sb, "012345", 6, DUMMY_PTR, 0); 400 | 401 | lbsSB_overwrite(&sb, 4, (unsigned char*)"ABCDEF", 6); 402 | check_buffer(&sb, "0123ABCDEF", 10, NOT_CHANGED_PTR, NOT_CHANGED); 403 | 404 | lbsSB_destroy(&sb); 405 | check_alloc(DUMMY_PTR, 256); 406 | }) 407 | 408 | TEST (test_overwrite_large_offset_appends, 409 | { 410 | luabins_SaveBuffer sb; 411 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 412 | 413 | lbsSB_write(&sb, (unsigned char*)"012345", 6); 414 | check_buffer(&sb, "012345", 6, DUMMY_PTR, 0); 415 | 416 | lbsSB_overwrite(&sb, 100, (unsigned char*)"ABCDEF", 6); 417 | check_buffer(&sb, "012345ABCDEF", 12, NOT_CHANGED_PTR, NOT_CHANGED); 418 | 419 | lbsSB_destroy(&sb); 420 | check_alloc(DUMMY_PTR, 256); 421 | }) 422 | 423 | /******************************************************************************/ 424 | 425 | TEST (test_overwritechar_empty_buffer, 426 | { 427 | luabins_SaveBuffer sb; 428 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 429 | 430 | lbsSB_overwritechar(&sb, 0, 'A'); 431 | check_buffer(&sb, "A", 1, DUMMY_PTR, 0); 432 | 433 | lbsSB_destroy(&sb); 434 | check_alloc(DUMMY_PTR, 256); 435 | }) 436 | 437 | TEST (test_overwritechar_inplace, 438 | { 439 | luabins_SaveBuffer sb; 440 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 441 | 442 | lbsSB_write(&sb, (unsigned char*)"ABCD", 4); 443 | check_buffer(&sb, "ABCD", 4, DUMMY_PTR, 0); 444 | 445 | lbsSB_overwritechar(&sb, 1, '!'); 446 | check_buffer(&sb, "A!CD", 4, NOT_CHANGED_PTR, NOT_CHANGED); 447 | 448 | lbsSB_destroy(&sb); 449 | check_alloc(DUMMY_PTR, 256); 450 | }) 451 | 452 | TEST (test_overwritechar_large_offset_appends, 453 | { 454 | luabins_SaveBuffer sb; 455 | lbsSB_init(&sb, dummy_alloc, DUMMY_PTR); 456 | 457 | lbsSB_write(&sb, (unsigned char*)"01234567", 8); 458 | check_buffer(&sb, "01234567", 8, DUMMY_PTR, 0); 459 | 460 | lbsSB_overwritechar(&sb, 100, '!'); 461 | check_buffer(&sb, "01234567!", 9, NOT_CHANGED_PTR, NOT_CHANGED); 462 | 463 | lbsSB_destroy(&sb); 464 | check_alloc(DUMMY_PTR, 256); 465 | }) 466 | 467 | /******************************************************************************/ 468 | 469 | void test_savebuffer() 470 | { 471 | init_globals(); 472 | 473 | test_init_destroy(); 474 | test_grow_zero(); 475 | test_grow_bufsiz(); 476 | test_grow_one(); 477 | test_grow_one_grow_one_noop(); 478 | test_grow_one_grow_bufsiz_noop(); 479 | test_grow_one_grow_bufsiz_one(); 480 | 481 | test_write_empty(); 482 | test_write(); 483 | test_write_embedded_zero(); 484 | test_write_write_smallsiz(); 485 | 486 | test_writechar(); 487 | test_writechar_zero(); 488 | test_write_writechar_smallsiz(); 489 | 490 | test_overwrite_empty(); 491 | test_overwrite_inplace(); 492 | test_overwrite_overflow(); 493 | test_overwrite_overflow_grows(); 494 | test_overwrite_large_offset_appends(); 495 | 496 | test_overwritechar_empty_buffer(); 497 | test_overwritechar_inplace(); 498 | test_overwritechar_large_offset_appends(); 499 | } 500 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## CONFIGURATION ############################################################## 2 | 3 | ifeq ($(shell uname),Darwin) 4 | LUA_DIR := /usr/local 5 | LUA_LIBDIR := $(LUA_DIR)/lib/lua/5.1 6 | LUA_INCDIR := $(LUA_DIR)/include 7 | LUALIB := lua 8 | else 9 | # Assuming Ubuntu 10 | LUA_LIBDIR := /usr/lib 11 | LUA_INCDIR := /usr/include/lua5.1 12 | LUALIB := lua5.1 13 | endif 14 | 15 | PROJECTNAME := luabins 16 | 17 | SONAME := $(PROJECTNAME).so 18 | ANAME := lib$(PROJECTNAME).a 19 | HNAME := $(PROJECTNAME).h 20 | TESTNAME := $(PROJECTNAME)-test 21 | TESTLUA := test.lua 22 | 23 | LUA := lua 24 | CP := cp 25 | RM := rm -f 26 | RMDIR := rm -df 27 | MKDIR := mkdir -p 28 | CC := gcc 29 | LD := gcc 30 | AR := ar rcu 31 | RANLIB := ranlib 32 | ECHO := @echo 33 | TOUCH := touch 34 | 35 | # Needed for tests only 36 | CXX := g++ 37 | LDXX := g++ 38 | 39 | OBJDIR := ./obj 40 | TMPDIR := ./tmp 41 | INCDIR := ./include 42 | LIBDIR := ./lib 43 | 44 | HFILE := $(INCDIR)/$(HNAME) 45 | 46 | CFLAGS += -O2 -Wall -I$(LUA_INCDIR) 47 | LDFLAGS += -L$(LUA_LIBDIR) 48 | 49 | # Tested on OS X and Ubuntu 50 | SOFLAGS := 51 | ifeq ($(shell uname),Darwin) 52 | SOFLAGS += -dynamiclib -undefined dynamic_lookup 53 | else 54 | CFLAGS += -fPIC 55 | SOFLAGS += -shared 56 | LDFLAGS += -ldl 57 | RMDIR := rm -rf 58 | endif 59 | 60 | CFLAGS += $(MYCFLAGS) 61 | LDFLAGS += $(MYLDFLAGS) 62 | 63 | ## MAIN TARGETS ############################################################### 64 | 65 | all: $(LIBDIR)/$(SONAME) $(LIBDIR)/$(ANAME) $(HFILE) 66 | 67 | clean: cleanlibs cleantest 68 | $(RM) $(HFILE) 69 | 70 | install: $(LIBDIR)/$(SONAME) 71 | # Note header and static library are not copied anywhere 72 | $(CP) $(LIBDIR)/$(SONAME) $(LUA_LIBDIR)/$(SONAME) 73 | 74 | $(HFILE): 75 | $(CP) src/$(HNAME) $(HFILE) 76 | 77 | ## GENERATED RELEASE TARGETS ################################################## 78 | 79 | cleanlibs: cleanobjects 80 | $(RM) $(LIBDIR)/$(SONAME) 81 | $(RM) $(LIBDIR)/$(ANAME) 82 | 83 | $(LIBDIR)/$(SONAME): $(OBJDIR)/fwrite.o $(OBJDIR)/load.o $(OBJDIR)/luabins.o $(OBJDIR)/luainternals.o $(OBJDIR)/lualess.o $(OBJDIR)/save.o $(OBJDIR)/savebuffer.o $(OBJDIR)/write.o 84 | $(MKDIR) $(LIBDIR) 85 | $(LD) -o $@ $(OBJDIR)/fwrite.o $(OBJDIR)/load.o $(OBJDIR)/luabins.o $(OBJDIR)/luainternals.o $(OBJDIR)/lualess.o $(OBJDIR)/save.o $(OBJDIR)/savebuffer.o $(OBJDIR)/write.o $(LDFLAGS) $(SOFLAGS) 86 | 87 | $(LIBDIR)/$(ANAME): $(OBJDIR)/fwrite.o $(OBJDIR)/load.o $(OBJDIR)/luabins.o $(OBJDIR)/luainternals.o $(OBJDIR)/lualess.o $(OBJDIR)/save.o $(OBJDIR)/savebuffer.o $(OBJDIR)/write.o 88 | $(MKDIR) $(LIBDIR) 89 | $(AR) $@ $(OBJDIR)/fwrite.o $(OBJDIR)/load.o $(OBJDIR)/luabins.o $(OBJDIR)/luainternals.o $(OBJDIR)/lualess.o $(OBJDIR)/save.o $(OBJDIR)/savebuffer.o $(OBJDIR)/write.o 90 | $(RANLIB) $@ 91 | 92 | # objects: 93 | 94 | cleanobjects: 95 | $(RM) $(OBJDIR)/fwrite.o $(OBJDIR)/load.o $(OBJDIR)/luabins.o $(OBJDIR)/luainternals.o $(OBJDIR)/lualess.o $(OBJDIR)/save.o $(OBJDIR)/savebuffer.o $(OBJDIR)/write.o 96 | 97 | $(OBJDIR)/fwrite.o: src/fwrite.c src/luaheaders.h src/fwrite.h \ 98 | src/saveload.h 99 | $(CC) $(CFLAGS) -o $@ -c src/fwrite.c 100 | 101 | $(OBJDIR)/load.o: src/load.c src/luaheaders.h src/luabins.h \ 102 | src/saveload.h src/luainternals.h 103 | $(CC) $(CFLAGS) -o $@ -c src/load.c 104 | 105 | $(OBJDIR)/luabins.o: src/luabins.c src/luaheaders.h src/luabins.h 106 | $(CC) $(CFLAGS) -o $@ -c src/luabins.c 107 | 108 | $(OBJDIR)/luainternals.o: src/luainternals.c src/luainternals.h 109 | $(CC) $(CFLAGS) -o $@ -c src/luainternals.c 110 | 111 | $(OBJDIR)/lualess.o: src/lualess.c 112 | $(CC) $(CFLAGS) -o $@ -c src/lualess.c 113 | 114 | $(OBJDIR)/save.o: src/save.c src/luaheaders.h src/luabins.h \ 115 | src/saveload.h src/savebuffer.h src/write.h 116 | $(CC) $(CFLAGS) -o $@ -c src/save.c 117 | 118 | $(OBJDIR)/savebuffer.o: src/savebuffer.c src/luaheaders.h \ 119 | src/saveload.h src/savebuffer.h 120 | $(CC) $(CFLAGS) -o $@ -c src/savebuffer.c 121 | 122 | $(OBJDIR)/write.o: src/write.c src/luaheaders.h src/write.h \ 123 | src/saveload.h src/savebuffer.h 124 | $(CC) $(CFLAGS) -o $@ -c src/write.c 125 | 126 | ## TEST TARGETS ############################################################### 127 | 128 | test: testc89 testc99 testc++98 129 | $(ECHO) "===== TESTS PASSED =====" 130 | 131 | resettest: resettestc89 resettestc99 resettestc++98 132 | 133 | cleantest: cleantestc89 cleantestc99 cleantestc++98 134 | 135 | ## GENERATED TEST TARGETS ##################################################### 136 | 137 | ## ----- Begin c89 ----- 138 | 139 | testc89: lua-testsc89 c-testsc89 140 | 141 | lua-testsc89: $(TMPDIR)/c89/.luatestspassed 142 | 143 | c-testsc89: $(TMPDIR)/c89/.ctestspassed 144 | 145 | $(TMPDIR)/c89/.luatestspassed: $(TMPDIR)/c89/$(SONAME) test/$(TESTLUA) 146 | $(ECHO) "===== Running Lua tests for c89 =====" 147 | @$(LUA) \ 148 | -e "package.cpath='$(TMPDIR)/c89/$(SONAME);'..package.cpath" \ 149 | test/$(TESTLUA) 150 | $(TOUCH) $(TMPDIR)/c89/.luatestspassed 151 | $(ECHO) "===== Lua tests for c89 PASSED =====" 152 | 153 | $(TMPDIR)/c89/.ctestspassed: $(TMPDIR)/c89/$(TESTNAME) test/$(TESTLUA) 154 | $(ECHO) "===== Running C tests for c89 =====" 155 | $(TMPDIR)/c89/$(TESTNAME) 156 | $(TOUCH) $(TMPDIR)/c89/.ctestspassed 157 | $(ECHO) "===== C tests for c89 PASSED =====" 158 | 159 | $(TMPDIR)/c89/$(TESTNAME): $(OBJDIR)/c89-test.o $(OBJDIR)/c89-test_api.o $(OBJDIR)/c89-test_fwrite_api.o $(OBJDIR)/c89-test_savebuffer.o $(OBJDIR)/c89-test_write_api.o $(OBJDIR)/c89-util.o $(TMPDIR)/c89/$(ANAME) 160 | $(MKDIR) $(TMPDIR)/c89 161 | $(LD) -o $@ $(OBJDIR)/c89-test.o $(OBJDIR)/c89-test_api.o $(OBJDIR)/c89-test_fwrite_api.o $(OBJDIR)/c89-test_savebuffer.o $(OBJDIR)/c89-test_write_api.o $(OBJDIR)/c89-util.o $(LDFLAGS) -lm -l$(LUALIB) -l$(PROJECTNAME) -L$(TMPDIR)/c89 162 | 163 | resettestc89: 164 | $(RM) $(TMPDIR)/c89/.luatestspassed 165 | $(RM) $(TMPDIR)/c89/.ctestspassed 166 | 167 | cleantestc89: cleanlibsc89 resettestc89 \ 168 | cleantestobjectsc89 169 | $(RM) $(TMPDIR)/c89/$(TESTNAME) 170 | $(RMDIR) $(TMPDIR)/c89 171 | 172 | # testobjectsc89: 173 | 174 | cleantestobjectsc89: 175 | $(RM) $(OBJDIR)/c89-test.o $(OBJDIR)/c89-test_api.o $(OBJDIR)/c89-test_fwrite_api.o $(OBJDIR)/c89-test_savebuffer.o $(OBJDIR)/c89-test_write_api.o $(OBJDIR)/c89-util.o 176 | 177 | $(OBJDIR)/c89-test.o: test/test.c test/test.h 178 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -Isrc/ -o $@ -c test/test.c 179 | 180 | $(OBJDIR)/c89-test_api.o: test/test_api.c src/luabins.h 181 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -Isrc/ -o $@ -c test/test_api.c 182 | 183 | $(OBJDIR)/c89-test_fwrite_api.o: test/test_fwrite_api.c src/lualess.h \ 184 | src/fwrite.h src/saveload.h test/test.h test/util.h \ 185 | test/write_tests.inc 186 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -Isrc/ -o $@ -c test/test_fwrite_api.c 187 | 188 | $(OBJDIR)/c89-test_savebuffer.o: test/test_savebuffer.c src/lualess.h \ 189 | src/savebuffer.h test/test.h 190 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -Isrc/ -o $@ -c test/test_savebuffer.c 191 | 192 | $(OBJDIR)/c89-test_write_api.o: test/test_write_api.c src/lualess.h \ 193 | src/write.h src/saveload.h src/savebuffer.h test/test.h test/util.h \ 194 | test/write_tests.inc 195 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -Isrc/ -o $@ -c test/test_write_api.c 196 | 197 | $(OBJDIR)/c89-util.o: test/util.c test/util.h 198 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -Isrc/ -o $@ -c test/util.c 199 | 200 | cleanlibsc89: cleanobjectsc89 201 | $(RM) $(TMPDIR)/c89/$(SONAME) 202 | $(RM) $(TMPDIR)/c89/$(ANAME) 203 | 204 | $(TMPDIR)/c89/$(SONAME): $(OBJDIR)/c89-fwrite.o $(OBJDIR)/c89-load.o $(OBJDIR)/c89-luabins.o $(OBJDIR)/c89-luainternals.o $(OBJDIR)/c89-lualess.o $(OBJDIR)/c89-save.o $(OBJDIR)/c89-savebuffer.o $(OBJDIR)/c89-write.o 205 | $(MKDIR) $(TMPDIR)/c89 206 | $(LD) -o $@ $(OBJDIR)/c89-fwrite.o $(OBJDIR)/c89-load.o $(OBJDIR)/c89-luabins.o $(OBJDIR)/c89-luainternals.o $(OBJDIR)/c89-lualess.o $(OBJDIR)/c89-save.o $(OBJDIR)/c89-savebuffer.o $(OBJDIR)/c89-write.o $(LDFLAGS) $(SOFLAGS) 207 | 208 | $(TMPDIR)/c89/$(ANAME): $(OBJDIR)/c89-fwrite.o $(OBJDIR)/c89-load.o $(OBJDIR)/c89-luabins.o $(OBJDIR)/c89-luainternals.o $(OBJDIR)/c89-lualess.o $(OBJDIR)/c89-save.o $(OBJDIR)/c89-savebuffer.o $(OBJDIR)/c89-write.o 209 | $(MKDIR) $(TMPDIR)/c89 210 | $(AR) $@ $(OBJDIR)/c89-fwrite.o $(OBJDIR)/c89-load.o $(OBJDIR)/c89-luabins.o $(OBJDIR)/c89-luainternals.o $(OBJDIR)/c89-lualess.o $(OBJDIR)/c89-save.o $(OBJDIR)/c89-savebuffer.o $(OBJDIR)/c89-write.o 211 | $(RANLIB) $@ 212 | 213 | # objectsc89: 214 | 215 | cleanobjectsc89: 216 | $(RM) $(OBJDIR)/c89-fwrite.o $(OBJDIR)/c89-load.o $(OBJDIR)/c89-luabins.o $(OBJDIR)/c89-luainternals.o $(OBJDIR)/c89-lualess.o $(OBJDIR)/c89-save.o $(OBJDIR)/c89-savebuffer.o $(OBJDIR)/c89-write.o 217 | 218 | $(OBJDIR)/c89-fwrite.o: src/fwrite.c src/luaheaders.h src/fwrite.h \ 219 | src/saveload.h 220 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/fwrite.c 221 | 222 | $(OBJDIR)/c89-load.o: src/load.c src/luaheaders.h src/luabins.h \ 223 | src/saveload.h src/luainternals.h 224 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/load.c 225 | 226 | $(OBJDIR)/c89-luabins.o: src/luabins.c src/luaheaders.h src/luabins.h 227 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/luabins.c 228 | 229 | $(OBJDIR)/c89-luainternals.o: src/luainternals.c src/luainternals.h 230 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/luainternals.c 231 | 232 | $(OBJDIR)/c89-lualess.o: src/lualess.c 233 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/lualess.c 234 | 235 | $(OBJDIR)/c89-save.o: src/save.c src/luaheaders.h src/luabins.h \ 236 | src/saveload.h src/savebuffer.h src/write.h 237 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/save.c 238 | 239 | $(OBJDIR)/c89-savebuffer.o: src/savebuffer.c src/luaheaders.h \ 240 | src/saveload.h src/savebuffer.h 241 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/savebuffer.c 242 | 243 | $(OBJDIR)/c89-write.o: src/write.c src/luaheaders.h src/write.h \ 244 | src/saveload.h src/savebuffer.h 245 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c89 -o $@ -c src/write.c 246 | 247 | ## ----- Begin c99 ----- 248 | 249 | testc99: lua-testsc99 c-testsc99 250 | 251 | lua-testsc99: $(TMPDIR)/c99/.luatestspassed 252 | 253 | c-testsc99: $(TMPDIR)/c99/.ctestspassed 254 | 255 | $(TMPDIR)/c99/.luatestspassed: $(TMPDIR)/c99/$(SONAME) test/$(TESTLUA) 256 | $(ECHO) "===== Running Lua tests for c99 =====" 257 | @$(LUA) \ 258 | -e "package.cpath='$(TMPDIR)/c99/$(SONAME);'..package.cpath" \ 259 | test/$(TESTLUA) 260 | $(TOUCH) $(TMPDIR)/c99/.luatestspassed 261 | $(ECHO) "===== Lua tests for c99 PASSED =====" 262 | 263 | $(TMPDIR)/c99/.ctestspassed: $(TMPDIR)/c99/$(TESTNAME) test/$(TESTLUA) 264 | $(ECHO) "===== Running C tests for c99 =====" 265 | $(TMPDIR)/c99/$(TESTNAME) 266 | $(TOUCH) $(TMPDIR)/c99/.ctestspassed 267 | $(ECHO) "===== C tests for c99 PASSED =====" 268 | 269 | $(TMPDIR)/c99/$(TESTNAME): $(OBJDIR)/c99-test.o $(OBJDIR)/c99-test_api.o $(OBJDIR)/c99-test_fwrite_api.o $(OBJDIR)/c99-test_savebuffer.o $(OBJDIR)/c99-test_write_api.o $(OBJDIR)/c99-util.o $(TMPDIR)/c99/$(ANAME) 270 | $(MKDIR) $(TMPDIR)/c99 271 | $(LD) -o $@ $(OBJDIR)/c99-test.o $(OBJDIR)/c99-test_api.o $(OBJDIR)/c99-test_fwrite_api.o $(OBJDIR)/c99-test_savebuffer.o $(OBJDIR)/c99-test_write_api.o $(OBJDIR)/c99-util.o $(LDFLAGS) -lm -l$(LUALIB) -l$(PROJECTNAME) -L$(TMPDIR)/c99 272 | 273 | resettestc99: 274 | $(RM) $(TMPDIR)/c99/.luatestspassed 275 | $(RM) $(TMPDIR)/c99/.ctestspassed 276 | 277 | cleantestc99: cleanlibsc99 resettestc99 \ 278 | cleantestobjectsc99 279 | $(RM) $(TMPDIR)/c99/$(TESTNAME) 280 | $(RMDIR) $(TMPDIR)/c99 281 | 282 | # testobjectsc99: 283 | 284 | cleantestobjectsc99: 285 | $(RM) $(OBJDIR)/c99-test.o $(OBJDIR)/c99-test_api.o $(OBJDIR)/c99-test_fwrite_api.o $(OBJDIR)/c99-test_savebuffer.o $(OBJDIR)/c99-test_write_api.o $(OBJDIR)/c99-util.o 286 | 287 | $(OBJDIR)/c99-test.o: test/test.c test/test.h 288 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -Isrc/ -o $@ -c test/test.c 289 | 290 | $(OBJDIR)/c99-test_api.o: test/test_api.c src/luabins.h 291 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -Isrc/ -o $@ -c test/test_api.c 292 | 293 | $(OBJDIR)/c99-test_fwrite_api.o: test/test_fwrite_api.c src/lualess.h \ 294 | src/fwrite.h src/saveload.h test/test.h test/util.h \ 295 | test/write_tests.inc 296 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -Isrc/ -o $@ -c test/test_fwrite_api.c 297 | 298 | $(OBJDIR)/c99-test_savebuffer.o: test/test_savebuffer.c src/lualess.h \ 299 | src/savebuffer.h test/test.h 300 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -Isrc/ -o $@ -c test/test_savebuffer.c 301 | 302 | $(OBJDIR)/c99-test_write_api.o: test/test_write_api.c src/lualess.h \ 303 | src/write.h src/saveload.h src/savebuffer.h test/test.h test/util.h \ 304 | test/write_tests.inc 305 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -Isrc/ -o $@ -c test/test_write_api.c 306 | 307 | $(OBJDIR)/c99-util.o: test/util.c test/util.h 308 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -Isrc/ -o $@ -c test/util.c 309 | 310 | cleanlibsc99: cleanobjectsc99 311 | $(RM) $(TMPDIR)/c99/$(SONAME) 312 | $(RM) $(TMPDIR)/c99/$(ANAME) 313 | 314 | $(TMPDIR)/c99/$(SONAME): $(OBJDIR)/c99-fwrite.o $(OBJDIR)/c99-load.o $(OBJDIR)/c99-luabins.o $(OBJDIR)/c99-luainternals.o $(OBJDIR)/c99-lualess.o $(OBJDIR)/c99-save.o $(OBJDIR)/c99-savebuffer.o $(OBJDIR)/c99-write.o 315 | $(MKDIR) $(TMPDIR)/c99 316 | $(LD) -o $@ $(OBJDIR)/c99-fwrite.o $(OBJDIR)/c99-load.o $(OBJDIR)/c99-luabins.o $(OBJDIR)/c99-luainternals.o $(OBJDIR)/c99-lualess.o $(OBJDIR)/c99-save.o $(OBJDIR)/c99-savebuffer.o $(OBJDIR)/c99-write.o $(LDFLAGS) $(SOFLAGS) 317 | 318 | $(TMPDIR)/c99/$(ANAME): $(OBJDIR)/c99-fwrite.o $(OBJDIR)/c99-load.o $(OBJDIR)/c99-luabins.o $(OBJDIR)/c99-luainternals.o $(OBJDIR)/c99-lualess.o $(OBJDIR)/c99-save.o $(OBJDIR)/c99-savebuffer.o $(OBJDIR)/c99-write.o 319 | $(MKDIR) $(TMPDIR)/c99 320 | $(AR) $@ $(OBJDIR)/c99-fwrite.o $(OBJDIR)/c99-load.o $(OBJDIR)/c99-luabins.o $(OBJDIR)/c99-luainternals.o $(OBJDIR)/c99-lualess.o $(OBJDIR)/c99-save.o $(OBJDIR)/c99-savebuffer.o $(OBJDIR)/c99-write.o 321 | $(RANLIB) $@ 322 | 323 | # objectsc99: 324 | 325 | cleanobjectsc99: 326 | $(RM) $(OBJDIR)/c99-fwrite.o $(OBJDIR)/c99-load.o $(OBJDIR)/c99-luabins.o $(OBJDIR)/c99-luainternals.o $(OBJDIR)/c99-lualess.o $(OBJDIR)/c99-save.o $(OBJDIR)/c99-savebuffer.o $(OBJDIR)/c99-write.o 327 | 328 | $(OBJDIR)/c99-fwrite.o: src/fwrite.c src/luaheaders.h src/fwrite.h \ 329 | src/saveload.h 330 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/fwrite.c 331 | 332 | $(OBJDIR)/c99-load.o: src/load.c src/luaheaders.h src/luabins.h \ 333 | src/saveload.h src/luainternals.h 334 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/load.c 335 | 336 | $(OBJDIR)/c99-luabins.o: src/luabins.c src/luaheaders.h src/luabins.h 337 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/luabins.c 338 | 339 | $(OBJDIR)/c99-luainternals.o: src/luainternals.c src/luainternals.h 340 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/luainternals.c 341 | 342 | $(OBJDIR)/c99-lualess.o: src/lualess.c 343 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/lualess.c 344 | 345 | $(OBJDIR)/c99-save.o: src/save.c src/luaheaders.h src/luabins.h \ 346 | src/saveload.h src/savebuffer.h src/write.h 347 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/save.c 348 | 349 | $(OBJDIR)/c99-savebuffer.o: src/savebuffer.c src/luaheaders.h \ 350 | src/saveload.h src/savebuffer.h 351 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/savebuffer.c 352 | 353 | $(OBJDIR)/c99-write.o: src/write.c src/luaheaders.h src/write.h \ 354 | src/saveload.h src/savebuffer.h 355 | $(CC) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c -std=c99 -o $@ -c src/write.c 356 | 357 | ## ----- Begin c++98 ----- 358 | 359 | testc++98: lua-testsc++98 c-testsc++98 360 | 361 | lua-testsc++98: $(TMPDIR)/c++98/.luatestspassed 362 | 363 | c-testsc++98: $(TMPDIR)/c++98/.ctestspassed 364 | 365 | $(TMPDIR)/c++98/.luatestspassed: $(TMPDIR)/c++98/$(SONAME) test/$(TESTLUA) 366 | $(ECHO) "===== Running Lua tests for c++98 =====" 367 | @$(LUA) \ 368 | -e "package.cpath='$(TMPDIR)/c++98/$(SONAME);'..package.cpath" \ 369 | test/$(TESTLUA) 370 | $(TOUCH) $(TMPDIR)/c++98/.luatestspassed 371 | $(ECHO) "===== Lua tests for c++98 PASSED =====" 372 | 373 | $(TMPDIR)/c++98/.ctestspassed: $(TMPDIR)/c++98/$(TESTNAME) test/$(TESTLUA) 374 | $(ECHO) "===== Running C tests for c++98 =====" 375 | $(TMPDIR)/c++98/$(TESTNAME) 376 | $(TOUCH) $(TMPDIR)/c++98/.ctestspassed 377 | $(ECHO) "===== C tests for c++98 PASSED =====" 378 | 379 | $(TMPDIR)/c++98/$(TESTNAME): $(OBJDIR)/c++98-test.o $(OBJDIR)/c++98-test_api.o $(OBJDIR)/c++98-test_fwrite_api.o $(OBJDIR)/c++98-test_savebuffer.o $(OBJDIR)/c++98-test_write_api.o $(OBJDIR)/c++98-util.o $(TMPDIR)/c++98/$(ANAME) 380 | $(MKDIR) $(TMPDIR)/c++98 381 | $(LDXX) -o $@ $(OBJDIR)/c++98-test.o $(OBJDIR)/c++98-test_api.o $(OBJDIR)/c++98-test_fwrite_api.o $(OBJDIR)/c++98-test_savebuffer.o $(OBJDIR)/c++98-test_write_api.o $(OBJDIR)/c++98-util.o $(LDFLAGS) -lm -l$(LUALIB) -l$(PROJECTNAME) -L$(TMPDIR)/c++98 382 | 383 | resettestc++98: 384 | $(RM) $(TMPDIR)/c++98/.luatestspassed 385 | $(RM) $(TMPDIR)/c++98/.ctestspassed 386 | 387 | cleantestc++98: cleanlibsc++98 resettestc++98 \ 388 | cleantestobjectsc++98 389 | $(RM) $(TMPDIR)/c++98/$(TESTNAME) 390 | $(RMDIR) $(TMPDIR)/c++98 391 | 392 | # testobjectsc++98: 393 | 394 | cleantestobjectsc++98: 395 | $(RM) $(OBJDIR)/c++98-test.o $(OBJDIR)/c++98-test_api.o $(OBJDIR)/c++98-test_fwrite_api.o $(OBJDIR)/c++98-test_savebuffer.o $(OBJDIR)/c++98-test_write_api.o $(OBJDIR)/c++98-util.o 396 | 397 | $(OBJDIR)/c++98-test.o: test/test.c test/test.h 398 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -Isrc/ -o $@ -c test/test.c 399 | 400 | $(OBJDIR)/c++98-test_api.o: test/test_api.c src/luabins.h 401 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -Isrc/ -o $@ -c test/test_api.c 402 | 403 | $(OBJDIR)/c++98-test_fwrite_api.o: test/test_fwrite_api.c src/lualess.h \ 404 | src/fwrite.h src/saveload.h test/test.h test/util.h \ 405 | test/write_tests.inc 406 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -Isrc/ -o $@ -c test/test_fwrite_api.c 407 | 408 | $(OBJDIR)/c++98-test_savebuffer.o: test/test_savebuffer.c src/lualess.h \ 409 | src/savebuffer.h test/test.h 410 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -Isrc/ -o $@ -c test/test_savebuffer.c 411 | 412 | $(OBJDIR)/c++98-test_write_api.o: test/test_write_api.c src/lualess.h \ 413 | src/write.h src/saveload.h src/savebuffer.h test/test.h test/util.h \ 414 | test/write_tests.inc 415 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -Isrc/ -o $@ -c test/test_write_api.c 416 | 417 | $(OBJDIR)/c++98-util.o: test/util.c test/util.h 418 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -Isrc/ -o $@ -c test/util.c 419 | 420 | cleanlibsc++98: cleanobjectsc++98 421 | $(RM) $(TMPDIR)/c++98/$(SONAME) 422 | $(RM) $(TMPDIR)/c++98/$(ANAME) 423 | 424 | $(TMPDIR)/c++98/$(SONAME): $(OBJDIR)/c++98-fwrite.o $(OBJDIR)/c++98-load.o $(OBJDIR)/c++98-luabins.o $(OBJDIR)/c++98-luainternals.o $(OBJDIR)/c++98-lualess.o $(OBJDIR)/c++98-save.o $(OBJDIR)/c++98-savebuffer.o $(OBJDIR)/c++98-write.o 425 | $(MKDIR) $(TMPDIR)/c++98 426 | $(LDXX) -o $@ $(OBJDIR)/c++98-fwrite.o $(OBJDIR)/c++98-load.o $(OBJDIR)/c++98-luabins.o $(OBJDIR)/c++98-luainternals.o $(OBJDIR)/c++98-lualess.o $(OBJDIR)/c++98-save.o $(OBJDIR)/c++98-savebuffer.o $(OBJDIR)/c++98-write.o $(LDFLAGS) $(SOFLAGS) 427 | 428 | $(TMPDIR)/c++98/$(ANAME): $(OBJDIR)/c++98-fwrite.o $(OBJDIR)/c++98-load.o $(OBJDIR)/c++98-luabins.o $(OBJDIR)/c++98-luainternals.o $(OBJDIR)/c++98-lualess.o $(OBJDIR)/c++98-save.o $(OBJDIR)/c++98-savebuffer.o $(OBJDIR)/c++98-write.o 429 | $(MKDIR) $(TMPDIR)/c++98 430 | $(AR) $@ $(OBJDIR)/c++98-fwrite.o $(OBJDIR)/c++98-load.o $(OBJDIR)/c++98-luabins.o $(OBJDIR)/c++98-luainternals.o $(OBJDIR)/c++98-lualess.o $(OBJDIR)/c++98-save.o $(OBJDIR)/c++98-savebuffer.o $(OBJDIR)/c++98-write.o 431 | $(RANLIB) $@ 432 | 433 | # objectsc++98: 434 | 435 | cleanobjectsc++98: 436 | $(RM) $(OBJDIR)/c++98-fwrite.o $(OBJDIR)/c++98-load.o $(OBJDIR)/c++98-luabins.o $(OBJDIR)/c++98-luainternals.o $(OBJDIR)/c++98-lualess.o $(OBJDIR)/c++98-save.o $(OBJDIR)/c++98-savebuffer.o $(OBJDIR)/c++98-write.o 437 | 438 | $(OBJDIR)/c++98-fwrite.o: src/fwrite.c src/luaheaders.h src/fwrite.h \ 439 | src/saveload.h 440 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/fwrite.c 441 | 442 | $(OBJDIR)/c++98-load.o: src/load.c src/luaheaders.h src/luabins.h \ 443 | src/saveload.h src/luainternals.h 444 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/load.c 445 | 446 | $(OBJDIR)/c++98-luabins.o: src/luabins.c src/luaheaders.h src/luabins.h 447 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/luabins.c 448 | 449 | $(OBJDIR)/c++98-luainternals.o: src/luainternals.c src/luainternals.h 450 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/luainternals.c 451 | 452 | $(OBJDIR)/c++98-lualess.o: src/lualess.c 453 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/lualess.c 454 | 455 | $(OBJDIR)/c++98-save.o: src/save.c src/luaheaders.h src/luabins.h \ 456 | src/saveload.h src/savebuffer.h src/write.h 457 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/save.c 458 | 459 | $(OBJDIR)/c++98-savebuffer.o: src/savebuffer.c src/luaheaders.h \ 460 | src/saveload.h src/savebuffer.h 461 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/savebuffer.c 462 | 463 | $(OBJDIR)/c++98-write.o: src/write.c src/luaheaders.h src/write.h \ 464 | src/saveload.h src/savebuffer.h 465 | $(CXX) $(CFLAGS) -Werror -Wall -Wextra -pedantic -x c++ -std=c++98 -o $@ -c src/write.c 466 | 467 | ## END OF GENERATED TARGETS ################################################### 468 | 469 | .PHONY: all clean install cleanlibs cleanobjects test resettest cleantest testc89 lua-testsc89 c-testsc89 resettestc89 cleantestc89 cleantestobjectsc89 cleanlibsc89 cleanobjectsc89 testc99 lua-testsc99 c-testsc99 resettestc99 cleantestc99 cleantestobjectsc99 cleanlibsc99 cleanobjectsc99 testc++98 lua-testsc++98 c-testsc++98 resettestc++98 cleantestc++98 cleantestobjectsc++98 cleanlibsc++98 cleanobjectsc++98 470 | -------------------------------------------------------------------------------- /test/test.lua: -------------------------------------------------------------------------------- 1 | -- ---------------------------------------------------------------------------- 2 | -- test.lua 3 | -- Luabins test suite 4 | -- See copyright notice in luabins.h 5 | -- ---------------------------------------------------------------------------- 6 | 7 | package.cpath = "./?.so;"..package.cpath 8 | 9 | local randomseed = 1235134892 10 | --local randomseed = os.time() 11 | 12 | print("===== BEGIN LUABINS TEST SUITE (seed " .. randomseed .. ") =====") 13 | math.randomseed(randomseed) 14 | 15 | -- ---------------------------------------------------------------------------- 16 | -- Utility functions 17 | -- ---------------------------------------------------------------------------- 18 | 19 | local invariant = function(v) 20 | return function() 21 | return v 22 | end 23 | end 24 | 25 | local escape_string = function(str) 26 | return str:gsub( 27 | "[^0-9A-Za-z_%- :]", 28 | function(c) 29 | return ("%%%02X"):format(c:byte()) 30 | end 31 | ) 32 | end 33 | 34 | local ensure_equals = function(msg, actual, expected) 35 | if actual ~= expected then 36 | error( 37 | msg..":\n actual: `"..escape_string(tostring(actual)) 38 | .."`\nexpected: `"..escape_string(tostring(expected)).."'" 39 | ) 40 | end 41 | end 42 | 43 | local ensure_equals_permute 44 | do 45 | -- Based on MIT-licensed 46 | -- http://snippets.luacode.org/sputnik.lua?p=snippets/ \ 47 | -- Iterator_over_Permutations_of_a_Table_62 48 | -- Which is based on PiL 49 | local function permgen(a, n, fn) 50 | if n == 0 then 51 | fn(a) 52 | else 53 | for i = 1, n do 54 | -- put i-th element as the last one 55 | a[n], a[i] = a[i], a[n] 56 | 57 | -- generate all permutations of the other elements 58 | permgen(a, n - 1, fn) 59 | 60 | -- restore i-th element 61 | a[n], a[i] = a[i], a[n] 62 | end 63 | end 64 | end 65 | 66 | --- an iterator over all permutations of the elements of a list. 67 | -- Please note that the same list is returned each time, 68 | -- so do not keep references! 69 | -- @param a list-like table 70 | -- @return an iterator which provides the next permutation as a list 71 | local function permute_iter(a, n) 72 | local n = n or #a 73 | local co = coroutine.create(function() permgen(a, n, coroutine.yield) end) 74 | return function() -- iterator 75 | local code, res = coroutine.resume(co) 76 | return res 77 | end 78 | end 79 | 80 | ensure_equals_permute = function( 81 | msg, 82 | actual, 83 | expected_prefix, 84 | expected_body, 85 | expected_suffix, 86 | expected_body_size 87 | ) 88 | expected_body_size = expected_body_size or #expected_body 89 | 90 | local expected 91 | for t in permute_iter(expected_body, expected_body_size) do 92 | expected = expected_prefix .. table.concat(t) .. expected_suffix 93 | if actual == expected then 94 | return actual 95 | end 96 | end 97 | 98 | error( 99 | msg..":\nactual: `"..escape_string(tostring(actual)) 100 | .."`\nexpected one of permutations: `" 101 | ..escape_string(tostring(expected)).."'" 102 | ) 103 | end 104 | end 105 | 106 | local function deepequals(lhs, rhs) 107 | if type(lhs) ~= "table" or type(rhs) ~= "table" then 108 | return lhs == rhs 109 | end 110 | 111 | local checked_keys = {} 112 | 113 | for k, v in pairs(lhs) do 114 | checked_keys[k] = true 115 | if not deepequals(v, rhs[k]) then 116 | return false 117 | end 118 | end 119 | 120 | for k, v in pairs(rhs) do 121 | if not checked_keys[k] then 122 | return false -- extra key 123 | end 124 | end 125 | 126 | return true 127 | end 128 | 129 | local nargs = function(...) 130 | return select("#", ...), ... 131 | end 132 | 133 | local pack = function(...) 134 | return select("#", ...), { ... } 135 | end 136 | 137 | local eat_true = function(t, ...) 138 | if t == nil then 139 | error("failed: " .. (...)) 140 | end 141 | return ... 142 | end 143 | 144 | -- ---------------------------------------------------------------------------- 145 | -- Test helper functions 146 | -- ---------------------------------------------------------------------------- 147 | 148 | local luabins_local = require 'luabins' 149 | assert(luabins_local == luabins) 150 | 151 | assert(type(luabins.save) == "function") 152 | assert(type(luabins.load) == "function") 153 | 154 | local check_load_fn_ok = function(eq, saved, ...) 155 | local expected = { nargs(...) } 156 | local loaded = { nargs(eat_true(luabins.load(saved))) } 157 | 158 | ensure_equals("num arguments match", loaded[1], expected[1]) 159 | for i = 2, expected[1] do 160 | assert(eq(loaded[i], expected[i])) 161 | end 162 | 163 | return saved 164 | end 165 | 166 | local check_load_ok = function(saved, ...) 167 | return check_load_fn_ok(deepequals, saved, ...) 168 | end 169 | 170 | local check_fn_ok = function(eq, ...) 171 | local saved = assert(luabins.save(...)) 172 | 173 | assert(type(saved) == "string") 174 | 175 | print("saved length", #saved, "(display truncated to 70 chars)") 176 | print(escape_string(saved):sub(1, 70)) 177 | 178 | return check_load_fn_ok(eq, saved, ...) 179 | end 180 | 181 | local check_ok = function(...) 182 | print("check_ok") 183 | return check_fn_ok(deepequals, ...) 184 | end 185 | 186 | local check_fail_save = function(msg, ...) 187 | print("check_fail_save") 188 | local res, err = luabins.save(...) 189 | ensure_equals("result", res, nil) 190 | ensure_equals("error message", err, msg) 191 | -- print("/check_fail_save") 192 | end 193 | 194 | local check_fail_load = function(msg, v) 195 | print("check_fail_load") 196 | local res, err = luabins.load(v) 197 | ensure_equals("result", res, nil) 198 | ensure_equals("error message", err, msg) 199 | -- print("/check_fail_load") 200 | end 201 | 202 | print("===== BEGIN LARGE DATA OK =====") 203 | 204 | -- Based on actual bug. 205 | -- This dataset triggered Lua C data stack overflow. 206 | -- (Note that bug is not triggered if check_ok is used) 207 | -- Update data with 208 | -- $ lua etc/toluabins.lua test/large_data.lua>test/large_data.luabins 209 | -- WARNING: Keep this test above other tests, so Lua stack is small. 210 | assert( 211 | luabins.load( 212 | assert(io.open("test/large_data.luabins", "r"):read("*a")) 213 | ) 214 | ) 215 | 216 | print("===== LARGE DATA OK =====") 217 | 218 | -- ---------------------------------------------------------------------------- 219 | -- Basic tests 220 | -- ---------------------------------------------------------------------------- 221 | 222 | print("===== BEGIN BASIC TESTS =====") 223 | 224 | print("---> basic corrupt data tests") 225 | 226 | check_fail_load("can't load: corrupt data", "") 227 | check_fail_load("can't load: corrupt data", "bad data") 228 | 229 | print("---> basic extra data tests") 230 | do 231 | local s 232 | 233 | s = check_ok() 234 | check_fail_load("can't load: extra data at end", s .. "-") 235 | 236 | s = check_ok(nil) 237 | check_fail_load("can't load: extra data at end", s .. "-") 238 | 239 | s = check_ok(true) 240 | check_fail_load("can't load: extra data at end", s .. "-") 241 | 242 | s = check_ok(false) 243 | check_fail_load("can't load: extra data at end", s .. "-") 244 | 245 | s = check_ok(42) 246 | check_fail_load("can't load: extra data at end", s .. "-") 247 | 248 | s = check_ok(math.pi) 249 | check_fail_load("can't load: extra data at end", s .. "-") 250 | 251 | s = check_ok(1/0) 252 | check_fail_load("can't load: extra data at end", s .. "-") 253 | 254 | s = check_ok(-1/0) 255 | check_fail_load("can't load: extra data at end", s .. "-") 256 | 257 | s = check_ok("Luabins") 258 | check_fail_load("can't load: extra data at end", s .. "-") 259 | 260 | s = check_ok({ }) 261 | 262 | check_fail_load("can't load: extra data at end", s .. "-") 263 | 264 | s = check_ok({ a = 1, 2 }) 265 | check_fail_load("can't load: extra data at end", s .. "-") 266 | end 267 | 268 | print("---> basic type tests") 269 | 270 | -- This is the way to detect NaN 271 | check_fn_ok(function(lhs, rhs) return lhs ~= lhs and rhs ~= rhs end, 0/0) 272 | 273 | check_ok("") 274 | 275 | check_ok("Embedded\0Zero") 276 | 277 | check_ok(("longstring"):rep(1024000)) 278 | 279 | check_fail_save("can't save: unsupported type detected", function() end) 280 | check_fail_save( 281 | "can't save: unsupported type detected", 282 | coroutine.create(function() end) 283 | ) 284 | check_fail_save("can't save: unsupported type detected", newproxy()) 285 | 286 | print("---> basic table tests") 287 | 288 | check_ok({ 1 }) 289 | check_ok({ a = 1 }) 290 | check_ok({ a = 1, 2, [42] = true, [math.pi] = math.huge }) 291 | check_ok({ { } }) 292 | check_ok({ a = {}, b = { c = 7 } }) 293 | check_ok({ 1, 2, 3 }) 294 | check_ok({ [1] = 1, [1.5] = 2, [2] = 3 }) 295 | check_ok({ 1, nil, 3 }) 296 | check_ok({ 1, nil, 3, [{ 1, nil, 3 }] = { 1, nil, 3 } }) 297 | 298 | print("---> basic tuple tests") 299 | 300 | check_ok(nil, nil) 301 | 302 | do 303 | local s = check_ok(nil, false, true, 42, "Embedded\0Zero", { { [{3}] = 54 } }) 304 | check_fail_load("can't load: extra data at end", s .. "-") 305 | 306 | check_ok(check_ok(s)) -- Save data string couple of times more 307 | end 308 | 309 | print("---> basic table tuple tests") 310 | 311 | check_ok({ a = {}, b = { c = 7 } }, nil, { { } }, 42) 312 | 313 | check_ok({ ["1"] = "str", [1] = "num" }) 314 | 315 | check_ok({ [true] = true }) 316 | check_ok({ [true] = true, [false] = false, 1 }) 317 | 318 | print("---> basic fail save tests") 319 | 320 | check_fail_save( 321 | "can't save: unsupported type detected", 322 | { { function() end } } 323 | ) 324 | 325 | check_fail_save( 326 | "can't save: unsupported type detected", 327 | nil, false, true, 42, "Embedded\0Zero", function() end, 328 | { { [{3}] = 54 } } 329 | ) 330 | 331 | print("---> recursive table test") 332 | 333 | local t = {}; t[1] = t 334 | check_fail_save("can't save: nesting is too deep", t) 335 | 336 | print("---> metatable test") 337 | 338 | check_ok(setmetatable({}, {__index = function(t, k) return k end})) 339 | 340 | print("===== BASIC TESTS OK =====") 341 | 342 | print("===== BEGIN FORMAT SANITY TESTS =====") 343 | 344 | -- Format sanity checks for LJ2 compatibility tests. 345 | -- These tests are intended to help debugging actual problems 346 | -- of test suite, and are not feature complete. 347 | -- What is not checked here, checked in the rest of suite. 348 | 349 | do 350 | do 351 | local saved = check_ok(1) 352 | local expected = 353 | "\001".."N" 354 | .. "\000\000\000\000\000\000\240\063" -- Note number is a double 355 | 356 | ensure_equals( 357 | "1 as number", 358 | expected, 359 | saved 360 | ) 361 | end 362 | 363 | do 364 | local saved = check_ok({ [true] = 1 }) 365 | local expected = 366 | "\001".."T" 367 | .. "\000\000\000\000".."\001\000\000\000" 368 | .. "1" 369 | .. "N\000\000\000\000\000\000\240\063" -- Note number is a double 370 | 371 | ensure_equals( 372 | "1 as value", 373 | expected, 374 | saved 375 | ) 376 | end 377 | 378 | do 379 | local saved = check_ok({ [1] = true }) 380 | local expected = 381 | "\001".."T" 382 | .. "\001\000\000\000".."\000\000\000\000" 383 | .. "N\000\000\000\000\000\000\240\063" -- Note number is a double 384 | .. "1" 385 | 386 | ensure_equals( 387 | "1 as key", 388 | expected, 389 | saved 390 | ) 391 | end 392 | end 393 | 394 | print("===== FORMAT SANITY TESTS OK =====") 395 | 396 | print("===== BEGIN AUTOCOLLAPSE TESTS =====") 397 | 398 | -- Note: those are ad-hoc tests, tuned for old implementation 399 | -- which generated save data on Lua stack. 400 | -- These tests are kept here for performance comparisons. 401 | 402 | local LUABINS_CONCATTHRESHOLD = 1024 403 | 404 | local gen_t = function(size) 405 | -- two per numeric entry, three per string entry, 406 | -- two entries per key-value pair 407 | local actual_size = math.ceil(size / (2 + 3)) 408 | print("generating table of "..actual_size.." pairs") 409 | local t = {} 410 | for i = 1, actual_size do 411 | t[i] = "a"..i 412 | end 413 | return t 414 | end 415 | 416 | -- Test table value autocollapse 417 | check_ok(gen_t(LUABINS_CONCATTHRESHOLD - 100)) -- underflow, no autocollapse 418 | check_ok(gen_t(LUABINS_CONCATTHRESHOLD)) -- autocollapse, no extra elements 419 | check_ok(gen_t(LUABINS_CONCATTHRESHOLD + 100)) -- autocollapse, extra elements 420 | 421 | -- Test table key autocollapse 422 | check_ok({ [gen_t(LUABINS_CONCATTHRESHOLD - 4)] = true }) 423 | 424 | -- Test multiarg autocollapse 425 | check_ok( 426 | 1, 427 | gen_t(LUABINS_CONCATTHRESHOLD - 5), 428 | 2, 429 | gen_t(LUABINS_CONCATTHRESHOLD - 5), 430 | 3 431 | ) 432 | 433 | print("===== AUTOCOLLAPSE TESTS OK =====") 434 | 435 | print("===== BEGIN MIN TABLE SIZE TESTS =====") 436 | 437 | do 438 | -- one small key 439 | do 440 | local data = { [true] = true } 441 | local saved = check_ok(data) 442 | ensure_equals( 443 | "format sanity check", 444 | "\001".."T".."\000\000\000\000".."\001\000\000\000".."11", 445 | saved 446 | ) 447 | check_fail_load( 448 | "can't load: corrupt data, bad size", 449 | saved:sub(1, #saved - 1) 450 | ) 451 | 452 | -- As long as array and hash size sum is correct 453 | -- (and both are within limits), load is successful. 454 | -- If values are swapped, we get some performance hit. 455 | check_load_ok( 456 | "\001".."T".."\001\000\000\000".."\000\000\000\000".."11", 457 | data 458 | ) 459 | 460 | check_fail_load( 461 | "can't load: corrupt data, bad size", 462 | "\001".."T".."\001\000\000\000".."\001\000\000\000".."11" 463 | ) 464 | 465 | check_fail_load( 466 | "can't load: corrupt data, bad size", 467 | "\001".."T".."\000\000\000\000".."\002\000\000\000".."11" 468 | ) 469 | 470 | check_fail_load( 471 | "can't load: extra data at end", 472 | "\001".."T".."\000\000\000\000".."\000\000\000\000".."11" 473 | ) 474 | 475 | check_fail_load( 476 | "can't load: corrupt data, bad size", 477 | "\001".."T".."\255\255\255\255".."\255\255\255\255".."11" 478 | ) 479 | check_fail_load( 480 | "can't load: corrupt data, bad size", 481 | "\001".."T".."\000\255\255\255".."\000\255\255\255".."11" 482 | ) 483 | check_fail_load( 484 | "can't load: corrupt data, bad size", 485 | "\255".."T".."\000\000\000\000".."\000\000\000\000" 486 | ) 487 | end 488 | 489 | -- two small keys 490 | do 491 | local data = { [true] = true, [false] = false } 492 | local saved = check_ok({ [true] = true, [false] = false }) 493 | ensure_equals_permute( 494 | "format sanity check", 495 | saved, 496 | "\001" .. "T" .. "\000\000\000\000" .. "\002\000\000\000", 497 | { 498 | "0" .. "0"; 499 | "1" .. "1"; 500 | }, 501 | "" 502 | ) 503 | check_fail_load( 504 | "can't load: corrupt data, bad size", 505 | saved:sub(1, #saved - 1) 506 | ) 507 | 508 | -- See above about swapped array and hash sizes 509 | check_load_ok( 510 | "\001".."T".."\001\000\000\000".."\001\000\000\000".."1100", 511 | data 512 | ) 513 | 514 | check_fail_load( 515 | "can't load: corrupt data, bad size", 516 | "\001".."T".."\000\000\000\000".."\003\000\000\000".."0011" 517 | ) 518 | end 519 | 520 | -- two small and one large key 521 | do 522 | local saved = check_ok({ [true] = true, [false] = false, [1] = true }) 523 | ensure_equals_permute( 524 | "format sanity check", 525 | saved, 526 | "\001" .. "T" .. "\001\000\000\000" .. "\002\000\000\000", 527 | { 528 | "0" .. "0"; 529 | "1" .. "1"; 530 | -- Note number is a double 531 | "N\000\000\000\000\000\000\240\063" .. "1"; 532 | }, 533 | "" 534 | ) 535 | 536 | check_fail_load( 537 | "can't load: corrupt data", 538 | saved:sub(1, #saved - 1) 539 | ) 540 | 541 | check_fail_load( 542 | "can't load: corrupt data, bad size", 543 | "\001".."T" 544 | .. "\002\000\000\000".."\002\000\000\000" 545 | .. "0011" 546 | .. "N\000\000\000\000\000\000\240\063" 547 | .. "1" 548 | ) 549 | 550 | check_fail_load( 551 | "can't load: corrupt data, bad size", 552 | "\001".."T" 553 | .. "\001\000\000\000".."\003\000\000\000" 554 | .. "0011" 555 | .. "N\000\000\000\000\000\000\240\063" 556 | .. "1" 557 | ) 558 | end 559 | 560 | -- two small and two large keys 561 | do 562 | local saved = check_ok( 563 | { [true] = true, [false] = false, [1] = true, [42] = true } 564 | ) 565 | local expected = 566 | "\001".."T" 567 | .. "\001\000\000\000".."\003\000\000\000" 568 | .. "0011" 569 | ensure_equals_permute( 570 | "format sanity check", 571 | saved, 572 | "\001" .. "T" .. "\001\000\000\000" .. "\003\000\000\000", 573 | { 574 | "0" .. "0"; 575 | "1" .. "1"; 576 | "N\000\000\000\000\000\000\069\064" .. "1"; 577 | "N\000\000\000\000\000\000\240\063" .. "1"; 578 | }, 579 | "" 580 | ) 581 | 582 | check_fail_load( 583 | "can't load: corrupt data", 584 | saved:sub(1, #saved - 1) 585 | ) 586 | 587 | check_fail_load( 588 | "can't load: corrupt data, bad size", 589 | "\001".."T" 590 | .. "\001\000\000\000".."\005\000\000\000" 591 | .. "0011" 592 | .. "N\000\000\000\000\000\000\069\064" 593 | .. "1" 594 | .. "N\000\000\000\000\000\000\240\063" 595 | .. "1" 596 | ) 597 | 598 | check_fail_load( 599 | "can't load: corrupt data, bad size", 600 | "\001".."T" 601 | .. "\003\000\000\000".."\003\000\000\000" 602 | .. "0011" 603 | .. "N\000\000\000\000\000\000\069\064" 604 | .. "1" 605 | .. "N\000\000\000\000\000\000\240\063" 606 | .. "1" 607 | ) 608 | end 609 | end 610 | 611 | print("===== MIN TABLE SIZE TESTS OK =====") 612 | 613 | print("===== BEGIN LOAD TRUNCATION TESTS =====") 614 | 615 | local function gen_random_dataset(num, nesting) 616 | num = num or math.random(0, 128) 617 | nesting = nesting or 1 618 | 619 | local gen_str = function() 620 | local t = {} 621 | local n = math.random(0, 1024) 622 | for i = 1, n do 623 | t[i] = string.char(math.random(0, 255)) 624 | end 625 | return table.concat(t) 626 | end 627 | 628 | local gen_bool = function() return math.random() >= 0.5 end 629 | 630 | local gen_nil = function() return nil end 631 | 632 | local generators = 633 | { 634 | gen_nil; 635 | gen_nil; 636 | gen_nil; 637 | gen_bool; 638 | gen_bool; 639 | gen_bool; 640 | function() return math.random() end; 641 | function() return math.random(-10000, 10000) end; 642 | function() return math.random() * math.random(-10000, 10000) end; 643 | gen_str; 644 | gen_str; 645 | gen_str; 646 | function() 647 | if nesting >= 24 then 648 | return nil 649 | end 650 | 651 | local t = {} 652 | local n = math.random(0, 24 - nesting) 653 | for i = 1, n do 654 | local k = gen_random_dataset(1, nesting + 1) 655 | if k == nil then 656 | k = "(nil)" 657 | end 658 | t[ k ] = gen_random_dataset( 659 | 1, 660 | nesting + 1 661 | ) 662 | end 663 | 664 | return t 665 | end; 666 | } 667 | 668 | local t = {} 669 | for i = 1, num do 670 | local n = math.random(1, #generators) 671 | t[i] = generators[n]() 672 | end 673 | return unpack(t, 0, num) 674 | end 675 | 676 | local random_dataset_num, random_dataset_data = pack(gen_random_dataset()) 677 | local random_dataset_saved = check_ok( 678 | unpack(random_dataset_data, 0, random_dataset_num) 679 | ) 680 | 681 | local num_tries = 100 682 | local errors = {} 683 | for i = 1, num_tries do 684 | local to = math.random(1, #random_dataset_saved - 1) 685 | local new_data = random_dataset_saved:sub(1, to) 686 | 687 | local res, err = luabins.load(new_data) 688 | ensure_equals("truncated data must not be loaded", res, nil) 689 | errors[err] = (errors[err] or 0) + 1 690 | end 691 | 692 | print("truncation errors encountered:") 693 | for err, n in pairs(errors) do 694 | print(err, n) 695 | end 696 | 697 | print("===== BASIC LOAD TRUNCATION OK =====") 698 | 699 | print("===== BEGIN LOAD MUTATION TESTS =====") 700 | 701 | local function mutate_string(str, num, override) 702 | num = num or math.random(1, 8) 703 | 704 | if num < 1 then 705 | return str 706 | end 707 | 708 | local mutators = 709 | { 710 | -- truncate at end 711 | function(str) 712 | local pos = math.random(1, #str) 713 | return str:sub(1, pos) 714 | end; 715 | -- truncate at beginning 716 | function(str) 717 | local pos = math.random(1, #str) 718 | return str:sub(-pos) 719 | end; 720 | -- cut out the middle 721 | function(str) 722 | local from = math.random(1, #str) 723 | local to = math.random(from, #str) 724 | return str:sub(1, from) .. str:sub(to) 725 | end; 726 | -- swap two halves 727 | function(str) 728 | local pos = math.random(1, #str) 729 | return str:sub(pos + 1, #str) .. str:sub(1, pos) 730 | end; 731 | -- swap two characters 732 | function(str) 733 | local pa, pb = math.random(1, #str), math.random(1, #str) 734 | local a, b = str:sub(pa, pa), str:sub(pb, pb) 735 | return 736 | str:sub(1, pa - 1) .. 737 | a .. 738 | str:sub(pa + 1, pb - 1) .. 739 | b .. 740 | str:sub(pb + 1, #str) 741 | end; 742 | -- replace one character 743 | function(str) 744 | local pos = math.random(1, #str) 745 | return 746 | str:sub(1, pos - 1) .. 747 | string.char(math.random(0, 255)) .. 748 | str:sub(pos + 1, #str) 749 | end; 750 | -- increase one character 751 | function(str) 752 | local pos = math.random(1, #str) 753 | local b = str:byte(pos, pos) + 1 754 | if b > 255 then 755 | b = 0 756 | end 757 | return 758 | str:sub(1, pos - 1) .. 759 | string.char(b) .. 760 | str:sub(pos + 1, #str) 761 | end; 762 | -- decrease one character 763 | function(str) 764 | local pos = math.random(1, #str) 765 | local b = str:byte(pos, pos) - 1 766 | if b < 0 then 767 | b = 255 768 | end 769 | return 770 | str:sub(1, pos - 1) .. 771 | string.char(b) .. 772 | str:sub(pos + 1, #str) 773 | end; 774 | } 775 | 776 | local n = override or math.random(1, #mutators) 777 | 778 | str = mutators[n](str) 779 | 780 | return mutate_string(str, num - 1, override) 781 | end 782 | 783 | local num_tries = 100000 784 | local num_successes = 0 785 | local errors = {} 786 | for i = 1, num_tries do 787 | local new_data = mutate_string(random_dataset_saved) 788 | 789 | local res, err = luabins.load(new_data) 790 | if res == nil then 791 | errors[err] = (errors[err] or 0) + 1 792 | else 793 | num_successes = num_successes + 1 794 | end 795 | end 796 | 797 | if num_successes == 0 then 798 | print("no mutated strings loaded successfully") 799 | else 800 | -- This is ok, since we may corrupt data, not format. 801 | -- If it is an issue for user, he must append checksum to data, 802 | -- as usual. 803 | print("mutated strings loaded successfully: "..num_successes) 804 | end 805 | 806 | print("mutation errors encountered:") 807 | for err, n in pairs(errors) do 808 | print(err, n) 809 | end 810 | 811 | print("===== BASIC LOAD MUTATION OK =====") 812 | 813 | print("OK") 814 | --------------------------------------------------------------------------------