--- /dev/null
+clib/%.o: clib/%.c clib/*.h
+ gcc -DLUA51_TARGET -O2 -o $@ -fPIC -c $< -llua
+
+clib/%.so: clib/%.o
+ gcc -shared -o $@ $< -llua
+
+all: clib/parse_helper.so
--- /dev/null
+#ifndef BUFFERED_FILE_H
+#define BUFFERED_FILE_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#define BUFFERED_FILE_BUFF_SIZE 4096
+typedef struct {
+ FILE* fp;
+ int buff_ptr;
+ char buff[BUFFERED_FILE_BUFF_SIZE];
+} buffered_file;
+
+buffered_file* buffered_file_open(const char* filepath) {
+ buffered_file* bf = (buffered_file*) malloc(sizeof(buffered_file));
+
+ bf->fp = fopen(filepath, "r");
+ if (bf->fp == NULL) {
+ return NULL;
+ }
+ bf->buff_ptr = BUFFERED_FILE_BUFF_SIZE;
+
+ return bf;
+}
+
+bool buffered_file_empty(buffered_file* bf) {
+ return bf->buff_ptr >= BUFFERED_FILE_BUFF_SIZE && feof(bf->fp);
+}
+
+unsigned char buffered_file_read_byte(buffered_file* bf) {
+ if (buffered_file_empty(bf)) return 0;
+
+ if (bf->buff_ptr >= BUFFERED_FILE_BUFF_SIZE) {
+ // Need to read more data
+ fread(bf->buff, BUFFERED_FILE_BUFF_SIZE, 1, bf->fp);
+ bf->buff_ptr = 0;
+ }
+
+ return bf->buff[bf->buff_ptr++];
+}
+
+unsigned char buffered_file_peek_byte(buffered_file* bf) {
+ if (buffered_file_empty(bf)) return 0;
+
+ if (bf->buff_ptr >= BUFFERED_FILE_BUFF_SIZE) {
+ // Need to read more data
+ fread(bf->buff, BUFFERED_FILE_BUFF_SIZE, 1, bf->fp);
+ bf->buff_ptr = 0;
+ }
+
+ return bf->buff[bf->buff_ptr];
+}
+
+
+#endif
--- /dev/null
+#include <stdio.h>
+#ifdef LUA51_TARGET
+ #include <lua5.1/lua.h>
+ #include <lua5.1/lualib.h>
+ #include <lua5.1/lauxlib.h>
+#else
+ #include <lua.h>
+ #include <lualib.h>
+ #include <lauxlib.h>
+#endif
+
+#include "buffered_file.h"
+
+#define new(class, var) class* var = (class*) malloc(sizeof(class));
+#define LUA_FUNCTION(fname) int fname(lua_State *L)
+
+typedef struct {
+ buffered_file* file;
+} Parser;
+
+LUA_FUNCTION(open_file) {
+ const char *filepath = lua_tostring(L, 1);
+
+ new(Parser, parser);
+ parser->file = buffered_file_open(filepath);
+
+ lua_pushlightuserdata(L, parser);
+ return 1;
+}
+
+LUA_FUNCTION(is_file_empty) {
+ Parser* parser = (Parser*) lua_touserdata(L, 1);
+
+ lua_pushboolean(L, buffered_file_empty(parser->file) == 1);
+ return 1;
+}
+
+LUA_FUNCTION(read_byte) {
+ Parser* parser = (Parser*) lua_touserdata(L, 1);
+
+ unsigned char byte = buffered_file_read_byte(parser->file);
+
+ lua_pushinteger(L, byte);
+ return 1;
+}
+
+LUA_FUNCTION(peek_byte) {
+ Parser* parser = (Parser*) lua_touserdata(L, 1);
+
+ unsigned char byte = buffered_file_peek_byte(parser->file);
+
+ lua_pushinteger(L, byte);
+ return 1;
+}
+
+LUA_FUNCTION(parse_uleb128) {
+ Parser* parser = (Parser*) lua_touserdata(L, 1);
+
+ unsigned long long result = 0;
+ int shift = 0;
+ unsigned char byte;
+
+ while (1) {
+ byte = buffered_file_read_byte(parser->file);
+ result |= (byte & 0x7f) << shift;
+ if ((byte & 0x80) == 0)
+ break;
+
+ shift += 7;
+ }
+ lua_pushnumber(L, result);
+ return 1;
+}
+
+LUA_FUNCTION(parse_sleb128) {
+ Parser* parser = (Parser*) lua_touserdata(L, 1);
+ int size = lua_tointeger(L, 2);
+
+ long long result = 0;
+ int shift = 0;
+
+ unsigned char byte;
+
+ do {
+ byte = buffered_file_read_byte(parser->file);
+ result |= (byte & 0x7f) << shift;
+ shift += 7;
+ } while ((byte & 0x80) != 0);
+
+ if ((shift < size) && (byte & 0x40) > 0)
+ result |= (~0ll << shift);
+
+ lua_pushnumber(L, (double) result);
+ return 1;
+}
+
+int luaopen_parse_helper(lua_State* L) {
+ const luaL_Reg functions[] = {
+ { "open_file", open_file },
+ { "read_byte", read_byte },
+ { "peek_byte", peek_byte },
+ { "is_file_empty", is_file_empty },
+
+ { "parse_uleb128", parse_uleb128 },
+ { "parse_sleb128", parse_sleb128 },
+ { NULL, NULL },
+ };
+
+#ifdef LUA51_TARGET
+ luaL_register(L, "parse_helper", functions);
+#else
+ luaL_newlib(L, functions);
+#endif
+ return 1;
+}
--- /dev/null
+function love.conf(t)
+ t.window.title = "TBD"
+ t.window.width = 1200
+ t.window.height = 900
+ t.window.resizable = false
+end
--- /dev/null
+À»x
\ No newline at end of file
--- /dev/null
+-- Recursive decent parser for the WASM v1.0 binary format
+-- Brendan Hansen 2020
+
+-- Look in clib folder for C-based libraries
+package.cpath = package.cpath .. [[;./clib/?.so]]
+
+import {
+ parse_helper = "parse_helper";
+ pprint = "lualib.pprint";
+
+ build_str = "utils:build_str";
+}
+
+VAL_TYPES = {
+ i32 = "i32",
+ i64 = "i64",
+ f32 = "f32",
+ f64 = "f64"
+}
+
+function parse_valtype(r)
+ local val = r:read_byte()
+
+ local valtypes_map = {
+ [0x7f] = VAL_TYPES.i32;
+ [0x7E] = VAL_TYPES.i64;
+ [0x7D] = VAL_TYPES.f32;
+ [0x7C] = VAL_TYPES.f64;
+ }
+
+ return valtypes_map[val]
+end
+
+function parse_vector(tp)
+ return function(r)
+ local n = r:read_uint(32)
+
+ local v = {}
+ for i=1, n do
+ table.insert(v, tp(r))
+ end
+
+ return v
+ end
+end
+
+function parse_byte(r)
+ return r:read_byte()
+end
+
+function parse_name(r)
+ local name = parse_vector(parse_byte)(r)
+ return name
+end
+
+function parse_blocktype(r)
+ if r:peek_byte() == 0x40 then
+ r:read_byte()
+ return {}
+ else
+ return { parse_valtype(r) }
+ end
+end
+
+function parse_functype(r)
+ assert(r:read_byte() == 0x60, "functype expected 0x60 as first byte")
+
+ local params = parse_vector(parse_valtype)(r)
+ local results = parse_vector(parse_valtype)(r)
+
+ return { param_types = params, result_types = results }
+end
+
+function parse_limits(r)
+ local t = r:read_byte()
+
+ local min = r:read_uint(32)
+ local max = nil
+ if t == 0x01 then
+ max = r:read_uint(32)
+ end
+
+ return { min = min, max = max }
+end
+
+function parse_memtype(r)
+ local lims = parse_limits(r)
+
+ return lims
+end
+
+function parse_tabletype(r)
+ local elemtype = parse_elemtype(r)
+ local limits = parse_limits(r)
+
+ return { lim = limits, et = elemtype }
+end
+
+function parse_elemtype(r)
+ assert(r:read_byte() == 0x70, "elemtype should be 0x70")
+ return "funcref"
+end
+
+function parse_globaltype(r)
+ local valtype = parse_valtype(r)
+ local ismut = parse_mut(r) == 0x01
+
+ return { t = valtype, m = ismut }
+end
+
+function parse_mut(r)
+ local v = r:read_byte()
+ assert(v == 0x00 or v == 0x01, "mut should be 0x00 or 0x01")
+ return v
+end
+
+function parse_instr(r)
+ local instr = r:read_byte()
+
+ return match(instr) {
+ [0x00] = function() return { "unreachable" } end;
+ [0x01] = function() return { "nop" } end;
+ [0x02] = function()
+ local rt = parse_blocktype(r)
+ local instrs = {}
+
+ while true do
+ if r:peek_byte() == 0x0B then
+ r:read_byte()
+ break
+ else
+ table.insert(instrs, parse_instr(r))
+ end
+ end
+
+ return { "block", rt = rt, instrs = instrs }
+ end;
+ [0x03] = function()
+ local rt = parse_blocktype(r)
+ local instrs = {}
+
+ while true do
+ if r:peek_byte() == 0x0B then
+ r:read_byte()
+ break
+ else
+ table.insert(instrs, parse_instr(r))
+ end
+ end
+
+ return { "loop", rt = rt, instrs = instrs }
+ end;
+ [0x04] = function()
+ local rt = parse_blocktype(r)
+ local instrs = {}
+ local else_instrs = {}
+ local inelse = false
+
+ while true do
+ local peek = r:peek_byte()
+ if peek == 0x0B then
+ r:read_byte()
+ break
+ elseif peek == 0x05 then
+ r:read_byte()
+ inelse = true
+ else
+ if not inelse then
+ table.insert(instrs, parse_instr(r))
+ else
+ table.insert(else_instrs, parse_instr(r))
+ end
+ end
+ end
+
+ return { "if", rt = rt, instrs = instrs, else_instrs = else_instrs }
+ end;
+
+ [0x0C] = function() return { "br", x = parse_labelidx(r) } end;
+ [0x0D] = function() return { "br_if", x = parse_labelidx(r) } end;
+ [0x0E] = function()
+ local labels = parse_vector(parse_labelidx)(r)
+ local labeln = parse_labelidx(r)
+
+ return { "br_table", labels = labels, labeln = labeln }
+ end;
+
+ [0x0F] = function() return { "return" } end;
+
+ [0x10] = function() return { "call", x = parse_funcidx(r) } end;
+ [0x11] = function()
+ local x = parse_typeidx(r)
+ assert(r:read_byte() ~= 0x00, "call_indirect expects 0x00 at end")
+ return { "call_indirect", x = x }
+ end;
+
+ [0x1A] = function() return { "drop" } end;
+ [0x1B] = function() return { "select" } end;
+
+ [0x20] = function() return { "local.get", x = parse_localidx(r) } end;
+ [0x21] = function() return { "local.set", x = parse_localidx(r) } end;
+ [0x22] = function() return { "local.tee", x = parse_localidx(r) } end;
+ [0x23] = function() return { "global.get", x = parse_globalidx(r) } end;
+ [0x24] = function() return { "global.set", x = parse_globalidx(r) } end;
+
+ [0x28] = function() return { "i32.load", x = parse_memarg(r) } end;
+ [0x29] = function() return { "i64.load", x = parse_memarg(r) } end;
+ [0x2A] = function() return { "f32.load", x = parse_memarg(r) } end;
+ [0x2B] = function() return { "f64.load", x = parse_memarg(r) } end;
+ [0x2C] = function() return { "i32.load8_s", x = parse_memarg(r) } end;
+ [0x2D] = function() return { "i32.load8_u", x = parse_memarg(r) } end;
+ [0x2E] = function() return { "i32.load16_s", x = parse_memarg(r) } end;
+ [0x2F] = function() return { "i32.load16_u", x = parse_memarg(r) } end;
+ [0x30] = function() return { "i64.load8_s", x = parse_memarg(r) } end;
+ [0x31] = function() return { "i64.load8_u", x = parse_memarg(r) } end;
+ [0x32] = function() return { "i64.load16_s", x = parse_memarg(r) } end;
+ [0x33] = function() return { "i64.load16_u", x = parse_memarg(r) } end;
+ [0x34] = function() return { "i64.load32_s", x = parse_memarg(r) } end;
+ [0x35] = function() return { "i64.load32_u", x = parse_memarg(r) } end;
+ [0x36] = function() return { "i32.store", x = parse_memarg(r) } end;
+ [0x37] = function() return { "i64.store", x = parse_memarg(r) } end;
+ [0x38] = function() return { "f32.store", x = parse_memarg(r) } end;
+ [0x39] = function() return { "f64.store", x = parse_memarg(r) } end;
+ [0x3A] = function() return { "i32.store8", x = parse_memarg(r) } end;
+ [0x3B] = function() return { "i32.store16", x = parse_memarg(r) } end;
+ [0x3C] = function() return { "i64.store8", x = parse_memarg(r) } end;
+ [0x3D] = function() return { "i64.store16", x = parse_memarg(r) } end;
+ [0x3E] = function() return { "i64.store32", x = parse_memarg(r) } end;
+ [0x3F] = function()
+ assert(r:read_byte() == 0x00, "memory.size expects 0x00")
+ return { "memory.size" }
+ end;
+ [0x40] = function()
+ assert(r:read_byte() == 0x00, "memory.grow expects 0x00")
+ return { "memory.grow" }
+ end;
+
+ [0x41] = function() return { "i32.const", x = r:read_sint(32) } end;
+ [0x42] = function() return { "i64.const", x = r:read_sint(64) } end;
+ [0x43] = function() return { "f32.const", x = r:read_float(32) } end;
+ [0x44] = function() return { "f64.const", x = r:read_float(64) } end;
+
+ [0x45] = function() return { "i32.eqz" } end;
+ [0x46] = function() return { "i32.eq" } end;
+ [0x47] = function() return { "i32.ne" } end;
+ [0x48] = function() return { "i32.lt_s" } end;
+ [0x49] = function() return { "i32.lt_u" } end;
+ [0x4A] = function() return { "i32.gt_s" } end;
+ [0x4B] = function() return { "i32.gt_u" } end;
+ [0x4C] = function() return { "i32.le_s" } end;
+ [0x4D] = function() return { "i32.le_u" } end;
+ [0x4E] = function() return { "i32.ge_s" } end;
+ [0x4F] = function() return { "i32.ge_u" } end;
+ [0x50] = function() return { "i64.eqz" } end;
+ [0x51] = function() return { "i64.eq" } end;
+ [0x52] = function() return { "i64.ne" } end;
+ [0x53] = function() return { "i64.lt_s" } end;
+ [0x54] = function() return { "i64.lt_u" } end;
+ [0x55] = function() return { "i64.gt_s" } end;
+ [0x56] = function() return { "i64.gt_u" } end;
+ [0x57] = function() return { "i64.le_s" } end;
+ [0x58] = function() return { "i64.le_u" } end;
+ [0x59] = function() return { "i64.ge_s" } end;
+ [0x5A] = function() return { "i64.ge_u" } end;
+ [0x5B] = function() return { "f32.eq" } end;
+ [0x5C] = function() return { "f32.ne" } end;
+ [0x5D] = function() return { "f32.lt" } end;
+ [0x5E] = function() return { "f32.gt" } end;
+ [0x5F] = function() return { "f32.le" } end;
+ [0x60] = function() return { "f32.ge" } end;
+ [0x61] = function() return { "f64.eq" } end;
+ [0x62] = function() return { "f64.ne" } end;
+ [0x63] = function() return { "f64.lt" } end;
+ [0x64] = function() return { "f64.gt" } end;
+ [0x65] = function() return { "f64.le" } end;
+ [0x66] = function() return { "f64.ge" } end;
+ [0x67] = function() return { "i32.clz" } end;
+ [0x68] = function() return { "i32.ctz" } end;
+ [0x69] = function() return { "i32.popcnt" } end;
+ [0x6A] = function() return { "i32.add" } end;
+ [0x6B] = function() return { "i32.sub" } end;
+ [0x6C] = function() return { "i32.mul" } end;
+ [0x6D] = function() return { "i32.div_s" } end;
+ [0x6E] = function() return { "i32.div_u" } end;
+ [0x6F] = function() return { "i32.rem_s" } end;
+ [0x70] = function() return { "i32.rem_u" } end;
+ [0x71] = function() return { "i32.and" } end;
+ [0x72] = function() return { "i32.or" } end;
+ [0x73] = function() return { "i32.xor" } end;
+ [0x74] = function() return { "i32.shl" } end;
+ [0x75] = function() return { "i32.shr_s" } end;
+ [0x76] = function() return { "i32.shr_u" } end;
+ [0x77] = function() return { "i32.rotl" } end;
+ [0x78] = function() return { "i32.rotr" } end;
+ [0x79] = function() return { "i64.clz" } end;
+ [0x7A] = function() return { "i64.ctz" } end;
+ [0x7B] = function() return { "i64.popcnt" } end;
+ [0x7C] = function() return { "i64.add" } end;
+ [0x7D] = function() return { "i64.sub" } end;
+ [0x7E] = function() return { "i64.mul" } end;
+ [0x7F] = function() return { "i64.div_s" } end;
+ [0x80] = function() return { "i64.div_u" } end;
+ [0x81] = function() return { "i64.rem_s" } end;
+ [0x82] = function() return { "i64.rem_u" } end;
+ [0x83] = function() return { "i64.and" } end;
+ [0x84] = function() return { "i64.or" } end;
+ [0x85] = function() return { "i64.xor" } end;
+ [0x86] = function() return { "i64.shl" } end;
+ [0x87] = function() return { "i64.shr_s" } end;
+ [0x88] = function() return { "i64.shr_u" } end;
+ [0x89] = function() return { "i64.rotl" } end;
+ [0x8A] = function() return { "i64.rotr" } end;
+ [0x8B] = function() return { "f32.abs" } end;
+ [0x8C] = function() return { "f32.neg" } end;
+ [0x8D] = function() return { "f32.ceil" } end;
+ [0x8E] = function() return { "f32.floor" } end;
+ [0x8F] = function() return { "f32.trunc" } end;
+ [0x90] = function() return { "f32.nearest" } end;
+ [0x91] = function() return { "f32.sqrt" } end;
+ [0x92] = function() return { "f32.add" } end;
+ [0x93] = function() return { "f32.sub" } end;
+ [0x94] = function() return { "f32.mul" } end;
+ [0x95] = function() return { "f32.div" } end;
+ [0x96] = function() return { "f32.min" } end;
+ [0x97] = function() return { "f32.max" } end;
+ [0x98] = function() return { "f32.copysign" } end;
+ [0x99] = function() return { "f64.abs" } end;
+ [0x9A] = function() return { "f64.neg" } end;
+ [0x9B] = function() return { "f64.ceil" } end;
+ [0x9C] = function() return { "f64.floor" } end;
+ [0x9D] = function() return { "f64.trunc" } end;
+ [0x9E] = function() return { "f64.nearest" } end;
+ [0x9F] = function() return { "f64.sqrt" } end;
+ [0xA0] = function() return { "f64.add" } end;
+ [0xA1] = function() return { "f64.sub" } end;
+ [0xA2] = function() return { "f64.mul" } end;
+ [0xA3] = function() return { "f64.div" } end;
+ [0xA4] = function() return { "f64.min" } end;
+ [0xA5] = function() return { "f64.max" } end;
+ [0xA6] = function() return { "f64.copysign" } end;
+
+ [0xA7] = function() return { "i32.wrap_i64" } end;
+ [0xA8] = function() return { "i32.trunc_f32_s" } end;
+ [0xA9] = function() return { "i32.trunc_f32_u" } end;
+ [0xAA] = function() return { "i32.trunc_f64_s" } end;
+ [0xAB] = function() return { "i32.trunc_f64_u" } end;
+ [0xAC] = function() return { "i64.extend_i32_s" } end;
+ [0xAD] = function() return { "i64.extend_i32_u" } end;
+ [0xAE] = function() return { "i64.trunc_f32_s" } end;
+ [0xAF] = function() return { "i64.trunc_f32_u" } end;
+ [0xB0] = function() return { "i64.trunc_f64_s" } end;
+ [0xB1] = function() return { "i64.trunc_f64_u" } end;
+ [0xB2] = function() return { "f32.convert_i32_s" } end;
+ [0xB3] = function() return { "f32.convert_i32_u" } end;
+ [0xB4] = function() return { "f32.convert_i64_s" } end;
+ [0xB5] = function() return { "f32.convert_i64_u" } end;
+ [0xB6] = function() return { "f32.demote_f64" } end;
+ [0xB7] = function() return { "f64.convert_i32_s" } end;
+ [0xB8] = function() return { "f64.convert_i32_u" } end;
+ [0xB9] = function() return { "f64.convert_i64_s" } end;
+ [0xBA] = function() return { "f64.convert_i64_u" } end;
+ [0xBB] = function() return { "f64.promote_f32" } end;
+ [0xBC] = function() return { "i32.reinterpret_f32" } end;
+ [0xBD] = function() return { "i64.reinterpret_f64" } end;
+ [0xBE] = function() return { "f32.reinterpret_i32" } end;
+ [0xBF] = function() return { "f64.reinterpret_i64" } end;
+ }
+end
+
+function parse_memarg(r)
+ local a = r:read_uint(32)
+ local o = r:read_uint(32)
+
+ return { "memarg"; align = a; offset = o }
+end
+
+function parse_expr(r)
+ local instrs = {}
+ while true do
+ if r:peek_byte() == 0x0B then
+ r:read_byte()
+ break
+ else
+ table.insert(instrs, parse_instr(r))
+ end
+ end
+
+ return instrs
+end
+
+function parse_typeidx(r) return r:read_uint(32) end
+function parse_funcidx(r) return r:read_uint(32) end
+function parse_tableidx(r) return r:read_uint(32) end
+function parse_memidx(r) return r:read_uint(32) end
+function parse_globalidx(r) return r:read_uint(32) end
+function parse_localidx(r) return r:read_uint(32) end
+function parse_labelidx(r) return r:read_uint(32) end
+
+function parse_section(r, expectN, B)
+ if r:peek_byte() ~= expectN then return end
+
+ local N = r:read_byte()
+ local size = r:read_uint(32)
+ local cont = B(r)
+
+ return { "section", contents = cont, size = size }
+end
+
+function parse_customsec(r)
+ local csect = parse_section(r, 0, parse_custom)
+ if not csect then return nil end
+
+ for i=1, csect.size do
+ -- Discard csect.size bytes
+ r:read_byte()
+ end
+ return csect
+end
+
+function parse_custom(r)
+ local name = parse_name(r)
+
+ return { "custom", name = name }
+end
+
+function parse_typesec(r)
+ return parse_section(r, 1, parse_vector(parse_functype))
+end
+
+function parse_importsec(r)
+ return parse_section(r, 2, parse_vector(parse_import))
+end
+
+function parse_import(r)
+ local mod = parse_name(r)
+ local nm = parse_name(r)
+ local d = parse_importdesc(r)
+
+ return { "import", mod = mod, name = nm, desc = d }
+end
+
+function parse_importdesc(r)
+ local t = r:read_byte()
+ if t == 0x00 then return { "func", x = parse_typeidx(r) }
+ elseif t == 0x01 then return { "table", tt = parse_tabletype(r) }
+ elseif t == 0x02 then return { "mem", mt = parse_memtype(r) }
+ elseif t == 0x03 then return { "global", gt = parse_globaltype(r) }
+ end
+
+ error("bad importdesc")
+end
+
+function parse_funcsec(r)
+ return parse_section(r, 3, parse_vector(parse_typeidx))
+end
+
+function parse_tablesec(r)
+ return parse_section(r, 4, parse_vector(parse_table))
+end
+
+function parse_table(r)
+ local tt = parse_tabletype(r)
+ return { "table", type_ = tt }
+end
+
+function parse_memsec(r)
+ return parse_section(r, 5, parse_vector(parse_mem))
+end
+
+function parse_mem(r)
+ local mt = parse_memtype(r)
+ return { "mem", type_ = mt }
+end
+
+function parse_globalsec(r)
+ return parse_section(r, 6, parse_vector(parse_global))
+end
+
+function parse_global(r)
+ local gt = parse_globaltype(r)
+ local e = parse_expr(r)
+ return { "global", type_ = gt, init = e }
+end
+
+function parse_exportsec(r)
+ return parse_section(r, 7, parse_vector(parse_export))
+end
+
+function parse_export(r)
+ local nm = parse_name(r)
+ local d = parse_exportdesc(r)
+ return { "export", name = nm, desc = d }
+end
+
+function parse_exportdesc(r)
+ local t = r:read_byte()
+ if t == 0x00 then return { "func", x = parse_typeidx(r) }
+ elseif t == 0x01 then return { "table", tt = parse_tableidx(r) }
+ elseif t == 0x02 then return { "mem", mt = parse_memidx(r) }
+ elseif t == 0x03 then return { "global", gt = parse_globalidx(r) }
+ end
+
+ error("bad exportdesc: ", t)
+end
+
+function parse_startsec(r)
+ return parse_section(r, 8, parse_start)
+end
+
+function parse_start(r)
+ local x = parse_funcidx(r)
+ return { "start", func = x }
+end
+
+function parse_elemsec(r)
+ return parse_section(r, 9, parse_vector(parse_elem))
+end
+
+function parse_elem(r)
+ local x = parse_tableidx(r)
+ local e = parse_expr(r)
+ local y = parse_vector(parse_funcidx)(r)
+
+ return { "elem", table = x, offset = e, init = y }
+end
+
+function parse_codesec(r)
+ return parse_section(r, 10, parse_vector(parse_code))
+end
+
+function parse_code(r)
+ local size = r:read_uint(32)
+ local code = parse_func(r)
+ return code
+end
+
+function parse_func(r)
+ local t = parse_vector(parse_locals)(r)
+ local e = parse_expr(r)
+
+ local localidx = 0
+ local locals = {}
+ for _, v in ipairs(t) do
+ for _, l in ipairs(v) do
+ l.localidx = localidx
+ table.insert(locals, l)
+ localidx = localidx + 1
+ end
+ end
+
+ return { "func", locals = locals, body = e }
+end
+
+function parse_locals(r)
+ local n = r:read_uint(32)
+ local t = parse_valtype(r)
+
+ --TODO: Make a list of values with names like local0, local1, ...
+
+ local locals = {}
+ for i = 0, n - 1 do
+ table.insert(locals, {
+ name = "local" .. i,
+ type_ = t,
+ localidx = i
+ })
+ end
+
+ return locals
+end
+
+function parse_datasec(r)
+ return parse_section(r, 11, parse_vector(parse_data))
+end
+
+function parse_data(r)
+ local x = parse_memidx(r)
+ local e = parse_expr(r)
+ local b = parse_vector(parse_byte)(r)
+
+ return { "data", data = x, offset = e, init = b }
+end
+
+function parse_magic(r)
+ assert(r:read_byte() == 0x00, "magic string is wrong")
+ assert(r:read_byte() == 0x61, "magic string is wrong")
+ assert(r:read_byte() == 0x73, "magic string is wrong")
+ assert(r:read_byte() == 0x6D, "magic string is wrong")
+end
+
+function parse_version(r)
+ assert(r:read_byte() == 0x01, "version is wrong")
+ assert(r:read_byte() == 0x00, "version is wrong")
+ assert(r:read_byte() == 0x00, "version is wrong")
+ assert(r:read_byte() == 0x00, "version is wrong")
+end
+
+function parse_module(r)
+ parse_magic(r)
+ parse_version(r)
+
+ local functypes = parse_typesec(r)
+ local imports = parse_importsec(r)
+ local typeidxs = parse_funcsec(r)
+ local tables = parse_tablesec(r)
+ local mems = parse_memsec(r)
+ local globals = parse_globalsec(r)
+ local exports = parse_exportsec(r)
+ local start = parse_startsec(r)
+ local elems = parse_elemsec(r)
+ local codes = parse_codesec(r)
+ local data = parse_datasec(r)
+
+ local funcs = {}
+ local funcidx = 0
+
+ if imports then
+ for k, v in ipairs(imports.contents) do
+ if v.desc[1] == "func" then
+ funcs[funcidx] = {
+ name = build_str(v.mod) .. "." .. build_str(v.name);
+ funcidx = funcidx;
+ type_ = functypes.contents[v.desc.x + 1];
+ }
+ funcidx = funcidx + 1
+ end
+ end
+ end
+
+ if codes then
+ for i=1, #codes.contents do
+ local locals = codes.contents[i].locals;
+ local type_ = functypes.contents[typeidxs.contents[i] + 1];
+ local param_types = type_.param_types;
+
+ local new_locals = {}
+ local new_local_idx = 0
+ for _, p in ipairs(param_types) do
+ table.insert(new_locals, {
+ name = "param" .. new_local_idx,
+ type_ = p;
+ localidx = new_local_idx
+ })
+ new_local_idx = new_local_idx + 1
+ end
+ for _, l in ipairs(locals) do
+ l.localidx = new_local_idx
+ table.insert(new_locals, l)
+ new_local_idx = new_local_idx + 1
+ end
+
+ funcs[funcidx] = {
+ funcidx = funcidx;
+ name = "func" .. funcidx;
+ type_ = functypes.contents[typeidxs.contents[i] + 1];
+ locals = new_locals;
+ body = codes.contents[i].body
+ }
+ funcidx = funcidx + 1
+ end
+ end
+
+ return {
+ "module";
+ types = functypes;
+ tables = tables;
+ mems = mems;
+ globals = globas;
+ exports = exports;
+ start = start;
+ elems = elems;
+ data = data;
+ imports = imports;
+ funcs = funcs;
+ }
+end
+
+
+-- Reader util class used for interfacing
+-- with the parse_helper c library
+class "Reader" {
+ init = function(self, filename)
+ self.wasm_file = parse_helper.open_file(filename)
+ end;
+
+ read_byte = function(self)
+ return parse_helper.read_byte(self.wasm_file)
+ end;
+
+ peek_byte = function(self)
+ return parse_helper.peek_byte(self.wasm_file)
+ end;
+
+ -- NOTE: N is unused
+ read_uint = function(self, N)
+ return parse_helper.parse_uleb128(self.wasm_file)
+ end;
+
+ read_sint = function(self, N)
+ return parse_helper.parse_sleb128(self.wasm_file, N)
+ end;
+
+ read_float = function(self, N)
+ return 0.0;
+ end;
+}
+
+function decompile(filepath)
+ local reader = Reader(filepath)
+
+ return parse_module(reader)
+end
+
+return module { decompile; VAL_TYPES = VAL_TYPES }
--- /dev/null
+After experimenting with using Lua for the parser, I think it will be better to write the parser in C / C++
+and then have a function that converts all the data to a lua table that will be used for visualization / examination.
--- /dev/null
+--Under MIT License\r
+--Originally developed by Brendan Hansen\r
+--Copyright 2017\r
+\r
+--[[ found on http://lua-users.org/lists/lua-l/2010-06/msg00314.html ]]\r
+setfenv = setfenv or function(f, t)\r
+ f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)\r
+ local name\r
+ local up = 0\r
+ repeat\r
+ up = up + 1\r
+ name = debug.getupvalue(f, up)\r
+ until name == '_ENV' or name == nil\r
+ if name then\r
+ debug.upvaluejoin(f, up, function() return t end, 1) -- use unique upvalue, set it to f\r
+ end\r
+end\r
+\r
+getfenv = getfenv or function(fn)\r
+ local i = 1\r
+ while true do\r
+ local name, val = debug.getupvalue(fn, i)\r
+ if name == "_ENV" then\r
+ return val\r
+ elseif not name then\r
+ break\r
+ end\r
+ i = i + 1\r
+ end\r
+end\r
+\r
+local function helper(obj, super, f)\r
+ return function(...)\r
+ local a = { ... }\r
+ local data = a[#a]\r
+ local env = getfenv(2)\r
+\r
+ data.extends = super\r
+ env[obj] = f(data)\r
+ end\r
+end\r
+\r
+\r
+--[[\r
+ Module System\r
+ "imports" are defined by:\r
+ import {\r
+ NAME = "path.to.lua.file";\r
+ NAME = "path.to.lua.file:specific_import";\r
+ NAME = "path.to.lua.file:"; --this uses the default export (see below)\r
+ }\r
+\r
+ "exports" are defined by:\r
+ return module {\r
+ the_default_export_variable;\r
+ other_export=some_value;\r
+ }\r
+]]\r
+\r
+local ALL_DEPS = {}\r
+function import(reqs)\r
+ local function convertPath(req)\r
+ local r = req\r
+ if req:find(":") ~= nil then\r
+ r = req:sub(1, req:find(":") - 1)\r
+ end\r
+ r = r:gsub("%.", "/")\r
+ return r\r
+ end\r
+\r
+ local function getField(req)\r
+ if req:find(":") ~= nil then\r
+ if req:sub(-1) == ":" then\r
+ return "default"\r
+ else\r
+ return req:sub(req:find(":")+1)\r
+ end\r
+ else\r
+ return nil\r
+ end\r
+ end\r
+\r
+ local dep = {}\r
+ for name, req in pairs(reqs) do\r
+ if type(req) ~= "string" then\r
+ error "Please use strings for referencing modules"\r
+ end\r
+\r
+ local mod = require(convertPath(req))\r
+ dep[name] = mod\r
+\r
+ if type(mod) == "table" then\r
+ local field = getField(req)\r
+ if field ~= nil then\r
+ if mod[field] ~= nil then\r
+ dep[name] = mod[field]\r
+ else\r
+ dep[name] = nil\r
+ end\r
+ end\r
+ end\r
+ end\r
+\r
+ local newenv = setmetatable({}, {\r
+ __index = function(_, k)\r
+ local v = dep[k]\r
+ if v == nil then v = _G[k] end\r
+ return v\r
+ end;\r
+ __newindex = function(_, k, v)\r
+ dep[k] = v;\r
+ end;\r
+ })\r
+ setfenv(2, newenv)\r
+end\r
+\r
+function module(exp)\r
+ exp["default"] = exp[1]\r
+ exp[1] = nil\r
+ return exp\r
+end\r
+\r
+--[[\r
+ Data - basically tables that can only hold numbers, strings, bools, tables, or nil (no functions)\r
+]]\r
+function data(d)\r
+ for k, v in pairs(d) do\r
+ if type(v) == "function" then\r
+ d[k] = nil\r
+ end\r
+ end\r
+ return d\r
+end\r
+\r
+--[[\r
+ Factories - basically tables that can only hold functions, normally static\r
+]]\r
+function factory(f)\r
+ for k, v in pairs(f) do\r
+ if type(v) ~= "function" then\r
+ f[k] = nil\r
+ end\r
+ end\r
+ return f\r
+end\r
+\r
+--[[\r
+ Classes - factories with a initializer and a self-reference, and are allowed to hold variables\r
+\r
+ Initialize with "init"\r
+ Create new instance by calling name of class\r
+ local ClassName = class { ... }\r
+ local ClassName = Base.extend { ... }\r
+ local instance_a = ClassName(args)\r
+\r
+ Can also declare classes as\r
+ class "ClassName" { ... }\r
+ or\r
+ class "ClassName extends Base" { ... }\r
+ or\r
+ class "ClassName" ["Base"] { ... }\r
+]]\r
+function class(obj)\r
+ if type(obj) == "string" then\r
+ local sub, super = obj:match "(%w+) +extends +(%w+)"\r
+ if sub then obj = sub end\r
+\r
+ if super then\r
+ return helper(obj, super, class)\r
+ else\r
+ local o = {}\r
+ o = setmetatable(o, {\r
+ __call = helper(obj, nil, class);\r
+\r
+ __index = function(_, super)\r
+ return helper(obj, super, class)\r
+ end;\r
+ })\r
+ return o\r
+ end\r
+ end\r
+\r
+ local cls = {}\r
+ if obj.extends ~= nil then\r
+ for k, v in pairs(obj.extends) do\r
+ cls[k] = v\r
+ end\r
+ end\r
+\r
+ for k, v in pairs(obj) do\r
+ if k ~= "extends" then\r
+ cls[k] = v\r
+ end\r
+ end\r
+\r
+ cls = setmetatable(cls, {\r
+ __call = function(_, ...)\r
+ local o = {}\r
+ local mt = {}\r
+\r
+ for k, v in pairs(cls) do\r
+ if k:sub(0, 2) == "__" then\r
+ mt[k] = v\r
+ end\r
+ end\r
+\r
+ mt.__index = cls\r
+ o = setmetatable(o, mt)\r
+\r
+ if cls.init then\r
+ cls.init(o, ...)\r
+ end\r
+\r
+ return o\r
+ end\r
+ })\r
+\r
+ cls.extend = function(d)\r
+ d.extends = cls\r
+ return class(d)\r
+ end\r
+\r
+ return cls;\r
+end\r
+\r
+--[[\r
+ Singleton - a single instance of a class\r
+]]\r
+function singleton(obj)\r
+ return (class(obj))()\r
+end\r
+\r
+function enum(obj)\r
+ if type(obj) == "string" then\r
+ return helper(obj, nil, enum)\r
+ end\r
+\r
+ local i = 0\r
+ for k, v in pairs(obj) do\r
+ obj[k] = nil\r
+ if type(v) == "string" then\r
+ obj[v] = i\r
+ i = i + 1\r
+ end\r
+ end\r
+ return obj\r
+end\r
+\r
+function interface(fns)\r
+ if type(fns) == "string" then\r
+ local sub, super = fns:match "(%w+) +extends +(%w+)"\r
+ if sub then fns = sub end\r
+\r
+ if super then\r
+ return helper(fns, super, interface)\r
+ else\r
+ local int = {}\r
+ int = setmetatable(int, {\r
+ __call = helper(fns, nil, interface);\r
+\r
+ __index = function(_, super)\r
+ return helper(fns, super, interface)\r
+ end;\r
+ })\r
+ return int\r
+ end\r
+ end\r
+\r
+ local int = {}\r
+ for k, v in pairs(fns) do\r
+ if type(v) == "string" then\r
+ int[v] = function(self) end\r
+ end\r
+ end\r
+\r
+ if fns.extends then\r
+ for k, v in pairs(fns.extends) do\r
+ if type(v) == "function" then\r
+ int[k] = function(self) end\r
+ end\r
+ end\r
+ end\r
+\r
+ local mt = {\r
+ __call = function(_, ...)\r
+ error "Interfaces cannot be instatiated with a constructor"\r
+ end;\r
+ }\r
+ int = setmetatable(int, mt)\r
+\r
+ return int\r
+end\r
+\r
+function match(var)\r
+ return function(tab)\r
+ local ran = false\r
+ for k, v in pairs(tab) do\r
+ if k ~= "default" and var == k then\r
+ return v()\r
+ end\r
+ end\r
+\r
+ if tab["default"] then\r
+ tab["default"]()\r
+ end\r
+ end\r
+end\r
+\r
+function with(o)\r
+ return function(tab)\r
+ for k, v in pairs(tab) do\r
+ o[k] = v\r
+ end\r
+ return o\r
+ end\r
+end\r
--- /dev/null
+local pprint = { VERSION = '0.1' }
+
+local depth = 1
+
+pprint.defaults = {
+ -- If set to number N, then limit table recursion to N deep.
+ depth_limit = false,
+ -- type display trigger, hide not useful datatypes by default
+ -- custom types are treated as table
+ show_nil = true,
+ show_boolean = true,
+ show_number = true,
+ show_string = true,
+ show_table = true,
+ show_function = false,
+ show_thread = false,
+ show_userdata = false,
+ -- additional display trigger
+ show_metatable = false, -- show metatable
+ show_all = false, -- override other show settings and show everything
+ use_tostring = false, -- use __tostring to print table if available
+ filter_function = nil, -- called like callback(value[,key, parent]), return truty value to hide
+ object_cache = 'local', -- cache blob and table to give it a id, 'local' cache per print, 'global' cache
+ -- per process, falsy value to disable (might cause infinite loop)
+ -- format settings
+ indent_size = 2, -- indent for each nested table level
+ level_width = 80, -- max width per indent level
+ wrap_string = true, -- wrap string when it's longer than level_width
+ wrap_array = false, -- wrap every array elements
+ sort_keys = true, -- sort table keys
+}
+
+local TYPES = {
+ ['nil'] = 1, ['boolean'] = 2, ['number'] = 3, ['string'] = 4,
+ ['table'] = 5, ['function'] = 6, ['thread'] = 7, ['userdata'] = 8
+}
+
+-- seems this is the only way to escape these, as lua don't know how to map char '\a' to 'a'
+local ESCAPE_MAP = {
+ ['\a'] = '\\a', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r',
+ ['\t'] = '\\t', ['\v'] = '\\v', ['\\'] = '\\\\',
+}
+
+-- generic utilities
+local function escape(s)
+ s = s:gsub('([%c\\])', ESCAPE_MAP)
+ local dq = s:find('"')
+ local sq = s:find("'")
+ if dq and sq then
+ return s:gsub('"', '\\"'), '"'
+ elseif sq then
+ return s, '"'
+ else
+ return s, "'"
+ end
+end
+
+local function is_plain_key(key)
+ return type(key) == 'string' and key:match('^[%a_][%a%d_]*$')
+end
+
+local CACHE_TYPES = {
+ ['table'] = true, ['function'] = true, ['thread'] = true, ['userdata'] = true
+}
+
+-- cache would be populated to be like:
+-- {
+-- function = { `fun1` = 1, _cnt = 1 }, -- object id
+-- table = { `table1` = 1, `table2` = 2, _cnt = 2 },
+-- visited_tables = { `table1` = 7, `table2` = 8 }, -- visit count
+-- }
+-- use weakrefs to avoid accidentall adding refcount
+local function cache_apperance(obj, cache, option)
+ if not cache.visited_tables then
+ cache.visited_tables = setmetatable({}, {__mode = 'k'})
+ end
+ local t = type(obj)
+
+ -- TODO can't test filter_function here as we don't have the ix and key,
+ -- might cause different results?
+ -- respect show_xxx and filter_function to be consistent with print results
+ if (not TYPES[t] and not option.show_table)
+ or (TYPES[t] and not option['show_'..t]) then
+ return
+ end
+
+ if CACHE_TYPES[t] or TYPES[t] == nil then
+ if not cache[t] then
+ cache[t] = setmetatable({}, {__mode = 'k'})
+ cache[t]._cnt = 0
+ end
+ if not cache[t][obj] then
+ cache[t]._cnt = cache[t]._cnt + 1
+ cache[t][obj] = cache[t]._cnt
+ end
+ end
+ if t == 'table' or TYPES[t] == nil then
+ if cache.visited_tables[obj] == false then
+ -- already printed, no need to mark this and its children anymore
+ return
+ elseif cache.visited_tables[obj] == nil then
+ cache.visited_tables[obj] = 1
+ else
+ -- visited already, increment and continue
+ cache.visited_tables[obj] = cache.visited_tables[obj] + 1
+ return
+ end
+ for k, v in pairs(obj) do
+ cache_apperance(k, cache, option)
+ cache_apperance(v, cache, option)
+ end
+ local mt = getmetatable(obj)
+ if mt and option.show_metatable then
+ cache_apperance(mt, cache, option)
+ end
+ end
+end
+
+-- makes 'foo2' < 'foo100000'. string.sub makes substring anyway, no need to use index based method
+local function str_natural_cmp(lhs, rhs)
+ while #lhs > 0 and #rhs > 0 do
+ local lmid, lend = lhs:find('%d+')
+ local rmid, rend = rhs:find('%d+')
+ if not (lmid and rmid) then return lhs < rhs end
+
+ local lsub = lhs:sub(1, lmid-1)
+ local rsub = rhs:sub(1, rmid-1)
+ if lsub ~= rsub then
+ return lsub < rsub
+ end
+
+ local lnum = tonumber(lhs:sub(lmid, lend))
+ local rnum = tonumber(rhs:sub(rmid, rend))
+ if lnum ~= rnum then
+ return lnum < rnum
+ end
+
+ lhs = lhs:sub(lend+1)
+ rhs = rhs:sub(rend+1)
+ end
+ return lhs < rhs
+end
+
+local function cmp(lhs, rhs)
+ local tleft = type(lhs)
+ local tright = type(rhs)
+ if tleft == 'number' and tright == 'number' then return lhs < rhs end
+ if tleft == 'string' and tright == 'string' then return str_natural_cmp(lhs, rhs) end
+ if tleft == tright then return str_natural_cmp(tostring(lhs), tostring(rhs)) end
+
+ -- allow custom types
+ local oleft = TYPES[tleft] or 9
+ local oright = TYPES[tright] or 9
+ return oleft < oright
+end
+
+-- setup option with default
+local function make_option(option)
+ if option == nil then
+ option = {}
+ end
+ for k, v in pairs(pprint.defaults) do
+ if option[k] == nil then
+ option[k] = v
+ end
+ if option.show_all then
+ for t, _ in pairs(TYPES) do
+ option['show_'..t] = true
+ end
+ option.show_metatable = true
+ end
+ end
+ return option
+end
+
+-- override defaults and take effects for all following calls
+function pprint.setup(option)
+ pprint.defaults = make_option(option)
+end
+
+-- format lua object into a string
+function pprint.pformat(obj, option, printer)
+ option = make_option(option)
+ local buf = {}
+ local function default_printer(s)
+ table.insert(buf, s)
+ end
+ printer = printer or default_printer
+
+ local cache
+ if option.object_cache == 'global' then
+ -- steal the cache into a local var so it's not visible from _G or anywhere
+ -- still can't avoid user explicitly referentce pprint._cache but it shouldn't happen anyway
+ cache = pprint._cache or {}
+ pprint._cache = nil
+ elseif option.object_cache == 'local' then
+ cache = {}
+ end
+
+ local last = '' -- used for look back and remove trailing comma
+ local status = {
+ indent = '', -- current indent
+ len = 0, -- current line length
+ }
+
+ local wrapped_printer = function(s)
+ printer(last)
+ last = s
+ end
+
+ local function _indent(d)
+ status.indent = string.rep(' ', d + #(status.indent))
+ end
+
+ local function _n(d)
+ wrapped_printer('\n')
+ wrapped_printer(status.indent)
+ if d then
+ _indent(d)
+ end
+ status.len = 0
+ return true -- used to close bracket correctly
+ end
+
+ local function _p(s, nowrap)
+ status.len = status.len + #s
+ if not nowrap and status.len > option.level_width then
+ _n()
+ wrapped_printer(s)
+ status.len = #s
+ else
+ wrapped_printer(s)
+ end
+ end
+
+ local formatter = {}
+ local function format(v)
+ local f = formatter[type(v)]
+ f = f or formatter.table -- allow patched type()
+ if option.filter_function and option.filter_function(v, nil, nil) then
+ return ''
+ else
+ return f(v)
+ end
+ end
+
+ local function tostring_formatter(v)
+ return tostring(v)
+ end
+
+ local function number_formatter(n)
+ return n == math.huge and '[[math.huge]]' or tostring(n)
+ end
+
+ local function nop_formatter(v)
+ return ''
+ end
+
+ local function make_fixed_formatter(t, has_cache)
+ if has_cache then
+ return function (v)
+ return string.format('[[%s %d]]', t, cache[t][v])
+ end
+ else
+ return function (v)
+ return '[['..t..']]'
+ end
+ end
+ end
+
+ local function string_formatter(s, force_long_quote)
+ local s, quote = escape(s)
+ local quote_len = force_long_quote and 4 or 2
+ if quote_len + #s + status.len > option.level_width then
+ _n()
+ -- only wrap string when is longer than level_width
+ if option.wrap_string and #s + quote_len > option.level_width then
+ -- keep the quotes together
+ _p('[[')
+ while #s + status.len >= option.level_width do
+ local seg = option.level_width - status.len
+ _p(string.sub(s, 1, seg), true)
+ _n()
+ s = string.sub(s, seg+1)
+ end
+ _p(s) -- print the remaining parts
+ return ']]'
+ end
+ end
+
+ return force_long_quote and '[['..s..']]' or quote..s..quote
+ end
+
+ local function table_formatter(t)
+ if option.use_tostring then
+ local mt = getmetatable(t)
+ if mt and mt.__tostring then
+ return string_formatter(tostring(t), true)
+ end
+ end
+
+ local print_header_ix = nil
+ local ttype = type(t)
+ if option.object_cache then
+ local cache_state = cache.visited_tables[t]
+ local tix = cache[ttype][t]
+ -- FIXME should really handle `cache_state == nil`
+ -- as user might add things through filter_function
+ if cache_state == false then
+ -- already printed, just print the the number
+ return string_formatter(string.format('%s %d', ttype, tix), true)
+ elseif cache_state > 1 then
+ -- appeared more than once, print table header with number
+ print_header_ix = tix
+ cache.visited_tables[t] = false
+ else
+ -- appeared exactly once, print like a normal table
+ end
+ end
+
+ local limit = tonumber(option.depth_limit)
+ if limit and depth > limit then
+ if print_header_ix then
+ return string.format('[[%s %d]]...', ttype, print_header_ix)
+ end
+ return string_formatter(tostring(t), true)
+ end
+
+ local tlen = #t
+ local wrapped = false
+ _p('{')
+ _indent(option.indent_size)
+ _p(string.rep(' ', option.indent_size - 1))
+ if print_header_ix then
+ _p(string.format('--[[%s %d]] ', ttype, print_header_ix))
+ end
+ for ix = 1,tlen do
+ local v = t[ix]
+ if formatter[type(v)] == nop_formatter or
+ (option.filter_function and option.filter_function(v, ix, t)) then
+ -- pass
+ else
+ if option.wrap_array then
+ wrapped = _n()
+ end
+ depth = depth+1
+ _p(format(v)..', ')
+ depth = depth-1
+ end
+ end
+
+ -- hashmap part of the table, in contrast to array part
+ local function is_hash_key(k)
+ if type(k) ~= 'number' then
+ return true
+ end
+
+ local numkey = math.floor(tonumber(k))
+ if numkey ~= k or numkey > tlen or numkey <= 0 then
+ return true
+ end
+ end
+
+ local function print_kv(k, v, t)
+ -- can't use option.show_x as obj may contain custom type
+ if formatter[type(v)] == nop_formatter or
+ formatter[type(k)] == nop_formatter or
+ (option.filter_function and option.filter_function(v, k, t)) then
+ return
+ end
+ wrapped = _n()
+ if is_plain_key(k) then
+ _p(k, true)
+ else
+ _p('[')
+ -- [[]] type string in key is illegal, needs to add spaces inbetween
+ local k = format(k)
+ if string.match(k, '%[%[') then
+ _p(' '..k..' ', true)
+ else
+ _p(k, true)
+ end
+ _p(']')
+ end
+ _p(' = ', true)
+ depth = depth+1
+ _p(format(v), true)
+ depth = depth-1
+ _p(',', true)
+ end
+
+ if option.sort_keys then
+ local keys = {}
+ for k, _ in pairs(t) do
+ if is_hash_key(k) then
+ table.insert(keys, k)
+ end
+ end
+ table.sort(keys, cmp)
+ for _, k in ipairs(keys) do
+ print_kv(k, t[k], t)
+ end
+ else
+ for k, v in pairs(t) do
+ if is_hash_key(k) then
+ print_kv(k, v, t)
+ end
+ end
+ end
+
+ if option.show_metatable then
+ local mt = getmetatable(t)
+ if mt then
+ print_kv('__metatable', mt, t)
+ end
+ end
+
+ _indent(-option.indent_size)
+ -- make { } into {}
+ last = string.gsub(last, '^ +$', '')
+ -- peek last to remove trailing comma
+ last = string.gsub(last, ',%s*$', ' ')
+ if wrapped then
+ _n()
+ end
+ _p('}')
+
+ return ''
+ end
+
+ -- set formatters
+ formatter['nil'] = option.show_nil and tostring_formatter or nop_formatter
+ formatter['boolean'] = option.show_boolean and tostring_formatter or nop_formatter
+ formatter['number'] = option.show_number and number_formatter or nop_formatter -- need to handle math.huge
+ formatter['function'] = option.show_function and make_fixed_formatter('function', option.object_cache) or nop_formatter
+ formatter['thread'] = option.show_thread and make_fixed_formatter('thread', option.object_cache) or nop_formatter
+ formatter['userdata'] = option.show_userdata and make_fixed_formatter('userdata', option.object_cache) or nop_formatter
+ formatter['string'] = option.show_string and string_formatter or nop_formatter
+ formatter['table'] = option.show_table and table_formatter or nop_formatter
+
+ if option.object_cache then
+ -- needs to visit the table before start printing
+ cache_apperance(obj, cache, option)
+ end
+
+ _p(format(obj))
+ printer(last) -- close the buffered one
+
+ -- put cache back if global
+ if option.object_cache == 'global' then
+ pprint._cache = cache
+ end
+
+ return table.concat(buf)
+end
+
+-- pprint all the arguments
+function pprint.pprint( ... )
+ local args = {...}
+ -- select will get an accurate count of array len, counting trailing nils
+ local len = select('#', ...)
+ for ix = 1,len do
+ pprint.pformat(args[ix], nil, io.write)
+ io.write('\n')
+ end
+end
+
+setmetatable(pprint, {
+ __call = function (_, ...)
+ pprint.pprint(...)
+ end
+})
+
+return pprint
+
--- /dev/null
+-- Required to use the import and class statements
+require "lualib.oop"
+
+import {
+ decompile = "decompile:";
+ VAL_TYPES = "decompile:VAL_TYPES";
+
+ Ui = "ui:Ui";
+ UiRegion = "ui:UiRegion";
+ ScrollingUiRegion = "ui:ScrollingUiRegion";
+ DraggableRect = "ui:DraggableRect";
+
+ render_text = "ui:render_text";
+
+ pprint = "lualib.pprint";
+}
+
+function build_func_header_text(func, mod)
+ local text = {}
+
+ table.insert(text, { text = "[" .. func.funcidx .. "] ", color = { 0, 0, 0 } })
+ table.insert(text, { text = func.name, color = { 0, 0, 1 } })
+ table.insert(text, { text = ": ", color = { 0, 0, 0 } })
+
+ local type_ = func.type_
+ for _, t in ipairs(type_.param_types) do
+ table.insert(text, { text = t .. " ", color = { 0, 0, 0 } })
+ end
+
+ if #type_.param_types == 0 then
+ table.insert(text, { text = "void ", color = { 0, 0, 0 } })
+ end
+
+ table.insert(text, { text = "-> ", color = { 0, 0, 0 } })
+
+ for _, t in ipairs(type_.result_types) do
+ table.insert(text, { text = t .. " ", color = { 0, 0, 0 } })
+ end
+
+
+ if #type_.result_types == 0 then
+ table.insert(text, { text = "void", color = { 0, 0, 0 } })
+ end
+
+ table.insert(text, { newline = true })
+
+ return text
+end
+
+local line = 1
+
+function add_line_number(textlist, newline)
+ if newline == nil then newline = true end
+ table.insert(textlist, { newline = newline })
+ table.insert(textlist, { text = ("%4d"):format(line), color = { 0.3, 0.3, 0.3 }, background = { 0.7, 0.7, 0.7 }, post_indent = true })
+ table.insert(textlist, { text = " ", color = { 0, 0, 0 }, post_indent = true })
+ line = line + 1
+end
+
+function instr_to_text(textlist, instr, func, mod)
+ if instr[1] == "block" then
+ table.insert(textlist, { text = "block", color = { 1, 0, 0 }, change_indent = 1 })
+ for _, ins in ipairs(instr.instrs) do
+ add_line_number(textlist)
+ instr_to_text(textlist, ins, func, mod)
+ end
+ table.insert(textlist, { change_indent = -1 })
+
+ elseif instr[1] == "loop" then
+ table.insert(textlist, { text = "loop", color = { 1, 0, 0 }, change_indent = 1 })
+ for _, ins in ipairs(instr.instrs) do
+ add_line_number(textlist)
+ instr_to_text(textlist, ins, func, mod)
+ end
+ table.insert(textlist, { change_indent = -1 })
+
+ elseif instr[1] == "if" then
+ table.insert(textlist, { text = "if", color = { 1, 0, 0 }, change_indent = 1 })
+ for _, ins in ipairs(instr.instrs) do
+ add_line_number(textlist)
+ instr_to_text(textlist, ins, func, mod)
+ end
+ table.insert(textlist, { change_indent = -1 })
+
+ if #instr.else_instrs > 0 then
+ add_line_number(textlist)
+ table.insert(textlist, { text = "else", color = { 1, 0, 0 }, change_indent = 1 })
+ for _, ins in ipairs(instr.instrs) do
+ add_line_number(textlist)
+ instr_to_text(textlist, ins, func, mod)
+ end
+ table.insert(textlist, { change_indent = -1 })
+ end
+
+ elseif instr[1] == "call" then
+ table.insert(textlist, { text = instr[1] .. " ", color = { 0, 0, 1 } })
+ table.insert(textlist, { text = mod.funcs[instr.x].name, color = { 0, 0.5, 0 } })
+
+ elseif instr[1]:match("local") then
+ table.insert(textlist, { text = instr[1] .. " ", color = { 0, 0, 1 } })
+ table.insert(textlist, { text = func.locals[instr.x + 1].name, color = { 0, 0.5, 0 } })
+
+ else
+ table.insert(textlist, { text = instr[1] .. " ", color = { 0, 0, 1 } })
+ if instr.x then
+ if type(instr.x) == "table" then
+ if instr.x[1] == "memarg" then
+ table.insert(textlist, {
+ text = ("align=0x%03x offset=0x%08x"):format(instr.x.align, instr.x.offset);
+ color = { 0, 0.5, 0 }
+ })
+ end
+ else
+ table.insert(textlist, { text = tostring(instr.x), color = { 0, 0.5, 0 } })
+ end
+ end
+ end
+
+ return line
+end
+
+function build_func_body_text(func, mod)
+ local text = {}
+ line = 1
+
+ table.insert(text, { text = "Locals:", color = { 0, 0, 1 }, newline = true, change_indent = 2 })
+ for _, loc in ipairs(func.locals) do
+ table.insert(text, { text = loc.type_ .. " " .. loc.name, color = { 0, 0, 0 }, newline = true })
+ end
+
+ if #func.locals == 0 then
+ table.insert(text, { text = "no locals", color = { 1, 0, 0 }, newline = true })
+ end
+
+ table.insert(text, { newline = true, change_indent = -2 })
+ table.insert(text, { text = "Body:", color = { 0, 0, 1 } })
+
+ for _, instr in ipairs(func.body) do
+ add_line_number(text)
+ instr_to_text(text, instr, func, mod)
+ end
+
+ -- table.remove(text, #text)
+
+ return text
+end
+
+class "FunctionBlock" [DraggableRect] {
+ init = function(self, func, x, y)
+ DraggableRect.init(self, x, y, 400, 400)
+
+ self.header_text = build_func_header_text(func, mod)
+ self.body_text = build_func_body_text(func, mod)
+ self.scroll = 0
+ end;
+
+ onwheel = function(self, _, dy)
+ self.scroll = self.scroll - dy * 10;
+ if self.scroll < 0 then self.scroll = 0 end
+ end;
+
+ ondrag = function(self, button, x, y, dx, dy)
+ DraggableRect.ondrag(self, button, x, y, dx, dy)
+
+ if button == 2 then
+ self.rect.w = math.max(100, x)
+ self.rect.h = math.max(22, y)
+ end
+ end;
+
+ draw = function(self)
+ love.graphics.setColor(0, 0, 0)
+ love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h)
+ love.graphics.setColor(1, 1, 1)
+ love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, self.rect.h - 4)
+ love.graphics.setColor(0.8, 0.8, 0.8)
+ love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, 18)
+
+ local sx, sy = love.graphics.transformPoint(0, 0)
+ love.graphics.intersectScissor(sx, sy, self.rect.w, self.rect.h)
+ local x, y = render_text(2, 2, self.header_text)
+
+ local sx, sy = love.graphics.transformPoint(2, y - 1)
+ love.graphics.intersectScissor(sx, sy, self.rect.w - 4, self.rect.h - 22)
+ render_text(2, y + 4 - self.scroll, self.body_text)
+ end;
+}
+
+mod = nil
+ui = nil
+function love.load()
+ ui = Ui()
+
+ mod = decompile(arg[2])
+
+ ui_region = ScrollingUiRegion()
+ ui_region.rect.x = 0
+ ui_region.rect.y = 0
+ ui_region.rect.w = 1200
+ ui_region.rect.h = 900
+ ui_region.background_color = { 0.1, 0.1, 0.1 }
+ ui_region.line_color = { 0.2, 0.2, 0.2 }
+
+ -- ui_region:add_object(DraggableRect(100, 100, 100, 100))
+ -- ui_region:add_object(DraggableRect(200, 100, 100, 100))
+ -- ui_region:add_object(DraggableRect(300, 100, 100, 100))
+ -- ui_region:add_object(DraggableRect(400, 100, 300, 100))
+
+ local x = 0
+ local y = 0
+ for _, func in pairs(mod.funcs) do
+ if func.body then
+ local block = FunctionBlock(func, x, y)
+ block.order = 600 - y
+ ui_region:add_object(block)
+ y = y + 20
+ end
+ end
+
+ ui:add_region(ui_region)
+
+ -- bottom_bar_region = UiRegion()
+ -- bottom_bar_region.rect.x = 0
+ -- bottom_bar_region.rect.y = 800
+ -- bottom_bar_region.rect.w = 1200
+ -- bottom_bar_region.rect.h = 100
+ -- bottom_bar_region.layer = 1
+ -- bottom_bar_region.background_color = { 1, 0, 0 }
+
+ -- ui:add_region(bottom_bar_region)
+end
+
+function love.update(dt)
+ if love.keyboard.isDown "escape" then
+ love.event.quit()
+ end
+
+ ui:update(dt)
+end
+
+function love.mousepressed(x, y, button)
+ ui:onmousedown(button, x, y)
+end
+
+function love.mousereleased(x, y, button)
+ ui:onmouseup(button, x, y)
+end
+
+function love.mousemoved(x, y, dx, dy)
+ ui:onmousemove(x, y, dx, dy)
+end
+
+function love.wheelmoved(dx, dy)
+ ui:onmousewheel(dx, dy)
+end
+
+function love.keypressed(key)
+ ui:onkeydown(key)
+end
+
+function love.keyreleased(key)
+ ui:onkeyup(key)
+end
+
+function love.resize(w, h)
+ ui:onresize(w, h)
+end
+
+function love.draw()
+ ui:draw()
+end
--- /dev/null
+require "lualib.oop"
+
+import {
+ decompile = "decompile:";
+ build_str = "utils:build_str";
+ pprint = "lualib.pprint";
+}
+
+local mod = decompile("./data/main.wasm")
+pprint(mod)
+
--- /dev/null
+import {
+ Rectangle = "utils:Rectangle";
+ uuid = "utils:uuidv4";
+ revipairs = "utils:revipairs";
+}
+
+class "Ui" {
+ init = function(self)
+ self.regions = {}
+ self.active_region = nil
+ end;
+
+ add_region = function(self, region)
+ table.insert(self.regions, region)
+ table.sort(self.regions)
+ end;
+
+ onmousedown = function(self, button, x, y)
+ local region = nil
+ for _, r in revipairs(self.regions) do
+ if r.rect:contains(x, y) then
+ region = r
+ break
+ end
+ end
+
+ if region ~= nil then
+ region:onmousedown(button, x - region.rect.x, y - region.rect.y)
+ end
+ end;
+
+ onmouseup = function(self, button, x, y)
+ local region = nil
+ for _, r in revipairs(self.regions) do
+ if r.rect:contains(x, y) then
+ region = r
+ break
+ end
+ end
+
+ if region ~= nil then
+ region:onmouseup(button, x - region.rect.x, y - region.rect.y)
+ end
+ end;
+
+ onmousemove = function(self, x, y, dx, dy)
+ local region = nil
+ for _, r in revipairs(self.regions) do
+ if r.rect:contains(x, y) then
+ region = r
+ break
+ end
+ end
+
+ if region ~= nil then
+ if region ~= self.active_region then
+ if self.active_region ~= nil then
+ self.active_region:onmouseleave()
+ end
+
+ region:onmouseenter()
+ end
+ region:onmousemove(x - region.rect.x, y - region.rect.y, dx, dy)
+ end
+
+ self.active_region = region
+ end;
+
+ onmousewheel = function(self, dx, dy)
+ if not self.active_region then return end
+ self.active_region:onmousewheel(dx, dy)
+ end;
+
+ onkeydown = function(self, key)
+ if not self.active_region then return end
+ self.active_region:onkeydown(key)
+ end;
+
+ onkeyup = function(self, key)
+ if not self.active_region then return end
+ self.active_region:onkeyup(key)
+ end;
+
+ onresize = function(self, new_width, new_height)
+ end;
+
+ update = function(self, dt)
+ for _, region in ipairs(self.regions) do
+ region:update(dt);
+ end
+ end;
+
+ draw = function(self)
+ for _, region in ipairs(self.regions) do
+ love.graphics.push()
+ love.graphics.setScissor(region.rect.x, region.rect.y, region.rect.w, region.rect.h)
+ love.graphics.translate(region.rect.x, region.rect.y)
+ region:draw()
+ love.graphics.pop()
+ end
+ end;
+}
+
+class "UiRegion" {
+ init = function(self)
+ self.active = false
+
+ self.selected_object = nil
+ self.objects = {}
+
+ self.rect = Rectangle(0, 0, 0, 0)
+ self.background_color = { 0, 0, 0 }
+ self.layer = 0
+
+ -- Internals
+ self.is_mouse_down = false
+ self.fire_onclick = false
+ end;
+
+ add_object = function(self, obj)
+ table.insert(self.objects, obj)
+ end;
+
+ -- Used for sorting in the Ui
+ __lt = function(self, other)
+ return self.layer < other.layer
+ end;
+
+ _obj_rect = function(self, obj)
+ return obj.rect
+ end;
+
+ onmousedown = function(self, button, x, y)
+ self.is_mouse_down = button
+ self.fire_onclick = true
+ local new_selected = nil
+
+ for _, obj in revipairs(self.objects) do
+ if self:_obj_rect(obj):contains(x, y) then
+ new_selected = obj
+ break
+ end
+ end
+
+ if self.selected_object ~= new_selected then
+ if self.selected_object ~= nil then
+ self.selected_object:onunselect()
+ end
+
+ self.selected_object = new_selected
+
+ if self.selected_object ~= nil then
+ self.selected_object.order = 0
+
+ for _, obj in ipairs(self.objects) do
+ if obj ~= self.selected_obj then
+ obj.order = obj.order + 1
+ end
+ end
+
+ table.sort(self.objects, function(a, b)
+ return a.order > b.order
+ end)
+
+ self.selected_object:onselect()
+ end
+ end
+ end;
+
+ onmouseup = function(self, button, x, y)
+ self.is_mouse_down = false
+
+ if self.selected_object ~= nil and self.fire_onclick then
+ local obj_rect = self:_obj_rect(self.selected_object)
+ self.selected_object:onclick(button, x - obj_rect.x, y - obj_rect.y)
+ end
+ end;
+ onmousemove = function(self, x, y, dx, dy)
+ if self.is_mouse_down and self.selected_object ~= nil then
+ local obj_rect = self:_obj_rect(self.selected_object)
+ self.selected_object:ondrag(self.is_mouse_down, x - obj_rect.x, y - obj_rect.y, dx, dy)
+ self.fire_onclick = false
+ end
+ end;
+ onmouseenter = function(self)
+ self.active = true
+ end;
+
+ onmouseleave = function(self)
+ self.active = false;
+ self.is_mouse_down = false
+ self.fire_onclick = false
+ self.selected_object = nil
+ end;
+
+ onmousewheel = function(self, dx, dy)
+ if self.selected_object ~= nil then
+ self.selected_object:onwheel(dx, dy)
+ end
+ end;
+
+ onkeydown = function(self, key) end;
+ onkeyup = function(self, key) end;
+
+ update = function(self, dt)
+ for _, obj in ipairs(self.objects) do
+ obj:update(dt)
+ end
+ end;
+
+ draw = function(self)
+ if self.background_color ~= nil then
+ love.graphics.setColor(self.background_color)
+ love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h)
+ end
+
+ for _, obj in ipairs(self.objects) do
+ love.graphics.push()
+ love.graphics.translate(obj.rect.x, obj.rect.y)
+ obj:draw()
+ love.graphics.setScissor()
+ love.graphics.pop()
+ end
+ end;
+}
+
+class "ScrollingUiRegion" [UiRegion] {
+ init = function(self)
+ UiRegion.init(self)
+
+ self.offset = { x = 0; y = 0; }
+ self.line_color = { 0, 0, 0 }
+
+ self.zoom = 1
+ end;
+
+ _obj_rect = function(self, obj)
+ return Rectangle(
+ obj.rect.x + self.offset.x,
+ obj.rect.y + self.offset.y,
+ obj.rect.w, obj.rect.h)
+ end;
+
+ onmousemove = function(self, x, y, dx, dy)
+ UiRegion.onmousemove(self, x, y, dx, dy)
+
+ if self.is_mouse_down and self.selected_object == nil then
+ self.offset.x = self.offset.x + dx
+ self.offset.y = self.offset.y + dy
+ end
+ end;
+
+ onmousewheel = function(self, dx, dy)
+ UiRegion.onmousewheel(self, dx, dy)
+
+ if self.selected_object == nil then
+ self.zoom = self.zoom * (dy > 0 and 1.05 or (1 / 1.05))
+ if self.zoom >= 1 then self.zoom = 1 end
+ end
+ end;
+
+ update = function(self, dt)
+ UiRegion.update(self, dt)
+
+ if self.active then
+ local speed = 300
+ if love.keyboard.isDown "left" then
+ self.offset.x = self.offset.x + speed * dt
+ end
+ if love.keyboard.isDown "right" then
+ self.offset.x = self.offset.x - speed * dt
+ end
+ if love.keyboard.isDown "up" then
+ self.offset.y = self.offset.y + speed * dt
+ end
+ if love.keyboard.isDown "down" then
+ self.offset.y = self.offset.y - speed * dt
+ end
+ end
+ end;
+
+ draw = function(self)
+ love.graphics.push()
+
+ if self.background_color ~= nil then
+ love.graphics.setColor(self.background_color)
+ love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h)
+ end
+ local tmpbgcolor = self.background_color
+ self.background_color = nil
+
+ local xoff = self.offset.x
+ local yoff = self.offset.y
+ local spacing = 64
+
+ while xoff >= spacing do xoff = xoff - spacing end
+ while yoff >= spacing do yoff = yoff - spacing end
+ while xoff <= -spacing do xoff = xoff + spacing end
+ while yoff <= -spacing do yoff = yoff + spacing end
+
+ love.graphics.setColor(self.line_color)
+ for x = 0, 40 do
+ love.graphics.line(x * spacing + xoff, 0, x * spacing + xoff, self.rect.h)
+ end
+ for y = 0, 40 do
+ love.graphics.line(0, y * spacing + yoff, self.rect.w, y * spacing + yoff)
+ end
+
+ love.graphics.translate(self.offset.x, self.offset.y)
+ UiRegion.draw(self)
+ love.graphics.pop()
+
+ self.background_color = tmpbgcolor
+ end;
+}
+
+class "UiObject" {
+ init = function(self)
+ self.rect = Rectangle(0, 0, 0, 0)
+ self.uuid = uuid()
+ self.selected = false
+ self.order = math.random(1, 128)
+ end;
+
+ onclick = function(self, button, x, y) end;
+ ondrag = function(self, button, x, y, dx, dy) end;
+ onwheel = function(self, dx, dy) end;
+
+ onselect = function(self)
+ self.selected = true
+ end;
+ onunselect = function(self)
+ self.selected = false
+ end;
+
+ onkey = function(self, button) end;
+
+ update = function(dt) end;
+ draw = function() end;
+}
+
+class "DraggableRect" [UiObject] {
+ init = function(self, x, y, w, h)
+ UiObject.init(self)
+
+ self.rect = Rectangle(x, y, w, h)
+ end;
+
+ ondrag = function(self, button, x, y, dx, dy)
+ if button == 1 then
+ self.rect.x = self.rect.x + dx
+ self.rect.y = self.rect.y + dy
+ end
+ end;
+
+ draw = function(self)
+ if self.selected then
+ love.graphics.setColor(0, 0, 1)
+ love.graphics.rectangle("fill", -2, -2, self.rect.w + 4, self.rect.h + 4)
+ end
+ love.graphics.setColor(1, 1, 1)
+ love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h)
+
+ render_text(0, 0, {
+ { text = "1 Hello ", color = { 1, 0, 0 } };
+ { text = "world", color = { 0, 0, 1 } };
+ { newline = true };
+ { text = "2 Nother line", color = { 0, 0.5, 0 } };
+ { newline = true };
+ { newline = true };
+ { text = "3 nother Nother line", color = { 0, 0.5, 0 } };
+ })
+ end;
+}
+
+programming_font = false
+function render_text(x, y, text)
+ if not programming_font then
+ programming_font = love.graphics.newFont("res/FiraCode-Regular.ttf", 14)
+ love.graphics.setFont(programming_font)
+ end
+
+ local origx = x
+ local fheight = programming_font:getHeight()
+ local indent_level = 0
+ local indentation = ""
+ local fresh_newline = true
+
+ for _, txt in ipairs(text) do
+ if txt.text then
+ local printed = txt.text
+ if fresh_newline then
+ if txt.post_indent then
+ printed = txt.text .. indentation
+ else
+ printed = indentation .. txt.text
+ end
+ fresh_newline = false
+ end
+
+ if txt.background then
+ love.graphics.setColor(txt.background)
+ love.graphics.rectangle("fill", x, y, programming_font:getWidth(txt.text), fheight + 2);
+ end
+
+ love.graphics.setColor(txt.color)
+ love.graphics.print(printed, x, y)
+
+ x = x + programming_font:getWidth(printed)
+ end
+
+ if txt.newline then
+ y = y + fheight + 2;
+ x = origx
+ fresh_newline = true
+ end
+
+ if txt.change_indent then
+ indent_level = indent_level + txt.change_indent
+ indentation = ""
+ for i=1,indent_level do
+ indentation = indentation .. " "
+ end
+ end
+ end
+
+ return x, y
+end
+
+return module {
+ Ui = Ui;
+ UiRegion = UiRegion;
+ ScrollingUiRegion = ScrollingUiRegion;
+ UiObject = UiObject;
+ DraggableRect = DraggableRect;
+
+ render_text = render_text;
+}
--- /dev/null
+import {}
+
+-- This is unsued in favor of the C equivalent
+function buffered_read(file, buffer_size)
+ buffer_size = buffer_size or 4 * 1024
+
+ local buffer = {}
+ local ptr = -1
+
+ return coroutine.wrap(function()
+ while true do
+ if ptr <= 0 or ptr >= buffer_size then
+ ptr = 0
+ local str = file:read(buffer_size)
+ if not str then break end
+
+ buffer = {}
+ for c in (str or ''):gmatch '.' do
+ buffer[#buffer + 1] = c
+ end
+ end
+
+ ptr = ptr + 1
+ if ptr > #buffer then break end
+ coroutine.yield(buffer[ptr]:byte())
+ end
+ return
+ end)
+end
+
+function build_str(tbl_int)
+ if #tbl_int < 30 then
+ return string.char(unpack(tbl_int))
+ else
+ -- This is slower but may be needed for long strings
+ local str = ""
+ for _, c in ipairs(tbl_int) do
+ str = str .. string.char(c)
+ end
+ return str
+ end
+end
+
+function uuidv4()
+ local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
+ return string.gsub(template, '[xy]', function (c)
+ local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
+ return string.format('%x', v)
+ end)
+end
+
+function revipairs(t)
+ local max = 1
+ while t[max] ~= nil do
+ max = max + 1
+ end
+ local function ripairs_it(t, i)
+ i = i - 1
+ local v = t[i]
+ if v ~= nil then
+ return i, v
+ else
+ return nil
+ end
+ end
+ return ripairs_it, t, max
+end
+
+class "Rectangle" {
+ init = function(self, x, y, w, h)
+ self.x = x
+ self.y = y
+ self.w = w
+ self.h = h
+ end;
+
+ contains = function(self, x, y)
+ return x >= self.x and x <= self.x + self.w
+ and y >= self.y and y <= self.y + self.h
+ end;
+
+ intersects = function(self, other)
+ return self.x <= other.x + other.w
+ and self.x + self.w >= other.x
+ and self.y <= other.y + other.h
+ and self.y + self.h >= other.y
+ end;
+
+ __lt = function(self, other) return self:intersects(other) end
+}
+
+return module {
+ buffered_read = buffered_read;
+ build_str = build_str;
+
+ uuidv4 = uuidv4;
+
+ revipairs = revipairs;
+
+ Rectangle = Rectangle;
+}