├── .gitignore ├── LICENSE ├── README.md ├── browser-buffer.js ├── brozula.js ├── cli.js ├── compiler.js ├── globals.js ├── interpreter.js ├── package.json ├── parser.js ├── runtime.js ├── tests ├── add.lua ├── arithmetic.lua ├── basic_logic.lua ├── bool.lua ├── callmeta.lua ├── concat.lua ├── demo_account.lua ├── demo_hello.lua ├── forloop.lua ├── indexmeta.lua ├── length.lua ├── loop.lua ├── math.lua ├── meta__eq.lua ├── nilglobal.lua ├── op_close.lua ├── rectangle.lua ├── table.lua ├── tute-coro.lua └── tute-oo.lua └── widgets ├── app.lua ├── cat.lua ├── closure.lua ├── dombuilder.lua ├── functions.lua ├── index.html ├── jumps.lua ├── loops.lua └── widgets.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.luax 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2012-2013 Tim Caswell 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brozula 2 | 3 | Brozula is a luajit bytecode compiler that generates ES5 JavaScript. This means 4 | you can write webapps using lua code, compile it to bytecode, and run the lua in 5 | either node.js or a browser environment. 6 | 7 | # Status 8 | 9 | This project is not done. I just started it while I was at 10 | Lua Workshop 2012 in DC. Currently it can parse nearly all luajit bytecode and 11 | execute a subset of the lua language. This is not a lua -> bytecode compiler. 12 | You'll need the luajit binary to do that part of the conversion. [Performance][] 13 | seems pretty good so far in initial benchmarks. Only time will tell how this scales. 14 | 15 | # Installing 16 | 17 | Brozula can be used as either a nodejs module or a browser library. To see how 18 | to use in the browser, look at [index.html][]. To use in nodejs, use the 19 | `brozula` module in npm. 20 | 21 | ## CLI 22 | 23 | There is a command-line-interface to brozula called `brozula` that can be 24 | installed by installing the npm module globally. 25 | 26 | ```sh 27 | npm install -g brozula 28 | ``` 29 | 30 | I'll let the tool speak for itself since it's self documented. 31 | 32 | There is also a [recording][] of using it in action. 33 | 34 | ``` 35 | $ brozula 36 | Usage: brozula [OPTION...] program.lua[x] 37 | Brozula compiles lua files to bytecode and then executes them using a JS VM 38 | The lua -> luax (luajit bytecode) step is done by using luajit 39 | 40 | Examples: 41 | brozula myprogram.lua 42 | brozula --print myprogram.lua 43 | brozula -pb myprogram.luax 44 | 45 | Main operation mode: 46 | 47 | -x, --execute Execute the generated javascript 48 | (This is the default behavior) 49 | -p, --print Print the generated javascript 50 | --serve port Serve the current folder over HTTP auto-compiling 51 | any lua scripts requested 52 | 53 | Operation modifiers: 54 | 55 | -u, --uglify Compress the generated javascript using uglify-js 56 | -b, --beautify Beautify the generated javascript using uglify-js 57 | -l, --lines Show line numbers when printing 58 | ``` 59 | 60 | ## End-to-end lua webapps. 61 | 62 | One use of this is to write end-to-end lua webapps using something like [luvit][] 63 | or [moonslice][] for the server-side half, and writing your browser-side scripts 64 | in lua that's then executed *in* the browser using Brozula. 65 | 66 | I plan on adding child process support to [luv][] so that a [moonslice][] framework 67 | can be built for doing everything from a single place. 68 | 69 | ## Obfuscated JavaScript Apps. 70 | 71 | The question comes up all the time. How can I ship a JavaScript app without 72 | sharing my code with the world. JavaScript minimizers do a pretty good job at 73 | this, but we can do better. 74 | 75 | If you want your JavaScript code protected from prying eyes, write it in Lua! 76 | Then you only have to share the binary bytecode publicly which is considerably 77 | harder to make sense of and near impossible to trace back to the original code. 78 | 79 | For example, consider the following lua module that calculates factorials: 80 | 81 | ```lua 82 | local function fact (n) 83 | if n == 0 then 84 | return 1 85 | else 86 | return n * fact(n-1) 87 | end 88 | end 89 | return fact 90 | ``` 91 | 92 | The only code you have to store in your node-webkit or web app would be brozula 93 | and the following bytecode: 94 | 95 | ```hd 96 | 00000000 1b 4c 4a 01 02 37 00 01 03 01 00 02 0b 09 00 00 |.LJ..7..........| 97 | 00000010 00 54 01 03 80 27 01 01 00 48 01 02 00 54 01 05 |.T...'...H...T..| 98 | 00000020 80 2b 01 00 00 15 02 01 00 3e 01 02 02 20 01 01 |.+.......>... ..| 99 | 00000030 00 48 01 02 00 47 00 01 00 00 c0 00 02 14 03 00 |.H...G..........| 100 | 00000040 01 00 01 00 03 31 00 00 00 30 00 00 80 48 00 02 |.....1...0...H..| 101 | 00000050 00 00 00 |...| 102 | 00000053 103 | ``` 104 | 105 | Notice that even the `fact` variable name was optimized out by luajit's compiler. 106 | Your original lua code never has to be released to the public since the JavaScript 107 | engines can't make any sense out of lua code anyway. 108 | 109 | Brozula converts this bytecode to JavaScript something like: 110 | 111 | ```js 112 | (function(){function r(r){return null===r?["nil"]:"object"==typeof r?["table"]:[typeof r]} 113 | function t(r){return Array.isArray(r)?r:void 0===r?[]:[r]}function e(r,t){ 114 | return Object.defineProperty(r,"__metatable",{value:t}),[r]}function n(){ 115 | console.log(Array.prototype.join.call(arguments," "))}function o(r,t){ 116 | return r?arguments:(a(t),void 0)}function a(r){throw r}function u(r){for(var e,n,o,a="0";;) 117 | switch(a){case"0":if(0!==r){a="5";break}return n=1,[n];case"5":return n=this.__proto__.closure[0], 118 | o=r-1,e=t(n(o)),n=e[0],e=void 0,n=r*n,[n];case"a":return[]}}function c(){for(var r,t="0";;) 119 | switch(t){case"0":r=u.bind(Object.create(this)),this.closure=[r],t="2";break;case"2":return[r]}} 120 | var i={type:r,setmetatable:e,print:n,assert:o,error:a};return c.apply(i,arguments)})(); 121 | ``` 122 | 123 | ## License 124 | 125 | Brozula is licensed under the MIT license. 126 | 127 | [luvit]: http://luvit.io/ 128 | [moonslice]: https://github.com/creationix/moonslice-luv 129 | [luv]: https://github.com/creationix/luv 130 | [index.html]: https://github.com/creationix/brozula/blob/master/widgets/index.html 131 | [recording]: http://codestre.am/a0b85e026d7f63958cb5adf7c 132 | [Performance]: http://jsperf.com/efficient-goto 133 | -------------------------------------------------------------------------------- /browser-buffer.js: -------------------------------------------------------------------------------- 1 | 2 | function Buffer(arraybuffer, offset, length) { 3 | var data; 4 | if (arguments.length === 1) { 5 | if (Array.isArray(arraybuffer)) { 6 | data = arraybuffer; 7 | arraybuffer = new ArrayBuffer(data.length); 8 | var v = new Uint8Array(arraybuffer); 9 | for (var i = 0, l = data.length; i < l; i++) { 10 | v[i] = data[i]; 11 | } 12 | } 13 | else if (typeof arraybuffer === "number") { 14 | arraybuffer = new ArrayBuffer(arraybuffer); 15 | } 16 | offset = 0; 17 | length = arraybuffer.byteLength; 18 | } 19 | var buffer = new Uint8Array(arraybuffer, offset, length); 20 | buffer.__proto__ = Buffer.prototype; 21 | buffer.view = new DataView(arraybuffer, offset, length); 22 | buffer.offset = offset; 23 | buffer.length = length; 24 | return buffer; 25 | } 26 | Buffer.prototype.__proto__ = Uint8Array.prototype; 27 | Buffer.prototype.slice = function (start, end) { 28 | return Buffer(this.buffer, start + this.offset, end - start); 29 | }; 30 | Buffer.prototype.readUInt32LE = function (offset) { 31 | return this.view.getUint32(offset, true); 32 | }; 33 | Buffer.prototype.readUInt16LE = function (offset) { 34 | return this.view.getUint16(offset, true); 35 | }; 36 | Buffer.prototype.readInt16LE = function (offset) { 37 | return this.view.getInt16(offset, true); 38 | }; 39 | Buffer.prototype.readDoubleLE = function (offset) { 40 | return this.view.getFloat64(offset, true); 41 | }; 42 | Buffer.prototype.writeDoubleLE = function (value, offset) { 43 | return this.view.setFloat64(offset, value, true); 44 | }; 45 | Buffer.prototype.writeUInt32LE = function (value, offset) { 46 | this.view.setUint32(offset, value, true); 47 | }; 48 | Buffer.prototype.toString = function (encoding) { 49 | if (!encoding) { 50 | return String.fromCharCode.apply(null, this); 51 | } 52 | }; -------------------------------------------------------------------------------- /brozula.js: -------------------------------------------------------------------------------- 1 | exports.intepret = require('./interpreter'); 2 | exports.parse = require('./parser'); 3 | exports.compile = require('./compiler'); 4 | exports.runtime = require('./runtime'); 5 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var parse = require('./parser'); 3 | var Closure = require('./interpreter').Closure; 4 | var globals = require('./globals'); 5 | var nopt = require("nopt"); 6 | var execFile = require("child_process").execFile; 7 | var readFile = require('fs').readFile; 8 | var statFile = require('fs').stat; 9 | var pathResolve = require("path").resolve; 10 | var pathJoin = require("path").join; 11 | var dirname = require('path').dirname; 12 | var basename = require('path').basename; 13 | 14 | // Compile lua files to luajit on the fly with a locking queue for concurrent 15 | // requests. 16 | var queues = {}; 17 | function luaToBytecode(path, callback) { 18 | if (path in queues) { 19 | return queues[path].push(callback); 20 | } 21 | var queue = queues[path] = [callback]; 22 | function callbacks(err, newpath) { 23 | delete queues[path]; 24 | queue.forEach(function (callback) { 25 | callback(err, newpath); 26 | }); 27 | } 28 | var newpath = pathJoin(dirname(path), "." + basename(path) + "x"); 29 | statFile(path, function (err, stat) { 30 | if (err) return callbacks(err); 31 | statFile(newpath, function (err, stat2) { 32 | if (err && err.code !== "ENOENT") { 33 | return callbacks(err); 34 | } 35 | if (stat2 && stat2.mtime >= stat.mtime) { 36 | return callbacks(null, newpath); 37 | } 38 | execFile("luajit", ["-b", path, newpath], function (err, stdout, stderr) { 39 | if (err) return callbacks(err); 40 | if (stderr) return callbacks(stderr); 41 | callbacks(null, newpath); 42 | }); 43 | }); 44 | }); 45 | } 46 | 47 | // Compile a lua script to javascript source string 48 | function compile(path, callback) { 49 | if (/\.luax/.test(path)) { 50 | // Load already compiled bytecode 51 | loadBytecode(path); 52 | } 53 | if (/\.lua$/.test(path)) { 54 | luaToBytecode(path, function (err, newpath) { 55 | if (err) return callback(err); 56 | loadBytecode(newpath); 57 | }); 58 | } 59 | function loadBytecode(path) { 60 | readFile(path, function (err, buffer) { 61 | if (err) return callback(err); 62 | generate(buffer); 63 | }); 64 | } 65 | function generate(buffer) { 66 | var program; 67 | try { 68 | program = parse(buffer); 69 | } 70 | catch (err) { 71 | return callback(err); 72 | } 73 | callback(null, program); 74 | } 75 | } 76 | 77 | var options = nopt({ 78 | "serve": Number, 79 | "execute": Boolean, 80 | "print": Boolean, 81 | "json": Boolean, 82 | "uglify": Boolean, 83 | "beautify": Boolean, 84 | "lines": Boolean 85 | }, { 86 | "x": ["--execute"], 87 | "p": ["--print"], 88 | "j": ["--json"], 89 | "u": ["--uglify"], 90 | "b": ["--beautify"], 91 | "l": ["--lines"] 92 | }); 93 | 94 | if (options.serve) { 95 | if (options.serve === 1) options.serve = 8080; 96 | var urlParse = require('url').parse; 97 | var base = process.cwd(); 98 | var send = require('send'); 99 | console.log("BASE", base); 100 | var server = require('http').createServer(function (req, res) { 101 | var url = urlParse(req.url); 102 | if (url.pathname === "/parser.js") { 103 | return send(req, pathJoin(__dirname, "/parser.js")).pipe(res); 104 | } 105 | if (url.pathname === "/interpreter.js") { 106 | return send(req, pathJoin(__dirname, "/interpreter.js")).pipe(res); 107 | } 108 | if (url.pathname === "/runtime.js") { 109 | return send(req, pathJoin(__dirname, "/runtime.js")).pipe(res); 110 | } 111 | if (url.pathname === "/globals.js") { 112 | return send(req, pathJoin(__dirname, "/globals.js")).pipe(res); 113 | } 114 | if (url.pathname === "/browser-buffer.js") { 115 | return send(req, pathJoin(__dirname, "/browser-buffer.js")).pipe(res); 116 | } 117 | var path = pathJoin(base, url.pathname); 118 | if (path[path.length - 1] === "/") { 119 | path += "index.html"; 120 | } 121 | console.log(req.method, path); 122 | if (/\.luax$/.test(path)) { 123 | luaToBytecode(path.substr(0, path.length - 1), function (err, path) { 124 | if (err) { 125 | if (err.code === "ENOENT") { 126 | res.statusCode = 404; 127 | return res.end(); 128 | } 129 | res.statusCode = 500; 130 | return res.end(err.stack); 131 | } 132 | console.log("sending", path, "for", req.url); 133 | send(req, path) 134 | .hidden(true) 135 | .pipe(res); 136 | }); 137 | return; 138 | } 139 | send(req, url.pathname) 140 | .root(base) 141 | .pipe(res); 142 | }); 143 | server.listen(options.serve, function () { 144 | console.log("Serving server listening at", server.address()); 145 | }); 146 | } 147 | else { 148 | 149 | // Default to running if not specified 150 | if (options.execute === undefined && !options.print && !options.json) { 151 | options.execute = true; 152 | } 153 | 154 | var filename = options.argv.remain[0]; 155 | 156 | if (!filename) { 157 | console.error([ 158 | "Usage: brozula [OPTION...] program.lua[x]", 159 | "Brozula compiles lua files to bytecode and then executes them using a JS VM", 160 | "The lua -> luax (luajit bytecode) step is done by using luajit", 161 | "", 162 | "Examples:", 163 | " brozula myprogram.lua", 164 | " brozula --print myprogram.lua", 165 | " brozula --json myprogram.lua", 166 | " brozula -pb myprogram.luax", 167 | "", 168 | " Main operation mode:", 169 | "", 170 | " -x, --execute Execute the generated javascript", 171 | " (This is the default behavior)", 172 | " -p, --print Print the generated javascript", 173 | " -j, --json Print the generated javascript as JSON", 174 | " --serve port Serve the current folder over HTTP auto-compiling", 175 | " any lua scripts requested", 176 | "", 177 | " Operation modifiers:", 178 | "", 179 | " -u, --uglify Compress the generated javascript using uglify-js", 180 | " -b, --beautify Beautify the generated javascript using uglify-js", 181 | " -l, --lines Show line numbers when printing", 182 | "" 183 | ].join("\n")); 184 | process.exit(-1); 185 | } 186 | 187 | 188 | filename = pathResolve(process.cwd(), filename); 189 | compile(filename, function (err, protos) { 190 | if (err) throw err; 191 | if (options.json) { 192 | console.log(options.beautify ? JSON.stringify(protos, null, 2) : JSON.stringify(protos)); 193 | } else if (options.print) { 194 | console.log(require('util').inspect(protos, false, 3 + (options.beautify ? 2 : 0), true) + "\n"); 195 | } 196 | if (options.execute) { 197 | (new Closure(protos[protos.length - 1])).toFunction(globals)(); 198 | } 199 | // program = "(function () {\n\n" + program + "\n\n}());"; 200 | // if (options.uglify) { 201 | // var UglifyJS = require("uglify-js"); 202 | // var toplevel_ast = UglifyJS.parse(program); 203 | // toplevel_ast.figure_out_scope(); 204 | // var compressor = UglifyJS.Compressor({}); 205 | // var compressed_ast = toplevel_ast.transform(compressor); 206 | // compressed_ast.figure_out_scope(); 207 | // compressed_ast.compute_char_frequency(); 208 | // compressed_ast.mangle_names(); 209 | // program = compressed_ast.print_to_string({}); 210 | // } 211 | // if (options.beautify) { 212 | // var UglifyJS = require("uglify-js"); 213 | // var toplevel_ast = UglifyJS.parse(program); 214 | // toplevel_ast.figure_out_scope(); 215 | // program = toplevel_ast.print_to_string({ 216 | // beautify: true, 217 | // indent_level: 2 218 | // }); 219 | // } 220 | // if (options.print) { 221 | // if (options.lines) { 222 | // var lines = program.split("\n"); 223 | // var digits = Math.ceil(Math.log(lines.length) / Math.LN10); 224 | // var padding = ""; 225 | // for (var i = 0; i < digits; i++) { 226 | // padding += "0"; 227 | // } 228 | // console.log(lines.map(function (line, i) { 229 | // var num = (i + 1) + ""; 230 | // return "\033[34m" + padding.substr(num.length) + num + "\033[0m " + line; 231 | // }).join("\n")); 232 | // } 233 | // else { 234 | // console.log(program); 235 | // } 236 | // } 237 | // if (options.execute) { 238 | // eval(program); 239 | // } 240 | }); 241 | } 242 | -------------------------------------------------------------------------------- /compiler.js: -------------------------------------------------------------------------------- 1 | ( // Module boilerplate to support browser globals, node.js and AMD. 2 | (typeof module !== "undefined" && function (m) { module.exports = m(); }) || 3 | (typeof define === "function" && function (m) { define(m); }) || 4 | (function (m) { window.brozula = window.brozula || {}; window.brozula.compile = m(); }) 5 | )(function () { 6 | "use strict"; 7 | 8 | // Generate a variable name for a stack slot 9 | function slot(i) { 10 | return "$" + i.toString(36); 11 | } 12 | function pr(i) { 13 | return "fn" + i.toString(36); 14 | } 15 | function stateLabel(i) { 16 | return '"' + i.toString(36) + '"'; 17 | } 18 | 19 | function compile(protos, skipBuiltins) { 20 | var code = []; 21 | if (!skipBuiltins) { 22 | code.push(builtins); 23 | } 24 | 25 | protos.forEach(function (proto, protoIndex) { 26 | 27 | // console.error(proto); 28 | 29 | // Scan for the blocks by looking for jump targets 30 | var targets = {}; 31 | var needBlock = false; 32 | for (var i = 0, l = proto.bcins.length; i < l; i++) { 33 | var bc = proto.bcins[i]; 34 | var def = bcdef[bc.op]; 35 | Object.keys(def).forEach(function (key, j) { 36 | if (def[key] !== "jump") return; 37 | targets[bc.args[j]] = true; 38 | needBlock = true; 39 | }); 40 | } 41 | 42 | // Form the function header 43 | var line = "function " + pr(protoIndex) + "("; 44 | for (var i = 0; i < proto.numparams; i++) { 45 | if (i) line += ", "; 46 | line += slot(i); 47 | } 48 | line += "){"; 49 | code.push(line); 50 | 51 | // Reserve a slow for the var line 52 | var varIndex = code.length; 53 | code.push(null); 54 | // Reserve the stack 55 | 56 | var state = { 57 | framesize: proto.framesize, 58 | need$: false, 59 | needthis: false 60 | }; 61 | 62 | if (needBlock) { 63 | code.push("var state=" + stateLabel(0) + ";"); 64 | code.push("for(;;){"); 65 | code.push("switch(state){"); 66 | targets[0] = true; 67 | } 68 | 69 | // Compile the opcodes 70 | for (var i = 0, l = proto.bcins.length; i < l; i++) { 71 | var bc = proto.bcins[i]; 72 | if (targets[i]) { 73 | code.push("case " + stateLabel(i) + ":"); 74 | } 75 | 76 | // console.error(bc); 77 | var line = generators[bc.op].apply(state, bc.args); 78 | if (conditionals[bc.op]) { 79 | var next = proto.bcins[++i]; 80 | line += "{" + generators[next.op].apply(state, next.args) + "}" 81 | } 82 | // console.error(line); 83 | code.push(line); 84 | } 85 | if (needBlock) { 86 | code.push("}}"); 87 | } 88 | 89 | var vars = []; 90 | if (state.need$) vars.push("$"); 91 | for (var i = proto.numparams; i < proto.framesize; i++) { 92 | vars.push(slot(i)); 93 | } 94 | if (vars.length) { 95 | code[varIndex] = "var " + vars.join(",") + ";"; 96 | } 97 | else { 98 | code[varIndex] = ""; 99 | } 100 | 101 | 102 | code.push("}"); 103 | }); 104 | 105 | code.push("return " + pr(protos.length - 1) + ".apply(builtins, arguments);"); 106 | 107 | return code.join("\n"); 108 | 109 | } 110 | 111 | var generators = { 112 | ISLT: function (a, d) { 113 | return "if(" + slot(a) + "<" + slot(d) + ")"; 114 | }, 115 | ISGE: function (a, d) { 116 | return "if(" + slot(a) + ">" + slot(d) + ")"; 117 | }, 118 | ISLE: function (a, d) { 119 | return "if(" + slot(a) + "<=" + slot(d) + ")"; 120 | }, 121 | ISGT: function (a, d) { 122 | return "if(" + slot(a) + ">=" + slot(d) + ")"; 123 | }, 124 | ISEQV: function (a, d) { 125 | return "if(" + slot(a) + "===" + slot(d) + ")"; 126 | }, 127 | ISNEV: function (a, d) { 128 | return "if(" + slot(a) + "!==" + slot(d) + ")"; 129 | }, 130 | ISEQS: function (a, d) { 131 | return "if(" + slot(a) + "===" + JSON.stringify(d) + ")"; 132 | }, 133 | ISNES: function (a, d) { 134 | return "if(" + slot(a) + "!==" + JSON.stringify(d) + ")"; 135 | }, 136 | ISEQN: function (a, d) { 137 | return "if(" + slot(a) + "===" + d + ")"; 138 | }, 139 | ISNEN: function (a, d) { 140 | return "if(" + slot(a) + "!==" + d + ")"; 141 | }, 142 | ISEQP: function (a, d) { 143 | return "if(" + slot(a) + "===" + JSON.stringify(d) + ")"; 144 | }, 145 | ISNEP: function (a, d) { 146 | return "if(" + slot(a) + "!==" + JSON.stringify(d) + ")"; 147 | }, 148 | ISTC: function (a, d) { 149 | return 'throw new Error("TODO: Implement ISTC");'; 150 | }, 151 | ISFC: function (a, d) { 152 | return 'throw new Error("TODO: Implement ISFC");'; 153 | }, 154 | IST: function (d) { 155 | return "if(!falsy(" + slot(d) + "))"; 156 | }, 157 | ISF: function (d) { 158 | return "if(falsy(" + slot(d) + "))"; 159 | }, 160 | MOV: function (a, d) { 161 | return slot(a) + "=" + slot(d) + ";"; 162 | }, 163 | NOT: function (a, d) { 164 | return slot(a) + "=falsy(" + slot(d) + ");"; 165 | }, 166 | UNM: function (a, d) { 167 | return slot(a) + "=-" + slot(d) + ";"; 168 | }, 169 | LEN: function (a, d) { 170 | return slot(a) + "=length(" + slot(d) + ");"; 171 | }, 172 | ADDVN: function (a, b, c) { 173 | return slot(a) + "=" + slot(b) + "+" + c + ";"; 174 | }, 175 | SUBVN: function (a, b, c) { 176 | return slot(a) + "=" + slot(b) + "-" + c + ";"; 177 | }, 178 | MULVN: function (a, b, c) { 179 | return slot(a) + "=" + slot(b) + "*" + c + ";"; 180 | }, 181 | DIVVN: function (a, b, c) { 182 | return slot(a) + "=" + slot(b) + "/" + c + ";"; 183 | }, 184 | MODVN: function (a, b, c) { 185 | return slot(a) + "=" + slot(b) + "%" + c + ";"; 186 | }, 187 | ADDNV: function (a, b, c) { 188 | return slot(a) + "=" + slot(c) + "+" + b + ";"; 189 | }, 190 | SUBNV: function (a, b, c) { 191 | return slot(a) + "=" + slot(c) + "-" + b + ";"; 192 | }, 193 | MULNV: function (a, b, c) { 194 | return slot(a) + "=" + slot(c) + "*" + b + ";"; 195 | }, 196 | DIVNV: function (a, b, c) { 197 | return slot(a) + "=" + slot(c) + "/" + b + ";"; 198 | }, 199 | MODNV: function (a, b, c) { 200 | return slot(a) + "=" + slot(c) + "%" + b + ";"; 201 | }, 202 | ADDVV: function (a, b, c) { 203 | return slot(a) + "=" + slot(b) + "+" + slot(c) + ";"; 204 | }, 205 | SUBVV: function (a, b, c) { 206 | return slot(a) + "=" + slot(b) + "-" + slot(c) + ";"; 207 | }, 208 | MULVV: function (a, b, c) { 209 | return slot(a) + "=" + slot(b) + "*" + slot(c) + ";"; 210 | }, 211 | DIVVV: function (a, b, c) { 212 | return slot(a) + "=" + slot(b) + "/" + slot(c) + ";"; 213 | }, 214 | MODVV: function (a, b, c) { 215 | return slot(a) + "=" + slot(b) + "%" + slot(c) + ";"; 216 | }, 217 | POW: function (a, b, c) { 218 | return slot(a) + "=Math.pow(" + slot(b) + "," + slot(c) + ");"; 219 | }, 220 | CAT: function (a, b, c) { 221 | var line = slot(a) + "=''"; 222 | for (var i = b; i <= c; i++) { 223 | line += "+" + slot(i); 224 | } 225 | return line + ";"; 226 | }, 227 | KSTR: function (a, d) { 228 | return slot(a) + "=" + JSON.stringify(d) + ";"; 229 | }, 230 | KCDATA: function (a, d) { 231 | return 'throw new Error("TODO: Implement KCDATA");'; 232 | }, 233 | KSHORT: function (a, d) { 234 | return slot(a) + "=" + d + ";"; 235 | }, 236 | KNUM: function (a, d) { 237 | return slot(a) + "=" + d + ";"; 238 | }, 239 | KPRI: function (a, d) { 240 | return slot(a) + "=" + JSON.stringify(d) + ";"; 241 | }, 242 | KNIL: function (a, d) { 243 | var line = ""; 244 | for (var i = a; i <= d; i++) { 245 | line += slot(i) + "=null;"; 246 | } 247 | return line; 248 | }, 249 | UGET: function (a, d) { 250 | var local = d & 0x8000; 251 | var immutable = d & 0x4000; 252 | d = d & 0x3fff; 253 | if (local) { 254 | return slot(a) + "=this.__proto__.closure[" + d + "]"; 255 | } 256 | return 'new Error("TODO: Implement UGET with non-local");'; 257 | }, 258 | USETV: function () { 259 | return 'throw new Error("TODO: Implement USETV");'; 260 | }, 261 | USETS: function () { 262 | return 'throw new Error("TODO: Implement USETS");'; 263 | }, 264 | USETN: function () { 265 | return 'throw new Error("TODO: Implement USETN");'; 266 | }, 267 | USETP: function () { 268 | return 'throw new Error("TODO: Implement USETP");'; 269 | }, 270 | UCLO: function (a, d) { 271 | var items = []; 272 | for (var i = a; i < this.framesize; i++) { 273 | items.push(slot(i)); 274 | } 275 | return "this.closure = [" + items.join(",") + "];state=" + stateLabel(d) + ";break;"; 276 | }, 277 | FNEW: function (a, d) { 278 | return slot(a) + "=" + pr(d) + ".bind(Object.create(this));"; 279 | }, 280 | TNEW: function (a, d) { 281 | var arrayn = d & 0x7ff; 282 | var hashn = d >>> 11; 283 | if (!arrayn || hashn) return slot(a) + "={};"; 284 | return slot(a) + "=new Array(" + arrayn + ");"; 285 | }, 286 | TDUP: function (a, d) { 287 | // TODO: handle more table types properly 288 | return slot(a) + "=" + JSON.stringify(d) + ";"; 289 | }, 290 | GGET: function (a, d) { 291 | return slot(a) + "=index(this," + JSON.stringify(d) + ");"; 292 | }, 293 | GSET: function (a, d) { 294 | return "newindex(this," + slot(a) + "," + JSON.stringify(d) + ");"; 295 | }, 296 | TGETV: function (a, b, c) { 297 | return slot(a) + "=index(" + slot(b) + "," + slot(c) + ");"; 298 | }, 299 | TGETS: function (a, b, c) { 300 | return slot(a) + "=index(" + slot(b) + "," + JSON.stringify(c) + ");"; 301 | }, 302 | TGETB: function (a, b, c) { 303 | return slot(a) + "=index(" + slot(b) + "," + c + ");"; 304 | }, 305 | TSETV: function (a, b, c) { 306 | return "newindex(" + slot(b) + "," + slot(c) + "," + slot(a) + ");"; 307 | }, 308 | TSETS: function (a, b, c) { 309 | return "newindex(" + slot(b) + "," + JSON.stringify(c) + "," + slot(a) + ");"; 310 | }, 311 | TSETB: function (a, b, c) { 312 | return "newindex(" + slot(b) + "," + c + "," + slot(a) + ");"; 313 | }, 314 | TSETM: function (a, d) { 315 | var b = new Buffer(8); 316 | b.writeDoubleLE(d, 0); 317 | d = b.readUInt32LE(0); 318 | return '$.forEach(function(v,i){' + slot(a-1) + '[i+' + d + '];});' + 319 | '$=undefined;'; 320 | }, 321 | CALLM: function (a, b, c) { 322 | var args; 323 | if (c) { 324 | args = []; 325 | for (var i = a + 1; i <= a + c; i++) { 326 | args.push(slot(i)); 327 | } 328 | args = "[" + args.join(",") + "].concat($)"; 329 | } 330 | else { 331 | args = "$"; 332 | } 333 | this.needthis = true; 334 | var fn = "call(" + slot(a) + "," + args + ")"; 335 | if (b === 0) { // multires 336 | this.need$ = true; 337 | return "$=" + fn + ";"; 338 | } 339 | if (b === 1) { // No return values 340 | return fn + ";"; 341 | } 342 | if (b === 2) { // one return value 343 | return slot(a) + "=" + fn + "[0] || null;"; 344 | } 345 | this.need$ = true; 346 | var line = "$=" + fn + ";"; 347 | for (var i = 0; i < b - 1; i++) { 348 | line += slot(a + i) + "=$[" + i + "];"; 349 | } 350 | line += "$=undefined;"; 351 | return line; 352 | }, 353 | CALL: function (a, b, c) { 354 | var args = []; 355 | for (var i = a + 1; i < a + c; i++) { 356 | args.push(slot(i)); 357 | } 358 | args = "[" + args.join(",") + "]"; 359 | this.needthis = true; 360 | var fn = "call(" + slot(a) + "," + args + ")"; 361 | if (b === 0) { // multires 362 | this.need$ = true; 363 | return "$=" + fn + ";"; 364 | } 365 | if (b === 1) { // No return values 366 | return fn + ";"; 367 | } 368 | if (b === 2) { // one return value 369 | return slot(a) + "=" + fn + "[0] || null;"; 370 | } 371 | this.need$ = true; 372 | var line = "$=" + fn + ";"; 373 | for (var i = 0; i < b - 1; i++) { 374 | line += slot(a + i) + "=$[" + i + "];"; 375 | } 376 | line += "$=undefined;"; 377 | return line; 378 | }, 379 | CALLMT: function (a) { 380 | this.needthis = true; 381 | return "return call(" + slot(a) + ",$);"; 382 | }, 383 | CALLT: function (a, d) { 384 | var args = []; 385 | for (var i = a + 1; i < a + d; i++) { 386 | args.push(slot(i)); 387 | } 388 | args = "[" + args.join(",") + "]"; 389 | this.needthis = true; 390 | return "return call(" + slot(a) + "," + args + ");"; 391 | }, 392 | ITERC: function (a, b, c) { 393 | var line = 394 | slot(a) + "=" + slot(a - 3) + ";" + 395 | slot(a + 1) + "=" + slot(a - 2) + ";" + 396 | slot(a + 2) + "=" + slot(a - 1) + ";"; 397 | this.needthis = true; 398 | var fn = "call(" + slot(a) + ",[" + slot(a + 1) + "," + slot(a + 2) + "])"; 399 | if (b === 0) { // multires 400 | this.need$ = true; 401 | return line + "$=" + fn + ";"; 402 | } 403 | if (b === 1) { // No return values 404 | return line + fn + ";"; 405 | } 406 | if (b === 2) { // one return value 407 | return line + slot(a) + "=" + fn + "[0] || null;"; 408 | } 409 | this.need$ = true; 410 | line += "$=" + fn + ";"; 411 | for (var i = 0; i <= b - 2; i++) { 412 | line += slot(a + i) + "=$[" + i + "];"; 413 | } 414 | line += "$=undefined;"; 415 | return line; 416 | }, 417 | ITERN: function (a, b, c) { 418 | var line = 419 | slot(a) + "=" + slot(a - 3) + ";" + 420 | slot(a + 1) + "=" + slot(a - 2) + ";" + 421 | slot(a + 2) + "=" + slot(a - 1) + ";"; 422 | var fn = "next(" + slot(a - 2) + "," + slot(a - 1) + ")"; 423 | if (b === 0) { // multires 424 | this.need$ = true; 425 | return line + "$=" + fn + ";"; 426 | } 427 | if (b === 1) { // No return values 428 | return line + fn + ";"; 429 | } 430 | if (b === 2) { // one return value 431 | return line + slot(a) + "=" + fn + "[0] || null;"; 432 | } 433 | this.need$ = true; 434 | line += "$=" + fn + ";"; 435 | for (var i = 0; i < b - 1; i++) { 436 | line += slot(a + i) + "=$[" + i + "];"; 437 | } 438 | line += "$=undefined;"; 439 | return line; 440 | }, 441 | VARG: function () { 442 | return 'throw new Error("TODO: Implement VARG");'; 443 | }, 444 | ISNEXT: function (a, d) { 445 | return 'if('+ slot(a - 3) + '===next&&rawType(' + slot(a - 2) + ')==="table"&&' + slot(a - 1) + '===null)' + 446 | '{state=' + stateLabel(d) + ';break;}' + 447 | 'else{throw"TODO: Implement ISNEXT failure";}'; 448 | }, 449 | RETM: function () { 450 | return 'throw new Error("TODO: Implement RETM");'; 451 | }, 452 | RET: function () { 453 | return 'throw new Error("TODO: Implement RET");'; 454 | }, 455 | RET0: function () { 456 | return "return[];"; 457 | }, 458 | RET1: function (a) { 459 | return "return[" + slot(a) + "];" 460 | }, 461 | FORI: function (a, d) { 462 | return slot(a + 3) + "=" + slot(a) + ";" + 463 | "var cmp=" + slot(a) + "<" + slot(a+1) + "?le:ge;"; 464 | }, 465 | JFORI: function () { 466 | return 'throw new Error("TODO: Implement JFORI");'; 467 | }, 468 | FORL: function (a, d) { 469 | return slot(a + 3) + "+=" + slot(a + 2) + ";" + 470 | "if(cmp(" + slot(a + 3) + "," + slot(a + 1) + ")){" + 471 | "state=" + stateLabel(d) + ";break;}" + 472 | "else{cmp=undefined;}" 473 | }, 474 | IFORL: function (a, d) { 475 | return 'throw new Error("TODO: Implement IFORL");'; 476 | }, 477 | JFORL: function () { 478 | return 'throw new Error("TODO: Implement JFORL");'; 479 | }, 480 | ITERL: function (a, d) { 481 | return "if (" + slot(a) + "){" + slot(a-1) + "=" + slot(a) + ";state=" + stateLabel(d) + ";break;}"; 482 | }, 483 | IITERL: function () { 484 | return 'throw new Error("TODO: Implement IITERL");'; 485 | }, 486 | JITERL: function () { 487 | return 'throw new Error("TODO: Implement JITERL");'; 488 | }, 489 | LOOP: function () { 490 | return ""; // tracing noop 491 | }, 492 | ILOOP: function () { 493 | return 'throw new Error("TODO: Implement ILOOP");'; 494 | }, 495 | JLOOP: function () { 496 | return 'throw new Error("TODO: Implement JLOOP");'; 497 | }, 498 | JMP: function (a, d) { 499 | return "state=" + stateLabel(d) + ";break;"; 500 | }, 501 | FUNCF: function () { 502 | return 'throw new Error("TODO: Implement FUNCF");'; 503 | }, 504 | IFUNCF: function () { 505 | return 'throw new Error("TODO: Implement IFUNCF");'; 506 | }, 507 | JFUNCF: function () { 508 | return 'throw new Error("TODO: Implement JFUNCF");'; 509 | }, 510 | FUNCV: function () { 511 | return 'throw new Error("TODO: Implement FUNCV");'; 512 | }, 513 | IFUNCV: function () { 514 | return 'throw new Error("TODO: Implement IFUNCV");'; 515 | }, 516 | JFUNCV: function () { 517 | return 'throw new Error("TODO: Implement JFUNCV");'; 518 | }, 519 | FUNCC: function () { 520 | return 'throw new Error("TODO: Implement FUNCC");'; 521 | }, 522 | FUNCCW: function () { 523 | return 'throw new Error("TODO: Implement FUNCCW");'; 524 | } 525 | }; 526 | 527 | return compile; 528 | 529 | }); -------------------------------------------------------------------------------- /globals.js: -------------------------------------------------------------------------------- 1 | ( // Module boilerplate to support browser globals, node.js and AMD. 2 | (typeof module !== "undefined" && function (m) { module.exports = m(require('./runtime')); }) || 3 | (typeof define === "function" && function (m) { define(["./runtime"], m); }) || 4 | (function (m) { window.brozula = window.brozula || {}; window.brozula.globals = m(window.brozula.runtime); }) 5 | )(function (runtime) { 6 | 7 | var falsy = runtime.falsy; 8 | var slice = Array.prototype.slice; 9 | var next = runtime.next; 10 | var inext = runtime.inext; 11 | 12 | var file = { 13 | close: function () { throw new Error("TODO: Implement io.file.close"); }, 14 | flush: function () { throw new Error("TODO: Implement io.file.flush"); }, 15 | lines: function () { throw new Error("TODO: Implement io.file.lines"); }, 16 | read: function () { throw new Error("TODO: Implement io.file.read"); }, 17 | seek: function () { throw new Error("TODO: Implement io.file.seek"); }, 18 | setvbuf: function () { throw new Error("TODO: Implement io.file.setvbuf"); }, 19 | write: function () { throw new Error("TODO: Implement io.file.write"); } 20 | }; 21 | 22 | var _G = { 23 | _VERSION: "Lua 5.1", 24 | assert: function assert(expr, message) { 25 | if (falsy(expr)) throw message; 26 | return slice.call(arguments); 27 | }, 28 | bit: {}, 29 | collectgarbage: function collectgarbage() { throw new Error("TODO: Implement collectgarbage"); }, 30 | coroutine: { 31 | create: function () { throw new Error("TODO: Implement coroutine.create"); }, 32 | resume: function () { throw new Error("TODO: Implement coroutine.resume"); }, 33 | running: function () { throw new Error("TODO: Implement coroutine.running"); }, 34 | status: function () { throw new Error("TODO: Implement coroutine.status"); }, 35 | wrap: function () { throw new Error("TODO: Implement coroutine.wrap"); }, 36 | yield: function () { throw new Error("TODO: Implement coroutine.yield"); } 37 | }, 38 | debug: {}, 39 | error: function error(message) { 40 | throw message; 41 | }, 42 | gcinfo: function gcinfo() { throw new Error("TODO: Implement gcinfo"); }, 43 | getfenv: function getfenv() { throw new Error("TODO: Implement getfenv"); }, 44 | getmetatable: function getmetatable(tab) { 45 | return [runtime.getmetatable(tab)]; 46 | }, 47 | io: { 48 | input: function () { throw new Error("TODO: Implement io.input"); }, 49 | tmpfile: function () { throw new Error("TODO: Implement io.tmpfile"); }, 50 | read: function () { throw new Error("TODO: Implement io.read"); }, 51 | output: function () { throw new Error("TODO: Implement io.output"); }, 52 | open: function () { throw new Error("TODO: Implement io.open"); }, 53 | close: function () { throw new Error("TODO: Implement io.close"); }, 54 | write: function () { throw new Error("TODO: Implement io.write"); }, 55 | popen: function () { throw new Error("TODO: Implement io.popen"); }, 56 | flush: function () { throw new Error("TODO: Implement io.flush"); }, 57 | type: function () { throw new Error("TODO: Implement io.type"); }, 58 | lines: function () { throw new Error("TODO: Implement io.lines"); }, 59 | stdin: file, 60 | stdout: file, 61 | stderr: file 62 | }, 63 | ipairs: function ipairs(tab) { 64 | return [inext, tab, 0]; 65 | }, 66 | jit: {}, 67 | math: { 68 | abs: function (x) { return [Math.abs(x)]; }, 69 | acos: function (x) { return [Math.acos(x)]; }, 70 | asin: function (x) { return [Math.asin(x)]; }, 71 | atan: function (x) { return [Math.atan(x)]; }, 72 | atan2: function (x,y) { return [Math.atan2(x,y)]; }, 73 | ceil: function (x) { return [Math.ceil(x)]; }, 74 | cos: function (x) { return [Math.cos(x)]; }, 75 | cosh: function (x) { return [ ( Math.exp(x)+Math.exp(-x) ) / 2.0 ]; }, 76 | deg: function (x) { return [x/(Math.PI/180)]; }, 77 | exp: function (x) { return [Math.exp(x)]; }, 78 | floor: function (x) { return [Math.floor(x)]; }, 79 | fmod: function () { throw new Error("TODO: Implement math.fmod"); }, 80 | frexp: function () { throw new Error("TODO: Implement math.frexp"); }, 81 | huge: Infinity, 82 | ldexp: function () { throw new Error("TODO: Implement math.ldexp"); }, 83 | log: function (x,b) { 84 | return [ (typeof b === 'undefined' ) ? 85 | Math.log(x) : 86 | Math.log(x) / Math.log(x) 87 | ]; 88 | }, 89 | log10: function (x) { // This is deprecated in Lua 5.2 90 | return [ Math.log ( x ) / Math.LN10 ]; 91 | }, 92 | max: function (max) { 93 | for ( var i = 1; i < arguments.length; i++ ) { 94 | max = Math.max ( max , arguments[i] ); 95 | } 96 | return [max]; 97 | }, 98 | min: function (min) { 99 | for ( var i = 1; i < arguments.length; i++ ) { 100 | min = Math.min ( min , arguments[i] ); 101 | } 102 | return [min]; 103 | }, 104 | modf: function () { throw new Error("TODO: Implement math.modf"); }, 105 | pi: Math.PI, 106 | pow: function (x) { return [Math.pow(x)]; }, 107 | rad: function (x) { return [x*(Math.PI/180)]; }, 108 | random: function (a,b) { 109 | var r = Math.random(x); 110 | switch ( arguments.length ) { 111 | case 0: 112 | return [rnd]; 113 | case 1: /* only upper limit */ 114 | if (1>a) { throw new Error("interval is empty"); } 115 | return [Math.floor(r*a)+1]; 116 | case 2: /* lowe and upper limits */ 117 | if (a>b) { throw new Error("interval is empty"); } 118 | return [Math.floor(r*(b-a+1))+1]; 119 | default: 120 | throw new Error("wrong number of arguments"); 121 | } 122 | }, 123 | randomseed: function () { throw new Error("TODO: Implement math.randomseed"); }, 124 | sin: function (x) { return [Math.sin(x)]; }, 125 | sinh: function (x) { return [ ( Math.exp(x)-Math.exp(-x) ) / 2.0 ]; }, 126 | sqrt: function (x) { return [Math.sqrt(x)]; }, 127 | tan: function (x) { return [Math.tan(x)]; }, 128 | tanh: function (x) { 129 | var ex = Math.exp(x); 130 | var ey = Math.exp(-x); 131 | return [ ( ex-ey ) / ( ex + ey ) ]; 132 | } 133 | }, 134 | module: function module() { throw new Error("TODO: Implement module"); }, 135 | newproxy: function newproxy() { throw new Error("TODO: Implement newproxy"); }, 136 | next: next, 137 | os: {}, 138 | package: {}, 139 | pairs: function pairs(tab) { 140 | return [next, tab, null]; 141 | }, 142 | pcall: function pcall() { throw new Error("TODO: Implement pcall"); }, 143 | print: function print() { 144 | console.log(Array.prototype.map.call(arguments, runtime.tostring).join("\t")); 145 | return []; 146 | }, 147 | rawequal: function rawequal() { throw new Error("TODO: Implement rawequal"); }, 148 | rawget: function rawget(tab, key) { 149 | return [runtime.rawget(tab, key)]; 150 | }, 151 | rawset: function rawset(tab, key, val) { 152 | runtime.rawset(tab, key, val); 153 | return []; 154 | }, 155 | require: function require() { throw new Error("TODO: Implement require"); }, 156 | select: function select(index) { 157 | if (typeof index === "number") { 158 | return Array.prototype.slice.call(arguments, index); 159 | } 160 | if (index === "#") { 161 | return arguments.length - 1; 162 | } 163 | }, 164 | setfenv: function setfenv() { throw new Error("TODO: Implement setfenv"); }, 165 | setmetatable: function setmetatable(tab, meta) { 166 | runtime.setmetatable(tab, meta); 167 | return [tab]; 168 | }, 169 | string: runtime.string, 170 | table: { 171 | concat: function (tab, joiner) { 172 | if (!(tab && typeof tab === "object")) throw "table expected"; 173 | if (!Array.isArray(tab)) return [""]; 174 | return [tab.join(joiner)]; 175 | }, 176 | insert: function (tab, value) { 177 | if (!(tab && typeof tab === "object")) throw "table expected"; 178 | if (!Array.isArray(tab)) throw "TODO: Implement insert on non-array tables"; 179 | tab.push(value); 180 | return []; 181 | }, 182 | sort: function (tab, cmp) { 183 | cmp = cmp || lt; 184 | tab.sort(function (a, b) { 185 | return cmp(a, b)[0] ? 1 : -1; 186 | }); 187 | return []; 188 | } 189 | }, 190 | tonumber: function tonumber(val, base) { 191 | return [runtime.tonumber(val, base)]; 192 | }, 193 | tostring: function tostring(val) { 194 | return [runtime.tostring(val)]; 195 | }, 196 | type: function type(val) { 197 | return [runtime.type(val)]; 198 | }, 199 | unpack: function unpack(tab) { 200 | return tab; 201 | }, 202 | xpcall: function xpcall() { throw new Error("TODO: Implement xpcall"); }, 203 | inspect: function (val) { 204 | console.log(require('util').inspect(val, false, 10, true)); 205 | return []; 206 | } 207 | }; 208 | _G._G = _G; 209 | 210 | return _G; 211 | 212 | }); -------------------------------------------------------------------------------- /interpreter.js: -------------------------------------------------------------------------------- 1 | ( // Module boilerplate to support browser globals, node.js and AMD. 2 | (typeof module !== "undefined" && function (m) { module.exports = m(require('./runtime')); }) || 3 | (typeof define === "function" && function (m) { define(["./runtime"], m); }) || 4 | (function (m) { window.brozula = window.brozula || {}; window.brozula.interpret = m(window.brozula.runtime); }) 5 | )(function (runtime) { 6 | "use strict"; 7 | 8 | function Closure(proto, parent) { 9 | this.flags = proto.flags; 10 | this.numparams = proto.numparams; 11 | this.framesize = proto.framesize; 12 | this.bcins = proto.bcins; // Program logic as bytecode instructions 13 | this.uvdata = proto.uvdata; // Upvalue searching data 14 | this.protoIndex = proto.index; 15 | this.parent = parent; 16 | this.multires = undefined; 17 | } 18 | //Function.prototype.toString = function () { return inspect(this); }; 19 | Closure.prototype.execute = function (env, args) { 20 | this.pc = 0; // Program Counter 21 | this.env = env; // global environment 22 | runtime = runtime || window.brozula.runtime; 23 | this.vararg = Array.prototype.slice.call(args, this.numparams); 24 | this.slots = new Array(this.framesize); // vm registers 25 | for (var i = 0, l = this.numparams; i < l; i++) { 26 | this.slots[i] = args[i]; 27 | } 28 | var ret; 29 | // try { 30 | while (!ret) { 31 | var bc = this.bcins[this.pc++]; 32 | // console.error(this.protoIndex + " " + this.slots.map(function (v, i) { return " " + i + " " + require('util').inspect(v, false, -1, true);}, this).join("\n").trim()); 33 | // console.error(this.protoIndex + "-" + (this.pc-1), require('util').inspect(bc, false, 1, true).replace(/\s+/g, " ")); 34 | ret = this[bc.op].apply(this, bc.args); 35 | } 36 | // } 37 | // catch (err) { 38 | // throw new Error("Lua error at " + this.protoIndex + "-" + (this.pc-1) + "\n" + err); 39 | // } 40 | return ret; 41 | }; 42 | 43 | Closure.prototype.toFunction = function (env) { 44 | var self = this; 45 | var fn = function () { 46 | return self.execute.call(self, env, arguments); 47 | }; 48 | fn.index = this.protoIndex; 49 | return fn; 50 | }; 51 | 52 | Closure.prototype.ISLT = function (a, d, mt) { 53 | if (!runtime[mt](this.slots[a], this.slots[d])) { this.pc++; } 54 | }; 55 | Closure.prototype.ISGE = function (a, d, mt) { 56 | if (runtime[mt](this.slots[a], this.slots[d])) { this.pc++; } 57 | }; 58 | Closure.prototype.ISLE = Closure.prototype.ISLT; 59 | Closure.prototype.ISGT = Closure.prototype.ISGE; 60 | Closure.prototype.ISEQV = Closure.prototype.ISLT; 61 | Closure.prototype.ISNEV = Closure.prototype.ISGE; 62 | Closure.prototype.ISEQS = function (a, d, mt) { 63 | if (!runtime[mt](this.slots[a], d)) { this.pc++; } 64 | }; 65 | Closure.prototype.ISEQN = Closure.prototype.ISEQS; 66 | Closure.prototype.ISEQP = Closure.prototype.ISEQS; 67 | Closure.prototype.ISNES = function (a, d, mt) { 68 | if (runtime[mt](this.slots[a], d)) { this.pc++; } 69 | }; 70 | Closure.prototype.ISNEN = Closure.prototype.ISNES; 71 | Closure.prototype.ISNEP = Closure.prototype.ISNES; 72 | Closure.prototype.ISTC = function (a, d) { 73 | if (runtime.falsy(this.slots[d])) { this.pc++; } 74 | else this.slots[a] = this.slots[d]; 75 | }; 76 | Closure.prototype.ISFC = function (a, d) { 77 | if (!runtime.falsy(this.slots[d])) { this.pc++; } 78 | else this.slots[a] = this.slots[d]; 79 | }; 80 | Closure.prototype.IST = function (d) { 81 | if (runtime.falsy(this.slots[d])) { this.pc++; } 82 | }; 83 | Closure.prototype.ISF = function (d) { 84 | if (!runtime.falsy(this.slots[d])) { this.pc++; } 85 | }; 86 | Closure.prototype.MOV = function (a, d) { 87 | this.slots[a] = this.slots[d]; 88 | }; 89 | Closure.prototype.NOT = function (a, d) { 90 | this.slots[a] = runtime.falsy(this.slots[d]); 91 | }; 92 | Closure.prototype.UNM = function (a, d, mt) { 93 | this.slots[a] = runtime[mt](this.slots[d]); 94 | }; 95 | Closure.prototype.LEN = Closure.prototype.UNM; 96 | Closure.prototype.ADDVN = function (a, b, c, mt) { 97 | this.slots[a] = runtime[mt](this.slots[b], c); 98 | }; 99 | Closure.prototype.SUBVN = Closure.prototype.ADDVN; 100 | Closure.prototype.MULVN = Closure.prototype.ADDVN; 101 | Closure.prototype.DIVVN = Closure.prototype.ADDVN; 102 | Closure.prototype.MODVN = Closure.prototype.ADDVN; 103 | Closure.prototype.ADDNV = function (a, b, c, mt) { 104 | this.slots[a] = runtime[mt](c, this.slots[b]); 105 | }; 106 | Closure.prototype.SUBNV = Closure.prototype.ADDNV; 107 | Closure.prototype.MULNV = Closure.prototype.ADDNV; 108 | Closure.prototype.DIVNV = Closure.prototype.ADDNV; 109 | Closure.prototype.MODNV = Closure.prototype.ADDNV; 110 | Closure.prototype.ADDVV = function (a, b, c, mt) { 111 | this.slots[a] = runtime[mt](this.slots[b], this.slots[c]); 112 | }; 113 | Closure.prototype.SUBVV = Closure.prototype.ADDVV; 114 | Closure.prototype.MULVV = Closure.prototype.ADDVV; 115 | Closure.prototype.DIVVV = Closure.prototype.ADDVV; 116 | Closure.prototype.MODVV = Closure.prototype.ADDVV; 117 | Closure.prototype.POW = Closure.prototype.ADDVV; 118 | Closure.prototype.CAT = function (a, b, c, mt) { 119 | // TODO: test this manually 120 | for (var i = c - 1; i >= b; i--) { 121 | this.slots[i] = runtime[mt](this.slots[i], this.slots[i + 1]); 122 | } 123 | if (a !== b) { 124 | this.slots[a] = this.slots[b]; 125 | } 126 | }; 127 | Closure.prototype.KSTR = function (a, d) { 128 | this.slots[a] = d; 129 | }; 130 | Closure.prototype.KCDATA = Closure.prototype.KSTR; 131 | Closure.prototype.KSHORT = Closure.prototype.KSTR; 132 | Closure.prototype.KNUM = Closure.prototype.KSTR; 133 | Closure.prototype.KPRI = Closure.prototype.KSTR; 134 | Closure.prototype.KNIL = function (a, d) { 135 | for (var i = a; i <= d; i++) { 136 | this.slots[i] = null; 137 | } 138 | }; 139 | 140 | Closure.prototype.getUv = function (index) { 141 | var uv = this.uvdata[index]; 142 | // console.log("getUv", this.protoIndex, index, uv); 143 | if (!uv.local) return this.parent.getUv(index); 144 | return this.parent.slots[uv.uv]; 145 | }; 146 | 147 | Closure.prototype.setUv = function (index, value) { 148 | var uv = this.uvdata[index]; 149 | // console.log("setUv", this.protoIndex, index, uv, value); 150 | if (!uv.local) return this.parent.setUv(index, value); 151 | if (uv.immutable) throw new Error("Cannot set immutable upvalue"); 152 | this.parent.slots[uv.uv] = value; 153 | }; 154 | 155 | Closure.prototype.UGET = function (a, d) { 156 | this.slots[a] = this.getUv(d); 157 | }; 158 | Closure.prototype.USETV = function (a, d) { 159 | this.setUv(a, this.slots[d]); 160 | }; 161 | Closure.prototype.USETN = function (a, d) { 162 | this.setUv(a, d); 163 | }; 164 | Closure.prototype.USETP = Closure.prototype.USETN; 165 | Closure.prototype.USETS = Closure.prototype.USETN; 166 | Closure.prototype.UCLO = function (a, d) { 167 | if (a) throw new Error("TODO: Implement non-zero rbase for UCLO"); 168 | this.pc += d; 169 | }; 170 | Closure.prototype.FNEW = function (a, d, mt) { 171 | var closure = new Closure(d, this); 172 | this.slots[a] = closure.toFunction(this.env); 173 | }; 174 | Closure.prototype.TNEW = function (a, d, mt) { 175 | // TODO: call mt when collected 176 | this.slots[a] = new runtime.Table(d & 0x7ff, d >>> 11); 177 | }; 178 | Closure.prototype.TDUP = function (a, d, mt) { 179 | // TODO: call mt when collected 180 | this.slots[a] = runtime.Table.clone(d); 181 | }; 182 | Closure.prototype.GGET = function (a, d, mt) { 183 | this.slots[a] = runtime[mt](this.env, d); 184 | }; 185 | Closure.prototype.GSET = function (a, d, mt) { 186 | runtime[mt](this.env, d, this.slots[a]); 187 | }; 188 | Closure.prototype.TGETV = Closure.prototype.ADDVV; 189 | Closure.prototype.TGETS = Closure.prototype.ADDVN; 190 | Closure.prototype.TGETB = Closure.prototype.ADDVN; 191 | Closure.prototype.TSETV = function (a, b, c, mt) { 192 | runtime[mt](this.slots[b], this.slots[c], this.slots[a]); 193 | }; 194 | Closure.prototype.TSETS = function (a, b, c, mt) { 195 | runtime[mt](this.slots[b], c, this.slots[a]); 196 | }; 197 | Closure.prototype.TSETB = Closure.prototype.TSETS; 198 | Closure.prototype.TSETM = function (a, d, mt) { 199 | var buffer = new Buffer(8); // Get the lower 32 bits from the number 200 | buffer.writeDoubleLE(d, 0); 201 | d = buffer.readUInt32LE(0); 202 | var tab = this.slots[a - 1]; 203 | for (var i = 0, l = this.multires.length; i < l; i++) { 204 | tab[d + i] = this.multires[i]; 205 | } 206 | this.multires = undefined; 207 | }; 208 | 209 | // Call helper 210 | Closure.prototype.call = function(a, b, fn, args) { 211 | if (b === 0) { // multires return 212 | this.multires = runtime.call(fn, args); 213 | } 214 | else if (b === 1) { 215 | this.slots[a] = runtime.call(fn, args)[0]; 216 | } 217 | else { 218 | var result = runtime.call(fn, args); 219 | for (var i = 0; i < b - 1; i++) { 220 | this.slots[a + i] = result[i]; 221 | } 222 | } 223 | }; 224 | Closure.prototype.CALLM = function (a, b, c, mt) { 225 | if (c > 0) { // extras 226 | this.call(a, b, this.slots[a], this.slots.slice(a + 1, a + c + 1).concat(this.multires)); 227 | } 228 | else { 229 | this.call(a, b, this.slots[a], this.multires); 230 | } 231 | }; 232 | Closure.prototype.CALL = function (a, b, c, mt) { 233 | this.call(a, b, this.slots[a], this.slots.slice(a + 1, a + c)); 234 | }; 235 | Closure.prototype.CALLMT = function (a, d, mt) { 236 | var ret; 237 | if (d > 0) { // extras 238 | ret = runtime[mt](this.slots[a], this.slots.slice(a + 1, a + d + 1).concat(this.multires)); 239 | } 240 | else { 241 | ret = runtime[mt](this.slots[a], this.multires); 242 | } 243 | return ret; 244 | }; 245 | Closure.prototype.CALLT = function (a, d, mt) { 246 | return runtime[mt](this.slots[a], this.slots.slice(a + 1, a + d)); 247 | }; 248 | Closure.prototype.ITERC = function (a, b, c, mt) { 249 | this.slots[a] = this.slots[a - 3]; 250 | this.slots[a + 1] = this.slots[a - 2]; 251 | this.slots[a + 2] = this.slots[a - 1]; 252 | this.call(a, b, this.slots[a], [this.slots[a - 2], this.slots[a - 1]]); 253 | }; 254 | Closure.prototype.ITERN = function (a, b, c, mt) { 255 | this.slots[a] = this.slots[a - 3]; 256 | this.slots[a + 1] = this.slots[a - 2]; 257 | this.slots[a + 2] = this.slots[a - 1]; 258 | this.call(a, b, this.env.next, [this.slots[a - 2], this.slots[a - 1]]); 259 | }; 260 | Closure.prototype.VARG = function (a, b, c) { 261 | if (b === 0) { 262 | this.multires = this.vararg; 263 | } 264 | else { 265 | for (var i = 0; i < b - 1; i++) { 266 | this.slots[a + i] = this.args[i]; 267 | } 268 | } 269 | }; 270 | Closure.prototype.ISNEXT = function (a, d) { 271 | if (this.slots[a - 3] === this.env.next && runtime.type(this.slots[a - 2]) === "table" && this.slots[a - 1] === null) { 272 | this.pc += d; 273 | } 274 | else { 275 | throw new Error("Implement ISNEXT failure"); 276 | } 277 | }; 278 | Closure.prototype.RETM = function (a, d) { 279 | var ret; 280 | if (d > 0) { 281 | ret = this.slots.slice(a, a + d + 1).concat(this.multires); 282 | } 283 | else { 284 | ret = this.multires; 285 | } 286 | return ret; 287 | }; 288 | Closure.prototype.RET = function (a, d) { 289 | return this.slots.slice(a, a + d); 290 | }; 291 | Closure.prototype.RET0 = function (a, d) { 292 | return []; 293 | }; 294 | Closure.prototype.RET1 = function (a, d) { 295 | return [this.slots[a]]; 296 | }; 297 | Closure.prototype.FORI = function (a, d) { 298 | this.slots[a + 3] = this.slots[a]; 299 | this.cmp = this.slots[a] < this.slots[a + 1]; 300 | }; 301 | Closure.prototype.FORL = function (a, d) { 302 | this.slots[a + 3] += this.slots[a + 2]; 303 | if (this.cmp ? (this.slots[a + 3] <= this.slots[a + 1]) : (this.slots[a + 3] >= this.slots[a + 1])) { 304 | this.pc += d; 305 | } 306 | else { 307 | this.cmp = undefined; 308 | } 309 | }; 310 | Closure.prototype.ITERL = function (a, d) { 311 | if (!(this.slots[a] === null || this.slots[a] === undefined)) { 312 | this.slots[a - 1] = this.slots[a]; 313 | this.pc += d; 314 | } 315 | }; 316 | Closure.prototype.LOOP = function (a, d) {}; 317 | Closure.prototype.JMP = function (a, d) { 318 | this.pc += d; 319 | }; 320 | Closure.prototype.FUNCF = function (a) { 321 | throw new Error("TODO: Implement FUNCF"); 322 | }; 323 | Closure.prototype.FUNCV = function (a) { 324 | throw new Error("TODO: Implement FUNCV"); 325 | }; 326 | Closure.prototype.FUNCC = function (a) { 327 | throw new Error("TODO: Implement FUNCC"); 328 | }; 329 | Closure.prototype.FUNCCW = function (a) { 330 | throw new Error("TODO: Implement FUNCCW"); 331 | }; 332 | 333 | return (proto, parent, env) => { 334 | const c = new Closure(proto[parent], env); 335 | return c.toFunction(window.brozula.globals); 336 | }; 337 | 338 | }); 339 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brozula", 3 | "version": "0.0.2", 4 | "description": "Lua VM for running luajit bytecode in JavaScript", 5 | "main": "brozula.js", 6 | "bin": { 7 | "brozula": "cli.js" 8 | }, 9 | "directories": { 10 | "test": "tests" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/creationix/brozula.git" 18 | }, 19 | "keywords": [ 20 | "lua", 21 | "vm", 22 | "bytecode" 23 | ], 24 | "author": "Tim Caswell", 25 | "license": "MIT", 26 | "readmeFilename": "README.md", 27 | "dependencies": { 28 | "nopt": "~2.0.0", 29 | "send": "~0.1.0", 30 | "uglify-js": "~2.2.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /parser.js: -------------------------------------------------------------------------------- 1 | ( // Module boilerplate to support browser globals, node.js and AMD. 2 | (typeof module !== "undefined" && function (m) { module.exports = m(); }) || 3 | (typeof define === "function" && function (m) { define(m); }) || 4 | (function (m) { window.brozula = window.brozula || {}; window.brozula.parse = m(); }) 5 | )(function () { 6 | "use strict"; 7 | 8 | // For mapping enum integer values to opcode names 9 | var opcodes = [ 10 | "ISLT", "ISGE", "ISLE", "ISGT", "ISEQV", "ISNEV", "ISEQS", "ISNES", "ISEQN", 11 | "ISNEN", "ISEQP", "ISNEP", "ISTC", "ISFC", "IST", "ISF", "MOV", "NOT", "UNM", 12 | "LEN", "ADDVN", "SUBVN", "MULVN", "DIVVN", "MODVN", "ADDNV", "SUBNV", "MULNV", 13 | "DIVNV", "MODNV", "ADDVV", "SUBVV", "MULVV", "DIVVV", "MODVV", "POW", "CAT", 14 | "KSTR", "KCDATA", "KSHORT", "KNUM", "KPRI", "KNIL", "UGET", "USETV", "USETS", 15 | "USETN", "USETP", "UCLO", "FNEW", "TNEW", "TDUP", "GGET", "GSET", "TGETV", 16 | "TGETS", "TGETB", "TSETV", "TSETS", "TSETB", "TSETM", "CALLM", "CALL", 17 | "CALLMT", "CALLT", "ITERC", "ITERN", "VARG", "ISNEXT", "RETM", "RET", "RET0", 18 | "RET1", "FORI", "JFORI", "FORL", "IFORL", "JFORL", "ITERL", "IITERL", 19 | "JITERL", "LOOP", "ILOOP", "JLOOP", "JMP", "FUNCF", "IFUNCF", "JFUNCF", 20 | "FUNCV", "IFUNCV", "JFUNCV", "FUNCC", "FUNCCW" 21 | ]; 22 | 23 | // For mapping opcode names to parse instructions 24 | var bcdef = { 25 | ISLT: {ma: "var", md: "var", mt: "lt"}, 26 | ISGE: {ma: "var", md: "var", mt: "lt"}, 27 | ISLE: {ma: "var", md: "var", mt: "le"}, 28 | ISGT: {ma: "var", md: "var", mt: "le"}, 29 | ISEQV: {ma: "var", md: "var", mt: "eq"}, 30 | ISNEV: {ma: "var", md: "var", mt: "eq"}, 31 | ISEQS: {ma: "var", md: "str", mt: "eq"}, 32 | ISNES: {ma: "var", md: "str", mt: "eq"}, 33 | ISEQN: {ma: "var", md: "num", mt: "eq"}, 34 | ISNEN: {ma: "var", md: "num", mt: "eq"}, 35 | ISEQP: {ma: "var", md: "pri", mt: "eq"}, 36 | ISNEP: {ma: "var", md: "pri", mt: "eq"}, 37 | ISTC: {ma: "dst", md: "var"}, 38 | ISFC: {ma: "dst", md: "var"}, 39 | IST: {md: "var"}, 40 | ISF: {md: "var"}, 41 | MOV: {ma: "dst", md: "var"}, 42 | NOT: {ma: "dst", md: "var"}, 43 | UNM: {ma: "dst", md: "var", mt: "unm"}, 44 | LEN: {ma: "dst", md: "var", mt: "len"}, 45 | ADDVN: {ma: "dst", mb: "var", mc: "num", mt: "add"}, 46 | SUBVN: {ma: "dst", mb: "var", mc: "num", mt: "sub"}, 47 | MULVN: {ma: "dst", mb: "var", mc: "num", mt: "mul"}, 48 | DIVVN: {ma: "dst", mb: "var", mc: "num", mt: "div"}, 49 | MODVN: {ma: "dst", mb: "var", mc: "num", mt: "mod"}, 50 | ADDNV: {ma: "dst", mb: "var", mc: "num", mt: "add"}, 51 | SUBNV: {ma: "dst", mb: "var", mc: "num", mt: "sub"}, 52 | MULNV: {ma: "dst", mb: "var", mc: "num", mt: "mul"}, 53 | DIVNV: {ma: "dst", mb: "var", mc: "num", mt: "div"}, 54 | MODNV: {ma: "dst", mb: "var", mc: "num", mt: "mod"}, 55 | ADDVV: {ma: "dst", mb: "var", mc: "var", mt: "add"}, 56 | SUBVV: {ma: "dst", mb: "var", mc: "var", mt: "sub"}, 57 | MULVV: {ma: "dst", mb: "var", mc: "var", mt: "mul"}, 58 | DIVVV: {ma: "dst", mb: "var", mc: "var", mt: "div"}, 59 | MODVV: {ma: "dst", mb: "var", mc: "var", mt: "mod"}, 60 | POW: {ma: "dst", mb: "var", mc: "var", mt: "pow"}, 61 | CAT: {ma: "dst", mb: "rbase", mc: "rbase", mt: "concat"}, 62 | KSTR: {ma: "dst", md: "str"}, 63 | KCDATA: {ma: "dst", md: "cdata"}, 64 | KSHORT: {ma: "dst", md: "lits"}, 65 | KNUM: {ma: "dst", md: "num"}, 66 | KPRI: {ma: "dst", md: "pri"}, 67 | KNIL: {ma: "base", md: "base"}, 68 | UGET: {ma: "dst", md: "uv"}, 69 | USETV: {ma: "uv", md: "var"}, 70 | USETS: {ma: "uv", md: "str"}, 71 | USETN: {ma: "uv", md: "num"}, 72 | USETP: {ma: "uv", md: "pri"}, 73 | UCLO: {ma: "rbase", md: "jump"}, 74 | FNEW: {ma: "dst", md: "func", mt: "gc"}, 75 | TNEW: {ma: "dst", md: "lit", mt: "gc"}, 76 | TDUP: {ma: "dst", md: "tab", mt: "gc"}, 77 | GGET: {ma: "dst", md: "str", mt: "index"}, 78 | GSET: {ma: "var", md: "str", mt: "newindex"}, 79 | TGETV: {ma: "dst", mb: "var", mc: "var", mt: "index"}, 80 | TGETS: {ma: "dst", mb: "var", mc: "str", mt: "index"}, 81 | TGETB: {ma: "dst", mb: "var", mc: "lit", mt: "index"}, 82 | TSETV: {ma: "var", mb: "var", mc: "var", mt: "newindex"}, 83 | TSETS: {ma: "var", mb: "var", mc: "str", mt: "newindex"}, 84 | TSETB: {ma: "var", mb: "var", mc: "lit", mt: "newindex"}, 85 | TSETM: {ma: "base", md: "num", mt: "newindex"}, 86 | CALLM: {ma: "base", mb: "lit", mc: "lit", mt: "call"}, 87 | CALL: {ma: "base", mb: "lit", mc: "lit", mt: "call"}, 88 | CALLMT: {ma: "base", md: "lit", mt: "call"}, 89 | CALLT: {ma: "base", md: "lit", mt: "call"}, 90 | ITERC: {ma: "base", mb: "lit", mc: "lit", mt: "call"}, 91 | ITERN: {ma: "base", mb: "lit", mc: "lit", mt: "call"}, 92 | VARG: {ma: "base", mb: "lit", mc: "lit"}, 93 | ISNEXT: {ma: "base", md: "jump"}, 94 | RETM: {ma: "base", md: "lit"}, 95 | RET: {ma: "rbase", md: "lit"}, 96 | RET0: {ma: "rbase", md: "lit"}, 97 | RET1: {ma: "rbase", md: "lit"}, 98 | FORI: {ma: "base", md: "jump"}, 99 | JFORI: {ma: "base", md: "jump"}, 100 | FORL: {ma: "base", md: "jump"}, 101 | IFORL: {ma: "base", md: "jump"}, 102 | JFORL: {ma: "base", md: "lit"}, 103 | ITERL: {ma: "base", md: "jump"}, 104 | IITERL: {ma: "base", md: "jump"}, 105 | JITERL: {ma: "base", md: "lit"}, 106 | LOOP: {ma: "rbase", md: "jump"}, 107 | ILOOP: {ma: "rbase", md: "jump"}, 108 | JLOOP: {ma: "rbase", md: "lit"}, 109 | JMP: {ma: "rbase", md: "jump"}, 110 | FUNCF: {ma: "rbase"}, 111 | IFUNCF: {ma: "rbase"}, 112 | JFUNCF: {ma: "rbase", md: "lit"}, 113 | FUNCV: {ma: "rbase"}, 114 | IFUNCV: {ma: "rbase"}, 115 | JFUNCV: {ma: "rbase", md: "lit"}, 116 | FUNCC: {ma: "rbase"}, 117 | FUNCCW: {ma: "rbase"} 118 | }; 119 | 120 | var kgctypes = ["CHILD", "TAB", "I64", "U64", "COMPLEX", "STR"]; 121 | var ktabtypes = ["NIL", "FALSE", "TRUE", "INT", "NUM", "STR"]; 122 | var kgcdecs = { 123 | TAB: function (parser) { 124 | var narray = parser.U(); 125 | var nhash = parser.U(); 126 | if (narray) { 127 | if (nhash) { 128 | throw new Error("TODO: implement mixed tables"); 129 | } 130 | var tab = new Array(narray); 131 | for (var i = 0; i < narray; i++) { 132 | tab[i] = readktabk(parser); 133 | } 134 | return tab; 135 | } 136 | if (nhash) { 137 | var tab = {}; 138 | for (var i = 0; i < nhash; i++) { 139 | var key = readktabk(parser); 140 | if (typeof key !== "string") throw new Error("TODO: Implement non-string keys"); 141 | tab[key] = readktabk(parser); 142 | } 143 | return tab; 144 | } 145 | return {}; 146 | }, 147 | I64: function (parser) { 148 | throw new Error("TODO: Implement I64 kgc decoder"); 149 | }, 150 | U64: function (parser) { 151 | throw new Error("TODO: Implement U64 kgc decoder"); 152 | }, 153 | COMPLEX: function (parser) { 154 | throw new Error("TODO: Implement COMPLEX kgc decoder"); 155 | }, 156 | STR: function (parser, len) { 157 | len -= 5; // Offset for STR enum 158 | var value = parser.buffer.slice(parser.index, parser.index + len).toString(); 159 | parser.index += len; 160 | return value; 161 | } 162 | }; 163 | var ktabdecs = { 164 | NIL: function (parser) { 165 | return null; 166 | }, 167 | FALSE: function (parser) { 168 | return false; 169 | }, 170 | TRUE: function (parser) { 171 | return true; 172 | }, 173 | INT: function (parser) { 174 | return parser.U(); 175 | }, 176 | NUM: readknum, 177 | STR: kgcdecs.STR 178 | }; 179 | // Opcodes that consume a jump 180 | var conditionals = {ISLT: true, ISGE: true, ISLE: true, ISGT: true, ISEQV: true, 181 | ISNEV: true, ISEQS: true, ISNES: true, ISEQN: true, ISNEN: true, ISEQP: true, 182 | ISNEP: true, ISTC: true, ISFC: true, IST: true, ISF: true}; 183 | 184 | // Used to consume bytes from a bytecode stream 185 | function Parser(buffer) { 186 | this.buffer = buffer; 187 | this.index = 0; 188 | this.mark = 0; 189 | } 190 | Parser.prototype.B = function () { 191 | return this.buffer[this.index++]; 192 | }; 193 | // Consume 16 bit value from stream and move pointer 194 | Parser.prototype.H = function () { 195 | var value = this.buffer.readUInt16LE(this.index); 196 | this.index += 2; 197 | return value; 198 | }; 199 | // Consume 32 bit value from stream and move pointer 200 | Parser.prototype.W = function () { 201 | var value = this.buffer.readUInt32LE(this.index); 202 | this.index += 4; 203 | return value; 204 | }; 205 | // Decode ULEB128 from the stream 206 | // http://en.wikipedia.org/wiki/LEB128 207 | Parser.prototype.U = function () { 208 | var value = 0; 209 | var shift = 0; 210 | var byte; 211 | do { 212 | byte = this.buffer[this.index++]; 213 | value |= (byte & 0x7f) << shift; 214 | shift += 7; 215 | } while (byte >= 0x80); 216 | return value >>> 0; 217 | }; 218 | 219 | function parse(buffer) { 220 | var parser = new Parser(buffer); 221 | 222 | // header = ESC 'L' 'J' versionB flagsU [namelenU nameB*] 223 | if (parser.B() !== 0x1b) throw new Error("Expected ESC in first byte"); 224 | if (parser.B() !== 0x4c) throw new Error("Expected L in second byte"); 225 | if (parser.B() !== 0x4a) throw new Error("Expected J in third byte"); 226 | if (parser.B() !== 1) throw new Error("Only version 1 supported"); 227 | var flags = parser.U(); 228 | if (flags & 1) throw new Error("Big endian encoding not supported yet"); 229 | if (!(flags & 2)) throw new Error("Non stripped bytecode not supported yet"); 230 | if (flags & 4) throw new Error("FFI bytecode not supported"); 231 | 232 | // proto+ 233 | var protoBuffers = []; 234 | do { 235 | var len = parser.U(); 236 | protoBuffers.push(buffer.slice(parser.index, parser.index + len)); 237 | parser.index += len; 238 | } while (buffer[parser.index]); 239 | 240 | // 0U and EOF 241 | if (parser.U() !== 0) throw new Error("Missing 0U at end of file"); 242 | if (parser.index < buffer.length) throw new Error((buffer.length - parser.index) + " bytes leftover"); 243 | 244 | var l = protoBuffers.length; 245 | var protos = new Array(l); 246 | for (var i = 0; i < l; i++) { 247 | readproto(protoBuffers[i], protos, i); 248 | } 249 | 250 | return protos; 251 | 252 | } 253 | 254 | function readproto(buffer, protos, protoIndex) { 255 | var parser = new Parser(buffer); 256 | 257 | // flagsB numparamsB framesizeB numuvB numkgcU numknU numbcU [debuglenU [firstlineU numlineU]] 258 | var flags = parser.B(); 259 | var numparams = parser.B(); 260 | var framesize = parser.B(); 261 | var numuv = parser.B(); 262 | var numkgc = parser.U(); 263 | var numkn = parser.U(); 264 | var numbc = parser.U(); 265 | 266 | var bcins = new Array(numbc); 267 | var uvdata = new Array(numuv); 268 | 269 | var proto = protos[protoIndex] = { 270 | index: protoIndex, 271 | flags: flags, 272 | numparams: numparams, 273 | framesize: framesize, 274 | bcins: bcins, 275 | uvdata: uvdata 276 | }; 277 | 278 | // bcinsW* uvdataH* kgc* knum* 279 | var i; 280 | for (i = 0; i < numbc; i++) { 281 | bcins[i] = parser.W(); 282 | } 283 | for (i = 0; i < numuv; i++) { 284 | var uv = parser.H(); 285 | uvdata[i] = { 286 | local: !!(uv & 0x8000), 287 | immutable: !!(uv & 0x4000), 288 | uv: uv & 0x3fff 289 | }; 290 | } 291 | var constants = new Array(numkgc + numkn); 292 | var childc = protoIndex; 293 | for (i = 0; i < numkgc; i++) { 294 | var kgctype = parser.U(); 295 | var type = kgctypes[kgctype] || "STR"; 296 | if (type === "CHILD") { 297 | constants[i + numkn] = protos[--childc]; 298 | } 299 | else { 300 | constants[i + numkn] = kgcdecs[type](parser, kgctype); 301 | } 302 | } 303 | for (i = 0; i < numkn; i++) { 304 | constants[i] = readknum(parser); 305 | } 306 | 307 | for (i = 0; i < numbc; i++) { 308 | bcins[i] = parseOpcode(bcins[i], i); 309 | } 310 | 311 | // Make sure we consumed all the bytes properly 312 | if (parser.index !== buffer.length) throw new Error((buffer.length - parser.index) + " bytes leftover"); 313 | 314 | function parseArg(type, val, i) { 315 | switch (type) { 316 | case "lit": return val >>> 0; 317 | case "lits": return val & 0x8000 ? val - 0x10000 : val; 318 | case "pri": return val === 0 ? null : val === 1 ? false : true; 319 | case "num": return constants[val]; 320 | case "str": case "tab": case "func": case "cdata": 321 | return constants[constants.length - val - 1]; 322 | case "jump": return val - 0x8000; 323 | default: return val; 324 | } 325 | } 326 | 327 | function parseOpcode(word, i) { 328 | var opcode = opcodes[word & 0xff]; 329 | var def = bcdef[opcode]; 330 | var args = []; 331 | var op = { 332 | op: opcode, 333 | args: args 334 | }; 335 | if (def.ma) { 336 | args.push(parseArg(def.ma, (word >>> 8) & 0xff, i)); 337 | } 338 | if (def.mb) { 339 | args.push(parseArg(def.mb, word >>> 24, i)); 340 | } 341 | if (def.mc) { 342 | args.push(parseArg(def.mc, (word >>> 16) & 0xff, i)); 343 | } 344 | if (def.md) { 345 | args.push(parseArg(def.md, word >>> 16, i)); 346 | } 347 | if (def.mt) { 348 | args.push(def.mt); 349 | } 350 | return op; 351 | } 352 | 353 | } 354 | 355 | function readknum(parser) { 356 | var isnum = parser.buffer[parser.index] & 1; 357 | var lo = parser.U() >> 1; 358 | if (isnum) { 359 | var buf = new Buffer(8); 360 | buf.writeUInt32LE(lo >>> 0, 0); 361 | buf.writeUInt32LE(parser.U(), 4); 362 | var num = buf.readDoubleLE(0); 363 | return num; 364 | } 365 | return lo; 366 | } 367 | 368 | // Read a constant table key or value 369 | function readktabk(parser) { 370 | var ktabtype = parser.U(); 371 | return ktabdecs[ktabtypes[ktabtype] || "STR"](parser, ktabtype); 372 | } 373 | 374 | 375 | return parse; 376 | 377 | }); 378 | -------------------------------------------------------------------------------- /runtime.js: -------------------------------------------------------------------------------- 1 | ( // Module boilerplate to support browser globals, node.js and AMD. 2 | (typeof module !== "undefined" && function (m) { module.exports = m(); }) || 3 | (typeof define === "function" && function (m) { define(m); }) || 4 | (function (m) { window.brozula = window.brozula || {}; window.brozula.runtime = m(); }) 5 | )(function () { 6 | 7 | function Table() { 8 | this.array = []; 9 | this.keys = []; 10 | this.values = []; 11 | this.hash = {}; 12 | } 13 | Table.prototype.remove = function (key) { 14 | var t = typeof key; 15 | if (t === "number") { 16 | delete this.array[key]; 17 | return; 18 | } 19 | if (t === "string") { 20 | delete this.hash[key]; 21 | } 22 | var index = this.keys.indexOf(key); 23 | if (index >= 0) { 24 | this.values.splice(index, 1); 25 | this.keys.splice(index, 1); 26 | } 27 | }; 28 | Table.prototype.haskey = function (key) { 29 | var t = typeof key; 30 | if (t === "number" && key >= 0 && (key >>> 0 === key)) return this.array[key] !== undefined; 31 | if (t === "string") return this.hash[key] !== undefined; 32 | return this.keys.indexOf(key) >= 0; 33 | }; 34 | Table.prototype.set = function(key, value) { 35 | if (value === undefined || value === null) { 36 | return this.remove(key); 37 | } 38 | var t = typeof key; 39 | if (t === "number" && key >= 0 && (key >>> 0 === key)) return this.array[key] = value; 40 | if (t === "string") return this.hash[key] = value; 41 | var index = this.keys.indexOf(key); 42 | if (index >= 0) { 43 | this.values[index] = value; 44 | } 45 | else { 46 | this.keys.push(key); 47 | this.values.push(value); 48 | } 49 | }; 50 | Table.prototype.get = function (key) { 51 | var t = typeof key; 52 | var v; 53 | if (t === "number") v = this.array[key]; 54 | else if (t === "string") v = this.hash[key]; 55 | else { 56 | var index = this.keys.indexOf(key); 57 | if (index >= 0) v = this.values[index]; 58 | } 59 | return v === undefined ? null : v; 60 | }; 61 | Table.prototype.inext = function (key) { 62 | if (typeof key === "number") { 63 | for (var i = key + 1, l = this.array.length; i < l; i++) { 64 | var v = this.array[i]; 65 | if (v !== undefined) return [i, v]; 66 | } 67 | } 68 | return []; 69 | }; 70 | Table.prototype.next = function (key) { 71 | // iterate over numbers first 72 | var isNil = key === null || key === undefined; 73 | var v; 74 | if (isNil || (typeof key === "number" && (key >>> 0) === key && key >= 0)) { 75 | if (isNil) key = -1; 76 | for (var i = key + 1, l = this.array.length; i < l; i++) { 77 | v = this.array[i]; 78 | if (v !== undefined) return [i, v]; 79 | } 80 | v = this.keys[0]; 81 | if (v !== undefined) { 82 | return [v, this.values[0]]; 83 | } 84 | key = null; 85 | } 86 | var index = this.keys.indexOf(key) + 1; 87 | if (index) { 88 | if (index < this.keys.length) { 89 | return [this.keys[index], this.values[index]]; 90 | } 91 | key = null; 92 | } 93 | var keys = Object.keys(this.hash); 94 | if (keys.length && key === null) { 95 | key = keys[0]; 96 | return [key, this.hash[key]]; 97 | } 98 | else { 99 | index = keys.indexOf(key) + 1; 100 | if (index) { 101 | if (index < keys.length) { 102 | key = keys[index]; 103 | return [key, this.hash[key]]; 104 | } 105 | } 106 | } 107 | return []; 108 | }; 109 | Table.prototype.len = function () { 110 | return this.array.length - 1; 111 | }; 112 | Table.clone = function (tab) { 113 | var clone = new Table(); 114 | if (Array.isArray(tab)) { 115 | clone.array = tab; 116 | return clone; 117 | } 118 | if (!(tab instanceof Table)) { 119 | clone.hash = tab; 120 | return clone; 121 | } 122 | throw new Error("TODO: Implement Table.clone"); 123 | }; 124 | 125 | var patternClasses = { 126 | "%a": "A-Za-z", "%A": "^A-Za-z", "%p": "\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e", 127 | "%P": "^\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e", "%s": "\x09-\x0d\x20", 128 | "%S": "^\x09-\x0d\x20", "%u": "A-Z", "%U": "^A-Z", "%d": "0-9", "%D": "^0-9", 129 | "%w": "0-9A-Za-z", "%W": "^0-9A-Za-z", "%x": "0-9A-Fa-f", "%X": "^0-9A-Fa-f", 130 | "%z": "\x00", "%Z": "^\x00", "%l": "a-z", "%L": "^a-z", "%c": "\x00-\x1f\x7f", 131 | "%C": "^\x00-\x1f\x7f" 132 | }; 133 | 134 | function patternToRegExp(pattern, flags) { 135 | pattern = pattern.replace(/%./g, function (match) { 136 | var cls = patternClasses[match]; 137 | if (cls) return cls; 138 | return match.substr(1); 139 | }).replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"); 140 | return new RegExp(pattern, flags); 141 | } 142 | 143 | var string = { 144 | byte: function (s,a,b) { 145 | a = Math.max( ((typeof a == "undefined")? 1 : a) , 1)-1; 146 | b = Math.min( ((typeof b == "undefined")? 1 : b) , s.length)-1; 147 | var bytes = []; 148 | for ( var i=a ; i <= b ; i++ ) { 149 | bytes.push ( s.charCodeAt ( i ) ); 150 | } 151 | return bytes; 152 | }, 153 | char: function () { 154 | return [ String.fromCharCode.apply ( null , arguments ) ]; 155 | }, 156 | dump: function () { 157 | throw new Error("TODO: implement string.dump"); 158 | }, 159 | find: function (s, pattern, init, plain) { 160 | if (init === null || init === undefined) init = 1; 161 | if (plain) { 162 | var start = s.indexOf(pattern, init - 1); 163 | if (start >= 0) { 164 | return [start + 1, start + pattern.length]; 165 | } 166 | return [null]; 167 | } 168 | if (init > 1) s = s.substr(init - 1); 169 | var match = s.match(patternToRegExp(pattern)); 170 | if (match) { 171 | return [match.index + init, match.index + init - 1 + match[0].length]; 172 | } 173 | return [null]; 174 | }, 175 | format: function () { 176 | throw new Error("TODO: implement string.format"); 177 | }, 178 | gfind: function () { 179 | throw new Error("TODO: implement string.gfind"); 180 | }, 181 | gmatch: function (s, pattern) { 182 | var regexp = patternToRegExp(pattern, "g"); 183 | return function () { 184 | var m = regexp.exec(s); 185 | if (!m) return []; 186 | if (m.length > 1) { 187 | return Array.prototype.slice.call(m, 1); 188 | } 189 | return [m[0]]; 190 | }; 191 | }, 192 | gsub: function (s, pattern, repl, n) { 193 | // TODO: Implement everything 194 | var regexp = patternToRegExp(pattern, "g"); 195 | s = s.replace(regexp, repl); 196 | return [s]; 197 | }, 198 | len: function () { 199 | throw new Error("TODO: implement string.len"); 200 | }, 201 | lower: function () { 202 | throw new Error("TODO: implement string.lower"); 203 | }, 204 | match: function (s, pattern, init) { 205 | if (init) throw new Error("TODO: Implement match init offset"); 206 | var regexp = patternToRegExp(pattern); 207 | var m = s.match(regexp); 208 | if (!m) return []; 209 | if (m.length > 1) { 210 | return Array.prototype.slice.call(m, 1); 211 | } 212 | return [m[0]]; 213 | }, 214 | rep: function (s, n) { 215 | var str = ""; 216 | for (var i = 0; i < n; i++) { 217 | str += s; 218 | } 219 | return str; 220 | }, 221 | reverse: function () { 222 | throw new Error("TODO: implement string.reverse"); 223 | }, 224 | sub: function (s, i, j) { 225 | var start, length; 226 | if (i < 0) i = s.length - i; 227 | start = i - 1; 228 | if (typeof j === "number") { 229 | if (j < 0) j = s.length - j; 230 | length = j - i + 1; 231 | } 232 | return [s.substr(start, length)]; 233 | }, 234 | upper: function () { 235 | throw new Error("TODO: implement string.upper"); 236 | } 237 | }; 238 | 239 | var stringmeta = {__index: string}; 240 | 241 | // Metatmethods 242 | function lt(op1, op2) { 243 | var t1 = type(op1), t2 = type(op2); 244 | if (t1 === t2) { 245 | if (t1 === "number" || t1 === "string") return op1 < op2; 246 | var h = getcomphandler(op1, op2, "__lt"); 247 | if (h) return h(op1, op2)[0]; 248 | } 249 | throw "attempt to compare " + t1 + " with " + t2; 250 | } 251 | function le(op1, op2) { 252 | var t1 = type(op1), t2 = type(op2); 253 | if (t1 === t2) { 254 | if (t1 === "number" || t1 === "string") return op1 <= op2; 255 | var h = getcomphandler(op1, op2, "__le"); 256 | if (h) return h(op1, op2)[0]; 257 | h = getcomphandler(op1, op2, "__lt"); 258 | if (h) return !(h(op2, op1)[0]); 259 | } 260 | throw "attempt to compare " + t1 + " with " + t2; 261 | } 262 | function eq(op1, op2) { 263 | var t1 = type(op1), t2 = type(op2); 264 | if (op1 === op2) return true; 265 | if (t1 !== t2) return false; 266 | var h = getcomphandler(op1, op2, "__eq"); 267 | if (h) return h(op1, op2)[0]; 268 | return false; 269 | } 270 | function unm(op) { 271 | var o = tonumber(op); 272 | if (typeof o === "number") return -o; 273 | var h = getmetamethod(op, "__unm"); 274 | if (h) return h(op)[0]; 275 | throw "attempt to perform arithmetic on a " + type(op) + " value"; 276 | } 277 | function len(op) { 278 | var t = type(op); 279 | if (t === "string") { 280 | return op.length; 281 | } 282 | var h = getmetamethod(op, "__len"); 283 | if (h) { 284 | return h(op)[0]; 285 | } 286 | if (t === "table") { 287 | if (op instanceof Table) return op.len(); 288 | if (Array.isArray(op)) return op.length - 1; 289 | return 0; 290 | } 291 | throw new Error("attempt to get length of " + t + " value"); 292 | } 293 | 294 | function add(op1, op2) { 295 | var o1 = tonumber(op1), o2 = tonumber(op2); 296 | if (typeof o1 === "number" && typeof o2 === "number") { 297 | return o1 + o2; 298 | } 299 | var h = getbinhandler(op1, op2, "__add"); 300 | if (h) { 301 | return h(op1, op2)[0]; 302 | } 303 | throw "attempt to perform arithmetic on a " + type(op1) + " value"; 304 | } 305 | function sub(op1, op2) { 306 | var o1 = tonumber(op1), o2 = tonumber(op2); 307 | if (typeof o1 === "number" && typeof o2 === "number") { 308 | return o1 - o2; 309 | } 310 | var h = getbinhandler(op1, op2, "__sub"); 311 | if (h) return h(op1, op2)[0]; 312 | throw "attempt to perform arithmetic on a " + type(op1) + " value"; 313 | } 314 | function mul(op1, op2) { 315 | var o1 = tonumber(op1), o2 = tonumber(op2); 316 | if (typeof o1 === "number" && typeof o2 === "number") { 317 | return o1 * o2; 318 | } 319 | var h = getbinhandler(op1, op2, "__mul"); 320 | if (h) return h(op1, op2)[0]; 321 | throw "attempt to perform arithmetic on a " + type(op1) + " value"; 322 | } 323 | function div(op1, op2) { 324 | var o1 = tonumber(op1), o2 = tonumber(op2); 325 | if (typeof o1 === "number" && typeof o2 === "number") { 326 | return o1 / o2; 327 | } 328 | var h = getbinhandler(op1, op2, "__div"); 329 | if (h) return h(op1, op2)[0]; 330 | throw "attempt to perform arithmetic on a " + type(op1) + " value"; 331 | } 332 | function mod(op1, op2) { 333 | var o1 = tonumber(op1), o2 = tonumber(op2); 334 | if (typeof o1 === "number" && typeof o2 === "number") { 335 | return o1 % o2; 336 | } 337 | var h = getbinhandler(op1, op2, "__mod"); 338 | if (h) return h(op1, op2)[0]; 339 | throw "attempt to perform arithmetic on a " + type(op1) + " value"; 340 | } 341 | function pow(op1, op2) { 342 | var o1 = tonumber(op1), o2 = tonumber(op2); 343 | if (typeof o1 === "number" && typeof o2 === "number") { 344 | return Math.pow(o1, o2); 345 | } 346 | var h = getbinhandler(op1, op2, "__pow"); 347 | if (h) return h(op1, op2)[0]; 348 | throw "attempt to perform arithmetic on a " + type(op1) + " value"; 349 | } 350 | function concat(op1, op2) { 351 | var t1 = typeof op1; 352 | var t2 = typeof op2; 353 | if ((t1 === "string" || t1 === "number") && (t2 === "string" || t2 === "number")) { 354 | return "" + op1 + op2; 355 | } 356 | var h = getbinhandler(op1, op2, "__concat"); 357 | if (h) { 358 | return h(op1, op2)[0]; 359 | } 360 | throw "attempt to concatenate a " + type(op1) + " value"; 361 | } 362 | function gc(tab) { 363 | // TODO: check metamethods 364 | } 365 | function index(table, key) { 366 | var t, h; 367 | t = type(table); 368 | if (t === "table") { 369 | var v = rawget(table, key); 370 | if (v !== null) return v; 371 | h = getmetamethod(table, "__index"); 372 | if (!h) return null; 373 | } 374 | else { 375 | h = getmetamethod(table, "__index"); 376 | if (!h) throw "attempt to index a " + t + " value"; 377 | } 378 | if (typeof h === "function") { 379 | return h(table, key)[0]; 380 | } 381 | return index(h, key); 382 | } 383 | function newindex(table, key, val) { 384 | var t, h; 385 | t = type(table); 386 | if (t === "table") { 387 | if (haskey(table, key)) { rawset(table, key, val); return; } 388 | h = getmetamethod(table, "__newindex"); 389 | if (!h) { rawset(table, key, val); return; } 390 | } 391 | else { 392 | h = getmetamethod(table, "__newindex"); 393 | if (!h) throw "attempt to index a " + t + " value"; 394 | } 395 | if (typeof h === "function") { 396 | h(table, key, val); 397 | } 398 | else { 399 | newindex(h, key, val); 400 | } 401 | } 402 | function call(func, args) { 403 | if (typeof func === "function") { 404 | return func.apply(null, args); 405 | } 406 | var h = getmetamethod(func, "__call"); 407 | if (h) { 408 | return h.apply(null, [func].concat(args)); 409 | } 410 | throw "attempt to call a " + type(func) + " value"; 411 | } 412 | 413 | // Helpers 414 | function falsy(val) { 415 | return val === false || val === null || val === undefined; 416 | } 417 | function type(val) { 418 | if (val === null || val === undefined) return "nil"; 419 | if (typeof val === "object") return "table"; 420 | return typeof val; 421 | } 422 | 423 | function rawget(tab, key) { 424 | if (tab instanceof Table) return tab.get(key); 425 | var v = tab[key]; 426 | return v === undefined ? null : v; 427 | } 428 | 429 | function haskey(tab, key) { 430 | if (tab instanceof Table) return tab.haskey(key); 431 | return tab[key] !== undefined; 432 | } 433 | 434 | function rawset(tab, key, val) { 435 | if (tab instanceof Table) return tab.set(key, val); 436 | tab[key] = val; 437 | } 438 | 439 | function setmetatable(tab, meta) { 440 | var t = type(tab); 441 | if (t !== "table") throw "table expected, got " + t; 442 | Object.defineProperty(tab, "__meta__", {value: meta}); 443 | } 444 | 445 | function getmetatable(tab) { 446 | var t = type(tab); 447 | if (t === "string") return stringmeta; 448 | if (t !== "table") return null; 449 | return tab.__meta__ || null; 450 | } 451 | 452 | var nextID = 1; 453 | function getpointer(tab) { 454 | if (typeof tab === "function" && tab.name) { 455 | return tab.name; 456 | } 457 | var id = tab.__id__; 458 | if (id === undefined) { 459 | id = (nextID++).toString(16); 460 | id = "0x" + "00000000".substr(id.length) + id; 461 | Object.defineProperty(tab, "__id__", {value: id}); 462 | } 463 | return id; 464 | } 465 | 466 | function tonumber(val, base) { 467 | var t = typeof val; 468 | if (base === undefined || base === null) base = 10; 469 | base = Math.floor(base); 470 | if (base < 2 || base > 36) throw "base out of range"; 471 | if (t === "number") return val; 472 | if (t === "string") { 473 | var num; 474 | if (base === 10) num = parseFloat(val, 10); 475 | else num = parseInt(val, base); 476 | if (!isNaN(num)) return num; 477 | } 478 | return null; 479 | } 480 | 481 | function tostring(val) { 482 | var h = getmetamethod(val, "__tostring"); 483 | if (h) return h(val)[0]; 484 | var t = type(val); 485 | if (t === "table") return "table: " + getpointer(val); 486 | if (t === "function") return "function: " + getpointer(val); 487 | if (t === "nil") return "nil"; 488 | return "" + val; 489 | } 490 | 491 | function getmetamethod(tab, event) { 492 | var meta = getmetatable(tab); 493 | if (meta) { 494 | var method = rawget(meta, event); 495 | if (method !== null) return method; 496 | } 497 | return null; 498 | } 499 | 500 | function getcomphandler(op1, op2, event) { 501 | var mm1 = getmetamethod(op1, event); 502 | var mm2 = getmetamethod(op2, event); 503 | if (mm1 === mm2) return mm1; 504 | return null; 505 | } 506 | 507 | function getbinhandler(op1, op2, event) { 508 | var h = getmetamethod(op1, event); 509 | if (h) return h; 510 | h = getmetamethod(op2, event); 511 | if (h) return h; 512 | return null; 513 | } 514 | 515 | function next(tab, key) { 516 | if (tab instanceof Table) return tab.next(key); 517 | var isNull = key === undefined || key === null; 518 | if (Array.isArray(tab)) { 519 | if (isNull) key = 0; 520 | return inext(tab, key); 521 | } 522 | var keys = Object.keys(tab); 523 | var newKey; 524 | if (isNull) { 525 | newKey = keys[0]; 526 | return [newKey, tab[newKey]]; 527 | } 528 | var index = keys.indexOf(key) + 1; 529 | if (index) { 530 | newKey = keys[index]; 531 | return [newKey, tab[newKey]]; 532 | } 533 | return []; 534 | } 535 | 536 | function inext(tab, key) { 537 | if (tab instanceof Table) return tab.inext(key); 538 | var newKey; 539 | if (Array.isArray(tab) && tab.length && typeof key === "number" && 540 | tab[newKey = key + 1] !== undefined) { 541 | return [newKey, tab[newKey]]; 542 | } 543 | return []; 544 | } 545 | 546 | 547 | return { 548 | lt: lt, le: le, eq: eq, unm: unm, len: len, add: add, sub: sub, mul: mul, 549 | div: div, mod: mod, pow: pow, concat: concat, gc: gc, index: index, 550 | newindex: newindex, call: call, falsy: falsy, type: type, 551 | setmetatable: setmetatable, getmetatable: getmetatable, rawget: rawget, 552 | rawset: rawset, tonumber: tonumber, tostring: tostring, string: string, 553 | Table: Table, next: next, inext: inext 554 | }; 555 | 556 | }); -------------------------------------------------------------------------------- /tests/add.lua: -------------------------------------------------------------------------------- 1 | a = { value = 5 }; 2 | setmetatable(a, { __add = function (a, b) 3 | --print("add", a, b) 4 | if type(a) == "table" then 5 | return a.value + b; 6 | else 7 | return a + b.value; 8 | end 9 | end }); 10 | 11 | print(5 + 5) 12 | print(a.value + 5) 13 | print(5 + a.value) 14 | print(a.value + a.value) 15 | print(a + 5) 16 | print(5 + a) 17 | -------------------------------------------------------------------------------- /tests/arithmetic.lua: -------------------------------------------------------------------------------- 1 | local _ = 0; -- To prevent constant folding 2 | assert((_ + 0) + 0 == 0, "(_ + 0) + 0 == 0") 3 | assert((_ + 0) + 1 == 1, "(_ + 0) + 1 == 1") 4 | assert((_ + 1) + 0 == 1, "(_ + 1) + 0 == 1") 5 | assert((_ + 1) + 1 == 2, "(_ + 1) + 1 == 2") 6 | 7 | assert((_ + 0) - 0 == 0, "(_ + 0) - 0 == 0") 8 | assert((_ + 0) - 1 == -1, "(_ + 0) - 1 == -1") 9 | assert((_ + 0) - 0 == 0, "(_ + 0) - 0 == 0") 10 | assert((_ + 1) - 1 == 0, "(_ + 1) - 1 == 0") 11 | 12 | assert((_ + 0) * 0 == 0, "(_ + 0) * 0 == 0") 13 | assert((_ + 0) * 1 == 0, "(_ + 0) * 1 == 0") 14 | assert((_ + 1) * 0 == 0, "(_ + 1) * 0 == 0") 15 | assert((_ + 1) * 1 == 1, "(_ + 1) * 1 == 1") 16 | 17 | assert((0 / 1) == 0, "(0 / 1) == 0") 18 | assert((1 / 1) == 1, "(1 / 1) == 1") 19 | assert((1 / 2) == 0.5, "(1 / 2) == 0.5") 20 | assert((2 / 1) == 2, "(2 / 1) == 2") 21 | 22 | -------------------------------------------------------------------------------- /tests/basic_logic.lua: -------------------------------------------------------------------------------- 1 | assert(0, "0"); 2 | assert(1 == 1, "1 == 1"); 3 | assert(1 < 2, "1 < 2"); 4 | assert(2 > 1, "2 > 1"); 5 | assert(1 <= 2, "1 <= 2"); 6 | assert(2 <= 2, "2 <= 2"); 7 | -------------------------------------------------------------------------------- /tests/bool.lua: -------------------------------------------------------------------------------- 1 | print(type(true)) 2 | print(type(false)) 3 | print(type(nil)) 4 | -------------------------------------------------------------------------------- /tests/callmeta.lua: -------------------------------------------------------------------------------- 1 | local t = setmetatable({ name="test" }, 2 | { 3 | __index=function(self, k) 4 | if k == "metaname" then return "fromindex" end 5 | end, 6 | __call=function(self, x) 7 | return self.name .. " / " 8 | .. self.metaname 9 | .. " / x:" 10 | .. x 11 | end 12 | }) 13 | assert(t(5) == 'test / fromindex / x:5', t(5)) 14 | -------------------------------------------------------------------------------- /tests/concat.lua: -------------------------------------------------------------------------------- 1 | assert(("foo".."bar") == "foobar"); 2 | -------------------------------------------------------------------------------- /tests/demo_account.lua: -------------------------------------------------------------------------------- 1 | -- account.lua 2 | -- from PiL 1, Chapter 16 3 | 4 | Account = {balance = 0, name = "base"} 5 | Account.__index = Account; 6 | 7 | function Account:new (o, name) 8 | o = o or {name=name} 9 | setmetatable(o, self) 10 | return o 11 | end 12 | 13 | function Account:deposit (v) 14 | self.balance = self.balance + v 15 | end 16 | 17 | function Account:withdraw (v) 18 | if v > self.balance then error("insufficient funds on account "..self.name) end 19 | self.balance = self.balance - v 20 | end 21 | 22 | function Account:show (title) 23 | print(title or "", self.name, self.balance) 24 | end 25 | 26 | a = Account:new(nil,"demo") 27 | a:show("after creation") 28 | a:deposit(1000.00) 29 | a:show("after deposit") 30 | a:withdraw(100.00) 31 | a:show("after withdraw") 32 | 33 | -- this would raise an error 34 | --[[ 35 | b = Account:new(nil,"DEMO") 36 | b:withdraw(100.00) 37 | --]] 38 | 39 | -------------------------------------------------------------------------------- /tests/demo_hello.lua: -------------------------------------------------------------------------------- 1 | -- hello.lua 2 | -- the first program in every language 3 | 4 | print("Hello world, from ",_VERSION,"!") 5 | 6 | -------------------------------------------------------------------------------- /tests/forloop.lua: -------------------------------------------------------------------------------- 1 | local ret = 0 2 | for i = 1, 10 do 3 | print("counting to 10...", i) 4 | ret = ret + i 5 | end 6 | assert(ret == 55, ret) 7 | 8 | ret=0 9 | for i = 1, 10,2 do 10 | print("counting to 10 by 2...", i) 11 | ret = ret + i 12 | end 13 | 14 | assert(ret == 25, ret) 15 | 16 | ret=0 17 | for i = 10, 1, -1 do 18 | print("counting down from 10...", i) 19 | ret = ret + i 20 | end 21 | assert(ret == 55, ret) 22 | print(i) 23 | 24 | function looponnvars(nvars) 25 | 26 | -- in ljs < 0.0011 this would mutate "nvars"... 27 | for i = nvars, 0, -1 do 28 | print("i,loop.nvars", i, nvars) 29 | end 30 | end 31 | 32 | function incnvars(nvars) 33 | nvars = nvars + 1 34 | end 35 | function test(a, nvars) 36 | local atstart = nvars 37 | print("test.nvars", nvars) 38 | incnvars(nvars) 39 | print("test.nvars", nvars) 40 | looponnvars(nvars) 41 | print("test.nvars", nvars) 42 | assert(atstart == nvars) 43 | end 44 | 45 | local nvars = 5 46 | print("main.nvars", nvars) 47 | test(1, nvars) 48 | print("main.nvars", nvars) 49 | assert(nvars == 5) -------------------------------------------------------------------------------- /tests/indexmeta.lua: -------------------------------------------------------------------------------- 1 | local t = setmetatable( 2 | { name="test" }, 3 | { __index=function(self, k) 4 | if k == "metaname" then return "fromindex" end 5 | if k == "tabval" then 6 | return setmetatable( 7 | {1234}, 8 | {__index={inner=true}} 9 | ) 10 | end 11 | end } 12 | ) 13 | assert(t.metaname == "fromindex", 't.metaname == "fromindex"') 14 | assert(#t.tabval == 1, #t.tabval) 15 | assert(t.tabval[1] == 1234, 't.tabval[1] == 1234') 16 | assert(t.tabval.inner == true, 't.tabval.inner == true') 17 | -------------------------------------------------------------------------------- /tests/length.lua: -------------------------------------------------------------------------------- 1 | assert(#("hello") == 5, "#('hello') == 5"); 2 | -------------------------------------------------------------------------------- /tests/loop.lua: -------------------------------------------------------------------------------- 1 | for i=1,1000 do print(i) end 2 | -------------------------------------------------------------------------------- /tests/math.lua: -------------------------------------------------------------------------------- 1 | assert ( math.abs( 5 ) , 5 ) 2 | assert ( math.abs( -5 ) , 5 ) 3 | 4 | assert ( math.cos(math.pi) == -1 ) 5 | 6 | assert ( math.huge + 1 == math.huge ) 7 | 8 | assert ( math.min ( 1 , 5 ) == 1 ) 9 | assert ( math.min ( 1 , 5 , -1 ) == -1 ) 10 | 11 | assert ( math.max ( 1 , 5 ) == 5 ) 12 | assert ( math.max ( 1 , 5 , -1 ) == 5 ) 13 | 14 | assert ( math.sin(0) == 0 ) 15 | -------------------------------------------------------------------------------- /tests/meta__eq.lua: -------------------------------------------------------------------------------- 1 | 2 | -- http://lua-users.org/wiki/MetatableEvents 3 | t1a = {} 4 | t1b = {} 5 | t2 = {} 6 | mt1 = { __eq = function( o1, o2 ) return 'whee' end } 7 | mt2 = { __eq = function( o1, o2 ) return 'whee' end } 8 | 9 | setmetatable( t1a, mt1 ) 10 | setmetatable( t1b, mt1 ) 11 | setmetatable( t2, mt2 ) 12 | 13 | print( t1a == t1b ) --> true 14 | print( t1a == t2 ) --> false 15 | 16 | assert(t1a == t1b) 17 | assert(t1a ~= t2) 18 | 19 | local fooset = false 20 | function foo (o1, o2) 21 | print( '__eq call' ) 22 | fooset = true 23 | return false 24 | end 25 | 26 | t1 = {} 27 | setmetatable( t1, {__eq = foo} ) 28 | 29 | t2 = t1 30 | print( t1 == t2 ) --> true 31 | assert(t1 == t2) 32 | assert(not fooset) 33 | -- string '__eq call' not printed (and comparison result is true, not like the return value of foo(...)), so no foo(...) call here 34 | 35 | t3 = {} 36 | setmetatable( t3, {__eq = foo} ) 37 | if t1 == t3 then assert(fooset) end --> __eq call 38 | -- foo(...) was called 39 | -------------------------------------------------------------------------------- /tests/nilglobal.lua: -------------------------------------------------------------------------------- 1 | print(hello) -------------------------------------------------------------------------------- /tests/op_close.lua: -------------------------------------------------------------------------------- 1 | local f = {}; 2 | for i=1,2 do 3 | local p; 4 | f[i] = function (set) 5 | if set then 6 | p = set; 7 | end 8 | return p; 9 | end; 10 | end 11 | 12 | assert(f[1]("foo") == "foo"); 13 | assert(f[2]("bar") == "bar"); 14 | assert(f[1]() == "foo"); 15 | assert(f[2]() == "bar"); 16 | -------------------------------------------------------------------------------- /tests/rectangle.lua: -------------------------------------------------------------------------------- 1 | local Rectangle = {} 2 | 3 | function Rectangle:initialize(w, h) 4 | self.w = w 5 | self.h = h 6 | end 7 | 8 | function Rectangle:area() 9 | return self.w * self.h 10 | end 11 | 12 | local rectangleMeta = {__index = Rectangle} 13 | 14 | local rect = setmetatable({}, rectangleMeta) 15 | rect:initialize(3, 4) 16 | 17 | print(rect:area()) -------------------------------------------------------------------------------- /tests/table.lua: -------------------------------------------------------------------------------- 1 | local tab = {} 2 | tab[print] = true 3 | tab[false] = 0 4 | tab[1] = "Hello" 5 | tab[1.5] = "between" 6 | tab[2] = "World" 7 | tab[0] = "Hidden" 8 | tab[-1] = "Stuff" 9 | tab.name = "Tim" 10 | tab.age = 30 11 | 12 | for k, v in ipairs(tab) do 13 | print(k, v) 14 | end 15 | 16 | print() 17 | 18 | for k, v in pairs(tab) do 19 | print(k, v) 20 | end -------------------------------------------------------------------------------- /tests/tute-coro.lua: -------------------------------------------------------------------------------- 1 | local read = function ( ) 2 | return coroutine.yield ( ) 3 | end 4 | 5 | local get_blah = function ( ) 6 | print ( "PRE 1" ) 7 | print ( read ( ) ) 8 | print ( "PRE 2" ) 9 | print ( read ( ) ) 10 | print ( "PRE 3" ) 11 | print ( read ( ) ) 12 | end 13 | 14 | -- Create coroutine 15 | local get_blah_co = coroutine.create ( get_blah ) 16 | 17 | -- Basic dispatcher 18 | while coroutine.status ( get_blah_co ) == "suspended" do 19 | local ok , err_msg = coroutine.resume ( get_blah_co ) 20 | if not ok then 21 | print("AN ERROR!",err_msg) 22 | break 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /tests/tute-oo.lua: -------------------------------------------------------------------------------- 1 | local mt = { 2 | __index = function ( ob , k ) 3 | if ob.parent then 4 | return ob.parent [ k ] 5 | end 6 | end ; 7 | } 8 | 9 | local root = setmetatable ( { 10 | parent = nil 11 | } , mt ) 12 | 13 | function root:say(phrase) 14 | print ( tostring(self) .. " says " .. phrase) 15 | end 16 | 17 | function root:derive ( newname ) 18 | return setmetatable ( { 19 | parent = self ; 20 | } , mt ) 21 | end 22 | 23 | root:say ( "I am root" ) 24 | 25 | local foo = root:derive ( ) 26 | foo:say ( "I am foo" ) 27 | function foo:say ( phrase ) 28 | print ( tostring(self) .. " annouces " .. phrase) 29 | end 30 | foo:say ( "I am foo" ) 31 | root:say ( "I am root" ) 32 | -------------------------------------------------------------------------------- /widgets/app.lua: -------------------------------------------------------------------------------- 1 | local document, window, console = eval "document", eval "window", eval "console" 2 | local domBuilder = window.domBuilder 3 | local alert = eval 'alert' 4 | document.body.textContent = ""; 5 | 6 | -- Create a table to store element references 7 | local elements = {} 8 | -- Create the template as a JSON-ML structure 9 | local template = { 10 | {".profile", -- "
11 | {".left.column", --
12 | {"#date", eval 'new Date().toString()' }, --
Today's Date
13 | {"#address", "Red Lick, Texas" } 14 | }, 15 | -- native event handlers, not a string to be evaled. 16 | {".right.column", { onclick = function (evt) alert "Foo!" end}, 17 | {"#email", "tim@creationix.com" }, 18 | {"#bio", "Cool Guy" } 19 | } 20 | }, 21 | {".form", 22 | -- $inputField means this element will be available as $.inputField when the call returns. 23 | {"input$inputField"}, 24 | -- Here we're using the reference in the onclick handler 25 | {"button", {onclick = function () alert(elements.inputField.value) end}, "Click Me"} 26 | }, 27 | {"hr", { 28 | -- The css key sets .style attributes 29 | css = {width="100px",height="50px"}, 30 | -- The oninit key gets called as soon as this element is created 31 | oninit = function (hr) console.log(hr) end 32 | }}, 33 | {"p", "Inspect the source (not view source) to see how clean this dom is!"} 34 | 35 | } 36 | 37 | 38 | -- Calling the function with the template and storage hash will return the root 39 | -- node (or document fragment is there are multiple root nodes). 40 | local root = domBuilder(template, elements) 41 | document.body.appendChild(root) 42 | -------------------------------------------------------------------------------- /widgets/cat.lua: -------------------------------------------------------------------------------- 1 | print("this " .. "is " .. "a " .. "string") 2 | -------------------------------------------------------------------------------- /widgets/closure.lua: -------------------------------------------------------------------------------- 1 | local name = "tim" 2 | 3 | print(name) 4 | 5 | local function rename() 6 | name = "newname" 7 | return function () 8 | print(1, name) 9 | return function () 10 | print(2, name) 11 | return function () 12 | name = "changed again" 13 | end 14 | end 15 | end 16 | end 17 | 18 | rename()()()() 19 | 20 | print(name) 21 | 22 | -------------------------------------------------------------------------------- /widgets/dombuilder.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------- 2 | -- -- 3 | -- Lua domBuilder Library -- 4 | -- -- 5 | -- Tim Caswell -- 6 | -- -- 7 | -------------------------------------- 8 | 9 | -- Access the DOM 10 | local document, window, console = eval "document", eval "window", eval "console" 11 | local isDom = func("val", "return val instanceof HTMLElement || val instanceof Text;") 12 | -- Pre-declare our functions 13 | local domBuilder, setAttrs, setStyle 14 | 15 | function domBuilder(json, refs) 16 | 17 | local jsonType = type(json) 18 | -- Render strings as text nodes 19 | if jsonType == "string" then 20 | console.log("Found string, returning to text...", json) 21 | return document.createTextNode(json) 22 | end 23 | 24 | -- Pass through html elements and text nodes as-is 25 | if isDom(json) then 26 | console.log("Found dom, returning as-is...", json) 27 | return json 28 | end 29 | 30 | -- Stringify any other value types 31 | if not(jsonType == "table") then 32 | console.log("Found unknown, returning stringified as text...", json) 33 | return document.createTextNode(tostring(json)) 34 | end 35 | 36 | -- Empty arrays are just empty fragments. 37 | if #json == 0 then 38 | console.log("found empty array, returning fragment...", json) 39 | return document.createDocumentFragment() 40 | end 41 | 42 | console.log("json", json) 43 | 44 | local node, first; 45 | for i, part in ipairs(json) do 46 | 47 | local partType = type(part) 48 | if partType == "table" and next(part) == 1 then partType = "array" end 49 | 50 | if not node and partType == "string" then 51 | -- Create a new dom node by parsing the tagline 52 | local tag = part:match("^[^.$#]+") or "div" 53 | node = document.createElement(tag) 54 | first = true 55 | local classes = {} 56 | for class in part:gmatch("%.[^.$#]+") do 57 | node.classList.add(class:sub(2)) 58 | end 59 | local id = part:match("#[^.$#]+") 60 | if id then node.setAttribute("id", id:sub(2)) end 61 | local ref = part:match("%$[^.$#]+") 62 | if ref then refs[ref:sub(2)] = node end 63 | skip = true 64 | else 65 | if not node then 66 | node = document.createDocumentFragment() 67 | end 68 | -- Except the first item if it's an attribute object 69 | if first and partType == "table" then 70 | setAttrs(node, part) 71 | else 72 | node.appendChild(domBuilder(part, refs)) 73 | end 74 | first = false 75 | end 76 | 77 | end 78 | return node 79 | end 80 | 81 | function setAttrs(node, attrs) 82 | for key, value in pairs(attrs) do 83 | if key == "oninit" then 84 | value(node) 85 | elseif key == "css" then 86 | setStyle(node.style, value) 87 | elseif key:sub(1, 2) == "on" then 88 | node.addEventListener(key:sub(3), value, false) 89 | else 90 | node.setAttribute(key, value) 91 | end 92 | end 93 | end 94 | 95 | function setStyle(style, attrs) 96 | for key, value in pairs(attrs) do 97 | style[key] = value 98 | end 99 | end 100 | 101 | return domBuilder 102 | -------------------------------------------------------------------------------- /widgets/functions.lua: -------------------------------------------------------------------------------- 1 | 2 | local function outer(a, b) 3 | print "start outer" 4 | local function inner(a, b) 5 | print "inner" 6 | return a + b 7 | end 8 | print "end outer" 9 | return inner(a, b) 10 | end 11 | 12 | print(outer(4, 5)) 13 | -------------------------------------------------------------------------------- /widgets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Widgets Demo 5 | 6 | 7 |

See javascript console for output

8 | 9 | 10 | 11 | 12 | 13 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /widgets/jumps.lua: -------------------------------------------------------------------------------- 1 | local data = {"This", "is", "a", "stream?"} 2 | local index = 1 3 | 4 | print("before") 5 | while data[index] do 6 | print(data[index]) 7 | index = index + 1 8 | end 9 | print("after") 10 | -------------------------------------------------------------------------------- /widgets/loops.lua: -------------------------------------------------------------------------------- 1 | print "\n1 to 10" 2 | for i = 1, 10 do 3 | print(i) 4 | end 5 | 6 | print "\n10 to 1" 7 | for i = 10, 1, -1 do 8 | print(i) 9 | end 10 | local list = {"This", "is", "a", "list"} 11 | local hash = {this="is",a="hash"} 12 | 13 | print "\nlist pairs" 14 | for k, v in pairs(list) do 15 | print(k, v) 16 | end 17 | 18 | print "\nhash pairs" 19 | for k, v in pairs(hash) do 20 | print(k, v) 21 | end 22 | 23 | print "\nlist ipairs" 24 | for k, v in ipairs(list) do 25 | print(k, v) 26 | end -------------------------------------------------------------------------------- /widgets/widgets.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creationix/brozula/d3a0b315d23e17fd6d1c71c3e87ce3601949a8fc/widgets/widgets.css --------------------------------------------------------------------------------