Merge branch 'master' into harmony

This commit is contained in:
Richard van Velzen
2015-12-26 17:55:38 +01:00
16 changed files with 609 additions and 124 deletions

View File

@@ -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 raw", {
}, null);
var AST_Node = DEFNODE("Node", "start end", {
@@ -1159,27 +1159,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];
@@ -1192,7 +1201,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 && node.body) {
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;

View File

@@ -199,7 +199,7 @@ merge(Compressor.prototype, {
};
function tighten_body(statements, compressor) {
var CHANGED;
var CHANGED, max_iter = 10;
do {
CHANGED = false;
if (compressor.option("angular")) {
@@ -218,7 +218,7 @@ merge(Compressor.prototype, {
if (compressor.option("join_vars")) {
statements = join_consecutive_vars(statements, compressor);
}
} while (CHANGED);
} while (CHANGED && max_iter-- > 0);
if (compressor.option("negate_iife")) {
negate_iifes(statements, compressor);
@@ -392,7 +392,12 @@ merge(Compressor.prototype, {
continue loop;
}
//---
if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
// XXX: what was the intention of this case?
// if sequences is not enabled, this can lead to an endless loop (issue #866).
// however, with sequences on this helps producing slightly better output for
// the example code.
if (compressor.option("sequences")
&& ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
&& (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) {
CHANGED = true;
ret.push(make_node(AST_Return, ret[0], {
@@ -729,6 +734,32 @@ merge(Compressor.prototype, {
return [ this ];
}
});
AST_Node.DEFMETHOD("is_constant", function(compressor){
// Accomodate when compress option evaluate=false
// as well as the common constant expressions !0 and !1
return this instanceof AST_Constant
|| (this instanceof AST_UnaryPrefix && this.operator == "!"
&& this.expression instanceof AST_Constant)
|| this.evaluate(compressor).length > 1;
});
// Obtain the constant value of an expression already known to be constant.
// Result only valid iff this.is_constant(compressor) is true.
AST_Node.DEFMETHOD("constant_value", function(compressor){
// Accomodate when option evaluate=false.
if (this instanceof AST_Constant) return this.value;
// Accomodate the common constant expressions !0 and !1 when option evaluate=false.
if (this instanceof AST_UnaryPrefix
&& this.operator == "!"
&& this.expression instanceof AST_Constant) {
return !this.expression.value;
}
var result = this.evaluate(compressor)
if (result.length > 1) {
return result[1];
}
// should never be reached
return undefined;
});
def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
});
@@ -1006,7 +1037,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;
@@ -2472,32 +2503,48 @@ merge(Compressor.prototype, {
alternative: alternative
});
}
// x=y?1:1 --> x=1
if (consequent instanceof AST_Constant
&& alternative instanceof AST_Constant
// y?1:1 --> 1
if (consequent.is_constant(compressor)
&& alternative.is_constant(compressor)
&& consequent.equivalent_to(alternative)) {
var consequent_value = consequent.constant_value();
if (self.condition.has_side_effects(compressor)) {
return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent.value, self)]);
return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]);
} else {
return make_node_from_constant(compressor, consequent.value, self);
return make_node_from_constant(compressor, consequent_value, self);
}
}
// x=y?true:false --> x=!!y
if (consequent instanceof AST_True
&& alternative instanceof AST_False) {
// y?true:false --> !!y
if (is_true(consequent) && is_false(alternative)) {
self.condition = self.condition.negate(compressor);
return make_node(AST_UnaryPrefix, self.condition, {
operator: "!",
expression: self.condition
});
}
// x=y?false:true --> x=!y
if (consequent instanceof AST_False
&& alternative instanceof AST_True) {
// y?false:true --> !y
if (is_false(consequent) && is_true(alternative)) {
return self.condition.negate(compressor)
}
return self;
// AST_True or !0
function is_true(node) {
return node instanceof AST_True
|| (node instanceof AST_UnaryPrefix
&& node.operator == "!"
&& node.expression instanceof AST_Constant
&& !node.expression.value);
}
// AST_False or !1
function is_false(node) {
return node instanceof AST_False
|| (node instanceof AST_UnaryPrefix
&& node.operator == "!"
&& node.expression instanceof AST_Constant
&& !!node.expression.value);
}
});
OPT(AST_Boolean, function(self, compressor){
@@ -2569,4 +2616,11 @@ merge(Compressor.prototype, {
OPT(AST_Object, literals_in_boolean_context);
OPT(AST_RegExp, literals_in_boolean_context);
OPT(AST_Return, function(self, compressor){
if (self.value instanceof AST_Undefined) {
self.value = null;
}
return self;
});
})();

View File

@@ -146,7 +146,14 @@
case "boolean":
return new (val ? AST_True : AST_False)(args);
default:
args.value = val;
var rx = M.regex;
if (rx && rx.pattern) {
// RegExpLiteral as per ESTree AST spec
args.value = new RegExp(rx.pattern, rx.flags).toString();
} else {
// support legacy RegExp
args.value = M.regex && M.raw ? M.raw : val;
}
return new AST_RegExp(args);
}
},
@@ -334,6 +341,19 @@
};
});
def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) {
var value = M.value;
return {
type: "Literal",
value: value,
raw: value.toString(),
regex: {
pattern: value.source,
flags: value.toString().match(/[gimuy]*$/)[0]
}
};
});
def_to_moz(AST_Constant, function To_Moz_Literal(M) {
var value = M.value;
if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) {
@@ -343,13 +363,15 @@
prefix: true,
argument: {
type: "Literal",
value: -value
value: -value,
raw: M.start.raw
}
};
}
return {
type: "Literal",
value: value
value: value,
raw: M.start.raw
};
});
@@ -369,6 +391,12 @@
/* -----[ tools ]----- */
function raw_token(moznode) {
if (moznode.type == "Literal") {
return moznode.raw != null ? moznode.raw : moznode.value + "";
}
}
function my_start_token(moznode) {
var loc = moznode.loc, start = loc && loc.start;
var range = moznode.range;
@@ -379,7 +407,8 @@
pos : range ? range[0] : moznode.start,
endline : start && start.line,
endcol : start && start.column,
endpos : range ? range[0] : moznode.start
endpos : range ? range[0] : moznode.start,
raw : raw_token(moznode),
});
};
@@ -393,7 +422,8 @@
pos : range ? range[1] : moznode.end,
endline : end && end.line,
endcol : end && end.column,
endpos : range ? range[1] : moznode.end
endpos : range ? range[1] : moznode.end,
raw : raw_token(moznode),
});
};

