Browse files

Updating to current version of uglify (quite a bit fixed internally, …

…yay)
  • Loading branch information...
1 parent 2d5048e commit bb1ead9dacb01b819b0f1d8ec1f40acf233b2d96 @SlexAxton committed Jan 30, 2012
Showing with 659 additions and 241 deletions.
  1. +83 −58 js/uglifyjs/lib/parse-js.js
  2. +526 −179 js/uglifyjs/lib/process.js
  3. +50 −4 js/uglifyjs/lib/squeeze-more.js
View
141 js/uglifyjs/lib/parse-js.js
@@ -61,7 +61,7 @@
//>> Start Uglifui
(function ( global ) {
//>> End Uglifui
-
+
/* -----[ Tokenizer (constants) ]----- */
var KEYWORDS = array_to_hash([
@@ -70,6 +70,7 @@ var KEYWORDS = array_to_hash([
"catch",
"const",
"continue",
+ "debugger",
"default",
"delete",
"do",
@@ -98,7 +99,6 @@ var RESERVED_WORDS = array_to_hash([
"byte",
"char",
"class",
- "debugger",
"double",
"enum",
"export",
@@ -145,7 +145,7 @@ var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^"));
var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
var RE_OCT_NUMBER = /^0[0-7]+$/;
-var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+\-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
+var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
var OPERATORS = array_to_hash([
"in",
@@ -194,7 +194,7 @@ var OPERATORS = array_to_hash([
"||"
]);
-var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\v\u200b"));
+var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000"));
var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
@@ -259,23 +259,18 @@ function parse_js_number(num) {
function JS_Parse_Error(message, line, col, pos) {
this.message = message;
- this.line = line;
- this.col = col;
- this.pos = pos;
- try {
- ({})();
- } catch(ex) {
- this.stack = ex.stack;
- };
+ this.line = line + 1;
+ this.col = col + 1;
+ this.pos = pos + 1;
+ this.stack = new Error().stack;
};
JS_Parse_Error.prototype.toString = function() {
return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack;
};
function js_error(message, line, col, pos) {
- //throw new JS_Parse_Error(message, line, col, pos);
- console.log(message, line, col, pos);
+ console.log(new JS_Parse_Error(message, line, col, pos) );
};
function is_token(token, type, val) {
@@ -301,12 +296,12 @@ function tokenizer($TEXT) {
function peek() { return S.text.charAt(S.pos); };
- function next(signal_eof) {
+ function next(signal_eof, in_string) {
var ch = S.text.charAt(S.pos++);
if (signal_eof && !ch)
throw EX_EOF;
if (ch == "\n") {
- S.newline_before = true;
+ S.newline_before = S.newline_before || !in_string;
++S.line;
S.col = 0;
} else {
@@ -336,12 +331,13 @@ function tokenizer($TEXT) {
(type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) ||
(type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value)));
var ret = {
- type : type,
- value : value,
- line : S.tokline,
- col : S.tokcol,
- pos : S.tokpos,
- nlb : S.newline_before
+ type : type,
+ value : value,
+ line : S.tokline,
+ col : S.tokcol,
+ pos : S.tokpos,
+ endpos : S.pos,
+ nlb : S.newline_before
};
if (!is_comment) {
ret.comments_before = S.comments_before;
@@ -403,18 +399,19 @@ function tokenizer($TEXT) {
}
};
- function read_escaped_char() {
- var ch = next(true);
+ function read_escaped_char(in_string) {
+ var ch = next(true, in_string);
switch (ch) {
case "n" : return "\n";
case "r" : return "\r";
case "t" : return "\t";
case "b" : return "\b";
- case "v" : return "\v";
+ case "v" : return "\u000b";
case "f" : return "\f";
- case "0" : return "\u0000";
+ case "0" : return "\0";
case "x" : return String.fromCharCode(hex_bytes(2));
case "u" : return String.fromCharCode(hex_bytes(4));
+ case "\n": return "";
default : return ch;
}
};
@@ -435,7 +432,24 @@ function tokenizer($TEXT) {
var quote = next(), ret = "";
for (;;) {
var ch = next(true);
- if (ch == "\\") ch = read_escaped_char();
+ if (ch == "\\") {
+ // read OctalEscapeSequence (XXX: deprecated if "strict mode")
+ // https://github.com/mishoo/UglifyJS/issues/178
+ var octal_len = 0, first = null;
+ ch = read_while(function(ch){
+ if (ch >= "0" && ch <= "7") {
+ if (!first) {
+ first = ch;
+ return ++octal_len;
+ }
+ else if (first <= "3" && octal_len <= 2) return ++octal_len;
+ else if (first >= "4" && octal_len <= 1) return ++octal_len;
+ }
+ return false;
+ });
+ if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
+ else ch = read_escaped_char(true);
+ }
else if (ch == quote) break;
ret += ch;
}
@@ -460,8 +474,7 @@ function tokenizer($TEXT) {
next();
return with_eof_error("Unterminated multiline comment", function(){
var i = find("*/", true),
- text = S.text.substring(S.pos, i),
- tok = token("comment2", text, true);
+ text = S.text.substring(S.pos, i);
S.pos = i + 2;
S.line += text.split("\n").length - 1;
S.newline_before = text.indexOf("\n") >= 0;
@@ -473,7 +486,7 @@ function tokenizer($TEXT) {
warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer.");
}
- return tok;
+ return token("comment2", text, true);
});
};
@@ -496,9 +509,9 @@ function tokenizer($TEXT) {
return name;
};
- function read_regexp() {
+ function read_regexp(regexp) {
return with_eof_error("Unterminated regular expression", function(){
- var prev_backslash = false, regexp = "", ch, in_class = false;
+ var prev_backslash = false, ch, in_class = false;
while ((ch = next(true))) if (prev_backslash) {
regexp += "\\" + ch;
prev_backslash = false;
@@ -547,7 +560,7 @@ function tokenizer($TEXT) {
S.regex_allowed = regex_allowed;
return next_token();
}
- return S.regex_allowed ? read_regexp() : read_operator("/");
+ return S.regex_allowed ? read_regexp("") : read_operator("/");
};
function handle_dot() {
@@ -578,8 +591,8 @@ function tokenizer($TEXT) {
};
function next_token(force_regexp) {
- if (force_regexp)
- return read_regexp();
+ if (force_regexp != null)
+ return read_regexp(force_regexp);
skip_whitespace();
start_token();
var ch = peek();
@@ -768,9 +781,9 @@ function parse($TEXT, exigent_mode, embed_tokens) {
};
var statement = maybe_embed_tokens(function() {
- if (is("operator", "/")) {
+ if (is("operator", "/") || is("operator", "/=")) {
S.peeked = null;
- S.token = S.input(true); // force regexp
+ S.token = S.input(S.token.value.substr(1)); // force regexp
}
switch (S.token.type) {
case "num":
@@ -840,6 +853,8 @@ function parse($TEXT, exigent_mode, embed_tokens) {
return as("switch", parenthesised(), switch_block_());
case "throw":
+ if (S.token.nlb)
+ croak("Illegal newline after 'throw'");
return as("throw", prog1(expression, semicolon));
case "try":
@@ -877,7 +892,10 @@ function parse($TEXT, exigent_mode, embed_tokens) {
};
function break_cont(type) {
- var name = is("name") ? S.token.value : null;
+ var name;
+ if (!can_insert_semicolon()) {
+ name = is("name") ? S.token.value : null;
+ }
if (name != null) {
next();
if (!member(name, S.labels))
@@ -896,8 +914,11 @@ function parse($TEXT, exigent_mode, embed_tokens) {
init = is("keyword", "var")
? (next(), var_(true))
: expression(true, true);
- if (is("operator", "in"))
+ if (is("operator", "in")) {
+ if (init[0] == "var" && init[1].length > 1)
+ croak("Only one variable declaration allowed in for..in loop");
return for_in(init);
+ }
}
return regular_for(init);
};
@@ -919,7 +940,7 @@ function parse($TEXT, exigent_mode, embed_tokens) {
return as("for-in", init, lhs, obj, in_loop(statement));
};
- var function_ = maybe_embed_tokens(function(in_statement) {
+ var function_ = function(in_statement) {
var name = is("name") ? prog1(S.token.value, next) : null;
if (in_statement && !name)
unexpected();
@@ -947,7 +968,7 @@ function parse($TEXT, exigent_mode, embed_tokens) {
S.in_loop = loop;
return a;
})());
- });
+ };
function if_() {
var cond = parenthesised(), body = statement(), belse;
@@ -1060,11 +1081,6 @@ function parse($TEXT, exigent_mode, embed_tokens) {
next();
return new_();
}
- if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
- return make_unary("unary-prefix",
- prog1(S.token.value, next),
- expr_atom(allow_calls));
- }
if (is("punc")) {
switch (S.token.value) {
case "(":
@@ -1165,13 +1181,23 @@ function parse($TEXT, exigent_mode, embed_tokens) {
next();
return subscripts(as("call", expr, expr_list(")")), true);
}
- if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) {
- return prog1(curry(make_unary, "unary-postfix", S.token.value, expr),
- next);
- }
return expr;
};
+ function maybe_unary(allow_calls) {
+ if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
+ return make_unary("unary-prefix",
+ prog1(S.token.value, next),
+ maybe_unary(allow_calls));
+ }
+ var val = expr_atom(allow_calls);
+ while (is("operator") && HOP(UNARY_POSTFIX, S.token.value) && !S.token.nlb) {
+ val = make_unary("unary-postfix", S.token.value, val);
+ next();
+ }
+ return val;
+ };
+
function make_unary(tag, op, expr) {
if ((op == "++" || op == "--") && !is_assignable(expr))
croak("Invalid use of " + op + " operator");
@@ -1184,14 +1210,14 @@ function parse($TEXT, exigent_mode, embed_tokens) {
var prec = op != null ? PRECEDENCE[op] : null;
if (prec != null && prec > min_prec) {
next();
- var right = expr_op(expr_atom(true), prec, no_in);
+ var right = expr_op(maybe_unary(true), prec, no_in);
return expr_op(as("binary", op, left, right), min_prec, no_in);
}
return left;
};
function expr_ops(no_in) {
- return expr_op(expr_atom(true), 0, no_in);
+ return expr_op(maybe_unary(true), 0, no_in);
};
function maybe_conditional(no_in) {
@@ -1207,7 +1233,7 @@ function parse($TEXT, exigent_mode, embed_tokens) {
function is_assignable(expr) {
if (!exigent_mode) return true;
- switch (expr[0]) {
+ switch (expr[0]+"") {
case "dot":
case "sub":
case "new":
@@ -1285,7 +1311,7 @@ function array_to_hash(a) {
};
function slice(a, start) {
- return Array.prototype.slice.call(a, start == null ? 0 : start);
+ return Array.prototype.slice.call(a, start || 0);
};
function characters(str) {
@@ -1294,7 +1320,7 @@ function characters(str) {
function member(name, array) {
for (var i = array.length; --i >= 0;)
- if (array[i] === name)
+ if (array[i] == name)
return true;
return false;
};
@@ -1310,7 +1336,6 @@ var parsejs = {};
(function ( exports ) {
//>> End Uglifui
-
/* -----[ Exports ]----- */
exports.tokenizer = tokenizer;
@@ -1332,8 +1357,8 @@ exports.set_logger = function(logger) {
//>> Start Uglifui
})( parsejs );
-
+
global.parsejs = parsejs;
-
+
})( this );
//>> End Uglifui
View
705 js/uglifyjs/lib/process.js 100755 → 100644
@@ -69,7 +69,7 @@ var jsp = global.parsejs, //require("./parse-js"),
/* -----[ helper for AST traversal ]----- */
-function ast_walker(ast) {
+function ast_walker() {
function _vardefs(defs) {
return [ this[0], MAP(defs, function(def){
var a = [ def[0] ];
@@ -142,6 +142,9 @@ function ast_walker(ast) {
"function": function(name, args, body) {
return [ this[0], name, args.slice(), MAP(body, walk) ];
},
+ "debugger": function() {
+ return [ this[0] ];
+ },
"defun": function(name, args, body) {
return [ this[0], name, args.slice(), MAP(body, walk) ];
},
@@ -221,7 +224,22 @@ function ast_walker(ast) {
}
gen = walkers[type];
return gen.apply(ast, ast.slice(1));
- }
+ }
+ //>> Start Uglifui
+ catch (e) {}
+ //>> End Uglifui
+ finally {
+ stack.pop();
+ }
+ };
+
+ function dive(ast) {
+ if (ast == null)
+ return null;
+ try {
+ stack.push(ast);
+ return walkers[ast[0]].apply(ast, ast.slice(1));
+ }
//>> Start Uglifui
catch (e) {}
//>> End Uglifui
@@ -246,6 +264,7 @@ function ast_walker(ast) {
return {
walk: walk,
+ dive: dive,
with_walkers: with_walkers,
parent: function() {
return stack[stack.length - 2]; // last one is current node
@@ -277,12 +296,13 @@ function Scope(parent) {
};
var base54 = (function(){
- var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
+ var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
return function(num) {
- var ret = "";
+ var ret = "", base = 54;
do {
- ret = DIGITS.charAt(num % 54) + ret;
- num = Math.floor(num / 54);
+ ret += DIGITS.charAt(num % base);
+ num = Math.floor(num / base);
+ base = 64;
} while (num > 0);
return ret;
};
@@ -346,20 +366,27 @@ Scope.prototype = {
return m;
}
},
+ set_mangle: function(name, m) {
+ this.rev_mangled[m] = name;
+ return this.mangled[name] = m;
+ },
get_mangled: function(name, newMangle) {
if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
var s = this.has(name);
if (!s) return name; // not in visible scope, no mangle
if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
if (!newMangle) return name; // not found and no mangling requested
-
- var m = s.next_mangled();
- s.rev_mangled[m] = name;
- return s.mangled[name] = m;
+ return s.set_mangle(name, s.next_mangled());
},
- define: function(name) {
- if (name != null)
- return this.names[name] = name;
+ references: function(name) {
+ return name && !this.parent || this.uses_with || this.uses_eval || this.refs[name];
+ },
+ define: function(name, type) {
+ if (name != null) {
+ if (type == "var" || !HOP(this.names, name))
+ this.names[name] = type || "var";
+ return name;
+ }
}
};
@@ -371,14 +398,15 @@ function ast_add_scope(ast) {
function with_new_scope(cont) {
current_scope = new Scope(current_scope);
+ current_scope.labels = new Scope();
var ret = current_scope.body = cont();
ret.scope = current_scope;
current_scope = current_scope.parent;
return ret;
};
- function define(name) {
- return current_scope.define(name);
+ function define(name, type) {
+ return current_scope.define(name, type);
};
function reference(name) {
@@ -387,33 +415,46 @@ function ast_add_scope(ast) {
function _lambda(name, args, body) {
var is_defun = this[0] == "defun";
- return [ this[0], is_defun ? define(name) : name, args, with_new_scope(function(){
- if (!is_defun) define(name);
- MAP(args, define);
+ return [ this[0], is_defun ? define(name, "defun") : name, args, with_new_scope(function(){
+ if (!is_defun) define(name, "lambda");
+ MAP(args, function(name){ define(name, "arg") });
return MAP(body, walk);
})];
};
+ function _vardefs(type) {
+ return function(defs) {
+ MAP(defs, function(d){
+ define(d[0], type);
+ if (d[1]) reference(d[0]);
+ });
+ };
+ };
+
+ function _breacont(label) {
+ if (label)
+ current_scope.labels.refs[label] = true;
+ };
+
return with_new_scope(function(){
// process AST
var ret = w.with_walkers({
"function": _lambda,
"defun": _lambda,
+ "label": function(name, stat) { current_scope.labels.define(name) },
+ "break": _breacont,
+ "continue": _breacont,
"with": function(expr, block) {
for (var s = current_scope; s; s = s.parent)
s.uses_with = true;
},
- "var": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
- "const": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
+ "var": _vardefs("var"),
+ "const": _vardefs("const"),
"try": function(t, c, f) {
if (c != null) return [
this[0],
MAP(t, walk),
- [ define(c[0]), MAP(c[1], walk) ],
+ [ define(c[0], "catch"), MAP(c[1], walk) ],
f != null ? MAP(f, walk) : null
];
},
@@ -488,19 +529,33 @@ function ast_mangle(ast, options) {
};
function _lambda(name, args, body) {
- var is_defun = this[0] == "defun";
- if (is_defun && name) name = get_mangled(name);
+ if (!options.no_functions) {
+ var is_defun = this[0] == "defun", extra;
+ if (name) {
+ if (is_defun) name = get_mangled(name);
+ else if (body.scope.references(name)) {
+ extra = {};
+ if (!(scope.uses_eval || scope.uses_with))
+ name = extra[name] = scope.next_mangled();
+ else
+ extra[name] = name;
+ }
+ else name = null;
+ }
+ }
body = with_scope(body.scope, function(){
- if (!is_defun && name) name = get_mangled(name);
args = MAP(args, function(name){ return get_mangled(name) });
return MAP(body, walk);
- });
+ }, extra);
return [ this[0], name, args, body ];
};
- function with_scope(s, cont) {
+ function with_scope(s, cont, extra) {
var _scope = scope;
scope = s;
+ if (extra) for (var i in extra) if (HOP(extra, i)) {
+ s.set_mangle(i, extra[i]);
+ }
for (var i in s.names) if (HOP(s.names, i)) {
get_mangled(i, true);
}
@@ -516,6 +571,10 @@ function ast_mangle(ast, options) {
}) ];
};
+ function _breacont(label) {
+ if (label) return [ this[0], scope.labels.get_mangled(label) ];
+ };
+
return w.with_walkers({
"function": _lambda,
"defun": function() {
@@ -530,6 +589,16 @@ function ast_mangle(ast, options) {
}
return ast;
},
+ "label": function(label, stat) {
+ if (scope.labels.refs[label]) return [
+ this[0],
+ scope.labels.get_mangled(label, true),
+ walk(stat)
+ ];
+ return walk(stat);
+ },
+ "break": _breacont,
+ "continue": _breacont,
"var": _vardefs,
"const": _vardefs,
"name": function(name) {
@@ -576,10 +645,12 @@ function last_stat(b) {
}
function aborts(t) {
- if (t) {
- t = last_stat(t);
- if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
- return true;
+ if (t) switch (last_stat(t)[0]) {
+ case "return":
+ case "break":
+ case "continue":
+ case "throw":
+ return true;
}
};
@@ -608,21 +679,6 @@ function boolean_expr(expr) {
);
};
-function make_conditional(c, t, e) {
- var make_real_conditional = function() {
- if (c[0] == "unary-prefix" && c[1] == "!") {
- return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
- } else {
- return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
- }
- };
- // shortcut the conditional if the expression has a constant value
- return when_constant(c, function(ast, val){
- warn_unreachable(val ? e : t);
- return (val ? t : e);
- }, make_real_conditional);
-};
-
function empty(b) {
return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
};
@@ -650,6 +706,7 @@ var when_constant = (function(){
switch (expr[1]) {
case "true": return true;
case "false": return false;
+ case "null": return null;
}
break;
case "unary-prefix":
@@ -672,6 +729,7 @@ var when_constant = (function(){
case "+" : return evaluate(left) + evaluate(right);
case "*" : return evaluate(left) * evaluate(right);
case "/" : return evaluate(left) / evaluate(right);
+ case "%" : return evaluate(left) % evaluate(right);
case "-" : return evaluate(left) - evaluate(right);
case "<<" : return evaluate(left) << evaluate(right);
case ">>" : return evaluate(left) >> evaluate(right);
@@ -698,7 +756,9 @@ var when_constant = (function(){
case "string": ast = [ "string", val ]; break;
case "number": ast = [ "num", val ]; break;
case "boolean": ast = [ "name", String(val) ]; break;
- default: throw new Error("Can't handle constant of type: " + (typeof val));
+ default:
+ if (val === null) { ast = [ "atom", "null" ]; break; }
+ throw new Error("Can't handle constant of type: " + (typeof val));
}
return yes.call(expr, ast, val);
} catch(ex) {
@@ -734,15 +794,225 @@ function warn_unreachable(ast) {
warn("Dropping unreachable code: " + gen_code(ast, true));
};
+function prepare_ifs(ast) {
+ var w = ast_walker(), walk = w.walk;
+ // In this first pass, we rewrite ifs which abort with no else with an
+ // if-else. For example:
+ //
+ // if (x) {
+ // blah();
+ // return y;
+ // }
+ // foobar();
+ //
+ // is rewritten into:
+ //
+ // if (x) {
+ // blah();
+ // return y;
+ // } else {
+ // foobar();
+ // }
+ function redo_if(statements) {
+ statements = MAP(statements, walk);
+
+ for (var i = 0; i < statements.length; ++i) {
+ var fi = statements[i];
+ if (fi[0] != "if") continue;
+
+ if (fi[3] && walk(fi[3])) continue;
+
+ var t = walk(fi[2]);
+ if (!aborts(t)) continue;
+
+ var conditional = walk(fi[1]);
+
+ var e_body = redo_if(statements.slice(i + 1));
+ var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ];
+
+ return statements.slice(0, i).concat([ [
+ fi[0], // "if"
+ conditional, // conditional
+ t, // then
+ e // else
+ ] ]);
+ }
+
+ return statements;
+ };
+
+ function redo_if_lambda(name, args, body) {
+ body = redo_if(body);
+ return [ this[0], name, args, body ];
+ };
+
+ function redo_if_block(statements) {
+ return [ this[0], statements != null ? redo_if(statements) : null ];
+ };
+
+ return w.with_walkers({
+ "defun": redo_if_lambda,
+ "function": redo_if_lambda,
+ "block": redo_if_block,
+ "splice": redo_if_block,
+ "toplevel": function(statements) {
+ return [ this[0], redo_if(statements) ];
+ },
+ "try": function(t, c, f) {
+ return [
+ this[0],
+ redo_if(t),
+ c != null ? [ c[0], redo_if(c[1]) ] : null,
+ f != null ? redo_if(f) : null
+ ];
+ }
+ }, function() {
+ return walk(ast);
+ });
+};
+
+function for_side_effects(ast, handler) {
+ var w = ast_walker(), walk = w.walk;
+ var $stop = {}, $restart = {};
+ function stop() { throw $stop };
+ function restart() { throw $restart };
+ function found(){ return handler.call(this, this, w, stop, restart) };
+ function unary(op) {
+ if (op == "++" || op == "--")
+ return found.apply(this, arguments);
+ };
+ return w.with_walkers({
+ "try": found,
+ "throw": found,
+ "return": found,
+ "new": found,
+ "switch": found,
+ "break": found,
+ "continue": found,
+ "assign": found,
+ "call": found,
+ "if": found,
+ "for": found,
+ "for-in": found,
+ "while": found,
+ "do": found,
+ "return": found,
+ "unary-prefix": unary,
+ "unary-postfix": unary,
+ "defun": found
+ }, function(){
+ while (true) try {
+ walk(ast);
+ break;
+ } catch(ex) {
+ if (ex === $stop) break;
+ if (ex === $restart) continue;
+ throw ex;
+ }
+ });
+};
+
+function ast_lift_variables(ast) {
+ var w = ast_walker(), walk = w.walk, scope;
+ function do_body(body, env) {
+ var _scope = scope;
+ scope = env;
+ body = MAP(body, walk);
+ var hash = {}, names = MAP(env.names, function(type, name){
+ if (type != "var") return MAP.skip;
+ if (!env.references(name)) return MAP.skip;
+ hash[name] = true;
+ return [ name ];
+ });
+ if (names.length > 0) {
+ // looking for assignments to any of these variables.
+ // we can save considerable space by moving the definitions
+ // in the var declaration.
+ for_side_effects([ "block", body ], function(ast, walker, stop, restart) {
+ if (ast[0] == "assign"
+ && ast[1] === true
+ && ast[2][0] == "name"
+ && HOP(hash, ast[2][1])) {
+ // insert the definition into the var declaration
+ for (var i = names.length; --i >= 0;) {
+ if (names[i][0] == ast[2][1]) {
+ if (names[i][1]) // this name already defined, we must stop
+ stop();
+ names[i][1] = ast[3]; // definition
+ names.push(names.splice(i, 1)[0]);
+ break;
+ }
+ }
+ // remove this assignment from the AST.
+ var p = walker.parent();
+ if (p[0] == "seq") {
+ var a = p[2];
+ a.unshift(0, p.length);
+ p.splice.apply(p, a);
+ }
+ else if (p[0] == "stat") {
+ p.splice(0, p.length, "block"); // empty statement
+ }
+ else {
+ stop();
+ }
+ restart();
+ }
+ stop();
+ });
+ body.unshift([ "var", names ]);
+ }
+ scope = _scope;
+ return body;
+ };
+ function _vardefs(defs) {
+ var ret = null;
+ for (var i = defs.length; --i >= 0;) {
+ var d = defs[i];
+ if (!d[1]) continue;
+ d = [ "assign", true, [ "name", d[0] ], d[1] ];
+ if (ret == null) ret = d;
+ else ret = [ "seq", d, ret ];
+ }
+ if (ret == null) {
+ if (w.parent()[0] == "for-in")
+ return [ "name", defs[0][0] ];
+ return MAP.skip;
+ }
+ return [ "stat", ret ];
+ };
+ function _toplevel(body) {
+ return [ this[0], do_body(body, this.scope) ];
+ };
+ return w.with_walkers({
+ "function": function(name, args, body){
+ for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
+ args.pop();
+ if (!body.scope.references(name)) name = null;
+ return [ this[0], name, args, do_body(body, body.scope) ];
+ },
+ "defun": function(name, args, body){
+ if (!scope.references(name)) return MAP.skip;
+ for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
+ args.pop();
+ return [ this[0], name, args, do_body(body, body.scope) ];
+ },
+ "var": _vardefs,
+ "toplevel": _toplevel
+ }, function(){
+ return walk(ast_add_scope(ast));
+ });
+};
+
function ast_squeeze(ast, options) {
options = defaults(options, {
make_seqs : true,
dead_code : true,
- keep_comps : true,
- no_warnings : false
+ no_warnings : false,
+ keep_comps : true
});
- var w = ast_walker(), walk = w.walk, scope;
+ var w = ast_walker(), walk = w.walk;
function negate(c) {
var not_c = [ "unary-prefix", "!", c ];
@@ -776,13 +1046,22 @@ function ast_squeeze(ast, options) {
return not_c;
};
- function with_scope(s, cont) {
- var _scope = scope;
- scope = s;
- var ret = cont();
- ret.scope = s;
- scope = _scope;
- return ret;
+ function make_conditional(c, t, e) {
+ var make_real_conditional = function() {
+ if (c[0] == "unary-prefix" && c[1] == "!") {
+ return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
+ } else {
+ return e ? best_of(
+ [ "conditional", c, t, e ],
+ [ "conditional", negate(c), e, t ]
+ ) : [ "binary", "&&", c, t ];
+ }
+ };
+ // shortcut the conditional if the expression has a constant value
+ return when_constant(c, function(ast, val){
+ warn_unreachable(val ? e : t);
+ return (val ? t : e);
+ }, make_real_conditional);
};
function rmblock(block) {
@@ -796,24 +1075,18 @@ function ast_squeeze(ast, options) {
};
function _lambda(name, args, body) {
- var is_defun = this[0] == "defun";
- body = with_scope(body.scope, function(){
- var ret = tighten(MAP(body, walk), "lambda");
- if (!is_defun && name && !HOP(scope.refs, name))
- name = null;
- return ret;
- });
- return [ this[0], name, args, body ];
+ return [ this[0], name, args, tighten(body, "lambda") ];
};
- // we get here for blocks that have been already transformed.
// this function does a few things:
// 1. discard useless blocks
// 2. join consecutive var declarations
// 3. remove obviously dead code
// 4. transform consecutive statements using the comma operator
// 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
function tighten(statements, block_type) {
+ statements = MAP(statements, walk);
+
statements = _(statements).reduce(function(a, stat){
if (stat[0] == "block") {
if (stat[1]) {
@@ -841,7 +1114,17 @@ function ast_squeeze(ast, options) {
if (options.dead_code) statements = (function(a, has_quit){
_(statements).forEach(function(st){
if (has_quit) {
- if (member(st[0], [ "function", "defun" , "var", "const" ])) {
+ if (st[0] == "function" || st[0] == "defun") {
+ a.push(st);
+ }
+ else if (st[0] == "var" || st[0] == "const") {
+ if (!options.no_warnings)
+ warn("Variables declared in unreachable code");
+ st[1] = MAP(st[1], function(def){
+ if (def[1] && !options.no_warnings)
+ warn_unreachable([ "assign", true, [ "name", def[0] ], def[1] ]);
+ return [ def[0] ];
+ });
a.push(st);
}
else if (!options.no_warnings)
@@ -865,45 +1148,68 @@ function ast_squeeze(ast, options) {
prev = cur;
}
});
+ if (a.length >= 2
+ && a[a.length-2][0] == "stat"
+ && (a[a.length-1][0] == "return" || a[a.length-1][0] == "throw")
+ && a[a.length-1][1])
+ {
+ a.splice(a.length - 2, 2,
+ [ a[a.length-1][0],
+ [ "seq", a[a.length-2][1], a[a.length-1][1] ]]);
+ }
return a;
})([]);
- if (block_type == "lambda") statements = (function(i, a, stat){
- while (i < statements.length) {
- stat = statements[i++];
- if (stat[0] == "if" && !stat[3]) {
- if (stat[2][0] == "return" && stat[2][1] == null) {
- a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
- break;
- }
- var last = last_stat(stat[2]);
- if (last[0] == "return" && last[1] == null) {
- a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
- break;
- }
- }
- a.push(stat);
- }
- return a;
- })(0, []);
+ // this increases jQuery by 1K. Probably not such a good idea after all..
+ // part of this is done in prepare_ifs anyway.
+ // if (block_type == "lambda") statements = (function(i, a, stat){
+ // while (i < statements.length) {
+ // stat = statements[i++];
+ // if (stat[0] == "if" && !stat[3]) {
+ // if (stat[2][0] == "return" && stat[2][1] == null) {
+ // a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
+ // break;
+ // }
+ // var last = last_stat(stat[2]);
+ // if (last[0] == "return" && last[1] == null) {
+ // a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
+ // break;
+ // }
+ // }
+ // a.push(stat);
+ // }
+ // return a;
+ // })(0, []);
return statements;
};
function make_if(c, t, e) {
return when_constant(c, function(ast, val){
if (val) {
+ t = walk(t);
warn_unreachable(e);
- return t;
+ return t || [ "block" ];
} else {
+ e = walk(e);
warn_unreachable(t);
- return e;
+ return e || [ "block" ];
}
}, function() {
return make_real_if(c, t, e);
});
};
+ function abort_else(c, t, e) {
+ var ret = [ [ "if", negate(c), e ] ];
+ if (t[0] == "block") {
+ if (t[1]) ret = ret.concat(t[1]);
+ } else {
+ ret.push(t);
+ }
+ return walk([ "block", ret ]);
+ };
+
function make_real_if(c, t, e) {
c = walk(c);
t = walk(t);
@@ -937,9 +1243,10 @@ function ast_squeeze(ast, options) {
}
else if (t[0] == "stat") {
if (e) {
- if (e[0] == "stat") {
+ if (e[0] == "stat")
ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
- }
+ else if (aborts(e))
+ ret = abort_else(c, t, e);
}
else {
ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
@@ -959,13 +1266,7 @@ function ast_squeeze(ast, options) {
ret = walk([ "block", ret ]);
}
else if (t && aborts(e)) {
- ret = [ [ "if", negate(c), e ] ];
- if (t[0] == "block") {
- if (t[1]) ret = ret.concat(t[1]);
- } else {
- ret.push(t);
- }
- ret = walk([ "block", ret ]);
+ ret = abort_else(c, t, e);
}
return ret;
};
@@ -993,14 +1294,12 @@ function ast_squeeze(ast, options) {
},
"if": make_if,
"toplevel": function(body) {
- return [ "toplevel", with_scope(this.scope, function(){
- return tighten(MAP(body, walk));
- }) ];
+ return [ "toplevel", tighten(body) ];
},
"switch": function(expr, body) {
var last = body.length - 1;
return [ "switch", walk(expr), MAP(body, function(branch, i){
- var block = tighten(MAP(branch[1], walk));
+ var block = tighten(branch[1]);
if (i == last && block.length > 0) {
var node = block[block.length - 1];
if (node[0] == "break" && !node[1])
@@ -1012,13 +1311,21 @@ function ast_squeeze(ast, options) {
"function": _lambda,
"defun": _lambda,
"block": function(body) {
- if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
+ if (body) return rmblock([ "block", tighten(body) ]);
},
"binary": function(op, left, right) {
return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
return best_of(walk(c), this);
}, function no() {
- return this;
+ return function(){
+ if(op != "==" && op != "!=") return;
+ var l = walk(left), r = walk(right);
+ if(l && l[0] == "unary-prefix" && l[1] == "!" && l[2][0] == "num")
+ left = ['num', +!l[2][1]];
+ else if (r && r[0] == "unary-prefix" && r[1] == "!" && r[2][0] == "num")
+ right = ['num', +!r[2][1]];
+ return ["binary", op, left, right];
+ }() || this;
});
},
"conditional": function(c, t, e) {
@@ -1027,9 +1334,9 @@ function ast_squeeze(ast, options) {
"try": function(t, c, f) {
return [
"try",
- tighten(MAP(t, walk)),
- c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
- f != null ? tighten(MAP(f, walk)) : null
+ tighten(t),
+ c != null ? [ c[0], tighten(c[1]) ] : null,
+ f != null ? tighten(f) : null
];
},
"unary-prefix": function(op, expr) {
@@ -1047,23 +1354,24 @@ function ast_squeeze(ast, options) {
case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
}
},
- "new": function(ctor, args) {
- if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
- if (args.length != 1) {
- return [ "array", args ];
- } else {
- return [ "call", [ "name", "Array" ], args ];
- }
- }
- },
- "call": function(expr, args) {
- if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
- return [ "array", args ];
+ "while": _do_while,
+ "assign": function(op, lvalue, rvalue) {
+ lvalue = walk(lvalue);
+ rvalue = walk(rvalue);
+ var okOps = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
+ if (op === true && lvalue[0] === "name" && rvalue[0] === "binary" &&
+ ~okOps.indexOf(rvalue[1]) && rvalue[2][0] === "name" &&
+ rvalue[2][1] === lvalue[1]) {
+ return [ this[0], rvalue[1], lvalue, rvalue[3] ]
}
- },
- "while": _do_while
+ return [ this[0], op, lvalue, rvalue ];
+ }
}, function() {
- return walk(ast_add_scope(ast));
+ for (var i = 0; i < 2; ++i) {
+ ast = prepare_ifs(ast);
+ ast = walk(ast);
+ }
+ return ast;
});
};
@@ -1077,12 +1385,13 @@ var DOT_CALL_NO_PARENS = jsp.array_to_hash([
"dot",
"sub",
"call",
- "regexp"
+ "regexp",
+ "defun"
]);
function make_string(str, ascii_only) {
var dq = 0, sq = 0;
- str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029]/g, function(s){
+ str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(s){
switch (s) {
case "\\": return "\\\\";
case "\b": return "\\b";
@@ -1094,6 +1403,7 @@ function make_string(str, ascii_only) {
case "\u2029": return "\\u2029";
case '"': ++dq; return '"';
case "'": ++sq; return "'";
+ case "\0": return "\\0";
}
return s;
});
@@ -1119,15 +1429,19 @@ function gen_code(ast, options) {
quote_keys : false,
space_colon : false,
beautify : false,
- ascii_only : false
+ ascii_only : false,
+ inline_script: false
});
var beautify = !!options.beautify;
var indentation = 0,
newline = beautify ? "\n" : "",
space = beautify ? " " : "";
function encode_string(str) {
- return make_string(str, options.ascii_only);
+ var ret = make_string(str, options.ascii_only);
+ if (options.inline_script)
+ ret = ret.replace(/<\x2fscript([>\/\t\n\f\r ])/gi, "<\\/script$1");
+ return ret;
};
function make_name(name) {
@@ -1149,9 +1463,7 @@ function gen_code(ast, options) {
if (incr == null) incr = 1;
indentation += incr;
try { return cont.apply(null, slice(arguments, 1)); }
- //>> Start Uglifui
- catch (e) {}
- //>> End Uglifui
+ catch (e){}
finally { indentation -= incr; }
};
@@ -1207,7 +1519,7 @@ function gen_code(ast, options) {
// we're the first in this "seq" and the
// parent is "stat", and so on. Messy stuff,
// but it worths the trouble.
- var a = slice($stack), self = a.pop(), p = a.pop();
+ var a = slice(w.stack()), self = a.pop(), p = a.pop();
while (p) {
if (p[0] == "stat") return true;
if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
@@ -1225,8 +1537,13 @@ function gen_code(ast, options) {
function make_num(num) {
var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
if (Math.floor(num) === num) {
- a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
- "0" + num.toString(8)); // same.
+ if (num >= 0) {
+ a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
+ "0" + num.toString(8)); // same.
+ } else {
+ a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless
+ "-0" + (-num).toString(8)); // same.
+ }
if ((m = /^(.*?)(0+)$/.exec(num))) {
a.push(m[1] + "e" + m[2].length);
}
@@ -1237,16 +1554,19 @@ function gen_code(ast, options) {
return best_of(a);
};
- var generators = {
+ var w = ast_walker();
+ var make = w.walk;
+ return w.with_walkers({
"string": encode_string,
"num": make_num,
"name": make_name,
+ "debugger": function(){ return "debugger" },
"toplevel": function(statements) {
return make_block_statements(statements)
.join(newline + newline);
},
"splice": function(statements) {
- var parent = $stack[$stack.length - 2][0];
+ var parent = w.parent();
if (HOP(SPLICE_NEEDS_BRACKETS, parent)) {
// we need block brackets in this case
return make_block.apply(this, arguments);
@@ -1275,7 +1595,9 @@ function gen_code(ast, options) {
return add_spaces([ "throw", make(expr) ]) + ";";
},
"new": function(ctor, args) {
- args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : "";
+ args = args.length > 0 ? "(" + add_commas(MAP(args, function(expr){
+ return parenthesize(expr, "seq");
+ })) + ")" : "";
return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
var w = ast_walker(), has_call = {};
try {
@@ -1330,7 +1652,7 @@ function gen_code(ast, options) {
},
"call": function(func, args) {
var f = make(func);
- if (needs_parens(func))
+ if (f.charAt(0) != "(" && needs_parens(func))
f = "(" + f + ")";
return f + "(" + add_commas(MAP(args, function(expr){
return parenthesize(expr, "seq");
@@ -1378,14 +1700,19 @@ function gen_code(ast, options) {
// we need to be smarter.
// adding parens all the time is the safest bet.
if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
- lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) {
+ lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]] ||
+ lvalue[0] == "function" && needs_parens(this)) {
left = "(" + left + ")";
}
if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
!(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
right = "(" + right + ")";
}
+ else if (!beautify && options.inline_script && (operator == "<" || operator == "<<")
+ && rvalue[0] == "regexp" && /^script/i.test(rvalue[1])) {
+ right = " " + right;
+ }
return add_spaces([ left, operator, right ]);
},
"unary-prefix": function(operator, expr) {
@@ -1407,16 +1734,17 @@ function gen_code(ast, options) {
return hash + "[" + make(subscript) + "]";
},
"object": function(props) {
+ var obj_needs_parens = needs_parens(this);
if (props.length == 0)
- return "{}";
- return "{" + newline + with_indent(function(){
+ return obj_needs_parens ? "({})" : "{}";
+ var out = "{" + newline + with_indent(function(){
return MAP(props, function(p){
if (p.length == 3) {
// getter/setter. The name is in p[0], the arg.list in p[1][2], the
// body in p[1][3] and type ("get" / "set") in p[2].
- return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
+ return indent(make_function(p[0], p[1][2], p[1][3], p[2], true));
}
- var key = p[0], val = make(p[1]);
+ var key = p[0], val = parenthesize(p[1], "seq");
if (options.quote_keys) {
key = encode_string(key);
} else if ((typeof key == "number" || !beautify && +key + "" == key)
@@ -1430,14 +1758,15 @@ function gen_code(ast, options) {
: [ key + ":", val ]));
}).join("," + newline);
}) + newline + indent("}");
+ return obj_needs_parens ? "(" + out + ")" : out;
},
"regexp": function(rx, mods) {
return "/" + rx + "/" + mods;
},
"array": function(elements) {
if (elements.length == 0) return "[]";
- return add_spaces([ "[", add_commas(MAP(elements, function(el){
- if (!beautify && el[0] == "atom" && el[1] == "undefined") return "";
+ return add_spaces([ "[", add_commas(MAP(elements, function(el, i){
+ if (!beautify && el[0] == "atom" && el[1] == "undefined") return i === elements.length - 1 ? "," : "";
return parenthesize(el, "seq");
})), "]" ]);
},
@@ -1456,7 +1785,7 @@ function gen_code(ast, options) {
"atom": function(name) {
return make_name(name);
}
- };
+ }, function(){ return make(ast) });
// The squeezer replaces "block"-s that contain only a single
// statement with the statement itself; technically, the AST
@@ -1466,12 +1795,13 @@ function gen_code(ast, options) {
// to the inner IF). This function checks for this case and
// adds the block brackets if needed.
function make_then(th) {
+ if (th == null) return ";";
if (th[0] == "do") {
// https://github.com/mishoo/UglifyJS/issues/#issue/57
// IE croaks with "syntax error" on code like this:
// if (foo) do ... while(cond); else ...
// we need block brackets around do/while
- return make([ "block", [ th ]]);
+ return make_block([ th ]);
}
var b = th;
while (true) {
@@ -1489,29 +1819,41 @@ function gen_code(ast, options) {
return make(th);
};
- function make_function(name, args, body, keyword) {
+ function make_function(name, args, body, keyword, no_parens) {
var out = keyword || "function";
if (name) {
out += " " + make_name(name);
}
out += "(" + add_commas(MAP(args, make_name)) + ")";
- return add_spaces([ out, make_block(body) ]);
+ out = add_spaces([ out, make_block(body) ]);
+ return (!no_parens && needs_parens(this)) ? "(" + out + ")" : out;
+ };
+
+ function must_has_semicolon(node) {
+ switch (node[0]) {
+ case "with":
+ case "while":
+ return empty(node[2]); // `with' or `while' with empty body?
+ case "for":
+ case "for-in":
+ return empty(node[4]); // `for' with empty body?
+ case "if":
+ if (empty(node[2]) && !node[3]) return true; // `if' with empty `then' and no `else'
+ if (node[3]) {
+ if (empty(node[3])) return true; // `else' present but empty
+ return must_has_semicolon(node[3]); // dive into the `else' branch
+ }
+ return must_has_semicolon(node[2]); // dive into the `then' branch
+ }
};
function make_block_statements(statements, noindent) {
for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
var stat = statements[i];
var code = make(stat);
if (code != ";") {
- if (!beautify && i == last) {
- if ((stat[0] == "while" && empty(stat[2])) ||
- (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
- (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
- (stat[0] == "if" && stat[3] && empty(stat[3]))) {
- code = code.replace(/;*\s*$/, ";");
- } else {
- code = code.replace(/;+\s*$/, "");
- }
+ if (!beautify && i == last && !must_has_semicolon(stat)) {
+ code = code.replace(/;+\s*$/, "");
}
a.push(code);
}
@@ -1551,20 +1893,6 @@ function gen_code(ast, options) {
return name;
};
- var $stack = [];
-
- function make(node) {
- var type = node[0];
- var gen = generators[type];
- if (!gen)
- throw new Error("Can't find generator for \"" + type + "\"");
- $stack.push(node);
- var ret = gen.apply(type, node.slice(1));
- $stack.pop();
- return ret;
- };
-
- return make(ast);
};
function split_lines(code, max_line_length) {
@@ -1605,7 +1933,7 @@ function split_lines(code, max_line_length) {
};
return custom;
}());
- return _(splits).map(function(pos, i){
+ return splits.map(function(pos, i){
return code.substring(pos, splits[i + 1] || code.length);
}).join("\n");
};
@@ -1649,28 +1977,47 @@ var MAP;
(function(){
MAP = function(a, f, o) {
- var ret = [];
- for (var i = 0; i < a.length; ++i) {
+ var ret = [], top = [], i;
+ function doit() {
var val = f.call(o, a[i], i);
- if (val instanceof AtTop) ret.unshift(val.v);
- else ret.push(val);
- }
- return ret;
+ if (val instanceof AtTop) {
+ val = val.v;
+ if (val instanceof Splice) {
+ top.push.apply(top, val.v);
+ } else {
+ top.push(val);
+ }
+ }
+ else if (val != skip) {
+ if (val instanceof Splice) {
+ ret.push.apply(ret, val.v);
+ } else {
+ ret.push(val);
+ }
+ }
+ };
+ if (a instanceof Array) for (i = 0; i < a.length; ++i) doit();
+ else for (i in a) if (HOP(a, i)) doit();
+ return top.concat(ret);
};
MAP.at_top = function(val) { return new AtTop(val) };
+ MAP.splice = function(val) { return new Splice(val) };
+ var skip = MAP.skip = {};
function AtTop(val) { this.v = val };
+ function Splice(val) { this.v = val };
})();
-/* -----[ Exports ]----- */
//>> Start Uglifui
var process = {};
(function ( exports ) {
//>> End Uglifui
+/* -----[ Exports ]----- */
exports.ast_walker = ast_walker;
exports.ast_mangle = ast_mangle;
exports.ast_squeeze = ast_squeeze;
+exports.ast_lift_variables = ast_lift_variables;
exports.gen_code = gen_code;
exports.ast_add_scope = ast_add_scope;
exports.set_logger = function(logger) { warn = logger };
View
54 js/uglifyjs/lib/squeeze-more.js
@@ -6,23 +6,69 @@ var jsp = global.parsejs, //require("./parse-js"),
//>> End Uglifui
slice = jsp.slice,
member = jsp.member,
+ curry = jsp.curry,
+ MAP = pro.MAP,
PRECEDENCE = jsp.PRECEDENCE,
OPERATORS = jsp.OPERATORS;
function ast_squeeze_more(ast) {
- var w = pro.ast_walker(), walk = w.walk;
+ var w = pro.ast_walker(), walk = w.walk, scope;
+ function with_scope(s, cont) {
+ var save = scope, ret;
+ scope = s;
+ ret = cont();
+ scope = save;
+ return ret;
+ };
+ function _lambda(name, args, body) {
+ return [ this[0], name, args, with_scope(body.scope, curry(MAP, body, walk)) ];
+ };
return w.with_walkers({
+ "toplevel": function(body) {
+ return [ this[0], with_scope(this.scope, curry(MAP, body, walk)) ];
+ },
+ "function": _lambda,
+ "defun": _lambda,
+ "new": function(ctor, args) {
+ if (ctor[0] == "name") {
+ if (ctor[1] == "Array" && !scope.has("Array")) {
+ if (args.length != 1) {
+ return [ "array", args ];
+ } else {
+ return walk([ "call", [ "name", "Array" ], args ]);
+ }
+ } else if (ctor[1] == "Object" && !scope.has("Object")) {
+ if (!args.length) {
+ return [ "object", [] ];
+ } else {
+ return walk([ "call", [ "name", "Object" ], args ]);
+ }
+ } else if ((ctor[1] == "RegExp" || ctor[1] == "Function" || ctor[1] == "Error") && !scope.has(ctor[1])) {
+ return walk([ "call", [ "name", ctor[1] ], args]);
+ }
+ }
+ },
"call": function(expr, args) {
if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
// foo.toString() ==> foo+""
return [ "binary", "+", expr[1], [ "string", "" ]];
}
+ if (expr[0] == "name") {
+ if (expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
+ return [ "array", args ];
+ }
+ if (expr[1] == "Object" && !args.length && !scope.has("Object")) {
+ return [ "object", [] ];
+ }
+ if (expr[1] == "String" && !scope.has("String")) {
+ return [ "binary", "+", args[0], [ "string", "" ]];
+ }
+ }
}
}, function() {
- return walk(ast);
+ return walk(pro.ast_add_scope(ast));
});
-};
-
+}
//>> Start Uglifui
var squeezemore = {};
(function ( exports ) {

0 comments on commit bb1ead9

Please sign in to comment.