Skip to content

Commit c30dc2e

Browse files
committed
Add PHP table generation
1 parent d1a8c15 commit c30dc2e

5 files changed

Lines changed: 270 additions & 0 deletions

File tree

‎bin/write-php-bytecode.js‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env node
2+
// Node CLI to write bytecode for TurtleScript parser, bytecode compiler,
3+
// startup code and standard library, as a PHP file.
4+
5+
// this is just the thunk to run it under node.
6+
var turtlescript = require('../');
7+
turtlescript.write_php_bytecode();

‎bin/write-php-ops.js‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env node
2+
// Node CLI to write out the opcode mapping from bytecode-table.js
3+
// as a PHP file.
4+
5+
// this is just the thunk to run it under node.
6+
var turtlescript = require('../');
7+
turtlescript.write_php_ops();

‎nodemain.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,11 @@ module.exports.write_rust_ops = function() {
4444
// This module has side-effects when imported.
4545
requirejs('./write-rust-ops');
4646
};
47+
module.exports.write_php_bytecode = function() {
48+
// This module has side-effects when imported.
49+
requirejs('./write-php-bytecode');
50+
};
51+
module.exports.write_php_ops = function() {
52+
// This module has side-effects when imported.
53+
requirejs('./write-php-ops');
54+
};

‎write-php-bytecode.js‎

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
});

‎write-php-ops.js‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Utility to write out the opcode mapping from `bytecode-table.js`
2+
// as a PHP file.
3+
//
4+
// Run it under `node` with the CLI in `bin/write-php-ops.js`
5+
define(['./bytecode-table'], function(bytecode_table) {
6+
var bops = [];
7+
while(true) {
8+
var bc = bytecode_table.for_num(bops.length);
9+
if (!bc) { break; }
10+
bops.push(bc);
11+
}
12+
var comma = function(i) { return (i < (bops.length-1)) ? ',' : ''; };
13+
var phpName = function(bc) {
14+
var name = bc.name.toUpperCase();
15+
if (name==='2DUP') { return 'DUP2'; }
16+
return name;
17+
};
18+
19+
console.log('<?php');
20+
console.log('// generated by TurtleScript write-php-ops.js');
21+
console.log('');
22+
console.log('namespace Wikimedia\\PhpTurtle;');
23+
console.log('');
24+
25+
console.log('class Op {');
26+
// ## Emit `Op` enumeration.
27+
bops.forEach(function(bc, i) {
28+
console.log('\tpublic const ' + phpName(bc) + ' = ' + i + ';');
29+
});
30+
console.log('');
31+
32+
var buildSwitch = function(fn) {
33+
console.log('\t\tswitch ( $op ) {');
34+
var group = [];
35+
bops.forEach(function(bc) {
36+
var val = fn(bc);
37+
if (typeof val === 'function') {
38+
console.log('\t\tcase self::' + phpName(bc) + ':');
39+
console.log('\t\t\treturn ' + val() + ';');
40+
} else {
41+
if (group[val]===undefined) { group[val] = []; }
42+
group[val].push(bc);
43+
}
44+
});
45+
var i = 1;
46+
while (i < group.length) {
47+
if (group[i] === undefined || group[i].length === 0) { continue; }
48+
group[i].forEach(function(bc) {
49+
console.log('\t\tcase self::' + phpName(bc) + ':');
50+
});
51+
console.log('\t\t\treturn ' + i + ';');
52+
i+=1;
53+
}
54+
console.log('\t\tdefault:');
55+
console.log('\t\t\treturn 0;');
56+
console.log('\t\t}');
57+
};
58+
59+
// ## Emit `Op` functions.
60+
console.log('\t/**');
61+
console.log('\t * Return the number of arguments used for the given opcode.');
62+
console.log('\t * @param int $op The opcode');
63+
console.log('\t * @return int The number of arguments required');
64+
console.log('\t */');
65+
console.log('\tpublic static function args( int $op ): int {');
66+
buildSwitch(function(bc) { return bc.args; });
67+
console.log('\t}');
68+
console.log('');
69+
70+
console.log('\t/**');
71+
console.log('\t * Return the number of stack slots pushed by the given opcode.');
72+
console.log('\t * @param int $op The opcode');
73+
console.log('\t * @return int The number of stack slots pushed');
74+
console.log('\t */');
75+
console.log('\tpublic static function stackpush( int $op ): int {');
76+
buildSwitch(function(bc) { return bc.stackpush(); });
77+
console.log('\t}');
78+
console.log('');
79+
80+
console.log('\t/**');
81+
console.log('\t * Return the number of stack slots popped by the given opcode.');
82+
console.log('\t * @param int $op The opcode');
83+
console.log('\t * @param array $args The arguments to that opcode');
84+
console.log('\t * @return int The number of stack slots popped');
85+
console.log('\t */');
86+
console.log('\tpublic static function stackpop( int $op, array $args ): int {');
87+
buildSwitch(function(bc) {
88+
if (bc.name === 'invoke') {
89+
return (function() { return '$args[0] + 2' });
90+
}
91+
return bc.stackpop();
92+
});
93+
console.log('\t}');
94+
95+
console.log('}');
96+
});

0 commit comments

Comments
 (0)