lib/uglify.js in uglifier-1.0.0 vs lib/uglify.js in uglifier-1.0.1

- old
+ new

@@ -237,11 +237,11 @@ "&=", "&&", "||" ]); -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")); var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); @@ -452,15 +452,16 @@ 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 "\0"; case "x" : return String.fromCharCode(hex_bytes(2)); case "u" : return String.fromCharCode(hex_bytes(4)); + case "\n": return ""; default : return ch; } }; function hex_bytes(n) { @@ -477,11 +478,28 @@ function read_string() { return with_eof_error("Unterminated string constant", function(){ 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(); + } else if (ch == quote) break; ret += ch; } return token("string", ret); }); @@ -1105,15 +1123,10 @@ var expr_atom = maybe_embed_tokens(function(allow_calls) { if (is("operator", "new")) { 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 "(": next(); return subscripts(prog1(expression, curry(expect, ")")), allow_calls); @@ -1210,17 +1223,27 @@ } if (allow_calls && is("punc", "(")) { 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"); return as(tag, op, expr); }; @@ -1229,18 +1252,18 @@ var op = is("operator") ? S.token.value : null; if (op && op == "in" && no_in) op = null; 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) { var expr = expr_ops(no_in); if (is("operator", "?")) { @@ -1705,24 +1728,31 @@ continue; 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; + } } }; function ast_add_scope(ast) { @@ -1736,47 +1766,55 @@ 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) { current_scope.refs[name] = true; }; 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]); + }); + }; + }; + return with_new_scope(function(){ // process AST var ret = w.with_walkers({ "function": _lambda, "defun": _lambda, + "label": function(name, stat) { define(name, "label") }, + "break": function(label) { if (label) reference(label) }, + "continue": function(label) { if (label) reference(label) }, "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 ]; }, "name": function(name) { if (name == "eval") @@ -1847,23 +1885,34 @@ return null; } }; function _lambda(name, args, body) { - var is_defun = this[0] == "defun"; - if (is_defun && name) name = get_mangled(name); + var is_defun = this[0] == "defun", extra; + if (name) { + if (is_defun) name = get_mangled(name); + else { + extra = {}; + if (!(scope.uses_eval || scope.uses_with)) + name = extra[name] = scope.next_mangled(); + else + extra[name] = name; + } + } 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); } var ret = cont(); ret.scope = s; @@ -1889,10 +1938,13 @@ case "defun": return MAP.at_top(ast); } return ast; }, + "label": function(label, stat) { return [ this[0], get_mangled(label), walk(stat) ] }, + "break": function(label) { if (label) return [ this[0], get_mangled(label) ] }, + "continue": function(label) { if (label) return [ this[0], get_mangled(label) ] }, "var": _vardefs, "const": _vardefs, "name": function(name) { return get_define(name) || [ this[0], get_mangled(name) ]; }, @@ -1935,14 +1987,16 @@ return b[1][b[1].length - 1]; return 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; } }; function boolean_expr(expr) { return ( (expr[0] == "unary-prefix" @@ -2093,10 +2147,222 @@ function warn_unreachable(ast) { if (!empty(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 = statements.slice(i + 1); + var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ]; + + var ret = statements.slice(0, i).concat([ [ + fi[0], // "if" + conditional, // conditional + t, // then + e // else + ] ]); + + return redo_if(ret); + } + + 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, @@ -2157,26 +2423,27 @@ }; 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)) + var ret = tighten(body, "lambda"); + if (!is_defun && name && !scope.references(name)) name = null; return ret; }); return [ this[0], name, args, body ]; }; - // 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]) { a.push.apply(a, stat[1]); } @@ -2200,13 +2467,23 @@ })([]); 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) warn_unreachable(st); } else { a.push(st); @@ -2224,31 +2501,42 @@ } else { a.push(cur); 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) { @@ -2353,17 +2641,17 @@ } }, "if": make_if, "toplevel": function(body) { return [ "toplevel", with_scope(this.scope, function(){ - return tighten(MAP(body, walk)); + return 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]) block.pop(); } @@ -2371,11 +2659,11 @@ }) ]; }, "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() { @@ -2386,13 +2674,13 @@ return make_conditional(walk(c), walk(t), walk(e)); }, "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) { expr = walk(expr); var ret = [ "unary-prefix", op, expr ]; @@ -2422,11 +2710,16 @@ return [ "array", args ]; } }, "while": _do_while }, function() { - return walk(ast_add_scope(ast)); + for (var i = 0; i < 2; ++i) { + ast = prepare_ifs(ast); + ast = ast_add_scope(ast); + ast = walk(ast); + } + return ast; }); }; /* -----[ re-generate code from the AST ]----- */ @@ -2479,19 +2772,23 @@ indent_start : 0, indent_level : 4, 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) { name = name.toString(); if (options.ascii_only) @@ -2564,11 +2861,11 @@ // statement. This means that the parent is // "stat", but it could also be a "seq" and // 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) || ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) { self = p; @@ -2594,20 +2891,22 @@ str.substr(str.indexOf("."))); } 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, "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); } else { return MAP(make_block_statements(statements, true), @@ -2743,10 +3042,14 @@ 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) { var val = make(expr); if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr))) @@ -2773,11 +3076,11 @@ 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])); } - 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) && parseFloat(key) >= 0) { key = make_num(+key); @@ -2813,11 +3116,11 @@ return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]); }, "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 // is correct, but this can create problems when we output an // IF having an ELSE clause where the THEN clause ends in an @@ -2828,11 +3131,11 @@ 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) { var type = b[0]; if (type == "if") { @@ -2855,24 +3158,35 @@ } out += "(" + add_commas(MAP(args, make_name)) + ")"; return add_spaces([ out, make_block(body) ]); }; + 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); } } return noindent ? a : MAP(a, indent); @@ -2908,24 +3222,10 @@ if (val != null) name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]); 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) { var splits = [ 0 ]; jsp.parse(function(){ @@ -3006,26 +3306,45 @@ 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 ]----- */ 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 }; exports.make_string = make_string; exports.split_lines = split_lines;