├── .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 | --------------------------------------------------------------------------------