Fixed some bugs and added features
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 1 Mar 2020 16:53:29 +0000 (10:53 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 1 Mar 2020 16:53:29 +0000 (10:53 -0600)
conf.lua
decompile.lua [deleted file]
docs/todo
main.lua
src/ui/ui.lua [new file with mode: 0644]
src/utils.lua [new file with mode: 0644]
src/wasm/decompile.lua [new file with mode: 0644]
src/wasm/text.lua [new file with mode: 0644]
ui.lua [deleted file]
utils.lua [deleted file]

index 3521b8eb121404600625b1f5ec64846aecfdac6e..e01fe289c689dcebf218f1c402624043199a473d 100644 (file)
--- a/conf.lua
+++ b/conf.lua
@@ -1,6 +1,53 @@
+
+local COLOR_SCHEMES = {
+       DARK = {
+               keyword = { 0.7, 0.7, 1.0 };
+               value = { 0.8, 1.0, 0.8 };
+               jumppoint = { 1, 0.5, 0.5 };
+
+               dark_background = { 0.1, 0.1, 0.1 };
+               background = { 0.15, 0.15, 0.15 };
+               code = { 0.9, 0.9, 0.9 };
+
+               primary         = { (0x0d / 0xff), (0x47 / 0xff), (0xa1 / 0xff) };
+               primary_light   = { (0x54 / 0xff), (0x72 / 0xff), (0xd3 / 0xff) };
+               primary_dark    = { (0x00 / 0xff), (0x21 / 0xff), (0x71 / 0xff) };
+               secondary       = { (0x00 / 0xff), (0x60 / 0xff), (0x64 / 0xff) };
+               secondary_light = { (0x42 / 0xff), (0x8e / 0xff), (0x92 / 0xff) };
+               secondary_dark  = { (0x00 / 0xff), (0x36 / 0xff), (0x3a / 0xff) };
+               primary_text    = { 1, 1, 1 };
+               secondary_text  = { 1, 1, 1 };
+       };
+
+       LIGHT = {
+               keyword = { 0, 0, 1 };
+               value = { 0.0, 0.5, 0.0 };
+               jumppoint = { 1, 0, 0 };
+
+               dark_background = { 0.8, 0.8, 0.8 };
+               background = { 1, 1, 1 };
+               code = { 0.0, 0.0, 0.0 };
+
+               primary         = { (0x90 / 0xff), (0xca / 0xff), (0xf9 / 0xff) };
+               primary_light   = { (0xc3 / 0xff), (0xfd / 0xff), (0xff / 0xff) };
+               primary_dark    = { (0x5d / 0xff), (0x99 / 0xff), (0xc6 / 0xff) };
+               secondary       = { (0x80 / 0xff), (0xde / 0xff), (0xea / 0xff) };
+               secondary_light = { (0xb4 / 0xff), (0xff / 0xff), (0xff / 0xff) };
+               secondary_dark  = { (0x4b / 0xff), (0xba / 0xff), (0xb8 / 0xff) };
+               primary_text    = { 0, 0, 0 };
+               secondary_text  = { 0, 0, 0 };
+       }
+}
+
 function love.conf(t)
        t.window.title = "TBD"
        t.window.width = 1200
        t.window.height = 900
        t.window.resizable = false
 end
+
+return {
+       COLOR_SCHEMES = COLOR_SCHEMES;
+
+       COLOR_SCHEME = COLOR_SCHEMES.LIGHT;
+}
diff --git a/decompile.lua b/decompile.lua
deleted file mode 100644 (file)
index b08a4a1..0000000
+++ /dev/null
@@ -1,740 +0,0 @@
--- 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";
-       random_str = "utils:random_str";
-       Stack = "utils:Stack";
-}
-
-function parse_valtype(r)
-       local val = r:read_byte()
-
-       local valtypes_map = {
-               [0x7f] = "i32";
-               [0x7E] = "i64";
-               [0x7D] = "f32";
-               [0x7C] = "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 = {}
-                       local name = random_str(8)
-
-                       local block = { "block", label = name, rt = rt }
-                       r.label_stack:push(block)
-
-                       while true do
-                               if r:peek_byte() == 0x0B then
-                                       r:read_byte()
-                                       break
-                               else
-                                       table.insert(instrs, parse_instr(r))
-                               end
-                       end
-
-                       r.label_stack:pop()
-
-                       block.instrs = instrs
-                       return block
-               end;
-               [0x03] = function()
-                       local rt = parse_blocktype(r)
-                       local instrs = {}
-                       local name = random_str(8)
-
-                       local block = { "loop", label = name, rt = rt }
-                       r.label_stack:push(block)
-
-                       while true do
-                               if r:peek_byte() == 0x0B then
-                                       r:read_byte()
-                                       break
-                               else
-                                       table.insert(instrs, parse_instr(r))
-                               end
-                       end
-
-                       r.label_stack:pop()
-
-                       block.instrs = instrs
-                       return block
-               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)
-       local idx = r:read_uint(32)
-       local block = r.label_stack:at(idx)
-       return { "labelidx", labelidx = idx, block = block }
-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 "Parser" {
-       init = function(self, filename)
-               self.wasm_file = parse_helper.open_file(filename)
-               self.label_stack = Stack()
-       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)
-               if N >= 32 then
-                       self:read_byte()
-                       self:read_byte()
-                       self:read_byte()
-                       self:read_byte()
-               end
-               if N >= 64 then
-                       self:read_byte()
-                       self:read_byte()
-                       self:read_byte()
-                       self:read_byte()
-               end
-               return 0.0;
-       end;
-}
-
-function decompile(filepath)
-       local reader = Parser(filepath)
-
-       return parse_module(reader)
-end
-
-return module { decompile }
index f3fb67e4624983775d30865955e4d4966cfe9eb5..52e7488f05bb5d1d8e8287ec0642d4663e3d0d42 100644 (file)
--- a/docs/todo
+++ b/docs/todo
@@ -1,2 +1,6 @@
-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.
+[X] Rewrite function text builder into a more abstract system
+[ ] Implement arrows from call sites to other functions
+[ ] Implement internal arrows from "br" to destination
+[ ] Implement right click context menu
+       * Own region dismissed on mouse leave
+       * Starts centered on mouse
index 67be1489c5d1ca87dcdd496740eeb392b8d98f93..81c37b399dea42baf9a9337a62aed2a87358b75a 100644 (file)
--- a/main.lua
+++ b/main.lua
 require "lualib.oop"
 
 import {
-       decompile = "decompile:";
-
-       Ui = "ui:Ui";
-       UiRegion = "ui:UiRegion";
-       ScrollingUiRegion = "ui:ScrollingUiRegion";
-       DraggableRect = "ui:DraggableRect";
-
-       render_text = "ui:render_text";
-
-       scissor_points = "utils:scissor_points";
        pprint = "lualib.pprint";
-}
-
-TEXT_COLORS = {
-       keyword = { 0.7, 0.7, 1.0 };
-       value = { 0.8, 1.0, 0.8 };
-       text = { 0.9, 0.9, 0.9 };
-       jumppoint = { 1, 0.5, 0.5 };
-
-       header_background = { 0.1, 0.1, 0.1 };
-       background = { 0.15, 0.15, 0.15 };
-}
 
