Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Last active August 26, 2023 13:28
Show Gist options
  • Save MCJack123/d5973e4d8b7e46991c5f99ac4b076aec to your computer and use it in GitHub Desktop.
Save MCJack123/d5973e4d8b7e46991c5f99ac4b076aec to your computer and use it in GitHub Desktop.
Backport of string.pack for Lua 5.2 [WIP]
-- MIT License
--
-- Copyright (c) 2021 JackMacWindows
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
-- If not using CC, replace `expect` with a suitable argument checking function.
--local expect = require "cc.expect".expect
local expect = dofile "/rom/modules/main/cc/expect.lua".expect
local ByteOrder = {BIG_ENDIAN = 1, LITTLE_ENDIAN = 2}
local isint = {b = 1, B = 1, h = 1, H = 1, l = 1, L = 1, j = 1, J = 1, T = 1}
local packoptsize_tbl = {b = 1, B = 1, x = 1, h = 2, H = 2, f = 4, j = 4, J = 4, l = 8, L = 8, T = 8, d = 8, n = 8}
local function round(n) if n % 1 >= 0.5 then return math.ceil(n) else return math.floor(n) end end
local function floatToRawIntBits(f)
if f == 0 then return 0
elseif f == -0 then return 0x80000000
elseif f == math.huge then return 0x7F800000
elseif f == -math.huge then return 0xFF800000 end
local m, e = math.frexp(f)
if e > 127 or e < -126 then error("number out of range", 3) end
e, m = e + 126, round((math.abs(m) - 0.5) * 0x1000000)
if m > 0x7FFFFF then e = e + 1 end
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0xFF), 23), bit32.band(m, 0x7FFFFF))
end
local function doubleToRawLongBits(f)
if f == 0 then return 0, 0
elseif f == -0 then return 0x80000000, 0
elseif f == math.huge then return 0x7FF00000, 0
elseif f == -math.huge then return 0xFFF00000, 0 end
local m, e = math.frexp(f)
if e > 1023 or e < -1022 then error("number out of range", 3) end
e, m = e + 1022, round((math.abs(m) - 0.5) * 0x20000000000000)
if m > 0xFFFFFFFFFFFFF then e = e + 1 end
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0x7FF), 20), bit32.band(m / 0x100000000, 0xFFFFF)), bit32.band(m, 0xFFFFFFFF)
end
local function intBitsToFloat(l)
if l == 0 then return 0
elseif l == 0x80000000 then return -0
elseif l == 0x7F800000 then return math.huge
elseif l == 0xFF800000 then return -math.huge end
local m, e = bit32.band(l, 0x7FFFFF), bit32.band(bit32.rshift(l, 23), 0xFF)
e, m = e - 126, m / 0x1000000 + 0.5
local n = math.ldexp(m, e)
return bit32.btest(l, 0x80000000) and -n or n
end
local function longBitsToDouble(lh, ll)
if lh == 0 and ll == 0 then return 0
elseif lh == 0x80000000 and ll == 0 then return -0
elseif lh == 0x7FF00000 and ll == 0 then return math.huge
elseif lh == 0xFFF00000 and ll == 0 then return -math.huge end
local m, e = bit32.band(lh, 0xFFFFF) * 0x100000000 + bit32.band(ll, 0xFFFFFFFF), bit32.band(bit32.rshift(lh, 20), 0x7FF)
e, m = e - 1022, m / 0x20000000000000 + 0.5
local n = math.ldexp(m, e)
return bit32.btest(lh, 0x80000000) and -n or n
end
local function packint(num, size, output, offset, alignment, endianness, signed)
local total_size = 0
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
local i = 0
while offset % math.min(size, alignment) ~= 0 and i < alignment do
output[offset] = 0
offset = offset + 1
total_size = total_size + 1
i = i + 1
end
end
if endianness == ByteOrder.BIG_ENDIAN then
local added_padding = 0
if size > 8 then for i = 0, size - 9 do
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
added_padding = added_padding + 1
total_size = total_size + 1
end end
for i = added_padding, size - 1 do
output[offset + i] = bit32.band(bit32.rshift(num, ((size - i - 1) * 8)), 0xFF)
total_size = total_size + 1
end
else
for i = 0, math.min(size, 8) - 1 do
output[offset + i] = num / 2^(i * 8) % 256
total_size = total_size + 1
end
for i = 8, size - 1 do
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
total_size = total_size + 1
end
end
return total_size
end
local function unpackint(str, offset, size, endianness, alignment, signed)
local result, rsize = 0, 0
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
for i = 0, alignment - 1 do
if offset % math.min(size, alignment) == 0 then break end
offset = offset + 1
rsize = rsize + 1
end
end
for i = 0, size - 1 do
result = result + str:byte(offset + i) * 2^((endianness == ByteOrder.BIG_ENDIAN and size - i - 1 or i) * 8)
rsize = rsize + 1
end
if (signed and result >= 2^(size * 8 - 1)) then result = result - 2^(size * 8) end
return result, rsize
end
local function packoptsize(opt, alignment)
local retval = packoptsize_tbl[opt] or 0
if (alignment > 1 and retval % alignment ~= 0) then retval = retval + (alignment - (retval % alignment)) end
return retval
end
--[[
* string.pack (fmt, v1, v2, ...)
*
* Returns a binary string containing the values v1, v2, etc.
* serialized in binary form (packed) according to the format string fmt.
]]
local function pack(...)
local fmt = expect(1, ..., "string")
local endianness = ByteOrder.LITTLE_ENDIAN
local alignment = 1
local pos = 1
local argnum = 2
local output = {}
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '=' or c == '<' then
endianness = ByteOrder.LITTLE_ENDIAN
elseif c == '>' then
endianness = ByteOrder.BIG_ENDIAN
elseif c == '!' then
local size = -1
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == -1) then alignment = 4
else alignment = size end
elseif isint[c] then
local num = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
if (num >= math.pow(2, (packoptsize(c, 0) * 8 - (c:match("%l") and 1 or 0))) or
num < (c:match("%l") and -math.pow(2, (packoptsize(c, 0) * 8 - 1)) or 0)) then
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
end
pos = pos + packint(num, packoptsize(c, 0), output, pos, alignment, endianness, false)
elseif c:lower() == 'i' then
local signed = c == 'i'
local size = -1
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
elseif (size == -1) then size = 4 end
local num = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
if (num >= math.pow(2, (size * 8 - (c:match("%l") and 1 or 0))) or
num < (c:match("%l") and -math.pow(2, (size * 8 - 1)) or 0)) then
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
end
pos = pos + packint(num, size, output, pos, alignment, endianness, signed)
elseif c == 'f' then
local f = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
local l = floatToRawIntBits(f)
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 0, alignment - 1 do
if pos % math.min(4, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 3 - j or j)] = bit32.band(bit32.rshift(l, (j * 8)), 0xFF) end
pos = pos + 4
elseif c == 'd' or c == 'n' then
local f = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
local lh, ll = doubleToRawLongBits(f)
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 0, alignment - 1 do
if pos % math.min(8, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(ll, (j * 8)), 0xFF) end
for j = 4, 7 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(lh, ((j - 4) * 8)), 0xFF) end
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size < pos or pos + size > 0xFFFFFFFF) then error("bad argument #1 to 'pack' (format result too large)", 2) end
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
if (#str > size) then error(string.format("bad argument #%d to 'pack' (string longer than given size)", argnum - 1), 2) end
if size > 0 then
for j = 0, size - 1 do output[pos+j] = str:byte(j + 1) or 0 end
pos = pos + size
end
elseif c == 'z' then
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
for b in str:gmatch "." do if (b == '\0') then error(string.format("bad argument #%d to 'pack' (string contains zeros)", argnum - 1), 2) end end
for j = 0, #str - 1 do output[pos+j] = str:byte(j + 1) end
output[pos + #str] = 0
pos = pos + #str + 1
elseif c == 's' then
local size = 0
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then size = 4 end
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
if (#str >= math.pow(2, (size * 8))) then
error(string.format("bad argument #%d to 'pack' (string length does not fit in given size)", argnum - 1), 2)
end
packint(#str, size, output, pos, 1, endianness, false)
for j = size, #str + size - 1 do output[pos+j] = str:byte(j - size + 1) or 0 end
pos = pos + #str + size
elseif c == 'x' then
output[pos] = 0
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
end
return string.char(table.unpack(output))
end
--[[
* string.packsize (fmt)
*
* Returns the size of a string resulting from string.pack with the given format.
* The format string cannot have the variable-length options 's' or 'z'.
]]
local function packsize(fmt)
local pos = 0
local alignment = 1
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '!' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then alignment = 4
else alignment = size end
elseif isint[c] then
local size = packoptsize(c, 0)
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + size
elseif c:lower() == 'i' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size))
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
elseif (size == 0) then size = 4 end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + size
elseif c == 'f' then
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(4, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + 4
elseif c == 'd' or c == 'n' then
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(8, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size < pos or pos + size > 0x7FFFFFFF) then error("bad argument #1 to 'packsize' (format result too large)", 2) end
pos = pos + size
elseif c == 'x' then
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
elseif c == 's' or c == 'z' then error("bad argument #1 to 'packsize' (variable-length format)", 2)
elseif c ~= ' ' and c ~= '<' and c ~= '>' and c ~= '=' then error(string.format("invalid format option '%s'", c), 2) end
end
return pos
end
--[[
* string.unpack (fmt, s [, pos])
*
* Returns the values packed in string s (see string.pack) according to the format string fmt.
* An optional pos marks where to start reading in s (default is 1).
* After the read values, this function also returns the index of the first unread byte in s.
]]
local function unpack(fmt, str, pos)
expect(1, fmt, "string")
expect(2, str, "string")
expect(3, pos, "number", "nil")
if pos then
if (pos < 0) then pos = #str + pos
elseif (pos == 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
if (pos > #str or pos < 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
else pos = 1 end
local endianness = ByteOrder.LITTLE_ENDIAN
local alignment = 1
local retval = {}
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '<' or c == '=' then
endianness = ByteOrder.LITTLE_ENDIAN
elseif c == '>' then
endianness = ByteOrder.BIG_ENDIAN
elseif c == '!' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size))
elseif (size == 0) then alignment = 4
else alignment = size end
elseif isint[c] then
if (pos + packoptsize(c, 0) > #str + 1) then error("data string too short", 2) end
local res, ressz = unpackint(str, pos, packoptsize(c, 0), endianness, alignment, c:match("%l") ~= nil)
retval[#retval+1] = res
pos = pos + ressz
elseif c:lower() == 'i' then
local signed = c == 'i'
local size = 0
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size > 8) then
error(string.format("%d-byte integer does not fit into Lua Integer", size), 2)
elseif (size == 0) then size = 4 end
if (pos + size > #str + 1) then error("data string too short", 2) end
local res, ressz = unpackint(str, pos, size, endianness, alignment, signed)
retval[#retval+1] = res
pos = pos + ressz
elseif c == 'f' then
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(4, alignment) == 0 then break end
pos = pos + 1
end
end
if (pos + 4 > #str + 1) then error("data string too short", 2) end
local res = unpackint(str, pos, 4, endianness, alignment, false)
retval[#retval+1] = intBitsToFloat(res)
pos = pos + 4
elseif c == 'd' or c == 'n' then
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(8, alignment) == 0 then break end
pos = pos + 1
end
end
if (pos + 8 > #str + 1) then error("data string too short", 2) end
local lh, ll = 0, 0
for j = 0, 3 do lh = bit32.bor(lh, bit32.lshift((str:byte(pos + j)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
for j = 0, 3 do ll = bit32.bor(ll, bit32.lshift((str:byte(pos + j + 4)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
if endianness == ByteOrder.LITTLE_ENDIAN then lh, ll = ll, lh end
retval[#retval+1] = longBitsToDouble(lh, ll)
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)") end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size > #str + 1) then error("data string too short", 2) end
retval[#retval+1] = str:sub(pos, pos + size - 1)
pos = pos + size
elseif c == 'z' then
local size = 0
while (str:byte(pos + size) ~= 0) do
size = size + 1
if (pos + size > #str) then error("unfinished string for format 'z'", 2) end
end
retval[#retval+1] = str:sub(pos, pos + size - 1)
pos = pos + size + 1
elseif c == 's' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then size = 4 end
if (pos + size > #str + 1) then error("data string too short", 2) end
local num, numsz = unpackint(str, pos, size, endianness, alignment, false)
pos = pos + numsz
if (pos + num > #str + 1) then error("data string too short", 2) end
retval[#retval+1] = str:sub(pos, pos + num - 1)
pos = pos + num
elseif c == 'x' then
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == -1) then size = 4 end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
end
retval[#retval+1] = pos
return table.unpack(retval)
end
return {
pack = pack,
packsize = packsize,
unpack = unpack
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment