Created
December 14, 2015 03:31
-
-
Save danielga/736b8e1d5a3389c725d0 to your computer and use it in GitHub Desktop.
A pure Lua module that provides the same functionality as the lpack C module.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| luapack | |
| A pure Lua module that provides the same functionality as the lpack | |
| C module. | |
| ----------------------------------------------------------------------- | |
| Copyright (c) 2015, Daniel Almeida | |
| All rights reserved. | |
| Redistribution and use in source and binary forms, with or without | |
| modification, are permitted provided that the following conditions | |
| are met: | |
| 1. Redistributions of source code must retain the above copyright | |
| notice, this list of conditions and the following disclaimer. | |
| 2. Redistributions in binary form must reproduce the above copyright | |
| notice, this list of conditions and the following disclaimer in the | |
| documentation and/or other materials provided with the distribution. | |
| 3. Neither the name of the copyright holder nor the names of its | |
| contributors may be used to endorse or promote products derived from | |
| this software without specific prior written permission. | |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| ]] | |
| local char, byte, format, find, sub = string.char, string.byte, string.format, string.find, string.sub | |
| local type, error, assert, select, unpack = type, error, assert, select, unpack or table.unpack | |
| local frexp, ldexp, floor = math.frexp, math.ldexp, math.floor | |
| local big_endian = false -- just a guess, find a way to do it properly | |
| local format_number = 0 | |
| local function swap2(a, b) | |
| if not big_endian then | |
| return b, a | |
| end | |
| return a, b | |
| end | |
| local function swap4(a, b, c, d) | |
| if not big_endian then | |
| return d, c, b, a | |
| end | |
| return a, b, c, d | |
| end | |
| local function swap8(a, b, c, d, e, f, g, h) | |
| if not big_endian then | |
| return h, g, f, e, d, c, b, a | |
| end | |
| return a, b, c, d, e, f, g, h | |
| end | |
| local unpackers = {} | |
| local function unpack_uchar(data, offset) | |
| assert(offset <= #data, "not enough data to unpack uint8") | |
| return byte(data, offset), offset + 1 | |
| end | |
| local function unpack_ushort(data, offset) | |
| assert(offset + 1 <= #data, "not enough data to unpack uint16") | |
| local b1, b2 = swap2(byte(data, offset, offset + 1)) | |
| return b1 * 0x100 + b2, offset + 2 | |
| end | |
| local function unpack_uinteger(data, offset) | |
| assert(offset + 3 <= #data, "not enough data to unpack uint32") | |
| local b1, b2, b3, b4 = swap4(byte(data, offset, offset + 3)) | |
| return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4, offset + 4 | |
| end | |
| local function unpack_ulong(data, offset) | |
| assert(offset + 7 <= #data, "not enough data to unpack uint64") | |
| local b1, b2, b3, b4, b5, b6, b7, b8 = swap8(byte(data, offset, offset + 7)) | |
| return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8, offset + 8 | |
| end | |
| -- zero-terminated string | |
| unpackers[byte("z")] = function(data, offset) | |
| local pos = find(data, "\0", offset) | |
| if pos == nil then | |
| pos = #data | |
| end | |
| return sub(data, offset, pos - 1), pos + 1 | |
| end | |
| -- string preceded by length byte | |
| unpackers[byte("p")] = function(data, offset) | |
| assert(offset <= #data, "not enough data to unpack uint8 length preceded string") | |
| local len = 0 | |
| len, offset = unpack_uchar(data, offset) | |
| if len == 0 then | |
| return "", offset | |
| end | |
| local pos = offset + len | |
| assert(pos <= #data, "not enough data to unpack uint8 length preceded string") | |
| return sub(data, offset, pos), pos + 1 | |
| end | |
| -- string preceded by length word | |
| unpackers[byte("P")] = function(data, offset) | |
| assert(offset + 1 <= #data, "not enough data to unpack uint16 length preceded string") | |
| local len = 0 | |
| len, offset = unpack_ushort(data, offset) | |
| if len == 0 then | |
| return "", offset | |
| end | |
| local pos = offset + len | |
| assert(pos <= #data, "not enough data to unpack uint16 length preceded string") | |
| return sub(data, offset, pos), pos + 1 | |
| end | |
| -- string preceded by length size_t | |
| unpackers[byte("a")] = function(data, offset) | |
| assert(offset + 3 <= #data, "not enough data to unpack size_t length preceded string") | |
| local len = 0 | |
| len, offset = unpack_uinteger(data, offset) | |
| if len == 0 then | |
| return "", offset | |
| end | |
| local pos = offset + len | |
| assert(pos <= #data, "not enough data to unpack size_t length preceded string") | |
| return sub(data, offset, pos), pos + 1 | |
| end | |
| -- string | |
| unpackers[byte("A")] = function(data, offset) | |
| if format_number == 0 then | |
| return "", offset | |
| end | |
| local pos = offset + format_number - 1 | |
| format_number = 0 | |
| return sub(data, offset, pos), pos + 1 | |
| end | |
| -- float | |
| unpackers[byte("f")] = function(data, offset) | |
| assert(offset + 3 <= #data, "not enough data to unpack float") | |
| local b1, b2, b3, b4 = swap4(byte(data, offset, offset + 3)) | |
| local sign = b1 > 0x7F | |
| local expo = (b1 % 0x80) * 0x2 + floor(b2 / 0x80) | |
| local mant = ((b2 % 0x80) * 0x100 + b3) * 0x100 + b4 | |
| if sign then | |
| sign = -1 | |
| else | |
| sign = 1 | |
| end | |
| local n | |
| if mant == 0 and expo == 0 then | |
| n = sign * 0 | |
| elseif expo == 0xFF then | |
| if mant == 0 then | |
| n = sign * huge | |
| else | |
| n = 0 / 0 | |
| end | |
| else | |
| n = sign * ldexp(1 + mant / 0x800000, expo - 0x7F) | |
| end | |
| return n, offset + 4 | |
| end | |
| -- double | |
| unpackers[byte("d")] = function(data, offset) | |
| assert(offset + 7 <= #data, "not enough data to unpack double") | |
| local b1, b2, b3, b4, b5, b6, b7, b8 = swap8(byte(data, offset, offset + 7)) | |
| local sign = b1 > 0x7F | |
| local expo = (b1 % 0x80) * 0x10 + floor(b2 / 0x10) | |
| local mant = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 | |
| if sign then | |
| sign = -1 | |
| else | |
| sign = 1 | |
| end | |
| local n | |
| if mant == 0 and expo == 0 then | |
| n = sign * 0 | |
| elseif expo == 0x7FF then | |
| if mant == 0 then | |
| n = sign * huge | |
| else | |
| n = 0 / 0 | |
| end | |
| else | |
| n = sign * ldexp(1 + mant / 4503599627370496, expo - 0x3FF) | |
| end | |
| return n, offset + 8 | |
| end | |
| -- lua_Number = double | |
| unpackers[byte("n")] = unpackers[byte("d")] | |
| -- char | |
| unpackers[byte("c")] = function(data, offset) | |
| assert(offset <= #data, "not enough data to unpack int8") | |
| local b1 = byte(data, offset) | |
| if b1 < 0x80 then | |
| return b1, offset + 1 | |
| end | |
| return b1 - 0x100, offset + 1 | |
| end | |
| -- unsigned char | |
| unpackers[byte("b")] = unpack_uchar | |
| -- short | |
| unpackers[byte("h")] = function(data, offset) | |
| assert(offset + 1 <= #data, "not enough data to unpack int16") | |
| local b1, b2 = swap2(byte(data, offset, offset + 1)) | |
| if b1 < 0x80 then | |
| return b1 * 0x100 + b2, offset + 2 | |
| end | |
| return ((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) - 1, offset + 2 | |
| end | |
| -- unsigned short | |
| unpackers[byte("H")] = unpack_ushort | |
| --int | |
| unpackers[byte("i")] = function(data, offset) | |
| assert(offset + 3 <= #data, "not enough data to unpack int32") | |
| local b1, b2, b3, b4 = swap4(byte(data, offset, offset + 3)) | |
| if b1 < 0x80 then | |
| return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4, offset + 4 | |
| end | |
| return ((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) - 1, offset + 4 | |
| end | |
| -- unsigned int | |
| unpackers[byte("I")] = unpack_uinteger | |
| -- long | |
| unpackers[byte("l")] = function(data, offset) | |
| assert(offset + 7 <= #data, "not enough data to unpack int64") | |
| local b1, b2, b3, b4, b5, b6, b7, b8 = swap8(byte(data, offset, offset + 7)) | |
| if b1 < 0x80 then | |
| return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8, offset + 8 | |
| end | |
| return ((((((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) * 0x100 + (b5 - 0xFF)) * 0x100 + (b6 - 0xFF)) * 0x100 + (b7 - 0xFF)) * 0x100 + (b8 - 0xFF)) - 1, offset + 8 | |
| end | |
| -- unsigned long | |
| unpackers[byte("L")] = unpack_ulong | |
| -- little endian | |
| unpackers[byte("<")] = function(data, offset) | |
| big_endian = false | |
| return true, offset | |
| end | |
| -- big endian | |
| unpackers[byte(">")] = function(data, offset) | |
| big_endian = true | |
| return true, offset | |
| end | |
| -- native endian | |
| unpackers[byte("=")] = function(data, offset) | |
| big_endian = false -- just a guess, find a way to do it properly | |
| return true, offset | |
| end | |
| local packers = {} | |
| local function pack_uchar(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| return char(data % 0x100) | |
| end | |
| local function pack_ushort(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| return char(swap2(floor(data / 0x100) % 0x100, data % 0x100)) | |
| end | |
| local function pack_uinteger(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| return char(swap4( | |
| floor(data / 0x1000000) % 0x100, | |
| floor(data / 0x10000) % 0x100, | |
| floor(data / 0x100) % 0x100, | |
| data % 0x100 | |
| )) | |
| end | |
| -- zero-terminated string | |
| packers[byte("z")] = function(data) | |
| assert(type(data) == "string", "bad argument type, expected string") | |
| return data .. "\0" | |
| end | |
| -- string preceded by length byte | |
| packers[byte("p")] = function(data) | |
| assert(type(data) == "string", "bad argument type, expected string") | |
| local datalen = #data | |
| assert(datalen <= 255, "string length is bigger than 255 (doesn't fit in 1 byte)") | |
| return pack_uchar(datalen) .. data | |
| end | |
| -- string preceded by length word | |
| packers[byte("P")] = function(data) | |
| assert(type(data) == "string", "bad argument type, expected string") | |
| local datalen = #data | |
| assert(datalen <= 65535, "string length is bigger than 65535 (doesn't fit in 2 bytes)") | |
| return pack_ushort(datalen) .. data | |
| end | |
| -- string preceded by length size_t | |
| packers[byte("a")] = function(data) | |
| assert(type(data) == "string", "bad argument type, expected string") | |
| local datalen = #data | |
| assert(datalen <= 4294967295, "string length is bigger than 4294967295 (doesn't fit in 4 bytes)") | |
| return pack_uinteger(datalen) .. data | |
| end | |
| -- string | |
| packers[byte("A")] = function(data) | |
| assert(type(data) == "string", "bad argument type, expected string") | |
| return data | |
| end | |
| -- float | |
| packers[byte("f")] = function(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| local sign = 0 | |
| if data < 0 then | |
| sign = 0x80 | |
| data = -data | |
| end | |
| local mant, expo = frexp(data) | |
| local b1, b2, b3, b4 | |
| if mant ~= mant then | |
| -- nan | |
| b1, b2, b3, b4 = swap4(0xFF, 0x88, 0x00, 0x00) | |
| elseif mant == huge or expo > 0x80 then | |
| if sign == 0 then | |
| -- inf | |
| b1, b2, b3, b4 = swap4(0x7F, 0x80, 0x00, 0x00) | |
| else | |
| -- -inf | |
| b1, b2, b3, b4 = swap4(0xFF, 0x80, 0x00, 0x00) | |
| end | |
| elseif (mant == 0 and expo == 0) or expo < -0x7E then | |
| -- zero | |
| b1, b2, b3, b4 = swap4(sign, 0x00, 0x00, 0x000) | |
| else | |
| expo = expo + 0x7E | |
| mant = (mant * 2 - 1) * ldexp(0.5, 24) | |
| b1, b2, b3, b4 = swap4( | |
| sign + floor(expo / 0x2), | |
| (expo % 0x2) * 0x80 + floor(mant / 0x10000), | |
| floor(mant / 0x100) % 0x100, | |
| mant % 0x100 | |
| ) | |
| end | |
| return char(b1, b2, b3, b4) | |
| end | |
| -- double | |
| packers[byte("d")] = function(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| local sign = 0 | |
| if data < 0 then | |
| sign = 0x80 | |
| data = -data | |
| end | |
| local mant, expo = frexp(data) | |
| local b1, b2, b3, b4, b5, b6, b7, b8 | |
| if mant ~= mant then | |
| -- nan | |
| b1, b2, b3, b4, b5, b6, b7, b8 = swap8(0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) | |
| elseif mant == huge then | |
| if sign == 0 then | |
| -- inf | |
| b1, b2, b3, b4, b5, b6, b7, b8 = swap8(0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) | |
| else | |
| -- -inf | |
| b1, b2, b3, b4, b5, b6, b7, b8 = swap8(0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) | |
| end | |
| elseif mant == 0 and expo == 0 then | |
| -- zero | |
| b1, b2, b3, b4, b5, b6, b7, b8 = swap8(sign, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) | |
| else | |
| expo = expo + 0x3FE | |
| mant = (mant * 2 - 1) * ldexp(0.5, 53) | |
| b1, b2, b3, b4, b5, b6, b7, b8 = swap8( | |
| sign + floor(expo / 0x10), | |
| (expo % 0x10) * 0x10 + floor(mant / 0x1000000000000), | |
| floor(mant / 0x10000000000) % 0x100, | |
| floor(mant / 0x100000000) % 0x100, | |
| floor(mant / 0x1000000) % 0x100, | |
| floor(mant / 0x10000) % 0x100, | |
| floor(mant / 0x100) % 0x100, | |
| mant % 0x100 | |
| ) | |
| end | |
| return char(b1, b2, b3, b4, b5, b6, b7, b8) | |
| end | |
| -- lua_Number = double | |
| packers[byte("n")] = packers[byte("d")] | |
| -- char | |
| packers[byte("c")] = function(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| return char((0x100 + data) % 0x100) | |
| end | |
| -- unsigned char | |
| packers[byte("b")] = pack_uchar | |
| -- short | |
| packers[byte("h")] = function(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| data = 0x10000 + data | |
| return char(swap2(floor(data / 0x100) % 0x100, data % 0x100)) | |
| end | |
| -- unsigned short | |
| packers[byte("H")] = pack_ushort | |
| -- int | |
| packers[byte("i")] = function(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| data = 0x100000000 + data | |
| return char(swap4( | |
| floor(data / 0x1000000) % 0x100, | |
| floor(data / 0x10000) % 0x100, | |
| floor(data / 0x100) % 0x100, | |
| data % 0x100 | |
| )) | |
| end | |
| -- unsigned int | |
| packers[byte("I")] = pack_uinteger | |
| -- long | |
| packers[byte("l")] = function(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| data = 0x10000000000000000 + data | |
| return char(swap8( | |
| floor(data / 0x100000000000000) % 0x100, | |
| floor(data / 0x1000000000000) % 0x100, | |
| floor(data / 0x10000000000) % 0x100, | |
| floor(data / 0x100000000) % 0x100, | |
| floor(data / 0x1000000) % 0x100, | |
| floor(data / 0x10000) % 0x100, | |
| floor(data / 0x100) % 0x100, | |
| data % 0x100 | |
| )) | |
| end | |
| -- unsigned long | |
| packers[byte("L")] = function(data) | |
| assert(type(data) == "number", "bad argument type, expected number") | |
| return char(swap8( | |
| floor(data / 0x100000000000000) % 0x100, | |
| floor(data / 0x1000000000000) % 0x100, | |
| floor(data / 0x10000000000) % 0x100, | |
| floor(data / 0x100000000) % 0x100, | |
| floor(data / 0x1000000) % 0x100, | |
| floor(data / 0x10000) % 0x100, | |
| floor(data / 0x100) % 0x100, | |
| data % 0x100 | |
| )) | |
| end | |
| -- little endian | |
| packers[byte("<")] = function(data) | |
| big_endian = false | |
| return true | |
| end | |
| -- big endian | |
| packers[byte(">")] = function(data) | |
| big_endian = true | |
| return true | |
| end | |
| -- native endian | |
| packers[byte("=")] = function(data) | |
| big_endian = false -- just a guess, find a way to do it properly | |
| return true | |
| end | |
| function string.unpack(data, fmt, offset) | |
| big_endian = false -- just a guess, find a way to do it properly | |
| format_number = 0 | |
| offset = offset or 1 | |
| local tunpacked = {} | |
| local nunpacked = 1 | |
| local c | |
| for i = 1, #fmt + 1 do | |
| local nc = byte(fmt, i) | |
| if nc == nil or unpackers[nc] ~= nil then | |
| if c ~= nil then | |
| format_number = format_number <= 0 and 1 or format_number | |
| while format_number > 0 do | |
| local unpacked, extra = unpackers[c](data, offset) | |
| if unpacked ~= true then | |
| tunpacked[nunpacked] = unpacked | |
| nunpacked = nunpacked + 1 | |
| offset = extra | |
| end | |
| format_number = format_number - 1 | |
| end | |
| end | |
| format_number = 0 | |
| c = nc | |
| elseif nc >= 48 and nc <= 57 then -- 0 to 9 | |
| format_number = format_number * 10 + nc - 48 | |
| else | |
| error(format("bad code '%c'", nc)) | |
| end | |
| end | |
| return offset, unpack(tunpacked) | |
| end | |
| function string.pack(fmt, ...) | |
| big_endian = false -- just a guess, find a way to do it properly | |
| format_number = 0 | |
| local pack = "" | |
| local index = 1 | |
| local c | |
| for i = 1, #fmt + 1 do | |
| local nc = byte(fmt, i) | |
| if nc == nil or packers[nc] ~= nil then | |
| if c ~= nil then | |
| format_number = format_number <= 0 and 1 or format_number | |
| while format_number > 0 do | |
| local data = select(index, ...) | |
| local packed = packers[c](data) | |
| if packed ~= true then | |
| pack = pack .. packed | |
| index = index + 1 | |
| end | |
| format_number = format_number - 1 | |
| end | |
| end | |
| format_number = 0 | |
| c = nc | |
| elseif nc >= 48 and nc <= 57 then -- 0 to 9 | |
| format_number = format_number * 10 + nc - 48 | |
| else | |
| error(format("bad code '%c'", nc)) | |
| end | |
| end | |
| return pack | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment