diff options
-rw-r--r-- | tools/eliminator/node_modules/uglify-js/lib/process.js | 2560 |
1 files changed, 742 insertions, 1818 deletions
diff --git a/tools/eliminator/node_modules/uglify-js/lib/process.js b/tools/eliminator/node_modules/uglify-js/lib/process.js index c3abb6f8..70a4b0a4 100644 --- a/tools/eliminator/node_modules/uglify-js/lib/process.js +++ b/tools/eliminator/node_modules/uglify-js/lib/process.js @@ -1,6 +1,6 @@ /*********************************************************************** - A JavaScript tokenizer / parser / beautifier / compressor. + A JavaScript tokenizer / parser / beautifier. This version is suitable for Node.js. With minimal changes (the exports stuff) it should work on any JS platform. @@ -10,12 +10,6 @@ Exported functions: - - ast_mangle(ast, options) -- mangles the variable/function names - in the AST. Returns an AST. - - - ast_squeeze(ast) -- employs various optimizations to make the - final generated code even smaller. Returns an AST. - - gen_code(ast, options) -- generates JS code from the AST. Pass true (or an object, see the code for some options) as second argument to get "pretty" (indented) code. @@ -67,1835 +61,773 @@ var jsp = require("./parse-js"), /* -----[ helper for AST traversal ]----- */ function ast_walker() { - function _vardefs(defs) { - return [ this[0], MAP(defs, function(def){ - var a = [ def[0] ]; - if (def.length > 1) - a[1] = walk(def[1]); - return a; - }) ]; - }; - function _block(statements) { - var out = [ this[0] ]; - if (statements != null) - out.push(MAP(statements, walk)); - return out; - }; - var walkers = { - "string": function(str) { - return [ this[0], str ]; - }, - "num": function(num) { - return [ this[0], num ]; - }, - "name": function(name) { - return [ this[0], name ]; - }, - "toplevel": function(statements) { - return [ this[0], MAP(statements, walk) ]; - }, - "block": _block, - "splice": _block, - "var": _vardefs, - "const": _vardefs, - "try": function(t, c, f) { - return [ - this[0], - MAP(t, walk), - c != null ? [ c[0], MAP(c[1], walk) ] : null, - f != null ? MAP(f, walk) : null - ]; - }, - "throw": function(expr) { - return [ this[0], walk(expr) ]; - }, - "new": function(ctor, args) { - return [ this[0], walk(ctor), MAP(args, walk) ]; - }, - "switch": function(expr, body) { - return [ this[0], walk(expr), MAP(body, function(branch){ - return [ branch[0] ? walk(branch[0]) : null, - MAP(branch[1], walk) ]; - }) ]; - }, - "break": function(label) { - return [ this[0], label ]; - }, - "continue": function(label) { - return [ this[0], label ]; - }, - "conditional": function(cond, t, e) { - return [ this[0], walk(cond), walk(t), walk(e) ]; - }, - "assign": function(op, lvalue, rvalue) { - return [ this[0], op, walk(lvalue), walk(rvalue) ]; - }, - "dot": function(expr) { - return [ this[0], walk(expr) ].concat(slice(arguments, 1)); - }, - "call": function(expr, args) { - return [ this[0], walk(expr), MAP(args, walk) ]; - }, - "function": function(name, args, body) { - return [ this[0], name, args.slice(), MAP(body, walk) ]; - }, - "defun": function(name, args, body) { - return [ this[0], name, args.slice(), MAP(body, walk) ]; - }, - "if": function(conditional, t, e) { - return [ this[0], walk(conditional), walk(t), walk(e) ]; - }, - "for": function(init, cond, step, block) { - return [ this[0], walk(init), walk(cond), walk(step), walk(block) ]; - }, - "for-in": function(vvar, key, hash, block) { - return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ]; - }, - "while": function(cond, block) { - return [ this[0], walk(cond), walk(block) ]; - }, - "do": function(cond, block) { - return [ this[0], walk(cond), walk(block) ]; - }, - "return": function(expr) { - return [ this[0], walk(expr) ]; - }, - "binary": function(op, left, right) { - return [ this[0], op, walk(left), walk(right) ]; - }, - "unary-prefix": function(op, expr) { - return [ this[0], op, walk(expr) ]; - }, - "unary-postfix": function(op, expr) { - return [ this[0], op, walk(expr) ]; - }, - "sub": function(expr, subscript) { - return [ this[0], walk(expr), walk(subscript) ]; - }, - "object": function(props) { - return [ this[0], MAP(props, function(p){ - return p.length == 2 - ? [ p[0], walk(p[1]) ] - : [ p[0], walk(p[1]), p[2] ]; // get/set-ter - }) ]; - }, - "regexp": function(rx, mods) { - return [ this[0], rx, mods ]; - }, - "array": function(elements) { - return [ this[0], MAP(elements, walk) ]; - }, - "stat": function(stat) { - return [ this[0], walk(stat) ]; - }, - "seq": function() { - return [ this[0] ].concat(MAP(slice(arguments), walk)); - }, - "label": function(name, block) { - return [ this[0], name, walk(block) ]; - }, - "with": function(expr, block) { - return [ this[0], walk(expr), walk(block) ]; - }, - "atom": function(name) { - return [ this[0], name ]; - } - }; - - var user = {}; - var stack = []; - function walk(ast) { - if (ast == null) - return null; - try { - stack.push(ast); - var type = ast[0]; - var gen = user[type]; - if (gen) { - var ret = gen.apply(ast, ast.slice(1)); - if (ret != null) - return ret; - } - gen = walkers[type]; - return gen.apply(ast, ast.slice(1)); - } finally { - stack.pop(); - } - }; - - function with_walkers(walkers, cont){ - var save = {}, i; - for (i in walkers) if (HOP(walkers, i)) { - save[i] = user[i]; - user[i] = walkers[i]; - } - var ret = cont(); - for (i in save) if (HOP(save, i)) { - if (!save[i]) delete user[i]; - else user[i] = save[i]; - } - return ret; - }; - - return { - walk: walk, - with_walkers: with_walkers, - parent: function() { - return stack[stack.length - 2]; // last one is current node - }, - stack: function() { - return stack; - } - }; + function _vardefs(defs) { + return [ this[0], MAP(defs, function(def){ + var a = [ def[0] ]; + if (def.length > 1) + a[1] = walk(def[1]); + return a; + }) ]; + }; + function _block(statements) { + var out = [ this[0] ]; + if (statements != null) + out.push(MAP(statements, walk)); + return out; + }; + var walkers = { + "string": function(str) { + return [ this[0], str ]; + }, + "num": function(num) { + return [ this[0], num ]; + }, + "name": function(name) { + return [ this[0], name ]; + }, + "toplevel": function(statements) { + return [ this[0], MAP(statements, walk) ]; + }, + "block": _block, + "splice": _block, + "var": _vardefs, + "const": _vardefs, + "try": function(t, c, f) { + return [ + this[0], + MAP(t, walk), + c != null ? [ c[0], MAP(c[1], walk) ] : null, + f != null ? MAP(f, walk) : null + ]; + }, + "throw": function(expr) { + return [ this[0], walk(expr) ]; + }, + "new": function(ctor, args) { + return [ this[0], walk(ctor), MAP(args, walk) ]; + }, + "switch": function(expr, body) { + return [ this[0], walk(expr), MAP(body, function(branch){ + return [ branch[0] ? walk(branch[0]) : null, + MAP(branch[1], walk) ]; + }) ]; + }, + "break": function(label) { + return [ this[0], label ]; + }, + "continue": function(label) { + return [ this[0], label ]; + }, + "conditional": function(cond, t, e) { + return [ this[0], walk(cond), walk(t), walk(e) ]; + }, + "assign": function(op, lvalue, rvalue) { + return [ this[0], op, walk(lvalue), walk(rvalue) ]; + }, + "dot": function(expr) { + return [ this[0], walk(expr) ].concat(slice(arguments, 1)); + }, + "call": function(expr, args) { + return [ this[0], walk(expr), MAP(args, walk) ]; + }, + "function": function(name, args, body) { + return [ this[0], name, args.slice(), MAP(body, walk) ]; + }, + "defun": function(name, args, body) { + return [ this[0], name, args.slice(), MAP(body, walk) ]; + }, + "if": function(conditional, t, e) { + return [ this[0], walk(conditional), walk(t), walk(e) ]; + }, + "for": function(init, cond, step, block) { + return [ this[0], walk(init), walk(cond), walk(step), walk(block) ]; + }, + "for-in": function(vvar, key, hash, block) { + return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ]; + }, + "while": function(cond, block) { + return [ this[0], walk(cond), walk(block) ]; + }, + "do": function(cond, block) { + return [ this[0], walk(cond), walk(block) ]; + }, + "return": function(expr) { + return [ this[0], walk(expr) ]; + }, + "binary": function(op, left, right) { + return [ this[0], op, walk(left), walk(right) ]; + }, + "unary-prefix": function(op, expr) { + return [ this[0], op, walk(expr) ]; + }, + "unary-postfix": function(op, expr) { + return [ this[0], op, walk(expr) ]; + }, + "sub": function(expr, subscript) { + return [ this[0], walk(expr), walk(subscript) ]; + }, + "object": function(props) { + return [ this[0], MAP(props, function(p){ + return p.length == 2 + ? [ p[0], walk(p[1]) ] + : [ p[0], walk(p[1]), p[2] ]; // get/set-ter + }) ]; + }, + "regexp": function(rx, mods) { + return [ this[0], rx, mods ]; + }, + "array": function(elements) { + return [ this[0], MAP(elements, walk) ]; + }, + "stat": function(stat) { + return [ this[0], walk(stat) ]; + }, + "seq": function() { + return [ this[0] ].concat(MAP(slice(arguments), walk)); + }, + "label": function(name, block) { + return [ this[0], name, walk(block) ]; + }, + "with": function(expr, block) { + return [ this[0], walk(expr), walk(block) ]; + }, + "atom": function(name) { + return [ this[0], name ]; + } + }; + + var user = {}; + var stack = []; + function walk(ast) { + if (ast == null) + return null; + try { + stack.push(ast); + var type = ast[0]; + var gen = user[type]; + if (gen) { + var ret = gen.apply(ast, ast.slice(1)); + if (ret != null) + return ret; + } + gen = walkers[type]; + return gen.apply(ast, ast.slice(1)); + } finally { + stack.pop(); + } + }; + + function with_walkers(walkers, cont){ + var save = {}, i; + for (i in walkers) if (HOP(walkers, i)) { + save[i] = user[i]; + user[i] = walkers[i]; + } + var ret = cont(); + for (i in save) if (HOP(save, i)) { + if (!save[i]) delete user[i]; + else user[i] = save[i]; + } + return ret; + }; + + return { + walk: walk, + with_walkers: with_walkers, + parent: function() { + return stack[stack.length - 2]; // last one is current node + }, + stack: function() { + return stack; + } + }; }; -/* -----[ Scope and mangling ]----- */ - -function Scope(parent) { - this.names = {}; // names defined in this scope - this.mangled = {}; // mangled names (orig.name => mangled) - this.rev_mangled = {}; // reverse lookup (mangled => orig.name) - this.cname = -1; // current mangled name - this.refs = {}; // names referenced from this scope - this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes - this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes - this.parent = parent; // parent scope - this.children = []; // sub-scopes - if (parent) { - this.level = parent.level + 1; - parent.children.push(this); - } else { - this.level = 0; - } -}; - -var base54 = (function(){ - var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_"; - return function(num) { - var ret = ""; - do { - ret = DIGITS.charAt(num % 54) + ret; - num = Math.floor(num / 54); - } while (num > 0); - return ret; - }; -})(); - -Scope.prototype = { - has: function(name) { - for (var s = this; s; s = s.parent) - if (HOP(s.names, name)) - return s; - }, - has_mangled: function(mname) { - for (var s = this; s; s = s.parent) - if (HOP(s.rev_mangled, mname)) - return s; - }, - toJSON: function() { - return { - names: this.names, - uses_eval: this.uses_eval, - uses_with: this.uses_with - }; - }, - - next_mangled: function() { - // we must be careful that the new mangled name: - // - // 1. doesn't shadow a mangled name from a parent - // scope, unless we don't reference the original - // name from this scope OR from any sub-scopes! - // This will get slow. - // - // 2. doesn't shadow an original name from a parent - // scope, in the event that the name is not mangled - // in the parent scope and we reference that name - // here OR IN ANY SUBSCOPES! - // - // 3. doesn't shadow a name that is referenced but not - // defined (possibly global defined elsewhere). - for (;;) { - var m = base54(++this.cname), prior; - - // case 1. - prior = this.has_mangled(m); - if (prior && this.refs[prior.rev_mangled[m]] === prior) - continue; - - // case 2. - prior = this.has(m); - if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m)) - continue; - - // case 3. - if (HOP(this.refs, m) && this.refs[m] == null) - continue; - - // I got "do" once. :-/ - if (!is_identifier(m)) - 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 - return s.set_mangle(name, s.next_mangled()); - }, - 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) { - - var current_scope = null; - var w = ast_walker(), walk = w.walk; - var having_eval = []; - - function with_new_scope(cont) { - current_scope = new Scope(current_scope); - var ret = current_scope.body = cont(); - ret.scope = current_scope; - current_scope = current_scope.parent; - return ret; - }; - - 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, "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": _vardefs("var"), - "const": _vardefs("const"), - "try": function(t, c, f) { - if (c != null) return [ - this[0], - MAP(t, walk), - [ define(c[0], "catch"), MAP(c[1], walk) ], - f != null ? MAP(f, walk) : null - ]; - }, - "name": function(name) { - if (name == "eval") - having_eval.push(current_scope); - reference(name); - } - }, function(){ - return walk(ast); - }); - - // the reason why we need an additional pass here is - // that names can be used prior to their definition. - - // scopes where eval was detected and their parents - // are marked with uses_eval, unless they define the - // "eval" name. - MAP(having_eval, function(scope){ - if (!scope.has("eval")) while (scope) { - scope.uses_eval = true; - scope = scope.parent; - } - }); - - // for referenced names it might be useful to know - // their origin scope. current_scope here is the - // toplevel one. - function fixrefs(scope, i) { - // do children first; order shouldn't matter - for (i = scope.children.length; --i >= 0;) - fixrefs(scope.children[i]); - for (i in scope.refs) if (HOP(scope.refs, i)) { - // find origin scope and propagate the reference to origin - for (var origin = scope.has(i), s = scope; s; s = s.parent) { - s.refs[i] = origin; - if (s === origin) break; - } - } - }; - fixrefs(current_scope); - - return ret; - }); - -}; - -/* -----[ mangle names ]----- */ - -function ast_mangle(ast, options) { - var w = ast_walker(), walk = w.walk, scope; - options = options || {}; - - function get_mangled(name, newMangle) { - if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel - if (options.except && member(name, options.except)) - return name; - return scope.get_mangled(name, newMangle); - }; - - function get_define(name) { - if (options.defines) { - // we always lookup a defined symbol for the current scope FIRST, so declared - // vars trump a DEFINE symbol, but if no such var is found, then match a DEFINE value - if (!scope.has(name)) { - if (HOP(options.defines, name)) { - return options.defines[name]; - } - } - return null; - } - }; - - function _lambda(name, args, body) { - 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(){ - 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, 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; - scope = _scope; - return ret; - }; - - function _vardefs(defs) { - return [ this[0], MAP(defs, function(d){ - return [ get_mangled(d[0]), walk(d[1]) ]; - }) ]; - }; - - return w.with_walkers({ - "function": _lambda, - "defun": function() { - // move function declarations to the top when - // they are not in some block. - var ast = _lambda.apply(this, arguments); - switch (w.parent()[0]) { - case "toplevel": - case "function": - 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) ]; - }, - "try": function(t, c, f) { - return [ this[0], - MAP(t, walk), - c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null, - f != null ? MAP(f, walk) : null ]; - }, - "toplevel": function(body) { - var self = this; - return with_scope(self.scope, function(){ - return [ self[0], MAP(body, walk) ]; - }); - } - }, function() { - return walk(ast_add_scope(ast)); - }); -}; - -/* -----[ - - compress foo["bar"] into foo.bar, - - remove block brackets {} where possible - - join consecutive var declarations - - various optimizations for IFs: - - if (cond) foo(); else bar(); ==> cond?foo():bar(); - - if (cond) foo(); ==> cond&&foo(); - - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); // also for throw - - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()} - ]----- */ - var warn = function(){}; function best_of(ast1, ast2) { - return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1; + return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1; }; function last_stat(b) { - if (b[0] == "block" && b[1] && b[1].length > 0) - return b[1][b[1].length - 1]; - return b; + if (b[0] == "block" && b[1] && b[1].length > 0) + return b[1][b[1].length - 1]; + return b; } function aborts(t) { - if (t) switch (last_stat(t)[0]) { - case "return": - case "break": - case "continue": - case "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" - && member(expr[1], [ "!", "delete" ])) || - - (expr[0] == "binary" - && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) || + return ((expr[0] == "unary-prefix" + && member(expr[1], [ "!", "delete" ])) || - (expr[0] == "binary" - && member(expr[1], [ "&&", "||" ]) - && boolean_expr(expr[2]) - && boolean_expr(expr[3])) || + (expr[0] == "binary" + && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) || - (expr[0] == "conditional" - && boolean_expr(expr[2]) - && boolean_expr(expr[3])) || + (expr[0] == "binary" + && member(expr[1], [ "&&", "||" ]) + && boolean_expr(expr[2]) + && boolean_expr(expr[3])) || - (expr[0] == "assign" - && expr[1] === true - && boolean_expr(expr[3])) || + (expr[0] == "conditional" + && boolean_expr(expr[2]) + && boolean_expr(expr[3])) || - (expr[0] == "seq" - && boolean_expr(expr[expr.length - 1])) - ); -}; + (expr[0] == "assign" + && expr[1] === true + && boolean_expr(expr[3])) || -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); + (expr[0] == "seq" + && boolean_expr(expr[expr.length - 1]))); }; function empty(b) { - return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); + return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); }; function is_string(node) { - return (node[0] == "string" || - node[0] == "unary-prefix" && node[1] == "typeof" || - node[0] == "binary" && node[1] == "+" && - (is_string(node[2]) || is_string(node[3]))); -}; - -var when_constant = (function(){ - - var $NOT_CONSTANT = {}; - - // this can only evaluate constant expressions. If it finds anything - // not constant, it throws $NOT_CONSTANT. - function evaluate(expr) { - switch (expr[0]) { - case "string": - case "num": - return expr[1]; - case "name": - case "atom": - switch (expr[1]) { - case "true": return true; - case "false": return false; - } - break; - case "unary-prefix": - switch (expr[1]) { - case "!": return !evaluate(expr[2]); - case "typeof": return typeof evaluate(expr[2]); - case "~": return ~evaluate(expr[2]); - case "-": return -evaluate(expr[2]); - case "+": return +evaluate(expr[2]); - } - break; - case "binary": - var left = expr[2], right = expr[3]; - switch (expr[1]) { - 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); - 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); - 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 "in" : return evaluate(left) in evaluate(right); - case "instanceof" : return evaluate(left) instanceof evaluate(right); - } - } - throw $NOT_CONSTANT; - }; - - return function(expr, yes, no) { - try { - var val = evaluate(expr), ast; - switch (typeof val) { - 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)); - } - return yes.call(expr, ast, val); - } catch(ex) { - if (ex === $NOT_CONSTANT) { - if (expr[0] == "binary" - && (expr[1] == "===" || expr[1] == "!==") - && ((is_string(expr[2]) && is_string(expr[3])) - || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) { - expr[1] = expr[1].substr(0, 2); - } - else if (no && expr[0] == "binary" - && (expr[1] == "||" || expr[1] == "&&")) { - // the whole expression is not constant but the lval may be... - try { - var lval = evaluate(expr[2]); - expr = ((expr[1] == "&&" && (lval ? expr[3] : lval)) || - (expr[1] == "||" && (lval ? lval : expr[3])) || - expr); - } catch(ex2) { - // IGNORE... lval is not constant - } - } - return no ? no.call(expr, expr) : null; - } - else throw ex; - } - }; - -})(); - -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, |