View File

@@ -95,7 +95,7 @@ function OutputStream(options) {
case "\f": return "\\f";
case "\n": return "\\n";
case "\r": return "\\r";
case "\x0B": return output.option("screw_ie8") ? "\\v" : "\\x0B";
case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B";
case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029";
case '"': ++dq; return '"';
@@ -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){
@@ -1311,10 +1319,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 && self.start.raw != null) {
output.print(self.start.raw);
} else {
output.print(make_num(self.getValue()));
}

View File

@@ -190,6 +190,9 @@ function parse_js_number(num) {
return parseInt(num.substr(2), 2);
} else if (RE_DEC_NUMBER.test(num)) {
return parseFloat(num);
} else {
var val = parseFloat(num);
if (val == num) return val;
}
};
@@ -291,6 +294,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
nlb : S.newline_before,
file : filename
};
if (/^(?:num|string|regexp)$/i.test(type)) {
ret.raw = $TEXT.substring(ret.pos, ret.endpos);
}
if (!is_comment) {
ret.comments_before = S.comments_before;
S.comments_before = [];
@@ -341,11 +347,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);
}
@@ -406,6 +408,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
else ch = read_escaped_char(true);
}
else if (ch == "\n") parse_error("Unterminated string constant");
else if (ch == quote) break;
ret += ch;
}
@@ -752,9 +755,9 @@ function parse($TEXT, options) {
);
};
function semicolon() {
function semicolon(optional) {
if (is("punc", ";")) next();
else if (!can_insert_semicolon()) unexpected();
else if (!optional && !can_insert_semicolon()) unexpected();
};
function parenthesised() {
@@ -843,7 +846,7 @@ function parse($TEXT, options) {
case "do":
return new AST_Do({
body : in_loop(statement),
condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(), tmp)
condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(true), tmp)
});
case "while":
@@ -1323,7 +1326,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({

View File

@@ -98,6 +98,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
// pass 1: setup scope chaining and handle definitions
var self = this;
var scope = self.parent_scope = null;
var labels = new Dictionary();
var defun = null;
var nesting = 0;
var in_destructuring = null;
@@ -121,20 +122,24 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
node.init_scope_vars(nesting);
var save_scope = node.parent_scope = scope;
var save_defun = defun;
var save_labels = labels;
defun = scope = node;
labels = new Dictionary();
++nesting; descend(); --nesting;
scope = save_scope;
defun = save_defun;
labels = save_labels;
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_LabeledStatement) {
var l = node.label;
if (labels.has(l.name)) {
throw new Error(string_template("Label {name} defined twice", l));
}
labels.set(l.name, l);
descend();
labels.del(l.name);
return true; // no descend again
}
if (node instanceof AST_With) {
for (var s = scope; s; s = s.parent_scope)
@@ -148,6 +153,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
node.object_destructuring_arg = !!in_destructuring;
defun.def_variable(node);
}
if (node instanceof AST_Label) {
node.thedef = node;
node.references = [];
}
if (node instanceof AST_SymbolLambda) {
defun.def_function(node);
}
@@ -178,6 +187,15 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
(options.screw_ie8 ? scope : defun)
.def_variable(node);
}
else if (node instanceof AST_LabelRef) {
var sym = labels.get(node.name);
if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
name: node.name,
line: node.start.line,
col: node.start.col
}));
node.thedef = sym;
}
});
self.walk(tw);
@@ -200,6 +218,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
cls = prev_cls;
return true;
}
if (node instanceof AST_LoopControl && node.label) {
node.label.thedef.references.push(node);
return true;
}
if (node instanceof AST_SymbolRef) {
var name = node.name;
var sym = node.scope.find_variable(name);
@@ -236,7 +258,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
@@ -247,10 +268,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;
@@ -274,11 +291,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));
});

View File

@@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker;
if (y !== undefined) x = y;
}
}
tw.pop();
tw.pop(this);
return x;
});
};