http://git-wip-us.apache.org/repos/asf/incubator-cordova-windows/blob/03bf0cde/windows8/framework/Cordova-Metro/lib/Jscex/src/jscex-jit.js ---------------------------------------------------------------------- diff --git a/windows8/framework/Cordova-Metro/lib/Jscex/src/jscex-jit.js b/windows8/framework/Cordova-Metro/lib/Jscex/src/jscex-jit.js new file mode 100644 index 0000000..43e09fd --- /dev/null +++ b/windows8/framework/Cordova-Metro/lib/Jscex/src/jscex-jit.js @@ -0,0 +1,1692 @@ +(function () { + "use strict"; + + var Jscex; + + 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 + '"'; + }; + })(); + + function sprintf(format) { + var args = arguments; + return format.toString().replace(new RegExp("{\\d+}", "g"), function (p) { + var n = parseInt(p.substring(1, p.length - 1), 10); + return args[n + 1]; + }); + } + + function trim(s) { + return s.replace(/ +/g, ""); + } + + function getPrecedence(ast) { + var type = ast[0]; + switch (type) { + case "dot": // . + case "sub": // [] + case "call": // () + return 1; + case "unary-postfix": // ++ -- - ~ ! delete new typeof void + case "unary-prefix": + return 2; + case "var": + case "binary": + switch (ast[1]) { + case "*": + case "/": + case "%": + return 3; + case "+": + case "-": + return 4; + case "<<": + case ">>": + case ">>>": + return 5; + case "<": + case "<=": + case ">": + case ">=": + case "instanceof": + return 6; + case "==": + case "!=": + case "===": + case "!==": + return 7; + case "&": + return 8; + case "^": + return 9; + case "|": + return 10; + case "&&": + return 11; + case "||": + return 12; + } + case "conditional": + return 13; + case "assign": + return 14; + case "new": + return 15; + case "seq": + case "stat": + case "name": + case "object": + case "array": + case "num": + case "regexp": + case "string": + case "function": + case "defun": + case "for": + case "for-in": + case "block": + case "while": + case "do": + case "if": + case "break": + case "continue": + case "return": + case "throw": + case "try": + case "switch": + return 0; + default: + return 100; // the lowest + } + } + + var CodeWriter = function (indent) { + this._indent = indent || " "; + this._indentLevel = 0; + + this.lines = []; + } + CodeWriter.prototype = { + write: function (str) { + if (str === undefined) return; + + if (this.lines.length == 0) { + this.lines.push(""); + } + + this.lines[this.lines.length - 1] += str; + return this; + }, + + writeLine: function () { + this.write.apply(this, arguments); + this.lines.push(""); + return this; + }, + + writeIndents: function () { + var indents = new Array(this._indentLevel); + for (var i = 0; i < this._indentLevel; i++) { + indents[i] = this._indent; + } + + this.write(indents.join("")); + return this; + }, + + addIndentLevel: function (diff) { + this._indentLevel += diff; + return this; + } + }; + + var SeedProvider = function () { + this._seeds = {}; + } + SeedProvider.prototype.next = function (key) { + var value = this._seeds[key]; + if (value == undefined) { + this._seeds[key] = 0; + return 0; + } else { + this._seeds[key] = ++value; + return value; + } + } + + 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] != compile.rootName) 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, seedProvider, codeWriter, commentWriter) { + + var builderName = ast[2][0][2][0][1]; + var funcAst = ast[2][0][2][1]; + + var jscexTreeGenerator = new JscexTreeGenerator(builderName, seedProvider); + var jscexAst = jscexTreeGenerator.generate(funcAst); + + commentWriter.write(builderName + " << "); + var codeGenerator = new CodeGenerator(builderName, seedProvider, codeWriter, commentWriter); + codeGenerator.generate(funcAst[2], jscexAst); + } + + var JscexTreeGenerator = function (builderName, seedProvider) { + this._binder = Jscex.binders[builderName]; + this._seedProvider = seedProvider; + } + JscexTreeGenerator.prototype = { + + generate: function (ast) { + + var params = ast[2], statements = ast[3]; + + var rootAst = { type: "delay", stmts: [] }; + + this._visitStatements(statements, rootAst.stmts); + + return rootAst; + }, + + _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 forStmt = { type: "for", bodyStmt: { type: "delay", stmts: bodyStmts } }; + delayStmt.stmts.push(forStmt); + + var condition = ast[2]; + if (condition) { + forStmt.condition = condition; + } + + var update = ast[3]; + if (update) { + forStmt.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 forInStmt = { type: "for-in", bodyStmts: bodyStmts, obj: ast[3] }; + + var argName = ast[2][1]; // ast[2] == ["name", m] + if (ast[1][0] == "var") { + forInStmt.argName = argName; + } else { + var keyVar = "_forInKey_$" + this._seedProvider.next("forInKey"); + forInStmt.argName = keyVar; + forInStmt.bodyStmts.unshift({ + type: "raw", + stmt: Jscex.parse(argName + " = " + keyVar + ";")[1][0] + }); + } + + return forInStmt; + }, + + "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: "while", 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 doStmt = { + type: "do", + bodyStmt: { type: "delay", stmts: bodyStmts }, + condition: ast[1] + }; + + return doStmt; + }, + + "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; + } + } + } + } + + var CodeGenerator = function (builderName, seedProvider, codeWriter, commentWriter) { + this._builderName = builderName; + this._binder = Jscex.binders[builderName]; + this._seedProvider = seedProvider; + + this._codeWriter = codeWriter; + this._commentWriter = commentWriter; + } + CodeGenerator.prototype = { + + _code: function () { + this._codeWriter.write.apply(this._codeWriter, arguments); + return this; + }, + + _codeLine: function () { + this._codeWriter.writeLine.apply(this._codeWriter, arguments); + return this; + }, + + _codeIndents: function () { + this._codeWriter.writeIndents(); + return this; + }, + + _codeIndentLevel: function (diff) { + this._codeWriter.addIndentLevel(diff); + return this; + }, + + _comment: function () { + this._commentWriter.write.apply(this._commentWriter, arguments); + return this; + }, + + _commentLine: function () { + this._commentWriter.writeLine.apply(this._commentWriter, arguments); + return this; + }, + + _commentIndents: function () { + this._commentWriter.writeIndents(); + return this; + }, + + _commentIndentLevel: function (diff) { + this._commentWriter.addIndentLevel(diff); + return this; + }, + + _both: function () { + this._codeWriter.write.apply(this._codeWriter, arguments); + this._commentWriter.write.apply(this._commentWriter, arguments); + + return this; + }, + + _bothLine: function () { + this._codeWriter.writeLine.apply(this._codeWriter, arguments); + this._commentWriter.writeLine.apply(this._commentWriter, arguments); + + return this; + }, + + _bothIndents: function () { + this._codeWriter.writeIndents(); + this._commentWriter.writeIndents(); + + return this; + }, + + _bothIndentLevel: function (diff) { + this._codeWriter.addIndentLevel(diff); + this._commentWriter.addIndentLevel(diff); + + return this; + }, + + _newLine: function () { + this._codeWriter.writeLine.apply(this._codeWriter, arguments); + this._commentWriter.writeLine(); // To Remove + return this; + }, + + generate: function (params, jscexAst) { + this._normalMode = false; + this._builderVar = "_builder_$" + this._seedProvider.next("builderId"); + + this._codeLine("(function (" + params.join(", ") + ") {")._commentLine("function (" + params.join(", ") + ") {"); + this._bothIndentLevel(1); + + this._codeIndents()._newLine("var " + this._builderVar + " = " + compile.rootName + ".builders[" + stringify(this._builderName) + "];"); + + this._codeIndents()._newLine("return " + this._builderVar + ".Start(this,"); + this._codeIndentLevel(1); + + this._pos = { }; + + this._bothIndents()._visitJscex(jscexAst)._newLine(); + this._codeIndentLevel(-1); + + this._codeIndents()._newLine(");"); + this._bothIndentLevel(-1); + + this._bothIndents()._code("})")._comment("}"); + }, + + _visitJscex: function (ast) { + this._jscexVisitors[ast.type].call(this, ast); + return this; + }, + + _visitRaw: function (ast) { + var type = ast[0]; + + var visitor = this._rawVisitors[type]; + if (visitor) { + visitor.call(this, ast); + } else { + throw new Error('"' + type + '" is not currently supported.'); + } + + 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._bothIndents()._visitJscex(stmt)._newLine(); + } else if (stmt.type == "delay") { + this._visitJscexStatements(stmt.stmts); + } else { + this._bothIndents()._code("return ")._visitJscex(stmt)._newLine(";"); + } + } + }, + + _visitRawStatements: function (statements) { + for (var i = 0; i < statements.length; i++) { + var s = statements[i]; + + this._bothIndents()._visitRaw(s)._bothLine(); + + switch (s[0]) { + case "break": + case "return": + case "continue": + case "throw": + return; + } + } + }, + + _visitRawBody: function (body) { + if (body[0] == "block") { + this._visitRaw(body); + } else { + this._bothLine(); + this._bothIndentLevel(1); + + this._bothIndents()._visitRaw(body); + this._bothIndentLevel(-1); + } + + return this; + }, + + _visitRawFunction: function (ast) { + var funcName = ast[1] || ""; + var args = ast[2]; + var statements = ast[3]; + + this._bothLine("function " + funcName + "(" + args.join(", ") + ") {") + this._bothIndentLevel(1); + + var currInFunction = this._pos.inFunction; + this._pos.inFunction = true; + + this._visitRawStatements(statements); + this._bothIndentLevel(-1); + + this._pos.inFunction = currInFunction; + + this._bothIndents()._both("}"); + }, + + _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 "for": + case "for-in": + case "while": + case "do": + case "try": + this._visitJscex(subStmt); + return; + case "return": + if (!subStmt.stmt[1]) { + this._visitJscex(subStmt); + return; + } + } + } + + this._newLine(this._builderVar + ".Delay(function () {"); + this._codeIndentLevel(1); + + this._visitJscexStatements(ast.stmts); + this._codeIndentLevel(-1); + + this._codeIndents()._code("})"); + }, + + "combine": function (ast) { + this._newLine(this._builderVar + ".Combine("); + this._codeIndentLevel(1); + + this._bothIndents()._visitJscex(ast.first)._newLine(","); + this._bothIndents()._visitJscex(ast.second)._newLine(); + this._codeIndentLevel(-1); + + this._codeIndents()._code(")"); + }, + + "for": function (ast) { + if (ast.condition) { + this._codeLine(this._builderVar + ".For(function () {") + ._commentLine("for ("); + this._codeIndentLevel(1); + + this._bothIndents() + ._code("return ") + ._comment("; ") + ._visitRaw(ast.condition) + ._newLine(";"); + this._codeIndentLevel(-1); + + this._bothIndents()._code("}, "); + } else { + this._code(this._builderVar + ".For(null, ") + ._comment("for (; "); + } + + if (ast.update) { + this._newLine("function () {"); + this._codeIndentLevel(1); + + this._bothIndents() + ._comment("; ") + ._visitRaw(ast.update) + ._codeLine(";") + ._commentLine(") {"); + this._codeIndentLevel(-1); + + this._codeIndents()._newLine("},"); + } else { + this._codeLine("null,")._commentLine("; ) {"); + } + this._bothIndentLevel(1); + + this._bothIndents()._visitJscex(ast.bodyStmt)._newLine(); + this._bothIndentLevel(-1); + + this._bothIndents()._code(")")._comment("}"); + }, + + "for-in": function (ast) { + this._code(this._builderVar + ".ForIn(") + ._comment("for (var " + ast.argName + " in ") + ._visitRaw(ast.obj) + ._codeLine(", function (" + ast.argName + ") {") + ._commentLine(") {"); + this._bothIndentLevel(1); + + this._visitJscexStatements(ast.bodyStmts); + this._bothIndentLevel(-1); + + this._bothIndents()._code("})")._comment("}"); + }, + + "while": function (ast) { + this._newLine(this._builderVar + ".While(function () {"); + this._codeIndentLevel(1); + + this._bothIndents() + ._code("return ") + ._comment("while (") + ._visitRaw(ast.condition) + ._codeLine(";") + ._commentLine(") {"); + this._codeIndentLevel(-1); + + this._codeIndents()._newLine("},"); + this._bothIndentLevel(1); + + this._bothIndents()._visitJscex(ast.bodyStmt)._newLine(); + this._bothIndentLevel(-1); + + this._bothIndents()._code(")")._comment("}"); + }, + + "do": function (ast) { + this._codeLine(this._builderVar + ".Do(")._commentLine("do {"); + this._bothIndentLevel(1); + + this._bothIndents()._visitJscex(ast.bodyStmt)._newLine(","); + this._commentIndentLevel(-1); + + this._codeIndents()._newLine("function () {"); + this._codeIndentLevel(1); + + this._bothIndents() + ._code("return ") + ._comment("} while (") + ._visitRaw(ast.condition) + ._codeLine(";") + ._commentLine(");"); + this._codeIndentLevel(-1); + + this._codeIndents()._newLine("}"); + this._codeIndentLevel(-1); + + this._codeIndents()._code(")"); + }, + + "raw": function (ast) { + this._visitRaw(ast.stmt, true); + }, + + "bind": function (ast) { + var info = ast.info; + + var commentPrefix = ""; + if (info.assignee == "return") { + commentPrefix = "return "; + } else if (info.argName != "") { + commentPrefix = "var " + info.argName + " = "; + } + + this._code(this._builderVar + ".Bind(")._comment(commentPrefix + this._binder + "(")._visitRaw(info.expression)._comment(");")._newLine(", function (" + info.argName + ") {"); + this._codeIndentLevel(1); + + if (info.assignee == "return") { + this._codeIndents() + ._newLine("return " + this._builderVar + ".Return(" + info.argName + ");"); + } else { + if (info.assignee) { + this._bothIndents() + ._visitRaw(info.assignee)._bothLine(" = " + info.argName + ";"); + } + + this._visitJscexStatements(ast.stmts); + } + this._codeIndentLevel(-1); + + this._codeIndents() + ._code("})"); + }, + + "if": function (ast) { + + for (var i = 0; i < ast.conditionStmts.length; i++) { + var stmt = ast.conditionStmts[i]; + + this._both("if (")._visitRaw(stmt.cond)._bothLine(") {"); + this._bothIndentLevel(1); + + this._visitJscexStatements(stmt.stmts); + this._bothIndentLevel(-1); + + if (i < ast.conditionStmts.length - 1 || ast.elseStmts) { + this._bothIndents()._both("} else "); + } else { + this._bothIndents()._code("} else ")._comment("}"); + } + } + + if (ast.elseStmts) { + this._bothLine("{"); + this._bothIndentLevel(1); + } else { + this._newLine("{"); + this._codeIndentLevel(1); + } + + if (ast.elseStmts) { + this._visitJscexStatements(ast.elseStmts); + } else { + this._codeIndents() + ._newLine("return " + this._builderVar + ".Normal();"); + } + + if (ast.elseStmts) { + this._bothIndentLevel(-1); + } else { + this._codeIndentLevel(-1); + } + + if (ast.elseStmts) { + this._bothIndents() + ._both("}"); + } else { + this._codeIndents() + ._code("}"); + } + }, + + "switch": function (ast) { + this._both("switch (")._visitRaw(ast.item)._bothLine(") {"); + this._bothIndentLevel(1); + + for (var i = 0; i < ast.caseStmts.length; i++) { + var caseStmt = ast.caseStmts[i]; + + if (caseStmt.item) { + this._bothIndents() + ._both("case ")._visitRaw(caseStmt.item)._bothLine(":"); + } else { + this._bothIndents()._bothLine("default:"); + } + this._bothIndentLevel(1); + + this._visitJscexStatements(caseStmt.stmts); + this._bothIndentLevel(-1); + } + + this._bothIndents()._code("}"); + }, + + "try": function (ast) { + this._codeLine(this._builderVar + ".Try(")._commentLine("try {"); + this._bothIndentLevel(1); + + this._bothIndents()._visitJscex(ast.bodyStmt)._newLine(","); + this._commentIndentLevel(-1); + + if (ast.catchStmts) { + this._bothIndents() + ._codeLine("function (" + ast.exVar + ") {") + ._commentLine("} catch (" + ast.exVar + ") {"); + this._bothIndentLevel(1); + + this._visitJscexStatements(ast.catchStmts); + this._bothIndentLevel(-1); + + this._bothIndents()._codeLine("},"); + if (ast.finallyStmt) { + this._commentLine("} finally {"); + } else { + this._commentLine("}"); + } + } else { + this._bothIndents()._codeLine("null,")._commentLine("} finally {"); + } + + if (ast.finallyStmt) { + this._commentIndentLevel(1); + this._bothIndents()._visitJscex(ast.finallyStmt)._newLine(); + this._commentIndentLevel(-1); + } else { + this._codeIndents()._newLine("null"); + } + this._codeIndentLevel(-1); + + this._codeIndents()._code(")"); + if (ast.finallyStmt) { + this._commentIndents()._comment("}"); + } + }, + + "normal": function (ast) { + this._code(this._builderVar + ".Normal()"); + }, + + "throw": function (ast) { + this + ._code(this._builderVar + ".Throw(") + ._comment("throw ") + ._visitRaw(ast.stmt[1]) + ._code(")")._comment(";"); + }, + + "break": function (ast) { + this._code(this._builderVar + ".Break()")._comment("break;"); + }, + + "continue": function (ast) { + this._code(this._builderVar + ".Continue()")._comment("continue;"); + }, + + "return": function (ast) { + this._code(this._builderVar + ".Return(")._comment("return"); + if (ast.stmt[1]) { + this._comment(" ")._visitRaw(ast.stmt[1]); + } + + this._code(")")._comment(";"); + } + }, + + _rawVisitors: { + "var": function (ast) { + this._both("var "); + + var items = ast[1]; + for (var i = 0; i < items.length; i++) { + this._both(items[i][0]); + if (items[i].length > 1) { + this._both(" = ")._visitRaw(items[i][1]); + } + if (i < items.length - 1) this._both(", "); + } + + this._both(";"); + }, + + "seq": function (ast, noBracket) { + var left = ast[1]; + var right = ast[2]; + + if (!noBracket) this._both("("); + + this._visitRaw(left); + this._both(", "); + + if (right[0] == "seq") { + arguments.callee.call(this, right, true); + } else { + this._visitRaw(right); + } + + if (!noBracket) this._both(")"); + }, + + "binary": function (ast) { + var op = ast[1], left = ast[2], right = ast[3]; + + if (getPrecedence(ast) < getPrecedence(left)) { + this._both("(")._visitRaw(left)._both(") "); + } else { + this._visitRaw(left)._both(" "); + } + + this._both(op); + + if (getPrecedence(ast) <= getPrecedence(right)) { + this._both(" (")._visitRaw(right)._both(")"); + } else { + this._both(" ")._visitRaw(right); + } + }, + + "sub": function (ast) { + var prop = ast[1], index = ast[2]; + + if (getPrecedence(ast) < getPrecedence(prop)) { + this._both("(")._visitRaw(prop)._both(")[")._visitRaw(index)._both("]"); + } else { + this._visitRaw(prop)._both("[")._visitRaw(index)._both("]"); + } + }, + + "unary-postfix": function (ast) { + var op = ast[1]; + var item = ast[2]; + + if (getPrecedence(ast) <= getPrecedence(item)) { + this._both("(")._visitRaw(item)._both(")"); + } else { + this._visitRaw(item); + } + + this._both(" " + op); + }, + + "unary-prefix": function (ast) { + var op = ast[1]; + var item = ast[2]; + + this._both(op + " "); + + if (getPrecedence(ast) < getPrecedence(item)) { + this._both("(")._visitRaw(item)._both(")"); + } else { + this._visitRaw(item); + } + }, + + "assign": function (ast) { + var op = ast[1]; + var name = ast[2]; + var value = ast[3]; + + if (name[0] == "assign") { + this._both("(")._visitRaw(name)._both(")"); + } else { + this._visitRaw(name); + } + + if ((typeof op) == "string") { + this._both(" " + op + "= "); + } else { + this._both(" = "); + } + + this._visitRaw(value); + }, + + "stat": function (ast) { + this._visitRaw(ast[1])._both(";"); + }, + + "dot": function (ast) { + var left = ast[1]; + var right = ast[2]; + + if (getPrecedence(ast) < getPrecedence(left)) { + this._both("(")._visitRaw(left)._both(").")._both(right); + } else { + this._visitRaw(left)._both(".")._both(right); + } + }, + + "new": function (ast) { + var ctor = ast[1]; + + this._both("new ")._visitRaw(ctor)._both("("); + + var args = ast[2]; + for (var i = 0, len = args.length; i < len; i++) { + this._visitRaw(args[i]); + if (i < len - 1) this._both(", "); + } + + this._both(")"); + }, + + "call": function (ast) { + + if (isJscexPattern(ast)) { + compileJscexPattern(ast, this._seedProvider, this._codeWriter, this._commentWriter); + } else { + var caller = ast[1]; + + var invalidBind = (caller[0] == "name") && (caller[1] == this._binder); + // throw? + + if (getPrecedence(ast) < getPrecedence(caller)) { + this._both("(")._visitRaw(caller)._both(")"); + } else { + this._visitRaw(caller); + } + + this._both("("); + + var args = ast[2]; + for (var i = 0; i < args.length; i++) { + this._visitRaw(args[i]); + if (i < args.length - 1) this._both(", "); + } + + this._both(")"); + } + }, + + "name": function (ast) { + this._both(ast[1]); + }, + + "object": function (ast) { + var items = ast[1]; + if (items.length <= 0) { + this._both("{ }"); + } else { + this._bothLine("{"); + this._bothIndentLevel(1); + + for (var i = 0; i < items.length; i++) { + this._bothIndents() + ._both(stringify(items[i][0]) + ": ") + ._visitRaw(items[i][1]); + + if (i < items.length - 1) { + this._bothLine(","); + } else { + this._bothLine(""); + } + } + + this._bothIndentLevel(-1); + this._bothIndents()._both("}"); + } + }, + + "array": function (ast) { + this._both("["); + + var items = ast[1]; + for (var i = 0; i < items.length; i++) { + this._visitRaw(items[i]); + if (i < items.length - 1) this._both(", "); + } + + this._both("]"); + }, + + "num": function (ast) { + this._both(ast[1]); + }, + + "regexp": function (ast) { + this._both("/" + ast[1] + "/" + ast[2]); + }, + + "string": function (ast) { + this._both(stringify(ast[1])); + }, + + "function": function (ast) { + this._visitRawFunction(ast); + }, + + "defun": function (ast) { + this._visitRawFunction(ast); + }, + + "for": function (ast) { + this._both("for ("); + + var setup = ast[1]; + if (setup) { + this._visitRaw(setup); + if (setup[0] != "var") { + this._both("; "); + } else { + this._both(" "); + } + } else { + this._both("; "); + } + + var condition = ast[2]; + if (condition) this._visitRaw(condition); + this._both("; "); + + var update = ast[3]; + if (update) this._visitRaw(update); + this._both(") "); + + 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._both("for ("); + + var declare = ast[1]; + if (declare[0] == "var") { // declare == ["var", [["m"]]] + this._both("var " + declare[1][0][0]); + } else { + this._visitRaw(declare); + } + + this._both(" in ")._visitRaw(ast[3])._both(") "); + + var body = ast[4]; + this._visitRawBody(body); + }, + + "block": function (ast) { + this._bothLine("{") + this._bothIndentLevel(1); + + this._visitRawStatements(ast[1]); + this._bothIndentLevel(-1); + + this._bothIndents() + ._both("}"); + }, + + "while": function (ast) { + var condition = ast[1]; + var body = ast[2]; + + var currInLoop = this._pos.inLoop; + this._pos.inLoop = true; + + this._both("while (")._visitRaw(condition)._both(") ")._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._both("do ")._visitRawBody(body); + + this._pos.inLoop = currInLoop; + + if (body[0] == "block") { + this._both(" "); + } else { + this._bothLine() + ._bothIndents(); + } + + this._both("while (")._visitRaw(condition)._both(");"); + }, + + "if": function (ast) { + var condition = ast[1]; + var thenPart = ast[2]; + + this._both("if (")._visitRaw(condition)._both(") ")._visitRawBody(thenPart); + + var elsePart = ast[3]; + if (elsePart) { + if (thenPart[0] == "block") { + this._both(" "); + } else { + this._bothLine("") + ._bothIndents(); + } + + if (elsePart[0] == "if") { + this._both("else ")._visitRaw(elsePart); + } else { + this._both("else ")._visitRawBody(elsePart); + } + } + }, + + "break": function (ast) { + if (this._pos.inLoop || this._pos.inSwitch) { + this._both("break;"); + } else { + this._code("return ")._visitJscex({ type: "break", stmt: ast })._code(";"); + } + }, + + "continue": function (ast) { + if (this._pos.inLoop) { + this._both("continue;"); + } else { + this._code("return ")._visitJscex({ type: "continue", stmt: ast })._code(";"); + } + }, + + "return": function (ast) { + if (this._pos.inFunction) { + this._both("return"); + var value = ast[1]; + if (value) this._both(" ")._visitRaw(value); + this._both(";"); + } else { + this._code("return ")._visitJscex({ type: "return", stmt: ast })._code(";"); + } + }, + + "throw": function (ast) { + var pos = this._pos; + if (pos.inTry || pos.inFunction) { + this._both("throw ")._visitRaw(ast[1])._both(";"); + } else { + this._code("return ")._visitJscex({ type: "throw", stmt: ast })._code(";"); + } + }, + + "conditional": function (ast) { + this._both("(")._visitRaw(ast[1])._both(") ? (")._visitRaw(ast[2])._both(") : (")._visitRaw(ast[3])._both(")"); + }, + + "try": function (ast) { + + this._bothLine("try {"); + this._bothIndentLevel(1); + + var currInTry = this._pos.inTry; + this._pos.inTry = true; + + this._visitRawStatements(ast[1]); + this._bothIndentLevel(-1); + + this._pos.inTry = currInTry; + + var catchClause = ast[2]; + var finallyStatements = ast[3]; + + if (catchClause) { + this._bothIndents() + ._bothLine("} catch (" + catchClause[0] + ") {") + this._bothIndentLevel(1); + + this._visitRawStatements(catchClause[1]); + this._bothIndentLevel(-1); + } + + if (finallyStatements) { + this._bothIndents() + ._bothLine("} finally {"); + this._bothIndentLevel(1); + + this._visitRawStatements(finallyStatements); + this._bothIndentLevel(-1); + } + + this._bothIndents() + ._both("}"); + }, + + "switch": function (ast) { + this._both("switch (")._visitRaw(ast[1])._bothLine(") {"); + this._bothIndentLevel(1); + + 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._bothIndents(); + + if (c[0]) { + this._both("case ")._visitRaw(c[0])._bothLine(":"); + } else { + this._bothLine("default:"); + } + this._bothIndentLevel(1); + + this._visitRawStatements(c[1]); + this._bothIndentLevel(-1); + } + this._bothIndentLevel(-1); + + this._pos.inSwitch = currInSwitch; + + this._bothIndents() + ._both("}"); + } + } + }; + + var merge = function (commentLines, codeLines) { + var length = commentLines.length; + + var maxShift = 0; + + for (var i = 0; i < length; i++) { + var matches = codeLines[i].match(" +"); + var spaceLength = matches ? matches[0].length : 0; + + var shift = commentLines[i].length - spaceLength + 10; + if (shift > maxShift) { + maxShift = shift; + } + } + + var shiftBuffer = new Array(maxShift); + for (var i = 0; i < maxShift; i++) { + shiftBuffer[i] = " "; + } + + var shiftSpaces = shiftBuffer.join(""); + + var buffer = []; + for (var i = 0; i < length; i++) { + var comment = commentLines[i]; + if (comment.replace(/ +/g, "").length > 0) { + comment = "/* " + comment + " */ "; + } + + var code = shiftSpaces + codeLines[i]; + + buffer.push(comment); + buffer.push(code.substring(comment.length)); + buffer.push("\n"); + } + + return buffer.join(""); + } + + var compile = function (builderName, func, separateCodeAndComment) { + var funcCode = func.toString(); + var evalCode = "eval(" + compile.rootName + ".compile(" + stringify(builderName) + ", " + funcCode + "))" + var evalCodeAst = Jscex.parse(evalCode); + + var codeWriter = new CodeWriter(); + var commentWriter = new CodeWriter(); + + // [ "toplevel", [ [ "stat", [ "call", ... ] ] ] ] + var evalAst = evalCodeAst[1][0][1]; + compileJscexPattern(evalAst, new SeedProvider(), codeWriter, commentWriter); + + if (separateCodeAndComment) { + return { + code: codeWriter.lines.join("\n"), + codeLines: codeWriter.lines, + comment: commentWriter.lines.join("\n"), + commentLines: commentWriter.lines + }; + } else { + var newCode = merge(commentWriter.lines, codeWriter.lines); + Jscex.logger.debug("// Original: \r\n" + funcCode + "\r\n\r\n// Jscexified: \r\n" + newCode + "\r\n"); + + return codeGenerator(newCode); + } + } + + compile.rootName = "Jscex"; + + // CommonJS + var isCommonJS = !!(typeof require === "function" && typeof module !== "undefined" && module.exports); + // CommonJS AMD + var isAmd = !!(typeof require === "function" && typeof define === "function" && define.amd); + + var defineModule = function () { + Jscex.define({ + name: "jit", + version: "0.6.6", + exports: isCommonJS && module.exports, + require: isCommonJS && require, + autoloads: [ "parser" ], + dependencies: { parser: "~0.6.5" }, + init: function () { + Jscex.compile = compile; + } + }); + } + + if (isCommonJS) { + try { + Jscex = require("./jscex"); + } catch (ex) { + Jscex = require("jscex"); + } + + defineModule(); + } else if (isAmd) { + require(["jscex"], function (jscex) { + Jscex = jscex; + defineModule(); + }); + } else { + var Fn = Function, global = Fn('return this')(); + if (!global.Jscex) { + throw new Error('Missing the root object, please load "jscex" component first.'); + } + + Jscex = global.Jscex; + defineModule(); + } +})();