From 2244743545e8e5a75b4cce219605588cd29581b1 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 12 Apr 2017 21:56:27 +0800 Subject: [PATCH] convert `AST_Seq` from binary tree to array (#1460) - rename `AST_Seq` to `AST_Sequence` - raise default sequences_limit from 200 to 800 --- bin/extract-props.js | 4 +- bin/uglifyjs | 4 +- lib/ast.js | 64 +----- lib/compress.js | 375 +++++++++++++++++++--------------- lib/mozilla-ast.js | 10 +- lib/output.js | 23 ++- lib/parse.js | 19 +- lib/propmangle.js | 9 +- lib/transform.js | 5 +- lib/utils.js | 2 +- test/compress/conditionals.js | 24 +-- test/compress/evaluate.js | 24 +-- test/compress/issue-640.js | 6 +- test/compress/sequences.js | 118 +++++++++-- 14 files changed, 378 insertions(+), 309 deletions(-) diff --git a/bin/extract-props.js b/bin/extract-props.js index a5b61458..7ce7d31e 100755 --- a/bin/extract-props.js +++ b/bin/extract-props.js @@ -50,8 +50,8 @@ function getProps(filename) { try { (function walk(node){ node.walk(new U2.TreeWalker(function(node){ - if (node instanceof U2.AST_Seq) { - walk(node.cdr); + if (node instanceof U2.AST_Sequence) { + walk(node.expressions[node.expressions.length - 1]); return true; } if (node instanceof U2.AST_String) { diff --git a/bin/uglifyjs b/bin/uglifyjs index 635ca365..ef776492 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -569,7 +569,7 @@ function getOptions(flag, constants) { } } ast.walk(new UglifyJS.TreeWalker(function(node){ - if (node instanceof UglifyJS.AST_Seq) return; // descend + if (node instanceof UglifyJS.AST_Sequence) return; // descend if (node instanceof UglifyJS.AST_Assign) { var name = node.left.print_to_string().replace(/-/g, "_"); var value = node.right; @@ -583,7 +583,7 @@ function getOptions(flag, constants) { ret[name] = true; return true; // no descend } - print_error(node.TYPE) + print_error(node.TYPE); print_error("Error parsing arguments for flag `" + flag + "': " + x); process.exit(1); })); diff --git a/lib/ast.js b/lib/ast.js index ba1330f4..f78ac576 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -613,68 +613,16 @@ var AST_New = DEFNODE("New", null, { $documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" }, AST_Call); -var AST_Seq = DEFNODE("Seq", "car cdr", { - $documentation: "A sequence expression (two comma-separated expressions)", +var AST_Sequence = DEFNODE("Sequence", "expressions", { + $documentation: "A sequence expression (comma-separated expressions)", $propdoc: { - car: "[AST_Node] first element in sequence", - cdr: "[AST_Node] second element in sequence" - }, - $cons: function(x, y) { - var seq = new AST_Seq(x); - seq.car = x; - seq.cdr = y; - return seq; - }, - $from_array: function(array) { - if (array.length == 0) return null; - if (array.length == 1) return array[0].clone(); - var list = null; - for (var i = array.length; --i >= 0;) { - list = AST_Seq.cons(array[i], list); - } - var p = list; - while (p) { - if (p.cdr && !p.cdr.cdr) { - p.cdr = p.cdr.car; - break; - } - p = p.cdr; - } - return list; - }, - to_array: function() { - var p = this, a = []; - while (p) { - a.push(p.car); - if (p.cdr && !(p.cdr instanceof AST_Seq)) { - a.push(p.cdr); - break; - } - p = p.cdr; - } - return a; - }, - add: function(node) { - var p = this; - while (p) { - if (!(p.cdr instanceof AST_Seq)) { - var cell = AST_Seq.cons(p.cdr, node); - return p.cdr = cell; - } - p = p.cdr; - } - }, - len: function() { - if (this.cdr instanceof AST_Seq) { - return this.cdr.len() + 1; - } else { - return 2; - } + expressions: "[AST_Node*] array of expressions (at least two)" }, _walk: function(visitor) { return visitor._visit(this, function(){ - this.car._walk(visitor); - if (this.cdr) this.cdr._walk(visitor); + this.expressions.forEach(function(node) { + node._walk(visitor); + }); }); } }); diff --git a/lib/compress.js b/lib/compress.js index 1d9258cf..8c254573 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -111,7 +111,7 @@ function Compressor(options, false_by_default) { }; } var sequences = this.options["sequences"]; - this.sequences_limit = sequences == 1 ? 200 : sequences | 0; + this.sequences_limit = sequences == 1 ? 800 : sequences | 0; this.warnings_produced = {}; }; @@ -440,6 +440,13 @@ merge(Compressor.prototype, { return new ctor(props); }; + function make_sequence(orig, expressions) { + if (expressions.length == 1) return expressions[0]; + return make_node(AST_Sequence, orig, { + expressions: expressions + }); + } + function make_node_from_constant(val, orig) { switch (typeof val) { case "string": @@ -482,16 +489,19 @@ merge(Compressor.prototype, { if (parent instanceof AST_UnaryPrefix && parent.operator == "delete" || parent instanceof AST_Call && parent.expression === orig && (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name == "eval")) { - return make_node(AST_Seq, orig, { - car: make_node(AST_Number, orig, { - value: 0 - }), - cdr: val - }); + return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]); } return val; } + function merge_sequence(array, node) { + if (node instanceof AST_Sequence) { + array.push.apply(array, node.expressions); + } else { + array.push(node); + } + } + function as_statement_array(thing) { if (thing === null) return []; if (thing instanceof AST_BlockStatement) return thing.body; @@ -1000,18 +1010,17 @@ merge(Compressor.prototype, { if (statements.length < 2) return statements; var seq = [], ret = []; function push_seq() { - seq = AST_Seq.from_array(seq); - if (seq) ret.push(make_node(AST_SimpleStatement, seq, { - body: seq - })); + if (!seq.length) return; + var body = make_sequence(seq[0], seq); + ret.push(make_node(AST_SimpleStatement, body, { body: body })); seq = []; }; statements.forEach(function(stat){ if (stat instanceof AST_SimpleStatement) { - if (seqLength(seq) >= compressor.sequences_limit) push_seq(); + if (seq.length >= compressor.sequences_limit) push_seq(); var body = stat.body; if (seq.length > 0) body = body.drop_side_effect_free(compressor); - if (body) seq.push(body); + if (body) merge_sequence(seq, body); } else { push_seq(); ret.push(stat); @@ -1023,27 +1032,16 @@ merge(Compressor.prototype, { return ret; }; - function seqLength(a) { - for (var len = 0, i = 0; i < a.length; ++i) { - var stat = a[i]; - if (stat instanceof AST_Seq) { - len += stat.len(); - } else { - len++; - } - } - return len; - }; - function sequencesize_2(statements, compressor) { function cons_seq(right) { ret.pop(); var left = prev.body; - if (left instanceof AST_Seq) { - left.add(right); - } else { - left = AST_Seq.cons(left, right); + if (!(left instanceof AST_Sequence)) { + left = make_node(AST_Sequence, left, { + expressions: [ left ] + }); } + merge_sequence(left.expressions, right); return left.transform(compressor); }; var ret = [], prev = null; @@ -1202,8 +1200,8 @@ merge(Compressor.prototype, { return this.consequent._eq_null(pure_getters) || this.alternative._eq_null(pure_getters); }) - def(AST_Seq, function(pure_getters) { - return this.cdr._eq_null(pure_getters); + def(AST_Sequence, function(pure_getters) { + return this.expressions[this.expressions.length - 1]._eq_null(pure_getters); }); def(AST_SymbolRef, function(pure_getters) { if (this.is_undefined) return true; @@ -1236,8 +1234,8 @@ merge(Compressor.prototype, { def(AST_Assign, function(){ return this.operator == "=" && this.right.is_boolean(); }); - def(AST_Seq, function(){ - return this.cdr.is_boolean(); + def(AST_Sequence, function(){ + return this.expressions[this.expressions.length - 1].is_boolean(); }); def(AST_True, return_true); def(AST_False, return_true); @@ -1263,8 +1261,8 @@ merge(Compressor.prototype, { return binary(this.operator.slice(0, -1)) || this.operator == "=" && this.right.is_number(compressor); }); - def(AST_Seq, function(compressor){ - return this.cdr.is_number(compressor); + def(AST_Sequence, function(compressor){ + return this.expressions[this.expressions.length - 1].is_number(compressor); }); def(AST_Conditional, function(compressor){ return this.consequent.is_number(compressor) && this.alternative.is_number(compressor); @@ -1287,8 +1285,8 @@ merge(Compressor.prototype, { def(AST_Assign, function(compressor){ return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor); }); - def(AST_Seq, function(compressor){ - return this.cdr.is_string(compressor); + def(AST_Sequence, function(compressor){ + return this.expressions[this.expressions.length - 1].is_string(compressor); }); def(AST_Conditional, function(compressor){ return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); @@ -1616,10 +1614,10 @@ merge(Compressor.prototype, { return this.expression; return basic_negation(this); }); - def(AST_Seq, function(compressor){ - var self = this.clone(); - self.cdr = self.cdr.negate(compressor); - return self; + def(AST_Sequence, function(compressor){ + var expressions = this.expressions.slice(); + expressions.push(expressions.pop().negate(compressor)); + return make_sequence(this, expressions); }); def(AST_Conditional, function(compressor, first_in_statement){ var self = this.clone(); @@ -1763,9 +1761,10 @@ merge(Compressor.prototype, { || this.expression.has_side_effects(compressor) || this.property.has_side_effects(compressor); }); - def(AST_Seq, function(compressor){ - return this.car.has_side_effects(compressor) - || this.cdr.has_side_effects(compressor); + def(AST_Sequence, function(compressor){ + return this.expressions.some(function(expression, index) { + return expression.has_side_effects(compressor); + }); }); })(function(node, func){ node.DEFMETHOD("has_side_effects", func); @@ -2015,12 +2014,12 @@ merge(Compressor.prototype, { for (var i = 0; i < def.length;) { var x = def[i]; if (x._unused_side_effects) { - side_effects.push(x._unused_side_effects); + merge_sequence(side_effects, x._unused_side_effects); def.splice(i, 1); } else { if (side_effects.length > 0) { - side_effects.push(x.value); - x.value = AST_Seq.from_array(side_effects); + merge_sequence(side_effects, x.value); + x.value = make_sequence(x.value, side_effects); side_effects = []; } ++i; @@ -2029,7 +2028,7 @@ merge(Compressor.prototype, { if (side_effects.length > 0) { side_effects = make_node(AST_BlockStatement, node, { body: [ make_node(AST_SimpleStatement, node, { - body: AST_Seq.from_array(side_effects) + body: make_sequence(node, side_effects) }) ] }); } else { @@ -2179,8 +2178,8 @@ merge(Compressor.prototype, { self.body.splice(i, 1); continue; } - if (expr instanceof AST_Seq - && (assign = expr.car) instanceof AST_Assign + if (expr instanceof AST_Sequence + && (assign = expr.expressions[0]) instanceof AST_Assign && assign.operator == "=" && (sym = assign.left) instanceof AST_Symbol && vars.has(sym.name)) @@ -2190,7 +2189,7 @@ merge(Compressor.prototype, { def.value = assign.right; remove(defs, def); defs.push(def); - self.body[i].body = expr.cdr; + self.body[i].body = make_sequence(expr, expr.expressions.slice(1)); continue; } } @@ -2224,12 +2223,14 @@ merge(Compressor.prototype, { // if all elements were dropped. Note: original array may be // returned if nothing changed. function trim(nodes, compressor, first_in_statement) { + var len = nodes.length; + if (!len) return null; var ret = [], changed = false; - for (var i = 0, len = nodes.length; i < len; i++) { + for (var i = 0; i < len; i++) { var node = nodes[i].drop_side_effect_free(compressor, first_in_statement); changed |= node !== nodes[i]; if (node) { - ret.push(node); + merge_sequence(ret, node); first_in_statement = false; } } @@ -2254,7 +2255,7 @@ merge(Compressor.prototype, { this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' '); } var args = trim(this.args, compressor, first_in_statement); - return args && AST_Seq.from_array(args); + return args && make_sequence(this, args); }); def(AST_Function, return_null); def(AST_Binary, function(compressor, first_in_statement){ @@ -2270,10 +2271,7 @@ merge(Compressor.prototype, { default: var left = this.left.drop_side_effect_free(compressor, first_in_statement); if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement); - return make_node(AST_Seq, this, { - car: left, - cdr: right - }); + return make_sequence(this, [ left, right ]); } }); def(AST_Assign, return_this); @@ -2316,14 +2314,14 @@ merge(Compressor.prototype, { }); def(AST_Object, function(compressor, first_in_statement){ var values = trim(this.properties, compressor, first_in_statement); - return values && AST_Seq.from_array(values); + return values && make_sequence(this, values); }); def(AST_ObjectProperty, function(compressor, first_in_statement){ return this.value.drop_side_effect_free(compressor, first_in_statement); }); def(AST_Array, function(compressor, first_in_statement){ var values = trim(this.elements, compressor, first_in_statement); - return values && AST_Seq.from_array(values); + return values && make_sequence(this, values); }); def(AST_Dot, function(compressor, first_in_statement){ if (this.expression.may_eq_null(compressor)) return this; @@ -2335,19 +2333,15 @@ merge(Compressor.prototype, { if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement); var property = this.property.drop_side_effect_free(compressor); if (!property) return expression; - return make_node(AST_Seq, this, { - car: expression, - cdr: property - }); + return make_sequence(this, [ expression, property ]); }); - def(AST_Seq, function(compressor){ - var cdr = this.cdr.drop_side_effect_free(compressor); - if (cdr === this.cdr) return this; - if (!cdr) return this.car; - return make_node(AST_Seq, this, { - car: this.car, - cdr: cdr - }); + def(AST_Sequence, function(compressor){ + var last = this.expressions[this.expressions.length - 1]; + var expr = last.drop_side_effect_free(compressor); + if (expr === last) return this; + var expressions = this.expressions.slice(0, -1); + if (expr) merge_sequence(expressions, expr); + return make_sequence(this, expressions); }); })(function(node, func){ node.DEFMETHOD("drop_side_effect_free", func); @@ -2737,7 +2731,7 @@ merge(Compressor.prototype, { return a; }, []); if (assignments.length == 0) return null; - return AST_Seq.from_array(assignments); + return make_sequence(this, assignments); }); OPT(AST_Definitions, function(self, compressor){ @@ -2979,12 +2973,12 @@ merge(Compressor.prototype, { var value = exp.body[0].value; if (!value || value.is_constant()) { var args = self.args.concat(value || make_node(AST_Undefined, self)); - return AST_Seq.from_array(args).transform(compressor); + return make_sequence(self, args).transform(compressor); } } if (compressor.option("side_effects") && all(exp.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); - return AST_Seq.from_array(args).transform(compressor); + return make_sequence(self, args).transform(compressor); } } if (compressor.option("drop_console")) { @@ -3025,40 +3019,85 @@ merge(Compressor.prototype, { return self; }); - OPT(AST_Seq, function(self, compressor){ - if (!compressor.option("side_effects")) + OPT(AST_Sequence, function(self, compressor){ + if (!compressor.option("side_effects")) return self; + var expressions = []; + filter_for_side_effects(); + var end = expressions.length - 1; + trim_right_for_undefined(); + if (end > 0 && compressor.option("cascade")) trim_left_for_assignment(); + if (end == 0) { + self = maintain_this_binding(compressor.parent(), self, expressions[0]); + if (!(self instanceof AST_Sequence)) self = self.optimize(compressor); return self; - self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor)); - if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr); - if (compressor.option("cascade")) { - var left; - if (self.car instanceof AST_Assign - && !self.car.left.has_side_effects(compressor)) { - left = self.car.left; - } else if (self.car instanceof AST_Unary - && (self.car.operator == "++" || self.car.operator == "--")) { - left = self.car.expression; - } - if (left - && !(left instanceof AST_SymbolRef - && left.definition().orig[0] instanceof AST_SymbolLambda)) { - var parent, field; - var cdr = self.cdr; + } + self.expressions = expressions; + return self; + + function filter_for_side_effects() { + var first = first_in_statement(compressor); + var last = self.expressions.length - 1; + self.expressions.forEach(function(expr, index) { + if (index < last) expr = expr.drop_side_effect_free(compressor, first); + if (expr) { + merge_sequence(expressions, expr); + first = false; + } + }); + } + + function trim_right_for_undefined() { + while (end > 0 && is_undefined(expressions[end], compressor)) end--; + if (end < expressions.length - 1) { + expressions[end] = make_node(AST_UnaryPrefix, self, { + operator : "void", + expression : expressions[end] + }); + expressions.length = end + 1; + } + } + + function trim_left_for_assignment() { + for (var i = 0, j = 1; j <= end; j++) { + var left = expressions[i]; + var cdr = expressions[j]; + if (left instanceof AST_Assign + && !left.left.has_side_effects(compressor)) { + left = left.left; + } else if (left instanceof AST_Unary + && (left.operator == "++" || left.operator == "--")) { + left = left.expression; + } else left = null; + if (!left || + left instanceof AST_SymbolRef + && left.definition().orig[0] instanceof AST_SymbolLambda) { + expressions[++i] = cdr; + continue; + } + var parent = null, field; while (true) { if (cdr.equivalent_to(left)) { - var car = self.car instanceof AST_UnaryPostfix ? make_node(AST_UnaryPrefix, self.car, { - operator: self.car.operator, - expression: left - }) : self.car; + var car = expressions[i]; + if (car instanceof AST_UnaryPostfix) { + car = make_node(AST_UnaryPrefix, car, { + operator: car.operator, + expression: left + }); + } if (parent) { parent[field] = car; - return self.cdr; + expressions[i] = expressions[j]; + } else { + expressions[i] = car; } - return car; + break; } if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) { if (cdr.left.is_constant()) { - if (cdr.operator == "||" || cdr.operator == "&&") break; + if (cdr.operator == "||" || cdr.operator == "&&") { + expressions[++i] = expressions[j]; + break; + } field = "right"; } else { field = "left"; @@ -3066,31 +3105,27 @@ merge(Compressor.prototype, { } else if (cdr instanceof AST_Call || cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) { field = "expression"; - } else break; + } else { + expressions[++i] = expressions[j]; + break; + } parent = cdr; cdr = cdr[field]; } } + end = i; + expressions.length = end + 1; } - if (is_undefined(self.cdr, compressor)) { - return make_node(AST_UnaryPrefix, self, { - operator : "void", - expression : self.car - }); - } - return self; }); AST_Unary.DEFMETHOD("lift_sequences", function(compressor){ if (compressor.option("sequences")) { - if (this.expression instanceof AST_Seq) { - var seq = this.expression; - var x = seq.to_array(); + if (this.expression instanceof AST_Sequence) { + var x = this.expression.expressions.slice(); var e = this.clone(); e.expression = x.pop(); x.push(e); - seq = AST_Seq.from_array(x).transform(compressor); - return seq; + return make_sequence(this, x).optimize(compressor); } } return this; @@ -3108,15 +3143,12 @@ merge(Compressor.prototype, { || e instanceof AST_NaN || e instanceof AST_Infinity || e instanceof AST_Undefined)) { - if (e instanceof AST_Seq) { - e = e.to_array(); + if (e instanceof AST_Sequence) { + e = e.expressions.slice(); e.push(make_node(AST_True, self)); - return AST_Seq.from_array(e).optimize(compressor); + return make_sequence(self, e).optimize(compressor); } - return make_node(AST_Seq, self, { - car: e, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor); } var seq = self.lift_sequences(compressor); if (seq !== self) { @@ -3146,10 +3178,10 @@ merge(Compressor.prototype, { // typeof always returns a non-empty string, thus it's // always true in booleans compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start); - return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_node(AST_Seq, self, { - car: e, - cdr: make_node(AST_True, self) - })).optimize(compressor); + return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [ + e, + make_node(AST_True, self) + ])).optimize(compressor); } } if (self.operator == "-" && e instanceof AST_Infinity) { @@ -3181,29 +3213,32 @@ merge(Compressor.prototype, { AST_Binary.DEFMETHOD("lift_sequences", function(compressor){ if (compressor.option("sequences")) { - if (this.left instanceof AST_Seq) { - var seq = this.left; - var x = seq.to_array(); + if (this.left instanceof AST_Sequence) { + var x = this.left.expressions.slice(); var e = this.clone(); e.left = x.pop(); x.push(e); - return AST_Seq.from_array(x).optimize(compressor); + return make_sequence(this, x).optimize(compressor); } - if (this.right instanceof AST_Seq && !this.left.has_side_effects(compressor)) { + if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) { var assign = this.operator == "=" && this.left instanceof AST_SymbolRef; - var root = this.right.clone(); - var cursor, seq = root; - while (assign || !seq.car.has_side_effects(compressor)) { - cursor = seq; - if (seq.cdr instanceof AST_Seq) { - seq = seq.cdr = seq.cdr.clone(); - } else break; - } - if (cursor) { + var x = this.right.expressions; + var last = x.length - 1; + for (var i = 0; i < last; i++) { + if (!assign && x[i].has_side_effects(compressor)) break; + } + if (i == last) { + x = x.slice(); var e = this.clone(); - e.right = cursor.cdr; - cursor.cdr = e; - return root.optimize(compressor); + e.right = x.pop(); + x.push(e); + return make_sequence(this, x).optimize(compressor); + } else if (i > 0) { + var e = this.clone(); + e.right = make_sequence(this.right, x.slice(i)); + x = x.slice(0, i); + x.push(e); + return make_sequence(this, x).optimize(compressor); } } } @@ -3272,17 +3307,17 @@ merge(Compressor.prototype, { var rr = self.right.evaluate(compressor); if (ll && typeof ll == "string") { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.right, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ + self.right, + make_node(AST_True, self) + ]).optimize(compressor); } if (rr && typeof rr == "string") { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ + self.left, + make_node(AST_True, self) + ]).optimize(compressor); } } if (compressor.option("comparisons") && self.is_boolean()) { @@ -3336,10 +3371,10 @@ merge(Compressor.prototype, { var rr = self.right.evaluate(compressor); if (!rr) { compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_False, self) - }).optimize(compressor); + return make_sequence(self, [ + self.left, + make_node(AST_False, self) + ]).optimize(compressor); } else if (rr !== self.right) { compressor.warn("Dropping side-effect-free && in boolean context [{file}:{line},{col}]", self.start); return self.left.optimize(compressor); @@ -3362,10 +3397,10 @@ merge(Compressor.prototype, { return self.left.optimize(compressor); } else if (rr !== self.right) { compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start); - return make_node(AST_Seq, self, { - car: self.left, - cdr: make_node(AST_True, self) - }).optimize(compressor); + return make_sequence(self, [ + self.left, + make_node(AST_True, self) + ]).optimize(compressor); } } break; @@ -3709,10 +3744,12 @@ merge(Compressor.prototype, { OPT(AST_Conditional, function(self, compressor){ if (!compressor.option("conditionals")) return self; - if (self.condition instanceof AST_Seq) { - var car = self.condition.car; - self.condition = self.condition.cdr; - return AST_Seq.cons(car, self); + // This looks like lift_sequences(), should probably be under "sequences" + if (self.condition instanceof AST_Sequence) { + var expressions = self.condition.expressions.slice(); + self.condition = expressions.pop(); + expressions.push(self); + return make_sequence(self, expressions); } var cond = self.condition.evaluate(compressor); if (cond !== self.condition) { @@ -3795,10 +3832,10 @@ merge(Compressor.prototype, { } // x ? y : y --> x, y if (consequent.equivalent_to(alternative)) { - return make_node(AST_Seq, self, { - car: self.condition, - cdr: consequent - }).optimize(compressor); + return make_sequence(self, [ + self.condition, + consequent + ]).optimize(compressor); } if (is_true(self.consequent)) { @@ -3968,10 +4005,10 @@ merge(Compressor.prototype, { function literals_in_boolean_context(self, compressor) { if (compressor.option("booleans") && compressor.in_boolean_context()) { - return best_of(compressor, self, make_node(AST_Seq, self, { - car: self, - cdr: make_node(AST_True, self) - }).optimize(compressor)); + return best_of(compressor, self, make_sequence(self, [ + self, + make_node(AST_True, self) + ]).optimize(compressor)); } return self; }; diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 12b55dc5..e97d6191 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -149,7 +149,11 @@ }); }, SequenceExpression: function(M) { - return AST_Seq.from_array(M.expressions.map(from_moz)); + return new AST_Sequence({ + start : my_start_token(M), + end : my_end_token(M), + expressions: M.expressions.map(from_moz) + }); }, MemberExpression: function(M) { return new (M.computed ? AST_Sub : AST_Dot)({ @@ -332,10 +336,10 @@ }; }); - def_to_moz(AST_Seq, function To_Moz_SequenceExpression(M) { + def_to_moz(AST_Sequence, function To_Moz_SequenceExpression(M) { return { type: "SequenceExpression", - expressions: M.to_array().map(to_moz) + expressions: M.expressions.map(to_moz) }; }); diff --git a/lib/output.js b/lib/output.js index 9ac50c08..fe982a7b 100644 --- a/lib/output.js +++ b/lib/output.js @@ -592,7 +592,7 @@ function OutputStream(options) { || p instanceof AST_Call && p.expression === this; }); - PARENS(AST_Seq, function(output){ + PARENS(AST_Sequence, function(output){ var p = output.parent(); return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4) || p instanceof AST_Unary // !(foo, bar, baz) @@ -1087,18 +1087,19 @@ function OutputStream(options) { AST_Call.prototype._codegen(self, output); }); - AST_Seq.DEFMETHOD("_do_print", function(output){ - this.car.print(output); - if (this.cdr) { - output.comma(); - if (output.should_break()) { - output.newline(); - output.indent(); + AST_Sequence.DEFMETHOD("_do_print", function(output){ + this.expressions.forEach(function(node, index) { + if (index > 0) { + output.comma(); + if (output.should_break()) { + output.newline(); + output.indent(); + } } - this.cdr.print(output); - } + node.print(output); + }); }); - DEFPRINT(AST_Seq, function(self, output){ + DEFPRINT(AST_Sequence, function(self, output){ self._do_print(output); // var p = output.parent(); // if (p instanceof AST_Statement) { diff --git a/lib/parse.js b/lib/parse.js index c34e13db..c7d75802 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1527,17 +1527,18 @@ function parse($TEXT, options) { var expression = function(commas, no_in) { var start = S.token; - var expr = maybe_assign(no_in); - if (commas && is("punc", ",")) { + var exprs = []; + while (true) { + exprs.push(maybe_assign(no_in)); + if (!commas || !is("punc", ",")) break; next(); - return new AST_Seq({ - start : start, - car : expr, - cdr : expression(true, no_in), - end : peek() - }); + commas = true; } - return expr; + return exprs.length == 1 ? exprs[0] : new AST_Sequence({ + start : start, + expressions : exprs, + end : peek() + }); }; function in_loop(cont) { diff --git a/lib/propmangle.js b/lib/propmangle.js index b6222990..aaf5936f 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -224,8 +224,8 @@ function mangle_properties(ast, options) { try { (function walk(node){ node.walk(new TreeWalker(function(node){ - if (node instanceof AST_Seq) { - walk(node.cdr); + if (node instanceof AST_Sequence) { + walk(node.expressions[node.expressions.length - 1]); return true; } if (node instanceof AST_String) { @@ -247,8 +247,9 @@ function mangle_properties(ast, options) { function mangleStrings(node) { return node.transform(new TreeTransformer(function(node){ - if (node instanceof AST_Seq) { - node.cdr = mangleStrings(node.cdr); + if (node instanceof AST_Sequence) { + var last = node.expressions.length - 1; + node.expressions[last] = mangleStrings(node.expressions[last]); } else if (node instanceof AST_String) { node.value = mangle(node.value); diff --git a/lib/transform.js b/lib/transform.js index 3018e8ff..112e5f28 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -174,9 +174,8 @@ TreeTransformer.prototype = new TreeWalker; self.args = do_list(self.args, tw); }); - _(AST_Seq, function(self, tw){ - self.car = self.car.transform(tw); - self.cdr = self.cdr.transform(tw); + _(AST_Sequence, function(self, tw){ + self.expressions = do_list(self.expressions, tw); }); _(AST_Dot, function(self, tw){ diff --git a/lib/utils.js b/lib/utils.js index fdb20471..e21fc5ec 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -346,7 +346,7 @@ function first_in_statement(stack) { for (var i = 0, p; p = stack.parent(i); i++) { if (p instanceof AST_Statement && p.body === node) return true; - if ((p instanceof AST_Seq && p.car === node ) || + if ((p instanceof AST_Sequence && p.expressions[0] === node) || (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || (p instanceof AST_Dot && p.expression === node ) || (p instanceof AST_Sub && p.expression === node ) || diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 200b487f..7a6688ba 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -979,12 +979,12 @@ delete_conditional_1: { console.log(delete (1 ? 0 / 0 : x)); } expect: { - console.log((void 0, !0)); - console.log((void 0, !0)); - console.log((1 / 0, !0)); - console.log((1 / 0, !0)); - console.log((NaN, !0)); - console.log((NaN, !0)); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); } expect_stdout: true } @@ -1006,12 +1006,12 @@ delete_conditional_2: { console.log(delete (0 ? x : 0 / 0)); } expect: { - console.log((void 0, !0)); - console.log((void 0, !0)); - console.log((Infinity, !0)); - console.log((1 / 0, !0)); - console.log((NaN, !0)); - console.log((NaN, !0)); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); } expect_stdout: true } diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 611acf0d..585ee2b9 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -922,12 +922,12 @@ delete_binary_1: { console.log(delete (true && (0 / 0))); } expect: { - console.log((void 0, !0)); - console.log((void 0, !0)); - console.log((1 / 0, !0)); - console.log((1 / 0, !0)); - console.log((NaN, !0)); - console.log((NaN, !0)); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); } expect_stdout: true } @@ -948,12 +948,12 @@ delete_binary_2: { console.log(delete (false || (0 / 0))); } expect: { - console.log((void 0, !0)); - console.log((void 0, !0)); - console.log((Infinity, !0)); - console.log((1 / 0, !0)); - console.log((NaN, !0)); - console.log((NaN, !0)); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); } expect_stdout: true } diff --git a/test/compress/issue-640.js b/test/compress/issue-640.js index fbf5f37f..c9a68dc9 100644 --- a/test/compress/issue-640.js +++ b/test/compress/issue-640.js @@ -159,7 +159,7 @@ negate_iife_4: { })(); } expect: { - (function(){ return t })() ? console.log(true) : console.log(false), function(){ + !function(){ return t }() ? console.log(false) : console.log(true), function(){ console.log("something"); }(); } @@ -183,7 +183,7 @@ negate_iife_5: { })(); } expect: { - (function(){ return t })() ? foo(true) : bar(false), function(){ + !function(){ return t }() ? bar(false) : foo(true), function(){ console.log("something"); }(); } @@ -207,7 +207,7 @@ negate_iife_5_off: { })(); } expect: { - (function(){ return t })() ? foo(true) : bar(false), function(){ + !function(){ return t }() ? bar(false) : foo(true), function(){ console.log("something"); }(); } diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 699341c0..3fb26278 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -460,7 +460,7 @@ issue_1758: { console.log(function(c) { var undefined = 42; return function() { - return c--, c--, c.toString(), void 0; + return c--, c--, void c.toString(); }(); }()); } @@ -481,12 +481,12 @@ delete_seq_1: { console.log(delete (1, 0 / 0)); } expect: { - console.log((void 0, !0)); - console.log((void 0, !0)); - console.log((1 / 0, !0)); - console.log((1 / 0, !0)); - console.log((NaN, !0)); - console.log((0 / 0, !0)); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); } expect_stdout: true } @@ -505,12 +505,12 @@ delete_seq_2: { console.log(delete (1, 2, 0 / 0)); } expect: { - console.log((void 0, !0)); - console.log((void 0, !0)); - console.log((1 / 0, !0)); - console.log((1 / 0, !0)); - console.log((NaN, !0)); - console.log((0 / 0, !0)); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); } expect_stdout: true } @@ -530,12 +530,12 @@ delete_seq_3: { console.log(delete (1, 2, 0 / 0)); } expect: { - console.log((void 0, !0)); - console.log((void 0, !0)); - console.log((Infinity, !0)); - console.log((1 / 0, !0)); - console.log((NaN, !0)); - console.log((0 / 0, !0)); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); + console.log(!0); } expect_stdout: true } @@ -606,7 +606,85 @@ delete_seq_6: { } expect: { var a; - console.log((a, !0)); + console.log(!0); } expect_stdout: true } + +side_effects: { + options = { + sequences: true, + side_effects: true, + } + input: { + 0, a(), 1, b(), 2, c(), 3; + } + expect: { + a(), b(), c(); + } +} + +side_effects_cascade_1: { + options = { + cascade: true, + conditionals: true, + sequences: true, + side_effects: true, + } + input: { + function f(a, b) { + a -= 42; + if (a < 0) a = 0; + b.a = a; + } + } + expect: { + function f(a, b) { + (a -= 42) < 0 && (a = 0), b.a = a; + } + } +} + +side_effects_cascade_2: { + options = { + cascade: true, + side_effects: true, + } + input: { + function f(a, b) { + b = a, + !a + (b += a) || (b += a), + b = a, + b; + } + } + expect: { + function f(a, b) { + b = a, + !a + (b += a) || (b += a), + b = a; + } + } +} + +side_effects_cascade_3: { + options = { + cascade: true, + conditionals: true, + side_effects: true, + } + input: { + function f(a, b) { + "foo" ^ (b += a), + b ? false : (b = a) ? -1 : (b -= a) - (b ^= a), + a-- || !a, + a; + } + } + expect: { + function f(a, b) { + !(b += a) && ((b = a) || (b -= a, b ^= a)), + --a; + } + } +} -- 2.34.1