├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── class.lua
├── enum.lua
├── kaitaistruct.lua
├── string_decode.lua
├── string_stream.lua
└── utils.lua
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Lua sources
2 | luac.out
3 |
4 | # luarocks build files
5 | *.src.rock
6 | *.zip
7 | *.tar.gz
8 |
9 | # Object files
10 | *.o
11 | *.os
12 | *.ko
13 | *.obj
14 | *.elf
15 |
16 | # Precompiled Headers
17 | *.gch
18 | *.pch
19 |
20 | # Libraries
21 | *.lib
22 | *.a
23 | *.la
24 | *.lo
25 | *.def
26 | *.exp
27 |
28 | # Shared objects (inc. Windows DLLs)
29 | *.dll
30 | *.so
31 | *.so.*
32 | *.dylib
33 |
34 | # Executables
35 | *.exe
36 | *.out
37 | *.app
38 | *.i*86
39 | *.x86_64
40 | *.hex
41 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "zzlib"]
2 | path = zzlib
3 | url = https://github.com/zerkman/zzlib
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017-2025 Kaitai Project: MIT license
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kaitai Struct: runtime library for Lua
2 |
3 | This library implements Kaitai Struct API for Lua 5.3 and 5.4.
4 |
5 | Kaitai Struct is a declarative language used for describe various binary
6 | data structures, laid out in files or in memory: i.e. binary file
7 | formats, network stream packet formats, etc.
8 |
9 | Further reading:
10 |
11 | * [About Kaitai Struct](http://kaitai.io/)
12 | * [About API implemented in this library](http://doc.kaitai.io/stream_api.html)
13 |
14 | ## Installation
15 |
16 | 1. You can clone the runtime library with Git:
17 |
18 |
git clone --recurse-submodules https://github.com/kaitai-io/kaitai_struct_lua_runtime.git
19 |
20 | If you clone without `--recurse-submodules`, the runtime library will work too, but you'll not be able to parse formats that use `process: zlib` - calling `KaitaiStream.process_zlib` will fail. If you need _zlib_ support but the runtime was cloned without `--recurse-submodules`, run:
21 |
22 | ```bash
23 | git submodule update --init --recursive
24 | ```
25 |
26 | 2. Or if you want to add the runtime library to your project as a Git submodule:
27 |
28 | ```bash
29 | git submodule add https://github.com/kaitai-io/kaitai_struct_lua_runtime.git []
30 | git submodule update --init --recursive
31 | ```
32 |
33 | The second command is only required if you need support for `process: zlib`.
34 |
35 | ## Licensing
36 |
37 | Copyright 2017-2025 Kaitai Project: MIT license
38 |
39 | Permission is hereby granted, free of charge, to any person obtaining
40 | a copy of this software and associated documentation files (the
41 | "Software"), to deal in the Software without restriction, including
42 | without limitation the rights to use, copy, modify, merge, publish,
43 | distribute, sublicense, and/or sell copies of the Software, and to
44 | permit persons to whom the Software is furnished to do so, subject to
45 | the following conditions:
46 |
47 | The above copyright notice and this permission notice shall be
48 | included in all copies or substantial portions of the Software.
49 |
50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
51 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
52 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
53 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
54 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
55 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
56 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
57 |
--------------------------------------------------------------------------------
/class.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- Adapted from http://lua-users.org/wiki/ObjectOrientationTutorial
3 | --
4 | -- The "class" function is called with one or more tables that specify the base
5 | -- classes.
6 | --
7 | -- If the user provides an "_init" method, this will be used as the class
8 | -- constructor.
9 | --
10 | -- Each class also provides Python-style properties, which are implemented as
11 | -- tables that provide a "get" and "set" method.
12 | --
13 |
14 | local Class = {}
15 |
16 | function Class.class(...)
17 | -- "cls" is the new class
18 | local cls, bases = {}, {...}
19 |
20 | -- Copy base class contents into the new class
21 | for _, base in ipairs(bases) do
22 | for k, v in pairs(base) do
23 | cls[k] = v
24 | end
25 | end
26 |
27 | -- Class also provides Python-style properties. These are implemented as
28 | -- tables with a "get" and "set" method
29 | cls.property = {}
30 |
31 | function cls:__index(key)
32 | local property = cls.property[key]
33 |
34 | if property then
35 | return property.get(self)
36 | else
37 | return cls[key]
38 | end
39 |
40 | return member
41 | end
42 |
43 | function cls:__newindex(key, value)
44 | local property = cls.property[key]
45 |
46 | if property then
47 | return property.set(self, value)
48 | else
49 | return rawset(self, key, value)
50 | end
51 | end
52 |
53 | -- Start filling an "is_a" table that contains this class and all of its
54 | -- bases so you can do an "instance of" check using
55 | -- my_instance.is_a[MyClass]
56 | cls.is_a = { [cls] = true }
57 | for i, base in ipairs(bases) do
58 | for c in pairs(base.is_a) do
59 | cls.is_a[c] = true
60 | end
61 | cls.is_a[base] = true
62 | end
63 |
64 | -- The class's __call metamethod
65 | setmetatable(cls, {
66 | __call = function(c, ...)
67 | local instance = setmetatable({}, c)
68 | -- Run the "init" method if it's there
69 | local init = instance._init
70 | if init then init(instance, ...) end
71 |
72 | return instance
73 | end
74 | })
75 |
76 | -- Return the new class table, that's ready to fill with methods
77 | return cls
78 | end
79 |
80 | return Class
81 |
--------------------------------------------------------------------------------
/enum.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- Each enum "value" is a table containing both a string label and an integer
3 | -- value. When two enums are compared these labels and values are individually
4 | -- compared.
5 | --
6 |
7 | local enum = {}
8 |
9 | function enum.Enum(t)
10 | local e = { _enums = {} }
11 |
12 | for k, v in pairs(t) do
13 | e._enums[k] = {
14 | label = k,
15 | value = v,
16 | }
17 | end
18 |
19 | return setmetatable(e, {
20 | __index = function(table, key)
21 | return rawget(table._enums, key)
22 | end,
23 |
24 | __call = function(table, value)
25 | for k, v in pairs(table._enums) do
26 | if v.value == value then
27 | return v
28 | end
29 | end
30 |
31 | return nil
32 | end,
33 |
34 | __eq = function(lhs, rhs)
35 | for k, v in pairs(lhs._enums) do
36 | if v ~= rhs._enums[k] then
37 | return false
38 | end
39 | end
40 |
41 | return true
42 | end
43 | })
44 | end
45 |
46 | return enum
47 |
--------------------------------------------------------------------------------
/kaitaistruct.lua:
--------------------------------------------------------------------------------
1 | local class = require("class")
2 | local stringstream = require("string_stream")
3 |
4 | KaitaiStruct = class.class()
5 |
6 | function KaitaiStruct:_init(io)
7 | self._io = io
8 | end
9 |
10 | function KaitaiStruct:close()
11 | self._io:close()
12 | end
13 |
14 | function KaitaiStruct:from_file(filename)
15 | local inp = assert(io.open(filename, "rb"))
16 |
17 | return self(KaitaiStream(inp))
18 | end
19 |
20 | function KaitaiStruct:from_string(s)
21 | local ss = stringstream(s)
22 |
23 | return self(KaitaiStream(ss))
24 | end
25 |
26 | KaitaiStream = class.class()
27 |
28 | function KaitaiStream:_init(io)
29 | self._io = io
30 | self:align_to_byte()
31 | end
32 |
33 | function KaitaiStream:close()
34 | self._io:close()
35 | end
36 |
37 | --=============================================================================
38 | -- Stream positioning
39 | --=============================================================================
40 |
41 | function KaitaiStream:is_eof()
42 | if self.bits_left > 0 then
43 | return false
44 | end
45 | local current = self._io:seek()
46 | local dummy = self._io:read(1)
47 | self._io:seek("set", current)
48 |
49 | return dummy == nil
50 | end
51 |
52 | function KaitaiStream:seek(n)
53 | self._io:seek("set", n)
54 | end
55 |
56 | function KaitaiStream:pos()
57 | return self._io:seek()
58 | end
59 |
60 | function KaitaiStream:size()
61 | local current = self._io:seek()
62 | local size = self._io:seek("end")
63 | self._io:seek("set", current)
64 |
65 | return size
66 | end
67 |
68 | --=============================================================================
69 | -- Integer numbers
70 | --=============================================================================
71 |
72 | -------------------------------------------------------------------------------
73 | -- Signed
74 | -------------------------------------------------------------------------------
75 |
76 | function KaitaiStream:read_s1()
77 | return string.unpack('b', self:read_bytes(1))
78 | end
79 |
80 | --.............................................................................
81 | -- Big-endian
82 | --.............................................................................
83 |
84 | function KaitaiStream:read_s2be()
85 | return string.unpack('>i2', self:read_bytes(2))
86 | end
87 |
88 | function KaitaiStream:read_s4be()
89 | return string.unpack('>i4', self:read_bytes(4))
90 | end
91 |
92 | function KaitaiStream:read_s8be()
93 | return string.unpack('>i8', self:read_bytes(8))
94 | end
95 |
96 | --.............................................................................
97 | -- Little-endian
98 | --.............................................................................
99 |
100 | function KaitaiStream:read_s2le()
101 | return string.unpack('I2', self:read_bytes(2))
126 | end
127 |
128 | function KaitaiStream:read_u4be()
129 | return string.unpack('>I4', self:read_bytes(4))
130 | end
131 |
132 | function KaitaiStream:read_u8be()
133 | return string.unpack('>I8', self:read_bytes(8))
134 | end
135 |
136 | --.............................................................................
137 | -- Little-endian
138 | --.............................................................................
139 |
140 | function KaitaiStream:read_u2le()
141 | return string.unpack('f', self:read_bytes(4))
162 | end
163 |
164 | function KaitaiStream:read_f8be()
165 | return string.unpack('>d', self:read_bytes(8))
166 | end
167 |
168 | -------------------------------------------------------------------------------
169 | -- Little-endian
170 | -------------------------------------------------------------------------------
171 |
172 | function KaitaiStream:read_f4le()
173 | return string.unpack(' 0 then
196 | -- 1 bit => 1 byte
197 | -- 8 bits => 1 byte
198 | -- 9 bits => 2 bytes
199 | local bytes_needed = math.ceil(bits_needed / 8)
200 | local buf = {self:read_bytes(bytes_needed):byte(1, bytes_needed)}
201 | for i = 1, bytes_needed do
202 | res = res << 8 | buf[i]
203 | end
204 |
205 | local new_bits = res
206 | res = res >> self.bits_left | self.bits << bits_needed
207 | self.bits = new_bits -- will be masked at the end of the function
208 | else
209 | res = self.bits >> -bits_needed -- shift unneeded bits out
210 | end
211 |
212 | local mask = (1 << self.bits_left) - 1 -- `bits_left` is in range 0..7
213 | self.bits = self.bits & mask
214 |
215 | return res
216 | end
217 |
218 | --
219 | -- Unused since Kaitai Struct Compiler v0.9+ - compatibility with older versions
220 | --
221 | -- Deprecated, use read_bits_int_be() instead.
222 | --
223 | function KaitaiStream:read_bits_int(n)
224 | return self:read_bits_int_be(n)
225 | end
226 |
227 | function KaitaiStream:read_bits_int_le(n)
228 | local res = 0
229 | local bits_needed = n - self.bits_left
230 |
231 | if bits_needed > 0 then
232 | -- 1 bit => 1 byte
233 | -- 8 bits => 1 byte
234 | -- 9 bits => 2 bytes
235 | local bytes_needed = math.ceil(bits_needed / 8)
236 | local buf = {self:read_bytes(bytes_needed):byte(1, bytes_needed)}
237 | for i = 1, bytes_needed do
238 | res = res | buf[i] << ((i - 1) * 8) -- NB: Lua uses 1-based indexing, but we need 0-based here
239 | end
240 |
241 | local new_bits = res >> bits_needed
242 | res = res << self.bits_left | self.bits
243 | self.bits = new_bits
244 | else
245 | res = self.bits
246 | self.bits = self.bits >> n
247 | end
248 |
249 | self.bits_left = -bits_needed % 8
250 |
251 | local mask = (1 << n) - 1 -- unlike some other languages, no problem with this in Lua
252 | res = res & mask
253 | return res
254 | end
255 |
256 | --=============================================================================
257 | -- Byte arrays
258 | --=============================================================================
259 |
260 | function KaitaiStream:read_bytes(n)
261 | local r = self._io:read(n)
262 | if r == nil then
263 | r = ""
264 | end
265 |
266 | if #r < n then
267 | error("requested " .. n .. " bytes, but only " .. #r .. " bytes available")
268 | end
269 |
270 | return r
271 | end
272 |
273 | function KaitaiStream:read_bytes_full()
274 | local r = self._io:read("*all")
275 | if r == nil then
276 | r = ""
277 | end
278 |
279 | return r
280 | end
281 |
282 | function KaitaiStream:read_bytes_term(term, include_term, consume_term, eos_error)
283 | local r = ""
284 |
285 | while true do
286 | local c = self._io:read(1)
287 |
288 | if c == nil then
289 | if eos_error then
290 | error("end of stream reached, but no terminator " .. term .. " found")
291 | end
292 |
293 | return r
294 | end
295 |
296 | if c:byte() == term then
297 | if include_term then
298 | r = r .. c
299 | end
300 |
301 | if not consume_term then
302 | self._io:seek("cur", -1)
303 | end
304 |
305 | return r
306 | end
307 |
308 | r = r .. c
309 | end
310 | end
311 |
312 | function KaitaiStream:read_bytes_term_multi(term, include_term, consume_term, eos_error)
313 | local unit_size = #term
314 | local r = ""
315 |
316 | while true do
317 | local c = self._io:read(unit_size)
318 |
319 | if c == nil then
320 | c = ""
321 | end
322 |
323 | if #c < unit_size then
324 | if eos_error then
325 | error("end of stream reached, but no terminator " .. term .. " found")
326 | end
327 |
328 | r = r .. c
329 | return r
330 | end
331 |
332 | if c == term then
333 | if include_term then
334 | r = r .. c
335 | end
336 |
337 | if not consume_term then
338 | self._io:seek("cur", -unit_size)
339 | end
340 |
341 | return r
342 | end
343 |
344 | r = r .. c
345 | end
346 | end
347 |
348 | function KaitaiStream:ensure_fixed_contents(expected)
349 | local actual = self:read_bytes(#expected)
350 |
351 | if actual ~= expected then
352 | error("unexpected fixed contents: got " .. actual .. ", was waiting for " .. expected)
353 | end
354 |
355 | return actual
356 | end
357 |
358 | function KaitaiStream.bytes_strip_right(src, pad_byte)
359 | local new_len = #src
360 |
361 | while new_len >= 1 and src:byte(new_len) == pad_byte do
362 | new_len = new_len - 1
363 | end
364 |
365 | return src:sub(1, new_len)
366 | end
367 |
368 | function KaitaiStream.bytes_terminate(src, term, include_term)
369 | local new_len = 1
370 | local max_len = #src
371 |
372 | while new_len <= max_len and src:byte(new_len) ~= term do
373 | new_len = new_len + 1
374 | end
375 |
376 | if include_term and new_len <= max_len then
377 | new_len = new_len + 1
378 | end
379 |
380 | return src:sub(1, new_len - 1)
381 | end
382 |
383 | function KaitaiStream.bytes_terminate_multi(src, term, include_term)
384 | local unit_size = #term
385 |
386 | for i = 1, #src, unit_size do
387 | if src:sub(i, i + unit_size - 1) == term then
388 | if include_term then
389 | i = i + unit_size
390 | end
391 | return src:sub(1, i - 1)
392 | end
393 | end
394 |
395 | return src
396 | end
397 |
398 | --=============================================================================
399 | -- Byte array processing
400 | --=============================================================================
401 |
402 | function KaitaiStream.process_xor_one(data, key)
403 | local r = ""
404 |
405 | for i = 1, #data do
406 | local c = data:byte(i) ~ key
407 | r = r .. string.char(c)
408 | end
409 |
410 | return r
411 | end
412 |
413 | function KaitaiStream.process_xor_many(data, key)
414 | local r = ""
415 | local kl = #key
416 | local ki = 1
417 |
418 | for i = 1, #data do
419 | local c = data:byte(i) ~ key:byte(ki)
420 | r = r .. string.char(c)
421 | ki = ki + 1
422 | if ki > kl then
423 | ki = 1
424 | end
425 | end
426 |
427 | return r
428 | end
429 |
430 | function KaitaiStream.process_rotate_left(data, amount, group_size)
431 | if group_size ~= 1 then
432 | error("unable to rotate group of " .. group_size .. " bytes yet")
433 | end
434 |
435 | local result = ""
436 | local mask = group_size * 8 - 1
437 | local anti_amount = -amount & mask
438 |
439 | for i = 1, #data do
440 | local c = data:byte(i)
441 | c = ((c << amount) & 0xFF) | (c >> anti_amount)
442 | result = result .. string.char(c)
443 | end
444 |
445 | return result
446 | end
447 |
448 | --=============================================================================
449 | -- zlib byte array processing
450 | --=============================================================================
451 |
452 | local zzlib, zzlib_load_err = (function()
453 | local old_pkg_path = package.path
454 | local old_pkg_loaded_keys = {}
455 |
456 | local load_err = nil
457 |
458 | -- check that the debug library is available, otherwise we can't resolve the script path
459 | if debug ~= nil then
460 | for key, _ in pairs(package.loaded) do
461 | old_pkg_loaded_keys[key] = true
462 | end
463 |
464 | if package.path:sub(1, 1) ~= ";" then
465 | package.path = ";" .. package.path
466 | end
467 | -- Get current script path - combined various suggestions from
468 | -- https://stackoverflow.com/a/35072122
469 | package.path = (debug.getinfo(2, "S").source:match("^@(.*[/\\])") or "") .. "zzlib/?.lua" .. package.path
470 | end
471 |
472 | local success, zzlib = pcall(function() return require("zzlib") end)
473 | if not success then
474 | load_err = zzlib
475 | zzlib = nil
476 | end
477 |
478 | if debug ~= nil then
479 | package.path = old_pkg_path
480 | for key, _ in pairs(package.loaded) do
481 | if not old_pkg_loaded_keys[key] then
482 | package.loaded[key] = nil
483 | end
484 | end
485 | end
486 |
487 | return zzlib, load_err
488 | end)()
489 |
490 | function KaitaiStream.process_zlib(data)
491 | if zzlib == nil then
492 | error("can't decompress zlib - failed to load submodule 'zzlib': " .. zzlib_load_err)
493 | end
494 | return zzlib.inflate(data)
495 | end
496 |
--------------------------------------------------------------------------------
/string_decode.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- String decoder functions
3 | --
4 |
5 | local stringdecode = {}
6 |
7 | -- From http://lua-users.org/wiki/LuaUnicode
8 | local function utf8_to_32(utf8str)
9 | assert(type(utf8str) == "string")
10 | local res, seq, val = {}, 0, nil
11 |
12 | for i = 1, #utf8str do
13 | local c = string.byte(utf8str, i)
14 | if seq == 0 then
15 | table.insert(res, val)
16 | seq = c < 0x80 and 1 or c < 0xE0 and 2 or c < 0xF0 and 3 or
17 | c < 0xF8 and 4 or --c < 0xFC and 5 or c < 0xFE and 6 or
18 | error("Invalid UTF-8 character sequence")
19 | val = c & (2^(8-seq) - 1)
20 | else
21 | val = (val << 6) | (c & 0x3F)
22 | end
23 |
24 | seq = seq - 1
25 | end
26 |
27 | table.insert(res, val)
28 |
29 | return res
30 | end
31 |
32 | function stringdecode.decode(str, encoding)
33 | local enc = encoding and encoding:lower() or "ascii"
34 |
35 | if enc == "ascii" then
36 | return str
37 | elseif enc == "utf-8" then
38 | local code_points = utf8_to_32(str)
39 |
40 | return utf8.char(table.unpack(code_points))
41 | else
42 | error("Encoding " .. encoding .. " not supported")
43 | end
44 | end
45 |
46 | return stringdecode
47 |
--------------------------------------------------------------------------------
/string_stream.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- A "string stream" class that provides file-like operations on a string.
3 | -- Inspired by https://gist.github.com/MikuAuahDark/e6428ac49248dd436f67c6c64fcec604
4 | --
5 |
6 | local class = require("class")
7 |
8 | local StringStream = class.class()
9 |
10 | function StringStream:_init(s)
11 | self._buf = s
12 | self._pos = 0
13 | end
14 |
15 | function StringStream:close()
16 | -- Nothing to do here
17 | end
18 |
19 | function StringStream:seek(whence, offset)
20 | local len = #self._buf
21 | whence = whence or "cur"
22 |
23 | if whence == "set" then
24 | self._pos = offset or 0
25 | elseif whence == "cur" then
26 | self._pos = self._pos + (offset or 0)
27 | elseif whence == "end" then
28 | self._pos = len + (offset or 0)
29 | else
30 | error("bad argument #1 to 'seek' (invalid option '" .. tostring(whence) .. "')", 2)
31 | end
32 |
33 | if self._pos < 0 then
34 | self._pos = 0
35 | elseif self._pos > len then
36 | self._pos = len
37 | end
38 |
39 | return self._pos
40 | end
41 |
42 | function StringStream:read(num)
43 | local len = #self._buf
44 |
45 | if num == "*all" then
46 | if self._pos == len then
47 | return nil
48 | end
49 |
50 | local ret = self._buf:sub(self._pos + 1)
51 | self._pos = len
52 |
53 | return ret
54 | elseif num <= 0 then
55 | return ""
56 | end
57 |
58 | local ret = self._buf:sub(self._pos + 1, self._pos + num)
59 |
60 | if #ret == 0 then
61 | return nil
62 | end
63 |
64 | self._pos = self._pos + num
65 | if self._pos > len then
66 | self._pos = len
67 | end
68 |
69 | return ret
70 | end
71 |
72 | function StringStream:pos()
73 | return self._pos
74 | end
75 |
76 | return StringStream
77 |
--------------------------------------------------------------------------------
/utils.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- Utility functions for KaitaiStruct
3 | --
4 |
5 | local utils = {}
6 |
7 | local function array_compare(arr, fn, subscript_fn)
8 | if #arr == 0 then
9 | return nil
10 | end
11 |
12 | local ret = subscript_fn(arr, 1)
13 |
14 | for i = 2, #arr do
15 | local el = subscript_fn(arr, i)
16 | if fn(el, ret) then
17 | ret = el
18 | end
19 | end
20 |
21 | return ret
22 | end
23 |
24 | local function array_subscript(arr, i)
25 | return arr[i]
26 | end
27 |
28 | local function bytes_subscript(str, i)
29 | return string.byte(str, i)
30 | end
31 |
32 | function utils.array_min(arr)
33 | return array_compare(arr, function(x, y) return x < y end, array_subscript)
34 | end
35 |
36 | function utils.array_max(arr)
37 | return array_compare(arr, function(x, y) return x > y end, array_subscript)
38 | end
39 |
40 | function utils.byte_array_min(str)
41 | return array_compare(str, function(x, y) return x < y end, bytes_subscript)
42 | end
43 |
44 | function utils.byte_array_max(str)
45 | return array_compare(str, function(x, y) return x > y end, bytes_subscript)
46 | end
47 |
48 | -- http://lua-users.org/wiki/TernaryOperator (section Boxing/unboxing, using functions)
49 |
50 | local False = {}
51 | local Nil = {}
52 |
53 | function utils.box_wrap(o)
54 | return o == nil and Nil or o == false and False or o
55 | end
56 |
57 | function utils.box_unwrap(o)
58 | if o == Nil then return nil
59 | elseif o == False then return false
60 | else return o end
61 | end
62 |
63 | return utils
64 |
--------------------------------------------------------------------------------