-function build_func_header_text(func, mod)
-       local text = {}
+       decompile = "src.wasm.decompile:";
+       WasmCodeGenerator = "src.wasm.text:";
 
-       table.insert(text, { text = "[" .. func.funcidx .. "] ", color =  TEXT_COLORS.text })
-       table.insert(text, { text = func.name, color = TEXT_COLORS.keyword })
-       table.insert(text, { text = ": ", color = TEXT_COLORS.text })
-
-       local type_ = func.type_
-       for _, t in ipairs(type_.param_types) do
-               table.insert(text, { text = t .. " " })
-       end
-
-       if #type_.param_types == 0 then
-               table.insert(text, { text = "void " })
-       end
-
-       table.insert(text, { text = "-> " })
-
-       for _, t in ipairs(type_.result_types) do
-               table.insert(text, { text = t .. " " })
-       end
+       Ui = "src.ui.ui:Ui";
+       UiRegion = "src.ui.ui:UiRegion";
+       ScrollingUiRegion = "src.ui.ui:ScrollingUiRegion";
+       DraggableRect = "src.ui.ui:DraggableRect";
 
+       render_text = "src.ui.ui:render_text";
 
-       if #type_.result_types == 0 then
-               table.insert(text, { text = "void" })
-       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 = TEXT_COLORS.text, background = TEXT_COLORS.header_background, post_indent = true })
-       table.insert(textlist, { text = " ", post_indent = true })
-       line = line + 1
-end
-
-function instr_to_text(textlist, instr, func, mod)
-       if instr[1] == "block" then
-               for _, ins in ipairs(instr.instrs) do
-                       instr_to_text(textlist, ins, func, mod)
-                       add_line_number(textlist)
-               end
-               table.insert(textlist, { text = "label ", color = TEXT_COLORS.jumppoint })
-               table.insert(textlist, { text = instr.label, color = TEXT_COLORS.value })
-
-
-       elseif instr[1] == "loop" then
-               table.insert(textlist, { text = "loop ", color = TEXT_COLORS.jumppoint })
-               table.insert(textlist, { text = instr.label, color = TEXT_COLORS.value, 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 })
+       scissor_points = "src.utils:scissor_points";
 
