|
| 1 | +// Utility to write bytecode for TurtleScript parser, bytecode compiler, |
| 2 | +// startup code and standard library, as a Lua file. |
| 3 | +// |
| 4 | +// Run it under `node` with the CLI in `bin/write-lua-bytecode.js` |
| 5 | +define(['./parse', './bcompile', './bytecode-table', './top-level', './str-escape', './tests', './stdlib', './extensions'], function(parse, bcompile, bytecode_table, top_level, str_escape, tests, stdlib) { |
| 6 | + var fake_require = |
| 7 | + "var __modules__ = {};\n"+ |
| 8 | + "define = function _define(name, deps, init_func) {\n"+ |
| 9 | + " var d = deps.map(function(m) { return __modules__[m]; });\n"+ |
| 10 | + " __modules__[name] = init_func.apply(this, d);\n"+ |
| 11 | + "};\n"; |
| 12 | + var make_compile_from_source = function(parse, bcompile, TOP_LEVEL) { |
| 13 | + var compile_from_source = function (source, as_object) { |
| 14 | + source = source || '{ return 1+2; }'; |
| 15 | + var tree = parse(source, TOP_LEVEL); |
| 16 | + var bc = bcompile(tree); |
| 17 | + var result = as_object ? bc : bc.encode(); |
| 18 | + return result; |
| 19 | + }; |
| 20 | + compile_from_source.make_repl = function() { |
| 21 | + var state = null; |
| 22 | + return function(source) { |
| 23 | + var rv = parse.repl(state, source, TOP_LEVEL); |
| 24 | + state = rv.state; |
| 25 | + return bcompile(rv.tree).encode(); |
| 26 | + }; |
| 27 | + }; |
| 28 | + return compile_from_source; |
| 29 | + }; |
| 30 | + var cfs_source = make_compile_from_source.toSource ? |
| 31 | + make_compile_from_source.toSource() : |
| 32 | + make_compile_from_source.toString(); |
| 33 | + cfs_source = 'define("compile_from_source", ["parse","bcompile","top-level"], '+ |
| 34 | + cfs_source + ');'; |
| 35 | + var top_level_source = 'define("top-level", [], function() { return ' + |
| 36 | + str_escape(top_level) + '; });'; |
| 37 | + var source = '{\n'+ |
| 38 | + stdlib.source()+'\n'+ |
| 39 | + fake_require + |
| 40 | + tests.lookup("tokenize")+"\n"+ |
| 41 | + tests.lookup("parse")+"\n"+ |
| 42 | + tests.lookup("bytecode-table")+"\n"+ |
| 43 | + tests.lookup("bcompile")+"\n"+ |
| 44 | + top_level_source+"\n"+ |
| 45 | + cfs_source + '\n' + |
| 46 | + /* |
| 47 | + "var test_nan = function() { return NaN; };\n" + |
| 48 | + "var test_inf = function() { return Infinity; };\n" + |
| 49 | + "var test_neg_inf = function() { return -Infinity; };\n" + |
| 50 | + */ |
| 51 | + "return __modules__['compile_from_source']; }\n"; |
| 52 | + |
| 53 | + /* XXX Hacks for initial tests: |
| 54 | + source = "{ console.log('Hello,', 'world!'); }"; |
| 55 | + source = "{ var fib=function(n){return (n<2)?1:fib(n-1)+fib(n-2);}; return fib(10); }"; |
| 56 | + source = '{ return 1+2; }'; |
| 57 | + source = "{ return 0+'x'; }"; |
| 58 | + */ |
| 59 | + |
| 60 | + var compile_from_source = make_compile_from_source(parse, bcompile, top_level); |
| 61 | + var bc = compile_from_source(source, true/*as object*/); |
| 62 | + |
| 63 | + var lua_esc = function(str) { |
| 64 | + // Escape string for PHP -- note UTF-16 to UTF-8 conversion. |
| 65 | + // XXX this isn't turtlescript... |
| 66 | + var re = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[^A-Za-z0-9_ !#-\/:-@\[\]^`{|}~]/g; |
| 67 | + return '"' + str.replace(re, function(c) { |
| 68 | + var code = c.charCodeAt(0); |
| 69 | + if (c.length===2) { |
| 70 | + var next = c.charCodeAt(1); |
| 71 | + code = ((code - 0xD800) * 0x400) + |
| 72 | + (next - 0xDC00) + 0x10000; |
| 73 | + } |
| 74 | + // Convert code to UTF-8 |
| 75 | + var esc = function(d) { |
| 76 | + var s = d.toString(10); |
| 77 | + while (s.length < 3) { s = '0' + s; } |
| 78 | + return "\\" + s; |
| 79 | + }; |
| 80 | + if (code < 0x80) { |
| 81 | + return esc(code); |
| 82 | + } |
| 83 | + var prefix, s='', repeat; |
| 84 | + if (code < 0x800) { |
| 85 | + prefix = esc(0xC0 | (code >>> 6)); |
| 86 | + repeat = 1; |
| 87 | + } else if (code < 0x10000) { |
| 88 | + prefix = esc(0xE0 | (code >>> 12)); |
| 89 | + repeat = 2; |
| 90 | + } else { |
| 91 | + prefix = esc(0xF0 | (code >>> 18)); |
| 92 | + repeat = 3; |
| 93 | + } |
| 94 | + while (repeat > 0) { |
| 95 | + s = esc(0x80 | (code & 0x3F)) + s; |
| 96 | + code = code >>> 6; |
| 97 | + repeat--; |
| 98 | + } |
| 99 | + return prefix + s; |
| 100 | + }) + '"'; |
| 101 | + }; |
| 102 | + |
| 103 | + var pad = function(s, width) { |
| 104 | + if (width===undefined) { width = 10; } |
| 105 | + while (s.length < width) { s += ' '; } |
| 106 | + return s; |
| 107 | + }; |
| 108 | + |
| 109 | + // ## Output module functions. |
| 110 | + console.log('-- generated by TurtleScript write-lua-bytecode.js'); |
| 111 | + console.log('local jsval = require("luaturtle.jsval")'); |
| 112 | + console.log('local ifunc = require("luaturtle.ifunc")'); |
| 113 | + console.log(''); |
| 114 | + console.log('local startup = {}'); |
| 115 | + console.log(''); |
| 116 | + console.log('-- Populate the function and literal arrays with the precompiled'); |
| 117 | + console.log('-- startup code, including the compiler and standard library.'); |
| 118 | + console.log(''); |
| 119 | + console.log('startup.functions = {'); |
| 120 | + var mkComma = function(len) { |
| 121 | + len--; |
| 122 | + return function(i) { return (i < len) ? ',' : ''; }; |
| 123 | + }; |
| 124 | + var comma = mkComma(bc.functions.length); |
| 125 | + bc.functions.forEach(function(f, i) { |
| 126 | + var name = f.name ? (' -- '+JSON.stringify(f.name)) : ''; |
| 127 | + console.log(' ifunc.Function:new{' + name); |
| 128 | + name = f.name ? lua_esc(f.name) : 'nil'; |
| 129 | + console.log(' name = ' + name + ','); |
| 130 | + console.log(' id = ' + f.id + ','); |
| 131 | + console.log(' nargs = ' + f.nargs + ','); |
| 132 | + console.log(' max_stack = ' + f.max_stack + ','); |
| 133 | + console.log(' bytecode = {'); |
| 134 | + var j = 0; |
| 135 | + while (j < f.bytecode.length) { |
| 136 | + var pc = j; |
| 137 | + var bc = bytecode_table.for_num(f.bytecode[j]); |
| 138 | + var a = f.bytecode.slice(j, j+=bc.args+1); |
| 139 | + a = a.map(function(b) { |
| 140 | + if (typeof(b) !== 'number') { b = b.label; } |
| 141 | + return ''+b; |
| 142 | + }); |
| 143 | + var b = a.slice(1); |
| 144 | + a = a.join(', ') + ((j < f.bytecode.length) ? ',' : ''); |
| 145 | + b = bc.name + ( b.length ? ( '(' + b.join(',') + ')' ) : '' ); |
| 146 | + console.log(' ' + pad(a) + '-- ' + pc + ': ' + b); |
| 147 | + } |
| 148 | + console.log(' }'); |
| 149 | + console.log(' }' + comma(i)); |
| 150 | + }); |
| 151 | + console.log('}'); |
| 152 | + |
| 153 | + // ## Output module literals. |
| 154 | + console.log('-- literals'); |
| 155 | + console.log('startup.literals = {'); |
| 156 | + comma = mkComma(bc.literals.length); |
| 157 | + bc.literals.forEach(function(lv, i) { |
| 158 | + var str; |
| 159 | + if (typeof(lv) === "number" ) { |
| 160 | + str = lv.toString(); |
| 161 | + if (isNaN(lv)) { str = '0/0'; } |
| 162 | + else if (!isFinite(lv)) { str = lv > 0 ? '1/0' : '-1/0'; } |
| 163 | + str = 'jsval.newNumber(' + str + ')'; |
| 164 | + } else if (typeof(lv) === "string") { |
| 165 | + str = lua_esc(lv); // Note: UTF8! |
| 166 | + str = 'jsval.newString(' + str + ')'; // convert to UTF16 |
| 167 | + } else if (typeof(lv) === "boolean") { |
| 168 | + str = 'jsval.' + (lv ? "True" : "False"); |
| 169 | + } else if (lv === null) { |
| 170 | + str = "jsval.Null"; |
| 171 | + } else if (lv === undefined) { |
| 172 | + str = "jsval.Undefined"; |
| 173 | + } else { |
| 174 | + console.assert(false); |
| 175 | + } |
| 176 | + console.log(' ' + pad(str + comma(i)) + ' -- ' + i); |
| 177 | + }); |
| 178 | + console.log('}'); |
| 179 | + console.log(''); |
| 180 | + |
| 181 | + console.log('return startup'); |
| 182 | +}); |
0 commit comments