1407 lines
49 KiB
JavaScript
1407 lines
49 KiB
JavaScript
(function () {
|
|
|
|
var codeGenerator = (typeof eval("(function () {})") == "function") ?
|
|
function (code) { return code; } :
|
|
function (code) { return "false || " + code; };
|
|
|
|
// support string type only.
|
|
var stringify = (typeof JSON !== "undefined" && JSON.stringify) ?
|
|
function (s) { return JSON.stringify(s); } :
|
|
(function () {
|
|
// Implementation comes from JSON2 (http://www.json.org/js.html)
|
|
|
|
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
|
|
|
|
var meta = { // table of character substitutions
|
|
'\b': '\\b',
|
|
'\t': '\\t',
|
|
'\n': '\\n',
|
|
'\f': '\\f',
|
|
'\r': '\\r',
|
|
'"' : '\\"',
|
|
'\\': '\\\\'
|
|
}
|
|
|
|
return function (s) {
|
|
// If the string contains no control characters, no quote characters, and no
|
|
// backslash characters, then we can safely slap some quotes around it.
|
|
// Otherwise we must also replace the offending characters with safe escape
|
|
// sequences.
|
|
|
|
escapable.lastIndex = 0;
|
|
return escapable.test(s) ? '"' + s.replace(escapable, function (a) {
|
|
var c = meta[a];
|
|
return typeof c === 's' ? c :
|
|
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
|
}) + '"' : '"' + s + '"';
|
|
};
|
|
})();
|
|
|
|
// seed defined in global
|
|
if (typeof __jscex__tempVarSeed === "undefined") {
|
|
__jscex__tempVarSeed = 0;
|
|
}
|
|
|
|
var init = function (root) {
|
|
|
|
if (root.modules["jit"]) {
|
|
return;
|
|
}
|
|
|
|
function JscexTreeGenerator(binder) {
|
|
this._binder = binder;
|
|
this._root = null;
|
|
}
|
|
JscexTreeGenerator.prototype = {
|
|
|
|
generate: function (ast) {
|
|
|
|
var params = ast[2], statements = ast[3];
|
|
|
|
this._root = { type: "delay", stmts: [] };
|
|
|
|
this._visitStatements(statements, this._root.stmts);
|
|
|
|
return this._root;
|
|
},
|
|
|
|
_getBindInfo: function (stmt) {
|
|
|
|
var type = stmt[0];
|
|
if (type == "stat") {
|
|
var expr = stmt[1];
|
|
if (expr[0] == "call") {
|
|
var callee = expr[1];
|
|
if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
|
|
return {
|
|
expression: expr[2][0],
|
|
argName: "",
|
|
assignee: null
|
|
};
|
|
}
|
|
} else if (expr[0] == "assign") {
|
|
var assignee = expr[2];
|
|
expr = expr[3];
|
|
if (expr[0] == "call") {
|
|
var callee = expr[1];
|
|
if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
|
|
return {
|
|
expression: expr[2][0],
|
|
argName: "$$_result_$$",
|
|
assignee: assignee
|
|
};
|
|
}
|
|
}
|
|
}
|
|
} else if (type == "var") {
|
|
var defs = stmt[1];
|
|
if (defs.length == 1) {
|
|
var item = defs[0];
|
|
var name = item[0];
|
|
var expr = item[1];
|
|
if (expr && expr[0] == "call") {
|
|
var callee = expr[1];
|
|
if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
|
|
return {
|
|
expression: expr[2][0],
|
|
argName: name,
|
|
assignee: null
|
|
};
|
|
}
|
|
}
|
|
}
|
|
} else if (type == "return") {
|
|
var expr = stmt[1];
|
|
if (expr && expr[0] == "call") {
|
|
var callee = expr[1];
|
|
if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
|
|
return {
|
|
expression: expr[2][0],
|
|
argName: "$$_result_$$",
|
|
assignee: "return"
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
_visitStatements: function (statements, stmts, index) {
|
|
if (arguments.length <= 2) index = 0;
|
|
|
|
if (index >= statements.length) {
|
|
stmts.push({ type: "normal" });
|
|
return this;
|
|
}
|
|
|
|
var currStmt = statements[index];
|
|
var bindInfo = this._getBindInfo(currStmt);
|
|
|
|
if (bindInfo) {
|
|
var bindStmt = { type: "bind", info: bindInfo };
|
|
stmts.push(bindStmt);
|
|
|
|
if (bindInfo.assignee != "return") {
|
|
bindStmt.stmts = [];
|
|
this._visitStatements(statements, bindStmt.stmts, index + 1);
|
|
}
|
|
|
|
} else {
|
|
var type = currStmt[0];
|
|
if (type == "return" || type == "break" || type == "continue" || type == "throw") {
|
|
|
|
stmts.push({ type: type, stmt: currStmt });
|
|
|
|
} else if (type == "if" || type == "try" || type == "for" || type == "do"
|
|
|| type == "while" || type == "switch" || type == "for-in") {
|
|
|
|
var newStmt = this._visit(currStmt);
|
|
|
|
if (newStmt.type == "raw") {
|
|
stmts.push(newStmt);
|
|
this._visitStatements(statements, stmts, index + 1);
|
|
} else {
|
|
var isLast = (index == statements.length - 1);
|
|
if (isLast) {
|
|
stmts.push(newStmt);
|
|
} else {
|
|
|
|
var combineStmt = {
|
|
type: "combine",
|
|
first: { type: "delay", stmts: [newStmt] },
|
|
second: { type: "delay", stmts: [] }
|
|
};
|
|
stmts.push(combineStmt);
|
|
|
|
this._visitStatements(statements, combineStmt.second.stmts, index + 1);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
stmts.push({ type: "raw", stmt: currStmt });
|
|
|
|
this._visitStatements(statements, stmts, index + 1);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
_visit: function (ast) {
|
|
|
|
var type = ast[0];
|
|
|
|
function throwUnsupportedError() {
|
|
throw new Error('"' + type + '" is not currently supported.');
|
|
}
|
|
|
|
var visitor = this._visitors[type];
|
|
|
|
if (visitor) {
|
|
return visitor.call(this, ast);
|
|
} else {
|
|
throwUnsupportedError();
|
|
}
|
|
},
|
|
|
|
_visitBody: function (ast, stmts) {
|
|
if (ast[0] == "block") {
|
|
this._visitStatements(ast[1], stmts);
|
|
} else {
|
|
this._visitStatements([ast], stmts);
|
|
}
|
|
},
|
|
|
|
_noBinding: function (stmts) {
|
|
switch (stmts[stmts.length - 1].type) {
|
|
case "normal":
|
|
case "return":
|
|
case "break":
|
|
case "throw":
|
|
case "continue":
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_collectCaseStatements: function (cases, index) {
|
|
var res = [];
|
|
|
|
for (var i = index; i < cases.length; i++) {
|
|
var rawStmts = cases[i][1];
|
|
for (var j = 0; j < rawStmts.length; j++) {
|
|
if (rawStmts[j][0] == "break") {
|
|
return res
|
|
}
|
|
|
|
res.push(rawStmts[j]);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
},
|
|
|
|
_visitors: {
|
|
|
|
"for": function (ast) {
|
|
|
|
var bodyStmts = [];
|
|
var body = ast[4];
|
|
this._visitBody(body, bodyStmts);
|
|
|
|
if (this._noBinding(bodyStmts)) {
|
|
return { type: "raw", stmt: ast };
|
|
}
|
|
|
|
var delayStmt = { type: "delay", stmts: [] };
|
|
|
|
var setup = ast[1];
|
|
if (setup) {
|
|
delayStmt.stmts.push({ type: "raw", stmt: setup });
|
|
}
|
|
|
|
var loopStmt = { type: "loop", bodyFirst: false, bodyStmt: { type: "delay", stmts: bodyStmts } };
|
|
delayStmt.stmts.push(loopStmt);
|
|
|
|
var condition = ast[2];
|
|
if (condition) {
|
|
loopStmt.condition = condition;
|
|
}
|
|
|
|
var update = ast[3];
|
|
if (update) {
|
|
loopStmt.update = update;
|
|
}
|
|
|
|
return delayStmt;
|
|
},
|
|
|
|
"for-in": function (ast) {
|
|
|
|
var body = ast[4];
|
|
|
|
var bodyStmts = [];
|
|
this._visitBody(body, bodyStmts);
|
|
|
|
if (this._noBinding(bodyStmts)) {
|
|
return { type: "raw", stmt: ast };
|
|
}
|
|
|
|
var id = (__jscex__tempVarSeed++);
|
|
var keysVar = "$$_keys_$$_" + id;
|
|
var indexVar = "$$_index_$$_" + id;
|
|
// var memVar = "$$_mem_$$_" + id;
|
|
|
|
var delayStmt = { type: "delay", stmts: [] };
|
|
|
|
// var members = Jscex._forInKeys(obj);
|
|
var keysAst = root.parse("var " + keysVar + " = Jscex._forInKeys(obj);")[1][0];
|
|
keysAst[1][0][1][2][0] = ast[3]; // replace obj with real AST;
|
|
delayStmt.stmts.push({ type: "raw", stmt: keysAst });
|
|
|
|
/*
|
|
// var members = [];
|
|
delayStmt.stmts.push({
|
|
type: "raw",
|
|
stmt: uglifyJS.parse("var " + membersVar + " = [];")[1][0]
|
|
});
|
|
|
|
// for (var mem in obj) members.push(mem);
|
|
var keysAst = uglifyJS.parse("for (var " + memVar +" in obj) " + membersVar + ".push(" + memVar + ");")[1][0];
|
|
keysAst[3] = ast[3]; // replace the "obj" with real AST.
|
|
delayStmt.stmts.push({ type : "raw", stmt: keysAst});
|
|
*/
|
|
|
|
// var index = 0;
|
|
delayStmt.stmts.push({
|
|
type: "raw",
|
|
stmt: root.parse("var " + indexVar + " = 0;")[1][0]
|
|
});
|
|
|
|
// index < members.length
|
|
var condition = root.parse(indexVar + " < " + keysVar + ".length")[1][0][1];
|
|
|
|
// index++
|
|
var update = root.parse(indexVar + "++")[1][0][1];
|
|
|
|
var loopStmt = {
|
|
type: "loop",
|
|
bodyFirst: false,
|
|
update: update,
|
|
condition: condition,
|
|
bodyStmt: { type: "delay", stmts: [] }
|
|
};
|
|
delayStmt.stmts.push(loopStmt);
|
|
|
|
var varName = ast[2][1]; // ast[2] == ["name", m]
|
|
if (ast[1][0] == "var") {
|
|
loopStmt.bodyStmt.stmts.push({
|
|
type: "raw",
|
|
stmt: root.parse("var " + varName + " = " + keysVar + "[" + indexVar + "];")[1][0]
|
|
});
|
|
} else {
|
|
loopStmt.bodyStmt.stmts.push({
|
|
type: "raw",
|
|
stmt: root.parse(varName + " = " + keysVar + "[" + indexVar + "];")[1][0]
|
|
});
|
|
}
|
|
|
|
this._visitBody(body, loopStmt.bodyStmt.stmts);
|
|
|
|
return delayStmt;
|
|
},
|
|
|
|
"while": function (ast) {
|
|
|
|
var bodyStmts = [];
|
|
var body = ast[2];
|
|
this._visitBody(body, bodyStmts);
|
|
|
|
if (this._noBinding(bodyStmts)) {
|
|
return { type: "raw", stmt: ast }
|
|
}
|
|
|
|
var loopStmt = { type: "loop", bodyFirst: false, bodyStmt: { type: "delay", stmts: bodyStmts } };
|
|
|
|
var condition = ast[1];
|
|
loopStmt.condition = condition;
|
|
|
|
return loopStmt;
|
|
},
|
|
|
|
"do": function (ast) {
|
|
|
|
var bodyStmts = [];
|
|
var body = ast[2];
|
|
this._visitBody(body, bodyStmts);
|
|
|
|
if (this._noBinding(bodyStmts)) {
|
|
return { type: "raw", stmt: ast };
|
|
}
|
|
|
|
var loopStmt = { type: "loop", bodyFirst: true, bodyStmt: { type: "delay", stmts: bodyStmts } };
|
|
|
|
var condition = ast[1];
|
|
loopStmt.condition = condition;
|
|
|
|
return loopStmt;
|
|
},
|
|
|
|
"switch": function (ast) {
|
|
var noBinding = true;
|
|
|
|
var switchStmt = { type: "switch", item: ast[1], caseStmts: [] };
|
|
|
|
var cases = ast[2];
|
|
for (var i = 0; i < cases.length; i++) {
|
|
var caseStmt = { item: cases[i][0], stmts: [] };
|
|
switchStmt.caseStmts.push(caseStmt);
|
|
|
|
var statements = this._collectCaseStatements(cases, i);
|
|
this._visitStatements(statements, caseStmt.stmts);
|
|
noBinding = noBinding && this._noBinding(caseStmt.stmts);
|
|
}
|
|
|
|
if (noBinding) {
|
|
return { type: "raw", stmt: ast };
|
|
} else {
|
|
return switchStmt;
|
|
}
|
|
},
|
|
|
|
"if": function (ast) {
|
|
|
|
var noBinding = true;
|
|
|
|
var ifStmt = { type: "if", conditionStmts: [] };
|
|
|
|
var currAst = ast;
|
|
while (true) {
|
|
var condition = currAst[1];
|
|
var condStmt = { cond: condition, stmts: [] };
|
|
ifStmt.conditionStmts.push(condStmt);
|
|
|
|
var thenPart = currAst[2];
|
|
this._visitBody(thenPart, condStmt.stmts);
|
|
|
|
noBinding = noBinding && this._noBinding(condStmt.stmts);
|
|
|
|
var elsePart = currAst[3];
|
|
if (elsePart && elsePart[0] == "if") {
|
|
currAst = elsePart;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
var elsePart = currAst[3];
|
|
if (elsePart) {
|
|
ifStmt.elseStmts = [];
|
|
|
|
this._visitBody(elsePart, ifStmt.elseStmts);
|
|
|
|
noBinding = noBinding && this._noBinding(ifStmt.elseStmts);
|
|
}
|
|
|
|
if (noBinding) {
|
|
return { type: "raw", stmt: ast };
|
|
} else {
|
|
return ifStmt;
|
|
}
|
|
},
|
|
|
|
"try": function (ast, stmts) {
|
|
|
|
var bodyStmts = [];
|
|
var bodyStatements = ast[1];
|
|
this._visitStatements(bodyStatements, bodyStmts);
|
|
|
|
var noBinding = this._noBinding(bodyStmts)
|
|
|
|
var tryStmt = { type: "try", bodyStmt: { type: "delay", stmts: bodyStmts } };
|
|
|
|
var catchClause = ast[2];
|
|
if (catchClause) {
|
|
var exVar = catchClause[0];
|
|
tryStmt.exVar = exVar;
|
|
tryStmt.catchStmts = [];
|
|
|
|
this._visitStatements(catchClause[1], tryStmt.catchStmts);
|
|
|
|
noBinding = noBinding && this._noBinding(tryStmt.catchStmts);
|
|
}
|
|
|
|
var finallyStatements = ast[3];
|
|
if (finallyStatements) {
|
|
tryStmt.finallyStmt = { type: "delay", stmts: [] };
|
|
|
|
this._visitStatements(finallyStatements, tryStmt.finallyStmt.stmts);
|
|
|
|
noBinding = noBinding && this._noBinding(tryStmt.finallyStmt.stmts);
|
|
}
|
|
|
|
if (noBinding) {
|
|
return { type: "raw", stmt: ast };
|
|
} else {
|
|
return tryStmt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function CodeGenerator(builderName, binder, indent) {
|
|
this._builderName = builderName;
|
|
this._binder = binder;
|
|
this._normalMode = false;
|
|
this._indent = indent;
|
|
this._indentLevel = 0;
|
|
this._builderVar = "$$_builder_$$_" + (__jscex__tempVarSeed++);
|
|
}
|
|
CodeGenerator.prototype = {
|
|
_write: function (s) {
|
|
this._buffer.push(s);
|
|
return this;
|
|
},
|
|
|
|
_writeLine: function (s) {
|
|
this._write(s)._write("\n");
|
|
return this;
|
|
},
|
|
|
|
_writeIndents: function () {
|
|
for (var i = 0; i < this._indent; i++) {
|
|
this._write(" ");
|
|
}
|
|
|
|
for (var i = 0; i < this._indentLevel; i++) {
|
|
this._write(" ");
|
|
}
|
|
return this;
|
|
},
|
|
|
|
generate: function (params, jscexAst) {
|
|
this._buffer = [];
|
|
|
|
this._writeLine("(function (" + params.join(", ") + ") {");
|
|
this._indentLevel++;
|
|
|
|
this._writeIndents()
|
|
._writeLine("var " + this._builderVar + " = Jscex.builders[" + stringify(this._builderName) + "];");
|
|
|
|
this._writeIndents()
|
|
._writeLine("return " + this._builderVar + ".Start(this,");
|
|
this._indentLevel++;
|
|
|
|
this._pos = { };
|
|
|
|
this._writeIndents()
|
|
._visitJscex(jscexAst)
|
|
._writeLine();
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._writeLine(");");
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write("})");
|
|
|
|
return this._buffer.join("");
|
|
},
|
|
|
|
_visitJscex: function (ast) {
|
|
this._jscexVisitors[ast.type].call(this, ast);
|
|
return this;
|
|
},
|
|
|
|
_visitRaw: function (ast) {
|
|
var type = ast[0];
|
|
|
|
function throwUnsupportedError() {
|
|
throw new Error('"' + type + '" is not currently supported.');
|
|
}
|
|
|
|
var visitor = this._rawVisitors[type];
|
|
|
|
if (visitor) {
|
|
visitor.call(this, ast);
|
|
} else {
|
|
throwUnsupportedError();
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
_visitJscexStatements: function (statements) {
|
|
for (var i = 0; i < statements.length; i++) {
|
|
var stmt = statements[i];
|
|
|
|
if (stmt.type == "raw" || stmt.type == "if" || stmt.type == "switch") {
|
|
this._writeIndents()
|
|
._visitJscex(stmt)._writeLine();
|
|
} else if (stmt.type == "delay") {
|
|
this._visitJscexStatements(stmt.stmts);
|
|
} else {
|
|
this._writeIndents()
|
|
._write("return ")._visitJscex(stmt)._writeLine(";");
|
|
}
|
|
}
|
|
},
|
|
|
|
_visitRawStatements: function (statements) {
|
|
for (var i = 0; i < statements.length; i++) {
|
|
var s = statements[i];
|
|
|
|
this._writeIndents()
|
|
._visitRaw(s)._writeLine();
|
|
|
|
switch (s[0]) {
|
|
case "break":
|
|
case "return":
|
|
case "continue":
|
|
case "throw":
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
_visitRawBody: function (body) {
|
|
if (body[0] == "block") {
|
|
this._visitRaw(body);
|
|
} else {
|
|
this._writeLine();
|
|
this._indentLevel++;
|
|
|
|
this._writeIndents()
|
|
._visitRaw(body);
|
|
this._indentLevel--;
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
_visitRawFunction: function (ast) {
|
|
var funcName = ast[1] || "";
|
|
var args = ast[2];
|
|
var statements = ast[3];
|
|
|
|
this._writeLine("function " + funcName + "(" + args.join(", ") + ") {")
|
|
this._indentLevel++;
|
|
|
|
var currInFunction = this._pos.inFunction;
|
|
this._pos.inFunction = true;
|
|
|
|
this._visitRawStatements(statements);
|
|
this._indentLevel--;
|
|
|
|
this._pos.inFunction = currInFunction;
|
|
|
|
this._writeIndents()
|
|
._write("}");
|
|
},
|
|
|
|
_jscexVisitors: {
|
|
"delay": function (ast) {
|
|
if (ast.stmts.length == 1) {
|
|
var subStmt = ast.stmts[0];
|
|
switch (subStmt.type) {
|
|
case "delay":
|
|
case "combine":
|
|
case "normal":
|
|
case "break":
|
|
case "continue":
|
|
case "loop":
|
|
case "try":
|
|
this._visitJscex(subStmt);
|
|
return;
|
|
case "return":
|
|
if (!subStmt.stmt[1]) {
|
|
this._visitJscex(subStmt);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
this._writeLine(this._builderVar + ".Delay(function () {");
|
|
this._indentLevel++;
|
|
|
|
this._visitJscexStatements(ast.stmts);
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write("})");
|
|
},
|
|
|
|
"combine": function (ast) {
|
|
this._writeLine(this._builderVar + ".Combine(");
|
|
this._indentLevel++;
|
|
|
|
this._writeIndents()
|
|
._visitJscex(ast.first)._writeLine(",");
|
|
this._writeIndents()
|
|
._visitJscex(ast.second)._writeLine();
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write(")");
|
|
},
|
|
|
|
"loop": function (ast) {
|
|
this._writeLine(this._builderVar + ".Loop(");
|
|
this._indentLevel++;
|
|
|
|
if (ast.condition) {
|
|
this._writeIndents()
|
|
._writeLine("function () {");
|
|
this._indentLevel++;
|
|
|
|
this._writeIndents()
|
|
._write("return ")._visitRaw(ast.condition)._writeLine(";");
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._writeLine("},");
|
|
} else {
|
|
this._writeIndents()._writeLine("null,");
|
|
}
|
|
|
|
if (ast.update) {
|
|
this._writeIndents()
|
|
._writeLine("function () {");
|
|
this._indentLevel++;
|
|
|
|
this._writeIndents()
|
|
._visitRaw(ast.update)._writeLine(";");
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._writeLine("},");
|
|
} else {
|
|
this._writeIndents()._writeLine("null,");
|
|
}
|
|
|
|
this._writeIndents()
|
|
._visitJscex(ast.bodyStmt)._writeLine(",");
|
|
|
|
this._writeIndents()
|
|
._writeLine(ast.bodyFirst);
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write(")");
|
|
},
|
|
|
|
"raw": function (ast) {
|
|
this._visitRaw(ast.stmt);
|
|
},
|
|
|
|
"bind": function (ast) {
|
|
var info = ast.info;
|
|
this._write(this._builderVar + ".Bind(")._visitRaw(info.expression)._writeLine(", function (" + info.argName + ") {");
|
|
this._indentLevel++;
|
|
|
|
if (info.assignee == "return") {
|
|
this._writeIndents()
|
|
._writeLine("return " + this._builderVar + ".Return(" + info.argName + ");");
|
|
} else {
|
|
if (info.assignee) {
|
|
this._writeIndents()
|
|
._visitRaw(info.assignee)._writeLine(" = " + info.argName + ";");
|
|
}
|
|
|
|
this._visitJscexStatements(ast.stmts);
|
|
}
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write("})");
|
|
},
|
|
|
|
"if": function (ast) {
|
|
|
|
for (var i = 0; i < ast.conditionStmts.length; i++) {
|
|
var stmt = ast.conditionStmts[i];
|
|
|
|
this._write("if (")._visitRaw(stmt.cond)._writeLine(") {");
|
|
this._indentLevel++;
|
|
|
|
this._visitJscexStatements(stmt.stmts);
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write("} else ");
|
|
}
|
|
|
|
this._writeLine("{");
|
|
this._indentLevel++;
|
|
|
|
if (ast.elseStmts) {
|
|
this._visitJscexStatements(ast.elseStmts);
|
|
} else {
|
|
this._writeIndents()
|
|
._writeLine("return " + this._builderVar + ".Normal();");
|
|
}
|
|
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write("}");
|
|
},
|
|
|
|
"switch": function (ast) {
|
|
this._write("switch (")._visitRaw(ast.item)._writeLine(") {");
|
|
this._indentLevel++;
|
|
|
|
for (var i = 0; i < ast.caseStmts.length; i++) {
|
|
var caseStmt = ast.caseStmts[i];
|
|
|
|
if (caseStmt.item) {
|
|
this._writeIndents()
|
|
._write("case ")._visitRaw(caseStmt.item)._writeLine(":");
|
|
} else {
|
|
this._writeIndents()._writeLine("default:");
|
|
}
|
|
this._indentLevel++;
|
|
|
|
this._visitJscexStatements(caseStmt.stmts);
|
|
this._indentLevel--;
|
|
}
|
|
|
|
this._writeIndents()
|
|
._write("}");
|
|
},
|
|
|
|
"try": function (ast) {
|
|
this._writeLine(this._builderVar + ".Try(");
|
|
this._indentLevel++;
|
|
|
|
this._writeIndents()
|
|
._visitJscex(ast.bodyStmt)._writeLine(",");
|
|
|
|
if (ast.catchStmts) {
|
|
this._writeIndents()
|
|
._writeLine("function (" + ast.exVar + ") {");
|
|
this._indentLevel++;
|
|
|
|
this._visitJscexStatements(ast.catchStmts);
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._writeLine("},");
|
|
} else {
|
|
this._writeIndents()
|
|
._writeLine("null,");
|
|
}
|
|
|
|
if (ast.finallyStmt) {
|
|
this._writeIndents()
|
|
._visitJscex(ast.finallyStmt)._writeLine();
|
|
} else {
|
|
this._writeIndents()
|
|
._writeLine("null");
|
|
}
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write(")");
|
|
},
|
|
|
|
"normal": function (ast) {
|
|
this._write(this._builderVar + ".Normal()");
|
|
},
|
|
|
|
"throw": function (ast) {
|
|
this._write(this._builderVar + ".Throw(")._visitRaw(ast.stmt[1])._write(")");
|
|
},
|
|
|
|
"break": function (ast) {
|
|
this._write(this._builderVar + ".Break()");
|
|
},
|
|
|
|
"continue": function (ast) {
|
|
this._write(this._builderVar + ".Continue()");
|
|
},
|
|
|
|
"return": function (ast) {
|
|
this._write(this._builderVar + ".Return(");
|
|
if (ast.stmt[1]) this._visitRaw(ast.stmt[1]);
|
|
this._write(")");
|
|
}
|
|
},
|
|
|
|
_rawVisitors: {
|
|
"var": function (ast) {
|
|
this._write("var ");
|
|
|
|
var items = ast[1];
|
|
for (var i = 0; i < items.length; i++) {
|
|
this._write(items[i][0]);
|
|
if (items[i].length > 1) {
|
|
this._write(" = ")._visitRaw(items[i][1]);
|
|
}
|
|
if (i < items.length - 1) this._write(", ");
|
|
}
|
|
|
|
this._write(";");
|
|
},
|
|
|
|
"seq": function (ast) {
|
|
for (var i = 1; i < ast.length; i++) {
|
|
this._visitRaw(ast[i]);
|
|
if (i < ast.length - 1) this._write(", ");
|
|
}
|
|
},
|
|
|
|
"binary": function (ast) {
|
|
var op = ast[1], left = ast[2], right = ast[3];
|
|
|
|
function needBracket(item) {
|
|
var type = item[0];
|
|
return !(type == "num" || type == "name" || type == "dot");
|
|
}
|
|
|
|
if (needBracket(left)) {
|
|
this._write("(")._visitRaw(left)._write(") ");
|
|
} else {
|
|
this._visitRaw(left)._write(" ");
|
|
}
|
|
|
|
this._write(op);
|
|
|
|
if (needBracket(right)) {
|
|
this._write(" (")._visitRaw(right)._write(")");
|
|
} else {
|
|
this._write(" ")._visitRaw(right);
|
|
}
|
|
},
|
|
|
|
"sub": function (ast) {
|
|
var prop = ast[1], index = ast[2];
|
|
|
|
function needBracket() {
|
|
return !(prop[0] == "name")
|
|
}
|
|
|
|
if (needBracket()) {
|
|
this._write("(")._visitRaw(prop)._write(")[")._visitRaw(index)._write("]");
|
|
} else {
|
|
this._visitRaw(prop)._write("[")._visitRaw(index)._write("]");
|
|
}
|
|
},
|
|
|
|
"unary-postfix": function (ast) {
|
|
var op = ast[1];
|
|
var item = ast[2];
|
|
this._visitRaw(item)._write(op);
|
|
},
|
|
|
|
"unary-prefix": function (ast) {
|
|
var op = ast[1];
|
|
var item = ast[2];
|
|
this._write(op);
|
|
if (op == "typeof") {
|
|
this._write("(")._visitRaw(item)._write(")");
|
|
} else {
|
|
this._visitRaw(item);
|
|
}
|
|
},
|
|
|
|
"assign": function (ast) {
|
|
var op = ast[1];
|
|
var name = ast[2];
|
|
var value = ast[3];
|
|
|
|
this._visitRaw(name);
|
|
if ((typeof op) == "string") {
|
|
this._write(" " + op + "= ");
|
|
} else {
|
|
this._write(" = ");
|
|
}
|
|
this._visitRaw(value);
|
|
},
|
|
|
|
"stat": function (ast) {
|
|
this._visitRaw(ast[1])._write(";");
|
|
},
|
|
|
|
"dot": function (ast) {
|
|
function needBracket() {
|
|
var leftOp = ast[1][0];
|
|
return !(leftOp == "dot" || leftOp == "name");
|
|
}
|
|
|
|
if (needBracket()) {
|
|
this._write("(")._visitRaw(ast[1])._write(").")._write(ast[2]);
|
|
} else {
|
|
this._visitRaw(ast[1])._write(".")._write(ast[2]);
|
|
}
|
|
},
|
|
|
|
"new": function (ast) {
|
|
var ctor = ast[1];
|
|
|
|
this._write("new ")._visitRaw(ctor)._write("(");
|
|
|
|
var args = ast[2];
|
|
for (var i = 0, len = args.length; i < len; i++) {
|
|
this._visitRaw(args[i]);
|
|
if (i < len - 1) this._write(", ");
|
|
}
|
|
|
|
this._write(")");
|
|
},
|
|
|
|
"call": function (ast) {
|
|
|
|
if (_isJscexPattern(ast)) {
|
|
var indent = this._indent + this._indentLevel * 4;
|
|
var newCode = _compileJscexPattern(ast, indent);
|
|
this._write(newCode);
|
|
} else {
|
|
|
|
var invalidBind = (ast[1][0] == "name") && (ast[1][1] == this._binder);
|
|
if (invalidBind) {
|
|
this._pos = { inFunction: true };
|
|
this._buffer = [];
|
|
}
|
|
|
|
this._visitRaw(ast[1])._write("(");
|
|
|
|
var args = ast[2];
|
|
for (var i = 0; i < args.length; i++) {
|
|
this._visitRaw(args[i]);
|
|
if (i < args.length - 1) this._write(", ");
|
|
}
|
|
|
|
this._write(")");
|
|
|
|
if (invalidBind) {
|
|
throw ("Invalid bind operation: " + this._buffer.join(""));
|
|
}
|
|
}
|
|
},
|
|
|
|
"name": function (ast) {
|
|
this._write(ast[1]);
|
|
},
|
|
|
|
"object": function (ast) {
|
|
var items = ast[1];
|
|
if (items.length <= 0) {
|
|
this._write("{ }");
|
|
} else {
|
|
this._writeLine("{");
|
|
this._indentLevel++;
|
|
|
|
for (var i = 0; i < items.length; i++) {
|
|
this._writeIndents()
|
|
._write(stringify(items[i][0]) + ": ")
|
|
._visitRaw(items[i][1]);
|
|
|
|
if (i < items.length - 1) {
|
|
this._writeLine(",");
|
|
} else {
|
|
this._writeLine("");
|
|
}
|
|
}
|
|
|
|
this._indentLevel--;
|
|
this._writeIndents()._write("}");
|
|
}
|
|
},
|
|
|
|
"array": function (ast) {
|
|
this._write("[");
|
|
|
|
var items = ast[1];
|
|
for (var i = 0; i < items.length; i++) {
|
|
this._visitRaw(items[i]);
|
|
if (i < items.length - 1) this._write(", ");
|
|
}
|
|
|
|
this._write("]");
|
|
},
|
|
|
|
"num": function (ast) {
|
|
this._write(ast[1]);
|
|
},
|
|
|
|
"regexp": function (ast) {
|
|
this._write("/" + ast[1] + "/" + ast[2]);
|
|
},
|
|
|
|
"string": function (ast) {
|
|
this._write(stringify(ast[1]));
|
|
},
|
|
|
|
"function": function (ast) {
|
|
this._visitRawFunction(ast);
|
|
},
|
|
|
|
"defun": function (ast) {
|
|
this._visitRawFunction(ast);
|
|
},
|
|
|
|
"return": function (ast) {
|
|
if (this._pos.inFunction) {
|
|
this._write("return");
|
|
var value = ast[1];
|
|
if (value) this._write(" ")._visitRaw(value);
|
|
this._write(";");
|
|
} else {
|
|
this._write("return ")._visitJscex({ type: "return", stmt: ast })._write(";");
|
|
}
|
|
},
|
|
|
|
"for": function (ast) {
|
|
this._write("for (");
|
|
|
|
var setup = ast[1];
|
|
if (setup) {
|
|
this._visitRaw(setup);
|
|
if (setup[0] != "var") {
|
|
this._write("; ");
|
|
} else {
|
|
this._write(" ");
|
|
}
|
|
} else {
|
|
this._write("; ");
|
|
}
|
|
|
|
var condition = ast[2];
|
|
if (condition) this._visitRaw(condition);
|
|
this._write("; ");
|
|
|
|
var update = ast[3];
|
|
if (update) this._visitRaw(update);
|
|
this._write(") ");
|
|
|
|
var currInLoop = this._pos.inLoop;
|
|
this._pos.inLoop = true;
|
|
|
|
var body = ast[4];
|
|
this._visitRawBody(body);
|
|
|
|
this._pos.inLoop = currInLoop;
|
|
},
|
|
|
|
"for-in": function (ast) {
|
|
this._write("for (");
|
|
|
|
var declare = ast[1];
|
|
if (declare[0] == "var") { // declare == ["var", [["m"]]]
|
|
this._write("var " + declare[1][0][0]);
|
|
} else {
|
|
this._visitRaw(declare);
|
|
}
|
|
|
|
this._write(" in ")._visitRaw(ast[3])._write(") ");
|
|
|
|
var body = ast[4];
|
|
this._visitRawBody(body);
|
|
},
|
|
|
|
"block": function (ast) {
|
|
this._writeLine("{")
|
|
this._indentLevel++;
|
|
|
|
this._visitRawStatements(ast[1]);
|
|
this._indentLevel--;
|
|
|
|
this._writeIndents()
|
|
._write("}");
|
|
},
|
|
|
|
"while": function (ast) {
|
|
var condition = ast[1];
|
|
var body = ast[2];
|
|
|
|
var currInLoop = this._pos.inLoop
|
|
this._pos.inLoop = true;
|
|
|
|
this._write("while (")._visitRaw(condition)._write(") ")._visitRawBody(body);
|
|
|
|
this._pos.inLoop = currInLoop;
|
|
},
|
|
|
|
"do": function (ast) {
|
|
var condition = ast[1];
|
|
var body = ast[2];
|
|
|
|
var currInLoop = this._pos.inLoop;
|
|
this._pos.inLoop = true;
|
|
|
|
this._write("do ")._visitRawBody(body);
|
|
|
|
this._pos.inLoop = currInLoop;
|
|
|
|
if (body[0] == "block") {
|
|
this._write(" ");
|
|
} else {
|
|
this._writeLine()._writeIndents();
|
|
}
|
|
|
|
this._write("while (")._visitRaw(condition)._write(");");
|
|
},
|
|
|
|
"if": function (ast) {
|
|
var condition = ast[1];
|
|
var thenPart = ast[2];
|
|
|
|
this._write("if (")._visitRaw(condition)._write(") ")._visitRawBody(thenPart);
|
|
|
|
var elsePart = ast[3];
|
|
if (elsePart) {
|
|
if (thenPart[0] == "block") {
|
|
this._write(" ");
|
|
} else {
|
|
this._writeLine("")
|
|
._writeIndents();
|
|
}
|
|
|
|
if (elsePart[0] == "if") {
|
|
this._write("else ")._visitRaw(elsePart);
|
|
} else {
|
|
this._write("else ")._visitRawBody(elsePart);
|
|
}
|
|
}
|
|
},
|
|
|
|
"break": function (ast) {
|
|
if (this._pos.inLoop || this._pos.inSwitch) {
|
|
this._write("break;");
|
|
} else {
|
|
this._write("return ")._visitJscex({ type: "break", stmt: ast })._write(";");
|
|
}
|
|
},
|
|
|
|
"continue": function (ast) {
|
|
if (this._pos.inLoop) {
|
|
this._write("continue;");
|
|
} else {
|
|
this._write("return ")._visitJscex({ type: "continue", stmt: ast })._write(";");
|
|
}
|
|
},
|
|
|
|
"throw": function (ast) {
|
|
var pos = this._pos;
|
|
if (pos.inTry || pos.inFunction) {
|
|
this._write("throw ")._visitRaw(ast[1])._write(";");
|
|
} else {
|
|
this._write("return ")._visitJscex({ type: "throw", stmt: ast })._write(";");
|
|
}
|
|
},
|
|
|
|
"conditional": function (ast) {
|
|
this._write("(")._visitRaw(ast[1])._write(") ? (")._visitRaw(ast[2])._write(") : (")._visitRaw(ast[3])._write(")");
|
|
},
|
|
|
|
"try": function (ast) {
|
|
|
|
this._writeLine("try {");
|
|
this._indentLevel++;
|
|
|
|
var currInTry = this._pos.inTry;
|
|
this._pos.inTry = true;
|
|
|
|
this._visitRawStatements(ast[1]);
|
|
this._indentLevel--;
|
|
|
|
this._pos.inTry = currInTry;
|
|
|
|
var catchClause = ast[2];
|
|
var finallyStatements = ast[3];
|
|
|
|
if (catchClause) {
|
|
this._writeIndents()
|
|
._writeLine("} catch (" + catchClause[0] + ") {")
|
|
this._indentLevel++;
|
|
|
|
this._visitRawStatements(catchClause[1]);
|
|
this._indentLevel--;
|
|
}
|
|
|
|
if (finallyStatements) {
|
|
this._writeIndents()
|
|
._writeLine("} finally {");
|
|
this._indentLevel++;
|
|
|
|
this._visitRawStatements(finallyStatements);
|
|
this._indentLevel--;
|
|
}
|
|
|
|
this._writeIndents()
|
|
._write("}");
|
|
},
|
|
|
|
"switch": function (ast) {
|
|
this._write("switch (")._visitRaw(ast[1])._writeLine(") {");
|
|
this._indentLevel++;
|
|
|
|
var currInSwitch = this._pos.inSwitch;
|
|
this._pos.inSwitch = true;
|
|
|
|
var cases = ast[2];
|
|
for (var i = 0; i < cases.length; i++) {
|
|
var c = cases[i];
|
|
this._writeIndents();
|
|
|
|
if (c[0]) {
|
|
this._write("case ")._visitRaw(c[0])._writeLine(":");
|
|
} else {
|
|
this._writeLine("default:");
|
|
}
|
|
this._indentLevel++;
|
|
|
|
this._visitRawStatements(c[1]);
|
|
this._indentLevel--;
|
|
}
|
|
this._indentLevel--;
|
|
|
|
this._pos.inSwitch = currInSwitch;
|
|
|
|
this._writeIndents()
|
|
._write("}");
|
|
}
|
|
}
|
|
}
|
|
|
|
function _isJscexPattern(ast) {
|
|
if (ast[0] != "call") return false;
|
|
|
|
var evalName = ast[1];
|
|
if (evalName[0] != "name" || evalName[1] != "eval") return false;
|
|
|
|
var compileCall = ast[2][0];
|
|
if (!compileCall || compileCall[0] != "call") return false;
|
|
|
|
var compileMethod = compileCall[1];
|
|
if (!compileMethod || compileMethod[0] != "dot" || compileMethod[2] != "compile") return false;
|
|
|
|
var jscexName = compileMethod[1];
|
|
if (!jscexName || jscexName[0] != "name" || jscexName[1] != "Jscex") return false;
|
|
|
|
var builder = compileCall[2][0];
|
|
if (!builder || builder[0] != "string") return false;
|
|
|
|
var func = compileCall[2][1];
|
|
if (!func || func[0] != "function") return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
function _compileJscexPattern(ast, indent) {
|
|
|
|
var builderName = ast[2][0][2][0][1];
|
|
var funcAst = ast[2][0][2][1];
|
|
var binder = root.binders[builderName];
|
|
|
|
var jscexTreeGenerator = new JscexTreeGenerator(binder);
|
|
var jscexAst = jscexTreeGenerator.generate(funcAst);
|
|
|
|
var codeGenerator = new CodeGenerator(builderName, binder, indent);
|
|
var newCode = codeGenerator.generate(funcAst[2], jscexAst);
|
|
|
|
return newCode;
|
|
}
|
|
|
|
function compile(builderName, func) {
|
|
|
|
var funcCode = func.toString();
|
|
var evalCode = "eval(Jscex.compile(" + stringify(builderName) + ", " + funcCode + "))"
|
|
var evalCodeAst = root.parse(evalCode);
|
|
|
|
// [ "toplevel", [ [ "stat", [ "call", ... ] ] ] ]
|
|
var evalAst = evalCodeAst[1][0][1];
|
|
var newCode = _compileJscexPattern(evalAst, 0);
|
|
|
|
root.logger.debug(funcCode + "\n\n>>>\n\n" + newCode);
|
|
|
|
return codeGenerator(newCode);
|
|
};
|
|
|
|
root.compile = compile;
|
|
|
|
root.modules["jit"] = true;
|
|
}
|
|
|
|
var isCommonJS = (typeof require !== "undefined" && typeof module !== "undefined" && module.exports);
|
|
var isAmd = (typeof define !== "undefined" && define.amd);
|
|
|
|
if (isCommonJS) {
|
|
module.exports.init = function (root) {
|
|
if (!root.modules["parser"]) {
|
|
require("./jscex-parser").init(root);
|
|
};
|
|
|
|
init(root);
|
|
}
|
|
} else if (isAmd) {
|
|
define("jscex-jit", ["file/jscex-parser"], function (parser) {
|
|
return {
|
|
init: function (root) {
|
|
if (!root.modules["parser"]) {
|
|
parser.init(root);
|
|
}
|
|
|
|
init(root);
|
|
}
|
|
};
|
|
});
|
|
} else {
|
|
if (typeof Jscex === "undefined") {
|
|
throw new Error('Missing root object, please load "jscex" module first.');
|
|
}
|
|
|
|
if (!Jscex.modules["parser"]) {
|
|
throw new Error('Missing essential components, please initialize "parser" module first.');
|
|
}
|
|
|
|
init(Jscex);
|
|
}
|
|
|
|
})();
|