-       elseif instr[1] == "if" then
-               table.insert(textlist, { text = "if", color = TEXT_COLORS.jumppoint, 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 = TEXT_COLORS.jumppoint, change_indent = 1 })
-                       for _, ins in ipairs(instr.else_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 = TEXT_COLORS.keyword })
-               table.insert(textlist, { text = mod.funcs[instr.x].name, color = TEXT_COLORS.value })
-
-       elseif instr[1]:match("local") then
-               table.insert(textlist, { text = instr[1] .. " ", color = TEXT_COLORS.keyword })
-               table.insert(textlist, { text = func.locals[instr.x + 1].name, color = TEXT_COLORS.value })
-
-       else
-               table.insert(textlist, { text = instr[1] .. " ", color = TEXT_COLORS.keyword })
-               if instr.x then
-                       if type(instr.x) == "table" then
-                               if instr.x[1] == "memarg" then
-                                       table.insert(textlist, {
-                                               text = ("align=0x%02x offset=0x%04x"):format(instr.x.align, instr.x.offset);
-                                               color = TEXT_COLORS.value
-                                       })
-                               end
-
-                               if instr.x[1] == "labelidx" then
-                                       table.insert(textlist, { text = instr.x.block.label .. " ", color = TEXT_COLORS.value })
-                                       table.insert(textlist, { text = ("[0x%02x]"):format(instr.x.labelidx), color = TEXT_COLORS.text })
-                               end
-                       else
-                               table.insert(textlist, { text = tostring(instr.x), color = TEXT_COLORS.value })
-                       end
-               end
-       end
-
-       return line
-end
-
-function build_func_body_text(func, mod)
-       local text = {}
-       line = 1
-
-       table.insert(text, { text = " Locals:", color = TEXT_COLORS.keyword, newline = true, change_indent = 2 })
-       for _, loc in ipairs(func.locals) do
-               table.insert(text, { text = loc.type_ .. " " .. loc.name, color = TEXT_COLORS.text , newline = true })
-       end
-
-       if #func.locals == 0 then
-               table.insert(text, { text = "no locals", color = TEXT_COLORS.text, newline = true })
-       end
-
-       table.insert(text, { newline = true, change_indent = -2 })
-       table.insert(text, { text = " Body:", color = TEXT_COLORS.keyword })
-
-       for _, instr in ipairs(func.body) do
-               add_line_number(text)
-               instr_to_text(text, instr, func, mod)
-       end
-
-       return text
-end
+       COLORS = "conf:COLOR_SCHEME";
+}
 
 class "FunctionBlock" [DraggableRect] {
        init = function(self, func, x, y)
-               DraggableRect.init(self, x, y, 400, 400)
+               DraggableRect.init(self, x, y, 400, 22)
 
-               self.header_text = build_func_header_text(func, mod)
+               WasmCodeGenerator:setup(func, mod, COLORS)
+               self.header_text = WasmCodeGenerator:build_header()
                if func.body then
-                       self.body_text = build_func_body_text(func, mod)
+                       self.body_text = WasmCodeGenerator:build_body()
                else
-                       self.rect.h = 48
                        self.body_text = {
-                               { text = "imported function", color = TEXT_COLORS.text }
+                               { text = "imported function", color = COLORS.code }
                        }
                end
+
                self.scroll = 0
+               self.body_visible = false
+               self.old_height = 400
+       end;
+
+       onclick = function(self, button, x, y)
+               DraggableRect.onclick(self, button, x, y)
+
+               if y <= 24 and button == 2 then
+                       self.body_visible = not self.body_visible
+                       self.rect.h = self.body_visible and self.old_height or 22
+               end
        end;
 
        onwheel = function(self, _, dy)
@@ -187,22 +57,25 @@ class "FunctionBlock" [DraggableRect] {
 
                if y >= 24 and button == 2 then
                        self.rect.w = math.max(100, x)
-                       self.rect.h = math.max(22, y)
+                       if self.body_visible then
+                               self.rect.h = math.max(22, y)
+                               self.old_height = self.rect.h
+                       end
                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(self.selected and TEXT_COLORS.background or TEXT_COLORS.header_background)
+               love.graphics.setColor(self.selected and COLORS.background or COLORS.dark_background)
                love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, self.rect.h - 4)
-               love.graphics.setColor(TEXT_COLORS.header_background)
+               love.graphics.setColor(COLORS.dark_background)
                love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, 18)
 
                love.graphics.intersectScissor(scissor_points(0, 0, self.rect.w, self.rect.h))
                local x, y = render_text(2, 2, self.header_text)
 
