├── .dart_tool └── pub │ └── bin │ ├── sdk-version │ └── test │ └── test.dart.snapshot.dart2 ├── .gitignore ├── CHANGELOG.md ├── README.md ├── analysis_options.yaml ├── benchmark ├── arithmetic.dart ├── arithmetic.lua ├── core.dart ├── sha256.dart ├── sha256.lua ├── variables.dart └── variables.lua ├── bin └── dartlua.dart ├── example ├── hello_world.dart └── hello_world.lua ├── lib ├── lua5.2.dart └── src │ ├── 5_2 │ ├── context.dart │ ├── lualib │ │ ├── base.dart │ │ ├── bit.dart │ │ ├── coroutine.dart │ │ ├── math.dart │ │ ├── string.dart │ │ └── table.dart │ ├── parser.dart │ ├── state.dart │ ├── table.dart │ └── vm.dart │ ├── decode.dart │ ├── disassemble.dart │ ├── flavor.dart │ ├── func.dart │ ├── inst.dart │ └── util.dart ├── luac.out ├── luadart.iml ├── luadist └── setup.sh ├── pubspec.yaml ├── test ├── generate_expect.dart ├── test_5.2.dart └── test_all.dart └── testcase ├── arith.lua ├── arith.txt ├── closures.lua ├── closures.txt ├── concat.lua ├── concat.txt ├── forloops.lua ├── forloops.txt ├── pairs.lua ├── pairs.txt ├── pcall.lua ├── pcall.txt ├── sha256.lua ├── sha256.txt ├── tables.lua ├── tables.txt ├── vararg.lua └── vararg.txt /.dart_tool/pub/bin/sdk-version: -------------------------------------------------------------------------------- 1 | 2.1.0-dev.3.1 2 | -------------------------------------------------------------------------------- /.dart_tool/pub/bin/test/test.dart.snapshot.dart2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/dartlua/12090c0cf6ad953ffac175bde37a02c9f2edfbf0/.dart_tool/pub/bin/test/test.dart.snapshot.dart2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | luadist/build/ 2 | luadist/bin/ 3 | 4 | # Files and directories created by pub 5 | .packages 6 | .pub/ 7 | build/ 8 | # Remove the following pattern if you wish to check in your lock file 9 | pubspec.lock 10 | 11 | # Directory created by dartdoc 12 | doc/api/ 13 | 14 | # IntelliJ IDEA 15 | .idea/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.1 4 | 5 | - First version, refactor from luabc 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://i.imgur.com/6aWytWW.png) 2 | # A library and cli for running, disassembling, and debugging Lua programs. 3 | 4 | ## Current features ~ 5 | * Fully functional VM 6 | * Unit tests 7 | * Big chunk of of the 5.2 builtins implemented 8 | * Versatile bytecode decoding 9 | * Disassembly 10 | * Identical edge case behavior to reference implementation 11 | 12 | ## Roadmap ~ 13 | See https://github.com/PixelToast/dartlua/milestones 14 | 15 | ## Contributing ~ 16 | PRs are welcome 17 | Try to maintain a consistent code style as the rest of the project and bear with me while things get documented 18 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true 3 | 4 | # Lint rules and documentation, see http://dart-lang.github.io/linter/lints 5 | linter: 6 | rules: 7 | - cancel_subscriptions 8 | - hash_and_equals 9 | - iterable_contains_unrelated_type 10 | - list_remove_unrelated_type 11 | - test_types_in_equals 12 | - unrelated_type_equality_checks 13 | - valid_regexps 14 | -------------------------------------------------------------------------------- /benchmark/arithmetic.dart: -------------------------------------------------------------------------------- 1 | import 'core.dart'; 2 | import 'dart:async'; 3 | import 'dart:developer'; 4 | 5 | Future main() async { 6 | await testAll("arithmetic", 16384); 7 | 8 | print("(paused for debugging)"); 9 | debugger(); 10 | } -------------------------------------------------------------------------------- /benchmark/arithmetic.lua: -------------------------------------------------------------------------------- 1 | local x = 0 2 | 3 | function step() 4 | for i = 1, 1000 do 5 | x = (x + i) / 2 6 | end 7 | end 8 | 9 | if not test then -- Running vanilla lua or LuaJIT 10 | local count = 16384 11 | 12 | for i = 1, count do 13 | step() 14 | end 15 | 16 | local socket = require("socket") 17 | local t0 = socket.gettime() 18 | 19 | for i = 1, count do 20 | step() 21 | end 22 | 23 | local dt = socket.gettime() - t0 24 | print(math.floor(dt * 1000000)) 25 | end -------------------------------------------------------------------------------- /benchmark/core.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | import 'package:lua/src/5_2/context.dart'; 5 | import 'package:lua/src/5_2/lualib/base.dart'; 6 | import 'package:lua/src/5_2/table.dart'; 7 | import 'package:lua/src/5_2/vm.dart'; 8 | import 'package:lua/src/decode.dart'; 9 | import 'package:lua/src/func.dart'; 10 | 11 | Map cache = {}; 12 | 13 | int _stepTest(CodeDump code, int count) { 14 | var env = new Table(); 15 | env["test"] = true; 16 | var context = new Context(env: env); 17 | 18 | loadBase(context); 19 | loadMath(context); 20 | loadString(context); 21 | loadBit(context); 22 | loadTable(context); 23 | 24 | var cl = new Closure( 25 | code.main, 26 | context: context, 27 | upvalues: [new Upval.store(context.env)], 28 | ); 29 | 30 | var res = new Thread(closure: cl).resume(); 31 | if (!res.success) throw res.values[0]; 32 | 33 | var step = env["step"] as Closure; 34 | if (step == null) throw "Benchmark diddn't create step function"; 35 | 36 | for (int i = 0; i < count; i++) { // Warm up 37 | new Thread(closure: step).resume(); 38 | } 39 | 40 | int dt; 41 | 42 | runZoned(() { 43 | var t0 = new DateTime.now().microsecondsSinceEpoch; 44 | 45 | for (int i = 0; i < count; i++) { 46 | new Thread(closure: step).resume(); 47 | } 48 | 49 | dt = new DateTime.now().microsecondsSinceEpoch - t0; 50 | }, zoneSpecification: new ZoneSpecification( 51 | print: (Zone self, ZoneDelegate parent, Zone zone, String line) {}, 52 | )); 53 | 54 | if (env["finish"] as Closure != null) { 55 | new Thread(closure: env["finish"]).resume(); 56 | } 57 | 58 | return dt; 59 | } 60 | 61 | // Returns microseconds elapsed 62 | Future test(String path, int count) async { 63 | CodeDump code; 64 | 65 | if (cache.containsKey(path)) { 66 | code = cache[path]; 67 | } else { 68 | var res = await Process.run("wsl", ["luac5.2", path]); 69 | 70 | if (res.stderr != "") throw res.stderr; 71 | 72 | var f = new File("luac.out"); 73 | 74 | if (!await f.exists()) throw "luac.out not found"; 75 | var fh = await f.open(mode: FileMode.READ); 76 | var buffer = new Uint8List(await f.length()); 77 | await fh.readInto(buffer); 78 | fh.close(); 79 | 80 | var decoder = new Decoder(buffer.buffer); 81 | code = decoder.readCodeDump(path); 82 | } 83 | 84 | for (int i = 0; i < 10; i++) { 85 | _stepTest(code, count ~/ 50); 86 | } 87 | 88 | return _stepTest(code, count); 89 | } 90 | 91 | Future testAll(String file, int count) async { 92 | var lua51 = Process.run("wsl", ["luadist/bin/lua5.1", "benchmark/$file.lua"]); 93 | var lua52 = Process.run("wsl", ["luadist/bin/lua5.2", "benchmark/$file.lua"]); 94 | var luajit = Process.run("wsl", ["luadist/bin/luajit", "benchmark/$file.lua"]); 95 | 96 | var base = 1 / ((await test("benchmark/$file.lua", count)) / (count * 1000000)); 97 | 98 | print("LuaDart: ${base.toStringAsFixed(2)} H/s"); 99 | 100 | diff(String name, ProcessResult res) { 101 | if (res.stderr != "") throw res.stderr; 102 | var o = 1 / (int.parse(res.stdout.split("\n").first) / (count * 1000000)); 103 | print("${"$name:".padRight(10)} ${o.toStringAsFixed(0)} H/s (${(o / base).toStringAsFixed(2)}x)"); 104 | } 105 | 106 | diff("Lua 5.2", await lua52); 107 | diff("LuaJIT", await luajit); 108 | diff("Lua 5.1", await lua51); 109 | 110 | //return base; 111 | return 1.0; 112 | } -------------------------------------------------------------------------------- /benchmark/sha256.dart: -------------------------------------------------------------------------------- 1 | import 'core.dart'; 2 | import 'dart:async'; 3 | import 'dart:developer'; 4 | import 'dart:io'; 5 | 6 | var count = 8192; 7 | 8 | Future main() async { 9 | var base = await testAll("sha256", 8192); 10 | 11 | if (await new File("/home/pixel/CLionProjects/shabenchmark/shabenchmark").exists()) { 12 | var shabench = await Process.run("/home/pixel/CLionProjects/shabenchmark/shabenchmark", []); 13 | // lol hardcoded string 14 | var o = 1 / (int.parse(shabench.stdout.split("\n").first) / (count * 1000000)); 15 | print("C: ${o.toStringAsFixed(0)} H/s (${(o / base).toStringAsFixed(2)}x)"); 16 | } 17 | 18 | 19 | print("(paused for debugging)"); 20 | debugger(); 21 | } -------------------------------------------------------------------------------- /benchmark/sha256.lua: -------------------------------------------------------------------------------- 1 | if not bit and not bit32 then 2 | bit = require("bit") 3 | end 4 | 5 | bit = bit or bit32 6 | 7 | local function chars2num(txt) 8 | return (txt:byte(1) * 16777216) + (txt:byte(2) * 65536) + (txt:byte(3) * 256) + txt:byte(4) 9 | end 10 | 11 | local function limit(num) 12 | return bit.band(num) 13 | end 14 | 15 | local z = 0 -- curb your linter errors 16 | local _hex = {[z] = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"} 17 | 18 | bit.tohex = bit.tohex or function(n) 19 | local o = "" 20 | local x = limit(n) 21 | for i = 0, 3 do 22 | o = _hex[math.floor(x / 16) % 16] .. _hex[x % 16] .. o 23 | x = math.floor(x / 256) 24 | end 25 | return o 26 | end 27 | 28 | local function num2chars(num, l) 29 | local out = "" 30 | for l1=1, l or 4 do 31 | out = string.char(math.floor(num / (256 ^ (l1 - 1))) % 256) .. out 32 | end 33 | return out 34 | end 35 | 36 | local sha256 37 | 38 | do 39 | local bxor = bit.bxor 40 | local ror = bit.rrotate or bit.ror 41 | local rshift = bit.rshift 42 | local band = bit.band 43 | local bnot = bit.bnot 44 | 45 | local k={ 46 | 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, 47 | 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, 48 | 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, 49 | 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, 50 | 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, 51 | 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, 52 | 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, 53 | 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2, 54 | } 55 | 56 | function sha256(txt) 57 | local ha = { 58 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 59 | 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, 60 | } 61 | 62 | local len = #txt 63 | 64 | txt = txt .. "\128" .. ("\0"):rep(64 - ((len + 9) % 64)) .. num2chars(8 * len, 8) 65 | 66 | local w = {} 67 | 68 | for chunkind = 1, #txt, 64 do 69 | local rawchunk = txt:sub(chunkind, chunkind + 63) 70 | local chunk = {} 71 | 72 | for i = 1, 64, 4 do 73 | local x = chars2num(rawchunk:sub(i)) 74 | chunk[math.floor(i / 4) + 1] = x 75 | end 76 | 77 | for i = 1, 16 do 78 | w[i] = chunk[i] 79 | end 80 | 81 | for i = 16, 63 do 82 | local s0 = bxor(ror(w[i - 14], 7), ror(w[i - 14], 18), rshift(w[i - 14], 3)) 83 | local s1 = bxor(ror(w[i - 1], 17), ror(w[i - 1], 19), rshift(w[i - 1], 10)) 84 | w[i + 1] = w[i - 15] + s0 + w[i - 6] + s1 85 | end 86 | 87 | local a, b, c, d, e, f, g, h = ha[1], ha[2], ha[3], ha[4], ha[5], ha[6], ha[7], ha[8] 88 | for i = 0, 63 do 89 | local temp1 = limit((h + bxor(ror(e, 6), ror(e, 11), ror(e, 25))) + (bxor(band(e, f), band(bnot(e), g)) + k[i + 1] + w[i + 1])) 90 | a, b, c, d, e, f, g, h = temp1 + bxor(ror(a, 2), ror(a, 13),ror(a, 22)) + bxor(band(a, b), band(a, c), band(b, c)), a, b, c, d + temp1, e, f, g 91 | end 92 | ha[1], ha[2], ha[3], ha[4], ha[5], ha[6], ha[7], ha[8] = limit(ha[1] + a), limit(ha[2] + b), limit(ha[3] + c), limit(ha[4] + d), limit(ha[5] + e), limit(ha[6] + f), limit(ha[7] + g), limit(ha[8] + h) 93 | end 94 | 95 | return bit.tohex(ha[1]) .. bit.tohex(ha[2]) .. bit.tohex(ha[3]) .. bit.tohex(ha[4]) .. 96 | bit.tohex(ha[5]) .. bit.tohex(ha[6]) .. bit.tohex(ha[7]) .. bit.tohex(ha[8]) 97 | end 98 | end 99 | 100 | local i = 1 101 | 102 | assert(sha256("potato") == "e91c254ad58860a02c788dfb5c1a65d6a8846ab1dc649631c7db16fef4af2dec") 103 | 104 | local hash = "potato" 105 | 106 | function step() 107 | hash = sha256(hash) 108 | end 109 | 110 | function finish() 111 | -- print(hash) 112 | end 113 | 114 | if not test then -- Running vanilla lua or LuaJIT 115 | local count = 8192 116 | 117 | for i = 1, count do 118 | step() 119 | end 120 | 121 | hash = "potato" 122 | 123 | local socket = require("socket") 124 | local t0 = socket.gettime() 125 | 126 | for i = 1, count do 127 | step() 128 | end 129 | 130 | local dt = socket.gettime() - t0 131 | print(math.floor(dt * 1000000)) 132 | 133 | -- print(hash) 134 | end -------------------------------------------------------------------------------- /benchmark/variables.dart: -------------------------------------------------------------------------------- 1 | import 'core.dart'; 2 | import 'dart:async'; 3 | import 'dart:developer'; 4 | 5 | Future main() async { 6 | await testAll("variables", 4096); 7 | 8 | print("(paused for debugging)"); 9 | debugger(); 10 | } -------------------------------------------------------------------------------- /benchmark/variables.lua: -------------------------------------------------------------------------------- 1 | local x = 0 2 | 3 | local a, b, c, d 4 | 5 | ;(function() 6 | a = function(i) 7 | x = (x + i) / 2 8 | end 9 | ;(function() 10 | b = function(i) 11 | x = (x + i) / 3 12 | end 13 | ;(function() 14 | c = function(i) 15 | x = (x + i) / 4 16 | end 17 | ;(function() 18 | d = function(i) 19 | x = (x + i) / 5 20 | end 21 | end)() 22 | end)() 23 | end)() 24 | end)() 25 | 26 | function step() 27 | for i = 1, 1000 do 28 | a(i) 29 | b(i) 30 | c(i) 31 | d(i) 32 | end 33 | end 34 | 35 | if not test then -- Running vanilla lua or LuaJIT 36 | local count = 4096 37 | 38 | for i = 1, count do 39 | step() 40 | end 41 | 42 | local socket = require("socket") 43 | local t0 = socket.gettime() 44 | 45 | for i = 1, count do 46 | step() 47 | end 48 | 49 | local dt = socket.gettime() - t0 50 | print(math.floor(dt * 1000000)) 51 | end -------------------------------------------------------------------------------- /bin/dartlua.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | import 'package:args/args.dart'; 4 | import 'package:lua/lua5.2.dart'; 5 | import 'package:lua/src/decode.dart'; 6 | import 'package:lua/src/disassemble.dart'; 7 | 8 | const help = 9 | "Usage: dartlua [options] [command] [script [args]]"; 10 | 11 | const commandHelp = 12 | "Available commands:\n" 13 | " run Runs the Lua script\n" 14 | " disassemble Disassembles the Lua script"; 15 | 16 | main(List rawArgs) async { 17 | var argParser = new ArgParser(); 18 | var args = argParser.parse(rawArgs); 19 | 20 | if (args.rest.length == 0) { 21 | stderr.writeln(help); 22 | stderr.writeln(commandHelp); 23 | exit(1); 24 | } 25 | 26 | var command = args.rest[0]; 27 | 28 | if (command == "run") { 29 | if (args.rest.length < 2) { 30 | stderr.writeln("Error: no file provided."); 31 | stderr.writeln(help); 32 | exit(1); 33 | } 34 | 35 | var state = new LuaState(); 36 | state.doFile(args.rest[1], args: args.rest.skip(2).toList()); 37 | } else if (command == "disassemble") { 38 | if (args.rest.length < 2) { 39 | stderr.writeln("Error: no file provided."); 40 | stderr.writeln(help); 41 | exit(1); 42 | } 43 | 44 | var path = args.rest[1]; 45 | var res = await Process.run("luadist/bin/luac5.2", [path]); 46 | 47 | if (res.stderr != "") throw res.stderr; 48 | 49 | var f = new File("luac.out"); 50 | 51 | if (!await f.exists()) throw "luac.out not found"; 52 | var fh = await f.open(mode: FileMode.READ); 53 | var buffer = new Uint8List(await f.length()); 54 | await fh.readInto(buffer); 55 | 56 | await f.delete(); 57 | 58 | var decoder = new Decoder(buffer.buffer); 59 | print(disassemble(decoder.readCodeDump(path))); 60 | } else { 61 | stderr.writeln("Error: no such command '$command'"); 62 | stderr.writeln(commandHelp); 63 | } 64 | } -------------------------------------------------------------------------------- /example/hello_world.dart: -------------------------------------------------------------------------------- 1 | import 'package:lua/lua5.2.dart'; 2 | 3 | main() async { 4 | var state = new LuaState(); 5 | await state.doFile("example/hello_world.lua"); 6 | } -------------------------------------------------------------------------------- /example/hello_world.lua: -------------------------------------------------------------------------------- 1 | print("Hello, world!") -------------------------------------------------------------------------------- /lib/lua5.2.dart: -------------------------------------------------------------------------------- 1 | /// Support for doing something awesome. 2 | /// 3 | /// More dartdocs go here. 4 | library lua; 5 | 6 | export 'package:lua/src/5_2/state.dart'; 7 | -------------------------------------------------------------------------------- /lib/src/5_2/context.dart: -------------------------------------------------------------------------------- 1 | import 'package:lua/src/5_2/state.dart'; 2 | import 'package:lua/src/5_2/table.dart'; 3 | import 'package:lua/src/5_2/vm.dart'; 4 | import 'package:lua/src/func.dart'; 5 | import 'package:lua/src/util.dart'; 6 | import 'package:meta/meta.dart'; 7 | 8 | class LuaErrorImpl extends LuaError { 9 | LuaErrorImpl(dynamic value, this.proto, this.inst, {this.dartStackTrace}) : value = value is LuaErrorImpl ? value.value : value; 10 | final dynamic value; 11 | final Prototype proto; 12 | final int inst; 13 | String get source => proto.root.name; 14 | StackTrace dartStackTrace; 15 | 16 | toStringShort() => "${proto.root.name}:${maybeAt(proto.lines, inst)}: $value"; 17 | toString() => "${toStringShort()}${dartStackTrace == null ? "" : "\n$dartStackTrace"}"; 18 | } 19 | 20 | typedef List LuaDartFunc(List params); 21 | typedef List LuaDartDebugFunc(Thread thread, List params); 22 | typedef num ArithCB(num x, num y); 23 | typedef num UArithCB(num x); 24 | 25 | class TypeMatcher { 26 | const TypeMatcher(); 27 | bool matches(dynamic x) => x is T; 28 | toString() { 29 | if (T == String) return "string"; 30 | if (T == Table) return "table"; 31 | if (T == num) return "number"; 32 | if (T == Closure) return "function"; 33 | if (T == LuaDartFunc) return "function"; 34 | if (T == Thread) return "thread"; 35 | if (T == Null) return "nil"; 36 | return T.toString(); 37 | } 38 | } 39 | 40 | class Context { 41 | Context({ 42 | this.userdata, 43 | @required this.env, 44 | }); 45 | 46 | dynamic userdata; 47 | 48 | Table env; 49 | Table stringMetatable; 50 | LuaDartFunc yield; 51 | 52 | static String getTypename(dynamic v) { 53 | if (v is String) return "string"; 54 | if (v is Table) return "table"; 55 | if (v is num) return "number"; 56 | if (v is Closure) return "function"; 57 | if (v is LuaDartFunc) return "function"; 58 | if (v is Thread) return "thread"; 59 | if (v == null) return "nil"; 60 | return "${v.runtimeType}"; 61 | } 62 | 63 | static dynamic getAny(List args, int idx, String name) { 64 | if (args.length <= idx) throw "bad argument #${idx + 1} to '$name' (value expected, got no value)"; 65 | return args[idx]; 66 | } 67 | 68 | static T getArg1(List args, int idx, String name) { 69 | if (args.length <= idx) throw "bad argument #${idx + 1} to '$name' (${new TypeMatcher()} expected, got no value)"; 70 | var x = args[idx]; 71 | if (new TypeMatcher().matches(x)) return x; 72 | throw "bad argument #${idx + 1} to '$name' (${new TypeMatcher()} expected, got ${getTypename(x)})"; 73 | } 74 | 75 | static dynamic getArg2(List args, int idx, String name) { 76 | if (args.length <= idx) throw "bad argument #${idx + 1} to '$name' (${new TypeMatcher()} expected, got no value)"; 77 | var x = args[idx]; 78 | if (new TypeMatcher().matches(x) || new TypeMatcher().matches(x)) return x; 79 | throw "bad argument #${idx + 1} to '$name' (${new TypeMatcher()} expected, got ${getTypename(x)})"; 80 | } 81 | 82 | static dynamic getArg3(List args, int idx, String name) { 83 | if (args.length <= idx) throw "bad argument #${idx + 1} to '$name' (${new TypeMatcher()} expected, got no value)"; 84 | var x = args[idx]; 85 | if (new TypeMatcher().matches(x) || new TypeMatcher().matches(x) || new TypeMatcher().matches(x)) return x; 86 | throw "bad argument #${idx + 1} to '$name' (${new TypeMatcher()} expected, got ${getTypename(x)})"; 87 | } 88 | 89 | static dynamic getMetatable(dynamic x) { 90 | if (x is Table && x.metatable != null) { 91 | return x.metatable.map.containsKey("__metatable") ? x.metatable.map["__metatable"] : x.metatable; 92 | } // TODO: strings 93 | return null; 94 | } 95 | 96 | static dynamic getLength(dynamic x) { 97 | if (hasMetamethod(x, "__len")) { 98 | return maybeAt(invokeMetamethod(x, "__len", [x]), 0); 99 | } else if (x is Table) { 100 | return x.length; 101 | } else if (x is String) { 102 | return x.length; 103 | } else { 104 | throw "attempt to get length of a ${getTypename(x)} value"; 105 | } 106 | } 107 | 108 | dynamic tableIndex(dynamic x, dynamic y) { 109 | if (x is Table) { 110 | var o = x.rawget(y); 111 | if (o != null) return o; 112 | if (x.metatable == null) return null; 113 | var ni = x.metatable.map["__index"]; 114 | if (ni == null) return null; 115 | if (ni is Closure) return ni([this, y]); 116 | return tableIndex(ni, y); 117 | } else if (x is String) { 118 | return stringMetatable.rawget(y); 119 | } else { // TODO: strings 120 | throw "attempt to index a ${getTypename(x)} value"; 121 | } 122 | } 123 | 124 | static void tableSet(dynamic x, dynamic k, dynamic v) { 125 | if (x is Table) { 126 | if (x.map.containsKey(k) && x.metatable != null && x.metatable.map.containsKey("__newindex")) { 127 | var ni = x.metatable.map["__newindex"]; 128 | if (ni is Closure) { 129 | ni([x, k, v]); 130 | } else { 131 | tableSet(ni, k, v); 132 | } 133 | } else { 134 | x.rawset(k, v); 135 | } 136 | } else { // TODO: strings 137 | throw "attempt to index a ${getTypename(x)} value"; 138 | } 139 | } 140 | 141 | static String numToString(num x) { 142 | if (x is int) return x.toString(); 143 | if (x == double.infinity) return "inf"; 144 | if (x != x) return "-nan"; 145 | if (x == double.negativeInfinity) return "-inf"; 146 | return x.toString().replaceFirst(new RegExp(r"\.0$"), ""); 147 | } 148 | 149 | static dynamic luaToString(dynamic x) { 150 | if (x == null) { 151 | return "nil"; 152 | } else if (x is num) { 153 | return numToString(x); 154 | } else if (hasMetamethod(x, "__tostring")) { 155 | return maybeAt(invokeMetamethod(x, "__tostring", [x]), 0); 156 | } else if (x is String) { 157 | return x; 158 | } else if (x is bool) { 159 | return x.toString(); 160 | } else { 161 | return "${getTypename(x)}: ${(x.hashCode % 0x100000000).toRadixString(16).padLeft(8, "0")}"; 162 | } 163 | } 164 | 165 | static const keywords = const [ 166 | "and", "break", "do", "else", "elseif", "or", 167 | "end", "false", "for", "function", "goto", 168 | "if", "in", "local", "nil", "not", "while", 169 | "repeat", "return", "then", "true", "until", 170 | ]; 171 | 172 | static String luaSerialize(dynamic x) { 173 | if (x is String) return "\"${luaEscape(x)}\""; 174 | else if (x is Table) { 175 | var o = "{"; 176 | for (var e in x.arr) { 177 | o += luaSerialize(e) + ","; 178 | } 179 | 180 | for (var k in x.map.keys) { 181 | if (k is String && new RegExp("^[a-zA-Z_][a-zA-Z0-9_]*\$").hasMatch(k) && !keywords.contains(k)) { 182 | o += "$k = "; 183 | } else { 184 | o += "[${luaSerialize(k)}] = "; 185 | } 186 | o += "${luaSerialize(x)},"; 187 | } 188 | 189 | if (o.endsWith(",")) o = o.substring(0, o.length - 1); 190 | return "$o}"; 191 | } else return luaToString(x); 192 | } 193 | 194 | static dynamic luaConcat(dynamic x, dynamic y) { 195 | if (hasMetamethod(x, "__concat")) { 196 | return maybeAt(invokeMetamethod(x, "__concat", [x, y]), 0); 197 | } else if (hasMetamethod(y, "__concat")) { 198 | return maybeAt(invokeMetamethod(y, "__concat", [x, y]), 0); 199 | } else if (x is! num && x is! String) { 200 | throw "attempt to concatenate a ${getTypename(x)} value"; 201 | } else if (y is! num && x is! String) { 202 | throw "attempt to concatenate a ${getTypename(y)} value"; 203 | } else { 204 | return luaToString(x) + luaToString(y); 205 | } 206 | } 207 | 208 | static List invokeMetamethod(dynamic x, String name, List params) { 209 | if (x is Table) { 210 | if (x.map[name] is! Closure) throw "attempt to call table value"; 211 | return x.map[name](params); 212 | } else { // TODO: strings 213 | throw "attempt to invoke metamethod on a ${getTypename(x)} value"; 214 | } 215 | } 216 | 217 | // TODO: strings 218 | static bool hasMetamethod(dynamic x, String method) => x is Table && x.metatable != null && x.metatable.map.containsKey(method); 219 | 220 | static dynamic attemptArithmetic(dynamic x, dynamic y, String method, ArithCB op) { 221 | if (hasMetamethod(x, method)) { 222 | return maybeAt(invokeMetamethod(x, method, [x, y]), 0); 223 | } else if (hasMetamethod(y, method)) { 224 | return maybeAt(invokeMetamethod(y, method, [x, y]), 0); 225 | } else if (x is! num) { 226 | throw "attempt to perform arithmetic on a ${getTypename(x)} value"; 227 | } else if (y is! num) { 228 | throw "attempt to perform arithmetic on a ${getTypename(y)} value"; 229 | } else { 230 | return op(x, y); 231 | } 232 | } 233 | 234 | static num attemptUnary(dynamic x, String method, UArithCB op) { 235 | if (hasMetamethod(x, method)) { 236 | return maybeAt(invokeMetamethod(x, method, [x]), 0); 237 | } else if (x is! num) { 238 | throw "attempt to perform arithmetic on a ${getTypename(x)} value"; 239 | } else { 240 | return op(x); 241 | } 242 | } 243 | 244 | static bool checkEQ(dynamic x, dynamic y) { 245 | if ( 246 | hasMetamethod(x, "__eq") && hasMetamethod(y, "__eq") && 247 | (x as Table).map["__eq"] == (y as Table).map["__eq"] 248 | ) { 249 | return truthy(invokeMetamethod(x, "__eq", [x, y])); 250 | } else { 251 | return x == y; 252 | } 253 | } 254 | 255 | static bool checkLT(dynamic x, dynamic y) { 256 | if (hasMetamethod(x, "__lt")) { 257 | return maybeAt(invokeMetamethod(x, "__lt", [x, y]), 0); 258 | } else if (hasMetamethod(y, "__lt")) { 259 | return maybeAt(invokeMetamethod(y, "__lt", [x, y]), 0); 260 | } else if (x is! num) { 261 | throw "attempt to compare ${getTypename(x)} with ${getTypename(y)}"; 262 | } else if (y is! num) { 263 | throw "attempt to compare ${getTypename(y)} with ${getTypename(x)}"; 264 | } else { 265 | return x < y; 266 | } 267 | } 268 | 269 | static bool checkLE(dynamic x, dynamic y) { 270 | if (hasMetamethod(x, "__le")) { 271 | return maybeAt(invokeMetamethod(x, "__le", [x, y]), 0); 272 | } else if (hasMetamethod(y, "__le")) { 273 | return maybeAt(invokeMetamethod(y, "__le", [x, y]), 0); 274 | } else if (x is! num) { 275 | throw "attempt to compare ${getTypename(x)} with ${getTypename(y)}"; 276 | } else if (y is! num) { 277 | throw "attempt to compare ${getTypename(y)} with ${getTypename(x)}"; 278 | } else { 279 | return !checkLT(y, x); 280 | } 281 | } 282 | 283 | static bool truthy(dynamic x) => x != null && x != false; 284 | static num add(num x, num y) => x + y; 285 | static num sub(num x, num y) => x - y; 286 | static num mul(num x, num y) => x * y; 287 | static num div(num x, num y) => x / y; 288 | static num mod(num x, num y) => x % y; 289 | static num unm(num x) => -x; 290 | static bool not(dynamic x) => !truthy(x); 291 | } -------------------------------------------------------------------------------- /lib/src/5_2/lualib/base.dart: -------------------------------------------------------------------------------- 1 | import 'package:lua/src/5_2/table.dart'; 2 | import 'package:lua/src/5_2/context.dart'; 3 | import 'package:lua/src/5_2/state.dart'; 4 | import 'package:lua/src/5_2/vm.dart'; 5 | import 'package:lua/src/util.dart'; 6 | 7 | export 'bit.dart'; 8 | export 'coroutine.dart'; 9 | export 'math.dart'; 10 | export 'string.dart'; 11 | export 'table.dart'; 12 | 13 | loadBase(Context ctx) { 14 | ctx.env["assert"] = (List args) { 15 | if (args.length < 1 || args[0] == null || args[0] == false) { 16 | throw args.length < 2 ? "assertion failed!" : args[1]; 17 | } 18 | 19 | return [args[0]]; 20 | }; 21 | 22 | ctx.env["collectgarbage"] = (List args) { 23 | throw "NYI"; // TODO 24 | }; 25 | 26 | ctx.env["dofile"] = (List args) { 27 | throw "NYI"; // TODO 28 | }; 29 | 30 | ctx.env["error"] = (List args) { 31 | throw (args.length < 1 ? null : args[0]) ?? ""; 32 | }; 33 | 34 | ctx.env["_G"] = ctx.env; 35 | 36 | ctx.env["getmetatable"] = (List args) { 37 | return [Context.getMetatable(args.length < 1 ? null : args[0])]; 38 | }; 39 | 40 | ctx.env["ipairs"] = (List args) { 41 | var t = args[0]; 42 | 43 | if (Context.hasMetamethod(t, "__ipairs")) { 44 | return Context.invokeMetamethod(t, "__ipairs", [t]).take(3).toList(growable: false); 45 | } 46 | 47 | return [ 48 | (List args) { 49 | var i = args[1] + 1; 50 | var v = ctx.tableIndex(args[0], i); 51 | return v != null ? [i, v] : []; 52 | }, t, 0, 53 | ]; 54 | }; 55 | 56 | ctx.env["load"] = (List args) { 57 | var ld = Context.getArg2(args, 0, "load"); 58 | 59 | if (ld is! LuaDartFunc) ld = Context.luaToString(ld); 60 | 61 | var source = maybeAt(args, 1) ?? ld; 62 | var mode = maybeAt(args, 2) ?? "bt"; 63 | var env = maybeAt(args, 3) ?? ctx.env; 64 | 65 | throw "NYI"; // TODO 66 | }; 67 | 68 | ctx.env["loadfile"] = (List args) { 69 | throw "NYI"; // TODO 70 | }; 71 | 72 | ctx.env["next"] = (List args) { 73 | Table table = Context.getArg1(args, 0, "next"); 74 | var k = table.next(maybeAt(args, 1)); 75 | return [k, table.rawget(k)]; 76 | }; 77 | 78 | ctx.env["pairs"] = (List args) { 79 | return [ctx.env["next"], Context.getArg1
(args, 0, "pairs"), null]; 80 | }; 81 | 82 | ctx.env["pcall"] = (Thread thread, List args) { 83 | var f = Context.getArg1(args, 0, "pcall"); 84 | try { 85 | return [true]..addAll(thread.attemptCall(f, args.skip(1).toList(growable: false))); 86 | } on LuaError catch(e) { 87 | if (e.value is String) { 88 | return [false, e.toStringShort()]; 89 | } else { 90 | return [false, e.value]; 91 | } 92 | } catch(e) { 93 | return [ 94 | false, 95 | e, 96 | ]; 97 | } 98 | }; 99 | 100 | ctx.env["print"] = (List args) { 101 | print(args.map((a) => Context.luaToString(a).toString()).join("\t")); 102 | return []; 103 | }; 104 | 105 | ctx.env["rawequal"] = (List args) { 106 | return [maybeAt(args, 0) == maybeAt(args, 1)]; 107 | }; 108 | 109 | ctx.env["rawget"] = (List args) { 110 | Table t = Context.getArg1
(args, 0, "rawget"); 111 | var k = Context.getAny(args, 1, "rawget",); 112 | return [ 113 | t.rawget(k), 114 | ]; 115 | }; 116 | 117 | ctx.env["rawlen"] = (List args) { 118 | Table t = Context.getArg1
(args, 0, "rawlen"); 119 | return [ 120 | t.length, 121 | ]; 122 | }; 123 | 124 | ctx.env["rawset"] = (List args) { 125 | Table t = Context.getArg1
(args, 0, "rawset"); 126 | var k = Context.getAny(args, 1, "rawset"); 127 | var v = Context.getAny(args, 2, "rawset"); 128 | t.rawset(k, v); 129 | return []; 130 | }; 131 | 132 | ctx.env["select"] = (List args) { 133 | var a = Context.getArg2(args, 0, "select"); 134 | 135 | if (a is String) { 136 | if (a == "#") { 137 | return [args.length - 1]; 138 | } else { 139 | throw "bad argument #1 to 'select' (number expected, got string)"; 140 | } 141 | } 142 | 143 | var n = (a as num).floor(); 144 | if (n < 1) throw "bad argument #1 to 'select' (index out of range)"; 145 | return args.skip(n).toList(growable: false); 146 | }; 147 | 148 | ctx.env["setmetatable"] = (List args) { 149 | Table t = Context.getArg1
(args, 0, "setmetatable"); 150 | 151 | if (args.length < 2) throw "bad argument #2 to 'setmetatable' (nil or table expected)"; 152 | 153 | Table v = Context.getArg1
(args, 1, "setmetatable"); 154 | 155 | t.metatable = v; 156 | 157 | return [t]; 158 | }; 159 | 160 | ctx.env["tonumber"] = (List args) { 161 | if (args.length == 0) throw "bad argument #1 to 'tonumber' (value expected)"; 162 | var x = args[0]; 163 | 164 | if (x is num) return [x]; 165 | 166 | if (x is String) return [ 167 | int.parse(x, onError: (_) => null) ?? double.parse(x, (_) => null), 168 | ]; 169 | 170 | return [null]; 171 | }; 172 | 173 | ctx.env["tostring"] = (List args) { 174 | if (args.length == 0) throw "bad argument #1 to 'tostring' (value expected)"; 175 | return [Context.luaToString(args[0])]; 176 | }; 177 | 178 | ctx.env["type"] = (List args) { 179 | if (args.length == 0) throw "bad argument #1 to 'type' (value expected)"; 180 | return [Context.getTypename(args[0])]; 181 | }; 182 | 183 | ctx.env["_VERSION"] = "Lua 5.2"; 184 | } 185 | 186 | -------------------------------------------------------------------------------- /lib/src/5_2/lualib/bit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:lua/src/5_2/table.dart'; 4 | import 'package:lua/src/5_2/context.dart'; 5 | 6 | int _tobit(num x) { 7 | if (x is int) return x % 0x100000000; 8 | return (x + 0.5).floor() % 0x100000000; 9 | } 10 | 11 | loadBit(Context ctx) { 12 | var bit = new Table(); 13 | ctx.env["bit"] = bit; 14 | 15 | bit["tobit"] = (List args) { 16 | num x = Context.getArg1(args, 0, "tobit"); 17 | return [_tobit(x)]; 18 | }; 19 | 20 | bit["tohex"] = (List args) { 21 | num x = Context.getArg1(args, 0, "tohex"); 22 | num n = args.length < 2 ? 8 : Context.getArg1(args, 1, "tohex").abs().floor(); 23 | var o = _tobit(x).toRadixString(16).padLeft(n, "0"); 24 | return [o.substring(o.length - n)]; 25 | }; 26 | 27 | bit["bnot"] = (List args) { 28 | num x = Context.getArg1(args, 0, "bnot"); 29 | return [~_tobit(x)]; 30 | }; 31 | 32 | bit["band"] = (List args) { 33 | var o = 0xFFFFFFFF; 34 | for (int i = 0; i < max(1, args.length); i++) { 35 | o = o & _tobit(Context.getArg1(args, i, "band")); 36 | } 37 | return [o]; 38 | }; 39 | 40 | bit["bor"] = (List args) { 41 | var o = 0; 42 | for (int i = 0; i < max(1, args.length); i++) { 43 | o = o | _tobit(Context.getArg1(args, i, "bor")); 44 | } 45 | return [o]; 46 | }; 47 | 48 | bit["bxor"] = (List args) { 49 | var o = 0; 50 | for (int i = 0; i < max(1, args.length); i++) { 51 | o = o ^ _tobit(Context.getArg1(args, i, "bxor")); 52 | } 53 | return [o]; 54 | }; 55 | 56 | bit["lshift"] = (List args) { 57 | num x = Context.getArg1(args, 0, "lshift"); 58 | num y = Context.getArg1(args, 1, "lshift"); 59 | return [_tobit(x) << _tobit(y)]; 60 | }; 61 | 62 | bit["rshift"] = (List args) { 63 | int x = _tobit(Context.getArg1(args, 0, "rshift")); 64 | int y = _tobit(Context.getArg1(args, 1, "rshift")); 65 | 66 | return [_tobit(x) >> _tobit(y)]; 67 | }; 68 | 69 | bit["arshift"] = (List args) { 70 | num x = Context.getArg1(args, 0, "arshift"); 71 | num y = _tobit(Context.getArg1(args, 1, "arshift")); 72 | var o = _tobit(x); 73 | return [(o >> y) | ((o & 0x80000000 == 0 ? 0 : (0xFFFFFFFF << (32 - y)) & 0xFFFFFFFF))]; 74 | }; 75 | 76 | bit["rol"] = (List args) { 77 | int x = _tobit(Context.getArg1(args, 0, "rol")); 78 | int y = _tobit(Context.getArg1(args, 1, "rol")) % 32; 79 | return [(x << y) | (x >> (32 - y))]; 80 | }; 81 | 82 | bit["ror"] = (List args) { 83 | if (args.length < 1) throw "bad argument #1 to 'ror' (number expected, got no value)"; 84 | if (args[0] is! num) throw "bad argument #1 to 'ror' (number expected, got ${Context.getTypename(args[0])})"; 85 | int x = _tobit(args[0]); 86 | if (args.length < 2) throw "bad argument #2 to 'ror' (number expected, got no value)"; 87 | if (args[0] is! num) throw "bad argument #2 to 'ror' (number expected, got ${Context.getTypename(args[1])})"; 88 | int y = _tobit(args[1]); 89 | 90 | return [_tobit((x >> y) | (x << (32 - y)))]; 91 | }; 92 | 93 | bit["bswap"] = (List args) { 94 | int x = _tobit(Context.getArg1(args, 0, "bswap")); 95 | return [ 96 | ((x & 0xFF) << 24) | 97 | ((x & 0xFF00) << 8) | 98 | ((x & 0xFF0000) >> 8) | 99 | ((x & 0xFF000000) >> 24) 100 | ]; 101 | }; 102 | } -------------------------------------------------------------------------------- /lib/src/5_2/lualib/coroutine.dart: -------------------------------------------------------------------------------- 1 | import 'package:lua/src/5_2/table.dart'; 2 | import 'package:lua/src/5_2/vm.dart'; 3 | import 'package:lua/src/5_2/state.dart'; 4 | import 'package:lua/src/5_2/context.dart'; 5 | import 'package:lua/src/util.dart'; 6 | 7 | loadCoroutine(Context ctx) { 8 | var coroutine = new Table(); 9 | ctx.env["coroutine"] = coroutine; 10 | 11 | coroutine["create"] = (List args) { 12 | Closure x = Context.getArg1(args, 0, "create"); 13 | return [new Thread(closure: x)]; 14 | }; 15 | 16 | coroutine["resume"] = (List args) { 17 | Thread x = Context.getArg1(args, 0, "resume"); 18 | 19 | if (x.status != CoroutineStatus.SUSPENDED) { 20 | return [false, "cannot resume non-suspended coroutine"]; 21 | } 22 | 23 | var res = x.resume(args.skip(1).toList(growable: false)); 24 | 25 | if (!res.success) return [false, maybeAt(res.values, 0)]; 26 | 27 | var o = [true]; 28 | o.addAll(res.values); 29 | }; 30 | 31 | coroutine["yield"] = ctx.yield = (List args) { 32 | throw "attempt to yield across Dart call boundry"; 33 | }; 34 | 35 | coroutine["status"] = (List args) { 36 | Thread x = Context.getArg1(args, 0, "status"); 37 | switch (x.status) { 38 | case CoroutineStatus.SUSPENDED: return ["suspended"]; 39 | case CoroutineStatus.DEAD: return ["dead"]; 40 | case CoroutineStatus.NORMAL: return ["normal"]; 41 | case CoroutineStatus.RUNNING: return ["running"]; 42 | } 43 | }; 44 | 45 | coroutine["running"] = (Thread thread, List args) { 46 | return thread; 47 | }; 48 | } -------------------------------------------------------------------------------- /lib/src/5_2/lualib/math.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:lua/src/5_2/table.dart'; 3 | import 'package:lua/src/5_2/context.dart'; 4 | 5 | const rpd = pi / 180; 6 | 7 | loadMath(Context ctx) { 8 | var math = new Table(); 9 | ctx.env["math"] = math; 10 | 11 | math["abs"] = (List args) { 12 | num x = Context.getArg1(args, 0, "abs"); 13 | return [x.abs()]; 14 | }; 15 | 16 | math["acos"] = (List args) { 17 | num x = Context.getArg1(args, 0, "acos"); 18 | return [acos(x)]; 19 | }; 20 | 21 | math["asin"] = (List args) { 22 | num x = Context.getArg1(args, 0, "asin"); 23 | return [asin(x)]; 24 | }; 25 | 26 | math["atan"] = (List args) { 27 | num x = Context.getArg1(args, 0, "atan"); 28 | return [atan(x)]; 29 | }; 30 | 31 | math["atan2"] = (List args) { 32 | num x = Context.getArg1(args, 0, "atan2"); 33 | num y = Context.getArg1(args, 1, "atan2"); 34 | return [atan2(x, y)]; 35 | }; 36 | 37 | math["ceil"] = (List args) { 38 | num x = Context.getArg1(args, 0, "ceil"); 39 | return [x.ceil()]; 40 | }; 41 | 42 | math["cos"] = (List args) { 43 | num x = Context.getArg1(args, 0, "cos"); 44 | return [cos(x)]; 45 | }; 46 | 47 | math["cosh"] = (List args) { 48 | num x = Context.getArg1(args, 0, "cosh"); 49 | return [ 50 | (pow(e, x) + pow(e, -x)) / 2, 51 | ]; 52 | }; 53 | 54 | math["deg"] = (List args) { 55 | num x = Context.getArg1(args, 0, "deg"); 56 | return [x / rpd]; 57 | }; 58 | 59 | math["exp"] = (List args) { 60 | num x = Context.getArg1(args, 0, "exp"); 61 | return [pow(e, x)]; 62 | }; 63 | 64 | math["floor"] = (List args) { 65 | num x = Context.getArg1(args, 0, "floor"); 66 | return [x.floor()]; 67 | }; 68 | 69 | math["fmod"] = (List args) { 70 | num x = Context.getArg1(args, 0, "fmod"); 71 | num y = Context.getArg1(args, 1, "fmod"); 72 | return [(x % y) * x.sign]; 73 | }; 74 | 75 | math["frexp"] = (List args) { 76 | num x = Context.getArg1(args, 0, "frexp"); 77 | 78 | int e = 0; 79 | 80 | while (x < 0.5) { 81 | x *= 2; 82 | e--; 83 | } 84 | 85 | while (x > 1.0) { 86 | x /= 2; 87 | e++; 88 | } 89 | 90 | return [x, e]; 91 | }; 92 | 93 | math["huge"] = double.infinity; 94 | 95 | math["ldexp"] = (List args) { 96 | num x = Context.getArg1(args, 0, "ldexp"); 97 | num y = Context.getArg1(args, 1, "ldexp"); 98 | return [x * pow(2, y)]; 99 | }; 100 | 101 | math["log"] = (List args) { 102 | num x = Context.getArg1(args, 0, "log"); 103 | if (args.length < 2) return [log(x)]; 104 | num y = Context.getArg1(args, 1, "log"); 105 | return [log(x) / log(y)]; 106 | }; 107 | 108 | math["max"] = (List args) { 109 | num x = Context.getArg1(args, 0, "max"); 110 | for (int i = 1; i < args.length; i++) { 111 | x = max(x, Context.getArg1(args, 0, "max")); 112 | } 113 | return [x]; 114 | }; 115 | 116 | math["min"] = (Listargs) { 117 | num x = Context.getArg1(args, 0, "min"); 118 | for (int i = 1; i < args.length; i++) { 119 | x = min(x, Context.getArg1(args, 0, "min")); 120 | } 121 | return [x]; 122 | }; 123 | 124 | math["modf"] = (List args) { 125 | num x = Context.getArg1(args, 0, "modf"); 126 | var o = x.floor(); 127 | return [o, x - o]; 128 | }; 129 | 130 | math["pi"] = pi; 131 | 132 | math["pow"] = (List args) { 133 | num x = Context.getArg1(args, 0, "pow"); 134 | num y = Context.getArg1(args, 1, "pow"); 135 | return [pow(x, y)]; 136 | }; 137 | 138 | math["rad"] = (List args) { 139 | num x = Context.getArg1(args, 0, "rad"); 140 | return [x * rpd]; 141 | }; 142 | 143 | var rng = new Random(); 144 | 145 | math["random"] = (List args) { 146 | if (args.length == 0) return [rng.nextDouble()]; 147 | num mn = Context.getArg1(args, 0, "random"); 148 | num mx; 149 | 150 | if (args.length < 2) { 151 | mx = mn; 152 | mn = 1; 153 | } else { 154 | mx = Context.getArg1(args, 1, "random"); 155 | if (mx < mn) throw "bad argument #2 to 'random' (interval is empty)"; 156 | } 157 | 158 | return [rng.nextInt(mx.floor() - mn.floor()) + mn.floor()]; 159 | }; 160 | 161 | math["randomseed"] = (List args) { 162 | num x = Context.getArg1(args, 0, "randomseed"); 163 | rng = new Random(x.floor()); 164 | return []; 165 | }; 166 | 167 | math["sin"] = (List args) { 168 | num x = Context.getArg1(args, 0, "sin"); 169 | return [sin(x)]; 170 | }; 171 | 172 | math["sinh"] = (List args) { 173 | num x = Context.getArg1(args, 0, "sinh"); 174 | return [(pow(e, x) - pow(e, -x)) / 2]; 175 | }; 176 | 177 | math["sqrt"] = (List args) { 178 | num x = Context.getArg1(args, 0, "sqrt"); 179 | return [sqrt(x)]; 180 | }; 181 | 182 | math["tan"] = (List args) { 183 | num x = Context.getArg1(args, 0, "tan"); 184 | return [tan(x)]; 185 | }; 186 | 187 | math["tanh"] = (List args) { 188 | num x = Context.getArg1(args, 0, "tanh"); 189 | return [(pow(e, x) - pow(e, -x)) / (pow(e, x) + pow(e, -x))]; 190 | }; 191 | } -------------------------------------------------------------------------------- /lib/src/5_2/lualib/string.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:lua/src/5_2/table.dart'; 4 | import 'package:lua/src/5_2/context.dart'; 5 | import 'package:lua/src/util.dart'; 6 | 7 | loadString(Context ctx) { 8 | var string = new Table(); 9 | 10 | ctx.env["string"] = string; 11 | ctx.stringMetatable = string; 12 | 13 | string["rep"] = (List args) { 14 | String str = Context.luaToString(Context.getArg1(args, 0, "rep")); 15 | num amount = Context.getArg1(args, 1, "rep"); 16 | return [str * amount.floor()]; 17 | }; 18 | 19 | string["char"] = (List args) { 20 | var o = new StringBuffer(); 21 | 22 | for (int i = 0; i < args.length; i++) { 23 | int n = Context.getArg1(args, i, "char").floor(); 24 | if (n < 0 || n > 255) throw "bad argument #${i + 1} to 'char' (value out of range)"; 25 | o.writeCharCode(n); 26 | } 27 | 28 | return [o.toString()]; 29 | }; 30 | 31 | string["sub"] = (List args) { 32 | if (args.length < 1) throw "bad argument #1 to 'sub' (string expected, got no value)"; 33 | var i = args[0]; 34 | String str; 35 | if (i is String) { 36 | str = i; 37 | } else if (i is num) { 38 | str = Context.numToString(i); 39 | } else { 40 | throw "bad argument #1 to 'sub' (string expected, got ${Context.getTypename(i)})"; 41 | } 42 | 43 | int start = Context.getArg1(args, 1, "sub").floor(); 44 | int end = maybeAt(args, 2) == null ? str.length : Context.getArg1(args, 2, "sub").floor(); 45 | 46 | if (start == 0) start = 1; 47 | if (start < 0) start = str.length + start + 1; 48 | end = min(end, str.length); 49 | if (end < 0) end = str.length + end + 1; 50 | 51 | return start > end ? [""] : [str.substring(start - 1, end)]; 52 | }; 53 | 54 | string["byte"] = (List args) { 55 | if (args.length < 1) throw "bad argument #1 to 'byte' (string expected, got no value)"; 56 | var i = args[0]; 57 | String str; 58 | if (i is String) { 59 | str = i; 60 | } else if (i is num) { 61 | str = Context.numToString(i); 62 | } else { 63 | throw "bad argument #1 to 'byte' (string expected, got ${Context.getTypename(i)})"; 64 | } 65 | 66 | int start = Context.getArg1(args, 1, "byte").floor(); 67 | int end = maybeAt(args, 2) == null ? start : Context.getArg1(args, 2, "byte"); 68 | 69 | if (start == 0) start = 1; 70 | if (start < 0) start = str.length + start + 1; 71 | end = min(end, str.length); 72 | if (end < 0) end = str.length + end + 1; 73 | 74 | return start > end ? [] : str.substring(start- 1, end).codeUnits; 75 | }; 76 | } -------------------------------------------------------------------------------- /lib/src/5_2/lualib/table.dart: -------------------------------------------------------------------------------- 1 | import 'package:lua/src/5_2/table.dart'; 2 | import 'package:lua/src/5_2/context.dart'; 3 | import 'package:lua/src/util.dart'; 4 | 5 | loadTable(Context ctx) { 6 | var table = new Table(); 7 | 8 | ctx.env["table"] = table; 9 | 10 | table["concat"] = (List args) { 11 | Table t = Context.getArg1
(args, 0, "concat"); 12 | var delim = Context.luaToString(maybeAt(args, 1) ?? ""); 13 | num s = maybeAt(args, 2) == null ? 1 : Context.getArg1(args, 2, "concat"); 14 | num e = maybeAt(args, 3) == null ? t.length : Context.getArg1(args, 3, "concat"); 15 | 16 | var o = new StringBuffer(); 17 | for (int i = s.floor(); i <= e.floor(); i++) { 18 | if (i != s.floor()) o.write(delim); 19 | var e = t.rawget(i); 20 | if (e == null) throw "invalid value (nil) at index $i in table for 'concat'"; 21 | o.write(Context.luaToString(e)); 22 | } 23 | 24 | return [o]; 25 | }; 26 | 27 | table["insert"] = (List args) { 28 | if (args.length < 2 || args.length > 3) throw "wrong number of arguments to 'insert"; 29 | 30 | Table t = Context.getArg1
(args, 0, "insert"); 31 | var len = t.length; 32 | 33 | var v = args[args.length < 3 ? 1 : 2]; 34 | int pos = args.length < 3 ? len + 1 : Context.getArg1(args, 1, "insert").floor(); 35 | if (pos > len || pos < 0) throw "bad argument #2 to 'insert' (position out of bounds)"; 36 | 37 | for (int i = len + 1; i > pos; i--) t.rawset(i, t.rawget(i - 1)); 38 | t.rawset(pos, v); 39 | 40 | return []; 41 | }; 42 | 43 | table["maxn"] = (List args) { 44 | Table t = Context.getArg1
(args, 0, "maxn"); 45 | return [t.map.keys.fold(t.length, (s, e) => e is num && e > s ? e : s)]; 46 | }; 47 | 48 | table["remove"] = (List args) { 49 | Table t = Context.getArg1
(args, 0, "remove"); 50 | int pos = maybeAt(args, 1) == null ? t.length : Context.getArg1(args, 1, "remove").floor(); 51 | 52 | var len = t.length; 53 | 54 | if (pos > len || pos < 0) throw "bad argument #2 to 'remove' (position out of bounds)"; 55 | 56 | for (int i = pos; i <= len; i++) { 57 | t.rawset(i, t.rawget(i + 1)); 58 | } 59 | 60 | t.rawset(len, null); 61 | 62 | return []; 63 | }; 64 | 65 | table["sort"] = (List args) { 66 | Table t = Context.getArg1
(args, 0, "maxn"); 67 | LuaDartFunc f = maybeAt(args, 1) == null ? null : Context.getArg1(args, 1, "sort"); 68 | 69 | t.arr.sort((a, b) { 70 | if (f != null) { 71 | var lt = Context.truthy(maybeAt(f([a, b]), 0)); 72 | var gt = Context.truthy(maybeAt(f([b, a]), 0)); 73 | return lt ? -1 : gt ? 1 : 0; 74 | } else if (a is num && b is num) { 75 | return a.compareTo(b); 76 | } else if ((a is Table && Context.hasMetamethod(a, "__le")) || (b is Table && Context.hasMetamethod(b, "__le"))) { 77 | var lt = Context.checkLT(a, b); 78 | var gt = Context.checkLT(b, a); 79 | return lt ? -1 : gt ? 1 : 0; 80 | } else { 81 | var at = Context.getTypename(a); 82 | var bt = Context.getTypename(b); 83 | 84 | if (at == bt) throw "attempt to compare two $at value"; 85 | throw "attempt to compare $at with $bt"; 86 | } 87 | }); 88 | 89 | return []; 90 | }; 91 | } -------------------------------------------------------------------------------- /lib/src/5_2/parser.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/dartlua/12090c0cf6ad953ffac175bde37a02c9f2edfbf0/lib/src/5_2/parser.dart -------------------------------------------------------------------------------- /lib/src/5_2/state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'dart:typed_data'; 5 | import 'package:lua/src/5_2/table.dart'; 6 | import 'package:lua/src/5_2/vm.dart'; 7 | import 'package:lua/src/5_2/context.dart'; 8 | import 'package:lua/src/5_2/lualib/base.dart' as lualib; 9 | import 'package:lua/src/decode.dart'; 10 | 11 | class CoroutineResult { 12 | CoroutineResult(this.success, this.values); 13 | final bool success; 14 | final List values; 15 | 16 | String toString() { 17 | return success ? values.map(Context.luaToString).join(", ") : values[0]; 18 | } 19 | } 20 | 21 | abstract class LuaFunction { 22 | List call(List args); 23 | CoroutineResult pcall(List args); 24 | LuaState get state; 25 | } 26 | 27 | abstract class LuaError { 28 | dynamic get value; 29 | StackTrace get dartStackTrace; 30 | String get source; 31 | String toStringShort(); 32 | String toString(); 33 | } 34 | 35 | enum CoroutineStatus { 36 | RUNNING, 37 | SUSPENDED, 38 | NORMAL, 39 | DEAD, 40 | } 41 | 42 | class Coroutine { 43 | Coroutine(LuaFunction f) {} 44 | 45 | CoroutineStatus _status; 46 | CoroutineStatus get status => _status; 47 | CoroutineResult resume([List args = const []]) { 48 | return new CoroutineResult(true, []); 49 | } 50 | } 51 | 52 | class _LuaFunctionImpl extends LuaFunction { 53 | _LuaFunctionImpl(this.closure); 54 | Closure closure; 55 | LuaState get state => closure.context.userdata as LuaState; 56 | List call(List args) => closure(args); 57 | CoroutineResult pcall(List args) { 58 | try { 59 | return new CoroutineResult(true, closure(args)); 60 | } on LuaError catch(e) { 61 | return new CoroutineResult(false, [e.toString()]); 62 | } 63 | } 64 | 65 | bool operator==(dynamic other) => other is _LuaFunctionImpl && other.closure == closure; 66 | int get hashCode => closure.hashCode; 67 | } 68 | 69 | class LuaState { 70 | Table get _G => _context.env; 71 | final Context _context; 72 | 73 | LuaState({bool loadLibs = true}) : _context = new Context(env: new Table()) { 74 | _context.userdata = this; 75 | 76 | if (loadLibs) { 77 | loadBase(); 78 | loadMath(); 79 | loadString(); 80 | loadBit(); 81 | loadTable(); 82 | } 83 | } 84 | 85 | void loadBase() => lualib.loadBase(_context); 86 | void loadMath() => lualib.loadMath(_context); 87 | void loadString() => lualib.loadString(_context); 88 | void loadBit() => lualib.loadBit(_context); 89 | void loadTable() => lualib.loadTable(_context); 90 | 91 | Future loadFile(String path) async { 92 | var res = await Process.run("luadist/bin/luac5.2", [path]); 93 | 94 | if (res.stderr != "") throw res.stderr; 95 | 96 | var f = new File("luac.out"); 97 | 98 | if (!await f.exists()) throw "luac.out not found"; 99 | var fh = await f.open(mode: FileMode.READ); 100 | var buffer = new Uint8List(await f.length()); 101 | await fh.readInto(buffer); 102 | 103 | await f.delete(); 104 | 105 | var decoder = new Decoder(buffer.buffer); 106 | var dump = decoder.readCodeDump(path); 107 | 108 | return new _LuaFunctionImpl(new Closure( 109 | dump.main, 110 | context: _context, 111 | upvalues: [new Upval.store(_context.env)], 112 | )); 113 | } 114 | 115 | Future doFile(String path, {List args = const []}) async => (await loadFile(path)).pcall(args); 116 | 117 | static dynamic _sanitize(dynamic x) { 118 | if (x is! LuaDartFunc && x is Function) { 119 | throw "Function does not match LuaDartFunc or LuaDebugFunc"; 120 | } else return x; 121 | } 122 | 123 | static dynamic _convert(dynamic x) { 124 | if (x is Closure) { 125 | return new _LuaFunctionImpl(x); 126 | } else return x; 127 | } 128 | 129 | dynamic getGlobal(dynamic k) => _convert(_G.rawget(_sanitize(k))); 130 | void setGlobal(dynamic k, dynamic v) => _G.rawset(_sanitize(k), _sanitize(v)); 131 | } -------------------------------------------------------------------------------- /lib/src/5_2/table.dart: -------------------------------------------------------------------------------- 1 | class Table { 2 | final List arr = []; 3 | final Map map = {}; 4 | 5 | void rawset(dynamic k, dynamic v) { 6 | if (k == null) { 7 | throw "table index is nil"; 8 | } if (k is num && k == arr.length && v == null) { 9 | arr.removeLast(); 10 | } if (k is num && k == arr.length + 1) { 11 | map.remove(k); 12 | arr.add(v); 13 | } else if (k is num && k.floor() == k && k > 0 && k <= arr.length) 14 | arr[k.toInt() - 1] = v; 15 | else 16 | map[k] = v; 17 | } 18 | 19 | dynamic rawget(dynamic k) { 20 | return (k is num && k == k.floor() && k <= arr.length && k > 0 ? arr[k.toInt() - 1] : null) ?? (map.containsKey(k) ? map[k] : null); 21 | } 22 | 23 | dynamic operator[](dynamic k) => rawget(k); 24 | void operator[]=(dynamic k, dynamic v) => rawset(k, v); 25 | 26 | Iterator _nextIter; 27 | 28 | dynamic next(dynamic k) { 29 | if (k == null) { 30 | if (arr.isNotEmpty) { 31 | return 1; 32 | } else if (map.isNotEmpty) { 33 | _nextIter = map.keys.iterator; 34 | return _nextIter.current; 35 | } else return null; 36 | } else if (_nextIter != null && k == _nextIter.current) { 37 | if (!_nextIter.moveNext()) return null; 38 | return _nextIter.current; 39 | } else if (k is num && k.floor() == k && k > 0 && k <= arr.length) { 40 | if (k == arr.length) { 41 | _nextIter = map.keys.iterator; 42 | return _nextIter.current; 43 | } 44 | return k + 1; 45 | } else { 46 | _nextIter = map.keys.iterator; 47 | while (_nextIter.current != k) if (!_nextIter.moveNext()) return null; 48 | if (!_nextIter.moveNext()) return null; 49 | return _nextIter.current; 50 | } 51 | } 52 | 53 | int get length { 54 | if (map.isEmpty) return arr.length; 55 | 56 | var j = arr.length; 57 | var i = j++; 58 | 59 | while (map.containsKey(j.toDouble())) { 60 | i = j; 61 | j *= 2; 62 | } 63 | 64 | while (j - i > 1) { 65 | var m = (i + j) ~/ 2; 66 | if (map.containsKey(m.toDouble())) { 67 | i = m; 68 | } else { 69 | j = m; 70 | } 71 | } 72 | 73 | return i; 74 | } 75 | 76 | Table metatable; 77 | } -------------------------------------------------------------------------------- /lib/src/5_2/vm.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:math'; 3 | 4 | import 'dart:typed_data'; 5 | import 'package:lua/src/5_2/context.dart'; 6 | import 'package:lua/src/5_2/table.dart'; 7 | import 'package:lua/src/func.dart'; 8 | import 'package:lua/src/inst.dart'; 9 | import 'package:lua/src/util.dart'; 10 | import 'package:meta/meta.dart'; 11 | import 'package:lua/src/5_2/state.dart'; 12 | 13 | class ThreadResult { 14 | ThreadResult(this.success, this.values, {this.resumeTo}); 15 | final bool success; 16 | final List values; 17 | final Frame resumeTo; 18 | 19 | String toString() { 20 | return success ? values.map(Context.luaToString).join(", ") : values[0]; 21 | } 22 | } 23 | 24 | class Upval extends LinkedListEntry { 25 | Upval(this.reg, this.registers) : open = true; 26 | Upval.store(this.storage) : open = false; 27 | 28 | bool open; 29 | int reg; 30 | List registers; 31 | dynamic storage; 32 | 33 | void close() { 34 | open = false; 35 | storage = registers[reg]; 36 | registers = null; 37 | unlink(); 38 | } 39 | 40 | void set(dynamic v) { 41 | if (open) { 42 | registers[reg] = v; 43 | } else { 44 | storage = v; 45 | } 46 | } 47 | 48 | dynamic get() => open ? registers[reg] : storage; 49 | } 50 | 51 | class Closure { 52 | Closure(this.proto, { 53 | this.parent, 54 | this.context, 55 | this.upvalues, 56 | }); 57 | 58 | final Prototype proto; 59 | final Frame parent; 60 | final Context context; 61 | final List upvalues; 62 | 63 | List call(List args) { 64 | var f = new Thread(closure: this).frame; 65 | f.loadArgs(args); 66 | var x = f.cont(); 67 | if (x.resumeTo != null) throw "cannot yield across dart call boundary"; 68 | if (!x.success) { 69 | var v = maybeAt(x.values, 0); 70 | if (v is LuaErrorImpl) throw v; 71 | throw new LuaErrorImpl(maybeAt(x.values, 0), proto, f._pc); 72 | } 73 | return x.values; 74 | } 75 | } 76 | 77 | class Frame { 78 | Frame(this._proto, { 79 | this.parent, 80 | Context context, 81 | List upvalues, 82 | @required Thread thread, 83 | }) : 84 | context = context ?? parent.context, 85 | _upvalues = upvalues ?? new List.filled(_proto.upvals.length, null), 86 | _K = _proto.constants, 87 | _R = new List.filled(_proto.registers, null, growable: true), 88 | _code = _proto.rawCode, 89 | _thread = thread {} 90 | 91 | final Thread _thread; 92 | final Frame parent; 93 | final Context context; 94 | final Prototype _proto; 95 | final List _upvalues; 96 | final List _K; 97 | final List _R; 98 | List _varargs; 99 | final Int32List _code; 100 | final _openUVs = new LinkedList(); 101 | 102 | int _pc = 0; 103 | int _top = 0; 104 | 105 | int _getExtraArg() => _code[_pc++ * 4 + 1]; 106 | int _getNextJump() => _code[_pc * 4 + 2]; 107 | 108 | dynamic _RK(int x) => x >= 256 ? _K[x - 256].value : _GR(x); 109 | // for debugging: 110 | dynamic _GR(int x) => _R[x]; 111 | dynamic _SR(int x, dynamic y) => _R[x] = y; 112 | 113 | void _loadReturns(List ret) { 114 | var pc = _pc - 1; 115 | var A = _code[pc * 4 + 1]; 116 | var B = _code[pc * 4 + 2]; 117 | var C = _code[pc * 4 + 3]; 118 | 119 | if (C == 2) { 120 | _SR(A, maybeAt(ret, 0)); 121 | } else if (C != 1) { 122 | if (C == 0) _setTop(A + ret.length - 1); 123 | if (B == 1) { 124 | if (C == 0) { 125 | for (int i = A; i < A + ret.length; i++) _SR(i, maybeAt(ret, i - A)); 126 | } else { 127 | int g = 0; 128 | for (int i = A; i < A + C; i++) _SR(i, maybeAt(ret, g++)); 129 | } 130 | } else { 131 | if (C == 0) { 132 | for (var i = 0; i < ret.length; i++) _SR(A + i, maybeAt(ret, i)); 133 | } else { 134 | var g = 0; 135 | for (int i = A; i < A + C - 1; i++) _SR(i, maybeAt(ret, g++)); 136 | } 137 | } 138 | } 139 | } 140 | 141 | bool convertToString(int reg) { 142 | var x = _GR(reg); 143 | if (x is num) _SR(reg, Context.numToString(x)); else if (x is! String) return false; 144 | return true; 145 | } 146 | 147 | List _callTM() { 148 | 149 | } 150 | 151 | List _concat(int total) { 152 | do { 153 | var top = _top; 154 | var n = 2; 155 | 156 | var a = _GR(top - 2); 157 | if ((a is! String && a is! num) || !convertToString(top - 1)) { 158 | var b = _GR(top - 1); 159 | 160 | } 161 | } while (total > 1); 162 | } 163 | 164 | dynamic _getUpval(int idx) => _upvalues[idx].get(); 165 | dynamic _setUpval(int idx, dynamic value) => _upvalues[idx].set(value); 166 | 167 | void _closeUpvals(int from) { 168 | if (_openUVs.isEmpty) return; 169 | 170 | var e = _openUVs.first; 171 | while (e != null && e.reg >= from) { 172 | var next = e.next; 173 | e.close(); 174 | e = next; 175 | } 176 | } 177 | 178 | Upval _openUpval(int reg) { 179 | if (_openUVs.isEmpty) { 180 | var uv = new Upval(reg, _R); 181 | _openUVs.addFirst(uv); 182 | return uv; 183 | } 184 | 185 | var e = _openUVs.first; 186 | while (e.reg >= reg) { 187 | if (e.reg == reg) return e; 188 | if (e.next == null) { 189 | var uv = new Upval(reg, _R); 190 | e.insertAfter(uv); 191 | return uv; 192 | } 193 | e = e.next; 194 | } 195 | 196 | var uv = new Upval(reg, _R); 197 | e.insertBefore(uv); 198 | return uv; 199 | } 200 | 201 | void _setTop(int x) { 202 | if (x >= _R.length) { // expand registers when 1full 203 | _R.length = x + 1; 204 | } 205 | _top = x; 206 | } 207 | 208 | void loadArgs(List args) { 209 | for (int i = 0; i < min(args.length, _proto.params); i++) { 210 | _SR(i, maybeAt(args, i)); 211 | } 212 | 213 | if (_proto.varag > 0) _varargs = args; 214 | } 215 | 216 | bool get finished => _pc >= _proto.code.length; 217 | 218 | ThreadResult cont() { 219 | try { 220 | while (true) { 221 | var pc = _pc++; 222 | var OP = _code[pc * 4]; 223 | var A = _code[pc * 4 + 1]; 224 | var B = _code[pc * 4 + 2]; 225 | var C = _code[pc * 4 + 3]; 226 | 227 | if (OP == 0) { // MOVE(AB) 228 | _SR(A, _GR(B)); 229 | } else if (OP == 1) { // LOADK(ABx) 230 | _SR(A, _K[B].value); 231 | } else if (OP == 2) { // LOADKX(A) 232 | _SR(A, _K[_getExtraArg()].value); 233 | } else if (OP == 3) { // LOADBOOL(ABC) 234 | _SR(A, B != 0); 235 | if (C != 0) _pc++; 236 | } else if (OP == 4) { // LOADNIL(AB) 237 | var a = A; 238 | _R.fillRange(a, a + B + 1); 239 | } else if (OP == 5) { // GETUPVAL(AB) 240 | _SR(A, _getUpval(B)); 241 | } else if (OP == 6) { // GETTABUP(ABC) 242 | var v = context.tableIndex(_getUpval(B), _RK(C)); 243 | _SR(A, v); 244 | } else if (OP == 7) { // GETTABLE(ABC) 245 | _SR(A, context.tableIndex(_RK(B), _RK(C))); 246 | } else if (OP == 8) { // SETTABUP(ABC) 247 | Context.tableSet(_getUpval(A), _RK(B), _RK(C)); 248 | } else if (OP == 9) { // SETUPVAL(A) 249 | _setUpval(B, _GR(A)); 250 | } else if (OP == 10) { // SETTABLE(ABC) 251 | Context.tableSet(_GR(A), _RK(B), _RK(C)); 252 | } else if (OP == 11) { // NEWTABLE(ABC) 253 | _SR(A, new Table()); 254 | } else if (OP == 12) { // SELF(ABC) 255 | _SR(A + 1, _GR(B)); 256 | _SR(A, context.tableIndex(_GR(B), _RK(C))); 257 | } else if (OP == 13) { // ADD(ABC) 258 | _SR(A, Context.attemptArithmetic(_RK(B), _RK(C), "__add", Context.add)); 259 | } else if (OP == 14) { // SUB(ABC) 260 | _SR(A, Context.attemptArithmetic(_RK(B), _RK(C), "__sub", Context.sub)); 261 | } else if (OP == 15) { // MUL(ABC) 262 | _SR(A, Context.attemptArithmetic(_RK(B), _RK(C), "__mul", Context.mul)); 263 | } else if (OP == 16) { // DIV(ABC) 264 | _SR(A, Context.attemptArithmetic(_RK(B), _RK(C), "__div", Context.div)); 265 | } else if (OP == 17) { // MOD(ABC) 266 | _SR(A, Context.attemptArithmetic(_RK(B), _RK(C), "__mod", Context.mod)); 267 | } else if (OP == 18) { // POW(ABC) 268 | _SR(A, Context.attemptArithmetic(_RK(B), _RK(C), "__pow", pow)); 269 | } else if (OP == 19) { // UNM(AB) 270 | _SR(A, Context.attemptUnary(_GR(A), "__unm", Context.unm)); 271 | } else if (OP == 20) { // NOT(AB) 272 | _SR(A, !Context.truthy(_GR(B))); 273 | } else if (OP == 21) { // LEN(AB) 274 | _SR(A, Context.getLength(_GR(B))); 275 | } else if (OP == 22) { // CONCAT 276 | var o = _GR(B); 277 | for (int i = B + 1; i <= C; i++) { 278 | o = Context.luaConcat(o, _GR(i)); 279 | } 280 | _SR(A, o); 281 | } else if (OP == 23) { // JMP(AsBx) 282 | _pc += B; 283 | if (A > 0) _closeUpvals(A - 1); 284 | } else if (OP == 24) { // EQ 285 | if (Context.checkEQ(_RK(B), _RK(C)) == (A != 0)) { 286 | _pc += _getNextJump() + 1; 287 | } else { 288 | _pc++; 289 | } 290 | } else if (OP == 25) { // LT 291 | if (Context.checkLT(_RK(B), _RK(C)) == (A != 0)) { 292 | _pc += _getNextJump() + 1; 293 | } else { 294 | _pc++; 295 | } 296 | } else if (OP == 26) { // LE 297 | if (Context.checkLE(_RK(B), _RK(C)) == (A != 0)) { 298 | _pc += _getNextJump() + 1; 299 | } else { 300 | _pc++; 301 | } 302 | } else if (OP == 27) { // TEST 303 | if (!Context.truthy(_GR(A)) == (C != 0)) { 304 | _pc++; 305 | } else { 306 | _pc += _getNextJump() + 1; 307 | } 308 | } else if (OP == 28) { // TESTSET 309 | if (!Context.truthy(_GR(B)) == (C != 0)) { 310 | _pc++; 311 | } else { 312 | _SR(A, _GR(B)); 313 | _pc += _getNextJump() + 1; 314 | } 315 | } else if (OP == 29) { // CALL 316 | if (B != 0) _setTop(A + B); 317 | var x = _GR(A); 318 | var args = new List(B == 0 ? _top - A : B - 1); 319 | if (B != 1) for (int i = 0; i < args.length; i++) args[i] = _GR(i + A + 1); 320 | 321 | if (x is Closure) { 322 | var f = _thread.newFrame(x); 323 | f.loadArgs(args); 324 | var res = f.cont(); 325 | if (res.resumeTo != null) return res; 326 | _loadReturns(res.values); 327 | } else { 328 | var ret = _thread.attemptCall(x, args); 329 | _loadReturns(ret); 330 | } 331 | } else if (OP == 30) { // TAILCALL(ABC) 332 | var args = new List(B == 0 ? _top - A : B - 1); 333 | if (B != 1) for (int i = 0; i < args.length; i++) args[i] = _GR(i + A + 1); 334 | var x = _GR(A); 335 | _closeUpvals(0); 336 | 337 | if (x is Closure) { 338 | var f = _thread.newFrame(x); 339 | f.loadArgs(args); 340 | return f.cont(); 341 | } else { 342 | var ret = _thread.attemptCall(_GR(A), args); 343 | return new ThreadResult(true, ret); 344 | } 345 | } else if (OP == 31) { // RETURN(ABC) 346 | _closeUpvals(0); 347 | var ret = new List(B == 0 ? 1 + _top - A : B - 1); 348 | for (int i = A; i < (B == 0 ? _top : A + B - 1); i++) ret[i - A] = _GR(i); 349 | return new ThreadResult(true, ret); 350 | } else if (OP == 32) { // FORLOOP(AsBx) 351 | var step = _GR(A + 2); 352 | var idx = _SR(A, _GR(A) + step); 353 | var limit = _GR(A + 1); 354 | 355 | if ((step > 0 && idx <= limit) || (step < 0 && limit <= idx)) { 356 | _pc += B; 357 | _SR(A + 3, _GR(A)); 358 | } 359 | } else if (OP == 33) { // FORPREP(AsBx) 360 | var init = _GR(A); 361 | var limit = _GR(A + 1); 362 | var step = _GR(A + 2); 363 | 364 | if (init is! num) throw "'for' initial value must be a number"; 365 | if (limit is! num) throw "'for' limit value must be a number"; 366 | if (step is! num) throw "'for' step value must be a number"; 367 | 368 | _SR(A, _GR(A) - step); 369 | _pc += B; 370 | } else if (OP == 34) { // TFORCALL(ABC) 371 | var ret = _thread.attemptCall(_GR(A), [_GR(A + 1), _GR(A + 2)]); 372 | var i = 0; 373 | for (int n = A + 3; n < A + C + 3; n++) _SR(n, maybeAt(ret, i++)); 374 | 375 | var b = _code[_pc * 4 + 2]; 376 | var a = _getExtraArg(); 377 | 378 | if (_GR(a + 1) != null) { 379 | _SR(a, _GR(a + 1)); 380 | _pc += b; 381 | } 382 | } else if (OP == 35) { // TFORLOOP(AsBx) 383 | if (_GR(A + 1) != null) { 384 | _SR(A, _GR(A + 1)); 385 | _pc += B; 386 | } 387 | } else if (OP == 36) { // SETLIST(ABC) 388 | if (B > 0) { 389 | for (int i = 1; i <= B; i++) Context.tableSet(_GR(A), ((C - 1) * 50) + i, _GR(A + i)); 390 | } else { 391 | for (int i = 1; i <= _top - A; i++) Context.tableSet(_GR(A), ((C - 1) * 50) + i, _GR(A + i)); 392 | } 393 | } else if (OP == 37) { // CLOSURE(ABC) 394 | var proto = _proto.prototypes[B]; 395 | 396 | _SR(A, new Closure( 397 | proto, 398 | context: context, 399 | parent: this, 400 | upvalues: new List.generate(proto.upvals.length, (i) { 401 | var def = proto.upvals[i]; 402 | return def.stack ? _openUpval(def.reg) : _upvalues[def.reg]; 403 | }), 404 | )); 405 | } else if (OP == 38) { // VARARG 406 | if (B > 0) { 407 | var i = 0; 408 | for (int n = A; n <= A + B - 2; n++) _SR(n, _varargs[i++]); 409 | } else { 410 | _setTop(A + _varargs.length - (_proto.params + 1)); 411 | var i = A; 412 | for (int n = _proto.params; n < _varargs.length; n++) _SR(i++, _varargs[n]); 413 | } 414 | } else { 415 | throw "invalid instruction"; 416 | } 417 | } 418 | } catch(e, bt) { 419 | if (e is LuaError) rethrow; 420 | throw new LuaErrorImpl(e, _proto, _pc - 1, dartStackTrace: bt); 421 | } 422 | } 423 | } 424 | 425 | class Thread { 426 | Thread({@required Closure closure}) { 427 | frame = newFrame(closure); 428 | } 429 | 430 | Frame newFrame(Closure closure) => new Frame(closure.proto, context: closure.context, upvalues: closure.upvalues, thread: this); 431 | 432 | Frame frame; 433 | Frame _resumeTo; 434 | 435 | CoroutineStatus status = CoroutineStatus.SUSPENDED; 436 | bool started = false; 437 | 438 | List attemptCall(dynamic x, [List args = const []]) { 439 | if (x is Table) { 440 | return Context.invokeMetamethod(x, "__call", args); 441 | } else if (x is LuaDartFunc) { 442 | return x(args); 443 | } else if (x is LuaDartDebugFunc) { 444 | return x(this, args); 445 | } else if (x is Closure) { 446 | return x(args); 447 | } else { 448 | throw "attempt to call a ${Context.getTypename(x)} value"; 449 | } 450 | } 451 | 452 | CoroutineResult resume([List params = const []]) { 453 | assert(status != CoroutineStatus.DEAD, "Coroutine dead, check status before calling resume"); 454 | 455 | if (!started) { 456 | started = true; 457 | _resumeTo = frame; 458 | } 459 | 460 | status = CoroutineStatus.RUNNING; 461 | 462 | try { 463 | _resumeTo.loadArgs(params); 464 | var res = _resumeTo.cont(); 465 | _resumeTo = res.resumeTo; 466 | status = _resumeTo != null ? CoroutineStatus.DEAD : CoroutineStatus.SUSPENDED; 467 | return new CoroutineResult(true, res.values); 468 | } catch(e) { 469 | status = CoroutineStatus.DEAD; 470 | return new CoroutineResult(false, [e]); 471 | } 472 | 473 | /*if (!started) { 474 | _updateFrame(); 475 | // load main body arguments 476 | 477 | started = true; 478 | } else if (status == CoroutineStatus.SUSPENDED) { // resume from yield 479 | status = CoroutineStatus.RUNNING; 480 | var pc = _pc - 1; 481 | var OP = _code[pc * 4]; 482 | var A = _code[pc * 4 + 1]; 483 | var B = _code[pc * 4 + 2]; 484 | var C = _code[pc * 4 + 3]; 485 | 486 | // ADD SUB MUL DIV MOD POW UNM LEN GETTABUP GETTABLE SELF 487 | if ((OP >= 13 && OP <= 19) || OP == 21 || OP == 6 || OP == 7 || OP == 12) { 488 | _SR(A, params[0]); 489 | } else if (OP == 22) { // CONCAT 490 | 491 | } 492 | }*/ 493 | 494 | 495 | } 496 | 497 | toString() => "thread: 0x${(hashCode % 0x100000000).toRadixString(16).padLeft(8, "0")}"; 498 | } -------------------------------------------------------------------------------- /lib/src/decode.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'func.dart'; 4 | import 'flavor.dart'; 5 | import 'package:lua/src/util.dart'; 6 | import 'inst.dart'; 7 | 8 | class DecoderException { 9 | DecoderException(this.error, this.doing, this.byteOffset); 10 | String error; 11 | String doing; 12 | int byteOffset; 13 | toString() => "$error while $doing (offset $byteOffset)"; 14 | } 15 | 16 | class Decoder { 17 | Decoder(this.buffer); 18 | ByteBuffer buffer; 19 | Flavor flavor; 20 | String doing; 21 | int index = 0; 22 | 23 | Uint8List read(int len) { 24 | if (index + len >= buffer.lengthInBytes + 1) throw "Unexpected EOF"; 25 | Uint8List out = new Uint8List.view(buffer, index, len); 26 | index += len; 27 | return out; 28 | } 29 | 30 | double readDouble() { 31 | return (new Uint8List(8)..setRange(0, 8, read(8))).buffer.asFloat64List()[0]; 32 | } 33 | 34 | int readInt(int sz, [bool bendian = false, bool sgn = false]) { 35 | int o = 0; 36 | if (bendian) { 37 | read(sz).forEach((n) { 38 | o = (o << 8) + n; 39 | }); 40 | } else { 41 | int i = 0; 42 | read(sz).forEach((n) { 43 | o += n << (8 * i); 44 | i++; 45 | }); 46 | } 47 | return sgn ? sign(o, sz * 8) : o; 48 | } 49 | 50 | String readString() { 51 | String o = new String.fromCharCodes(read(readInt(code.ptrSize, code.bigEndian) - 1)); 52 | if (read(1)[0] != 0) throw "Corrupt string"; 53 | return o; 54 | } 55 | 56 | Inst readInst() { 57 | var raw = readInt(code.instSize); 58 | return flavor.decode(raw); 59 | } 60 | 61 | Prototype readFunc(Prototype parent, CodeDump root) { 62 | doing = "reading primitive"; 63 | var prim = new Prototype(root); 64 | prim.parent = parent; 65 | prim.lineStart = readInt(code.intSize, code.bigEndian); 66 | prim.lineEnd = readInt(code.intSize, code.bigEndian); 67 | prim.params = read(1)[0]; 68 | prim.varag = read(1)[0]; 69 | prim.registers = read(1)[0]; 70 | doing = "reading instructions"; 71 | int instCount = readInt(code.intSize, code.bigEndian); 72 | prim.code = new InstBlock(new List.generate(instCount, (i) => readInst())); 73 | prim.rawCode = new Int32List.fromList(prim.code.expand((e) => [e.OP, e.A, e.B, e.C]).toList()); 74 | doing = "reading constants"; 75 | prim.constants = new List.generate(readInt(code.intSize, code.bigEndian), (i) { 76 | switch (read(1)[0]) { 77 | case 0: return new Const(); 78 | case 1: return new BoolConst(read(1)[0] > 0); 79 | case 3: 80 | num x = code.useInt ? readInt(code.numSize, code.bigEndian) : readDouble(); 81 | if (x == x.floor()) x = x.floor(); // Convert to int when possible 82 | return new NumberConst(x); 83 | case 4: return new StringConst(readString()); 84 | default: throw "Unknown constant id"; 85 | } 86 | }); 87 | 88 | prim.constantScope = parent == null ? prim.constants : [parent.constantScope, prim.constants].expand((f) => f); 89 | prim.prototypes = new List.generate(readInt(code.intSize, code.bigEndian), (i) => readFunc(prim, root)); 90 | doing = "reading upvals"; 91 | prim.upvals = new List.generate(readInt(code.intSize, code.bigEndian), (i) { 92 | return new UpvalDef(read(1)[0] == 1, read(1)[0]); 93 | }); 94 | doing = "reading source code"; 95 | prim.source = readString(); 96 | doing = "reading debug lines"; 97 | prim.lines = new List.generate(readInt(code.intSize, code.bigEndian), (i) => readInt(code.intSize, code.bigEndian)); 98 | doing = "reading locals"; 99 | prim.locals = new List.generate(readInt(code.intSize, code.bigEndian), (i) { 100 | return new Local(readString(), readInt(code.intSize, code.bigEndian), readInt(code.intSize, code.bigEndian)); 101 | }); 102 | doing = "reading debug upvals"; 103 | int len = readInt(code.intSize, code.bigEndian); 104 | for (int i = 0; i < len; i++) prim.upvals[i].name = readString(); 105 | return prim; 106 | } 107 | 108 | CodeDump code; 109 | CodeDump readCodeDump([String name = "stdin"]) { 110 | try { 111 | code = new CodeDump(); 112 | 113 | code.name = name; 114 | 115 | doing = "reading header"; 116 | if ( 117 | read(1)[0] != 0x1B || 118 | read(1)[0] != 0x4C || 119 | read(1)[0] != 0x75 || 120 | read(1)[0] != 0x61 121 | ) throw "Invalid signature"; 122 | var version = read(1)[0]; 123 | code.versionMajor = version >> 4; 124 | code.versionMinor = version & 15; 125 | code.implementation = read(1)[0]; 126 | 127 | var qualFlavors = flavors.where((f) => f.version_major == code.versionMajor && f.version_minor == code.versionMinor); 128 | 129 | if (qualFlavors.isEmpty) { 130 | throw "No flavors for version ${code.versionMinor}.${code.versionMinor}"; 131 | } 132 | 133 | for (int i = 0; i < qualFlavors.length; i++) { 134 | int sindex = index; 135 | flavor = qualFlavors.elementAt(i); 136 | try { 137 | code.littleEndian = read(1)[0] > 0; 138 | code.intSize = read(1)[0]; 139 | code.ptrSize = read(1)[0]; 140 | code.instSize = read(1)[0]; 141 | code.numSize = read(1)[0]; 142 | code.useInt = read(1)[0] > 0; 143 | doing = "reading magic"; 144 | 145 | if ( 146 | read(1)[0] != 0x19 || 147 | read(1)[0] != 0x93 || 148 | read(1)[0] != 0x0d || 149 | read(1)[0] != 0x0a || 150 | read(1)[0] != 0x1a || 151 | read(1)[0] != 0x0a 152 | ) throw "Invalid magic"; 153 | 154 | code.main = readFunc(null, code); 155 | code.flavor = flavor; 156 | 157 | return code; 158 | } on String { 159 | if (i == qualFlavors.length - 1) { 160 | rethrow; 161 | } else { 162 | index = sindex; // retry with next flavor 163 | } 164 | } 165 | } 166 | } on String catch(e) { 167 | throw new DecoderException(e, doing, index); 168 | } 169 | 170 | return null; // not possible 171 | } 172 | } -------------------------------------------------------------------------------- /lib/src/disassemble.dart: -------------------------------------------------------------------------------- 1 | import 'func.dart'; 2 | import 'package:lua/src/util.dart'; 3 | 4 | String disassemble(CodeDump code) { 5 | String o = ""; 6 | int indent = 0; 7 | void writeLine(String txt) { 8 | o += " " * indent + txt + "\n"; 9 | } 10 | writeLine(".version 0x${(code.versionMajor * 16 + code.versionMinor).toRadixString(16)}"); 11 | writeLine(".implementation ${code.implementation}"); 12 | writeLine(".endian ${code.littleEndian ? 1 : 0}"); 13 | writeLine(".int_size ${code.intSize}"); 14 | writeLine(".size_t ${code.ptrSize}"); 15 | writeLine(".inst_size ${code.instSize}"); 16 | writeLine(".number_size ${code.instSize}"); 17 | writeLine(".use_int ${code.useInt ? 1 : 0}"); 18 | 19 | void writeFunc(Prototype func, String name) { 20 | if (name != "main") { 21 | writeLine(".func " + name); 22 | indent++; 23 | } 24 | if (func.lineStart != 0 || func.lineEnd != 0) writeLine(".lines " + func.lineStart.toString() + " " + func.lineEnd.toString()); 25 | if (func.params != 0) writeLine(".params " + func.params.toString()); 26 | if (func.varag != 0) writeLine(".vararg " + func.varag.toString()); 27 | void writeConst(int i, [String comment]) { 28 | writeLine(".const " + i.toString() + " " + func.constantScope.elementAt(i - 1).toString() + (comment == null ? "" : " ; $comment")); 29 | } 30 | for (int i = 0; i < func.constants.length; i++) { 31 | writeConst(func.constantScope.toList().indexOf(func.constants[i]) + 1); 32 | } 33 | for (int i = 0; i < func.prototypes.length; i++) { 34 | writeFunc(func.prototypes[i], i.toString()); 35 | } 36 | for (int i = 0; i < func.upvals.length; i++) { 37 | writeLine(".upval " + (func.upvals[i].stack ? "0" : "1") + " " + func.upvals[i].reg.toString() + (func.upvals[i].name == null ? "" : " \"" + luaEscape(func.upvals[i].name) + "\"")); 38 | } 39 | writeLine(".registers " + func.registers.toString()); 40 | Map> local_start = {}; 41 | Map> local_end = {}; 42 | for (int i = 0; i < func.locals.length; i++) { 43 | var c = func.locals[i]; 44 | local_start[c.from] = local_start[c.from] ?? []; 45 | local_start[c.from].add(c); 46 | local_end[c.to] = local_end[c.to] ?? []; 47 | local_end[c.to].add(c); 48 | } 49 | 50 | int clocal = 0; 51 | 52 | if (local_start.containsKey(0)) { 53 | local_start[0].forEach((e) => writeLine(".local " + (clocal++).toString() + " \"" + luaEscape(e.name) + "\"")); 54 | } 55 | 56 | for (int i = 0; i < func.code.length; i++) { 57 | var c = func.code[i]; 58 | if (local_end.containsKey(i + 1)) { 59 | local_end[i + 1].forEach((e) => writeLine(".endlocal " + (--clocal).toString())); 60 | } 61 | 62 | if (local_start.containsKey(i + 1)) { 63 | local_start[i + 1].forEach((e) => writeLine(".local " + (clocal++).toString() + " \"" + luaEscape(e.name) + "\"")); 64 | } 65 | 66 | var info = code.flavor.instructions[c.OP]; 67 | writeLine(info.name + " " + info.getParams(c).item1.join(" ")); 68 | } 69 | 70 | if (name != "main") { 71 | indent--; 72 | writeLine(".end"); 73 | } 74 | } 75 | 76 | writeLine(""); 77 | writeFunc(code.main, "main"); 78 | 79 | return o; 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/flavor.dart: -------------------------------------------------------------------------------- 1 | import 'package:lua/src/inst.dart'; 2 | 3 | class Flavor { 4 | Flavor({this.name, this.version_major, this.version_minor, this.instructions}) { 5 | int i = 0; 6 | instructions.forEach((e) => e.opcode = i++); 7 | } 8 | final String name; 9 | final int version_major; 10 | final int version_minor; 11 | final List instructions; 12 | 13 | InstInfo getInfo(int raw) => instructions[raw & 63]; 14 | Inst decode(int raw) => getInfo(raw).decode(raw); 15 | } 16 | 17 | List flavors = [ 18 | new Flavor( 19 | name: "Vanilla 5.2", 20 | version_major: 5, version_minor: 2, 21 | instructions: [ 22 | /* 0*/ new InstInfo.parse("MOVE", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 23 | /* 1*/ new InstInfo.parse("LOADK", "ABx", "RC ", iBasicInst(write: [0], read: [1])), 24 | /* 2*/ new InstInfo.parse("LOADKX", "ABx", "R ", iBasicInst(write: [0])), 25 | /* 3*/ new InstInfo.parse("LOADBOOL", "ABC", "RLL", iBasicInst(write: [0])), 26 | /* 4*/ new InstInfo.parse("LOADNIL", "ABC", "RL ", iLoadNil), 27 | /* 5*/ new InstInfo.parse("GETUPVAL", "ABC", "RU ", iBasicInst(write: [0], read: [1])), 28 | /* 6*/ new InstInfo.parse("GETTABUP", "ABC", "RUT", iBasicInst(write: [0], read: [1, 2])), 29 | /* 7*/ new InstInfo.parse("GETTABLE", "ABC", "RRT", iBasicInst(write: [0], read: [1, 2])), 30 | /* 8*/ new InstInfo.parse("SETTABUP", "ABC", "UTT", iBasicInst(read: [0, 1, 2])), 31 | /* 9*/ new InstInfo.parse("SETUPVAL", "ABC", "RU ", iBasicInst(write: [0], read: [1])), 32 | /*10*/ new InstInfo.parse("SETTABLE", "ABC", "RTT", iBasicInst(read: [0, 1, 2])), 33 | /*11*/ new InstInfo.parse("NEWTABLE", "ABC", "RLL", iBasicInst(write: [0])), 34 | /*12*/ new InstInfo.parse("SELF", "ABC", "RTT", iSelf), 35 | /*13*/ new InstInfo.parse("ADD", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 36 | /*14*/ new InstInfo.parse("SUB", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 37 | /*15*/ new InstInfo.parse("MUL", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 38 | /*16*/ new InstInfo.parse("DIV", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 39 | /*17*/ new InstInfo.parse("MOD", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 40 | /*18*/ new InstInfo.parse("POW", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 41 | /*19*/ new InstInfo.parse("UNM", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 42 | /*20*/ new InstInfo.parse("NOT", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 43 | /*21*/ new InstInfo.parse("LEN", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 44 | /*22*/ new InstInfo.parse("CONCAT", "ABC", "RRR", iConcat), 45 | /*23*/ new InstInfo.parse("JMP", "AsBx", "RJ ", iBasicInst(reljmp: [1])), 46 | /*24*/ new InstInfo.parse("EQ", "ABC", "LTT", iBasicInst()), 47 | /*25*/ new InstInfo.parse("LT", "ABC", "LTT", iBasicInst()), 48 | /*26*/ new InstInfo.parse("LE", "ABC", "LTT", iBasicInst()), 49 | /*27*/ new InstInfo.parse("TEST", "ABC", "R L", iBasicInst()), 50 | /*28*/ new InstInfo.parse("TESTSET", "ABC", "RRL", iBasicInst()), 51 | /*29*/ new InstInfo.parse("CALL", "ABC", "RRR", iBasicInst()), 52 | /*30*/ new InstInfo.parse("TAILCALL", "ABC", "RR ", iBasicInst()), 53 | /*31*/ new InstInfo.parse("RETURN", "ABC", "RR ", iBasicInst()), 54 | /*32*/ new InstInfo.parse("FORLOOP", "AsBx", "RJ ", iBasicInst()), 55 | /*33*/ new InstInfo.parse("FORPREP", "AsBx", "RJ ", iBasicInst()), 56 | /*34*/ new InstInfo.parse("TFORCALL", "ABC", "R R", iBasicInst()), 57 | /*35*/ new InstInfo.parse("TFORLOOP", "AsBx", "RJ ", iBasicInst()), 58 | /*36*/ new InstInfo.parse("SETLIST", "ABC", "RLL", iBasicInst()), 59 | /*37*/ new InstInfo.parse("CLOSURE", "ABx", "RF ", iBasicInst()), 60 | /*38*/ new InstInfo.parse("VARARG", "ABC", "RR ", iBasicInst()), 61 | /*39*/ new InstInfo.parse("EXTRAARG", "Ax", "L ", iBasicInst()), 62 | ], 63 | ), 64 | 65 | new Flavor( 66 | name: "Vanilla 5.3", 67 | version_major: 5, version_minor: 3, 68 | instructions: [ 69 | /* 0*/ new InstInfo.parse("MOVE", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 70 | /* 1*/ new InstInfo.parse("LOADK", "ABx", "RC ", iBasicInst(write: [0], read: [1])), 71 | /* 2*/ new InstInfo.parse("LOADKX", "ABx", "R ", iBasicInst(write: [0])), 72 | /* 3*/ new InstInfo.parse("LOADBOOL", "ABC", "RLL", iBasicInst(write: [0])), 73 | /* 4*/ new InstInfo.parse("LOADNIL", "ABC", "RL ", iLoadNil), 74 | /* 5*/ new InstInfo.parse("GETUPVAL", "ABC", "RU ", iBasicInst(write: [0], read: [1])), 75 | /* 6*/ new InstInfo.parse("GETTABUP", "ABC", "RUT", iBasicInst(write: [0], read: [1, 2])), 76 | /* 7*/ new InstInfo.parse("GETTABLE", "ABC", "RRT", iBasicInst(write: [0], read: [1, 2])), 77 | /* 8*/ new InstInfo.parse("SETTABUP", "ABC", "UTT", iBasicInst(read: [0, 1, 2])), 78 | /* 9*/ new InstInfo.parse("SETUPVAL", "ABC", "RU ", iBasicInst(write: [0], read: [1])), 79 | /*10*/ new InstInfo.parse("SETTABLE", "ABC", "RTT", iBasicInst(read: [0, 1, 2])), 80 | /*11*/ new InstInfo.parse("NEWTABLE", "ABC", "RLL", iBasicInst(write: [0])), 81 | /*12*/ new InstInfo.parse("SELF", "ABC", "RTT", iSelf), 82 | /*13*/ new InstInfo.parse("ADD", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 83 | /*14*/ new InstInfo.parse("SUB", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 84 | /*15*/ new InstInfo.parse("MUL", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 85 | /*16*/ new InstInfo.parse("DIV", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 86 | /*17*/ new InstInfo.parse("MOD", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 87 | /*18*/ new InstInfo.parse("POW", "ABC", "RTT", iBasicInst(write: [0], read: [1, 2])), 88 | /*19*/ new InstInfo.parse("IDIV", "ABC", "RTT", iBasicInst()), 89 | /*20*/ new InstInfo.parse("BAND", "ABC", "RTT", iBasicInst()), 90 | /*21*/ new InstInfo.parse("BOR", "ABC", "RTT", iBasicInst()), 91 | /*22*/ new InstInfo.parse("BXOR", "ABC", "RTT", iBasicInst()), 92 | /*23*/ new InstInfo.parse("SHL", "ABC", "RTT", iBasicInst()), 93 | /*24*/ new InstInfo.parse("SHR", "ABC", "RTT", iBasicInst()), 94 | /*19*/ new InstInfo.parse("UNM", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 95 | /*26*/ new InstInfo.parse("BNOT", "ABC", "RR ", iBasicInst()), 96 | /*27*/ new InstInfo.parse("NOT", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 97 | /*28*/ new InstInfo.parse("LEN", "ABC", "RR ", iBasicInst(write: [0], read: [1])), 98 | /*29*/ new InstInfo.parse("CONCAT", "ABC", "RRR", iConcat), 99 | /*30*/ new InstInfo.parse("JMP", "AsBx", "RJ ", iBasicInst(reljmp: [1])), 100 | /*31*/ new InstInfo.parse("EQ", "ABC", "LTT", iBasicInst()), 101 | /*32*/ new InstInfo.parse("LT", "ABC", "LTT", iBasicInst()), 102 | /*33*/ new InstInfo.parse("LE", "ABC", "LTT", iBasicInst()), 103 | /*34*/ new InstInfo.parse("TEST", "ABC", "R L", iBasicInst()), 104 | /*35*/ new InstInfo.parse("TESTSET", "ABC", "RRL", iBasicInst()), 105 | /*36*/ new InstInfo.parse("CALL", "ABC", "RRR", iBasicInst()), 106 | /*37*/ new InstInfo.parse("TAILCALL", "ABC", "RR ", iBasicInst()), 107 | /*38*/ new InstInfo.parse("RETURN", "ABC", "RR ", iBasicInst()), 108 | /*39*/ new InstInfo.parse("FORLOOP", "AsBx", "RJ ", iBasicInst()), 109 | /*40*/ new InstInfo.parse("FORPREP", "AsBx", "RJ ", iBasicInst()), 110 | /*41*/ new InstInfo.parse("TFORCALL", "ABC", "R R", iBasicInst()), 111 | /*42*/ new InstInfo.parse("TFORLOOP", "AsBx", "RJ ", iBasicInst()), 112 | /*43*/ new InstInfo.parse("SETLIST", "ABC", "RLL", iBasicInst()), 113 | /*44*/ new InstInfo.parse("CLOSURE", "ABx", "RF ", iBasicInst()), 114 | /*45*/ new InstInfo.parse("VARARG", "ABC", "RR ", iBasicInst()), 115 | /*46*/ new InstInfo.parse("EXTRAARG", "Ax", "L ", iBasicInst()), 116 | ], 117 | ), 118 | ]; 119 | -------------------------------------------------------------------------------- /lib/src/func.dart: -------------------------------------------------------------------------------- 1 | import 'package:lua/src/flavor.dart'; 2 | import 'package:lua/src/inst.dart'; 3 | import 'package:lua/src/util.dart'; 4 | 5 | import 'dart:typed_data'; 6 | 7 | class CodeDump { 8 | String name; 9 | int versionMajor; 10 | int versionMinor; 11 | int implementation; 12 | bool littleEndian; 13 | bool get bigEndian => !littleEndian; 14 | int intSize; 15 | int ptrSize; 16 | int instSize; 17 | int numSize; 18 | bool useInt; 19 | Prototype main; 20 | Flavor flavor; 21 | } 22 | 23 | enum ConstType { 24 | CONST_NIL, 25 | CONST_BOOL, 26 | CONST_UNKNOWN, 27 | CONST_NUMBER, 28 | CONST_STRING, 29 | } 30 | 31 | class Const { 32 | const Const(); 33 | final type = ConstType.CONST_NIL; 34 | dynamic get value => null; 35 | toString() => "nil"; 36 | } 37 | 38 | class BoolConst extends Const { 39 | const BoolConst(this.value); 40 | final type = ConstType.CONST_BOOL; 41 | final bool value; 42 | toString() => value.toString(); 43 | } 44 | 45 | class NumberConst extends Const { 46 | const NumberConst(this.value); 47 | final type = ConstType.CONST_NUMBER; 48 | final num value; 49 | toString() => value.toString(); 50 | } 51 | 52 | class StringConst extends Const { 53 | const StringConst(this.value); 54 | final type = ConstType.CONST_STRING; 55 | final String value; 56 | toString() => "\"${luaEscape(value)}\""; 57 | } 58 | 59 | class UpvalDef { 60 | UpvalDef(this.stack, this.reg); 61 | String name; 62 | bool stack; 63 | int reg; 64 | } 65 | 66 | class Local { 67 | Local(this.name, this.from, this.to); 68 | String name; 69 | int from; 70 | int to; 71 | } 72 | 73 | class Prototype { 74 | Prototype(this.root); 75 | final CodeDump root; 76 | Prototype parent; 77 | String name; 78 | int lineStart; 79 | int lineEnd; 80 | int params; 81 | int varag; 82 | int registers; 83 | InstBlock code; 84 | Int32List rawCode; 85 | List constants; 86 | Iterable constantScope; 87 | List prototypes; 88 | List upvals; 89 | String source; 90 | List lines; 91 | List locals; 92 | } -------------------------------------------------------------------------------- /lib/src/inst.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'package:lua/src/util.dart'; 3 | import 'package:tuple/tuple.dart'; 4 | 5 | enum InstLayout { 6 | LAYOUT_ABC, 7 | LAYOUT_ABx, 8 | LAYOUT_AsBx, 9 | LAYOUT_Ax, 10 | } 11 | const instLayoutStr = const [ 12 | "ABC", 13 | "ABx", 14 | "AsBx", 15 | "Ax", 16 | ]; 17 | 18 | enum InstParamType { 19 | PARAM_NONE, 20 | PARAM_REGISTER, 21 | PARAM_CONST, 22 | PARAM_RK, 23 | PARAM_UPVALUE, 24 | PARAM_CLOSURE, 25 | PARAM_LITERAL, 26 | PARAM_JUMP, 27 | } 28 | const instParamTypeStr = const [ 29 | " ", 30 | "R", 31 | "C", 32 | "T", 33 | "U", 34 | "F", 35 | "L", 36 | "J", 37 | ]; 38 | 39 | abstract class IO {} 40 | 41 | class ReadRegister extends IO { 42 | ReadRegister(this.reg); 43 | int reg; 44 | } 45 | 46 | class WriteRegister extends IO { 47 | WriteRegister(this.reg); 48 | int reg; 49 | } 50 | 51 | class JumpTo extends IO { 52 | JumpTo(this.val); 53 | int val; 54 | } 55 | 56 | class RelJumpTo extends IO { 57 | RelJumpTo(this.val); 58 | int val; 59 | } 60 | 61 | _InstInfoIO iBasicInst({List read, List write, List jmp, List reljmp}) { 62 | return (Inst i, InstInfo info) { 63 | var params = info.getParams(i).item1; 64 | return [ 65 | (read ?? []).map((p) => new ReadRegister(params[p])), 66 | (write ?? []).map((p) => new WriteRegister(params[p])) 67 | ].expand((e) => e).toList(); 68 | }; 69 | } 70 | 71 | List iLoadNil(Inst i, InstInfo info) { 72 | var param = info.getParams(i).item1; 73 | return new Iterable.generate(param[1], (a) => new WriteRegister(param[0] + a + 1)).toList(); 74 | } 75 | 76 | List iSelf(Inst i, InstInfo info) { 77 | var param = info.getParams(i).item1; 78 | return [ 79 | new WriteRegister(param[0] + 1), 80 | new ReadRegister(param[1]), 81 | new WriteRegister(param[0]), 82 | ]; 83 | } 84 | 85 | List iConcat(Inst i, InstInfo info) { 86 | var param = info.getParams(i).item1; 87 | return >[ 88 | [new WriteRegister(param[0])], 89 | new Iterable.generate(param[2] - param[1] + 1, (a) => new ReadRegister(a)), 90 | ].expand((e) => e).toList(); 91 | } 92 | 93 | final int specialBool = 1; 94 | final int specialMaybeSkip = 2; 95 | final int specialSelf = 4; 96 | 97 | typedef List _InstInfoIO(Inst i, InstInfo info); 98 | 99 | class InstInfo { 100 | InstInfo.parse(this.name, String sLayout, String sParams, this.io) { 101 | layout = InstLayout.values[instLayoutStr.indexOf(sLayout)]; 102 | assert (sParams.length == 3); 103 | params = []; 104 | for (int i = 0; i < 3; i++) { 105 | params.add(InstParamType.values[instParamTypeStr.indexOf(sParams[i])]); 106 | } 107 | } 108 | 109 | _InstInfoIO io; 110 | InstInfo(this.name, this.layout, this.params); 111 | String name; 112 | InstLayout layout; 113 | List params; 114 | int opcode; 115 | 116 | Inst withParams(List iparams) { 117 | int A = 0; 118 | int B = 0; 119 | int C = 0; 120 | 121 | List out = [0, 0, 0]; 122 | int j = 0; 123 | for (int i = 0; i < 3; i++) { 124 | if (params[i] == InstParamType.PARAM_NONE) continue; 125 | out[i] = iparams[j++]; 126 | } 127 | if (layout == InstLayout.LAYOUT_ABC) { 128 | A = out[0]; 129 | B = out[1]; 130 | C = out[2]; 131 | } else if (layout == InstLayout.LAYOUT_ABx) { 132 | A = out[0]; 133 | B = out[1]; 134 | } else if (layout == InstLayout.LAYOUT_AsBx) { 135 | A = out[0]; 136 | B = out[1]; 137 | } else if (layout == InstLayout.LAYOUT_Ax) { 138 | A = out[0]; 139 | } 140 | 141 | return new Inst(opcode, A, B, C); 142 | } 143 | 144 | Inst decode(int raw) { 145 | int OP = raw & 63; 146 | int A = 0; 147 | int B = 0; 148 | int C = 0; 149 | 150 | if (layout == InstLayout.LAYOUT_ABC || layout == InstLayout.LAYOUT_ABx || layout == InstLayout.LAYOUT_AsBx) 151 | A = (raw >> 6) & 255; 152 | 153 | if (layout == InstLayout.LAYOUT_ABC) { 154 | B = (raw >> 23) & 511; 155 | C = (raw >> 14) & 511; 156 | } 157 | 158 | if (layout == InstLayout.LAYOUT_Ax) A = (raw >> 6) & 67108863; 159 | if (layout == InstLayout.LAYOUT_ABx) B = (raw >> 14) & 262143; 160 | if (layout == InstLayout.LAYOUT_AsBx) B = ((raw >> 14) & 262143) - 131071; 161 | 162 | return new Inst(OP, A, B, C); 163 | } 164 | 165 | int encode(Inst inst) { 166 | int raw = inst.OP & 63; 167 | 168 | if (layout == InstLayout.LAYOUT_ABC || layout == InstLayout.LAYOUT_ABx || layout == InstLayout.LAYOUT_AsBx) 169 | raw |= (inst.A & 255) << 6; 170 | 171 | if (layout == InstLayout.LAYOUT_ABC) { 172 | raw |= (inst.B & 511) << 23; 173 | raw |= (inst.C & 511) << 14; 174 | } 175 | 176 | if (layout == InstLayout.LAYOUT_Ax) raw |= (inst.A & 67108863) << 6; 177 | if (layout == InstLayout.LAYOUT_ABx) raw |= (inst.B & 262143) << 14; 178 | if (layout == InstLayout.LAYOUT_AsBx) raw |= (inst.B & 262143) + 131071; 179 | 180 | return raw; 181 | } 182 | 183 | Tuple2, List> getParams(Inst inst) { 184 | List data = []; 185 | List types = []; 186 | 187 | List rparams; 188 | if (layout == InstLayout.LAYOUT_ABC) { 189 | rparams = [inst.A, inst.B, inst.C]; 190 | } else if (layout == InstLayout.LAYOUT_ABx) { 191 | rparams = [inst.A, inst.B]; 192 | } else if (layout == InstLayout.LAYOUT_AsBx) { 193 | rparams = [inst.A, inst.B]; 194 | } else if (layout == InstLayout.LAYOUT_Ax) { 195 | rparams = [inst.A]; 196 | } 197 | 198 | for (int i = 0; i < rparams.length; i++) { 199 | if (params[i] == InstParamType.PARAM_NONE) continue; 200 | if (params[i] == InstParamType.PARAM_RK) { 201 | data.add(i == 0 ? lua_sign(rparams[i], 8) : rparams[i] >> 8 == 0 ? rparams[i] & 255 : -(rparams[i] & 255)); // assumes only A B or C 202 | } else { 203 | data.add(rparams[i]); 204 | } 205 | types.add(params[i]); 206 | } 207 | 208 | return new Tuple2(data, types); 209 | } 210 | } 211 | 212 | class Inst { 213 | Inst(this.OP, this.A, this.B, this.C); 214 | final int OP; 215 | final int A; 216 | final int B; 217 | final int C; 218 | } 219 | 220 | class InstBlock extends ListBase { 221 | InstBlock(this._list); 222 | 223 | after(Inst x) => new InstBlock(skipWhile((e) => e != x).toList()); 224 | before(Inst x) => new InstBlock(takeWhile((e) => e != x).toList()); 225 | range(Inst x, Inst y) => new InstBlock(skip(indexOf(x)).take(indexOf(y) - indexOf(x)).toList()); 226 | 227 | InstBlock get copy => new InstBlock(this.toList()); 228 | List _list; 229 | 230 | int get length => _list.length; 231 | set length(int x) => _list.length = x; 232 | operator [](int i) => _list[i]; 233 | operator []=(int i, Inst v) { 234 | _list[i] = v; 235 | } 236 | } -------------------------------------------------------------------------------- /lib/src/util.dart: -------------------------------------------------------------------------------- 1 | int sign(int n, int bits) { 2 | int mask = 1 << (bits - 1); 3 | return -(n & mask) + (n & ~mask); 4 | } 5 | 6 | int lua_sign(int n, int bits) { 7 | return n - (1 << (bits - 1)); 8 | } 9 | 10 | int unsign(n, bits) => n < 0 ? (~(n + 1)) & (1 << (bits - 1)) : n; 11 | 12 | int lua_unsign(int n, int bits) { 13 | return n + (1 << (bits - 1)); 14 | } 15 | 16 | Map _luaEscapeChars = { 17 | "\x07": "a", 18 | "\b": "b", 19 | "\f": "f", 20 | "\n": "n", 21 | "\r": "r", 22 | "\t": "t", 23 | "\v": "v", 24 | "\\": "\\", 25 | "\"": "\"", 26 | "\'": "\'", 27 | }; 28 | 29 | String luaEscape(String x) { 30 | String o = ""; 31 | for (int i = 0; i < x.length; i++) { 32 | var c = x[i]; 33 | if (_luaEscapeChars.containsKey(c)) { 34 | o += "\\${_luaEscapeChars[c]}"; 35 | } else if (c.codeUnitAt(0) <= 0x1F || c.codeUnitAt(0) >= 0x7F) { 36 | if (i < x.length - 1 && x[i + 1].codeUnitAt(0) >= 0x30 && x[i + 1].codeUnitAt(0) <= 0x39) { 37 | o += "\\${c.codeUnitAt(0).toString().padLeft(3, "0")}"; 38 | } else { 39 | o += "\\${c.codeUnitAt(0)}"; 40 | } 41 | } else { 42 | o += c; 43 | } 44 | } 45 | return o; 46 | } 47 | 48 | T maybeAt(List l, int idx) => idx < 0 || idx >= l.length ? null : l[idx]; 49 | -------------------------------------------------------------------------------- /luac.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/dartlua/12090c0cf6ad953ffac175bde37a02c9f2edfbf0/luac.out -------------------------------------------------------------------------------- /luadart.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /luadist/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir build 4 | cd build 5 | 6 | RED='\033[1;92m\033[1m' 7 | NC='\033[0m' 8 | 9 | echo -e "${RED}Downloading lua...${NC}" 10 | 11 | sudo apt-get install libreadline-dev libncurses-dev 12 | 13 | wget -N http://www.lua.org/ftp/lua-5.2.4.tar.gz & 14 | wget -N http://www.lua.org/ftp/lua-5.1.5.tar.gz & 15 | wget -N http://luajit.org/download/LuaJIT-2.1.0-beta3.tar.gz & 16 | wait 17 | 18 | echo -e "${RED}Unpacking lua...${NC}" 19 | 20 | tar zxf lua-5.2.4.tar.gz > /dev/null & 21 | tar zxf lua-5.1.5.tar.gz > /dev/null & 22 | tar zxf LuaJIT-2.1.0-beta3.tar.gz > /dev/null & 23 | wait 24 | 25 | echo -e "${RED}Building Lua 5.2...${NC}" 26 | 27 | cd lua-5.2.4 28 | make clean 29 | make linux -j`grep -c ^processor /proc/cpuinfo` > /dev/null 30 | 31 | echo -e "${RED}Building Lua 5.1...${NC}" 32 | 33 | cd ../lua-5.1.5 34 | make clean 35 | make linux -j`grep -c ^processor /proc/cpuinfo` > /dev/null 36 | 37 | echo -e "${RED}Building LuaJIT 2.1...${NC}" 38 | 39 | cd ../LuaJIT-2.1.0-beta3 40 | make clean 41 | make -j`grep -c ^processor /proc/cpuinfo` > /dev/null 42 | 43 | echo -e "${RED}Copying to bin...${NC}" 44 | 45 | cd ../.. 46 | mkdir bin 47 | cp -f build/lua-5.2.4/src/lua bin/lua5.2 48 | cp -f build/lua-5.2.4/src/luac bin/luac5.2 49 | cp -f build/lua-5.1.5/src/lua bin/lua5.1 50 | cp -f build/lua-5.1.5/src/luac bin/luac5.1 51 | cp -f build/LuaJIT-2.1.0-beta3/src/luajit bin/luajit 52 | 53 | echo -e "${RED}Done!${NC}" -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: lua 2 | description: A library and cli for running, disassembling, and debugging Lua programs. 3 | version: 0.0.1 4 | homepage: https://github.com/P-T-/luadart 5 | author: Andre Lipke 6 | 7 | environment: 8 | sdk: '>=2.0.0' 9 | 10 | dependencies: 11 | tuple: '^1.0.2' 12 | args: '^1.5.0' 13 | 14 | dev_dependencies: 15 | test: '^1.3.0' 16 | -------------------------------------------------------------------------------- /test/generate_expect.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | main() async { 4 | await for (var f in new Directory("testcase").list().where((e) => e.path.endsWith(".lua"))) { 5 | print("Running ${f.path}..."); 6 | var p = await Process.run("lua5.2", [f.path]); 7 | if (p.stderr != "") { 8 | print(p.stdout); 9 | print(p.stderr); 10 | return 1; 11 | } 12 | await new File(f.path.replaceAll(".lua", ".txt")).writeAsString(p.stdout); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/test_5.2.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:lua/lua5.2.dart'; 4 | import 'package:lua/src/util.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | import 'generate_expect.dart' as foo; 8 | 9 | main() async { 10 | await foo.main(); // Temporary 11 | 12 | Map tests = {}; 13 | 14 | await for (var f in new Directory("testcase").list().where((e) => e.path.endsWith(".lua"))) { 15 | tests[f] = await new File(f.path.replaceAll(".lua", ".txt")).readAsString(); 16 | } 17 | 18 | group("Lua 5.2", () { 19 | for (var f in tests.keys) { 20 | test(f.uri.pathSegments.last, () async { 21 | var out = new StringBuffer(); 22 | 23 | CoroutineResult res = await runZoned(() async { 24 | var state = new LuaState(); 25 | return await state.doFile(f.path); 26 | }, zoneSpecification: new ZoneSpecification( 27 | print: (Zone self, ZoneDelegate parent, Zone zone, String line) { 28 | out.writeln(line); 29 | }, 30 | )); 31 | 32 | expect(res.success, true, reason: maybeAt(res.values, 0)); 33 | 34 | expect(out.toString(), tests[f]); 35 | }); 36 | } 37 | }); 38 | } -------------------------------------------------------------------------------- /test/test_all.dart: -------------------------------------------------------------------------------- 1 | import 'test_5.2.dart' as test52; 2 | 3 | main() async { 4 | await test52.main(); 5 | } -------------------------------------------------------------------------------- /testcase/arith.lua: -------------------------------------------------------------------------------- 1 | local x = 1 2 | local y = 2 3 | local z = 69 4 | local a = 420 5 | 6 | print(x + y) 7 | print(x / y) 8 | print(y * a) 9 | print(a % z) 10 | 11 | print(1 / 0) 12 | print(0 / 0) 13 | print(-1 / 0) 14 | 15 | local function approx(x) 16 | return math.floor((x * 1000) + 0.5) / 1000 17 | end 18 | 19 | print(approx(1 / 3)) 20 | print(approx(3 / 5)) 21 | print(approx(-1 / 9)) -------------------------------------------------------------------------------- /testcase/arith.txt: -------------------------------------------------------------------------------- 1 | 3 2 | 0.5 3 | 840 4 | 6 5 | inf 6 | -nan 7 | -inf 8 | 0.333 9 | 0.6 10 | -0.111 11 | -------------------------------------------------------------------------------- /testcase/closures.lua: -------------------------------------------------------------------------------- 1 | -- Basic access 2 | 3 | local x = 1 4 | 5 | print((function() 6 | return x 7 | end)()) 8 | 9 | local y = 12 10 | 11 | print((function() 12 | return (function() 13 | return y 14 | end)() 15 | end)()) 16 | 17 | local z = 123 18 | 19 | print((function() 20 | return (function() 21 | return (function() 22 | return z 23 | end)() 24 | end)() 25 | end)()) 26 | 27 | -- Modification 28 | 29 | local a = 0 30 | 31 | ;(function(n) 32 | a = a + n 33 | end)(1) 34 | 35 | ;(function(n) 36 | a = a + n 37 | end)(1) 38 | 39 | print(a) 40 | 41 | local b = 1 42 | 43 | ;(function() 44 | (function() 45 | b = b + 1 46 | end)(); 47 | end)() 48 | 49 | print(b) 50 | 51 | local c = 1 52 | 53 | ;(function() 54 | local function inc() 55 | c = c + 1 56 | end 57 | 58 | local function inc2() 59 | c = c + 10 60 | end 61 | 62 | inc() 63 | inc2() 64 | inc() 65 | end)() 66 | 67 | print(c) 68 | 69 | -- Not overwriting parameters 70 | 71 | local a = 42 72 | 73 | print(a) 74 | 75 | ;(function(x) 76 | x = 1234 77 | print(x) 78 | end)(a) 79 | 80 | print(a) 81 | 82 | -- Closing properly 83 | 84 | local y 85 | local x = (function() 86 | do 87 | local a = 691 88 | local b = 692 89 | local c = 693 90 | y = function() 91 | print(a, b, c) 92 | end 93 | end 94 | 95 | local a, b, c = 421, 422, 423 96 | print(a, b, c) 97 | end)() 98 | 99 | local a, b, c = 421, 422, 423 100 | 101 | y() 102 | print(a, b, c) 103 | -------------------------------------------------------------------------------- /testcase/closures.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 12 3 | 123 4 | 2 5 | 2 6 | 13 7 | 42 8 | 1234 9 | 42 10 | 421 422 423 11 | 691 692 693 12 | 421 422 423 13 | -------------------------------------------------------------------------------- /testcase/concat.lua: -------------------------------------------------------------------------------- 1 | local x = "potato" 2 | local y = "walrus" 3 | local z = "fizz" 4 | local t = "buzz" 5 | 6 | print(x .. y .. z .. t) -------------------------------------------------------------------------------- /testcase/concat.txt: -------------------------------------------------------------------------------- 1 | potatowalrusfizzbuzz 2 | -------------------------------------------------------------------------------- /testcase/forloops.lua: -------------------------------------------------------------------------------- 1 | local x = {} 2 | 3 | for i = 1, 5 do 4 | x[i] = i * 13 5 | end 6 | 7 | print(x[4]) 8 | print(x[5]) 9 | 10 | for i = 5, 1, -1 do 11 | print(x[i]) 12 | end 13 | 14 | -- iterators 15 | 16 | local function fib(max) 17 | local p = 0 18 | local c = 0 19 | return function(a) 20 | if c == 0 then 21 | c = 1 22 | return 1 23 | end 24 | 25 | local o = p + c 26 | p = c 27 | c = o 28 | 29 | if o > max then 30 | return nil 31 | end 32 | 33 | return o 34 | end 35 | end 36 | 37 | print("fib") 38 | 39 | for n in fib(100) do 40 | print(n) 41 | end -------------------------------------------------------------------------------- /testcase/forloops.txt: -------------------------------------------------------------------------------- 1 | 52 2 | 65 3 | 65 4 | 52 5 | 39 6 | 26 7 | 13 8 | fib 9 | 1 10 | 1 11 | 2 12 | 3 13 | 5 14 | 8 15 | 13 16 | 21 17 | 34 18 | 55 19 | 89 20 | -------------------------------------------------------------------------------- /testcase/pairs.lua: -------------------------------------------------------------------------------- 1 | local x = { 2 | 1, 2, 3, 4, 3 | } 4 | 5 | for k, v in pairs(x) do 6 | print(k, v) 7 | end 8 | 9 | print("done") -------------------------------------------------------------------------------- /testcase/pairs.txt: -------------------------------------------------------------------------------- 1 | 1 1 2 | 2 2 3 | 3 3 4 | 4 4 5 | done 6 | -------------------------------------------------------------------------------- /testcase/pcall.lua: -------------------------------------------------------------------------------- 1 | local x, y = pcall(function(a, b) 2 | print(a) 3 | error(b) 4 | end, "ayy", "md") 5 | 6 | print(x, y) 7 | 8 | local x, y = pcall(function(a, b) 9 | print(a) 10 | return b 11 | end, "69", "420") 12 | 13 | print(x, y) -------------------------------------------------------------------------------- /testcase/pcall.txt: -------------------------------------------------------------------------------- 1 | ayy 2 | false testcase/pcall.lua:3: md 3 | 69 4 | true 420 5 | -------------------------------------------------------------------------------- /testcase/sha256.lua: -------------------------------------------------------------------------------- 1 | if not bit and not bit32 then 2 | bit = require("bit") 3 | end 4 | 5 | bit = bit or bit32 6 | 7 | local function chars2num(txt) 8 | return (txt:byte(1) * 16777216) + (txt:byte(2) * 65536) + (txt:byte(3) * 256) + txt:byte(4) 9 | end 10 | 11 | local function limit(num) 12 | return bit.band(num) 13 | end 14 | 15 | local z = 0 -- curb your linter errors 16 | local _hex = {[z] = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"} 17 | 18 | bit.tohex = bit.tohex or function(n) 19 | local o = "" 20 | local x = limit(n) 21 | for i = 0, 3 do 22 | o = _hex[math.floor(x / 16) % 16] .. _hex[x % 16] .. o 23 | x = math.floor(x / 256) 24 | end 25 | return o 26 | end 27 | 28 | local function num2chars(num, l) 29 | local out = "" 30 | for l1=1, l or 4 do 31 | out = string.char(math.floor(num / (256 ^ (l1 - 1))) % 256) .. out 32 | end 33 | return out 34 | end 35 | 36 | do 37 | local bxor = bit.bxor 38 | local ror = bit.rrotate or bit.ror 39 | local rshift = bit.rshift 40 | local band = bit.band 41 | local bnot = bit.bnot 42 | 43 | local k={ 44 | 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, 45 | 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, 46 | 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, 47 | 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, 48 | 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, 49 | 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, 50 | 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, 51 | 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2, 52 | } 53 | 54 | function sha256(txt) 55 | local ha = { 56 | 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 57 | 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, 58 | } 59 | 60 | local len = #txt 61 | 62 | txt = txt .. "\128" .. ("\0"):rep(64 - ((len + 9) % 64)) .. num2chars(8 * len, 8) 63 | 64 | local w = {} 65 | 66 | for chunkind = 1, #txt, 64 do 67 | local rawchunk = txt:sub(chunkind, chunkind + 63) 68 | local chunk = {} 69 | 70 | for i = 1, 64, 4 do 71 | local x = chars2num(rawchunk:sub(i)) 72 | chunk[math.floor(i / 4) + 1] = x 73 | end 74 | 75 | for i = 1, 16 do 76 | w[i] = chunk[i] 77 | end 78 | 79 | for i = 16, 63 do 80 | local s0 = bxor(ror(w[i - 14], 7), ror(w[i - 14], 18), rshift(w[i - 14], 3)) 81 | local s1 = bxor(ror(w[i - 1], 17), ror(w[i - 1], 19), rshift(w[i - 1], 10)) 82 | w[i + 1] = w[i - 15] + s0 + w[i - 6] + s1 83 | end 84 | 85 | local a, b, c, d, e, f, g, h = ha[1], ha[2], ha[3], ha[4], ha[5], ha[6], ha[7], ha[8] 86 | for i = 0, 63 do 87 | local temp1 = limit((h + bxor(ror(e, 6), ror(e, 11), ror(e, 25))) + (bxor(band(e, f), band(bnot(e), g)) + k[i + 1] + w[i + 1])) 88 | a, b, c, d, e, f, g, h = temp1 + bxor(ror(a, 2), ror(a, 13),ror(a, 22)) + bxor(band(a, b), band(a, c), band(b, c)), a, b, c, d + temp1, e, f, g 89 | end 90 | ha[1], ha[2], ha[3], ha[4], ha[5], ha[6], ha[7], ha[8] = limit(ha[1] + a), limit(ha[2] + b), limit(ha[3] + c), limit(ha[4] + d), limit(ha[5] + e), limit(ha[6] + f), limit(ha[7] + g), limit(ha[8] + h) 91 | end 92 | 93 | return 94 | bit.tohex(ha[1]) .. bit.tohex(ha[2]) .. bit.tohex(ha[3]) .. bit.tohex(ha[4]) .. 95 | bit.tohex(ha[5]) .. bit.tohex(ha[6]) .. bit.tohex(ha[7]) .. bit.tohex(ha[8]) 96 | end 97 | end 98 | 99 | print(sha256("potato")) -------------------------------------------------------------------------------- /testcase/sha256.txt: -------------------------------------------------------------------------------- 1 | e91c254ad58860a02c788dfb5c1a65d6a8846ab1dc649631c7db16fef4af2dec 2 | -------------------------------------------------------------------------------- /testcase/tables.lua: -------------------------------------------------------------------------------- 1 | local x = {1, 12, 123, 1234, 12345} 2 | 3 | print(x[1], x[2], x[3], x[4], x[5]) 4 | 5 | x[1] = 42 6 | 7 | print(x[1]) 8 | 9 | x[2] = 420 10 | 11 | print(x[2]) 12 | 13 | x[1] = nil 14 | 15 | print(x[1]) 16 | print(x[2]) 17 | 18 | x["69"] = 69 19 | 20 | print(x["69"]) -------------------------------------------------------------------------------- /testcase/tables.txt: -------------------------------------------------------------------------------- 1 | 1 12 123 1234 12345 2 | 42 3 | 420 4 | nil 5 | 420 6 | 69 7 | -------------------------------------------------------------------------------- /testcase/vararg.lua: -------------------------------------------------------------------------------- 1 | print(select("#")) 2 | print(select("#", "a", "s", "d", "f")) 3 | 4 | local function x(a, ...) 5 | print(select("#", ...)) 6 | end 7 | 8 | x("a", "s", "d") 9 | x("a", "s") 10 | x("a", "s", "d", "f", "1", "2", "3") -------------------------------------------------------------------------------- /testcase/vararg.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 4 3 | 2 4 | 1 5 | 6 6 | --------------------------------------------------------------------------------