diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9a0c11443c92905e084194ee687ee88581bf385..ef5d566d4e165272fd54221fa8cf4fade88cd637 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,7 @@ file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/src/lua) set(lua_sources) lua_source(lua_sources lua/uuid.lua) lua_source(lua_sources lua/session.lua) +lua_source(lua_sources lua/msgpackffi.lua) set(bin_sources) bin_source(bin_sources bootstrap.snap bootstrap.h) diff --git a/src/lua/init.cc b/src/lua/init.cc index ca8abfbb83786288d189e5ae6f037d07756c26ae..69a5dcd9be61c3a0c3c92149e7af22770b6ccd01 100644 --- a/src/lua/init.cc +++ b/src/lua/init.cc @@ -67,9 +67,9 @@ extern "C" { struct lua_State *tarantool_L; /* contents of src/lua/ files */ -extern char uuid_lua[]; -extern char session_lua[]; -static const char *lua_sources[] = { uuid_lua, session_lua, NULL }; +extern char uuid_lua[], session_lua[], msgpackffi_lua[]; +static const char *lua_sources[] = { uuid_lua, session_lua, msgpackffi_lua, + NULL }; /* * {{{ box Lua library: common functions diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua new file mode 100644 index 0000000000000000000000000000000000000000..eff23ddc9098580e527a7f6a999b4976f6106e3b --- /dev/null +++ b/src/lua/msgpackffi.lua @@ -0,0 +1,514 @@ +-- msgpackffi.lua (internal file) + +local ffi = require('ffi') +local builtin = ffi.C + +local MAXNESTING = 16 +local NULL = ffi.cast('void *', 0) +local uint16_ptr_t = ffi.typeof('uint16_t *') +local uint32_ptr_t = ffi.typeof('uint32_t *') +local uint64_ptr_t = ffi.typeof('uint64_t *') +local float_ptr_t = ffi.typeof('float *') +local double_ptr_t = ffi.typeof('double *') +local const_char_ptr_t = ffi.typeof('const char *') + +ffi.cdef([[ +uint32_t bswap_u32(uint32_t x); +uint64_t bswap_u64(uint64_t x); +double +mp_bswap_double(double d); +float +mp_bswap_float(float f); +]]) +local function bswap_u16(num) + return bit.rshift(bit.bswap(tonumber(num)), 16) +end +local bswap_u32 = builtin.bswap_u32 +local bswap_u64 = builtin.bswap_u64 +--[[ -- LuaJIT 2.1 +local bswap_u32 = bit.bswap +local bswap_u64 = bit.bswap +--]] + +-------------------------------------------------------------------------------- +--- Buffer +-------------------------------------------------------------------------------- + +local DEFAULT_CAPACITY = 4096; + +local tmpbuf = {} +tmpbuf.s = ffi.new("char[?]", DEFAULT_CAPACITY) +tmpbuf.e = tmpbuf.s + DEFAULT_CAPACITY +tmpbuf.p = tmpbuf.s +tmpbuf.reserve = function(buf, needed) + if buf.p + needed <= buf.e then + return + end + + local size = buf.p - buf.s + local capacity = buf.e - buf.s + while capacity - size < needed do + capacity = 2 * capacity + end + + local s = ffi.new("char[?]", capacity) + ffi.copy(s, buf.s, size) + buf.s = s + buf.e = s + capacity + buf.p = s + size +end +tmpbuf.reset = function(buf) + buf.p = buf.s +end + +-------------------------------------------------------------------------------- +-- Encoder +-------------------------------------------------------------------------------- + +local encode_ext_cdata = {} + +-- Set trigger that called when encoding cdata +local function on_encode(ctype_or_udataname, callback) + if type(ctype_or_udataname) ~= "cdata" or type(callback) ~= "function" then + error("Usage: on_encode(ffi.typeof('mytype'), function(buf, obj)") + end + local ctypeid = tonumber(ffi.typeof(ctype_or_udataname)) + local prev = encode_ext_cdata[ctypeid] + encode_ext_cdata[ctypeid] = callback + return prev +end + +local function encode_fix(buf, code, num) + buf:reserve(1) + buf.p[0] = bit.bor(code, tonumber(num)) + -- buf.p[0] = bit.bor(code, num) -- LuaJIT 2.1 + buf.p = buf.p + 1 +end + +local function encode_u8(buf, code, num) + buf:reserve(2) + buf.p[0] = code + ffi.cast(uint16_ptr_t, buf.p + 1)[0] = num + buf.p = buf.p + 2 +end + +local function encode_u16(buf, code, num) + buf:reserve(3) + buf.p[0] = code + ffi.cast(uint16_ptr_t, buf.p + 1)[0] = bswap_u16(num) + buf.p = buf.p + 3 +end + +local function encode_u32(buf, code, num) + buf:reserve(5) + buf.p[0] = code + ffi.cast(uint32_ptr_t, buf.p + 1)[0] = bswap_u32(num) + buf.p = buf.p + 5 +end + +local function encode_u64(buf, code, num) + buf:reserve(9) + buf.p[0] = code + ffi.cast(uint64_ptr_t, buf.p + 1)[0] = bswap_u64(num) + buf.p = buf.p + 9 +end + +local function encode_float(buf, num) + buf:reserve(5) + buf.p[0] = 0xca; + ffi.cast(float_ptr_t, buf.p + 1)[0] = builtin.mp_bswap_float(num) + buf.p = buf.p + 5 +end + +local function encode_double(buf, num) + buf:reserve(9) + buf.p[0] = 0xcb; + ffi.cast(double_ptr_t, buf.p + 1)[0] = builtin.mp_bswap_double(num) + buf.p = buf.p + 9 +end + +local function encode_int(buf, num) + if num >= 0 then + if num <= 0x7f then + encode_fix(buf, 0, num) + elseif num <= 0xff then + encode_u8(buf, 0xcc, num) + elseif num <= 0xffff then + encode_u16(buf, 0xcd, num) + elseif num <= 0xffffffff then + encode_u32(buf, 0xce, num) + elseif num <= 18446744073709551615ULL then + encode_u64(buf, 0xcf, num) + else + encode_double(buf, num) + end + else + if num >= -0x20 then + encode_fix(buf, 0xe0, num) + elseif num >= -0x7f then + encode_u8(buf, 0xd0, num) + elseif num >= -0x7fff then + encode_u16(buf, 0xd1, num) + elseif num >= -0x7fffffff then + encode_u32(buf, 0xd2, num) + elseif num >= -9223372036854775807LL then + encode_u64(buf, 0xd3, num) + else + encode_double(buf, num) + end + end +end + +local function encode_str(buf, str) + local len = #str + buf:reserve(5 + len) + if len <= 31 then + encode_fix(buf, 0xa0, len) + elseif len <= 0xff then + encode_u8(buf, 0xd9, len) + elseif len <= 0xffff then + encode_u16(buf, 0xda, len) + else + encode_u32(buf, 0xdb, len) + end + ffi.copy(buf.p, str, len) + buf.p = buf.p + len +end + +local function encode_array(buf, size) + if size <= 0xf then + encode_fix(buf, 0x90, size) + elseif size <= 0xffff then + encode_u16(buf, 0xdc, size) + else + encode_u32(buf, 0xdd, size) + end +end + +local function encode_map(buf, size) + if size <= 0xf then + encode_fix(buf, 0x80, size) + elseif size <= 0xffff then + encode_u16(buf, 0xde, size) + else + encode_u32(buf, 0xdf, size) + end +end + +local function encode_bool(buf, val) + encode_fix(buf, 0xc2, val and 1 or 0) +end + +local function encode_bool_cdata(buf, val) + encode_fix(buf, 0xc2, val ~= 0 and 1 or 0) +end + +local function encode_nil(buf) + buf:reserve(1) + buf.p[0] = 0xc0 + buf.p = buf.p + 1 +end + +local function encode_r(buf, obj, level) + if type(obj) == "number" then + if obj % 1 == 0 then -- Lua-way to check that number is an integer + encode_int(buf, obj) + else + encode_double(buf, obj) + end + elseif type(obj) == "string" then + encode_str(buf, obj) + elseif type(obj) == "table" then + if level >= MAXNESTING then -- Limit nested tables + encode_nil(buf) + return + end + if #obj > 0 then + encode_array(buf, #obj) + local i + for i=1,#obj,1 do + encode_r(buf, obj[i], level + 1) + end + else + local size = 0 + local key, val + for key, val in pairs(obj) do -- goodbye, JIT + size = size + 1 + end + encode_map(buf, size) + for key, val in pairs(obj) do + encode_r(buf, key, level + 1) + encode_r(buf, val, level + 1) + end + end + elseif obj == nil then + encode_nil(buf) + elseif type(obj) == "boolean" then + encode_bool(buf, obj) + elseif type(obj) == "cdata" then + if obj == nil then -- a workaround for nil + encode_nil(buf, obj) + return + end + local ctypeid = tonumber(ffi.typeof(obj)) + local fun = encode_ext_cdata[ctypeid] + if fun ~= nil then + fun(buf, obj) + else + error('unsupported FFI type: '..ffi.typeof(obj)) + end + else + error("Unsupported Lua type: "..type(obj)) + end +end + +local function encode(obj) + tmpbuf:reset() + encode_r(tmpbuf, obj, 0) + return ffi.string(tmpbuf.s, tmpbuf.p - tmpbuf.s) +end + +on_encode(ffi.typeof('uint8_t'), encode_int) +on_encode(ffi.typeof('uint16_t'), encode_int) +on_encode(ffi.typeof('uint32_t'), encode_int) +on_encode(ffi.typeof('uint64_t'), encode_int) +on_encode(ffi.typeof('int8_t'), encode_int) +on_encode(ffi.typeof('int16_t'), encode_int) +on_encode(ffi.typeof('int32_t'), encode_int) +on_encode(ffi.typeof('int64_t'), encode_int) +on_encode(ffi.typeof('char'), encode_int) +on_encode(ffi.typeof('const char'), encode_int) +on_encode(ffi.typeof('unsigned char'), encode_int) +on_encode(ffi.typeof('const unsigned char'), encode_int) +on_encode(ffi.typeof('bool'), encode_bool_cdata) +on_encode(ffi.typeof('float'), encode_float) +on_encode(ffi.typeof('double'), encode_double) + +-------------------------------------------------------------------------------- +-- Decoder +-------------------------------------------------------------------------------- + +local decode_r + +local function decode_u8(data) + local num = ffi.cast('uint8_t *', data[0])[0] + data[0] = data[0] + 1 + return num +end + +local function decode_u16(data) + local num = bswap_u16(ffi.cast(uint16_ptr_t, data[0])[0]) + data[0] = data[0] + 2 + return num +end + +local function decode_u32(data) + local num = bswap_u32(ffi.cast(uint32_ptr_t, data[0])[0]) + data[0] = data[0] + 4 + return num +end + +local function decode_u64(data) + local num = bswap_u64(ffi.cast(uint64_ptr_t, data[0])[0]) + data[0] = data[0] + 8 + return num +end + +local function decode_i8(data) + local num = ffi.cast(uint8_ptr_t, data[0])[0] + data[0] = data[0] + 1 + return num +end + +local function decode_i16(data) + local num = bswap_u16(ffi.cast(uint16_ptr_t, data[0])[0]) + data[0] = data[0] + 2 + return tonumber(ffi.cast('int16_t', ffi.cast('uint16_t', num))) +end + +local function decode_i32(data) + local num = bswap_u32(ffi.cast(uint32_ptr_t, data[0])[0]) + data[0] = data[0] + 4 + return tonumber(ffi.cast('int32_t', ffi.cast('uint32_t', num))) +end + +local function decode_i64(data) + local num = bswap_u64(ffi.cast(uint64_ptr_t, data[0])[0]) + data[0] = data[0] + 8 + return ffi.cast('int64_t', ffi.cast('uint64_t', num)) +end + +local function decode_float(data) + local num = builtin.mp_bswap_float(ffi.cast(float_ptr_t,data[0])[0]) + data[0] = data[0] + 4 + return tonumber(num) +end + +local function decode_double(data) + local num = builtin.mp_bswap_double(ffi.cast(double_ptr_t, data[0])[0]) + data[0] = data[0] + 8 + return tonumber(num) +end + +local function decode_str(data, size) + local ret = ffi.string(data[0], size) + data[0] = data[0] + size + return ret +end + +local function decode_array(data, size) + assert (type(size) == "number") + local arr = { [size] = 0} + local i + for i=1,size,1 do + arr[i] = decode_r(data) + end + return arr +end + +local function decode_map(data, size) + assert (type(size) == "number") + local map = {} + local i + for i=1,size,1 do + local key = decode_r(data); + local val = decode_r(data); + map[key] = val + end + return map +end + +local decoder_hint = { + --[[{{{ MP_BIN]] + [0xc4] = function(data) return decode_str(data, decode_u8(data)) end; + [0xc5] = function(data) return decode_str(data, decode_u16(data)) end; + [0xc6] = function(data) return decode_str(data, decode_u32(data)) end; + + --[[MP_FLOAT, MP_DOUBLE]] + [0xca] = decode_float; + [0xcb] = decode_double; + + --[[MP_UINT]] + [0xcc] = decode_u8; + [0xcd] = decode_u16; + [0xce] = decode_u32; + [0xcf] = decode_u64; + + --[[MP_INT]] + [0xd0] = decode_i8; + [0xd1] = decode_i16; + [0xd2] = decode_i32; + [0xd3] = decode_i64; + + --[[MP_STR]] + [0xd9] = function(data) return decode_str(data, decode_u8(data)) end; + [0xda] = function(data) return decode_str(data, decode_u16(data)) end; + [0xdb] = function(data) return decode_str(data, decode_u32(data)) end; + + --[[MP_ARRAY]] + [0xdc] = function(data) return decode_array(data, decode_u16(data)) end; + [0xdd] = function(data) return decode_array(data, decode_u32(data)) end; + + --[[MP_MAP]] + [0xde] = function(data) return decode_map(data, decode_u16(data)) end; + [0xdf] = function(data) return decode_map(data, decode_u32(data)) end; +} + +decode_r = function(data) + local c = data[0][0] + data[0] = data[0] + 1 + if c <= 0x7f then + return c -- fixint + elseif c >= 0xa0 and c <= 0xbf then + return decode_str(data, bit.band(c, 0x1f)) -- fixstr + elseif c >= 0x90 and c <= 0x9f then + return decode_array(data, bit.band(c, 0xf)) -- fixarray + elseif c >= 0x80 and c <= 0x8f then + return decode_map(data, bit.band(c, 0xf)) -- fixmap + elseif c <= 0x7f then + return c2 + elseif c >= 0xe0 then + return ffi.cast('signed char',c) + elseif c == 0xc0 then + return NULL + elseif c == 0xc2 then + return false + elseif c == 0xc3 then + return true + else + local fun = decoder_hint[c]; + assert (type(fun) == "function") + return fun(data) + end +end + +--- +-- A temporary const char ** buffer. +-- All decode_XXX functions accept const char **data as its first argument, +-- like libmsgpuck does. After decoding data[0] position is changed to the next +-- element. It is significally faster on LuaJIT to use double pointer than +-- return result, newpos. +-- +local bufp = ffi.new('const unsigned char *[1]'); + +local function check_offset(offset, len) + if offset == nil then + return 1 + end + local offset = ffi.cast('ptrdiff_t', offset) + if offset < 1 or offset > len then + error(string.format("offset = %d is out of bounds [1..%d]", + tonumber(offset), len)) + end + return offset +end + +-- decode_unchecked(str, offset) -> res, new_offset +-- decode_unchecked(buf) -> res, new_buf +local function decode_unchecked(str, offset) + if type(str) == "string" then + offset = check_offset(offset, #str) + local buf = ffi.cast(const_char_ptr_t, str) + bufp[0] = buf + offset - 1 + local r = decode_r(bufp) + return r, bufp[0] - buf + 1 + elseif ffi.istype(const_char_ptr_t, str) then + bufp[0] = str + local r = decode_r(bufp) + return r, bufp[0] + else + error("msgpackffi.decode_unchecked(str, offset) -> res, new_offset | ".. + "msgpackffi.decode_unchecked(const char *buf) -> res, new_buf") + end +end + +-------------------------------------------------------------------------------- +-- box-specific optimized API +-------------------------------------------------------------------------------- + +local function encode_tuple(obj) + tmpbuf:reset() + if type(obj) == "table" then + encode_array(tmpbuf, #obj) + local i + for i=1,#obj,1 do + encode_r(tmpbuf, tuple[i], 1) + end + else + encode_fix(tmpbuf, 0x90, 1) -- array of one element + encode_r(tmpbuf, obj, 1) + end + return tmpbuf.s, tmpbuf.p +end + +-------------------------------------------------------------------------------- +-- exports +-------------------------------------------------------------------------------- + +package.loaded.msgpackffi = { + NULL = NULL; + encode = encode; + on_encode = on_encode; + decode_unchecked = decode_unchecked; + encode_tuple = encode_tuple; +} diff --git a/test/box/msgpack.result b/test/box/msgpack.result index 3defdfab7c218b4d2564ce6a17ca7300326fc6bc..1f59c421df21fc371448cfebe3a929881f0f918f 100644 --- a/test/box/msgpack.result +++ b/test/box/msgpack.result @@ -1,6 +1,12 @@ -------------------------------------------------------------------------------- -- Parameters parsing -------------------------------------------------------------------------------- +ffi = require('ffi') +--- +... +msgpackffi = require('msgpackffi') +--- +... msgpack.encode() --- - error: 'msgpack.encode: a Lua object expected' @@ -23,6 +29,10 @@ function deepcompare(a, b) return a == b end + if ffi.istype('bool', a) then a = (a == 1) end + if ffi.istype('bool', b) then b = (b == 1) end + + if a == nil and b == nil then return true end if type(a) ~= type(b) then return false end if type(a) == "table" then @@ -41,12 +51,34 @@ end; --- ... function test(x) - local x2 = msgpack.decode(msgpack.encode(x)) - xstr = type(x) == "table" and "table" or tostring(x) - if deepcompare(x2, x) then - return string.format("%s ok", xstr) + local buf1 = msgpack.encode(x) + local buf2 = msgpackffi.encode(x) + local x1, offset1 = msgpack.next(buf1) + local x2, offset2 = msgpackffi.decode_unchecked(buf2) + local xstr + if type(x) == "table" then + xstr = "table" + elseif ffi.istype('float', x) then + xstr = string.format('%0.2f (ffi float)', tonumber(x)) + elseif ffi.istype('double', x) then + xstr = string.format('%0.2f (ffi double)', tonumber(x)) + elseif ffi.istype("bool", x) then + xstr = string.format("%s (ffi bool)", x == 1 and "true" or "false") + else + xstr = tostring(x) + end + if #buf1 ~= #buf2 then + return string.format("%s fail, length mismatch", xstr) + elseif offset1 ~= #buf1 + 1 then + return string.format("%s fail, invalid offset", xstr) + elseif offset2 ~= #buf2 + 1 then + return string.format("%s fail, invalid offset (ffi)", xstr) + elseif not deepcompare(x1, x) then + return string.format("%s fail, invalid result %s", xstr, x1) + elseif not deepcompare(x2, x) then + return string.format("%s fail, invalid result (ffi) %s", xstr, x2) else - return string.format("%s fail, got %s", xstr, x2) + return string.format("%s ok", xstr) end end; --- @@ -228,6 +260,22 @@ test(1e100) --- - 1e+100 ok ... +test(ffi.new('float', 123456)) +--- +- 123456.00 (ffi float) ok +... +test(ffi.new('double', 123456)) +--- +- 123456.00 (ffi double) ok +... +test(ffi.new('float', 12.121)) +--- +- 12.12 (ffi float) ok +... +test(ffi.new('double', 12.121)) +--- +- 12.12 (ffi double) ok +... -------------------------------------------------------------------------------- -- Test str, bool, nil encoding / decoding -------------------------------------------------------------------------------- @@ -235,6 +283,10 @@ test(nil) --- - nil ok ... +test(ffi.cast('void *', 0)) +--- +- 'cdata<void *>: NULL ok' +... test(false) --- - false ok @@ -243,6 +295,14 @@ test(true) --- - true ok ... +test(ffi.new('bool', true)) +--- +- true (ffi bool) ok +... +test(ffi.new('bool', false)) +--- +- false (ffi bool) ok +... test("") --- - ' ok' @@ -270,10 +330,6 @@ test({k1 = 'v1', k2 = 'v2', k3 = 'v3'}) --- - table ok ... -test({[0] = 1, 2, 3, 4, 5}) ---- -- table ok -... msgpack.decode(msgpack.encode({[0] = 1, 2, 3, 4, 5})) --- - 0: 1 @@ -283,10 +339,6 @@ msgpack.decode(msgpack.encode({[0] = 1, 2, 3, 4, 5})) 4: 5 ... -- test sparse / dense arrays -test({1, 2, 3, 4, 5, [10] = 10 }) ---- -- table ok -... msgpack.decode(msgpack.encode({1, 2, 3, 4, 5, [10] = 10})) --- - 2: 2 @@ -296,10 +348,6 @@ msgpack.decode(msgpack.encode({1, 2, 3, 4, 5, [10] = 10})) 10: 10 4: 4 ... -test({1, 2, 3, 4, 5, [100] = 100 }) ---- -- table ok -... msgpack.decode(msgpack.encode({1, 2, 3, 4, 5, [100] = 100})) --- - 2: 2 @@ -309,6 +357,15 @@ msgpack.decode(msgpack.encode({1, 2, 3, 4, 5, [100] = 100})) 100: 100 4: 4 ... +msgpackffi.decode_unchecked(msgpackffi.encode({1, 2, 3, 4, 5, [100] = 100})) +--- +- - 1 + - 2 + - 3 + - 4 + - 5 +- 7 +... -------------------------------------------------------------------------------- -- Test serializer flags -------------------------------------------------------------------------------- @@ -406,6 +463,59 @@ msgpack.decode(msgpack.encode(a)); - c - null ... +msgpackffi.decode_unchecked(msgpackffi.encode(a)); +--- +- - 1 + - 2 + - 3 + - - a + - b + - c + - - 1 + - 2 + - 3 + - - a + - b + - c + - - 1 + - 2 + - 3 + - - a + - b + - c + - - 1 + - 2 + - 3 + - - a + - b + - c + - - 1 + - 2 + - 3 + - - a + - b + - c + - - 1 + - 2 + - 3 + - - a + - b + - c + - - 1 + - 2 + - 3 + - - a + - b + - c + - - 1 + - 2 + - 3 + - - a + - b + - c + - null +- 90 +... --# setopt delimiter '' -- Test aliases, loads and dumps a = { 1, 2, 3 } @@ -462,3 +572,42 @@ a, offset = msgpack.next(dump, offset) - error: '[string "a, offset = msgpack.next(dump, offset) "]:1: msgpack.next: offset is out of bounds' ... +-- Test decode with offset +dump = msgpackffi.encode({1, 2, 3})..msgpackffi.encode({4, 5, 6}) +--- +... +dump:len() +--- +- 8 +... +a, offset = msgpackffi.decode_unchecked(dump) +--- +... +a +--- +- - 1 + - 2 + - 3 +... +offset +--- +- 5 +... +a, offset = msgpackffi.decode_unchecked(dump, offset) +--- +... +a +--- +- - 4 + - 5 + - 6 +... +offset +--- +- 9 +... +a, offset = msgpackffi.decode_unchecked(dump, offset) +--- +- error: '[string "-- msgpackffi.lua (internal file)..."]:460: offset = 9 is out of + bounds [1..8]' +... diff --git a/test/box/msgpack.test.lua b/test/box/msgpack.test.lua index d48a264bdd398d86a29eb99d25d96711201ecf77..f29d05124eb0145cee7cd7e6fd2241d42a41ba57 100644 --- a/test/box/msgpack.test.lua +++ b/test/box/msgpack.test.lua @@ -2,6 +2,9 @@ -- Parameters parsing -------------------------------------------------------------------------------- +ffi = require('ffi') +msgpackffi = require('msgpackffi') + msgpack.encode() msgpack.decode() @@ -15,6 +18,10 @@ function deepcompare(a, b) return a == b end + if ffi.istype('bool', a) then a = (a == 1) end + if ffi.istype('bool', b) then b = (b == 1) end + + if a == nil and b == nil then return true end if type(a) ~= type(b) then return false end if type(a) == "table" then @@ -32,12 +39,34 @@ function deepcompare(a, b) end; function test(x) - local x2 = msgpack.decode(msgpack.encode(x)) - xstr = type(x) == "table" and "table" or tostring(x) - if deepcompare(x2, x) then - return string.format("%s ok", xstr) + local buf1 = msgpack.encode(x) + local buf2 = msgpackffi.encode(x) + local x1, offset1 = msgpack.next(buf1) + local x2, offset2 = msgpackffi.decode_unchecked(buf2) + local xstr + if type(x) == "table" then + xstr = "table" + elseif ffi.istype('float', x) then + xstr = string.format('%0.2f (ffi float)', tonumber(x)) + elseif ffi.istype('double', x) then + xstr = string.format('%0.2f (ffi double)', tonumber(x)) + elseif ffi.istype("bool", x) then + xstr = string.format("%s (ffi bool)", x == 1 and "true" or "false") + else + xstr = tostring(x) + end + if #buf1 ~= #buf2 then + return string.format("%s fail, length mismatch", xstr) + elseif offset1 ~= #buf1 + 1 then + return string.format("%s fail, invalid offset", xstr) + elseif offset2 ~= #buf2 + 1 then + return string.format("%s fail, invalid offset (ffi)", xstr) + elseif not deepcompare(x1, x) then + return string.format("%s fail, invalid result %s", xstr, x1) + elseif not deepcompare(x2, x) then + return string.format("%s fail, invalid result (ffi) %s", xstr, x2) else - return string.format("%s fail, got %s", xstr, x2) + return string.format("%s ok", xstr) end end; @@ -118,6 +147,10 @@ test(-3.14159265358979323846) test(-1e100) test(1e100) +test(ffi.new('float', 123456)) +test(ffi.new('double', 123456)) +test(ffi.new('float', 12.121)) +test(ffi.new('double', 12.121)) -------------------------------------------------------------------------------- -- Test str, bool, nil encoding / decoding @@ -125,10 +158,15 @@ test(1e100) test(nil) +test(ffi.cast('void *', 0)) + test(false) test(true) +test(ffi.new('bool', true)) +test(ffi.new('bool', false)) + test("") test("abcde") test(string.rep("x", 33)) @@ -143,16 +181,15 @@ test({1, 2, 3}) test({k1 = 'v1', k2 = 'v2', k3 = 'v3'}) -test({[0] = 1, 2, 3, 4, 5}) msgpack.decode(msgpack.encode({[0] = 1, 2, 3, 4, 5})) -- test sparse / dense arrays -test({1, 2, 3, 4, 5, [10] = 10 }) msgpack.decode(msgpack.encode({1, 2, 3, 4, 5, [10] = 10})) -test({1, 2, 3, 4, 5, [100] = 100 }) msgpack.decode(msgpack.encode({1, 2, 3, 4, 5, [100] = 100})) +msgpackffi.decode_unchecked(msgpackffi.encode({1, 2, 3, 4, 5, [100] = 100})) + -------------------------------------------------------------------------------- -- Test serializer flags -------------------------------------------------------------------------------- @@ -178,6 +215,7 @@ b[4] = a; a; msgpack.decode(msgpack.encode(a)); +msgpackffi.decode_unchecked(msgpackffi.encode(a)); --# setopt delimiter '' -- Test aliases, loads and dumps @@ -195,4 +233,13 @@ a offset a, offset = msgpack.next(dump, offset) - +-- Test decode with offset +dump = msgpackffi.encode({1, 2, 3})..msgpackffi.encode({4, 5, 6}) +dump:len() +a, offset = msgpackffi.decode_unchecked(dump) +a +offset +a, offset = msgpackffi.decode_unchecked(dump, offset) +a +offset +a, offset = msgpackffi.decode_unchecked(dump, offset)