-               if self.body_text then
+               if self.body_visible and self.body_text then
                        love.graphics.intersectScissor(scissor_points(2, y - 1, self.rect.w - 4, self.rect.h - 22))
                        render_text(2, y + 4 - self.scroll, self.body_text)
                end
@@ -221,14 +94,14 @@ function love.load()
        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.15, 0.15, 0.2 }
+       ui_region.background_color = COLORS.background
+       ui_region.line_color = COLORS.dark_background
 
        local x = 0
        local y = 0
        for _, func in pairs(mod.funcs) do
                local block = FunctionBlock(func, x, y)
-               block.order = 600 - y
+               block.order = #mod.funcs * 20 + 1 - y
                ui_region:add_object(block)
                y = y + 20
        end
diff --git a/src/ui/ui.lua b/src/ui/ui.lua
new file mode 100644 (file)
index 0000000..3b6b7c4
--- /dev/null
@@ -0,0 +1,461 @@
+import {
+       Rectangle = "src.utils:Rectangle";
+       uuid = "src.utils:uuidv4";
+       revipairs = "src.utils:revipairs";
+       scissor_points = "src.utils:scissor_points";
+}
+
+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(scissor_points(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 obj.rect
+       end;
+
+       _transform_mouse = function(self, x, y)
+               return
+                       ((x + self.rect.x) - self.rect.w / 2) / self.zoom + self.rect.w / 2 - self.offset.x - self.rect.x,
+                       ((y + self.rect.y) - self.rect.h / 2) / self.zoom + self.rect.h / 2 - self.offset.y - self.rect.y
+       end;
+
+       onmousedown = function(self, button, x, y)
+               local mx, my = self:_transform_mouse(x, y)
+               UiRegion.onmousedown(self, button, mx, my)
+       end;
+
+       onmouseup = function(self, button, x, y)
+               local mx, my = self:_transform_mouse(x, y)
+               UiRegion.onmouseup(self, button, mx, my)
+       end;
+
+       onmousemove = function(self, x, y, dx, dy)
+               local mx, my = self:_transform_mouse(x, y)
+               UiRegion.onmousemove(self, mx, my, dx / self.zoom, dy / self.zoom)
+
+               if self.is_mouse_down and self.selected_object == nil then
+                       self.offset.x = self.offset.x + dx / self.zoom
+                       self.offset.y = self.offset.y + dy / self.zoom
+               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
+                       if self.zoom <= (1 / 1.05) ^ 30 then self.zoom = (1 / 1.05) ^ 30 end
+               end
+       end;
+
+       update = function(self, dt)
+               UiRegion.update(self, dt)
+
+               if self.active then
+                       local speed = 300 / self.zoom
+                       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
+
+               love.graphics.translate(self.rect.w / 2, self.rect.h / 2)
+               love.graphics.scale(self.zoom, self.zoom)
+               love.graphics.translate(-self.rect.w / 2, -self.rect.h / 2)
+
+
+               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 = -60, 100 do
+                       love.graphics.line(x * spacing + xoff, -self.rect.h * 2, x * spacing + xoff, self.rect.h * 4)
+               end
+               for y = -60, 100 do
+                       love.graphics.line(-self.rect.w * 2, y * spacing + yoff, self.rect.w * 4, 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, 1024)
+       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
+
+                       if txt.color then
+                               love.graphics.setColor(txt.color)
+                       end
+                       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;
+}
diff --git a/src/utils.lua b/src/utils.lua
new file mode 100644 (file)
index 0000000..d5ff427
--- /dev/null
@@ -0,0 +1,137 @@
+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 random_str(n)
+       local str = ""
+       for i=1, n do
+               str = str .. string.char(math.random(0,25) + 0x41 + 0x20 * math.random(0, 1))
+       end
+       return str
+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
+}
+
+class "Stack" {
+       init = function(self)
+               self.data = {}
+       end;
+
+       push = function(self, it)
+               table.insert(self.data, it)
+       end;
+
+       pop = function(self)
+               local rt = table.remove(self.data)
+               return rt
+       end;
+
+       at = function(self, x)
+               return self.data[#self.data - x]
+       end
+}
+
+function scissor_points(x, y, w, h)
+       local nx, ny = love.graphics.transformPoint(x, y)
+       local tx, ty = love.graphics.transformPoint(w + x, h + y)
+       return nx, ny, tx - nx, ty - ny
+end
+
+return module {
+       buffered_read = buffered_read;
+       build_str = build_str;
+       random_str = random_str;
+
+       uuidv4 = uuidv4;
+       revipairs = revipairs;
+
+       scissor_points = scissor_points;
+
+       Rectangle = Rectangle;
+       Stack = Stack;
+}
diff --git a/src/wasm/decompile.lua b/src/wasm/decompile.lua
new file mode 100644 (file)
index 0000000..08534db
--- /dev/null
@@ -0,0 +1,741 @@
+-- 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 = "src.utils:build_str";
+       random_str = "src.utils:random_str";
+       Stack = "src.utils:Stack";
+}
+
+function parse_valtype(r)
+       local val = r:read_byte()
+
+       local valtypes_map = {
+               [0x7f] = "i32";
+               [0x7E] = "i64";
+               [0x7D] = "f32";
+               [0x7C] = "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 = {}
+                       local name = random_str(8)
+
+                       local block = { "block", label = name, rt = rt }
+                       r.label_stack:push(block)
+
+                       while true do
+                               if r:peek_byte() == 0x0B then
+                                       r:read_byte()
+                                       break
+                               else
+                                       table.insert(instrs, parse_instr(r))
+                               end
+                       end
+
+                       r.label_stack:pop()
+
+                       block.instrs = instrs
+                       return block
+               end;
+               [0x03] = function()
+                       local rt = parse_blocktype(r)
+                       local instrs = {}
+                       local name = random_str(8)
+
+                       local block = { "loop", label = name, rt = rt }
+                       r.label_stack:push(block)
+
+                       while true do
+                               if r:peek_byte() == 0x0B then
+                                       r:read_byte()
+                                       break
+                               else
+                                       table.insert(instrs, parse_instr(r))
+                               end
+                       end
+
+                       r.label_stack:pop()
+
+                       block.instrs = instrs
+                       return block
+               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, table = r:read_byte() }
+               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)
+       local idx = r:read_uint(32)
+       local block = r.label_stack:at(idx)
+       if block == nil then block = { label = "return " } end
+       return { "labelidx", labelidx = idx, block = block }
+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 "Parser" {
+       init = function(self, filename)
+               self.wasm_file = parse_helper.open_file(filename)
+               self.label_stack = Stack()
+       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)
+               if N >= 32 then
+                       self:read_byte()
+                       self:read_byte()
+                       self:read_byte()
+                       self:read_byte()
+               end
+               if N >= 64 then
+                       self:read_byte()
+                       self:read_byte()
+                       self:read_byte()
+                       self:read_byte()
+               end
+               return 0.0;
+       end;
+}
+
+function decompile(filepath)
+       local reader = Parser(filepath)
+
+       return parse_module(reader)
+end
+
+return module { decompile }
diff --git a/src/wasm/text.lua b/src/wasm/text.lua
new file mode 100644 (file)
index 0000000..ed9d61e
--- /dev/null
@@ -0,0 +1,162 @@
+import {
+}
+
+WasmCodeGenerator = singleton {
+       init = function(self) end;
+
+       setup = function(self, func, mod, colors)
+               self.func = func
+               self.mod = mod
+               self.colors = colors;
+               self.line = 1
+       end;
+
+       build_header = function(self)
+               local text = {}
+
+               table.insert(text, { text = "[" .. self.func.funcidx .. "] ", color = self.colors.code })
+               table.insert(text, { text = self.func.name, color = self.colors.keyword })
+               table.insert(text, { text = ": ", color = self.colors.code })
+
+               local type_ = self.func.type_
+               for _, t in ipairs(type_.param_types) do
+                       table.insert(text, { text = t .. " " })
+               end
+
+               if #type_.param_types == 0 then
+                       table.insert(text, { text = "void " })
+               end
+
+               table.insert(text, { text = "-> " })
+
+               for _, t in ipairs(type_.result_types) do
+                       table.insert(text, { text = t .. " " })
+               end
+
+               if #type_.result_types == 0 then
+                       table.insert(text, { text = "void" })
+               end
+
+               table.insert(text, { newline = true })
+
+               return text
+       end;
+
+       add_line_number = function(self, textlist, newline)
+               if newline == nil then newline = true end
+
+               table.insert(textlist, { newline = newline })
+               table.insert(textlist, {
+                       text = ("%4d"):format(line),
+                       color = self.colors.code,
+                       background = self.colors.dark_background,
+                       post_indent = true
+               })
+               table.insert(textlist, { text = " ", post_indent = true })
+
+               line = line + 1
+       end;
+
+       instr_to_text = function(self, textlist, instr)
+               if instr[1] == "block" then
+                       table.insert(textlist, { change_indent = 1 })
+                       for _, ins in ipairs(instr.instrs) do
+                               self:instr_to_text(textlist, ins)
+                       end
+                       table.insert(textlist, { change_indent = -1 })
+                       self:add_line_number(textlist)
+                       table.insert(textlist, { text = "label ", color = self.colors.jumppoint })
+                       table.insert(textlist, { text = instr.label, color = self.colors.value })
+
+               elseif instr[1] == "loop" then
+                       self:add_line_number(textlist)
+                       table.insert(textlist, { text = "loop ", color = self.colors.jumppoint })
+                       table.insert(textlist, { text = instr.label, color = self.colors.value, change_indent = 1 })
+
+                       for _, ins in ipairs(instr.instrs) do
+                               self:instr_to_text(textlist, ins)
+                       end
+                       table.insert(textlist, { change_indent = -1 })
+
+               elseif instr[1] == "if" then
+                       self:add_line_number(textlist)
+                       table.insert(textlist, { text = "if", color = self.colors.jumppoint, change_indent = 1 })
+                       for _, ins in ipairs(instr.instrs) do
+                               self:instr_to_text(textlist, ins)
+                       end
+                       table.insert(textlist, { change_indent = -1 })
+
+                       if #instr.else_instrs > 0 then
+                               self:add_line_number(textlist)
+                               table.insert(textlist, { text = "else", color = self.colors.jumppoint, change_indent = 1 })
+                               for _, ins in ipairs(instr.else_instrs) do
+                                       self:instr_to_text(textlist, ins)
+                               end
+                               table.insert(textlist, { change_indent = -1 })
+                       end
+
+               elseif instr[1] == "call" then
+                       self:add_line_number(textlist)
+                       table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword })
+                       table.insert(textlist, { text = self.mod.funcs[instr.x].name, color = self.colors.value })
+
+               elseif instr[1]:match("local") then
+                       self:add_line_number(textlist)
+                       table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword })
+                       table.insert(textlist, { text = self.func.locals[instr.x + 1].name, color = self.colors.value })
+
+               else
+                       self:add_line_number(textlist)
+                       table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword })
+                       if instr.x then
+                               if type(instr.x) == "table" then
+                                       if instr.x[1] == "memarg" then
+                                               table.insert(textlist, {
+                                                       text = ("align=0x%02x offset=0x%04x"):format(instr.x.align, instr.x.offset);
+                                                       color = self.colors.value
+                                               })
+                                       end
+
+                                       if instr.x[1] == "labelidx" then
+                                               if instr.x.block then
+                                                       table.insert(textlist, { text = instr.x.block.label .. " ", color = self.colors.value })
+                                               end
+                                               table.insert(textlist, { text = ("[0x%02x]"):format(instr.x.labelidx), color = self.colors.code })
+                                       end
+                               else
+                                       table.insert(textlist, { text = tostring(instr.x), color = self.colors.value })
+                               end
+                       end
+               end
+
+               return line
+       end;
+
+       build_body = function(self)
+               local text = {}
+               line = 1
+
+               table.insert(text, { text = " Locals:", color = self.colors.keyword, newline = true, change_indent = 2 })
+               for _, loc in ipairs(self.func.locals) do
+                       table.insert(text, { text = loc.type_ .. " " .. loc.name, color = self.colors.code , newline = true })
+               end
+
+               if #self.func.locals == 0 then
+                       table.insert(text, { text = "no locals", color = self.colors.code, newline = true })
+               end
+
+               table.insert(text, { newline = true, change_indent = -2 })
+               table.insert(text, { text = " Body:", color = self.colors.keyword })
+
+               for _, instr in ipairs(self.func.body) do
+                       self:instr_to_text(text, instr)
+               end
+
+               return text
+       end;
+}
+
+return module {
+       WasmCodeGenerator;
+}
+
diff --git a/ui.lua b/ui.lua
deleted file mode 100644 (file)
index 1d3f1b7..0000000
--- a/ui.lua
+++ /dev/null
@@ -1,463 +0,0 @@
-import {
-       Rectangle = "utils:Rectangle";
-       uuid = "utils:uuidv4";
-       revipairs = "utils:revipairs";
-       scissor_points = "utils:scissor_points";
-}
-
-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(scissor_points(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 obj.rect
-       end;
-
-       _transform_mouse = function(self, x, y)
-               return
-                       ((x + self.rect.x) - self.rect.w / 2) / self.zoom + self.rect.w / 2 - self.offset.x - self.rect.x,
-                       ((y + self.rect.y) - self.rect.h / 2) / self.zoom + self.rect.h / 2 - self.offset.y - self.rect.y
-       end;
-
-       onmousedown = function(self, button, x, y)
-               print(x, y)
-               local mx, my = self:_transform_mouse(x, y)
-               print(mx, my)
-               UiRegion.onmousedown(self, button, mx, my)
-       end;
-
-       onmouseup = function(self, button, x, y)
-               local mx, my = self:_transform_mouse(x, y)
-               UiRegion.onmouseup(self, button, mx, my)
-       end;
-
-       onmousemove = function(self, x, y, dx, dy)
-               local mx, my = self:_transform_mouse(x, y)
-               UiRegion.onmousemove(self, mx, my, dx / self.zoom, dy / self.zoom)
-
-               if self.is_mouse_down and self.selected_object == nil then
-                       self.offset.x = self.offset.x + dx / self.zoom
-                       self.offset.y = self.offset.y + dy / self.zoom
-               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
-                       if self.zoom <= (1 / 1.05) ^ 20 then self.zoom = (1 / 1.05) ^ 20 end
-               end
-       end;
-
-       update = function(self, dt)
-               UiRegion.update(self, dt)
-
-               if self.active then
-                       local speed = 300 / self.zoom
-                       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
-
-               love.graphics.translate(self.rect.w / 2, self.rect.h / 2)
-               love.graphics.scale(self.zoom, self.zoom)
-               love.graphics.translate(-self.rect.w / 2, -self.rect.h / 2)
-
-
-               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 = -40, 80 do
-                       love.graphics.line(x * spacing + xoff, -self.rect.h, x * spacing + xoff, self.rect.h * 2)
-               end
-               for y = -40, 80 do
-                       love.graphics.line(-self.rect.w, y * spacing + yoff, self.rect.w * 2, 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, 1024)
-       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
-
-                       if txt.color then
-                               love.graphics.setColor(txt.color)
-                       end
-                       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;
-}
diff --git a/utils.lua b/utils.lua
deleted file mode 100644 (file)
index d5ff427..0000000
--- a/utils.lua
+++ /dev/null
@@ -1,137 +0,0 @@
-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 random_str(n)
-       local str = ""
-       for i=1, n do
-               str = str .. string.char(math.random(0,25) + 0x41 + 0x20 * math.random(0, 1))
-       end
-       return str
-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
-}
-
-class "Stack" {
-       init = function(self)
-               self.data = {}
-       end;
-
-       push = function(self, it)
-               table.insert(self.data, it)
-       end;
-
-       pop = function(self)
-               local rt = table.remove(self.data)
-               return rt
-       end;
-
-       at = function(self, x)
-               return self.data[#self.data - x]
-       end
-}
-
-function scissor_points(x, y, w, h)
-       local nx, ny = love.graphics.transformPoint(x, y)
-       local tx, ty = love.graphics.transformPoint(w + x, h + y)
-       return nx, ny, tx - nx, ty - ny
-end
-
-return module {
-       buffered_read = buffered_read;
-       build_str = build_str;
-       random_str = random_str;
-
-       uuidv4 = uuidv4;
-       revipairs = revipairs;
-
-       scissor_points = scissor_points;
-
-       Rectangle = Rectangle;
-       Stack = Stack;
-}