From 7691bebea525e96cb74d52e0bb8f294cf778c966 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 11 Nov 2015 22:15:25 +0200 Subject: [PATCH] Rework has_directive It's now available during tree walking, i.e. walker.has_directive("use asm"), rather than as part of the scope. It's thus no longer necessary to call `figure_out_scope` before codegen. Added special bits in the code generator to overcome the fact that it doesn't inherit from TreeWalker. Fix #861 --- bin/uglifyjs | 29 +++++++++++++++++------------ lib/ast.js | 30 ++++++++++++++++++++++++------ lib/compress.js | 2 +- lib/output.js | 16 +++++++++++----- lib/parse.js | 11 +++++------ lib/scope.js | 19 ------------------- lib/transform.js | 2 +- tools/node.js | 10 +--------- 8 files changed, 60 insertions(+), 59 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 8d4fe4d9..f7f22215 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -409,14 +409,17 @@ async.eachLimit(files, 1, function (file, cb) { writeNameCache("props", cache); })(); + var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint var TL_CACHE = readNameCache("vars"); - time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); - if (ARGS.lint) { - TOPLEVEL.scope_warnings(); - } - }); + if (SCOPE_IS_NEEDED) { + time_it("scope", function(){ + TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + if (ARGS.lint) { + TOPLEVEL.scope_warnings(); + } + }); + } if (COMPRESS) { time_it("squeeze", function(){ @@ -424,12 +427,14 @@ async.eachLimit(files, 1, function (file, cb) { }); } - time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); - if (MANGLE && !TL_CACHE) { - TOPLEVEL.compute_char_frequency(MANGLE); - } - }); + if (SCOPE_IS_NEEDED) { + time_it("scope", function(){ + TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + if (MANGLE && !TL_CACHE) { + TOPLEVEL.compute_char_frequency(MANGLE); + } + }); + } if (MANGLE) time_it("mangle", function(){ MANGLE.cache = TL_CACHE; diff --git a/lib/ast.js b/lib/ast.js index e7952847..f5225d78 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -85,7 +85,7 @@ function DEFNODE(type, props, methods, base) { return ctor; }; -var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", { +var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file literal", { }, null); var AST_Node = DEFNODE("Node", "start end", { @@ -927,27 +927,36 @@ var AST_True = DEFNODE("True", null, { function TreeWalker(callback) { this.visit = callback; this.stack = []; + this.directives = Object.create(null); }; TreeWalker.prototype = { _visit: function(node, descend) { - this.stack.push(node); + this.push(node); var ret = this.visit(node, descend ? function(){ descend.call(node); } : noop); if (!ret && descend) { descend.call(node); } - this.stack.pop(); + this.pop(node); return ret; }, parent: function(n) { return this.stack[this.stack.length - 2 - (n || 0)]; }, push: function (node) { + if (node instanceof AST_Lambda) { + this.directives = Object.create(this.directives); + } else if (node instanceof AST_Directive) { + this.directives[node.value] = this.directives[node.value] ? "up" : true; + } this.stack.push(node); }, - pop: function() { - return this.stack.pop(); + pop: function(node) { + this.stack.pop(); + if (node instanceof AST_Lambda) { + this.directives = Object.getPrototypeOf(this.directives); + } }, self: function() { return this.stack[this.stack.length - 1]; @@ -960,7 +969,16 @@ TreeWalker.prototype = { } }, has_directive: function(type) { - return this.find_parent(AST_Scope).has_directive(type); + var dir = this.directives[type]; + if (dir) return dir; + var node = this.stack[this.stack.length - 1]; + if (node instanceof AST_Scope) { + for (var i = 0; i < node.body.length; ++i) { + var st = node.body[i]; + if (!(st instanceof AST_Directive)) break; + if (st.value == type) return true; + } + } }, in_boolean_context: function() { var stack = this.stack; diff --git a/lib/compress.js b/lib/compress.js index 50353fe0..3efca411 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -991,7 +991,7 @@ merge(Compressor.prototype, { /* -----[ optimizers ]----- */ OPT(AST_Directive, function(self, compressor){ - if (self.scope.has_directive(self.value) !== self.scope) { + if (compressor.has_directive(self.value) === "up") { return make_node(AST_EmptyStatement, self); } return self; diff --git a/lib/output.js b/lib/output.js index b7f69717..9dadf0e5 100644 --- a/lib/output.js +++ b/lib/output.js @@ -382,8 +382,13 @@ function OutputStream(options) { nodetype.DEFMETHOD("_codegen", generator); }; + var use_asm = false; + AST_Node.DEFMETHOD("print", function(stream, force_parens){ - var self = this, generator = self._codegen; + var self = this, generator = self._codegen, prev_use_asm = use_asm; + if (self instanceof AST_Directive && self.value == "use asm") { + use_asm = true; + } function doit() { self.add_comments(stream); self.add_source_map(stream); @@ -396,6 +401,9 @@ function OutputStream(options) { doit(); } stream.pop_node(); + if (self instanceof AST_Lambda) { + use_asm = prev_use_asm; + } }); AST_Node.DEFMETHOD("print_to_string", function(options){ @@ -1170,10 +1178,8 @@ function OutputStream(options) { output.print_string(self.getValue(), self.quote); }); DEFPRINT(AST_Number, function(self, output){ - if (self.literal !== undefined - && +self.literal === self.value /* paranoid check */ - && self.scope && self.scope.has_directive('use asm')) { - output.print(self.literal); + if (use_asm) { + output.print(self.start.literal); } else { output.print(make_num(self.getValue())); } diff --git a/lib/parse.js b/lib/parse.js index cb35118a..901d10a3 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -285,6 +285,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { nlb : S.newline_before, file : filename }; + if (/^(?:num|string|regexp)$/i.test(type)) { + ret.literal = $TEXT.substring(ret.pos, ret.endpos); + } if (!is_comment) { ret.comments_before = S.comments_before; S.comments_before = []; @@ -335,11 +338,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (prefix) num = prefix + num; var valid = parse_js_number(num); if (!isNaN(valid)) { - var tok = token("num", valid); - if (num.indexOf('.') >= 0) { - tok.literal = num; - } - return tok; + return token("num", valid); } else { parse_error("Invalid syntax: " + num); } @@ -1152,7 +1151,7 @@ function parse($TEXT, options) { ret = _make_symbol(AST_SymbolRef); break; case "num": - ret = new AST_Number({ start: tok, end: tok, value: tok.value, literal: tok.literal }); + ret = new AST_Number({ start: tok, end: tok, value: tok.value }); break; case "string": ret = new AST_String({ diff --git a/lib/scope.js b/lib/scope.js index 06bd65ae..4a3739c4 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -114,15 +114,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ defun = save_defun; return true; // don't descend again in TreeWalker } - if (node instanceof AST_Directive) { - node.scope = scope; - push_uniq(scope.directives, node.value); - return true; - } - if (node instanceof AST_Number) { - node.scope = scope; - return true; - } if (node instanceof AST_With) { for (var s = scope; s; s = s.parent_scope) s.uses_with = true; @@ -202,7 +193,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ }); AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ - this.directives = []; // contains the directives defined in this scope, i.e. "use strict" this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement @@ -213,10 +203,6 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ this.nesting = nesting; // the nesting level of this scope (0 means toplevel) }); -AST_Scope.DEFMETHOD("strict", function(){ - return this.has_directive("use strict"); -}); - AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; @@ -240,11 +226,6 @@ AST_Scope.DEFMETHOD("find_variable", function(name){ || (this.parent_scope && this.parent_scope.find_variable(name)); }); -AST_Scope.DEFMETHOD("has_directive", function(value){ - return this.parent_scope && this.parent_scope.has_directive(value) - || (this.directives.indexOf(value) >= 0 ? this : null); -}); - AST_Scope.DEFMETHOD("def_function", function(symbol){ this.functions.set(symbol.name, this.def_variable(symbol)); }); diff --git a/lib/transform.js b/lib/transform.js index c3c34f58..62e6e02b 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker; if (y !== undefined) x = y; } } - tw.pop(); + tw.pop(this); return x; }); }; diff --git a/tools/node.js b/tools/node.js index 7e61d2a1..f6048661 100644 --- a/tools/node.js +++ b/tools/node.js @@ -45,7 +45,6 @@ exports.minify = function(files, options) { UglifyJS.base54.reset(); // 1. parse - var haveScope = false; var toplevel = null, sourcesContent = {}; @@ -74,7 +73,6 @@ exports.minify = function(files, options) { var compress = { warnings: options.warnings }; UglifyJS.merge(compress, options.compress); toplevel.figure_out_scope(); - haveScope = true; var sq = UglifyJS.Compressor(compress); toplevel = toplevel.transform(sq); } @@ -82,17 +80,11 @@ exports.minify = function(files, options) { // 3. mangle if (options.mangle) { toplevel.figure_out_scope(options.mangle); - haveScope = true; toplevel.compute_char_frequency(options.mangle); toplevel.mangle_names(options.mangle); } - // 4. scope (if needed) - if (!haveScope) { - toplevel.figure_out_scope(); - } - - // 5. output + // 4. output var inMap = options.inSourceMap; var output = {}; if (typeof options.inSourceMap == "string") { -- 2.34.1