You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1406 lines
49 KiB
1406 lines
49 KiB
(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", ["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); |
|
} |
|
|
|
})();
|
|
|