├── lib └── ant.jar ├── src └── uglify │ ├── ECMAScriptEngineFactory.java │ ├── UglifyJS.java │ ├── UglifyOptions.java │ ├── javascript │ ├── uglifyjs.js │ ├── adapter │ │ ├── Array.js │ │ ├── sys.js │ │ └── JSON.js │ ├── parse-js.js │ └── process.js │ └── ant │ └── UglifyTask.java └── README.md /lib/ant.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanyan/UglifyJS-java/HEAD/lib/ant.jar -------------------------------------------------------------------------------- /src/uglify/ECMAScriptEngineFactory.java: -------------------------------------------------------------------------------- 1 | package uglify; 2 | 3 | import javax.script.ScriptEngine; 4 | import javax.script.ScriptEngineManager; 5 | 6 | public class ECMAScriptEngineFactory { 7 | 8 | public static ScriptEngine getECMAScriptEngine(){ 9 | ScriptEngine engine = null; 10 | ScriptEngineManager manager = new ScriptEngineManager(); 11 | 12 | engine = manager.getEngineByExtension("js"); 13 | 14 | if(engine == null){ 15 | throw new RuntimeException("the java version do not install ECMAScipt engine, must be above java 1.6"); 16 | } 17 | 18 | return engine; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/uglify/UglifyJS.java: -------------------------------------------------------------------------------- 1 | package uglify; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.io.InputStreamReader; 6 | import java.io.Reader; 7 | import java.net.URL; 8 | 9 | import javax.script.ScriptEngine; 10 | import javax.script.ScriptException; 11 | 12 | public class UglifyJS { 13 | 14 | public static void main(String[] args) { 15 | 16 | new UglifyJS().exec(args); 17 | } 18 | 19 | public Reader getResourceReader(String url) { 20 | 21 | Reader reader = null; 22 | 23 | try { 24 | //TODO jar包内资源加载有通常三种方式,但测试中发现只有第一种方式才找到了资源 25 | if(reader == null) { 26 | reader = new InputStreamReader(getClass().getClassLoader().getResourceAsStream(url)); 27 | } 28 | 29 | /* 30 | if (reader == null) { 31 | reader = new InputStreamReader(getClass().getResourceAsStream("../"+url)); 32 | } 33 | 34 | if (reader == null) { 35 | reader = new InputStreamReader(getClass().getResourceAsStream("/"+url)); 36 | } 37 | */ 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | 42 | return reader; 43 | } 44 | 45 | public void exec(String[] args) { 46 | 47 | ScriptEngine engine = ECMAScriptEngineFactory.getECMAScriptEngine(); 48 | engine.put("uglify_args", args); 49 | engine.put("uglify_no_output", false); 50 | run(engine); 51 | 52 | try { 53 | engine.eval("uglify();"); 54 | } catch (ScriptException e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | 59 | 60 | private void run(ScriptEngine engine) { 61 | try { 62 | 63 | Reader parsejsReader = getResourceReader("uglify/javascript/parse-js.js"); 64 | Reader processjsReader = getResourceReader("uglify/javascript/process.js"); 65 | Reader sysjsReader = getResourceReader("uglify/javascript/adapter/sys.js"); 66 | Reader jsonjsReader = getResourceReader("uglify/javascript/adapter/JSON.js"); 67 | Reader arrayjsReader = getResourceReader("uglify/javascript/adapter/Array.js"); 68 | Reader uglifyjsReader = getResourceReader("uglify/javascript/uglifyjs.js"); 69 | 70 | engine.eval(arrayjsReader); 71 | engine.eval(sysjsReader); 72 | engine.eval(parsejsReader); 73 | engine.eval(processjsReader); 74 | engine.eval(jsonjsReader); 75 | engine.eval(uglifyjsReader); 76 | 77 | } catch (ScriptException e) { 78 | e.printStackTrace(); 79 | } 80 | 81 | } 82 | 83 | public String uglify(String[] args){ 84 | 85 | ScriptEngine engine = ECMAScriptEngineFactory.getECMAScriptEngine(); 86 | engine.put("uglify_args", args); 87 | engine.put("uglify_no_output", true); 88 | run(engine); 89 | 90 | String result = null; 91 | 92 | try { 93 | result = (String)engine.eval("uglify();"); 94 | } catch (ScriptException e) { 95 | e.printStackTrace(); 96 | } 97 | 98 | return result; 99 | } 100 | 101 | 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###UglifyJS-java - JavaScript parser / mangler / compressor / beautifier library for for Java 2 | 3 | ###About: 4 | The original [UglifyJS](https://github.com/mishoo/UglifyJS) based on [node.js](https://github.com/joyent/node), but it is hard to do automated integration with ant or maven, so i moved UglifyJS from node.js to the java platform. 5 | 6 | ###Usage: 7 | java -jar uglifyjs-java.jar [ options... ] [ filename ] 8 | 9 | filename should be the last argument and should name the file from which to read the JavaScript code. 10 | 11 | ####Supported options: 12 | 13 | * -b or --beautify — output indented code; when passed, additional options control the beautifier: 14 | * -i N or --indent N — indentation level (number of spaces) 15 | * -q or --quote-keys — quote keys in literal objects (by default, only keys that cannot be identifier names will be quotes). 16 | 17 | * --ascii — pass this argument to encode non-ASCII characters as *\uXXXX* sequences. By default UglifyJS won’t bother to do it and will output Unicode characters instead. (the output is always encoded in UTF8, but if you pass this option you’ll only get ASCII). 18 | 19 | * -nm or --no-mangle — don’t mangle variable names 20 | 21 | * -ns or --no-squeeze — don’t call ast_squeeze() (which does various optimizations that result in smaller, less readable code). 22 | 23 | * -mt or --mangle-toplevel — mangle names in the toplevel scope too (by default we don’t do this). 24 | 25 | * --no-seqs — when ast_squeeze() is called (thus, unless you pass --no-squeeze) it will reduce consecutive statements in blocks into a sequence. For example, *a = 10; b = 20; foo();* will be written as *a=10,b=20,foo();*. In various occasions, this allows us to discard the block brackets (since the block becomes a single statement). This is ON by default because it seems safe and saves a few hundred bytes on some libs that I tested it on, but pass --no-seqs to disable it. 26 | 27 | * --no-dead-code — by default, UglifyJS will remove code that is obviously unreachable (code that follows a return, throw, break or continue statement and is not a function/variable declaration). Pass this option to disable this optimization. 28 | 29 | * -nc or --no-copyright — by default, uglifyjs will keep the initial comment tokens in the generated code (assumed to be copyright information etc.). If you pass this it will discard it. 30 | 31 | * -o filename or --output filename — put the result in filename. If this isn’t given, the result goes to conversion output **filename.min.js**. 32 | 33 | * --overwrite — if the code is read from a file and you pass --overwrite then the output will be written in the same file. 34 | 35 | * --ast — pass this if you want to get the Abstract Syntax Tree instead of JavaScript as output. Useful for debugging or learning more about the internals. 36 | 37 | * -v or --verbose — output some notes on STDERR (for now just how long each operation takes). 38 | 39 | * --extra — enable additional optimizations that have not yet been extensively tested. These might, or might not, break your code. If you find a bug using this option, please report a test case. 40 | 41 | * --unsafe — enable other additional optimizations that are known to be unsafe in some contrived situations, but could still be generally useful. For now only this: 42 | >foo.toString() ==> foo+”” 43 | 44 | * --max-line-len (default 32K characters) — add a newline after around 32K characters. I’ve seen both FF and Chrome croak when all the code was on a single line of around 670K. Pass --max-line-len 0 to disable this safety feature. 45 | 46 | * --reserved-names — some libraries rely on certain names to be used, so this option allow you to exclude such names from the mangler. For example, to keep names require and $super intact you’d specify --reserved-names "require,$super". 47 | 48 | ###Links: 49 | UglifyJS http://github.com/mishoo/UglifyJS 50 | 51 | ###Note: 52 | UglifyJS-java must be running on java 1.6 version or above 53 | -------------------------------------------------------------------------------- /src/uglify/UglifyOptions.java: -------------------------------------------------------------------------------- 1 | package uglify; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class UglifyOptions { 6 | //-v or --verbose — output some notes on STDERR (for now just how long each operation takes). 7 | public boolean verbose; 8 | 9 | //-b or --beautify — output indented code; when passed, additional options control the beautifier: 10 | public boolean beautify; 11 | 12 | //-i N or --indent N — indentation level (number of spaces) 13 | public Integer indent; 14 | 15 | // -q or --quote-keys — quote keys in literal objects (by default, only keys that cannot be identifier names will be quotes). 16 | public boolean quoteKeys; 17 | 18 | public boolean ascii; 19 | 20 | //-nm or --no-mangle — don't mangle variable names 21 | public boolean noMangle; 22 | 23 | //-ns or --no-squeeze — don't call ast_squeeze() (which does various optimizations that result in smaller, less readable code). 24 | public boolean noSqueeze; 25 | 26 | //-mt or --mangle-toplevel — mangle names in the toplevel scope too (by default we don't do this). 27 | public boolean mangleToplevel; 28 | 29 | //--no-seqs — when ast_squeeze() is called (thus, unless you pass --no-squeeze) it will reduce consecutive statements in blocks into a sequence. For example, "a = 10; b = 20; foo();" will be written as "a=10,b=20,foo();". In various occasions, this allows us to discard the block brackets (since the block becomes a single statement). This is ON by default because it seems safe and saves a few hundred bytes on some libs that I tested it on, but pass --no-seqs to disable it. 30 | public boolean noSeqs; 31 | 32 | //--no-dead-code — by default, UglifyJS will remove code that is obviously unreachable (code that follows a return, throw, break or continue statement and is not a function/variable declaration). Pass this option to disable this optimization. 33 | public boolean noDeadCode; 34 | 35 | //-nc or --no-copyright — by default, uglifyjs will keep the initial comment tokens in the generated code (assumed to be copyright information etc.). If you pass this it will discard it. 36 | public boolean noCopyright; 37 | 38 | //-o filename or --output filename — put the result in filename. If this isn't given, the result goes to standard output (or see next one). 39 | public String output; 40 | 41 | //--overwrite — if the code is read from a file (not from STDIN) and you pass --overwrite then the output will be written in the same file. 42 | public boolean overwrite; 43 | 44 | //--ast — pass this if you want to get the Abstract Syntax Tree instead of JavaScript as output. Useful for debugging or learning more about the internals. 45 | public boolean ast; 46 | 47 | //--extra — enable additional optimizations that have not yet been extensively tested. These might, or might not, break your code. If you find a bug using this option, please report a test case. 48 | public boolean extra; 49 | 50 | //--unsafe — enable other additional optimizations that are known to be unsafe in some contrived situations, but could still be generally useful. For now only this: 51 | //foo.toString() ==> foo+"" 52 | public boolean unsafe; 53 | //--max-line-len (default 32K characters) — add a newline after around 32K characters. I've seen both FF and Chrome croak when all the code was on a single line of around 670K. Pass –max-line-len 0 to disable this safety feature. 54 | public Integer maxLineLen; 55 | 56 | //--reserved-names — some libraries rely on certain names to be used, as pointed out in issue #92 and #81, so this option allow you to exclude such names from the mangler. For example, to keep names require and $super intact you'd specify –reserved-names "require,$super". 57 | public String reservedNames; 58 | 59 | 60 | 61 | public ArrayList toArgList(){ 62 | 63 | ArrayList options= new ArrayList(); 64 | 65 | if(verbose){ 66 | options.add("--verbose"); 67 | } 68 | if(beautify){ 69 | options.add("--beautify"); 70 | } 71 | if(quoteKeys){ 72 | options.add("--quote-keys"); 73 | } 74 | if(noMangle){ 75 | options.add("--no-mangle"); 76 | } 77 | if(noSqueeze ){ 78 | options.add("--no-squeeze"); 79 | } 80 | if(mangleToplevel){ 81 | options.add("--mangle-toplevel"); 82 | } 83 | if(noSeqs){ 84 | options.add("--no-seqs"); 85 | } 86 | if(noDeadCode){ 87 | options.add("--no-dead-code"); 88 | } 89 | if(noCopyright){ 90 | options.add("--no-copyright"); 91 | } 92 | if(overwrite){ 93 | options.add("--overwrite"); 94 | } 95 | if(ast){ 96 | options.add("--ast"); 97 | } 98 | if(extra){ 99 | options.add("--extra"); 100 | } 101 | if(unsafe){ 102 | options.add("--unsafe"); 103 | } 104 | if(ascii){ 105 | options.add("--ascii"); 106 | } 107 | 108 | if(maxLineLen != null){ 109 | options.add("--max-line-len"); 110 | options.add(""+maxLineLen); 111 | } 112 | 113 | if(indent != null){ 114 | options.add("--indent"); 115 | options.add(""+indent); 116 | 117 | } 118 | if(reservedNames != null){ 119 | options.add("--reserved-names"); 120 | options.add(reservedNames); 121 | } 122 | 123 | if(output != null){ 124 | options.add("--output"); 125 | options.add(output); 126 | } 127 | 128 | return options; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/uglify/javascript/uglifyjs.js: -------------------------------------------------------------------------------- 1 | var options = { 2 | ast: false, 3 | mangle: true, 4 | mangle_toplevel: false, 5 | squeeze: true, 6 | make_seqs: true, 7 | dead_code: true, 8 | verbose: false, 9 | show_copyright: true, 10 | out_same_file: false, 11 | max_line_length: 32 * 1024, 12 | unsafe: false, 13 | reserved_names: null, 14 | codegen_options: { 15 | ascii_only: false, 16 | beautify: false, 17 | indent_level: 4, 18 | indent_start: 0, 19 | quote_keys: false, 20 | space_colon: false 21 | }, 22 | output: true // stdout 23 | }; 24 | 25 | var args = jsp.slice(uglify_args, 0); 26 | 27 | //println("args: "+args); 28 | 29 | var filename; 30 | 31 | out: while (args.length > 0) { 32 | var v = ""+args.shift(); //convert object to String 33 | switch (v) { 34 | case "-b": 35 | case "--beautify": 36 | options.codegen_options.beautify = true; 37 | break; 38 | case "-i": 39 | case "--indent": 40 | options.codegen_options.indent_level = args.shift(); 41 | break; 42 | case "-q": 43 | case "--quote-keys": 44 | options.codegen_options.quote_keys = true; 45 | break; 46 | case "-mt": 47 | case "--mangle-toplevel": 48 | options.mangle_toplevel = true; 49 | break; 50 | case "--no-mangle": 51 | case "-nm": 52 | options.mangle = false; 53 | break; 54 | case "--no-squeeze": 55 | case "-ns": 56 | options.squeeze = false; 57 | break; 58 | case "--no-seqs": 59 | options.make_seqs = false; 60 | break; 61 | case "--no-dead-code": 62 | options.dead_code = false; 63 | break; 64 | case "--no-copyright": 65 | case "-nc": 66 | options.show_copyright = false; 67 | break; 68 | case "-o": 69 | case "--output": 70 | options.output = args.shift(); 71 | break; 72 | case "--overwrite": 73 | options.out_same_file = true; 74 | break; 75 | case "-v": 76 | case "--verbose": 77 | options.verbose = true; 78 | break; 79 | case "--ast": 80 | options.ast = true; 81 | break; 82 | case "--unsafe": 83 | options.unsafe = true; 84 | break; 85 | case "--max-line-len": 86 | options.max_line_length = parseInt(args.shift(), 10); 87 | break; 88 | case "--reserved-names": 89 | options.reserved_names = args.shift().split(","); 90 | break; 91 | case "--ascii": 92 | options.codegen_options.ascii_only = true; 93 | break; 94 | default: 95 | filename = v; 96 | break out; 97 | } 98 | } 99 | 100 | if (options.verbose) { 101 | pro.set_logger(function(msg){ 102 | sys.debug(msg); 103 | }); 104 | } 105 | 106 | jsp.set_logger(function(msg){ 107 | sys.debug(msg); 108 | }); 109 | 110 | 111 | function output(text) { 112 | 113 | var out; 114 | if (options.out_same_file && filename) 115 | options.output = filename; 116 | 117 | if (options.output === true) { //add a default output rules: filename.min.js 118 | var parts = filename.split("."); 119 | parts.pop(); 120 | parts.push("min"); 121 | parts.push("js"); 122 | var output = parts.join("."); 123 | out = new java.io.FileWriter(output); 124 | 125 | sys.debug("Output file: "+ output ); 126 | } 127 | else if (options.output === "stdout") { 128 | out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(java.lang.System.out)); 129 | } else { 130 | out = new java.io.FileWriter(options.output); 131 | 132 | sys.debug("Output file: "+ options.output ); 133 | } 134 | 135 | out.write(text); 136 | out.flush(); 137 | 138 | 139 | if (options.output !== true) { 140 | out.close(); 141 | } 142 | }; 143 | 144 | 145 | // --------- main ends here. 146 | 147 | 148 | function show_copyright(comments) { 149 | var ret = ""; 150 | for (var i = 0; i < comments.length; ++i) { 151 | var c = comments[i]; 152 | if (c.type == "comment1") { 153 | ret += "//" + c.value + "\n"; 154 | } else { 155 | ret += "/*" + c.value + "*/"; 156 | } 157 | } 158 | return ret; 159 | }; 160 | 161 | 162 | function squeeze_it(code) { 163 | 164 | var result = ""; 165 | if (options.show_copyright) { 166 | var tok = jsp.tokenizer(code), c; 167 | c = tok(); 168 | result += show_copyright(c.comments_before); 169 | } 170 | 171 | // try { 172 | var ast = time_it("parse", function(){ return jsp.parse(code); }); 173 | if (options.mangle) ast = time_it("mangle", function(){ 174 | return pro.ast_mangle(ast, { 175 | toplevel: options.mangle_toplevel, 176 | except: options.reserved_names 177 | }); 178 | }); 179 | if (options.squeeze) ast = time_it("squeeze", function(){ 180 | 181 | ast = pro.ast_squeeze(ast, { 182 | make_seqs : options.make_seqs, 183 | dead_code : options.dead_code, 184 | keep_comps : !options.unsafe 185 | }); 186 | 187 | if (options.unsafe) 188 | ast = pro.ast_squeeze_more(ast); 189 | 190 | return ast; 191 | }); 192 | if (options.ast) 193 | return sys.inspect(ast, null, null); 194 | result += time_it("generate", function(){ return pro.gen_code(ast, options.codegen_options) }); 195 | if (!options.codegen_options.beautify && options.max_line_length) { 196 | result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) }); 197 | } 198 | 199 | 200 | return result; 201 | // } catch(ex) { 202 | //sys.debug(ex.stack); 203 | // sys.debug(sys.inspect(ex)); 204 | // } 205 | }; 206 | 207 | function time_it(name, cont) { 208 | if (!options.verbose) 209 | return cont(); 210 | var t1 = new Date().getTime(); 211 | try { return cont(); } 212 | finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); } 213 | }; 214 | 215 | 216 | function uglify(){ 217 | 218 | if (filename) { 219 | 220 | var input=new java.io.BufferedReader(new java.io.FileReader(filename)); 221 | var text="",line=""; 222 | while((line = input.readLine())!= null) 223 | text += line+'\n'; 224 | input.close(); 225 | 226 | var result = squeeze_it(text); 227 | 228 | if(uglify_no_output == false ){ 229 | output(result); 230 | } 231 | return result; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/uglify/javascript/adapter/Array.js: -------------------------------------------------------------------------------- 1 | var $AP = Array.prototype; 2 | 3 | /*Array prototype extentions start */ 4 | 5 | /** 6 | * remove the element from the first argument to the second argument, 7 | * if the second argument not gived ,it will remove the element 8 | * which the fisrt argument index. 9 | * @param {Object} from 10 | * @param {Object} to {optional} 11 | */ 12 | $AP.remove = function(from, to){ 13 | var rest = this.slice((to || from) + 1 || this.length); 14 | this.length = from < 0 ? this.length + from: from; 15 | return this.push.apply(this, rest); 16 | }; 17 | 18 | 19 | /** 20 | * ECMA-262-5 15.4.4.14 21 | * Returns the first (least) index of an element within the array 22 | * equal to the specified value, or -1 if none is found. 23 | * 24 | * @param {Mixed} searchElement 25 | * @param {number} fromIndex {optional} 26 | * @return {number} 27 | * @example ['a','b','c'].indexOf('b') === 1; 28 | */ 29 | $AP.indexOf || 30 | ($AP.indexOf = function(searchElement, fromIndex){ 31 | // Pull out the length so that modifications to the length in the 32 | // loop will not affect the looping. 33 | var len = this.length; 34 | 35 | if (len == 0){ 36 | return - 1; 37 | } 38 | 39 | if (fromIndex === undefined){ 40 | fromIndex = 0; 41 | } else{ 42 | // If fromIndex is negative, fromIndex from the end of the array. 43 | if (fromIndex < 0) 44 | fromIndex = len + fromIndex; 45 | // If fromIndex is still negative, search the entire array. 46 | if (fromIndex < 0) 47 | fromIndex = 0; 48 | } 49 | if (searchElement !== undefined){ 50 | for (var i = fromIndex; i < len; i++){ 51 | if (this[i] === searchElement) 52 | return i; 53 | } 54 | return - 1; 55 | } 56 | // Lookup through the array. 57 | for (var i = fromIndex; i < len; i++){ 58 | if (this[i] === undefined && i in this){ 59 | return i; 60 | } 61 | } 62 | return - 1; 63 | }); 64 | 65 | 66 | 67 | /** 68 | * ECMA-262-5 15.4.4.15 69 | * Returns the last (greatest) index of an element within the array 70 | * equal to the specified value, or -1 if none is found. 71 | * 72 | * @param {Mixed} searchElement 73 | * @param {number} fromIndex {optional} 74 | * @return {number} 75 | * @example ['a','a','c'].lastIndexOf('a') === 1; 76 | */ 77 | $AP.lastIndexOf || 78 | ($AP.lastIndexOf = function(searchElement, fromIndex){ 79 | // Pull out the length so that modifications to the length in the 80 | // loop will not affect the looping. 81 | var len = this.length; 82 | 83 | if (len == 0){ 84 | return - 1; 85 | } 86 | 87 | if (arguments.length < 2){ 88 | fromIndex = len - 1; 89 | } 90 | else{ 91 | // If index is negative, index from end of the array. 92 | if (fromIndex < 0) 93 | fromIndex = len + fromIndex; 94 | // If fromIndex is still negative, do not search the array. 95 | if (fromIndex < 0) 96 | fromIndex = -1; 97 | else 98 | if (fromIndex >= len) 99 | fromIndex = len - 1; 100 | } 101 | // Lookup through the array. 102 | if (searchElement !== undefined){ 103 | for (var i = fromIndex; i >= 0; i--){ 104 | if (this[i] === searchElement) 105 | return i; 106 | } 107 | return - 1; 108 | } 109 | for (var i = fromIndex; i >= 0; i--){ 110 | if (this[i] === undefined && i in this){ 111 | return i; 112 | } 113 | } 114 | return - 1; 115 | 116 | 117 | }); 118 | 119 | 120 | /** 121 | * ECMA-262-5 15.4.4.16 122 | * Returns true if every element in this array 123 | * satisfies the provided testing function. 124 | * @param {Object} callbackfn 125 | * @param {Object} thisArg {optional} 126 | */ 127 | $AP.every || 128 | ($AP.every = function(callbackfn, thisArg){ 129 | 130 | // Pull out the length so that modifications to the length in the 131 | // loop will not affect the looping. 132 | var len = this.length; 133 | 134 | for (var i = 0; i < len; ++i){ 135 | var current = this[i]; 136 | if (current !== undefined || i in this){ 137 | if (!callbackfn.call(thisArg, current, i, this)) 138 | return false; 139 | } 140 | } 141 | 142 | return true; 143 | }); 144 | 145 | 146 | /** 147 | * ECMA-262-5 15.4.4.17 148 | * Returns true if at least one element in this array 149 | * satisfies the provided testing function. 150 | * @param {Object} callbackfn 151 | * @param {Object} thisArg {optional} 152 | */ 153 | $AP.some || 154 | ($AP.some = function(callbackfn, thisArg){ 155 | 156 | // Pull out the length so that modifications to the length in the 157 | // loop will not affect the looping. 158 | var len = this.length; 159 | for (var i = 0; i < len; ++i){ 160 | var current = this[i]; 161 | if (current !== undefined || i in this){ 162 | if (callbackfn.call(thisArg, current, i, this)) 163 | return true; 164 | } 165 | } 166 | 167 | return false; 168 | }); 169 | 170 | 171 | /** 172 | * ECMA-262-5 15.4.4.18 173 | * Calls a function for each element in the array. 174 | * @param {Object} callbackfn 175 | * @param {Object} thisArg {optional} 176 | */ 177 | $AP.forEach || 178 | ($AP.forEach = function(callbackfn, thisArg){ 179 | 180 | // Pull out the length so that modifications to the length in the 181 | // loop will not affect the looping. 182 | var len = this.length; 183 | for (var i = 0; i < len; ++i){ 184 | var current = this[i]; 185 | if (current !== undefined || i in this){ 186 | callbackfn.call(thisArg, current, i, this); 187 | } 188 | } 189 | }); 190 | 191 | /** 192 | * ECMA-262-5 15.4.4.19 193 | * Creates a new array with the results of calling 194 | * a provided function on every element in this array. 195 | * @param {Object} callbackfn 196 | * @param {Object} thisArg 197 | * @return {Array} 198 | */ 199 | $AP.map || 200 | ($AP.map = function(callbackfn, thisArg){ 201 | 202 | // Pull out the length so that modifications to the length in the 203 | // loop will not affect the looping. 204 | var len = this.length, 205 | result = new Array(len); 206 | for (var i = 0; i < len; ++i){ 207 | var current = this[i]; 208 | if (current !== undefined || i in this){ 209 | result[i] = callbackfn.call(thisArg, current, i, this); 210 | } 211 | } 212 | return result; 213 | }); 214 | 215 | 216 | /** 217 | * ECMA-262-5 15.4.4.20 218 | * Creates a new array with all of the elements of this array 219 | * for which the provided filtering function returns true. 220 | * @param {Object} callbackfn 221 | * @param {Object} thisArg 222 | * @return {Array} 223 | */ 224 | $AP.filter || 225 | ($AP.filter = function(callbackfn, thisArg){ 226 | 227 | // Pull out the length so that modifications to the length in the 228 | // loop will not affect the looping. 229 | var len = this.length, 230 | result = []; 231 | for (var i = 0; i < len; ++i){ 232 | var current = this[i]; 233 | if (current !== undefined || i in this){ 234 | callbackfn.call(thisArg, current, i, this) && result.push(this[i]); 235 | } 236 | } 237 | 238 | return result; 239 | }); 240 | 241 | 242 | /** 243 | * ECMA-262-5 15.4.4.21 244 | * Apply a function simultaneously against two values of 245 | * the array (from left-to-right) as to reduce it to a single value. 246 | * @param {Object} callbackfn 247 | * @param {Object} initialValue {optional} 248 | */ 249 | $AP.reduce || 250 | ($AP.reduce = function(callbackfn, current){ 251 | 252 | // Pull out the length so that modifications to the length in the 253 | // loop will not affect the looping. 254 | var len = this.length, 255 | i = 0; 256 | 257 | find_initial: 258 | if (arguments.length < 2){ 259 | for (; i < len; i++){ 260 | current = this[i]; 261 | if (current !== undefined || i in this){ 262 | i++; 263 | break find_initial; 264 | } 265 | } 266 | println("reduce of empty array with no initial value"); 267 | } 268 | 269 | for (; i < len; i++){ 270 | var element = this[i]; 271 | if (element !== undefined || i in this){ 272 | current = callbackfn.call(null, current, element, i, this); 273 | } 274 | } 275 | return current; 276 | }); 277 | 278 | 279 | /** 280 | * ECMA-262-5 15.4.4.22 281 | * Apply a function simultaneously against two values of 282 | * the array (from right-to-left) as to reduce it to a single value. 283 | * @param {Object} callbackfn 284 | * @param {Object} initialValue {optional} 285 | */ 286 | $AP.reduceRight || 287 | ($AP.reduceRight = function(callbackfn, current){ 288 | if (!san.isFunction(callbackfn)) 289 | san.error(callbackfn + " is not a function"); 290 | // Pull out the length so that modifications to the length in the 291 | // loop will not affect the looping. 292 | var i = this.length - 1; 293 | 294 | find_initial: 295 | if (arguments.length < 2){ 296 | for (; i >= 0; i--){ 297 | current = this[i]; 298 | if (current !== undefined || i in this){ 299 | i--; 300 | break find_initial; 301 | } 302 | } 303 | san.error("reduce of empty array with no initial value"); 304 | } 305 | 306 | for (; i >= 0; i--){ 307 | var element = this[i]; 308 | if (element !== undefined || i in this){ 309 | current = callbackfn.call(null, current, element, i, this); 310 | } 311 | } 312 | return current; 313 | }); 314 | -------------------------------------------------------------------------------- /src/uglify/ant/UglifyTask.java: -------------------------------------------------------------------------------- 1 | package uglify.ant; 2 | 3 | import org.apache.tools.ant.BuildException; 4 | import org.apache.tools.ant.Project; 5 | import org.apache.tools.ant.Task; 6 | import org.apache.tools.ant.types.FileList; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.io.OutputStreamWriter; 12 | import java.util.ArrayList; 13 | import java.util.Date; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | import uglify.UglifyJS; 18 | import uglify.UglifyOptions; 19 | 20 | /** 21 | * This class implements a simple Ant task to do almost the same as 22 | * CommandLineRunner. 23 | * 24 | * Most of the public methods of this class are entry points for the Ant code to 25 | * hook into. 26 | * 27 | */ 28 | public final class UglifyTask extends Task { 29 | 30 | public boolean verbose; 31 | 32 | // -b or --beautify — output indented code; when passed, additional options 33 | // control the beautifier: 34 | public boolean beautify; 35 | 36 | // -i N or --indent N — indentation level (number of spaces) 37 | public Integer indent; 38 | 39 | // -q or --quote-keys — quote keys in literal objects (by default, only keys 40 | // that cannot be identifier names will be quotes). 41 | public boolean quoteKeys; 42 | 43 | public boolean ascii; 44 | 45 | // -nm or --no-mangle — don't mangle variable names 46 | public boolean noMangle; 47 | 48 | // -ns or --no-squeeze — don't call ast_squeeze() (which does various 49 | // optimizations that result in smaller, less readable code). 50 | public boolean noSqueeze; 51 | 52 | // -mt or --mangle-toplevel — mangle names in the toplevel scope too (by 53 | // default we don't do this). 54 | public boolean mangleToplevel; 55 | 56 | // --no-seqs — when ast_squeeze() is called (thus, unless you pass 57 | // --no-squeeze) it will reduce consecutive statements in blocks into a 58 | // sequence. For example, "a = 10; b = 20; foo();" will be written as 59 | // "a=10,b=20,foo();". In various occasions, this allows us to discard the 60 | // block brackets (since the block becomes a single statement). This is ON 61 | // by default because it seems safe and saves a few hundred bytes on some 62 | // libs that I tested it on, but pass --no-seqs to disable it. 63 | public boolean noSeqs; 64 | 65 | // --no-dead-code — by default, UglifyJS will remove code that is obviously 66 | // unreachable (code that follows a return, throw, break or continue 67 | // statement and is not a function/variable declaration). Pass this option 68 | // to disable this optimization. 69 | public boolean noDeadCode; 70 | 71 | // -nc or --no-copyright — by default, uglifyjs will keep the initial 72 | // comment tokens in the generated code (assumed to be copyright information 73 | // etc.). If you pass this it will discard it. 74 | public boolean noCopyright; 75 | 76 | // --overwrite — if the code is read from a file (not from STDIN) and you 77 | // pass --overwrite then the output will be written in the same file. 78 | public boolean overwrite; 79 | 80 | // --ast — pass this if you want to get the Abstract Syntax Tree instead of 81 | // JavaScript as output. Useful for debugging or learning more about the 82 | // internals. 83 | public boolean ast; 84 | 85 | // --extra — enable additional optimizations that have not yet been 86 | // extensively tested. These might, or might not, break your code. If you 87 | // find a bug using this option, please report a test case. 88 | public boolean extra; 89 | 90 | // --unsafe — enable other additional optimizations that are known to be 91 | // unsafe in some contrived situations, but could still be generally useful. 92 | // For now only this: 93 | // foo.toString() ==> foo+"" 94 | public boolean unsafe; 95 | // --max-line-len (default 32K characters) — add a newline after around 32K 96 | // characters. I've seen both FF and Chrome croak when all the code was on a 97 | // single line of around 670K. Pass –max-line-len 0 to disable this safety 98 | // feature. 99 | public Integer maxLineLen; 100 | 101 | // --reserved-names — some libraries rely on certain names to be used, as 102 | // pointed out in issue #92 and #81, so this option allow you to exclude 103 | // such names from the mangler. For example, to keep names require and 104 | // $super intact you'd specify –reserved-names "require,$super". 105 | public String reservedNames; 106 | 107 | // -o filename or --output filename — put the result in filename. If this 108 | // isn't given, the result goes to standard output (or see next one). 109 | //public String output; 110 | 111 | private String outputEncoding = "UTF-8"; 112 | private File outputFile; 113 | private final List sourceFileLists; 114 | 115 | public UglifyTask() { 116 | this.sourceFileLists = new LinkedList(); 117 | } 118 | 119 | public void setVerbose(boolean verbose) { 120 | this.verbose = verbose; 121 | } 122 | 123 | public void setBeautify(boolean beautify) { 124 | this.beautify = beautify; 125 | } 126 | 127 | public void setIndent(Integer indent) { 128 | this.indent = indent; 129 | } 130 | 131 | public void setQuoteKeys(boolean quoteKeys) { 132 | this.quoteKeys = quoteKeys; 133 | } 134 | 135 | public void setAscii(boolean ascii) { 136 | this.ascii = ascii; 137 | } 138 | 139 | public void setNoMangle(boolean noMangle) { 140 | this.noMangle = noMangle; 141 | } 142 | 143 | public void setNoSqueeze(boolean noSqueeze) { 144 | this.noSqueeze = noSqueeze; 145 | } 146 | 147 | public void setMangleToplevel(boolean mangleToplevel) { 148 | this.mangleToplevel = mangleToplevel; 149 | } 150 | 151 | public void setNoSeqs(boolean noSeqs) { 152 | this.noSeqs = noSeqs; 153 | } 154 | 155 | public void setNoDeadCode(boolean noDeadCode) { 156 | this.noDeadCode = noDeadCode; 157 | } 158 | 159 | public void setNoCopyright(boolean noCopyright) { 160 | this.noCopyright = noCopyright; 161 | } 162 | 163 | public void setOverwrite(boolean overwrite) { 164 | this.overwrite = overwrite; 165 | } 166 | 167 | public void setAst(boolean ast) { 168 | this.ast = ast; 169 | } 170 | 171 | public void setExtra(boolean extra) { 172 | this.extra = extra; 173 | } 174 | 175 | public void setUnsafe(boolean unsafe) { 176 | this.unsafe = unsafe; 177 | } 178 | 179 | public void setMaxLineLen(Integer maxLineLen) { 180 | this.maxLineLen = maxLineLen; 181 | } 182 | 183 | /** 184 | * super,class 185 | * 186 | * @param reservedNames 187 | */ 188 | public void setReservedNames(String reservedNames) { 189 | this.reservedNames = reservedNames; 190 | } 191 | 192 | /** 193 | * Set output file. file or dir all ok 194 | */ 195 | public void setOutput(File value) { 196 | this.outputFile = value; 197 | } 198 | 199 | /** 200 | * Sets the source files. 201 | */ 202 | public void addSources(FileList list) { 203 | this.sourceFileLists.add(list); 204 | } 205 | 206 | public void execute() { 207 | 208 | File[] sources = findSourceFiles(); 209 | 210 | if (sources != null) { 211 | log("Compiling " + sources.length + " file(s)"); 212 | 213 | UglifyJS uglifyjs = new UglifyJS(); 214 | 215 | ArrayList options = createUglifyOptions(); 216 | 217 | for (File source : sources) { 218 | 219 | options.add(source.getAbsolutePath()); 220 | 221 | String[] args = new String[options.size()]; 222 | 223 | args = options.toArray(args); 224 | 225 | if (this.outputFile == null) { 226 | 227 | uglifyjs.exec(args); 228 | 229 | } else { 230 | 231 | String result = uglifyjs.uglify(args); 232 | if (result != null) { 233 | writeResult(result); 234 | } else { 235 | throw new BuildException("uglify failed."); 236 | } 237 | 238 | } 239 | 240 | } 241 | 242 | } else { 243 | log("no source."); 244 | } 245 | } 246 | 247 | private ArrayList createUglifyOptions() { 248 | 249 | UglifyOptions options = new UglifyOptions(); 250 | 251 | options.ascii = this.ascii; 252 | options.ast = this.ast; 253 | options.verbose = this.verbose; 254 | options.beautify = this.beautify; 255 | options.indent = this.indent; 256 | options.quoteKeys = this.quoteKeys; 257 | options.ascii = this.ascii; 258 | options.noMangle = this.noMangle; 259 | options.noSqueeze = this.noSqueeze; 260 | options.mangleToplevel = this.mangleToplevel; 261 | options.noSeqs = this.noSeqs; 262 | options.noDeadCode = this.noDeadCode; 263 | options.noCopyright = this.noCopyright; 264 | options.overwrite = this.overwrite; 265 | options.ast = this.ast; 266 | options.extra = this.extra; 267 | options.unsafe = this.unsafe; 268 | options.maxLineLen = this.maxLineLen; 269 | options.reservedNames = this.reservedNames; 270 | 271 | return options.toArgList(); 272 | } 273 | 274 | private File[] findSourceFiles() { 275 | List files = new LinkedList(); 276 | 277 | for (FileList list : this.sourceFileLists) { 278 | files.addAll(findJavaScriptFiles(list)); 279 | } 280 | 281 | return files.toArray(new File[files.size()]); 282 | } 283 | 284 | /** 285 | * Translates an Ant file list into the file format that the compiler 286 | * expects. 287 | */ 288 | private List findJavaScriptFiles(FileList fileList) { 289 | List files = new LinkedList(); 290 | File baseDir = fileList.getDir(getProject()); 291 | 292 | for (String included : fileList.getFiles(getProject())) { 293 | files.add(new File(baseDir, included)); 294 | } 295 | 296 | return files; 297 | } 298 | 299 | private void writeResult(String source) { 300 | if (this.outputFile.getParentFile().mkdirs()) { 301 | log("Created missing parent directory " 302 | + this.outputFile.getParentFile(), Project.MSG_DEBUG); 303 | } 304 | 305 | try { 306 | OutputStreamWriter out = new OutputStreamWriter( 307 | new FileOutputStream(this.outputFile), this.outputEncoding); 308 | out.append(source); 309 | out.flush(); 310 | out.close(); 311 | } catch (IOException e) { 312 | throw new BuildException(e); 313 | } 314 | 315 | log("Compiled javascript written to " 316 | + this.outputFile.getAbsolutePath(), Project.MSG_DEBUG); 317 | } 318 | 319 | } 320 | -------------------------------------------------------------------------------- /src/uglify/javascript/adapter/sys.js: -------------------------------------------------------------------------------- 1 | var sys =new Object; 2 | 3 | sys.puts = function() { 4 | for (var i = 0, len = arguments.length; i < len; ++i) { 5 | println(arguments[i]); 6 | } 7 | }; 8 | 9 | sys.print = function() { 10 | for (var i = 0, len = arguments.length; i < len; ++i) { 11 | print(arguments[i]); 12 | } 13 | }; 14 | 15 | 16 | sys.puts = function() { 17 | for (var i = 0, len = arguments.length; i < len; ++i) { 18 | print(arguments[i] + '\n'); 19 | } 20 | }; 21 | 22 | 23 | sys.debug = function(x) { 24 | print('DEBUG: ' + x + '\n'); 25 | }; 26 | 27 | 28 | var error = sys.error = function(x) { 29 | for (var i = 0, len = arguments.length; i < len; ++i) { 30 | print(arguments[i] + '\n'); 31 | } 32 | }; 33 | 34 | 35 | /** 36 | * Echos the value of a value. Trys to print the value out 37 | * in the best way possible given the different types. 38 | * 39 | * @param {Object} obj The object to print out. 40 | * @param {Boolean} showHidden Flag that shows hidden (not enumerable) 41 | * properties of objects. 42 | * @param {Number} depth Depth in which to descend in object. Default is 2. 43 | * @param {Boolean} colors Flag to turn on ANSI escape codes to color the 44 | * output. Default is false (no coloring). 45 | */ 46 | sys.inspect = function(obj, showHidden, depth, colors) { 47 | var seen = []; 48 | 49 | var stylize = function(str, styleType) { 50 | // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics 51 | var styles = 52 | { 'bold' : [1, 22], 53 | 'italic' : [3, 23], 54 | 'underline' : [4, 24], 55 | 'inverse' : [7, 27], 56 | 'white' : [37, 39], 57 | 'grey' : [90, 39], 58 | 'black' : [30, 39], 59 | 'blue' : [34, 39], 60 | 'cyan' : [36, 39], 61 | 'green' : [32, 39], 62 | 'magenta' : [35, 39], 63 | 'red' : [31, 39], 64 | 'yellow' : [33, 39] }; 65 | 66 | var style = 67 | { 'special': 'cyan', 68 | 'number': 'blue', 69 | 'boolean': 'yellow', 70 | 'undefined': 'grey', 71 | 'null': 'bold', 72 | 'string': 'green', 73 | 'date': 'magenta', 74 | // "name": intentionally not styling 75 | 'regexp': 'red' }[styleType]; 76 | 77 | if (style) { 78 | return '\033[' + styles[style][0] + 'm' + str + 79 | '\033[' + styles[style][1] + 'm'; 80 | } else { 81 | return str; 82 | } 83 | }; 84 | if (! colors) { 85 | stylize = function(str, styleType) { return str; }; 86 | } 87 | 88 | function format(value, recurseTimes) { 89 | value = ""+value; 90 | // Provide a hook for user-specified inspect functions. 91 | // Check that value is an object with an inspect function on it 92 | if (value && typeof value.inspect === 'function' && 93 | // Filter out the util module, it's inspect function is special 94 | value !== sys && 95 | // Also filter out any prototype objects using the circular check. 96 | !(value.constructor && value.constructor.prototype === value)) { 97 | return value.inspect(recurseTimes); 98 | } 99 | 100 | // Primitive types cannot have properties 101 | switch (typeof value) { 102 | case 'undefined': 103 | return stylize('undefined', 'undefined'); 104 | 105 | case 'string': 106 | var simple = JSON.stringify(value).replace(/'/g, "\\'") 107 | .replace(/\\"/g, '"') 108 | .replace(/(^"|"$)/g, "'"); 109 | return stylize(simple, 'string'); 110 | 111 | case 'number': 112 | return stylize('' + value, 'number'); 113 | 114 | case 'boolean': 115 | return stylize('' + value, 'boolean'); 116 | } 117 | // For some reason typeof null is "object", so special case here. 118 | if (value === null) { 119 | return stylize('null', 'null'); 120 | } 121 | 122 | // Look up the keys of the object. 123 | function objectKeys(obj){ 124 | var arr=[]; 125 | for(var k in obj){ 126 | arr.push(k); 127 | } 128 | return arr; 129 | } 130 | 131 | var visible_keys = objectKeys(value); 132 | var keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; 133 | 134 | // Functions without properties can be shortcutted. 135 | if (typeof value === 'function' && keys.length === 0) { 136 | if (isRegExp(value)) { 137 | return stylize('' + value, 'regexp'); 138 | } else { 139 | var name = value.name ? ': ' + value.name : ''; 140 | return stylize('[Function' + name + ']', 'special'); 141 | } 142 | } 143 | 144 | // Dates without properties can be shortcutted 145 | if (isDate(value) && keys.length === 0) { 146 | return stylize(value.toUTCString(), 'date'); 147 | } 148 | 149 | var base, type, braces; 150 | // Determine the object type 151 | if (isArray(value)) { 152 | type = 'Array'; 153 | braces = ['[', ']']; 154 | } else { 155 | type = 'Object'; 156 | braces = ['{', '}']; 157 | } 158 | 159 | // Make functions say that they are functions 160 | if (typeof value === 'function') { 161 | var n = value.name ? ': ' + value.name : ''; 162 | base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; 163 | } else { 164 | base = ''; 165 | } 166 | 167 | // Make dates with properties first say the date 168 | if (isDate(value)) { 169 | base = ' ' + value.toUTCString(); 170 | } 171 | 172 | if (keys.length === 0) { 173 | return braces[0] + base + braces[1]; 174 | } 175 | 176 | if (recurseTimes < 0) { 177 | if (isRegExp(value)) { 178 | return stylize('' + value, 'regexp'); 179 | } else { 180 | return stylize('[Object]', 'special'); 181 | } 182 | } 183 | 184 | seen.push(value); 185 | 186 | 187 | var output = keys.map(function(key) { 188 | var name, str; 189 | if (value.__lookupGetter__) { 190 | if (value.__lookupGetter__(key)) { 191 | if (value.__lookupSetter__(key)) { 192 | str = stylize('[Getter/Setter]', 'special'); 193 | } else { 194 | str = stylize('[Getter]', 'special'); 195 | } 196 | } else { 197 | if (value.__lookupSetter__(key)) { 198 | str = stylize('[Setter]', 'special'); 199 | } 200 | } 201 | } 202 | if (visible_keys.indexOf(key) < 0) { 203 | name = '[' + key + ']'; 204 | } 205 | if (!str) { 206 | if (seen.indexOf(value[key]) < 0) { 207 | if (recurseTimes === null) { 208 | str = format(value[key]); 209 | } else { 210 | str = format(value[key], recurseTimes - 1); 211 | } 212 | if (str.indexOf('\n') > -1) { 213 | if (isArray(value)) { 214 | str = str.split('\n').map(function(line) { 215 | return ' ' + line; 216 | }).join('\n').substr(2); 217 | } else { 218 | str = '\n' + str.split('\n').map(function(line) { 219 | return ' ' + line; 220 | }).join('\n'); 221 | } 222 | } 223 | } else { 224 | str = stylize('[Circular]', 'special'); 225 | } 226 | } 227 | if (typeof name === 'undefined') { 228 | if (type === 'Array' && key.match(/^\d+$/)) { 229 | return str; 230 | } 231 | name = JSON.stringify('' + key); 232 | if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { 233 | name = name.substr(1, name.length - 2); 234 | name = stylize(name, 'name'); 235 | } else { 236 | name = name.replace(/'/g, "\\'") 237 | .replace(/\\"/g, '"') 238 | .replace(/(^"|"$)/g, "'"); 239 | name = stylize(name, 'string'); 240 | } 241 | } 242 | 243 | return name + ': ' + str; 244 | }); 245 | 246 | seen.pop(); 247 | 248 | var numLinesEst = 0; 249 | var length = output.reduce(function(prev, cur) { 250 | numLinesEst++; 251 | if (cur.indexOf('\n') >= 0) numLinesEst++; 252 | return prev + cur.length + 1; 253 | }, 0); 254 | 255 | if (length > 50) { //(require('readline').columns || 50) 256 | output = braces[0] + 257 | (base === '' ? '' : base + '\n ') + 258 | ' ' + 259 | output.join(',\n ') + 260 | ' ' + 261 | braces[1]; 262 | 263 | } else { 264 | output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; 265 | } 266 | 267 | return output; 268 | } 269 | return format(obj, (typeof depth === 'undefined' ? 2 : depth)); 270 | }; 271 | 272 | 273 | function isArray(ar) { 274 | return ar instanceof Array || //TODO 275 | //Array.isArray(ar) || 276 | (ar && ar !== Object.prototype && isArray(ar.__proto__)); 277 | } 278 | 279 | 280 | function isRegExp(re) { 281 | var s = '' + re; 282 | return re instanceof RegExp || // easy case 283 | // duck-type for context-switching evalcx case 284 | typeof(re) === 'function' && 285 | re.constructor.name === 'RegExp' && 286 | re.compile && 287 | re.test && 288 | re.exec && 289 | s.match(/^\/.*\/[gim]{0,3}$/); 290 | } 291 | 292 | 293 | function isDate(d) { 294 | if (d instanceof Date) return true; //TODO 295 | /*if (typeof d !== 'object') return false; 296 | var properties = Date.prototype && Object.getOwnPropertyNames(Date.prototype); 297 | var proto = d.__proto__ && Object.getOwnPropertyNames(d.__proto__); 298 | return JSON.stringify(proto) === JSON.stringify(properties);*/ 299 | } 300 | 301 | 302 | var pWarning; 303 | sys.p = function() { 304 | if (!pWarning) { 305 | pWarning = 'util.p will be removed in future versions of Node. ' + 306 | 'Use util.puts(util.inspect()) instead.\n'; 307 | sys.error(pWarning); 308 | } 309 | for (var i = 0, len = arguments.length; i < len; ++i) { 310 | error(sys.inspect(arguments[i])); 311 | } 312 | }; 313 | 314 | 315 | function pad(n) { 316 | return n < 10 ? '0' + n.toString(10) : n.toString(10); 317 | } 318 | 319 | 320 | var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 321 | 'Oct', 'Nov', 'Dec']; 322 | 323 | // 26 Feb 16:19:34 324 | function timestamp() { 325 | var d = new Date(); 326 | var time = [pad(d.getHours()), 327 | pad(d.getMinutes()), 328 | pad(d.getSeconds())].join(':'); 329 | return [d.getDate(), months[d.getMonth()], time].join(' '); 330 | } 331 | 332 | 333 | sys.log = function(msg) { 334 | sys.puts(timestamp() + ' - ' + msg.toString()); 335 | }; 336 | 337 | /** 338 | * Inherit the prototype methods from one constructor into another. 339 | * 340 | * The Function.prototype.inherits from lang.js rewritten as a standalone 341 | * function (not on Function.prototype). NOTE: If this file is to be loaded 342 | * during bootstrapping this function needs to be revritten using some native 343 | * functions as prototype setup using normal JavaScript does not work as 344 | * expected during bootstrapping (see mirror.js in r114903). 345 | * 346 | * @param {function} ctor Constructor function which needs to inherit the 347 | * prototype. 348 | * @param {function} superCtor Constructor function to inherit prototype from. 349 | */ 350 | sys.inherits = function(ctor, superCtor) { 351 | ctor.super_ = superCtor; 352 | ctor.prototype = Object.create(superCtor.prototype, { 353 | constructor: { value: ctor, enumerable: false } 354 | }); 355 | }; 356 | -------------------------------------------------------------------------------- /src/uglify/javascript/adapter/JSON.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2009-08-17 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | This file creates a global JSON object containing two methods: stringify 12 | and parse. 13 | 14 | JSON.stringify(value, replacer, space) 15 | value any JavaScript value, usually an object or array. 16 | 17 | replacer an optional parameter that determines how object 18 | values are stringified for objects. It can be a 19 | function or an array of strings. 20 | 21 | space an optional parameter that specifies the indentation 22 | of nested structures. If it is omitted, the text will 23 | be packed without extra whitespace. If it is a number, 24 | it will specify the number of spaces to indent at each 25 | level. If it is a string (such as '\t' or ' '), 26 | it contains the characters used to indent at each level. 27 | 28 | This method produces a JSON text from a JavaScript value. 29 | 30 | When an object value is found, if the object contains a toJSON 31 | method, its toJSON method will be called and the result will be 32 | stringified. A toJSON method does not serialize: it returns the 33 | value represented by the name/value pair that should be serialized, 34 | or undefined if nothing should be serialized. The toJSON method 35 | will be passed the key associated with the value, and this will be 36 | bound to the value 37 | 38 | For example, this would serialize Dates as ISO strings. 39 | 40 | Date.prototype.toJSON = function (key) { 41 | function f(n) { 42 | // Format integers to have at least two digits. 43 | return n < 10 ? '0' + n : n; 44 | } 45 | 46 | return this.getUTCFullYear() + '-' + 47 | f(this.getUTCMonth() + 1) + '-' + 48 | f(this.getUTCDate()) + 'T' + 49 | f(this.getUTCHours()) + ':' + 50 | f(this.getUTCMinutes()) + ':' + 51 | f(this.getUTCSeconds()) + 'Z'; 52 | }; 53 | 54 | You can provide an optional replacer method. It will be passed the 55 | key and value of each member, with this bound to the containing 56 | object. The value that is returned from your method will be 57 | serialized. If your method returns undefined, then the member will 58 | be excluded from the serialization. 59 | 60 | If the replacer parameter is an array of strings, then it will be 61 | used to select the members to be serialized. It filters the results 62 | such that only members with keys listed in the replacer array are 63 | stringified. 64 | 65 | Values that do not have JSON representations, such as undefined or 66 | functions, will not be serialized. Such values in objects will be 67 | dropped; in arrays they will be replaced with null. You can use 68 | a replacer function to replace those with JSON values. 69 | JSON.stringify(undefined) returns undefined. 70 | 71 | The optional space parameter produces a stringification of the 72 | value that is filled with line breaks and indentation to make it 73 | easier to read. 74 | 75 | If the space parameter is a non-empty string, then that string will 76 | be used for indentation. If the space parameter is a number, then 77 | the indentation will be that many spaces. 78 | 79 | Example: 80 | 81 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 82 | // text is '["e",{"pluribus":"unum"}]' 83 | 84 | 85 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 86 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 87 | 88 | text = JSON.stringify([new Date()], function (key, value) { 89 | return this[key] instanceof Date ? 90 | 'Date(' + this[key] + ')' : value; 91 | }); 92 | // text is '["Date(---current time---)"]' 93 | 94 | 95 | JSON.parse(text, reviver) 96 | This method parses a JSON text to produce an object or array. 97 | It can throw a SyntaxError exception. 98 | 99 | The optional reviver parameter is a function that can filter and 100 | transform the results. It receives each of the keys and values, 101 | and its return value is used instead of the original value. 102 | If it returns what it received, then the structure is not modified. 103 | If it returns undefined then the member is deleted. 104 | 105 | Example: 106 | 107 | // Parse the text. Values that look like ISO date strings will 108 | // be converted to Date objects. 109 | 110 | myData = JSON.parse(text, function (key, value) { 111 | var a; 112 | if (typeof value === 'string') { 113 | a = 114 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 115 | if (a) { 116 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 117 | +a[5], +a[6])); 118 | } 119 | } 120 | return value; 121 | }); 122 | 123 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 124 | var d; 125 | if (typeof value === 'string' && 126 | value.slice(0, 5) === 'Date(' && 127 | value.slice(-1) === ')') { 128 | d = new Date(value.slice(5, -1)); 129 | if (d) { 130 | return d; 131 | } 132 | } 133 | return value; 134 | }); 135 | 136 | 137 | This is a reference implementation. You are free to copy, modify, or 138 | redistribute. 139 | 140 | This code should be minified before deployment. 141 | See http://javascript.crockford.com/jsmin.html 142 | 143 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 144 | NOT CONTROL. 145 | */ 146 | 147 | /*jslint evil: true */ 148 | 149 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 150 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 151 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 152 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 153 | test, toJSON, toString, valueOf 154 | */ 155 | 156 | "use strict"; 157 | 158 | // Create a JSON object only if one does not already exist. We create the 159 | // methods in a closure to avoid creating global variables. 160 | 161 | if (!this.JSON) { 162 | this.JSON = {}; 163 | } 164 | 165 | (function () { 166 | 167 | function f(n) { 168 | // Format integers to have at least two digits. 169 | return n < 10 ? '0' + n : n; 170 | } 171 | 172 | if (typeof Date.prototype.toJSON !== 'function') { 173 | 174 | Date.prototype.toJSON = function (key) { 175 | 176 | return isFinite(this.valueOf()) ? 177 | this.getUTCFullYear() + '-' + 178 | f(this.getUTCMonth() + 1) + '-' + 179 | f(this.getUTCDate()) + 'T' + 180 | f(this.getUTCHours()) + ':' + 181 | f(this.getUTCMinutes()) + ':' + 182 | f(this.getUTCSeconds()) + 'Z' : null; 183 | }; 184 | 185 | String.prototype.toJSON = 186 | Number.prototype.toJSON = 187 | Boolean.prototype.toJSON = function (key) { 188 | return this.valueOf(); 189 | }; 190 | } 191 | 192 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 193 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | gap, 195 | indent, 196 | meta = { // table of character substitutions 197 | '\b': '\\b', 198 | '\t': '\\t', 199 | '\n': '\\n', 200 | '\f': '\\f', 201 | '\r': '\\r', 202 | '"' : '\\"', 203 | '\\': '\\\\' 204 | }, 205 | rep; 206 | 207 | 208 | function quote(string) { 209 | 210 | // If the string contains no control characters, no quote characters, and no 211 | // backslash characters, then we can safely slap some quotes around it. 212 | // Otherwise we must also replace the offending characters with safe escape 213 | // sequences. 214 | 215 | escapable.lastIndex = 0; 216 | return escapable.test(string) ? 217 | '"' + string.replace(escapable, function (a) { 218 | var c = meta[a]; 219 | return typeof c === 'string' ? c : 220 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 221 | }) + '"' : 222 | '"' + string + '"'; 223 | } 224 | 225 | 226 | function str(key, holder) { 227 | // Produce a string from holder[key]. 228 | 229 | var i, // The loop counter. 230 | k, // The member key. 231 | v, // The member value. 232 | length, 233 | mind = gap, 234 | partial, 235 | value = holder[key]; 236 | 237 | // If the value has a toJSON method, call it to obtain a replacement value. 238 | 239 | if (value && typeof value === 'object' && 240 | typeof value.toJSON === 'function') { 241 | value = value.toJSON(key); 242 | } 243 | 244 | // If we were called with a replacer function, then call the replacer to 245 | // obtain a replacement value. 246 | 247 | if (typeof rep === 'function') { 248 | value = rep.call(holder, key, value); 249 | } 250 | 251 | // What happens next depends on the value's type. 252 | 253 | switch (typeof value) { 254 | case 'string': 255 | return quote(value); 256 | 257 | case 'number': 258 | 259 | // JSON numbers must be finite. Encode non-finite numbers as null. 260 | 261 | return isFinite(value) ? String(value) : 'null'; 262 | 263 | case 'boolean': 264 | case 'null': 265 | 266 | // If the value is a boolean or null, convert it to a string. Note: 267 | // typeof null does not produce 'null'. The case is included here in 268 | // the remote chance that this gets fixed someday. 269 | 270 | return String(value); 271 | 272 | // If the type is 'object', we might be dealing with an object or an array or 273 | // null. 274 | 275 | case 'object': 276 | 277 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 278 | // so watch out for that case. 279 | 280 | if (!value) { 281 | return 'null'; 282 | } 283 | 284 | // Make an array to hold the partial results of stringifying this object value. 285 | 286 | gap += indent; 287 | partial = []; 288 | 289 | // Is the value an array? 290 | 291 | if (Object.prototype.toString.apply(value) === '[object Array]') { 292 | 293 | // The value is an array. Stringify every element. Use null as a placeholder 294 | // for non-JSON values. 295 | 296 | length = value.length; 297 | for (i = 0; i < length; i += 1) { 298 | partial[i] = str(i, value) || 'null'; 299 | } 300 | 301 | // Join all of the elements together, separated with commas, and wrap them in 302 | // brackets. 303 | 304 | v = partial.length === 0 ? '[]' : 305 | gap ? '[\n' + gap + 306 | partial.join(',\n' + gap) + '\n' + 307 | mind + ']' : 308 | '[' + partial.join(',') + ']'; 309 | gap = mind; 310 | return v; 311 | } 312 | 313 | // If the replacer is an array, use it to select the members to be stringified. 314 | 315 | if (rep && typeof rep === 'object') { 316 | length = rep.length; 317 | for (i = 0; i < length; i += 1) { 318 | k = rep[i]; 319 | if (typeof k === 'string') { 320 | v = str(k, value); 321 | if (v) { 322 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 323 | } 324 | } 325 | } 326 | } else { 327 | 328 | // Otherwise, iterate through all of the keys in the object. 329 | 330 | for (k in value) { 331 | if (Object.hasOwnProperty.call(value, k)) { 332 | v = str(k, value); 333 | if (v) { 334 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 335 | } 336 | } 337 | } 338 | } 339 | 340 | // Join all of the member texts together, separated with commas, 341 | // and wrap them in braces. 342 | 343 | v = partial.length === 0 ? '{}' : 344 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 345 | mind + '}' : '{' + partial.join(',') + '}'; 346 | gap = mind; 347 | return v; 348 | } 349 | } 350 | 351 | // If the JSON object does not yet have a stringify method, give it one. 352 | 353 | if (typeof JSON.stringify !== 'function') { 354 | JSON.stringify = function (value, replacer, space) { 355 | // The stringify method takes a value and an optional replacer, and an optional 356 | // space parameter, and returns a JSON text. The replacer can be a function 357 | // that can replace values, or an array of strings that will select the keys. 358 | // A default replacer method can be provided. Use of the space parameter can 359 | // produce text that is more easily readable. 360 | 361 | var i; 362 | gap = ''; 363 | indent = ''; 364 | 365 | // If the space parameter is a number, make an indent string containing that 366 | // many spaces. 367 | 368 | if (typeof space === 'number') { 369 | for (i = 0; i < space; i += 1) { 370 | indent += ' '; 371 | } 372 | 373 | // If the space parameter is a string, it will be used as the indent string. 374 | 375 | } else if (typeof space === 'string') { 376 | indent = space; 377 | } 378 | 379 | // If there is a replacer, it must be a function or an array. 380 | // Otherwise, throw an error. 381 | 382 | rep = replacer; 383 | if (replacer && typeof replacer !== 'function' && 384 | (typeof replacer !== 'object' || 385 | typeof replacer.length !== 'number')) { 386 | throw new Error('JSON.stringify'); 387 | } 388 | 389 | // Make a fake root object containing our value under the key of ''. 390 | // Return the result of stringifying the value. 391 | 392 | return str('', {'': value}); 393 | }; 394 | } 395 | 396 | 397 | // If the JSON object does not yet have a parse method, give it one. 398 | 399 | if (typeof JSON.parse !== 'function') { 400 | JSON.parse = function (text, reviver) { 401 | 402 | // The parse method takes a text and an optional reviver function, and returns 403 | // a JavaScript value if the text is a valid JSON text. 404 | 405 | var j; 406 | 407 | function walk(holder, key) { 408 | 409 | // The walk method is used to recursively walk the resulting structure so 410 | // that modifications can be made. 411 | 412 | var k, v, value = holder[key]; 413 | if (value && typeof value === 'object') { 414 | for (k in value) { 415 | if (Object.hasOwnProperty.call(value, k)) { 416 | v = walk(value, k); 417 | if (v !== undefined) { 418 | value[k] = v; 419 | } else { 420 | delete value[k]; 421 | } 422 | } 423 | } 424 | } 425 | return reviver.call(holder, key, value); 426 | } 427 | 428 | 429 | // Parsing happens in four stages. In the first stage, we replace certain 430 | // Unicode characters with escape sequences. JavaScript handles many characters 431 | // incorrectly, either silently deleting them, or treating them as line endings. 432 | 433 | cx.lastIndex = 0; 434 | if (cx.test(text)) { 435 | text = text.replace(cx, function (a) { 436 | return '\\u' + 437 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 438 | }); 439 | } 440 | 441 | // In the second stage, we run the text against regular expressions that look 442 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 443 | // because they can cause invocation, and '=' because it can cause mutation. 444 | // But just to be safe, we want to reject all unexpected forms. 445 | 446 | // We split the second stage into 4 regexp operations in order to work around 447 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 448 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 449 | // replace all simple value tokens with ']' characters. Third, we delete all 450 | // open brackets that follow a colon or comma or that begin the text. Finally, 451 | // we look to see that the remaining characters are only whitespace or ']' or 452 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 453 | 454 | if (/^[\],:{}\s]*$/. 455 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 456 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 457 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 458 | 459 | // In the third stage we use the eval function to compile the text into a 460 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 461 | // in JavaScript: it can begin a block or an object literal. We wrap the text 462 | // in parens to eliminate the ambiguity. 463 | 464 | j = eval('(' + text + ')'); 465 | 466 | // In the optional fourth stage, we recursively walk the new structure, passing 467 | // each name/value pair to a reviver function for possible transformation. 468 | 469 | return typeof reviver === 'function' ? 470 | walk({'': j}, '') : j; 471 | } 472 | 473 | // If the text is not JSON parseable, then a SyntaxError is thrown. 474 | 475 | throw new SyntaxError('JSON.parse'); 476 | }; 477 | } 478 | }()); 479 | -------------------------------------------------------------------------------- /src/uglify/javascript/parse-js.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | 5 | This version is suitable for Node.js. With minimal changes (the 6 | exports stuff) it should work on any JS platform. 7 | 8 | This file contains the tokenizer/parser. It is a port to JavaScript 9 | of parse-js [1], a JavaScript parser library written in Common Lisp 10 | by Marijn Haverbeke. Thank you Marijn! 11 | 12 | [1] http://marijn.haverbeke.nl/parse-js/ 13 | 14 | Exported functions: 15 | 16 | - tokenizer(code) -- returns a function. Call the returned 17 | function to fetch the next token. 18 | 19 | - parse(code) -- returns an AST of the given JavaScript code. 20 | 21 | -------------------------------- (C) --------------------------------- 22 | 23 | Author: Mihai Bazon 24 | 25 | http://mihai.bazon.net/blog 26 | 27 | Distributed under the BSD license: 28 | 29 | Copyright 2010 (c) Mihai Bazon 30 | Based on parse-js (http://marijn.haverbeke.nl/parse-js/). 31 | 32 | Redistribution and use in source and binary forms, with or without 33 | modification, are permitted provided that the following conditions 34 | are met: 35 | 36 | * Redistributions of source code must retain the above 37 | copyright notice, this list of conditions and the following 38 | disclaimer. 39 | 40 | * Redistributions in binary form must reproduce the above 41 | copyright notice, this list of conditions and the following 42 | disclaimer in the documentation and/or other materials 43 | provided with the distribution. 44 | 45 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 46 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 48 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 49 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 50 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 51 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 52 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 53 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 54 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 55 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 56 | SUCH DAMAGE. 57 | 58 | ***********************************************************************/ 59 | 60 | /* -----[ Tokenizer (constants) ]----- */ 61 | 62 | var KEYWORDS = array_to_hash([ 63 | "break", 64 | "case", 65 | "catch", 66 | "const", 67 | "continue", 68 | "default", 69 | "delete", 70 | "do", 71 | "else", 72 | "finally", 73 | "for", 74 | "function", 75 | "if", 76 | "in", 77 | "instanceof", 78 | "new", 79 | "return", 80 | "switch", 81 | "throw", 82 | "try", 83 | "typeof", 84 | "var", 85 | "void", 86 | "while", 87 | "with" 88 | ]); 89 | 90 | var RESERVED_WORDS = array_to_hash([ 91 | "abstract", 92 | "boolean", 93 | "byte", 94 | "char", 95 | "class", 96 | "debugger", 97 | "double", 98 | "enum", 99 | "export", 100 | "extends", 101 | "final", 102 | "float", 103 | "goto", 104 | "implements", 105 | "import", 106 | "int", 107 | "interface", 108 | "long", 109 | "native", 110 | "package", 111 | "private", 112 | "protected", 113 | "public", 114 | "short", 115 | "static", 116 | "super", 117 | "synchronized", 118 | "throws", 119 | "transient", 120 | "volatile" 121 | ]); 122 | 123 | var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([ 124 | "return", 125 | "new", 126 | "delete", 127 | "throw", 128 | "else", 129 | "case" 130 | ]); 131 | 132 | var KEYWORDS_ATOM = array_to_hash([ 133 | "false", 134 | "null", 135 | "true", 136 | "undefined" 137 | ]); 138 | 139 | var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^")); 140 | 141 | var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; 142 | var RE_OCT_NUMBER = /^0[0-7]+$/; 143 | var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i; 144 | 145 | var OPERATORS = array_to_hash([ 146 | "in", 147 | "instanceof", 148 | "typeof", 149 | "new", 150 | "void", 151 | "delete", 152 | "++", 153 | "--", 154 | "+", 155 | "-", 156 | "!", 157 | "~", 158 | "&", 159 | "|", 160 | "^", 161 | "*", 162 | "/", 163 | "%", 164 | ">>", 165 | "<<", 166 | ">>>", 167 | "<", 168 | ">", 169 | "<=", 170 | ">=", 171 | "==", 172 | "===", 173 | "!=", 174 | "!==", 175 | "?", 176 | "=", 177 | "+=", 178 | "-=", 179 | "/=", 180 | "*=", 181 | "%=", 182 | ">>=", 183 | "<<=", 184 | ">>>=", 185 | "%=", 186 | "|=", 187 | "^=", 188 | "&=", 189 | "&&", 190 | "||" 191 | ]); 192 | 193 | var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t\u200b")); 194 | 195 | var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); 196 | 197 | var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); 198 | 199 | var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); 200 | 201 | /* -----[ Tokenizer ]----- */ 202 | 203 | // regexps adapted from http://xregexp.com/plugins/#unicode 204 | var UNICODE = { 205 | letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"), 206 | non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"), 207 | space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"), 208 | connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]") 209 | }; 210 | 211 | function is_letter(ch) { 212 | return UNICODE.letter.test(ch); 213 | }; 214 | 215 | function is_digit(ch) { 216 | ch = ch.charCodeAt(0); 217 | return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9 218 | }; 219 | 220 | function is_alphanumeric_char(ch) { 221 | return is_digit(ch) || is_letter(ch); 222 | }; 223 | 224 | function is_unicode_combining_mark(ch) { 225 | return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch); 226 | }; 227 | 228 | function is_unicode_connector_punctuation(ch) { 229 | return UNICODE.connector_punctuation.test(ch); 230 | }; 231 | 232 | function is_identifier_start(ch) { 233 | return ch == "$" || ch == "_" || is_letter(ch); 234 | }; 235 | 236 | function is_identifier_char(ch) { 237 | return is_identifier_start(ch) 238 | || is_unicode_combining_mark(ch) 239 | || is_digit(ch) 240 | || is_unicode_connector_punctuation(ch) 241 | || ch == "\u200c" // zero-width non-joiner 242 | || ch == "\u200d" // zero-width joiner (in my ECMA-262 PDF, this is also 200c) 243 | ; 244 | }; 245 | 246 | function parse_js_number(num) { 247 | if (RE_HEX_NUMBER.test(num)) { 248 | return parseInt(num.substr(2), 16); 249 | } else if (RE_OCT_NUMBER.test(num)) { 250 | return parseInt(num.substr(1), 8); 251 | } else if (RE_DEC_NUMBER.test(num)) { 252 | return parseFloat(num); 253 | } 254 | }; 255 | 256 | function JS_Parse_Error(message, line, col, pos) { 257 | this.message = message; 258 | this.line = line; 259 | this.col = col; 260 | this.pos = pos; 261 | try { 262 | ({})(); 263 | } catch(ex) { 264 | this.stack = ex.stack; 265 | }; 266 | }; 267 | 268 | JS_Parse_Error.prototype.toString = function() { 269 | return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; 270 | }; 271 | 272 | function js_error(message, line, col, pos) { 273 | println(message+" | "+line+" | "+col+" | "+pos); 274 | throw new JS_Parse_Error(message, line, col, pos); 275 | }; 276 | 277 | function is_token(token, type, val) { 278 | return token.type == type && (val == null || token.value == val); 279 | }; 280 | 281 | var EX_EOF = {}; 282 | 283 | function tokenizer($TEXT) { 284 | 285 | var S = { 286 | text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), 287 | pos : 0, 288 | tokpos : 0, 289 | line : 0, 290 | tokline : 0, 291 | col : 0, 292 | tokcol : 0, 293 | newline_before : false, 294 | regex_allowed : false, 295 | comments_before : [] 296 | }; 297 | 298 | function peek() { return S.text.charAt(S.pos); }; 299 | 300 | function next(signal_eof) { 301 | var ch = S.text.charAt(S.pos++); 302 | if (signal_eof && !ch) 303 | throw EX_EOF; 304 | if (ch == "\n") { 305 | S.newline_before = true; 306 | ++S.line; 307 | S.col = 0; 308 | } else { 309 | ++S.col; 310 | } 311 | return ch; 312 | }; 313 | 314 | function eof() { 315 | return !S.peek(); 316 | }; 317 | 318 | function find(what, signal_eof) { 319 | var pos = S.text.indexOf(what, S.pos); 320 | if (signal_eof && pos == -1) throw EX_EOF; 321 | return pos; 322 | }; 323 | 324 | function start_token() { 325 | S.tokline = S.line; 326 | S.tokcol = S.col; 327 | S.tokpos = S.pos; 328 | }; 329 | 330 | function token(type, value, is_comment) { 331 | S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) || 332 | (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) || 333 | (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value))); 334 | var ret = { 335 | type : type, 336 | value : value, 337 | line : S.tokline, 338 | col : S.tokcol, 339 | pos : S.tokpos, 340 | nlb : S.newline_before 341 | }; 342 | if (!is_comment) { 343 | ret.comments_before = S.comments_before; 344 | S.comments_before = []; 345 | } 346 | S.newline_before = false; 347 | return ret; 348 | }; 349 | 350 | function skip_whitespace() { 351 | while (HOP(WHITESPACE_CHARS, peek())) 352 | next(); 353 | }; 354 | 355 | function read_while(pred) { 356 | var ret = "", ch = peek(), i = 0; 357 | while (ch && pred(ch, i++)) { 358 | ret += next(); 359 | ch = peek(); 360 | } 361 | return ret; 362 | }; 363 | 364 | function parse_error(err) { 365 | js_error(err, S.tokline, S.tokcol, S.tokpos); 366 | }; 367 | 368 | function read_num(prefix) { 369 | var has_e = false, after_e = false, has_x = false, has_dot = prefix == "."; 370 | var num = read_while(function(ch, i){ 371 | if (ch == "x" || ch == "X") { 372 | if (has_x) return false; 373 | return has_x = true; 374 | } 375 | if (!has_x && (ch == "E" || ch == "e")) { 376 | if (has_e) return false; 377 | return has_e = after_e = true; 378 | } 379 | if (ch == "-") { 380 | if (after_e || (i == 0 && !prefix)) return true; 381 | return false; 382 | } 383 | if (ch == "+") return after_e; 384 | after_e = false; 385 | if (ch == ".") { 386 | if (!has_dot && !has_x) 387 | return has_dot = true; 388 | return false; 389 | } 390 | return is_alphanumeric_char(ch); 391 | }); 392 | if (prefix) 393 | num = prefix + num; 394 | var valid = parse_js_number(num); 395 | if (!isNaN(valid)) { 396 | return token("num", valid); 397 | } else { 398 | parse_error("Invalid syntax: " + num); 399 | } 400 | }; 401 | 402 | function read_escaped_char() { 403 | var ch = next(true); 404 | switch (ch) { 405 | case "n" : return "\n"; 406 | case "r" : return "\r"; 407 | case "t" : return "\t"; 408 | case "b" : return "\b"; 409 | case "v" : return "\v"; 410 | case "f" : return "\f"; 411 | case "0" : return "\0"; 412 | case "x" : return String.fromCharCode(hex_bytes(2)); 413 | case "u" : return String.fromCharCode(hex_bytes(4)); 414 | default : return ch; 415 | } 416 | }; 417 | 418 | function hex_bytes(n) { 419 | var num = 0; 420 | for (; n > 0; --n) { 421 | var digit = parseInt(next(true), 16); 422 | if (isNaN(digit)) 423 | parse_error("Invalid hex-character pattern in string"); 424 | num = (num << 4) | digit; 425 | } 426 | return num; 427 | }; 428 | 429 | function read_string() { 430 | return with_eof_error("Unterminated string constant", function(){ 431 | var quote = next(), ret = ""; 432 | for (;;) { 433 | var ch = next(true); 434 | if (ch == "\\") ch = read_escaped_char(); 435 | else if (ch == quote) break; 436 | ret += ch; 437 | } 438 | return token("string", ret); 439 | }); 440 | }; 441 | 442 | function read_line_comment() { 443 | next(); 444 | var i = find("\n"), ret; 445 | if (i == -1) { 446 | ret = S.text.substr(S.pos); 447 | S.pos = S.text.length; 448 | } else { 449 | ret = S.text.substring(S.pos, i); 450 | S.pos = i; 451 | } 452 | return token("comment1", ret, true); 453 | }; 454 | 455 | function read_multiline_comment() { 456 | next(); 457 | return with_eof_error("Unterminated multiline comment", function(){ 458 | var i = find("*/", true), 459 | text = S.text.substring(S.pos, i), 460 | tok = token("comment2", text, true); 461 | S.pos = i + 2; 462 | S.line += text.split("\n").length - 1; 463 | S.newline_before = text.indexOf("\n") >= 0; 464 | 465 | // https://github.com/mishoo/UglifyJS/issues/#issue/100 466 | if (/^@cc_on/i.test(text)) { 467 | warn("WARNING: at line " + S.line); 468 | warn("*** Found \"conditional comment\": " + text); 469 | warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer."); 470 | } 471 | 472 | return tok; 473 | }); 474 | }; 475 | 476 | function read_name() { 477 | var backslash = false, name = "", ch; 478 | while ((ch = peek()) != null) { 479 | if (!backslash) { 480 | if (ch == "\\") backslash = true, next(); 481 | else if (is_identifier_char(ch)) name += next(); 482 | else break; 483 | } 484 | else { 485 | if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); 486 | ch = read_escaped_char(); 487 | if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); 488 | name += ch; 489 | backslash = false; 490 | } 491 | } 492 | return name; 493 | }; 494 | 495 | function read_regexp() { 496 | return with_eof_error("Unterminated regular expression", function(){ 497 | var prev_backslash = false, regexp = "", ch, in_class = false; 498 | while ((ch = next(true))) if (prev_backslash) { 499 | regexp += "\\" + ch; 500 | prev_backslash = false; 501 | } else if (ch == "[") { 502 | in_class = true; 503 | regexp += ch; 504 | } else if (ch == "]" && in_class) { 505 | in_class = false; 506 | regexp += ch; 507 | } else if (ch == "/" && !in_class) { 508 | break; 509 | } else if (ch == "\\") { 510 | prev_backslash = true; 511 | } else { 512 | regexp += ch; 513 | } 514 | var mods = read_name(); 515 | return token("regexp", [ regexp, mods ]); 516 | }); 517 | }; 518 | 519 | function read_operator(prefix) { 520 | function grow(op) { 521 | if (!peek()) return op; 522 | var bigger = op + peek(); 523 | if (HOP(OPERATORS, bigger)) { 524 | next(); 525 | return grow(bigger); 526 | } else { 527 | return op; 528 | } 529 | }; 530 | return token("operator", grow(prefix || next())); 531 | }; 532 | 533 | function handle_slash() { 534 | next(); 535 | var regex_allowed = S.regex_allowed; 536 | switch (peek()) { 537 | case "/": 538 | S.comments_before.push(read_line_comment()); 539 | S.regex_allowed = regex_allowed; 540 | return next_token(); 541 | case "*": 542 | S.comments_before.push(read_multiline_comment()); 543 | S.regex_allowed = regex_allowed; 544 | return next_token(); 545 | } 546 | return S.regex_allowed ? read_regexp() : read_operator("/"); 547 | }; 548 | 549 | function handle_dot() { 550 | next(); 551 | return is_digit(peek()) 552 | ? read_num(".") 553 | : token("punc", "."); 554 | }; 555 | 556 | function read_word() { 557 | var word = read_name(); 558 | return !HOP(KEYWORDS, word) 559 | ? token("name", word) 560 | : HOP(OPERATORS, word) 561 | ? token("operator", word) 562 | : HOP(KEYWORDS_ATOM, word) 563 | ? token("atom", word) 564 | : token("keyword", word); 565 | }; 566 | 567 | function with_eof_error(eof_error, cont) { 568 | try { 569 | return cont(); 570 | } catch(ex) { 571 | if (ex === EX_EOF) parse_error(eof_error); 572 | else throw ex; 573 | } 574 | }; 575 | 576 | function next_token(force_regexp) { 577 | if (force_regexp) 578 | return read_regexp(); 579 | skip_whitespace(); 580 | start_token(); 581 | var ch = peek(); 582 | if (!ch) return token("eof"); 583 | if (is_digit(ch)) return read_num(); 584 | if (ch == '"' || ch == "'") return read_string(); 585 | if (HOP(PUNC_CHARS, ch)) return token("punc", next()); 586 | if (ch == ".") return handle_dot(); 587 | if (ch == "/") return handle_slash(); 588 | if (HOP(OPERATOR_CHARS, ch)) return read_operator(); 589 | if (ch == "\\" || is_identifier_start(ch)) return read_word(); 590 | parse_error("Unexpected character '" + ch + "'"); 591 | }; 592 | 593 | next_token.context = function(nc) { 594 | if (nc) S = nc; 595 | return S; 596 | }; 597 | 598 | return next_token; 599 | 600 | }; 601 | 602 | /* -----[ Parser (constants) ]----- */ 603 | 604 | var UNARY_PREFIX = array_to_hash([ 605 | "typeof", 606 | "void", 607 | "delete", 608 | "--", 609 | "++", 610 | "!", 611 | "~", 612 | "-", 613 | "+" 614 | ]); 615 | 616 | var UNARY_POSTFIX = array_to_hash([ "--", "++" ]); 617 | 618 | var ASSIGNMENT = (function(a, ret, i){ 619 | while (i < a.length) { 620 | ret[a[i]] = a[i].substr(0, a[i].length - 1); 621 | i++; 622 | } 623 | return ret; 624 | })( 625 | ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="], 626 | { "=": true }, 627 | 0 628 | ); 629 | 630 | var PRECEDENCE = (function(a, ret){ 631 | for (var i = 0, n = 1; i < a.length; ++i, ++n) { 632 | var b = a[i]; 633 | for (var j = 0; j < b.length; ++j) { 634 | ret[b[j]] = n; 635 | } 636 | } 637 | return ret; 638 | })( 639 | [ 640 | ["||"], 641 | ["&&"], 642 | ["|"], 643 | ["^"], 644 | ["&"], 645 | ["==", "===", "!=", "!=="], 646 | ["<", ">", "<=", ">=", "in", "instanceof"], 647 | [">>", "<<", ">>>"], 648 | ["+", "-"], 649 | ["*", "/", "%"] 650 | ], 651 | {} 652 | ); 653 | 654 | var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); 655 | 656 | var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); 657 | 658 | /* -----[ Parser ]----- */ 659 | 660 | function NodeWithToken(str, start, end) { 661 | this.name = str; 662 | this.start = start; 663 | this.end = end; 664 | }; 665 | 666 | NodeWithToken.prototype.toString = function() { return this.name; }; 667 | 668 | function parse($TEXT, exigent_mode, embed_tokens) { 669 | 670 | var S = { 671 | input : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT, 672 | token : null, 673 | prev : null, 674 | peeked : null, 675 | in_function : 0, 676 | in_loop : 0, 677 | labels : [] 678 | }; 679 | 680 | S.token = next(); 681 | 682 | function is(type, value) { 683 | return is_token(S.token, type, value); 684 | }; 685 | 686 | function peek() { return S.peeked || (S.peeked = S.input()); }; 687 | 688 | function next() { 689 | S.prev = S.token; 690 | if (S.peeked) { 691 | S.token = S.peeked; 692 | S.peeked = null; 693 | } else { 694 | S.token = S.input(); 695 | } 696 | return S.token; 697 | }; 698 | 699 | function prev() { 700 | return S.prev; 701 | }; 702 | 703 | function croak(msg, line, col, pos) { 704 | var ctx = S.input.context(); 705 | js_error(msg, 706 | line != null ? line : ctx.tokline, 707 | col != null ? col : ctx.tokcol, 708 | pos != null ? pos : ctx.tokpos); 709 | }; 710 | 711 | function token_error(token, msg) { 712 | croak(msg, token.line, token.col); 713 | }; 714 | 715 | function unexpected(token) { 716 | if (token == null) 717 | token = S.token; 718 | token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")"); 719 | }; 720 | 721 | function expect_token(type, val) { 722 | if (is(type, val)) { 723 | return next(); 724 | } 725 | token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type); 726 | }; 727 | 728 | function expect(punc) { return expect_token("punc", punc); }; 729 | 730 | function can_insert_semicolon() { 731 | return !exigent_mode && ( 732 | S.token.nlb || is("eof") || is("punc", "}") 733 | ); 734 | }; 735 | 736 | function semicolon() { 737 | if (is("punc", ";")) next(); 738 | else if (!can_insert_semicolon()) unexpected(); 739 | }; 740 | 741 | function as() { 742 | return slice(arguments); 743 | }; 744 | 745 | function parenthesised() { 746 | expect("("); 747 | var ex = expression(); 748 | expect(")"); 749 | return ex; 750 | }; 751 | 752 | function add_tokens(str, start, end) { 753 | return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end); 754 | }; 755 | 756 | var statement = embed_tokens ? function() { 757 | var start = S.token; 758 | var ast = $statement.apply(this, arguments); 759 | ast[0] = add_tokens(ast[0], start, prev()); 760 | return ast; 761 | } : $statement; 762 | 763 | function $statement() { 764 | if (is("operator", "/")) { 765 | S.peeked = null; 766 | S.token = S.input(true); // force regexp 767 | } 768 | switch (S.token.type) { 769 | case "num": 770 | case "string": 771 | case "regexp": 772 | case "operator": 773 | case "atom": 774 | return simple_statement(); 775 | 776 | case "name": 777 | return is_token(peek(), "punc", ":") 778 | ? labeled_statement(prog1(S.token.value, next, next)) 779 | : simple_statement(); 780 | 781 | case "punc": 782 | switch (S.token.value) { 783 | case "{": 784 | return as("block", block_()); 785 | case "[": 786 | case "(": 787 | return simple_statement(); 788 | case ";": 789 | next(); 790 | return as("block"); 791 | default: 792 | unexpected(); 793 | } 794 | 795 | case "keyword": 796 | switch (prog1(S.token.value, next)) { 797 | case "break": 798 | return break_cont("break"); 799 | 800 | case "continue": 801 | return break_cont("continue"); 802 | 803 | case "debugger": 804 | semicolon(); 805 | return as("debugger"); 806 | 807 | case "do": 808 | return (function(body){ 809 | expect_token("keyword", "while"); 810 | return as("do", prog1(parenthesised, semicolon), body); 811 | })(in_loop(statement)); 812 | 813 | case "for": 814 | return for_(); 815 | 816 | case "function": 817 | return function_(true); 818 | 819 | case "if": 820 | return if_(); 821 | 822 | case "return": 823 | if (S.in_function == 0) 824 | croak("'return' outside of function"); 825 | return as("return", 826 | is("punc", ";") 827 | ? (next(), null) 828 | : can_insert_semicolon() 829 | ? null 830 | : prog1(expression, semicolon)); 831 | 832 | case "switch": 833 | return as("switch", parenthesised(), switch_block_()); 834 | 835 | case "throw": 836 | return as("throw", prog1(expression, semicolon)); 837 | 838 | case "try": 839 | return try_(); 840 | 841 | case "var": 842 | return prog1(var_, semicolon); 843 | 844 | case "const": 845 | return prog1(const_, semicolon); 846 | 847 | case "while": 848 | return as("while", parenthesised(), in_loop(statement)); 849 | 850 | case "with": 851 | return as("with", parenthesised(), statement()); 852 | 853 | default: 854 | unexpected(); 855 | } 856 | } 857 | }; 858 | 859 | function labeled_statement(label) { 860 | S.labels.push(label); 861 | var start = S.token, stat = statement(); 862 | if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0])) 863 | unexpected(start); 864 | S.labels.pop(); 865 | return as("label", label, stat); 866 | }; 867 | 868 | function simple_statement() { 869 | return as("stat", prog1(expression, semicolon)); 870 | }; 871 | 872 | function break_cont(type) { 873 | var name = is("name") ? S.token.value : null; 874 | if (name != null) { 875 | next(); 876 | if (!member(name, S.labels)) 877 | croak("Label " + name + " without matching loop or statement"); 878 | } 879 | else if (S.in_loop == 0) 880 | croak(type + " not inside a loop or switch"); 881 | semicolon(); 882 | return as(type, name); 883 | }; 884 | 885 | function for_() { 886 | expect("("); 887 | var init = null; 888 | if (!is("punc", ";")) { 889 | init = is("keyword", "var") 890 | ? (next(), var_(true)) 891 | : expression(true, true); 892 | if (is("operator", "in")) 893 | return for_in(init); 894 | } 895 | return regular_for(init); 896 | }; 897 | 898 | function regular_for(init) { 899 | expect(";"); 900 | var test = is("punc", ";") ? null : expression(); 901 | expect(";"); 902 | var step = is("punc", ")") ? null : expression(); 903 | expect(")"); 904 | return as("for", init, test, step, in_loop(statement)); 905 | }; 906 | 907 | function for_in(init) { 908 | var lhs = init[0] == "var" ? as("name", init[1][0]) : init; 909 | next(); 910 | var obj = expression(); 911 | expect(")"); 912 | return as("for-in", init, lhs, obj, in_loop(statement)); 913 | }; 914 | 915 | var function_ = embed_tokens ? function() { 916 | var start = prev(); 917 | var ast = $function_.apply(this, arguments); 918 | ast[0] = add_tokens(ast[0], start, prev()); 919 | return ast; 920 | } : $function_; 921 | 922 | function $function_(in_statement) { 923 | var name = is("name") ? prog1(S.token.value, next) : null; 924 | if (in_statement && !name) 925 | unexpected(); 926 | expect("("); 927 | return as(in_statement ? "defun" : "function", 928 | name, 929 | // arguments 930 | (function(first, a){ 931 | while (!is("punc", ")")) { 932 | if (first) first = false; else expect(","); 933 | if (!is("name")) unexpected(); 934 | a.push(S.token.value); 935 | next(); 936 | } 937 | next(); 938 | return a; 939 | })(true, []), 940 | // body 941 | (function(){ 942 | ++S.in_function; 943 | var loop = S.in_loop; 944 | S.in_loop = 0; 945 | var a = block_(); 946 | --S.in_function; 947 | S.in_loop = loop; 948 | return a; 949 | })()); 950 | }; 951 | 952 | function if_() { 953 | var cond = parenthesised(), body = statement(), belse; 954 | if (is("keyword", "else")) { 955 | next(); 956 | belse = statement(); 957 | } 958 | return as("if", cond, body, belse); 959 | }; 960 | 961 | function block_() { 962 | expect("{"); 963 | var a = []; 964 | while (!is("punc", "}")) { 965 | if (is("eof")) unexpected(); 966 | a.push(statement()); 967 | } 968 | next(); 969 | return a; 970 | }; 971 | 972 | var switch_block_ = curry(in_loop, function(){ 973 | expect("{"); 974 | var a = [], cur = null; 975 | while (!is("punc", "}")) { 976 | if (is("eof")) unexpected(); 977 | if (is("keyword", "case")) { 978 | next(); 979 | cur = []; 980 | a.push([ expression(), cur ]); 981 | expect(":"); 982 | } 983 | else if (is("keyword", "default")) { 984 | next(); 985 | expect(":"); 986 | cur = []; 987 | a.push([ null, cur ]); 988 | } 989 | else { 990 | if (!cur) unexpected(); 991 | cur.push(statement()); 992 | } 993 | } 994 | next(); 995 | return a; 996 | }); 997 | 998 | function try_() { 999 | var body = block_(), bcatch, bfinally; 1000 | if (is("keyword", "catch")) { 1001 | next(); 1002 | expect("("); 1003 | if (!is("name")) 1004 | croak("Name expected"); 1005 | var name = S.token.value; 1006 | next(); 1007 | expect(")"); 1008 | bcatch = [ name, block_() ]; 1009 | } 1010 | if (is("keyword", "finally")) { 1011 | next(); 1012 | bfinally = block_(); 1013 | } 1014 | if (!bcatch && !bfinally) 1015 | croak("Missing catch/finally blocks"); 1016 | return as("try", body, bcatch, bfinally); 1017 | }; 1018 | 1019 | function vardefs(no_in) { 1020 | var a = []; 1021 | for (;;) { 1022 | if (!is("name")) 1023 | unexpected(); 1024 | var name = S.token.value; 1025 | next(); 1026 | if (is("operator", "=")) { 1027 | next(); 1028 | a.push([ name, expression(false, no_in) ]); 1029 | } else { 1030 | a.push([ name ]); 1031 | } 1032 | if (!is("punc", ",")) 1033 | break; 1034 | next(); 1035 | } 1036 | return a; 1037 | }; 1038 | 1039 | function var_(no_in) { 1040 | return as("var", vardefs(no_in)); 1041 | }; 1042 | 1043 | function const_() { 1044 | return as("const", vardefs()); 1045 | }; 1046 | 1047 | function new_() { 1048 | var newexp = expr_atom(false), args; 1049 | if (is("punc", "(")) { 1050 | next(); 1051 | args = expr_list(")"); 1052 | } else { 1053 | args = []; 1054 | } 1055 | return subscripts(as("new", newexp, args), true); 1056 | }; 1057 | 1058 | function expr_atom(allow_calls) { 1059 | if (is("operator", "new")) { 1060 | next(); 1061 | return new_(); 1062 | } 1063 | if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) { 1064 | return make_unary("unary-prefix", 1065 | prog1(S.token.value, next), 1066 | expr_atom(allow_calls)); 1067 | } 1068 | if (is("punc")) { 1069 | switch (S.token.value) { 1070 | case "(": 1071 | next(); 1072 | return subscripts(prog1(expression, curry(expect, ")")), allow_calls); 1073 | case "[": 1074 | next(); 1075 | return subscripts(array_(), allow_calls); 1076 | case "{": 1077 | next(); 1078 | return subscripts(object_(), allow_calls); 1079 | } 1080 | unexpected(); 1081 | } 1082 | if (is("keyword", "function")) { 1083 | next(); 1084 | return subscripts(function_(false), allow_calls); 1085 | } 1086 | if (HOP(ATOMIC_START_TOKEN, S.token.type)) { 1087 | var atom = S.token.type == "regexp" 1088 | ? as("regexp", S.token.value[0], S.token.value[1]) 1089 | : as(S.token.type, S.token.value); 1090 | return subscripts(prog1(atom, next), allow_calls); 1091 | } 1092 | unexpected(); 1093 | }; 1094 | 1095 | function expr_list(closing, allow_trailing_comma, allow_empty) { 1096 | var first = true, a = []; 1097 | while (!is("punc", closing)) { 1098 | if (first) first = false; else expect(","); 1099 | if (allow_trailing_comma && is("punc", closing)) break; 1100 | if (is("punc", ",") && allow_empty) { 1101 | a.push([ "atom", "undefined" ]); 1102 | } else { 1103 | a.push(expression(false)); 1104 | } 1105 | } 1106 | next(); 1107 | return a; 1108 | }; 1109 | 1110 | function array_() { 1111 | return as("array", expr_list("]", !exigent_mode, true)); 1112 | }; 1113 | 1114 | function object_() { 1115 | var first = true, a = []; 1116 | while (!is("punc", "}")) { 1117 | if (first) first = false; else expect(","); 1118 | if (!exigent_mode && is("punc", "}")) 1119 | // allow trailing comma 1120 | break; 1121 | var type = S.token.type; 1122 | var name = as_property_name(); 1123 | if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) { 1124 | a.push([ as_name(), function_(false), name ]); 1125 | } else { 1126 | expect(":"); 1127 | a.push([ name, expression(false) ]); 1128 | } 1129 | } 1130 | next(); 1131 | return as("object", a); 1132 | }; 1133 | 1134 | function as_property_name() { 1135 | switch (S.token.type) { 1136 | case "num": 1137 | case "string": 1138 | return prog1(S.token.value, next); 1139 | } 1140 | return as_name(); 1141 | }; 1142 | 1143 | function as_name() { 1144 | switch (S.token.type) { 1145 | case "name": 1146 | case "operator": 1147 | case "keyword": 1148 | case "atom": 1149 | return prog1(S.token.value, next); 1150 | default: 1151 | unexpected(); 1152 | } 1153 | }; 1154 | 1155 | function subscripts(expr, allow_calls) { 1156 | if (is("punc", ".")) { 1157 | next(); 1158 | return subscripts(as("dot", expr, as_name()), allow_calls); 1159 | } 1160 | if (is("punc", "[")) { 1161 | next(); 1162 | return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls); 1163 | } 1164 | if (allow_calls && is("punc", "(")) { 1165 | next(); 1166 | return subscripts(as("call", expr, expr_list(")")), true); 1167 | } 1168 | if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) { 1169 | return prog1(curry(make_unary, "unary-postfix", S.token.value, expr), 1170 | next); 1171 | } 1172 | return expr; 1173 | }; 1174 | 1175 | function make_unary(tag, op, expr) { 1176 | if ((op == "++" || op == "--") && !is_assignable(expr)) 1177 | croak("Invalid use of " + op + " operator"); 1178 | return as(tag, op, expr); 1179 | }; 1180 | 1181 | function expr_op(left, min_prec, no_in) { 1182 | var op = is("operator") ? S.token.value : null; 1183 | if (op && op == "in" && no_in) op = null; 1184 | var prec = op != null ? PRECEDENCE[op] : null; 1185 | if (prec != null && prec > min_prec) { 1186 | next(); 1187 | var right = expr_op(expr_atom(true), prec, no_in); 1188 | return expr_op(as("binary", op, left, right), min_prec, no_in); 1189 | } 1190 | return left; 1191 | }; 1192 | 1193 | function expr_ops(no_in) { 1194 | return expr_op(expr_atom(true), 0, no_in); 1195 | }; 1196 | 1197 | function maybe_conditional(no_in) { 1198 | var expr = expr_ops(no_in); 1199 | if (is("operator", "?")) { 1200 | next(); 1201 | var yes = expression(false); 1202 | expect(":"); 1203 | return as("conditional", expr, yes, expression(false, no_in)); 1204 | } 1205 | return expr; 1206 | }; 1207 | 1208 | function is_assignable(expr) { 1209 | if (!exigent_mode) return true; 1210 | switch (expr[0]) { 1211 | case "dot": 1212 | case "sub": 1213 | case "new": 1214 | case "call": 1215 | return true; 1216 | case "name": 1217 | return expr[1] != "this"; 1218 | } 1219 | }; 1220 | 1221 | function maybe_assign(no_in) { 1222 | var left = maybe_conditional(no_in), val = S.token.value; 1223 | if (is("operator") && HOP(ASSIGNMENT, val)) { 1224 | if (is_assignable(left)) { 1225 | next(); 1226 | return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in)); 1227 | } 1228 | croak("Invalid assignment"); 1229 | } 1230 | return left; 1231 | }; 1232 | 1233 | function expression(commas, no_in) { 1234 | if (arguments.length == 0) 1235 | commas = true; 1236 | var expr = maybe_assign(no_in); 1237 | if (commas && is("punc", ",")) { 1238 | next(); 1239 | return as("seq", expr, expression(true, no_in)); 1240 | } 1241 | return expr; 1242 | }; 1243 | 1244 | function in_loop(cont) { 1245 | try { 1246 | ++S.in_loop; 1247 | return cont(); 1248 | } finally { 1249 | --S.in_loop; 1250 | } 1251 | }; 1252 | 1253 | return as("toplevel", (function(a){ 1254 | while (!is("eof")) 1255 | a.push(statement()); 1256 | return a; 1257 | })([])); 1258 | 1259 | }; 1260 | 1261 | /* -----[ Utilities ]----- */ 1262 | 1263 | function curry(f) { 1264 | var args = slice(arguments, 1); 1265 | return function() { return f.apply(this, args.concat(slice(arguments))); }; 1266 | }; 1267 | 1268 | function prog1(ret) { 1269 | if (ret instanceof Function) 1270 | ret = ret(); 1271 | for (var i = 1, n = arguments.length; --n > 0; ++i) 1272 | arguments[i](); 1273 | return ret; 1274 | }; 1275 | 1276 | function array_to_hash(a) { 1277 | var ret = {}; 1278 | for (var i = 0; i < a.length; ++i) 1279 | ret[a[i]] = true; 1280 | return ret; 1281 | }; 1282 | 1283 | function slice(a, start) { 1284 | return Array.prototype.slice.call(a, start == null ? 0 : start); 1285 | }; 1286 | 1287 | function characters(str) { 1288 | return str.split(""); 1289 | }; 1290 | 1291 | function member(name, array) { 1292 | for (var i = array.length; --i >= 0;) 1293 | if (array[i] === name) 1294 | return true; 1295 | return false; 1296 | }; 1297 | 1298 | function HOP(obj, prop) { 1299 | return Object.prototype.hasOwnProperty.call(obj, prop); 1300 | }; 1301 | 1302 | var warn = function() {}; 1303 | 1304 | /* -----[ Exports ]----- */ 1305 | /* 1306 | exports.tokenizer = tokenizer; 1307 | exports.parse = parse; 1308 | exports.slice = slice; 1309 | exports.curry = curry; 1310 | exports.member = member; 1311 | exports.array_to_hash = array_to_hash; 1312 | exports.PRECEDENCE = PRECEDENCE; 1313 | exports.KEYWORDS_ATOM = KEYWORDS_ATOM; 1314 | exports.RESERVED_WORDS = RESERVED_WORDS; 1315 | exports.KEYWORDS = KEYWORDS; 1316 | exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; 1317 | exports.OPERATORS = OPERATORS; 1318 | exports.is_alphanumeric_char = is_alphanumeric_char; 1319 | exports.set_logger = function(logger) { 1320 | warn = logger; 1321 | }; 1322 | */ 1323 | var jsp = new Object; 1324 | jsp.tokenizer = tokenizer; 1325 | jsp.parse = parse; 1326 | jsp.slice = slice; 1327 | jsp.curry = curry; 1328 | jsp.member = member; 1329 | jsp.array_to_hash = array_to_hash; 1330 | jsp.PRECEDENCE = PRECEDENCE; 1331 | jsp.KEYWORDS_ATOM = KEYWORDS_ATOM; 1332 | jsp.RESERVED_WORDS = RESERVED_WORDS; 1333 | jsp.KEYWORDS = KEYWORDS; 1334 | jsp.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; 1335 | jsp.OPERATORS = OPERATORS; 1336 | jsp.is_alphanumeric_char = is_alphanumeric_char; 1337 | jsp.set_logger = function(logger) { 1338 | warn = logger; 1339 | }; -------------------------------------------------------------------------------- /src/uglify/javascript/process.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | 5 | This version is suitable for Node.js. With minimal changes (the 6 | exports stuff) it should work on any JS platform. 7 | 8 | This file implements some AST processors. They work on data built 9 | by parse-js. 10 | 11 | Exported functions: 12 | 13 | - ast_mangle(ast, options) -- mangles the variable/function names 14 | in the AST. Returns an AST. 15 | 16 | - ast_squeeze(ast) -- employs various optimizations to make the 17 | final generated code even smaller. Returns an AST. 18 | 19 | - gen_code(ast, options) -- generates JS code from the AST. Pass 20 | true (or an object, see the code for some options) as second 21 | argument to get "pretty" (indented) code. 22 | 23 | -------------------------------- (C) --------------------------------- 24 | 25 | Author: Mihai Bazon 26 | 27 | http://mihai.bazon.net/blog 28 | 29 | Distributed under the BSD license: 30 | 31 | Copyright 2010 (c) Mihai Bazon 32 | 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions 35 | are met: 36 | 37 | * Redistributions of source code must retain the above 38 | copyright notice, this list of conditions and the following 39 | disclaimer. 40 | 41 | * Redistributions in binary form must reproduce the above 42 | copyright notice, this list of conditions and the following 43 | disclaimer in the documentation and/or other materials 44 | provided with the distribution. 45 | 46 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 47 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 48 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 49 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 50 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 51 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 52 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 53 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 54 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 55 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 56 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 57 | SUCH DAMAGE. 58 | 59 | ***********************************************************************/ 60 | 61 | var slice = jsp.slice, 62 | member = jsp.member, 63 | PRECEDENCE = jsp.PRECEDENCE, 64 | OPERATORS = jsp.OPERATORS; 65 | 66 | 67 | /* -----[ helper for AST traversal ]----- */ 68 | 69 | function ast_walker(ast) { 70 | function _vardefs(defs) { 71 | return [ this[0], MAP(defs, function(def){ 72 | var a = [ def[0] ]; 73 | if (def.length > 1) 74 | a[1] = walk(def[1]); 75 | return a; 76 | }) ]; 77 | }; 78 | var walkers = { 79 | "string": function(str) { 80 | return [ this[0], str ]; 81 | }, 82 | "num": function(num) { 83 | return [ this[0], num ]; 84 | }, 85 | "name": function(name) { 86 | return [ this[0], name ]; 87 | }, 88 | "toplevel": function(statements) { 89 | return [ this[0], MAP(statements, walk) ]; 90 | }, 91 | "block": function(statements) { 92 | var out = [ this[0] ]; 93 | if (statements != null) 94 | out.push(MAP(statements, walk)); 95 | return out; 96 | }, 97 | "var": _vardefs, 98 | "const": _vardefs, 99 | "try": function(t, c, f) { 100 | return [ 101 | this[0], 102 | MAP(t, walk), 103 | c != null ? [ c[0], MAP(c[1], walk) ] : null, 104 | f != null ? MAP(f, walk) : null 105 | ]; 106 | }, 107 | "throw": function(expr) { 108 | return [ this[0], walk(expr) ]; 109 | }, 110 | "new": function(ctor, args) { 111 | return [ this[0], walk(ctor), MAP(args, walk) ]; 112 | }, 113 | "switch": function(expr, body) { 114 | return [ this[0], walk(expr), MAP(body, function(branch){ 115 | return [ branch[0] ? walk(branch[0]) : null, 116 | MAP(branch[1], walk) ]; 117 | }) ]; 118 | }, 119 | "break": function(label) { 120 | return [ this[0], label ]; 121 | }, 122 | "continue": function(label) { 123 | return [ this[0], label ]; 124 | }, 125 | "conditional": function(cond, t, e) { 126 | return [ this[0], walk(cond), walk(t), walk(e) ]; 127 | }, 128 | "assign": function(op, lvalue, rvalue) { 129 | return [ this[0], op, walk(lvalue), walk(rvalue) ]; 130 | }, 131 | "dot": function(expr) { 132 | return [ this[0], walk(expr) ].concat(slice(arguments, 1)); 133 | }, 134 | "call": function(expr, args) { 135 | return [ this[0], walk(expr), MAP(args, walk) ]; 136 | }, 137 | "function": function(name, args, body) { 138 | return [ this[0], name, args.slice(), MAP(body, walk) ]; 139 | }, 140 | "defun": function(name, args, body) { 141 | return [ this[0], name, args.slice(), MAP(body, walk) ]; 142 | }, 143 | "if": function(conditional, t, e) { 144 | return [ this[0], walk(conditional), walk(t), walk(e) ]; 145 | }, 146 | "for": function(init, cond, step, block) { 147 | return [ this[0], walk(init), walk(cond), walk(step), walk(block) ]; 148 | }, 149 | "for-in": function(vvar, key, hash, block) { 150 | return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ]; 151 | }, 152 | "while": function(cond, block) { 153 | return [ this[0], walk(cond), walk(block) ]; 154 | }, 155 | "do": function(cond, block) { 156 | return [ this[0], walk(cond), walk(block) ]; 157 | }, 158 | "return": function(expr) { 159 | return [ this[0], walk(expr) ]; 160 | }, 161 | "binary": function(op, left, right) { 162 | return [ this[0], op, walk(left), walk(right) ]; 163 | }, 164 | "unary-prefix": function(op, expr) { 165 | return [ this[0], op, walk(expr) ]; 166 | }, 167 | "unary-postfix": function(op, expr) { 168 | return [ this[0], op, walk(expr) ]; 169 | }, 170 | "sub": function(expr, subscript) { 171 | return [ this[0], walk(expr), walk(subscript) ]; 172 | }, 173 | "object": function(props) { 174 | return [ this[0], MAP(props, function(p){ 175 | return p.length == 2 176 | ? [ p[0], walk(p[1]) ] 177 | : [ p[0], walk(p[1]), p[2] ]; // get/set-ter 178 | }) ]; 179 | }, 180 | "regexp": function(rx, mods) { 181 | return [ this[0], rx, mods ]; 182 | }, 183 | "array": function(elements) { 184 | return [ this[0], MAP(elements, walk) ]; 185 | }, 186 | "stat": function(stat) { 187 | return [ this[0], walk(stat) ]; 188 | }, 189 | "seq": function() { 190 | return [ this[0] ].concat(MAP(slice(arguments), walk)); 191 | }, 192 | "label": function(name, block) { 193 | return [ this[0], name, walk(block) ]; 194 | }, 195 | "with": function(expr, block) { 196 | return [ this[0], walk(expr), walk(block) ]; 197 | }, 198 | "atom": function(name) { 199 | return [ this[0], name ]; 200 | } 201 | }; 202 | 203 | var user = {}; 204 | var stack = []; 205 | function walk(ast) { 206 | if (ast == null) 207 | return null; 208 | try { 209 | stack.push(ast); 210 | var type = ast[0]; 211 | var gen = user[type]; 212 | if (gen) { 213 | var ret = gen.apply(ast, ast.slice(1)); 214 | if (ret != null) 215 | return ret; 216 | } 217 | gen = walkers[type]; 218 | return gen.apply(ast, ast.slice(1)); 219 | } finally { 220 | stack.pop(); 221 | } 222 | }; 223 | 224 | function with_walkers(walkers, cont){ 225 | var save = {}, i; 226 | for (i in walkers) if (HOP(walkers, i)) { 227 | save[i] = user[i]; 228 | user[i] = walkers[i]; 229 | } 230 | var ret = cont(); 231 | for (i in save) if (HOP(save, i)) { 232 | if (!save[i]) delete user[i]; 233 | else user[i] = save[i]; 234 | } 235 | return ret; 236 | }; 237 | 238 | return { 239 | walk: walk, 240 | with_walkers: with_walkers, 241 | parent: function() { 242 | return stack[stack.length - 2]; // last one is current node 243 | }, 244 | stack: function() { 245 | return stack; 246 | } 247 | }; 248 | }; 249 | 250 | /* -----[ Scope and mangling ]----- */ 251 | 252 | function Scope(parent) { 253 | this.names = {}; // names defined in this scope 254 | this.mangled = {}; // mangled names (orig.name => mangled) 255 | this.rev_mangled = {}; // reverse lookup (mangled => orig.name) 256 | this.cname = -1; // current mangled name 257 | this.refs = {}; // names referenced from this scope 258 | this.uses_with = false; // will become TRUE if eval() is detected in this or any subscopes 259 | this.uses_eval = false; // will become TRUE if with() is detected in this or any subscopes 260 | this.parent = parent; // parent scope 261 | this.children = []; // sub-scopes 262 | if (parent) { 263 | this.level = parent.level + 1; 264 | parent.children.push(this); 265 | } else { 266 | this.level = 0; 267 | } 268 | }; 269 | 270 | var base54 = (function(){ 271 | var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_"; 272 | return function(num) { 273 | var ret = ""; 274 | do { 275 | ret = DIGITS.charAt(num % 54) + ret; 276 | num = Math.floor(num / 54); 277 | } while (num > 0); 278 | return ret; 279 | }; 280 | })(); 281 | 282 | Scope.prototype = { 283 | has: function(name) { 284 | for (var s = this; s; s = s.parent) 285 | if (HOP(s.names, name)) 286 | return s; 287 | }, 288 | has_mangled: function(mname) { 289 | for (var s = this; s; s = s.parent) 290 | if (HOP(s.rev_mangled, mname)) 291 | return s; 292 | }, 293 | toJSON: function() { 294 | return { 295 | names: this.names, 296 | uses_eval: this.uses_eval, 297 | uses_with: this.uses_with 298 | }; 299 | }, 300 | 301 | next_mangled: function() { 302 | // we must be careful that the new mangled name: 303 | // 304 | // 1. doesn't shadow a mangled name from a parent 305 | // scope, unless we don't reference the original 306 | // name from this scope OR from any sub-scopes! 307 | // This will get slow. 308 | // 309 | // 2. doesn't shadow an original name from a parent 310 | // scope, in the event that the name is not mangled 311 | // in the parent scope and we reference that name 312 | // here OR IN ANY SUBSCOPES! 313 | // 314 | // 3. doesn't shadow a name that is referenced but not 315 | // defined (possibly global defined elsewhere). 316 | for (;;) { 317 | var m = base54(++this.cname), prior; 318 | 319 | // case 1. 320 | prior = this.has_mangled(m); 321 | if (prior && this.refs[prior.rev_mangled[m]] === prior) 322 | continue; 323 | 324 | // case 2. 325 | prior = this.has(m); 326 | if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m)) 327 | continue; 328 | 329 | // case 3. 330 | if (HOP(this.refs, m) && this.refs[m] == null) 331 | continue; 332 | 333 | // I got "do" once. :-/ 334 | if (!is_identifier(m)) 335 | continue; 336 | 337 | return m; 338 | } 339 | }, 340 | get_mangled: function(name, newMangle) { 341 | if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use 342 | var s = this.has(name); 343 | if (!s) return name; // not in visible scope, no mangle 344 | if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope 345 | if (!newMangle) return name; // not found and no mangling requested 346 | 347 | var m = s.next_mangled(); 348 | s.rev_mangled[m] = name; 349 | return s.mangled[name] = m; 350 | }, 351 | define: function(name) { 352 | if (name != null) 353 | return this.names[name] = name; 354 | } 355 | }; 356 | 357 | function ast_add_scope(ast) { 358 | 359 | var current_scope = null; 360 | var w = ast_walker(), walk = w.walk; 361 | var having_eval = []; 362 | 363 | function with_new_scope(cont) { 364 | current_scope = new Scope(current_scope); 365 | var ret = current_scope.body = cont(); 366 | ret.scope = current_scope; 367 | current_scope = current_scope.parent; 368 | return ret; 369 | }; 370 | 371 | function define(name) { 372 | return current_scope.define(name); 373 | }; 374 | 375 | function reference(name) { 376 | current_scope.refs[name] = true; 377 | }; 378 | 379 | function _lambda(name, args, body) { 380 | return [ this[0], define(name), args, with_new_scope(function(){ 381 | MAP(args, define); 382 | return MAP(body, walk); 383 | })]; 384 | }; 385 | 386 | return with_new_scope(function(){ 387 | // process AST 388 | var ret = w.with_walkers({ 389 | "function": _lambda, 390 | "defun": _lambda, 391 | "with": function(expr, block) { 392 | for (var s = current_scope; s; s = s.parent) 393 | s.uses_with = true; 394 | }, 395 | "var": function(defs) { 396 | MAP(defs, function(d){ define(d[0]) }); 397 | }, 398 | "const": function(defs) { 399 | MAP(defs, function(d){ define(d[0]) }); 400 | }, 401 | "try": function(t, c, f) { 402 | if (c != null) return [ 403 | this[0], 404 | MAP(t, walk), 405 | [ define(c[0]), MAP(c[1], walk) ], 406 | f != null ? MAP(f, walk) : null 407 | ]; 408 | }, 409 | "name": function(name) { 410 | if (name == "eval") 411 | having_eval.push(current_scope); 412 | reference(name); 413 | } 414 | }, function(){ 415 | return walk(ast); 416 | }); 417 | 418 | // the reason why we need an additional pass here is 419 | // that names can be used prior to their definition. 420 | 421 | // scopes where eval was detected and their parents 422 | // are marked with uses_eval, unless they define the 423 | // "eval" name. 424 | MAP(having_eval, function(scope){ 425 | if (!scope.has("eval")) while (scope) { 426 | scope.uses_eval = true; 427 | scope = scope.parent; 428 | } 429 | }); 430 | 431 | // for referenced names it might be useful to know 432 | // their origin scope. current_scope here is the 433 | // toplevel one. 434 | function fixrefs(scope, i) { 435 | // do children first; order shouldn't matter 436 | for (i = scope.children.length; --i >= 0;) 437 | fixrefs(scope.children[i]); 438 | for (i in scope.refs) if (HOP(scope.refs, i)) { 439 | // find origin scope and propagate the reference to origin 440 | for (var origin = scope.has(i), s = scope; s; s = s.parent) { 441 | s.refs[i] = origin; 442 | if (s === origin) break; 443 | } 444 | } 445 | }; 446 | fixrefs(current_scope); 447 | 448 | return ret; 449 | }); 450 | 451 | }; 452 | 453 | /* -----[ mangle names ]----- */ 454 | 455 | function ast_mangle(ast, options) { 456 | var w = ast_walker(), walk = w.walk, scope; 457 | options = options || {}; 458 | 459 | function get_mangled(name, newMangle) { 460 | if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel 461 | if (options.except && member(name, options.except)) 462 | return name; 463 | return scope.get_mangled(name, newMangle); 464 | }; 465 | 466 | function _lambda(name, args, body) { 467 | if (name) name = get_mangled(name); 468 | body = with_scope(body.scope, function(){ 469 | args = MAP(args, function(name){ return get_mangled(name) }); 470 | return MAP(body, walk); 471 | }); 472 | return [ this[0], name, args, body ]; 473 | }; 474 | 475 | function with_scope(s, cont) { 476 | var _scope = scope; 477 | scope = s; 478 | for (var i in s.names) if (HOP(s.names, i)) { 479 | get_mangled(i, true); 480 | } 481 | var ret = cont(); 482 | ret.scope = s; 483 | scope = _scope; 484 | return ret; 485 | }; 486 | 487 | function _vardefs(defs) { 488 | return [ this[0], MAP(defs, function(d){ 489 | return [ get_mangled(d[0]), walk(d[1]) ]; 490 | }) ]; 491 | }; 492 | 493 | return w.with_walkers({ 494 | "function": _lambda, 495 | "defun": function() { 496 | // move function declarations to the top when 497 | // they are not in some block. 498 | var ast = _lambda.apply(this, arguments); 499 | switch (w.parent()[0]) { 500 | case "toplevel": 501 | case "function": 502 | case "defun": 503 | return MAP.at_top(ast); 504 | } 505 | return ast; 506 | }, 507 | "var": _vardefs, 508 | "const": _vardefs, 509 | "name": function(name) { 510 | return [ this[0], get_mangled(name) ]; 511 | }, 512 | "try": function(t, c, f) { 513 | return [ this[0], 514 | MAP(t, walk), 515 | c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null, 516 | f != null ? MAP(f, walk) : null ]; 517 | }, 518 | "toplevel": function(body) { 519 | var self = this; 520 | return with_scope(self.scope, function(){ 521 | return [ self[0], MAP(body, walk) ]; 522 | }); 523 | } 524 | }, function() { 525 | return walk(ast_add_scope(ast)); 526 | }); 527 | }; 528 | 529 | /* -----[ 530 | - compress foo["bar"] into foo.bar, 531 | - remove block brackets {} where possible 532 | - join consecutive var declarations 533 | - various optimizations for IFs: 534 | - if (cond) foo(); else bar(); ==> cond?foo():bar(); 535 | - if (cond) foo(); ==> cond&&foo(); 536 | - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); // also for throw 537 | - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()} 538 | ]----- */ 539 | 540 | var warn = function(){}; 541 | 542 | function best_of(ast1, ast2) { 543 | return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1; 544 | }; 545 | 546 | function last_stat(b) { 547 | if (b[0] == "block" && b[1] && b[1].length > 0) 548 | return b[1][b[1].length - 1]; 549 | return b; 550 | } 551 | 552 | function aborts(t) { 553 | if (t) { 554 | t = last_stat(t); 555 | if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw") 556 | return true; 557 | } 558 | }; 559 | 560 | function boolean_expr(expr) { 561 | return ( (expr[0] == "unary-prefix" 562 | && member(expr[1], [ "!", "delete" ])) || 563 | 564 | (expr[0] == "binary" 565 | && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) || 566 | 567 | (expr[0] == "binary" 568 | && member(expr[1], [ "&&", "||" ]) 569 | && boolean_expr(expr[2]) 570 | && boolean_expr(expr[3])) || 571 | 572 | (expr[0] == "conditional" 573 | && boolean_expr(expr[2]) 574 | && boolean_expr(expr[3])) || 575 | 576 | (expr[0] == "assign" 577 | && expr[1] === true 578 | && boolean_expr(expr[3])) || 579 | 580 | (expr[0] == "seq" 581 | && boolean_expr(expr[expr.length - 1])) 582 | ); 583 | }; 584 | 585 | function make_conditional(c, t, e) { 586 | if (c[0] == "unary-prefix" && c[1] == "!") { 587 | return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ]; 588 | } else { 589 | return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ]; 590 | } 591 | }; 592 | 593 | function empty(b) { 594 | return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); 595 | }; 596 | 597 | function is_string(node) { 598 | return (node[0] == "string" || 599 | node[0] == "unary-prefix" && node[1] == "typeof" || 600 | node[0] == "binary" && node[1] == "+" && 601 | (is_string(node[2]) || is_string(node[3]))); 602 | }; 603 | 604 | var when_constant = (function(){ 605 | 606 | var $NOT_CONSTANT = {}; 607 | 608 | // this can only evaluate constant expressions. If it finds anything 609 | // not constant, it throws $NOT_CONSTANT. 610 | function evaluate(expr) { 611 | switch (expr[0]) { 612 | case "string": 613 | case "num": 614 | return expr[1]; 615 | case "name": 616 | case "atom": 617 | switch (expr[1]) { 618 | case "true": return true; 619 | case "false": return false; 620 | } 621 | break; 622 | case "unary-prefix": 623 | switch (expr[1]) { 624 | case "!": return !evaluate(expr[2]); 625 | case "typeof": return typeof evaluate(expr[2]); 626 | case "~": return ~evaluate(expr[2]); 627 | case "-": return -evaluate(expr[2]); 628 | case "+": return +evaluate(expr[2]); 629 | } 630 | break; 631 | case "binary": 632 | var left = expr[2], right = expr[3]; 633 | switch (expr[1]) { 634 | case "&&" : return evaluate(left) && evaluate(right); 635 | case "||" : return evaluate(left) || evaluate(right); 636 | case "|" : return evaluate(left) | evaluate(right); 637 | case "&" : return evaluate(left) & evaluate(right); 638 | case "^" : return evaluate(left) ^ evaluate(right); 639 | case "+" : return evaluate(left) + evaluate(right); 640 | case "*" : return evaluate(left) * evaluate(right); 641 | case "/" : return evaluate(left) / evaluate(right); 642 | case "-" : return evaluate(left) - evaluate(right); 643 | case "<<" : return evaluate(left) << evaluate(right); 644 | case ">>" : return evaluate(left) >> evaluate(right); 645 | case ">>>" : return evaluate(left) >>> evaluate(right); 646 | case "==" : return evaluate(left) == evaluate(right); 647 | case "===" : return evaluate(left) === evaluate(right); 648 | case "!=" : return evaluate(left) != evaluate(right); 649 | case "!==" : return evaluate(left) !== evaluate(right); 650 | case "<" : return evaluate(left) < evaluate(right); 651 | case "<=" : return evaluate(left) <= evaluate(right); 652 | case ">" : return evaluate(left) > evaluate(right); 653 | case ">=" : return evaluate(left) >= evaluate(right); 654 | case "in" : return evaluate(left) in evaluate(right); 655 | case "instanceof" : return evaluate(left) instanceof evaluate(right); 656 | } 657 | } 658 | throw $NOT_CONSTANT; 659 | }; 660 | 661 | return function(expr, yes, no) { 662 | try { 663 | var val = evaluate(expr), ast; 664 | switch (typeof val) { 665 | case "string": ast = [ "string", val ]; break; 666 | case "number": ast = [ "num", val ]; break; 667 | case "boolean": ast = [ "name", String(val) ]; break; 668 | default: throw new Error("Can't handle constant of type: " + (typeof val)); 669 | } 670 | return yes.call(expr, ast, val); 671 | } catch(ex) { 672 | if (ex === $NOT_CONSTANT) { 673 | if (expr[0] == "binary" 674 | && (expr[1] == "===" || expr[1] == "!==") 675 | && ((is_string(expr[2]) && is_string(expr[3])) 676 | || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) { 677 | expr[1] = expr[1].substr(0, 2); 678 | } 679 | return no ? no.call(expr, expr) : null; 680 | } 681 | else throw ex; 682 | } 683 | }; 684 | 685 | })(); 686 | 687 | function warn_unreachable(ast) { 688 | if (!empty(ast)) 689 | warn("Dropping unreachable code: " + gen_code(ast, true)); 690 | }; 691 | 692 | function ast_squeeze(ast, options) { 693 | options = defaults(options, { 694 | make_seqs : true, 695 | dead_code : true, 696 | keep_comps : true, 697 | no_warnings : false 698 | }); 699 | 700 | var w = ast_walker(), walk = w.walk, scope; 701 | 702 | function negate(c) { 703 | var not_c = [ "unary-prefix", "!", c ]; 704 | switch (c[0]) { 705 | case "unary-prefix": 706 | return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c; 707 | case "seq": 708 | c = slice(c); 709 | c[c.length - 1] = negate(c[c.length - 1]); 710 | return c; 711 | case "conditional": 712 | return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]); 713 | case "binary": 714 | var op = c[1], left = c[2], right = c[3]; 715 | if (!options.keep_comps) switch (op) { 716 | case "<=" : return [ "binary", ">", left, right ]; 717 | case "<" : return [ "binary", ">=", left, right ]; 718 | case ">=" : return [ "binary", "<", left, right ]; 719 | case ">" : return [ "binary", "<=", left, right ]; 720 | } 721 | switch (op) { 722 | case "==" : return [ "binary", "!=", left, right ]; 723 | case "!=" : return [ "binary", "==", left, right ]; 724 | case "===" : return [ "binary", "!==", left, right ]; 725 | case "!==" : return [ "binary", "===", left, right ]; 726 | case "&&" : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]); 727 | case "||" : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]); 728 | } 729 | break; 730 | } 731 | return not_c; 732 | }; 733 | 734 | function with_scope(s, cont) { 735 | var _scope = scope; 736 | scope = s; 737 | var ret = cont(); 738 | ret.scope = s; 739 | scope = _scope; 740 | return ret; 741 | }; 742 | 743 | function rmblock(block) { 744 | if (block != null && block[0] == "block" && block[1]) { 745 | if (block[1].length == 1) 746 | block = block[1][0]; 747 | else if (block[1].length == 0) 748 | block = [ "block" ]; 749 | } 750 | return block; 751 | }; 752 | 753 | function _lambda(name, args, body) { 754 | return [ this[0], name, args, with_scope(body.scope, function(){ 755 | return tighten(MAP(body, walk), "lambda"); 756 | }) ]; 757 | }; 758 | 759 | // we get here for blocks that have been already transformed. 760 | // this function does a few things: 761 | // 1. discard useless blocks 762 | // 2. join consecutive var declarations 763 | // 3. remove obviously dead code 764 | // 4. transform consecutive statements using the comma operator 765 | // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... } 766 | function tighten(statements, block_type) { 767 | statements = statements.reduce(function(a, stat){ 768 | 769 | if (stat[0] == "block") { 770 | if (stat[1]) { 771 | a.push.apply(a, stat[1]); 772 | } 773 | } else { 774 | 775 | a.push(stat); 776 | } 777 | return a; 778 | }, []); 779 | 780 | statements = (function(a, prev){ 781 | statements.forEach(function(cur){ 782 | if (prev && ((cur[0] == "var" && prev[0] == "var") || 783 | (cur[0] == "const" && prev[0] == "const"))) { 784 | prev[1] = prev[1].concat(cur[1]); 785 | } else { 786 | a.push(cur); 787 | prev = cur; 788 | } 789 | }); 790 | return a; 791 | })([]); 792 | 793 | if (options.dead_code) statements = (function(a, has_quit){ 794 | statements.forEach(function(st){ 795 | if (has_quit) { 796 | if (member(st[0], [ "function", "defun" , "var", "const" ])) { 797 | a.push(st); 798 | } 799 | else if (!options.no_warnings) 800 | warn_unreachable(st); 801 | } 802 | else { 803 | a.push(st); 804 | if (member(st[0], [ "return", "throw", "break", "continue" ])) 805 | has_quit = true; 806 | } 807 | }); 808 | return a; 809 | })([]); 810 | 811 | if (options.make_seqs) statements = (function(a, prev) { 812 | statements.forEach(function(cur){ 813 | if (prev && prev[0] == "stat" && cur[0] == "stat") { 814 | prev[1] = [ "seq", prev[1], cur[1] ]; 815 | } else { 816 | a.push(cur); 817 | prev = cur; 818 | } 819 | }); 820 | return a; 821 | })([]); 822 | 823 | if (block_type == "lambda") statements = (function(i, a, stat){ 824 | while (i < statements.length) { 825 | stat = statements[i++]; 826 | if (stat[0] == "if" && !stat[3]) { 827 | if (stat[2][0] == "return" && stat[2][1] == null) { 828 | a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ])); 829 | break; 830 | } 831 | var last = last_stat(stat[2]); 832 | if (last[0] == "return" && last[1] == null) { 833 | a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ])); 834 | break; 835 | } 836 | } 837 | a.push(stat); 838 | } 839 | return a; 840 | })(0, []); 841 | 842 | return statements; 843 | }; 844 | 845 | function make_if(c, t, e) { 846 | return when_constant(c, function(ast, val){ 847 | if (val) { 848 | warn_unreachable(e); 849 | return t; 850 | } else { 851 | warn_unreachable(t); 852 | return e; 853 | } 854 | }, function() { 855 | return make_real_if(c, t, e); 856 | }); 857 | }; 858 | 859 | function make_real_if(c, t, e) { 860 | c = walk(c); 861 | t = walk(t); 862 | e = walk(e); 863 | 864 | if (empty(t)) { 865 | c = negate(c); 866 | t = e; 867 | e = null; 868 | } else if (empty(e)) { 869 | e = null; 870 | } else { 871 | // if we have both else and then, maybe it makes sense to switch them? 872 | (function(){ 873 | var a = gen_code(c); 874 | var n = negate(c); 875 | var b = gen_code(n); 876 | if (b.length < a.length) { 877 | var tmp = t; 878 | t = e; 879 | e = tmp; 880 | c = n; 881 | } 882 | })(); 883 | } 884 | if (empty(e) && empty(t)) 885 | return [ "stat", c ]; 886 | var ret = [ "if", c, t, e ]; 887 | if (t[0] == "if" && empty(t[3]) && empty(e)) { 888 | ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ])); 889 | } 890 | else if (t[0] == "stat") { 891 | if (e) { 892 | if (e[0] == "stat") { 893 | ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]); 894 | } 895 | } 896 | else { 897 | ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]); 898 | } 899 | } 900 | else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) { 901 | ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]); 902 | } 903 | else if (e && aborts(t)) { 904 | ret = [ [ "if", c, t ] ]; 905 | if (e[0] == "block") { 906 | if (e[1]) ret = ret.concat(e[1]); 907 | } 908 | else { 909 | ret.push(e); 910 | } 911 | ret = walk([ "block", ret ]); 912 | } 913 | else if (t && aborts(e)) { 914 | ret = [ [ "if", negate(c), e ] ]; 915 | if (t[0] == "block") { 916 | if (t[1]) ret = ret.concat(t[1]); 917 | } else { 918 | ret.push(t); 919 | } 920 | ret = walk([ "block", ret ]); 921 | } 922 | return ret; 923 | }; 924 | 925 | function _do_while(cond, body) { 926 | return when_constant(cond, function(cond, val){ 927 | if (!val) { 928 | warn_unreachable(body); 929 | return [ "block" ]; 930 | } else { 931 | return [ "for", null, null, null, walk(body) ]; 932 | } 933 | }); 934 | }; 935 | 936 | return w.with_walkers({ 937 | "sub": function(expr, subscript) { 938 | if (subscript[0] == "string") { 939 | var name = subscript[1]; 940 | if (is_identifier(name)) 941 | return [ "dot", walk(expr), name ]; 942 | else if (/^[1-9][0-9]*$/.test(name) || name === "0") 943 | return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ]; 944 | } 945 | }, 946 | "if": make_if, 947 | "toplevel": function(body) { 948 | return [ "toplevel", with_scope(this.scope, function(){ 949 | return tighten(MAP(body, walk)); 950 | }) ]; 951 | }, 952 | "switch": function(expr, body) { 953 | var last = body.length - 1; 954 | return [ "switch", walk(expr), MAP(body, function(branch, i){ 955 | var block = tighten(MAP(branch[1], walk)); 956 | if (i == last && block.length > 0) { 957 | var node = block[block.length - 1]; 958 | if (node[0] == "break" && !node[1]) 959 | block.pop(); 960 | } 961 | return [ branch[0] ? walk(branch[0]) : null, block ]; 962 | }) ]; 963 | }, 964 | "function": function() { 965 | var ret = _lambda.apply(this, arguments); 966 | if (ret[1] && !HOP(scope.refs, ret[1])) { 967 | ret[1] = null; 968 | } 969 | return ret; 970 | }, 971 | "defun": _lambda, 972 | "block": function(body) { 973 | if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]); 974 | }, 975 | "binary": function(op, left, right) { 976 | return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){ 977 | return best_of(walk(c), this); 978 | }, function no() { 979 | return this; 980 | }); 981 | }, 982 | "conditional": function(c, t, e) { 983 | return make_conditional(walk(c), walk(t), walk(e)); 984 | }, 985 | "try": function(t, c, f) { 986 | return [ 987 | "try", 988 | tighten(MAP(t, walk)), 989 | c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null, 990 | f != null ? tighten(MAP(f, walk)) : null 991 | ]; 992 | }, 993 | "unary-prefix": function(op, expr) { 994 | expr = walk(expr); 995 | var ret = [ "unary-prefix", op, expr ]; 996 | if (op == "!") 997 | ret = best_of(ret, negate(expr)); 998 | return when_constant(ret, function(ast, val){ 999 | return walk(ast); // it's either true or false, so minifies to !0 or !1 1000 | }, function() { return ret }); 1001 | }, 1002 | "name": function(name) { 1003 | switch (name) { 1004 | case "true": return [ "unary-prefix", "!", [ "num", 0 ]]; 1005 | case "false": return [ "unary-prefix", "!", [ "num", 1 ]]; 1006 | } 1007 | }, 1008 | "new": function(ctor, args) { 1009 | if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) { 1010 | if (args.length != 1) { 1011 | return [ "array", args ]; 1012 | } else { 1013 | return [ "call", [ "name", "Array" ], args ]; 1014 | } 1015 | } 1016 | }, 1017 | "call": function(expr, args) { 1018 | if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) { 1019 | return [ "array", args ]; 1020 | } 1021 | }, 1022 | "while": _do_while, 1023 | "do": _do_while 1024 | }, function() { 1025 | return walk(ast_add_scope(ast)); 1026 | }); 1027 | }; 1028 | 1029 | /* -----[ re-generate code from the AST ]----- */ 1030 | 1031 | var DOT_CALL_NO_PARENS = jsp.array_to_hash([ 1032 | "name", 1033 | "array", 1034 | "string", 1035 | "dot", 1036 | "sub", 1037 | "call", 1038 | "regexp" 1039 | ]); 1040 | 1041 | function make_string(str, ascii_only) { 1042 | var dq = 0, sq = 0; 1043 | str = str.replace(/[\\\b\f\n\r\t\x22\x27]/g, function(s){ 1044 | switch (s) { 1045 | case "\\": return "\\\\"; 1046 | case "\b": return "\\b"; 1047 | case "\f": return "\\f"; 1048 | case "\n": return "\\n"; 1049 | case "\r": return "\\r"; 1050 | case "\t": return "\\t"; 1051 | case '"': ++dq; return '"'; 1052 | case "'": ++sq; return "'"; 1053 | } 1054 | return s; 1055 | }); 1056 | if (ascii_only) str = to_ascii(str); 1057 | if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'"; 1058 | else return '"' + str.replace(/\x22/g, '\\"') + '"'; 1059 | }; 1060 | 1061 | function to_ascii(str) { 1062 | return str.replace(/[\u0080-\uffff]/g, function(ch) { 1063 | var code = ch.charCodeAt(0).toString(16); 1064 | while (code.length < 4) code = "0" + code; 1065 | return "\\u" + code; 1066 | }); 1067 | }; 1068 | 1069 | function gen_code(ast, options) { 1070 | options = defaults(options, { 1071 | indent_start : 0, 1072 | indent_level : 4, 1073 | quote_keys : false, 1074 | space_colon : false, 1075 | beautify : false, 1076 | ascii_only : false 1077 | }); 1078 | var beautify = !!options.beautify; 1079 | var indentation = 0, 1080 | newline = beautify ? "\n" : "", 1081 | space = beautify ? " " : ""; 1082 | 1083 | function encode_string(str) { 1084 | return make_string(str, options.ascii_only); 1085 | }; 1086 | 1087 | function make_name(name) { 1088 | name = name.toString(); 1089 | if (options.ascii_only) 1090 | name = to_ascii(name); 1091 | return name; 1092 | }; 1093 | 1094 | function indent(line) { 1095 | if (line == null) 1096 | line = ""; 1097 | if (beautify) 1098 | line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line; 1099 | return line; 1100 | }; 1101 | 1102 | function with_indent(cont, incr) { 1103 | if (incr == null) incr = 1; 1104 | indentation += incr; 1105 | try { return cont.apply(null, slice(arguments, 1)); } 1106 | finally { indentation -= incr; } 1107 | }; 1108 | 1109 | function add_spaces(a) { 1110 | if (beautify) 1111 | return a.join(" "); 1112 | var b = []; 1113 | for (var i = 0; i < a.length; ++i) { 1114 | var next = a[i + 1]; 1115 | b.push(a[i]); 1116 | if (next && 1117 | ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) || 1118 | (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) { 1119 | b.push(" "); 1120 | } 1121 | } 1122 | return b.join(""); 1123 | }; 1124 | 1125 | function add_commas(a) { 1126 | return a.join("," + space); 1127 | }; 1128 | 1129 | function parenthesize(expr) { 1130 | var gen = make(expr); 1131 | for (var i = 1; i < arguments.length; ++i) { 1132 | var el = arguments[i]; 1133 | if ((el instanceof Function && el(expr)) || expr[0] == el) 1134 | return "(" + gen + ")"; 1135 | } 1136 | return gen; 1137 | }; 1138 | 1139 | function best_of(a) { 1140 | if (a.length == 1) { 1141 | return a[0]; 1142 | } 1143 | if (a.length == 2) { 1144 | var b = a[1]; 1145 | a = a[0]; 1146 | return a.length <= b.length ? a : b; 1147 | } 1148 | return best_of([ a[0], best_of(a.slice(1)) ]); 1149 | }; 1150 | 1151 | function needs_parens(expr) { 1152 | if (expr[0] == "function") { 1153 | // dot/call on a literal function requires the 1154 | // function literal itself to be parenthesized 1155 | // only if it's the first "thing" in a 1156 | // statement. This means that the parent is 1157 | // "stat", but it could also be a "seq" and 1158 | // we're the first in this "seq" and the 1159 | // parent is "stat", and so on. Messy stuff, 1160 | // but it worths the trouble. 1161 | var a = slice($stack), self = a.pop(), p = a.pop(); 1162 | while (p) { 1163 | if (p[0] == "stat") return true; 1164 | if ((p[0] == "seq" && p[1] === self) || 1165 | (p[0] == "call" && p[1] === self) || 1166 | (p[0] == "binary" && p[2] === self)) { 1167 | self = p; 1168 | p = a.pop(); 1169 | } else { 1170 | return false; 1171 | } 1172 | } 1173 | } 1174 | return !HOP(DOT_CALL_NO_PARENS, expr[0]); 1175 | }; 1176 | 1177 | function make_num(num) { 1178 | var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m; 1179 | if (Math.floor(num) === num) { 1180 | a.push("0x" + num.toString(16).toLowerCase(), // probably pointless 1181 | "0" + num.toString(8)); // same. 1182 | if ((m = /^(.*?)(0+)$/.exec(num))) { 1183 | a.push(m[1] + "e" + m[2].length); 1184 | } 1185 | } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) { 1186 | a.push(m[2] + "e-" + (m[1].length + m[2].length), 1187 | str.substr(str.indexOf("."))); 1188 | } 1189 | return best_of(a); 1190 | }; 1191 | 1192 | var generators = { 1193 | "string": encode_string, 1194 | "num": make_num, 1195 | "name": make_name, 1196 | "toplevel": function(statements) { 1197 | return make_block_statements(statements) 1198 | .join(newline + newline); 1199 | }, 1200 | "block": make_block, 1201 | "var": function(defs) { 1202 | return "var " + add_commas(MAP(defs, make_1vardef)) + ";"; 1203 | }, 1204 | "const": function(defs) { 1205 | return "const " + add_commas(MAP(defs, make_1vardef)) + ";"; 1206 | }, 1207 | "try": function(tr, ca, fi) { 1208 | var out = [ "try", make_block(tr) ]; 1209 | if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1])); 1210 | if (fi) out.push("finally", make_block(fi)); 1211 | return add_spaces(out); 1212 | }, 1213 | "throw": function(expr) { 1214 | return add_spaces([ "throw", make(expr) ]) + ";"; 1215 | }, 1216 | "new": function(ctor, args) { 1217 | args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : ""; 1218 | return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){ 1219 | var w = ast_walker(), has_call = {}; 1220 | try { 1221 | w.with_walkers({ 1222 | "call": function() { throw has_call }, 1223 | "function": function() { return this } 1224 | }, function(){ 1225 | w.walk(expr); 1226 | }); 1227 | } catch(ex) { 1228 | if (ex === has_call) 1229 | return true; 1230 | throw ex; 1231 | } 1232 | }) + args ]); 1233 | }, 1234 | "switch": function(expr, body) { 1235 | return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]); 1236 | }, 1237 | "break": function(label) { 1238 | var out = "break"; 1239 | if (label != null) 1240 | out += " " + make_name(label); 1241 | return out + ";"; 1242 | }, 1243 | "continue": function(label) { 1244 | var out = "continue"; 1245 | if (label != null) 1246 | out += " " + make_name(label); 1247 | return out + ";"; 1248 | }, 1249 | "conditional": function(co, th, el) { 1250 | return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?", 1251 | parenthesize(th, "seq"), ":", 1252 | parenthesize(el, "seq") ]); 1253 | }, 1254 | "assign": function(op, lvalue, rvalue) { 1255 | if (op && op !== true) op += "="; 1256 | else op = "="; 1257 | return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]); 1258 | }, 1259 | "dot": function(expr) { 1260 | var out = make(expr), i = 1; 1261 | if (expr[0] == "num") { 1262 | if (!/\./.test(expr[1])) 1263 | out += "."; 1264 | } else if (needs_parens(expr)) 1265 | out = "(" + out + ")"; 1266 | while (i < arguments.length) 1267 | out += "." + make_name(arguments[i++]); 1268 | return out; 1269 | }, 1270 | "call": function(func, args) { 1271 | var f = make(func); 1272 | if (needs_parens(func)) 1273 | f = "(" + f + ")"; 1274 | return f + "(" + add_commas(MAP(args, function(expr){ 1275 | return parenthesize(expr, "seq"); 1276 | })) + ")"; 1277 | }, 1278 | "function": make_function, 1279 | "defun": make_function, 1280 | "if": function(co, th, el) { 1281 | var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ]; 1282 | if (el) { 1283 | out.push("else", make(el)); 1284 | } 1285 | return add_spaces(out); 1286 | }, 1287 | "for": function(init, cond, step, block) { 1288 | var out = [ "for" ]; 1289 | init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space); 1290 | cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space); 1291 | step = (step != null ? make(step) : "").replace(/;*\s*$/, ""); 1292 | var args = init + cond + step; 1293 | if (args == "; ; ") args = ";;"; 1294 | out.push("(" + args + ")", make(block)); 1295 | return add_spaces(out); 1296 | }, 1297 | "for-in": function(vvar, key, hash, block) { 1298 | return add_spaces([ "for", "(" + 1299 | (vvar ? make(vvar).replace(/;+$/, "") : make(key)), 1300 | "in", 1301 | make(hash) + ")", make(block) ]); 1302 | }, 1303 | "while": function(condition, block) { 1304 | return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]); 1305 | }, 1306 | "do": function(condition, block) { 1307 | return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";"; 1308 | }, 1309 | "return": function(expr) { 1310 | var out = [ "return" ]; 1311 | if (expr != null) out.push(make(expr)); 1312 | return add_spaces(out) + ";"; 1313 | }, 1314 | "binary": function(operator, lvalue, rvalue) { 1315 | var left = make(lvalue), right = make(rvalue); 1316 | // XXX: I'm pretty sure other cases will bite here. 1317 | // we need to be smarter. 1318 | // adding parens all the time is the safest bet. 1319 | if (member(lvalue[0], [ "assign", "conditional", "seq" ]) || 1320 | lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) { 1321 | left = "(" + left + ")"; 1322 | } 1323 | if (member(rvalue[0], [ "assign", "conditional", "seq" ]) || 1324 | rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] && 1325 | !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) { 1326 | right = "(" + right + ")"; 1327 | } 1328 | return add_spaces([ left, operator, right ]); 1329 | }, 1330 | "unary-prefix": function(operator, expr) { 1331 | var val = make(expr); 1332 | if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr))) 1333 | val = "(" + val + ")"; 1334 | return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val; 1335 | }, 1336 | "unary-postfix": function(operator, expr) { 1337 | var val = make(expr); 1338 | if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr))) 1339 | val = "(" + val + ")"; 1340 | return val + operator; 1341 | }, 1342 | "sub": function(expr, subscript) { 1343 | var hash = make(expr); 1344 | if (needs_parens(expr)) 1345 | hash = "(" + hash + ")"; 1346 | return hash + "[" + make(subscript) + "]"; 1347 | }, 1348 | "object": function(props) { 1349 | if (props.length == 0) 1350 | return "{}"; 1351 | return "{" + newline + with_indent(function(){ 1352 | return MAP(props, function(p){ 1353 | if (p.length == 3) { 1354 | // getter/setter. The name is in p[0], the arg.list in p[1][2], the 1355 | // body in p[1][3] and type ("get" / "set") in p[2]. 1356 | return indent(make_function(p[0], p[1][2], p[1][3], p[2])); 1357 | } 1358 | var key = p[0], val = make(p[1]); 1359 | if (options.quote_keys) { 1360 | key = encode_string(key); 1361 | } else if ((typeof key == "number" || !beautify && +key + "" == key) 1362 | && parseFloat(key) >= 0) { 1363 | key = make_num(+key); 1364 | } else if (!is_identifier(key)) { 1365 | key = encode_string(key); 1366 | } 1367 | return indent(add_spaces(beautify && options.space_colon 1368 | ? [ key, ":", val ] 1369 | : [ key + ":", val ])); 1370 | }).join("," + newline); 1371 | }) + newline + indent("}"); 1372 | }, 1373 | "regexp": function(rx, mods) { 1374 | return "/" + rx + "/" + mods; 1375 | }, 1376 | "array": function(elements) { 1377 | if (elements.length == 0) return "[]"; 1378 | return add_spaces([ "[", add_commas(MAP(elements, function(el){ 1379 | if (!beautify && el[0] == "atom" && el[1] == "undefined") return ""; 1380 | return parenthesize(el, "seq"); 1381 | })), "]" ]); 1382 | }, 1383 | "stat": function(stmt) { 1384 | return make(stmt).replace(/;*\s*$/, ";"); 1385 | }, 1386 | "seq": function() { 1387 | return add_commas(MAP(slice(arguments), make)); 1388 | }, 1389 | "label": function(name, block) { 1390 | return add_spaces([ make_name(name), ":", make(block) ]); 1391 | }, 1392 | "with": function(expr, block) { 1393 | return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]); 1394 | }, 1395 | "atom": function(name) { 1396 | return make_name(name); 1397 | } 1398 | }; 1399 | 1400 | // The squeezer replaces "block"-s that contain only a single 1401 | // statement with the statement itself; technically, the AST 1402 | // is correct, but this can create problems when we output an 1403 | // IF having an ELSE clause where the THEN clause ends in an 1404 | // IF *without* an ELSE block (then the outer ELSE would refer 1405 | // to the inner IF). This function checks for this case and 1406 | // adds the block brackets if needed. 1407 | function make_then(th) { 1408 | if (th[0] == "do") { 1409 | // https://github.com/mishoo/UglifyJS/issues/#issue/57 1410 | // IE croaks with "syntax error" on code like this: 1411 | // if (foo) do ... while(cond); else ... 1412 | // we need block brackets around do/while 1413 | return make([ "block", [ th ]]); 1414 | } 1415 | var b = th; 1416 | while (true) { 1417 | var type = b[0]; 1418 | if (type == "if") { 1419 | if (!b[3]) 1420 | // no else, we must add the block 1421 | return make([ "block", [ th ]]); 1422 | b = b[3]; 1423 | } 1424 | else if (type == "while" || type == "do") b = b[2]; 1425 | else if (type == "for" || type == "for-in") b = b[4]; 1426 | else break; 1427 | } 1428 | return make(th); 1429 | }; 1430 | 1431 | function make_function(name, args, body, keyword) { 1432 | var out = keyword || "function"; 1433 | if (name) { 1434 | out += " " + make_name(name); 1435 | } 1436 | out += "(" + add_commas(MAP(args, make_name)) + ")"; 1437 | return add_spaces([ out, make_block(body) ]); 1438 | }; 1439 | 1440 | function make_block_statements(statements) { 1441 | for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) { 1442 | var stat = statements[i]; 1443 | var code = make(stat); 1444 | if (code != ";") { 1445 | if (!beautify && i == last) { 1446 | if ((stat[0] == "while" && empty(stat[2])) || 1447 | (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) || 1448 | (stat[0] == "if" && empty(stat[2]) && !stat[3]) || 1449 | (stat[0] == "if" && stat[3] && empty(stat[3]))) { 1450 | code = code.replace(/;*\s*$/, ";"); 1451 | } else { 1452 | code = code.replace(/;+\s*$/, ""); 1453 | } 1454 | } 1455 | a.push(code); 1456 | } 1457 | } 1458 | return MAP(a, indent); 1459 | }; 1460 | 1461 | function make_switch_block(body) { 1462 | var n = body.length; 1463 | if (n == 0) return "{}"; 1464 | return "{" + newline + MAP(body, function(branch, i){ 1465 | var has_body = branch[1].length > 0, code = with_indent(function(){ 1466 | return indent(branch[0] 1467 | ? add_spaces([ "case", make(branch[0]) + ":" ]) 1468 | : "default:"); 1469 | }, 0.5) + (has_body ? newline + with_indent(function(){ 1470 | return make_block_statements(branch[1]).join(newline); 1471 | }) : ""); 1472 | if (!beautify && has_body && i < n - 1) 1473 | code += ";"; 1474 | return code; 1475 | }).join(newline) + newline + indent("}"); 1476 | }; 1477 | 1478 | function make_block(statements) { 1479 | if (!statements) return ";"; 1480 | if (statements.length == 0) return "{}"; 1481 | return "{" + newline + with_indent(function(){ 1482 | return make_block_statements(statements).join(newline); 1483 | }) + newline + indent("}"); 1484 | }; 1485 | 1486 | function make_1vardef(def) { 1487 | var name = def[0], val = def[1]; 1488 | if (val != null) 1489 | name = add_spaces([ make_name(name), "=", make(val) ]); 1490 | return name; 1491 | }; 1492 | 1493 | var $stack = []; 1494 | 1495 | function make(node) { 1496 | var type = node[0]; 1497 | var gen = generators[type]; 1498 | if (!gen) 1499 | throw new Error("Can't find generator for \"" + type + "\""); 1500 | $stack.push(node); 1501 | var ret = gen.apply(type, node.slice(1)); 1502 | $stack.pop(); 1503 | return ret; 1504 | }; 1505 | 1506 | return make(ast); 1507 | }; 1508 | 1509 | function split_lines(code, max_line_length) { 1510 | var splits = [ 0 ]; 1511 | jsp.parse(function(){ 1512 | var next_token = jsp.tokenizer(code); 1513 | var last_split = 0; 1514 | var prev_token; 1515 | function current_length(tok) { 1516 | return tok.pos - last_split; 1517 | }; 1518 | function split_here(tok) { 1519 | last_split = tok.pos; 1520 | splits.push(last_split); 1521 | }; 1522 | function custom(){ 1523 | var tok = next_token.apply(this, arguments); 1524 | out: { 1525 | if (prev_token) { 1526 | if (prev_token.type == "keyword") break out; 1527 | } 1528 | if (current_length(tok) > max_line_length) { 1529 | switch (tok.type) { 1530 | case "keyword": 1531 | case "atom": 1532 | case "name": 1533 | case "punc": 1534 | split_here(tok); 1535 | break out; 1536 | } 1537 | } 1538 | } 1539 | prev_token = tok; 1540 | return tok; 1541 | }; 1542 | custom.context = function() { 1543 | return next_token.context.apply(this, arguments); 1544 | }; 1545 | return custom; 1546 | }()); 1547 | return splits.map(function(pos, i){ 1548 | return code.substring(pos, splits[i + 1] || code.length); 1549 | }).join("\n"); 1550 | }; 1551 | 1552 | /* -----[ Utilities ]----- */ 1553 | 1554 | function repeat_string(str, i) { 1555 | if (i <= 0) return ""; 1556 | if (i == 1) return str; 1557 | var d = repeat_string(str, i >> 1); 1558 | d += d; 1559 | if (i & 1) d += str; 1560 | return d; 1561 | }; 1562 | 1563 | function defaults(args, defs) { 1564 | var ret = {}; 1565 | if (args === true) 1566 | args = {}; 1567 | for (var i in defs) if (HOP(defs, i)) { 1568 | ret[i] = (args && HOP(args, i)) ? args[i] : defs[i]; 1569 | } 1570 | return ret; 1571 | }; 1572 | 1573 | function is_identifier(name) { 1574 | return /^[a-z_$][a-z0-9_$]*$/i.test(name) 1575 | && name != "this" 1576 | && !HOP(jsp.KEYWORDS_ATOM, name) 1577 | && !HOP(jsp.RESERVED_WORDS, name) 1578 | && !HOP(jsp.KEYWORDS, name); 1579 | }; 1580 | 1581 | function HOP(obj, prop) { 1582 | return Object.prototype.hasOwnProperty.call(obj, prop); 1583 | }; 1584 | 1585 | // some utilities 1586 | 1587 | var MAP; 1588 | 1589 | (function(){ 1590 | MAP = function(a, f, o) { 1591 | var ret = []; 1592 | for (var i = 0; i < a.length; ++i) { 1593 | var val = f.call(o, a[i], i); 1594 | if (val instanceof AtTop) ret.unshift(val.v); 1595 | else ret.push(val); 1596 | } 1597 | return ret; 1598 | }; 1599 | MAP.at_top = function(val) { return new AtTop(val) }; 1600 | function AtTop(val) { this.v = val }; 1601 | })(); 1602 | 1603 | /* -----[ Exports ]----- */ 1604 | var pro = new Object; 1605 | pro.ast_walker = ast_walker; 1606 | pro.ast_mangle = ast_mangle; 1607 | pro.ast_squeeze = ast_squeeze; 1608 | pro.gen_code = gen_code; 1609 | pro.ast_add_scope = ast_add_scope; 1610 | pro.set_logger = function(logger) { warn = logger }; 1611 | pro.make_string = make_string; 1612 | pro.split_lines = split_lines; 1613 | pro.MAP = MAP; 1614 | 1615 | // keep this last! 1616 | var slice = jsp.slice, 1617 | member = jsp.member, 1618 | PRECEDENCE = jsp.PRECEDENCE, 1619 | OPERATORS = jsp.OPERATORS; 1620 | 1621 | pro.ast_squeeze_more = function(ast) { 1622 | var w = pro.ast_walker(), walk = w.walk; 1623 | return w.with_walkers({ 1624 | "call": function(expr, args) { 1625 | if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) { 1626 | // foo.toString() ==> foo+"" 1627 | return [ "binary", "+", expr[1], [ "string", "" ]]; 1628 | } 1629 | } 1630 | }, function() { 1631 | return walk(ast); 1632 | }); 1633 | }; --------------------------------------------------------------------------------