|
| 1 | +// Utility to write bytecode for TurtleScript parser, bytecode compiler, |
| 2 | +// startup code and standard library, as a PHP file. |
| 3 | +// |
| 4 | +// Run it under `node` with the CLI in `bin/write-php-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(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 | + */ |
| 57 | + |
| 58 | + var compile_from_source = make_compile_from_source(parse, bcompile, top_level); |
| 59 | + var bc = compile_from_source(source, true/*as object*/); |
| 60 | + |
| 61 | + var php_esc = function(str) { |
| 62 | + // Escape string for PHP -- note UTF-16 to UTF-8 conversion. |
| 63 | + var re = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[^A-Za-z0-9_ !#-\/:-@\[\]^`{|}~]/g; |
| 64 | + return '"' + str.replace(re, function(c) { |
| 65 | + var code = c.charCodeAt(0); |
| 66 | + if (c.length===2) { |
| 67 | + var next = c.charCodeAt(1); |
| 68 | + code = ((code - 0xD800) * 0x400) + |
| 69 | + (next - 0xDC00) + 0x10000; |
| 70 | + } |
| 71 | + c = code.toString(16).toUpperCase(); |
| 72 | + while (c.length < 4) { c = '0' + c; } |
| 73 | + return "\\u{" + c + "}"; |
| 74 | + }) + '"'; |
| 75 | + }; |
| 76 | + |
| 77 | + var pad = function(s, width) { |
| 78 | + if (width===undefined) { width = 10; } |
| 79 | + while (s.length < width) { s += ' '; } |
| 80 | + return s; |
| 81 | + }; |
| 82 | + |
| 83 | + // ## Output module functions. |
| 84 | + console.log('<?php'); |
| 85 | + console.log('// generated by TurtleScript write-php-bytecode.js'); |
| 86 | + console.log(''); |
| 87 | + console.log('namespace Wikimedia\\PhpTurtle;'); |
| 88 | + console.log(''); |
| 89 | + //console.log('use Wikimedia\\PhpTurtle\\BytecodeFunction;'); |
| 90 | + //console.log(''); |
| 91 | + console.log('// phpcs:disable Generic.Files.LineLength.TooLong'); |
| 92 | + console.log('class Startup {'); |
| 93 | + console.log('\t/**'); |
| 94 | + console.log('\t * Populate the function and literal arrays with the precompiled'); |
| 95 | + console.log('\t * startup code, including the compiler and standard library.'); |
| 96 | + console.log('\t *'); |
| 97 | + console.log('\t * @param array &$functions'); |
| 98 | + console.log('\t * @param array &$literals'); |
| 99 | + console.log('\t */'); |
| 100 | + console.log('\tpublic function init( array &$functions, array &$literals ) {'); |
| 101 | + console.log('\t\t// functions'); |
| 102 | + bc.functions.forEach(function(f, i) { |
| 103 | + var name = f.name ? (' // '+JSON.stringify(f.name)) : ''; |
| 104 | + console.log('\t\t$functions[] = new BytecodeFunction(' + name); |
| 105 | + name = f.name ? php_esc(f.name) : 'null'; |
| 106 | + console.log('\t\t\t' + pad(name + ',') + ' // name'); |
| 107 | + console.log('\t\t\t' + pad(f.id + ',') + ' // id'); |
| 108 | + console.log('\t\t\t' + pad(f.nargs + ',') + ' // nargs'); |
| 109 | + console.log('\t\t\t' + pad(f.max_stack + ',') + ' // max_stack'); |
| 110 | + console.log('\t\t\t['); |
| 111 | + var j = 0; |
| 112 | + while (j < f.bytecode.length) { |
| 113 | + var pc = j; |
| 114 | + var bc = bytecode_table.for_num(f.bytecode[j]); |
| 115 | + var a = f.bytecode.slice(j, j+=bc.args+1); |
| 116 | + a = a.map(function(b) { |
| 117 | + if (typeof(b) !== 'number') { b = b.label; } |
| 118 | + return ''+b; |
| 119 | + }); |
| 120 | + var b = a.slice(1); |
| 121 | + a = a.join(', ') + ((j < f.bytecode.length) ? ',' : ''); |
| 122 | + b = bc.name + ( b.length ? ( '(' + b.join(',') + ')' ) : '' ); |
| 123 | + console.log('\t\t\t\t' + pad(a) + '// ' + pc + ': ' + b); |
| 124 | + } |
| 125 | + console.log('\t\t\t] );'); |
| 126 | + }); |
| 127 | + console.log(''); |
| 128 | + |
| 129 | + // ## Output module literals. |
| 130 | + console.log('\t\t// literals'); |
| 131 | + bc.literals.forEach(function(lv, i) { |
| 132 | + var str; |
| 133 | + if (typeof(lv) === "number" ) { |
| 134 | + str = 'floatval( ' + lv.toString() + ' )'; |
| 135 | + if (isNaN(lv)) { str = 'NAN'; } |
| 136 | + else if (!isFinite(lv)) { str = lv > 0 ? 'INF' : '-INF'; } |
| 137 | + } else if (typeof(lv) === "string") { |
| 138 | + str = "Environment::valFromPhpStr( " + php_esc(lv) + " )"; |
| 139 | + } else if (typeof(lv) === "boolean") { |
| 140 | + str = (lv ? "true" : "false"); |
| 141 | + } else if (lv === null) { |
| 142 | + str = "null"; |
| 143 | + } else if (lv === undefined) { |
| 144 | + str = "JsUndefined::value()"; |
| 145 | + } else { |
| 146 | + console.assert(false); |
| 147 | + } |
| 148 | + console.log('\t\t$literals[] = ' + pad(str + ';') + ' // ' + i); |
| 149 | + }); |
| 150 | + console.log('\t}'); |
| 151 | + console.log('}'); |
| 152 | +}); |
0 commit comments