From: Ingvar Stepanyan Date: Fri, 1 Aug 2014 12:13:31 +0000 (+0300) Subject: Improved UglifyJS<->SpiderMonkey AST conversions. X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=0e3ff1f970b173dddc4f624a8935925ea4cfca3e;p=UglifyJS.git Improved UglifyJS<->SpiderMonkey AST conversions. * Added directives recognition in SM AST. * Moved semi-standard SM `Property` type to separate handler. * Added `const` recognition from SM AST. * Removed redundant `this`-as-identifier recognition. * Removed redundant rules for abstract SM types. * Described `CatchClause` using string syntax. * Added support for semi-standard `range` tuple as location source. * Added back-conversion support (to be improved). --- diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index bc24dfd6..2d35fb26 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -46,7 +46,22 @@ (function(){ var MOZ_TO_ME = { - TryStatement : function(M) { + ExpressionStatement: function(M) { + var expr = M.expression; + if (expr.type === "Literal" && typeof expr.value === "string") { + return new AST_Directive({ + start: my_start_token(M), + end: my_end_token(M), + value: expr.value + }); + } + return new AST_SimpleStatement({ + start: my_start_token(M), + end: my_end_token(M), + body: from_moz(expr) + }); + }, + TryStatement: function(M) { return new AST_Try({ start : my_start_token(M), end : my_end_token(M), @@ -55,44 +70,40 @@ bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null }); }, - CatchClause : function(M) { - return new AST_Catch({ - start : my_start_token(M), - end : my_end_token(M), - argname : from_moz(M.param), - body : from_moz(M.body).body - }); + Property: function(M) { + var key = M.key; + var name = key.type == "Identifier" ? key.name : key.value; + var args = { + start : my_start_token(key), + end : my_end_token(M.value), + key : name, + value : from_moz(M.value) + }; + switch (M.kind) { + case "init": + return new AST_ObjectKeyVal(args); + case "set": + args.value.name = from_moz(key); + return new AST_ObjectSetter(args); + case "get": + args.value.name = from_moz(key); + return new AST_ObjectGetter(args); + } }, - ObjectExpression : function(M) { + ObjectExpression: function(M) { return new AST_Object({ start : my_start_token(M), end : my_end_token(M), properties : M.properties.map(function(prop){ - var key = prop.key; - var name = key.type == "Identifier" ? key.name : key.value; - var args = { - start : my_start_token(key), - end : my_end_token(prop.value), - key : name, - value : from_moz(prop.value) - }; - switch (prop.kind) { - case "init": - return new AST_ObjectKeyVal(args); - case "set": - args.value.name = from_moz(key); - return new AST_ObjectSetter(args); - case "get": - args.value.name = from_moz(key); - return new AST_ObjectGetter(args); - } + prop.type = "Property"; + return from_moz(prop) }) }); }, - SequenceExpression : function(M) { + SequenceExpression: function(M) { return AST_Seq.from_array(M.expressions.map(from_moz)); }, - MemberExpression : function(M) { + MemberExpression: function(M) { return new (M.computed ? AST_Sub : AST_Dot)({ start : my_start_token(M), end : my_end_token(M), @@ -100,7 +111,7 @@ expression : from_moz(M.object) }); }, - SwitchCase : function(M) { + SwitchCase: function(M) { return new (M.test ? AST_Case : AST_Default)({ start : my_start_token(M), end : my_end_token(M), @@ -108,7 +119,14 @@ body : M.consequent.map(from_moz) }); }, - Literal : function(M) { + VariableDeclaration: function(M) { + return new (M.kind === "const" ? AST_Const : AST_Var)({ + start : my_start_token(M), + end : my_end_token(M), + definitions : M.declarations.map(from_moz) + }); + }, + Literal: function(M) { var val = M.value, args = { start : my_start_token(M), end : my_end_token(M) @@ -132,8 +150,7 @@ UpdateExpression: From_Moz_Unary, Identifier: function(M) { var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; - return new (M.name == "this" ? AST_This - : p.type == "LabeledStatement" ? AST_Label + return new ( p.type == "LabeledStatement" ? AST_Label : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar) : p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) @@ -147,6 +164,136 @@ } }; + AST_Directive.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "ExpressionStatement", + expression: set_moz_loc(this, { + type: "Literal", + value: this.value + }) + }); + }; + + AST_SimpleStatement.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "ExpressionStatement", + expression: to_moz(this.body) + }); + }; + + AST_SwitchBranch.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "SwitchCase", + test: to_moz(this.expression), + consequent: this.body.map(to_moz) + }); + }; + + AST_Try.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "TryStatement", + block: { + type: "BlockStatement", + body: this.body.map(to_moz) + }, + handler: to_moz(this.bcatch), + finalizer: this.bfinally ? this.bfinally.body.map(to_moz) : null + }); + }; + + AST_Definitions.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "VariableDeclaration", + kind: this instanceof AST_Const ? "const" : "var", + declarations: this.definitions.map(to_moz) + }); + }; + + AST_Seq.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "SequenceExpression", + expressions: this.to_array().map(to_moz) + }); + }; + + AST_PropAccess.prototype.to_mozilla_ast = function() { + var isComputed = this instanceof AST_Sub; + return set_moz_loc(this, { + type: "MemberExpression", + object: to_moz(this.expression), + computed: isComputed, + property: isComputed ? to_moz(this.property) : {type: "Identifier", name: this.property} + }); + }; + + AST_Unary.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: this.operator == "++" || this.operator == "--" ? "UpdateExpression" : "UnaryExpression", + operator: this.operator, + prefix: this instanceof AST_UnaryPrefix, + argument: to_moz(this.argument) + }); + }; + + AST_Object.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "ObjectExpression", + properties: this.properties.map(to_moz) + }); + }; + + AST_ObjectProperty.prototype.to_mozilla_ast = function() { + var key; + if (is_identifier_string(this.key) && !RESERVED_WORDS(this.key)) { + key = {type: "Identifier", name: this.key}; + } else { + key = {type: "Literal", value: this.key}; + } + var kind; + if (this instanceof AST_ObjectKeyVal) { + kind = "init"; + } else + if (this instanceof AST_ObjectGetter) { + kind = "get"; + } else + if (this instanceof AST_ObjectSetter) { + kind = "set"; + } + return set_moz_loc(this, { + type: "Property", + kind: kind, + key: key, + value: to_moz(this.value) + }); + }; + + AST_Symbol.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "Identifier", + name: this.name + }); + }; + + AST_Null.prototype.to_mozilla_ast = + AST_Boolean.prototype.to_mozilla_ast = + AST_Constant.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "Literal", + value: this.value + }); + }; + + AST_Atom.prototype.to_mozilla_ast = function() { + return set_moz_loc(this, { + type: "Identifier", + name: String(this.value) + }); + }; + + AST_Hole.prototype.to_mozilla_ast = function() { + return null; + }; + function From_Moz_Unary(M) { var prefix = "prefix" in M ? M.prefix : M.type == "UnaryExpression" ? true : false; @@ -158,14 +305,9 @@ }); }; - var ME_TO_MOZ = {}; - - map("Node", AST_Node); map("Program", AST_Toplevel, "body@body"); - map("Function", AST_Function, "id>name, params@argnames, body%body"); map("EmptyStatement", AST_EmptyStatement); map("BlockStatement", AST_BlockStatement, "body@body"); - map("ExpressionStatement", AST_SimpleStatement, "expression>body"); map("IfStatement", AST_If, "test>condition, consequent>body, alternate>alternative"); map("LabeledStatement", AST_LabeledStatement, "label>label, body>body"); map("BreakStatement", AST_Break, "label>label"); @@ -180,74 +322,137 @@ map("ForInStatement", AST_ForIn, "left>init, right>object, body>body"); map("DebuggerStatement", AST_Debugger); map("FunctionDeclaration", AST_Defun, "id>name, params@argnames, body%body"); - map("VariableDeclaration", AST_Var, "declarations@definitions"); map("VariableDeclarator", AST_VarDef, "id>name, init>value"); + map("CatchClause", AST_Catch, "param>argname, body%body"); map("ThisExpression", AST_This); map("ArrayExpression", AST_Array, "elements@elements"); map("FunctionExpression", AST_Function, "id>name, params@argnames, body%body"); map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right"); - map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right"); map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right"); + map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right"); map("ConditionalExpression", AST_Conditional, "test>condition, consequent>consequent, alternate>alternative"); map("NewExpression", AST_New, "callee>expression, arguments@args"); map("CallExpression", AST_Call, "callee>expression, arguments@args"); + AST_Accessor.prototype.to_mozilla_ast = AST_Function.prototype.to_mozilla_ast; + + AST_Binary.prototype.to_mozilla_ast = function () { + return { + type: this.operator == "&&" || this.operator == "||" ? "LogicalExpression" : "BinaryExpression", + left: to_moz(this.left), + operator: this.operator, + right: to_moz(this.right) + }; + }; + /* -----[ tools ]----- */ function my_start_token(moznode) { + var loc = moznode.loc; + var range = moznode.range; return new AST_Token({ - file : moznode.loc && moznode.loc.source, - line : moznode.loc && moznode.loc.start.line, - col : moznode.loc && moznode.loc.start.column, - pos : moznode.start, - endpos : moznode.start + file : loc && loc.source, + line : loc && loc.start.line, + col : loc && loc.start.column, + pos : range ? range[0] : moznode.start, + endpos : range ? range[0] : moznode.start }); }; function my_end_token(moznode) { + var loc = moznode.loc; + var range = moznode.range; return new AST_Token({ - file : moznode.loc && moznode.loc.source, - line : moznode.loc && moznode.loc.end.line, - col : moznode.loc && moznode.loc.end.column, - pos : moznode.end, - endpos : moznode.end + file : loc && loc.source, + line : loc && loc.end.line, + col : loc && loc.end.column, + pos : range ? range[1] : moznode.end, + endpos : range ? range[1] : moznode.end }); }; + function moz_sub_loc(token) { + return token.line ? { + line: token.line, + column: token.col + } : null; + }; + + function set_moz_loc(mynode, moznode) { + var start = mynode.start; + var end = mynode.end; + if (start.pos != null && end.pos != null) { + moznode.range = [start.pos, end.pos]; + } + if (start.line) { + moznode.loc = { + start: moz_sub_loc(start), + end: moz_sub_loc(end) + }; + if (start.file) { + moznode.loc.source = start.file; + } + } + return moznode; + }; + function map(moztype, mytype, propmap) { var moz_to_me = "function From_Moz_" + moztype + "(M){\n"; moz_to_me += "return new mytype({\n" + "start: my_start_token(M),\n" + "end: my_end_token(M)"; + var me_to_moz = "function To_Moz_" + moztype + "(){\n"; + me_to_moz += "return set_moz_loc(this, {\n" + + "type: moztype"; + if (propmap) propmap.split(/\s*,\s*/).forEach(function(prop){ var m = /([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)/i.exec(prop); if (!m) throw new Error("Can't understand property map: " + prop); - var moz = "M." + m[1], how = m[2], my = m[3]; + var moz = m[1], how = m[2], my = m[3]; moz_to_me += ",\n" + my + ": "; - if (how == "@") { - moz_to_me += moz + ".map(from_moz)"; - } else if (how == ">") { - moz_to_me += "from_moz(" + moz + ")"; - } else if (how == "=") { - moz_to_me += moz; - } else if (how == "%") { - moz_to_me += "from_moz(" + moz + ").body"; - } else throw new Error("Can't understand operator in propmap: " + prop); + me_to_moz += ",\n" + moz + ": "; + switch (how) { + case "@": + moz_to_me += "M." + moz + ".map(from_moz)"; + me_to_moz += "this." + my + ".map(to_moz)"; + break; + case ">": + moz_to_me += "from_moz(M." + moz + ")"; + me_to_moz += "to_moz(this." + my + ")"; + break; + case "=": + moz_to_me += "M." + moz; + me_to_moz += "this." + my; + break; + case "%": + moz_to_me += "from_moz(M." + moz + ").body"; + me_to_moz += "{type: \"BlockStatement\", body: this." + my + ".map(to_moz)}"; + break; + default: + throw new Error("Can't understand operator in propmap: " + prop); + } }); - moz_to_me += "\n})}"; - // moz_to_me = parse(moz_to_me).print_to_string({ beautify: true }); - // console.log(moz_to_me); + moz_to_me += "\n})\n}"; + me_to_moz += "\n})\n}"; + + moz_to_me = parse(moz_to_me).print_to_string({ beautify: true }); + me_to_moz = parse(me_to_moz).print_to_string({ beautify: true }); + //console.log(moz_to_me); moz_to_me = new Function("mytype", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")( mytype, my_start_token, my_end_token, from_moz ); - return MOZ_TO_ME[moztype] = moz_to_me; + me_to_moz = new Function("moztype", "set_moz_loc", "to_moz", "return(" + me_to_moz + ")")( + moztype, set_moz_loc, to_moz + ); + MOZ_TO_ME[moztype] = moz_to_me; + mytype.prototype.to_mozilla_ast = me_to_moz; }; - var FROM_MOZ_STACK = null; + var FROM_MOZ_STACK = null, TO_MOZ_STACK = null; function from_moz(node) { FROM_MOZ_STACK.push(node); @@ -264,4 +469,8 @@ return ast; }; + function to_moz(node) { + return node != null ? node.to_mozilla_ast() : null; + }; + })();