Add binary operations for lua

This is something I was always missing - posibbility to operate on
binary files or streams in pure lua. In most cases we do only need to
read simple variables from files such as integers with different amount
of bytes. This "class" will provide that ability.

It's a simple implementation of following C module for lua:
http://www.inf.puc-rio.br/~roberto/struct/

It has much less, though. Following elements have been implemented:

">" flag to set mode to big endian.
"<" flag to set mode to little endian.
"b" a signed char.
"B" an unsigned char.
"h" a signed short (2 bytes).
"H" an unsigned short (2 bytes).
"i" a signed int (4 bytes).
"I" an unsigned int (4 bytes).
"l" a signed long (8 bytes).
"L" an unsigned long (8 bytes).
"s" a zero-terminated string.

An example how to use it:

```lua
local packed = Struct.pack('<LIhBsb', 123456789123456789, 123456789,
-3200, 255, 'Test message', -1)
-- packed is now a lua string we can save to file as binary data
local L, I, h, B, s, b = Struct.unpack('<LIhBsb', packed)
print(L, I, h, B, s, b)
```

You can use g_resources.readFileContents as function to read binary
files and parse them via this class.
This commit is contained in:
Konrad Kuśnierz 2015-05-18 21:31:16 +02:00
parent 0597ded1d3
commit 7ea6c46b2c
2 changed files with 87 additions and 0 deletions

View File

@ -10,6 +10,7 @@ Module
dofile 'string'
dofile 'table'
dofile 'bitwise'
dofile 'struct'
dofile 'const'
dofile 'util'

View File

@ -0,0 +1,86 @@
Struct = {}
function Struct.pack(format, ...)
local stream = ''
local vars = {...}
local endianness = true
for i = 1, string.len(format) do
local opt = string.sub(format, i, i)
if opt == '<' or opt == '>' then
endianness = opt == '<' and true or false
elseif opt == 'b' or opt == 'B' or opt == 'h' or opt == 'H' or opt == 'i' or opt == 'I' or opt == 'l' or opt == 'L' then
local val = tonumber(table.remove(vars, 1))
local n = ((opt == 'h' or opt == 'H') and 2) or ((opt == 'i' or opt == 'I') and 4) or ((opt == 'l' or opt == 'L') and 8) or 1
if val < 0 then
val = val + 2 ^ (n * 8 - 1)
end
local binary = ''
for j = 1, n do
binary = binary .. string.char(val % (2 ^ 8))
val = math.floor(val / (2 ^ 8))
end
if not endianness then
binary = string.reverse(binary)
end
stream = stream .. binary
elseif opt == 's' then
stream = stream .. tostring(table.remove(vars, 1))
stream = stream .. string.char(0)
end
end
return stream
end
function Struct.unpack(format, stream)
local vars = {}
local endianness = true
for i = 1, string.len(format) do
local opt = string.sub(format, i, i)
if opt == '<' or opt == '>' then
endianness = opt == '<' and true or false
elseif opt == 'b' or opt == 'B' or opt == 'h' or opt == 'H' or opt == 'i' or opt == 'I' or opt == 'l' or opt == 'L' then
local n = ((opt == 'h' or opt == 'H') and 2) or ((opt == 'i' or opt == 'I') and 4) or ((opt == 'l' or opt == 'L') and 8) or 1
local signed = opt == 'b' or opt == 'h' or opt == 'i'
local val = 0
for j = 1, n do
local byte = string.byte(string.sub(stream, 1, 1))
if endianness then
val = val + byte * (2 ^ ((j - 1) * 8))
else
val = val + byte * (2 ^ ((n - j) * 8))
end
stream = string.sub(stream, 2)
end
if signed then
val = val - 2 ^ (n * 8 - 1)
end
table.insert(vars, val)
elseif opt == 's' then
local str = ''
for j = 1, string.len(stream) do
if string.sub(stream, j, j) == string.char(0) then
break
end
str = str .. string.sub(stream, j, j)
end
stream = string.sub(stream, string.len(str) + 2)
table.insert(vars, str)
end
end
return unpack(vars)
end