├── test.php ├── test.js ├── readme.txt ├── UglifyJS ├── squeeze-more.js ├── uglifyjs.js ├── parse-js.js └── process.js ├── uglifyjs.php ├── utility-classes.php ├── parse-js.php ├── javascript-tokenizer.php ├── javascript-parser.php └── license.txt /test.php: -------------------------------------------------------------------------------- 1 | get_tokens()); 12 | 13 | /* End of file test.php */ 14 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a comment 3 | */ 4 | 5 | (function(window, undefined) { 6 | 7 | // Here is a comment 8 | var myObj = { 9 | str: 'A string', 10 | num: 12345, 11 | hex: 0x2f, 12 | oct: 01234, 13 | func: function() { 14 | console.log('hi'); 15 | }, 16 | regex: /myRegexPattern/i 17 | }; 18 | 19 | }(window)); 20 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | =================================== 2 | UglifyJS.php 3 | Author: James Brumond 4 | =================================== 5 | 6 | A PHP port of the UglifyJS (https://github.com/mishoo/UglifyJS) software; 7 | originally based on the parse-js (http://marijn.haverbeke.nl/parse-js/). 8 | 9 | =========================================================================== 10 | 11 | NOTICE: 12 | This project was never completed nor do I intend to continue work on it. I will, 13 | though, leave this code here for anyone who wishes to see it. At this time, it 14 | is *not* functional and, last I checked, was leaking memory (pretty badly). 15 | -------------------------------------------------------------------------------- /UglifyJS/squeeze-more.js: -------------------------------------------------------------------------------- 1 | var jsp = require("./parse-js"), 2 | pro = require("./process"), 3 | slice = jsp.slice, 4 | member = jsp.member, 5 | PRECEDENCE = jsp.PRECEDENCE, 6 | OPERATORS = jsp.OPERATORS; 7 | 8 | function ast_squeeze_more(ast) { 9 | var w = pro.ast_walker(), walk = w.walk; 10 | return w.with_walkers({ 11 | "call": function(expr, args) { 12 | if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) { 13 | // foo.toString() ==> foo+"" 14 | return [ "binary", "+", expr[1], [ "string", "" ]]; 15 | } 16 | } 17 | }, function() { 18 | return walk(ast); 19 | }); 20 | }; 21 | 22 | exports.ast_squeeze_more = ast_squeeze_more; 23 | -------------------------------------------------------------------------------- /uglifyjs.php: -------------------------------------------------------------------------------- 1 | false, 7 | 'mangle' => true, 8 | 'mangle_toplevel' => false, 9 | 'squeeze' => true, 10 | 'make_seqs' => true, 11 | 'dead_code' => true, 12 | 'verbose' => false, 13 | 'show_copyright' => true, 14 | 'out_same_file' => false, 15 | 'max_line_length' => 32768, // 32 x 1024 16 | 'unsafe' => false, 17 | 'reserved_names' => null, 18 | 'codegen_options' => array( 19 | 'ascii_only' => false, 20 | 'beautify' => false, 21 | 'indent_level' => 4, 22 | 'indent_start' => 0, 23 | 'quote_keys' => false, 24 | 'space_colon' => false 25 | ), 26 | 'output' => true 27 | ); 28 | 29 | public static function run($args) { 30 | foreach ($args as $arg) { 31 | 32 | } 33 | } 34 | 35 | } 36 | 37 | // ---------------------------------------------------------------------------- 38 | // If not running as an include, auto-execute 39 | 40 | if (! debug_backtrace()) { 41 | $args = ($argv) ? $argv : $_SERVER['argv']; 42 | array_shift($args); 43 | UglifyJS::run($args); 44 | } 45 | 46 | /* End of file uglifyjs.php */ 47 | -------------------------------------------------------------------------------- /utility-classes.php: -------------------------------------------------------------------------------- 1 | type = $type; 37 | $this->value = $value; 38 | $this->line = $line; 39 | $this->col = $col; 40 | $this->pos = $pos; 41 | $this->nlb = $nlb; 42 | } 43 | } 44 | 45 | // ---------------------------------------------------------------------------- 46 | // NodeWithToken class 47 | 48 | class NodeWithToken { 49 | public $name = null; 50 | public $start = null; 51 | public $end = null; 52 | public function __construct($name, $start, $end) { 53 | $this->name = $name; 54 | $this->start = $start; 55 | $this->end = $end; 56 | } 57 | public function __toString() { 58 | return $this->name; 59 | } 60 | } 61 | 62 | // ---------------------------------------------------------------------------- 63 | // EOF exception class 64 | 65 | class JS_EOF extends Exception { } 66 | 67 | // ---------------------------------------------------------------------------- 68 | // JavaScript parse error class 69 | 70 | class JS_Parse_Error extends Exception { 71 | 72 | public $js_message = null; 73 | public $js_line = null; 74 | public $js_col = null; 75 | public $js_pos = null; 76 | 77 | public function __construct($msg, $line, $col, $pos) { 78 | $this->js_message = $msg; 79 | $this->js_line = $line; 80 | $this->js_col = $col; 81 | $this->js_pos = $pos; 82 | parent::__construct($this->as_string(false), E_USER_WARNING); 83 | } 84 | 85 | public function __toString() { 86 | return $this->as_string(true); 87 | } 88 | 89 | public function as_string($with_stack = false) { 90 | $ret = $this->js_message.' (line: '.$this->js_line.', col: '.$this->js_col.', pos: '.$this->js_pos.')'; 91 | if ($with_stack) { 92 | $ret .= "\n\n".$this->getTraceAsString(); 93 | } 94 | return $ret; 95 | } 96 | 97 | } 98 | 99 | /* End of file utility-classes.php */ 100 | -------------------------------------------------------------------------------- /UglifyJS/uglifyjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // -*- js2 -*- 3 | 4 | global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util"); 5 | var fs = require("fs"); 6 | var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js 7 | jsp = uglify.parser, 8 | pro = uglify.uglify; 9 | 10 | var options = { 11 | ast: false, 12 | mangle: true, 13 | mangle_toplevel: false, 14 | squeeze: true, 15 | make_seqs: true, 16 | dead_code: true, 17 | verbose: false, 18 | show_copyright: true, 19 | out_same_file: false, 20 | max_line_length: 32 * 1024, 21 | unsafe: false, 22 | reserved_names: null, 23 | codegen_options: { 24 | ascii_only: false, 25 | beautify: false, 26 | indent_level: 4, 27 | indent_start: 0, 28 | quote_keys: false, 29 | space_colon: false 30 | }, 31 | output: true // stdout 32 | }; 33 | 34 | var args = jsp.slice(process.argv, 2); 35 | var filename; 36 | 37 | out: while (args.length > 0) { 38 | var v = args.shift(); 39 | switch (v) { 40 | case "-b": 41 | case "--beautify": 42 | options.codegen_options.beautify = true; 43 | break; 44 | case "-i": 45 | case "--indent": 46 | options.codegen_options.indent_level = args.shift(); 47 | break; 48 | case "-q": 49 | case "--quote-keys": 50 | options.codegen_options.quote_keys = true; 51 | break; 52 | case "-mt": 53 | case "--mangle-toplevel": 54 | options.mangle_toplevel = true; 55 | break; 56 | case "--no-mangle": 57 | case "-nm": 58 | options.mangle = false; 59 | break; 60 | case "--no-squeeze": 61 | case "-ns": 62 | options.squeeze = false; 63 | break; 64 | case "--no-seqs": 65 | options.make_seqs = false; 66 | break; 67 | case "--no-dead-code": 68 | options.dead_code = false; 69 | break; 70 | case "--no-copyright": 71 | case "-nc": 72 | options.show_copyright = false; 73 | break; 74 | case "-o": 75 | case "--output": 76 | options.output = args.shift(); 77 | break; 78 | case "--overwrite": 79 | options.out_same_file = true; 80 | break; 81 | case "-v": 82 | case "--verbose": 83 | options.verbose = true; 84 | break; 85 | case "--ast": 86 | options.ast = true; 87 | break; 88 | case "--unsafe": 89 | options.unsafe = true; 90 | break; 91 | case "--max-line-len": 92 | options.max_line_length = parseInt(args.shift(), 10); 93 | break; 94 | case "--reserved-names": 95 | options.reserved_names = args.shift().split(","); 96 | break; 97 | case "--ascii": 98 | options.codegen_options.ascii_only = true; 99 | break; 100 | default: 101 | filename = v; 102 | break out; 103 | } 104 | } 105 | 106 | if (options.verbose) { 107 | pro.set_logger(function(msg){ 108 | sys.debug(msg); 109 | }); 110 | } 111 | 112 | jsp.set_logger(function(msg){ 113 | sys.debug(msg); 114 | }); 115 | 116 | if (filename) { 117 | fs.readFile(filename, "utf8", function(err, text){ 118 | if (err) throw err; 119 | output(squeeze_it(text)); 120 | }); 121 | } else { 122 | var stdin = process.openStdin(); 123 | stdin.setEncoding("utf8"); 124 | var text = ""; 125 | stdin.on("data", function(chunk){ 126 | text += chunk; 127 | }); 128 | stdin.on("end", function() { 129 | output(squeeze_it(text)); 130 | }); 131 | } 132 | 133 | function output(text) { 134 | var out; 135 | if (options.out_same_file && filename) 136 | options.output = filename; 137 | if (options.output === true) { 138 | out = process.stdout; 139 | } else { 140 | out = fs.createWriteStream(options.output, { 141 | flags: "w", 142 | encoding: "utf8", 143 | mode: 0644 144 | }); 145 | } 146 | out.write(text); 147 | if (options.output !== true) { 148 | out.end(); 149 | } 150 | }; 151 | 152 | // --------- main ends here. 153 | 154 | function show_copyright(comments) { 155 | var ret = ""; 156 | for (var i = 0; i < comments.length; ++i) { 157 | var c = comments[i]; 158 | if (c.type == "comment1") { 159 | ret += "//" + c.value + "\n"; 160 | } else { 161 | ret += "/*" + c.value + "*/"; 162 | } 163 | } 164 | return ret; 165 | }; 166 | 167 | function squeeze_it(code) { 168 | var result = ""; 169 | if (options.show_copyright) { 170 | var tok = jsp.tokenizer(code), c; 171 | c = tok(); 172 | result += show_copyright(c.comments_before); 173 | } 174 | try { 175 | var ast = time_it("parse", function(){ return jsp.parse(code); }); 176 | if (options.mangle) ast = time_it("mangle", function(){ 177 | return pro.ast_mangle(ast, { 178 | toplevel: options.mangle_toplevel, 179 | except: options.reserved_names 180 | }); 181 | }); 182 | if (options.squeeze) ast = time_it("squeeze", function(){ 183 | ast = pro.ast_squeeze(ast, { 184 | make_seqs : options.make_seqs, 185 | dead_code : options.dead_code, 186 | keep_comps : !options.unsafe 187 | }); 188 | if (options.unsafe) 189 | ast = pro.ast_squeeze_more(ast); 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 | return result; 199 | } catch(ex) { 200 | sys.debug(ex.stack); 201 | sys.debug(sys.inspect(ex)); 202 | sys.debug(JSON.stringify(ex)); 203 | } 204 | }; 205 | 206 | function time_it(name, cont) { 207 | if (!options.verbose) 208 | return cont(); 209 | var t1 = new Date().getTime(); 210 | try { return cont(); } 211 | finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); } 212 | }; 213 | -------------------------------------------------------------------------------- /parse-js.php: -------------------------------------------------------------------------------- 1 | run(); 47 | } 48 | 49 | // ---------------------------------------------------------------------------- 50 | // Constants 51 | 52 | public static $KEYWORDS = array( 53 | "break", "case", "catch", "const", "continue", "default", "delete", 54 | "do", "else", "finally", "for", "function", "if", "in", "instanceof", 55 | "new", "return", "switch", "throw", "try", "typeof", "var", "void", 56 | "while", "with" 57 | ); 58 | 59 | public static $RESERVED_WORDS = array( 60 | "abstract", "boolean", "byte", "char", "class", "debugger", "double", 61 | "enum", "export", "extends", "final", "float", "goto", "implements", 62 | "import", "int", "interface", "long", "native", "package", "private", 63 | "public", "public", "short", "static", "super", "synchronized", 64 | "throws", "transient", "volatile" 65 | ); 66 | 67 | public static $KEYWORDS_BEFORE_EXPRESSION = array( 68 | "return", "new", "delete", "throw", "else", "case" 69 | ); 70 | 71 | public static $KEYWORDS_ATOM = array( 72 | "false", "null", "true", "undefined" 73 | ); 74 | 75 | public static $OPERATOR_CHARS = array( 76 | "+", "-", "*", "&", "%", "=", "<", ">", "!", "?", "|", "~", "^" 77 | ); 78 | 79 | public static $RE_HEX_NUMBER = '/^0x[0-9a-f]+$/i'; 80 | public static $RE_OCT_NUMBER = '/^0[0-7]+$/'; 81 | public static $RE_DEC_NUMBER = '/^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i'; 82 | 83 | public static $OPERATORS = array( 84 | "in", "instanceof", "typeof", "new", "void", "delete", "++", "--", "+", 85 | "-", "!", "~", "&", "|", "^", "*", "/", "%", ">>", "<<", ">>>", "<", 86 | ">", "<=", ">=", "==", "===", "!=", "!==", "?", "=", "+=", "-=", "/=", 87 | "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&=", "&&", "||" 88 | ); 89 | 90 | public static $WHITESPACE_CHARS = array( 91 | " ", "\n", "\r", "\t", "\u200b" 92 | ); 93 | 94 | public static $PUNC_BEFORE_EXPRESSION = array( 95 | "[", "{", "}", "(", ",", ".", ";", ":" 96 | ); 97 | 98 | public static $PUNC_CHARS = array( 99 | "[", "]", "{", "}", "(", ")", ",", ";", ":" 100 | ); 101 | 102 | public static $REGEXP_MODIFIERS = array( 103 | "g", "m", "s", "i", "y" 104 | ); 105 | 106 | // regexps adapted from http://xregexp.com/plugins/#unicode 107 | public static $UNICODE = array( 108 | 'letter' => '/[\p{L}]/u', 109 | 'non_spacing_mark' => '/[\p{Mn}]/u', 110 | 'space_combining_mark' => '/[\p{Mc}]/u', 111 | 'connector_punctuation' => '/[\p{Pc}]/u' 112 | ); 113 | 114 | public static $UNARY_PREFIX = array( 115 | "typeof", "void", "delete", "--", "++", "!", "~", "-", "+" 116 | ); 117 | 118 | public static $UNARY_POSTFIX = array( 119 | "--", "++" 120 | ); 121 | 122 | public static $ASSIGNMENT = null; 123 | public static function _ASSIGNMENT() { 124 | if (! self::$ASSIGNMENT) { 125 | $a = array("+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="); 126 | $ret = array( '=' => true ); 127 | foreach ($a as $i => $op) { 128 | $ret[$op] = substr($op, strlen($op) - 1); 129 | } 130 | self::$ASSIGNMENT = $ret; 131 | } 132 | } 133 | 134 | public static $PRECEDENCE = null; 135 | public static function _PRECEDENCE() { 136 | if (! self::$PRECEDENCE) { 137 | $a = array( 138 | array("||"), 139 | array("&&"), 140 | array("|"), 141 | array("^"), 142 | array("&"), 143 | array("==", "===", "!=", "!=="), 144 | array("<", ">", "<=", ">=", "in", "instanceof"), 145 | array(">>", "<<", ">>>"), 146 | array("+", "-"), 147 | array("*", "/", "%") 148 | ); 149 | $ret = array(); 150 | for ($i = 0, $n = 1, $c1 = count($a); $i < $c1; $i++, $n++) { 151 | $b = $a[$i]; 152 | for ($j = 0, $c2 = count($b); $j < $c2; $j++) { 153 | $ret[$b[$j]] = $n; 154 | } 155 | } 156 | self::$PRECEDENCE = $ret; 157 | } 158 | } 159 | 160 | public static $STATEMENTS_WITH_LABELS = array( 161 | "for", "do", "while", "switch" 162 | ); 163 | 164 | public static $ATOMIC_START_TOKEN = array( 165 | "atom", "num", "string", "regexp", "name" 166 | ); 167 | 168 | // ---------------------------------------------------------------------------- 169 | // Utilities 170 | 171 | public static function is_letter($ch) { 172 | return preg_match(self::$UNICODE['letter'], $ch); 173 | } 174 | 175 | public static function is_digit($ch) { 176 | $ch = ord($ch); 177 | return ($ch >= 48 && $ch <= 57); 178 | } 179 | 180 | public static function is_alphanumeric_char($ch) { 181 | return (self::is_letter($ch) || self::is_digit($ch)); 182 | } 183 | 184 | public static function is_unicode_combining_mark($ch) { 185 | return (preg_match(self::$UNICODE['space_combining_mark'], $ch) || 186 | preg_match(self::$UNICODE['non_spacing_mark'], $ch)); 187 | } 188 | 189 | public static function is_unicode_connector_punctuation($ch) { 190 | return preg_match(self::$UNICODE['connector_punctuation'], $ch); 191 | } 192 | 193 | public static function is_identifier_start($ch) { 194 | return ($ch == '$' || $ch == '_' || self::is_letter($ch)); 195 | } 196 | 197 | public static function is_identifier_char($ch) { 198 | return ( 199 | self::is_identifier_start($ch) || 200 | self::is_unicode_combining_mark($ch) || 201 | self::is_digit($ch) || 202 | self::is_unicode_connector_punctuation($ch) || 203 | $ch == "\u200c" || $ch == "\u200d" 204 | ); 205 | } 206 | 207 | public static function parse_js_number($num) { 208 | if (preg_match(self::$RE_HEX_NUMBER, $num)) { 209 | return intval(substring($num, 2), 16); 210 | } elseif (preg_match(self::$RE_OCT_NUMBER, $num)) { 211 | return intval(substring($num, 1), 8); 212 | } elseif (preg_match(self::$RE_DEC_NUMBER, $num)) { 213 | return floatval($num); 214 | } 215 | } 216 | 217 | public static function is_token($token, $type, $value = null) { 218 | return ($token->type == $type && ($value === null || $token->value == $value)); 219 | } 220 | 221 | public static function unichr($code) { 222 | return html_entity_decode('&#'.$code.';', ENT_NOQUOTES, 'UTF-8'); 223 | } 224 | 225 | public static function uniord($c) { 226 | $h = ord($c{0}); 227 | if ($h <= 0x7F) { 228 | return $h; 229 | } else if ($h < 0xC2) { 230 | return false; 231 | } else if ($h <= 0xDF) { 232 | return ($h & 0x1F) << 6 | (ord($c{1}) & 0x3F); 233 | } else if ($h <= 0xEF) { 234 | return ($h & 0x0F) << 12 | (ord($c{1}) & 0x3F) << 6 235 | | (ord($c{2}) & 0x3F); 236 | } else if ($h <= 0xF4) { 237 | return ($h & 0x0F) << 18 | (ord($c{1}) & 0x3F) << 12 238 | | (ord($c{2}) & 0x3F) << 6 239 | | (ord($c{3}) & 0x3F); 240 | } else { 241 | return false; 242 | } 243 | } 244 | 245 | } 246 | 247 | // Do some initializing 248 | ParseJS::_PRECEDENCE(); 249 | ParseJS::_ASSIGNMENT(); 250 | 251 | /* End of file parse-js.php */ 252 | -------------------------------------------------------------------------------- /javascript-tokenizer.php: -------------------------------------------------------------------------------- 1 | text = $input; 48 | } 49 | 50 | public function context($state = null) { 51 | if (is_array($state)) { 52 | $state = array_merge(array( 53 | 'text' => '', 54 | 'pos' => 0, 55 | 'tokpos' => 0, 56 | 'line' => 0, 57 | 'tokline' => 0, 58 | 'col' => 0, 59 | 'tokcol' => 0, 60 | 'newline_before' => false, 61 | 'regex_allowed' => false, 62 | 'comments_before' => array() 63 | ), $state); 64 | } 65 | return array( 66 | 'text' => $this->text, 67 | 'pos' => $this->pos, 68 | 'tokpos' => $this->tokpos, 69 | 'line' => $this->line, 70 | 'tokline' => $this->tokline, 71 | 'col' => $this->col, 72 | 'tokcol' => $this->tokcol, 73 | 'newline_before' => $this->newline_before, 74 | 'regex_allowed' => $this->regex_allowed, 75 | 'comments_before' => $this->comments_before 76 | ); 77 | } 78 | 79 | public function tokenize() { 80 | $tokens = array(); 81 | $this->context($this->context()); 82 | for (;;) { 83 | $tokens[] = $this->next_token(); 84 | if (ParseJS::is_token($tokens[count($tokens) - 1], 'eof')) break; 85 | } 86 | return $tokens; 87 | } 88 | 89 | public function get_tokens() { 90 | if (! $this->tokens) { 91 | $this->tokens = $this->tokenize(); 92 | } 93 | return $this->tokens; 94 | } 95 | 96 | public function next_token($force_regexp = false) { 97 | if ($force_regexp) { 98 | return $this->read_regexp(); 99 | } 100 | $this->skip_whitespace(); 101 | $this->start_token(); 102 | $ch = $this->peek(); 103 | if (! $ch) { 104 | return $this->token('eof'); 105 | } 106 | if (ParseJS::is_digit($ch)) { 107 | return $this->read_num(); 108 | } 109 | if ($ch == '"' || $ch == "'") { 110 | return $this->read_string(); 111 | } 112 | if (in_array($ch, ParseJS::$PUNC_CHARS)) { 113 | return $this->token('punc', $this->next()); 114 | } 115 | if ($ch == '.') { 116 | return $this->handle_dot(); 117 | } 118 | if ($ch == '/') { 119 | return $this->handle_slash(); 120 | } 121 | if (in_array($ch, ParseJS::$OPERATOR_CHARS)) { 122 | return $this->read_operator(); 123 | } 124 | if ($ch == '\\' || ParseJS::is_identifier_char($ch)) { 125 | return $this->read_word(); 126 | } 127 | return $this->parse_error("Unexpected character '${ch}'"); 128 | } 129 | 130 | // ---------------------------------------------------------------------------- 131 | // Internal helper functions 132 | 133 | public function raise($msg, $line, $col, $pos) { 134 | throw new JS_Parse_Error($msg, $line, $col, $pos); 135 | } 136 | 137 | public function parse_error($msg) { 138 | return $this->raise($msg, $this->line, $this->col, $this->pos); 139 | } 140 | 141 | public function peek() { 142 | return ((isset($this->text[$this->pos])) ? $this->text[$this->pos] : null); 143 | } 144 | 145 | public function next($signal_eof = false) { 146 | $ch = $this->text[$this->pos++]; 147 | if ($signal_eof && ! $ch) { 148 | throw new JS_EOF(); 149 | } 150 | if ($ch == "\n") { 151 | $this->newline_before = true; 152 | $this->line++; 153 | $this->col = 0; 154 | } else { 155 | $this->col++; 156 | } 157 | return $ch; 158 | } 159 | 160 | public function eof() { 161 | return (! $this->peek()); 162 | } 163 | 164 | public function find($what, $signal_eof = null) { 165 | $pos = strpos($this->text, $what, $this->pos); 166 | if ($signal_eof && $pos === false) throw new JS_EOF(); 167 | return $pos; 168 | } 169 | 170 | public function start_token() { 171 | $this->tokline = $this->line; 172 | $this->tokcol = $this->col; 173 | $this->tokpos = $this->pos; 174 | } 175 | 176 | public function token($type, $value = null, $is_comment = false) { 177 | $this->regex_allowed = ( 178 | ($type == 'operator' && ! in_array($value, ParseJS::$UNARY_POSTFIX)) || 179 | ($type == 'keyword' && ! in_array($value, ParseJS::$KEYWORDS_BEFORE_EXPRESSION)) || 180 | ($type == 'punc' && ! in_array($value, ParseJS::$PUNC_BEFORE_EXPRESSION)) 181 | ); 182 | $ret = new JS_Token($type, $value, $this->tokline, $this->tokcol, $this->tokpos, $this->newline_before); 183 | if (! $is_comment) { 184 | $ret->comments_before = $this->comments_before; 185 | $this->comments_before = array(); 186 | } 187 | $this->newline_before = false; 188 | return $ret; 189 | } 190 | 191 | public function skip_whitespace() { 192 | while (in_array($this->peek(), ParseJS::$WHITESPACE_CHARS)) { 193 | $this->next(); 194 | } 195 | } 196 | 197 | public function read_while($pred) { 198 | $i = 0; 199 | $ret = ''; 200 | $ch = $this->peek(); 201 | while ($ch && $pred($ch, $i)) { 202 | $ret .= $this->next(); 203 | $ch = $this->peek(); 204 | } 205 | return $ret; 206 | } 207 | 208 | public function read_num($prefix = '') { 209 | $has_e = false; 210 | $after_e = false; 211 | $has_x = false; 212 | $has_dot = ($prefix == '.'); 213 | $num = $this->read_while(function($ch, $i) use(&$has_e, &$after_e, &$has_x, &$has_dot) { 214 | if ($ch == 'x' || $ch == 'X') { 215 | if ($has_x) return false; 216 | return ($has_x = true); 217 | } 218 | if (! $has_x && ($ch == 'e' || $ch == 'E')) { 219 | if ($has_e) return false; 220 | return ($has_e = $after_e = true); 221 | } 222 | if ($ch == '-') { 223 | if ($after_e || ($i == 0 && ! $prefix)) return true; 224 | return false; 225 | } 226 | if ($ch == '+') return $after_e; 227 | $after_e = false; 228 | if ($ch == '.') { 229 | if (! $has_dot && ! $has_x) { 230 | return ($has_dot = true); 231 | } 232 | return false; 233 | } 234 | return ParseJS::is_alphanumeric_char($ch); 235 | }); 236 | if ($prefix) { 237 | $num = $prefix.$num; 238 | } 239 | $valid = ParseJS::parse_js_number($num); 240 | if (is_numeric($valid)) { 241 | return $this->token('num', $valid); 242 | } else { 243 | return $this->parse_error('Invalid syntax: '.$num); 244 | } 245 | } 246 | 247 | public function read_escaped_char() { 248 | $ch = $this->next(true); 249 | switch ($char) { 250 | case 'n': return "\n"; 251 | case 'r': return "\r"; 252 | case 't': return "\t"; 253 | case 'b': return "\b"; 254 | case 'v': return "\v"; 255 | case 'f': return "\f"; 256 | case '0': return "\0"; 257 | case 'x': return ParseJS::unichr($this->hex_bytes(2)); 258 | case 'u': return ParseJS::unichr($this->hex_bytes(4)); 259 | default: return $ch; 260 | } 261 | } 262 | 263 | public function hex_bytes($n) { 264 | $num = 0; 265 | for (; $n > 0; --$n) { 266 | $digit = intval($this->next(true), 16); 267 | if (! is_numeric($digit)) { 268 | return $this->parse_error('Invalid hex character pattern in string'); 269 | } 270 | $num = ($num << 4) | $digit; 271 | } 272 | return $num; 273 | } 274 | 275 | public function read_string() { 276 | $self =& $this; 277 | return $this->with_eof_error('Unterminated string constant', function() use(&$self) { 278 | $quote = $self->next(); 279 | $ret = ''; 280 | for (;;) { 281 | $ch = $self->next(true); 282 | if ($ch == '//') { 283 | $ch = $self->read_escaped_char(); 284 | } elseif ($ch == $quote) { 285 | break; 286 | } 287 | $ret .= $ch; 288 | } 289 | return $self->token('string', $ret); 290 | }); 291 | } 292 | 293 | public function substr($str, $start, $end = null) { 294 | if ($end === null) $end = strlen($str); 295 | return substr($str, $start, $end - $start); 296 | } 297 | 298 | public function read_line_comment() { 299 | $this->next(); 300 | $i = $this->find("\n"); 301 | if ($i === false) { 302 | $ret = $this->substr($this->text, $this->pos); 303 | $this->pos = strlen($this->text); 304 | } else { 305 | $ret = $this->substr($this->text, $this->pos, $i); 306 | $this->pos = $i; 307 | } 308 | return $this->token('comment1', $ret, true); 309 | } 310 | 311 | public function read_multiline_comment() { 312 | $this->next(); 313 | $self =& $this; 314 | return $this->with_eof_error('Unterminated multiline comment', function() use(&$self) { 315 | $i = $self->find('*/', true); 316 | $text = $self->substr($self->text, $self->pos, $i); 317 | $tok = $self->token('comment2', $text, true); 318 | $self->pos = $i + 2; 319 | $self->line .= count(explode("\n", $text)) - 1; 320 | $self->newline_before = (strpos($text, "\n") !== false); 321 | return $tok; 322 | }); 323 | } 324 | 325 | public function read_name() { 326 | $backslash = false; 327 | $name = ''; 328 | while (($ch = $this->peek()) !== null) { 329 | if (! $backslash) { 330 | if ($ch == '//') { 331 | $backslash = true; 332 | $this->next(); 333 | } elseif (ParseJS::is_identifier_char($ch)) { 334 | $name .= $this->next(); 335 | } else { 336 | break; 337 | } 338 | } else { 339 | if ($ch != 'u') { 340 | return $this->parse_error('Expecting UnicodeEscapeSequence -- uXXXX'); 341 | } 342 | $ch = $this->read_escaped_char(); 343 | if (! ParseJS::is_identifier_char($ch)) { 344 | return $this->parse_error('Unicode char: '.ParseJS::uniord($ch).' is not valid in identifier'); 345 | } 346 | $name .= $ch; 347 | $backslash = false; 348 | } 349 | } 350 | return $name; 351 | } 352 | 353 | public function read_regexp() { 354 | $self =& $this; 355 | return $this->with_eof_error('Unterminated regular expression', function() use(&$self) { 356 | $prev_backslash = false; 357 | $regexp = ''; 358 | $in_class = false; 359 | while ($ch = $self->next(true)) { 360 | if ($prev_backslash) { 361 | $regexp .= '\\'.$ch; 362 | } elseif ($ch == '[') { 363 | $in_class = true; 364 | $regexp .= $ch; 365 | } elseif ($ch == ']' && $in_class) { 366 | $in_class = false; 367 | $regexp .= $ch; 368 | } elseif ($ch == '/' && ! $in_class) { 369 | break; 370 | } elseif ($ch == '\\') { 371 | $prev_backslash = true; 372 | } else { 373 | $regexp .= $ch; 374 | } 375 | } 376 | $mods = $self->read_name(); 377 | return $self->token('regexp', array($regexp, $mods)); 378 | }); 379 | } 380 | 381 | public function read_operator($prefix = null) { 382 | $self =& $this; 383 | $grow = function($op) use(&$self) { 384 | if (! $self->peek()) return $op; 385 | $bigger = $op.$self->peek(); 386 | if (in_array($bigger, ParseJS::$OPERATORS)) { 387 | $self->next(); 388 | return $grow($bigger); 389 | } else { 390 | return $op; 391 | } 392 | }; 393 | $value = ($prefix) ? $prefix : $this->next(); 394 | return $this->token('operator', $grow($value)); 395 | } 396 | 397 | public function handle_slash() { 398 | $this->next(); 399 | $regex_allowed = $this->regex_allowed; 400 | switch ($this->peek()) { 401 | case '/': 402 | $this->comments_before[] = $this->read_line_comment(); 403 | $this->regex_allowed = $regex_allowed; 404 | return $this->next_token(); 405 | break; 406 | case '*': 407 | $this->comments_before[] = $this->read_multiline_comment(); 408 | $this->regex_allowed = $regex_allowed; 409 | return $this->next_token(); 410 | break; 411 | } 412 | return (($this->regex_allowed) ? $this->read_regexp() : $this->read_operator('/')); 413 | } 414 | 415 | public function handle_dot() { 416 | $this->next(); 417 | return (ParseJS::is_digit($this->peek()) ? 418 | $this->read_num('.') : 419 | $this->token('punc', '.')); 420 | } 421 | 422 | public function read_word() { 423 | $word = $this->read_name(); 424 | if (! in_array($word, ParseJS::$KEYWORDS)) { 425 | return $this->token('name', $word); 426 | } elseif (in_array($word, ParseJS::$OPERATORS)) { 427 | return $this->token('operator', $word); 428 | } elseif (in_array($word, ParseJS::$KEYWORDS_ATOM)) { 429 | return $this->token('atom', $word); 430 | } else { 431 | return $this->token('keyword', $word); 432 | } 433 | } 434 | 435 | public function with_eof_error($err, $cont) { 436 | try { 437 | return $cont(); 438 | } catch (Exception $ex) { 439 | if ($ex instanceof JS_EOF) { 440 | return $this->parse_error($err); 441 | } 442 | throw $ex; 443 | } 444 | } 445 | 446 | } 447 | 448 | /* End of file javascript-tokenizer.php */ 449 | -------------------------------------------------------------------------------- /javascript-parser.php: -------------------------------------------------------------------------------- 1 | input = new JavaScript_Tokenizer($input); 46 | } else { 47 | $this->input = $input; 48 | } 49 | $this->token = $this->next(); 50 | $this->exigent_mode = $exigent_mode; 51 | $this->embed_tokens = $embed_tokens; 52 | } 53 | 54 | public function run() { 55 | $a = array(); 56 | while (! $this->is('eof')) { 57 | $a[] = $this->statement(); 58 | } 59 | return array('toplevel', $a); 60 | } 61 | 62 | // ---------------------------------------------------------------------------- 63 | // Error handler functions 64 | 65 | public function raise($msg, $line, $col, $pos) { 66 | throw new JS_Parse_Error($msg, $line, $col, $pos); 67 | } 68 | 69 | public function croak($msg, $line = null, $col = null, $pos = null) { 70 | $ctx = (object) $this->input->context(); 71 | $this->raise( 72 | $msg, 73 | (($line !== null) ? $line : $ctx->line), 74 | (($col !== null) ? $col : $ctx->col), 75 | (($pos !== null) ? $pos : $ctx->pos) 76 | ); 77 | } 78 | 79 | public function token_error($token, $msg) { 80 | $this->croak($msg, $token->line, $token->col); 81 | } 82 | 83 | public function unexpected($token = null) { 84 | if (! $token) { 85 | $token = $this->token; 86 | } 87 | $this->token_error($token, 'Unexpected token '.$token->type.' ('.$token->value.')'); 88 | } 89 | 90 | public function expect_token($type, $val = null) { 91 | if ($this->is($type, $val)) { 92 | return $this->next(); 93 | } 94 | $this->token_error($this->token, 'Unexpected token '.$this->token->type.', expected '.$type); 95 | } 96 | 97 | public function expect($punc) { 98 | return $this->expect_token('punc', $punc); 99 | } 100 | 101 | // ---------------------------------------------------------------------------- 102 | // Semicolon handling functions 103 | 104 | public function can_insert_semicolon() { 105 | return (! $this->exigent_mode && ( 106 | $this->token->nlb || $this->is('eof') || $this->is('punc', '}') 107 | )); 108 | } 109 | 110 | public function semicolon() { 111 | if ($this->is('punc', ';')) { 112 | $this->next(); 113 | } elseif (! $this->can_insert_semicolon()) { 114 | $this->unexpected(); 115 | } 116 | } 117 | 118 | // ---------------------------------------------------------------------------- 119 | // Internal helper functions 120 | 121 | public function is($type, $value = null) { 122 | return ParseJS::is_token($this->token, $type, $value); 123 | } 124 | 125 | public function peek() { 126 | return (($this->peeked) ? $this->peeked : ($this->peeked = $this->input->next_token())); 127 | } 128 | 129 | public function next() { 130 | $this->prev = $this->token; 131 | if ($this->peeked) { 132 | $this->token = $this->peeked; 133 | $this->peeked = null; 134 | } else { 135 | $this->token = $this->input->next_token(); 136 | } 137 | return $this->token; 138 | } 139 | 140 | public function parenthesised() { 141 | $this->expect('('); 142 | $exp = $this->expression(); 143 | $this->expect(')'); 144 | return $exp; 145 | } 146 | 147 | public function add_tokens($str, $start = null, $end = null) { 148 | if ($str instanceof NodeWithToken) { 149 | return $str; 150 | } else { 151 | return new NodeWithToken($str, $start, $end); 152 | } 153 | } 154 | 155 | public function statement() { 156 | if ($this->embed_tokens) { 157 | $start = $this->token; 158 | } 159 | $argv = func_get_args(); 160 | $self =& $this; 161 | $ast = call_user_func(function() use(&$self, $argv) { 162 | if ($self->is('operator', '/')) { 163 | $self->peeked = null; 164 | $self->token = $self->input->next_token(true); 165 | } 166 | var_dump($self->token); 167 | switch ($self->token->type) { 168 | case 'num': 169 | case 'string': 170 | case 'regexp': 171 | case 'operator': 172 | case 'atom': 173 | return $self->simple_statement(); 174 | break; 175 | case 'name': 176 | if (ParseJS::is_token($self->peek(), 'punc', ':')) { 177 | $self->next(); 178 | $self->next(); 179 | return $self->labeled_statement($self->token->value); 180 | } else { 181 | return $self->simple_statement(); 182 | } 183 | break; 184 | case 'punc': 185 | switch ($self->token->value) { 186 | case '{': 187 | return array('block', $self->block_()); 188 | break; 189 | case '[': 190 | case '(': 191 | return $self->simple_statement(); 192 | break; 193 | case ';': 194 | $self->next(); 195 | return array('block'); 196 | break; 197 | } 198 | break; 199 | case 'keyword': 200 | $token_value = $self->token->value; 201 | $self->next(); 202 | switch($token_value) { 203 | case 'break': 204 | case 'continue': 205 | return $this->break_cont($token_value); 206 | break; 207 | case 'debugger': 208 | $self->semicolon(); 209 | return array('debugger'); 210 | break; 211 | case 'do': 212 | $body = $self->do_in_loop('statement'); 213 | $self->expect_token('keyword', 'while'); 214 | $paren = $self->parenthesised(); 215 | $self->semicolon(); 216 | return array('do', $paren, $body); 217 | break; 218 | case 'for': 219 | return $self->for_(); 220 | break; 221 | case 'function': 222 | return $self->function_(true); 223 | break; 224 | case 'if': 225 | return $self->if_(); 226 | break; 227 | case 'return': 228 | if (! $self->in_function) { 229 | $self->croak('"return" outside of function'); 230 | } 231 | if ($self->is('punc', ';')) { 232 | $self->next(); 233 | return array('return', null); 234 | } elseif ($self->can_insert_semicolon()) { 235 | return array('return', null); 236 | } else { 237 | $exp = $self->expression(); 238 | $self->semicolon(); 239 | return array('return', $exp); 240 | } 241 | break; 242 | case 'switch': 243 | return array('switch', $self->parenthesised(), $self->switch_block_()); 244 | break; 245 | case 'throw': 246 | $exp = $self->expression(); 247 | $self->semicolon(); 248 | return array('throw', $exp); 249 | break; 250 | case 'try': 251 | return $self->try_(); 252 | break; 253 | case 'var': 254 | $var = $self->var_(); 255 | $self->semicolon(); 256 | return $var; 257 | break; 258 | case 'const': 259 | $const = $self->const_(); 260 | $self->semicolon(); 261 | return $const; 262 | break; 263 | case 'while': 264 | return array('while', $self->parenthesised(), $self->do_in_loop('statement')); 265 | break; 266 | case 'with': 267 | return array('with', $self->parenthesised(), $self->statement()); 268 | break; 269 | } 270 | break; 271 | default: 272 | $self->unexpected(); 273 | break; 274 | } 275 | }); 276 | if ($this->embed_tokens) { 277 | $ast[0] = $this->add_tokens($ast[0], $start, $this->prev); 278 | } 279 | return $ast; 280 | } 281 | 282 | public function labeled_statement($label) { 283 | $this->labels[] = $label; 284 | $start = $this->token; 285 | $stat = $this->statement(); 286 | if ($this->exigent_mode && ! in_array($stat[0], ParseJS::$STATEMENTS_WITH_LABELS)) { 287 | $this->unexpected($start); 288 | } 289 | array_pop($this->labels); 290 | return array('label', $label, $stat); 291 | } 292 | 293 | public function simple_statement() { 294 | $exp = $this->expression(); 295 | $this->semicolon(); 296 | return array('stat', $exp); 297 | } 298 | 299 | public function break_cont($type) { 300 | $name = $this->is('name') ? $this->token->value : null; 301 | if ($name !== null) { 302 | $this->next(); 303 | if (! in_array($name, $this->labels)) { 304 | $this->croak('Label "'.$name.'" without matching loop or statement'); 305 | } 306 | } elseif (! $this->in_loop) { 307 | $this->croak($type.' not inside a loop or switch'); 308 | } 309 | $this->semicolon(); 310 | return array($type, $name); 311 | } 312 | 313 | public function for_() { 314 | $this->expect('('); 315 | $init = null; 316 | if (! $this->is('punc', ';')) { 317 | if ($this->is('keyword', 'var')) { 318 | $this->next(); 319 | $init = $this->var_(true); 320 | } else { 321 | $init = $this->expression(true, true); 322 | } 323 | if ($this->is('operator', 'in')) { 324 | return $this->for_in($init); 325 | } 326 | } 327 | return $this->regular_for($init); 328 | } 329 | 330 | public function regular_for($init) { 331 | $this->expect(';'); 332 | $test = $this->is('punc', ';') ? null : $this->expression(); 333 | $this->expect(';'); 334 | $step = $this->is('punc', ')') ? null : $this->expression(); 335 | $this->expect(')'); 336 | return array('for', $init, $test, $step, $this->do_in_loop('statement')); 337 | } 338 | 339 | public function for_in($init) { 340 | $lhs = ($init[0] == 'var') ? array('name', $init[1][0]) : $init; 341 | $this->next(); 342 | $obj = $this->expression(); 343 | $this->expect(')'); 344 | return array('for-in', $init, $lhs, $obj, $this->do_in_loop('statement')); 345 | } 346 | 347 | public function function_($in_statement = null) { 348 | if ($this->embed_tokens) { 349 | $start = $this->prev; 350 | } 351 | $self =& $this; 352 | $ast = call_user_func(function() use(&$self, $in_statement) { 353 | $name = null; 354 | if ($self->is('name')) { 355 | $value = $self->token->value; 356 | $self->next(); 357 | $name = $value; 358 | } 359 | if ($in_statement && ! $name) { 360 | $self->unexpected(); 361 | } 362 | $self->expect('('); 363 | $type = $in_statement ? 'defun' : 'function'; 364 | // Get arguments 365 | $first = true; 366 | $args = array(); 367 | while (! $self->is('punc', ')')) { 368 | if ($first) { 369 | $first = false; 370 | } else { 371 | $self->expect(','); 372 | } 373 | if (! $self->is('name')) { 374 | $self->unexpected(); 375 | } 376 | $args[] = $self->token->value; 377 | $self->next(); 378 | } 379 | $self->next(); 380 | // Get function body 381 | $self->in_function++; 382 | $loop = $self->in_loop; 383 | $self->in_loop = 0; 384 | $body = $self->block_(); 385 | $self->in_function--; 386 | $self->in_loop = $loop; 387 | return array($type, $name, $args, $body); 388 | }); 389 | if ($this->embed_tokens) { 390 | $ast[0] = $this->add_tokens($ast[0], $start, $this->prev); 391 | } 392 | return $ast; 393 | } 394 | 395 | public function if_() { 396 | $cond = $this->parenthesised(); 397 | $body = $this->statement(); 398 | $belse = null; 399 | if ($this->is('keyword', 'else')) { 400 | $this->next(); 401 | $belse = $this->statement(); 402 | } 403 | return array('if', $cond, $body, $else); 404 | } 405 | 406 | public function block_() { 407 | $this->expect('{'); 408 | $arr = array(); 409 | while (! $this->is('punc', '}')) { 410 | if ($this->is('eof')) $this->unexpected(); 411 | $arr[] = $this->statement(); 412 | } 413 | $this->next(); 414 | return $arr; 415 | } 416 | 417 | public function switch_block_() { 418 | $self =& $this; 419 | return $this->do_in_loop(function() use(&$self) { 420 | $self->expect('{'); 421 | $arr = array(); 422 | $cur = null; 423 | while (! $self->is('punc', '}')) { 424 | if ($self->is('eof')) $self->unexpected(); 425 | if ($self->is('keyword', 'case')) { 426 | $self->next(); 427 | $cur = array(); 428 | $arr[] = array($self->expression(), &$cur); 429 | $self->expect(':'); 430 | } elseif ($self->is('keyword', 'default')) { 431 | $self->next(); 432 | $self->expect(':'); 433 | $cur = array(); 434 | $arr[] = array(null, &$cur); 435 | } else { 436 | if (! $cur) $self->unexpected(); 437 | $cur[] = $self->statement(); 438 | } 439 | } 440 | $self->next(); 441 | return $arr; 442 | }); 443 | } 444 | 445 | public function try_() { 446 | $body = $this->block_(); 447 | $bcatch = null; 448 | $bfinally = null; 449 | if ($this->is('keyword', 'catch')) { 450 | $this->next(); 451 | $this->expect('('); 452 | if (! $this->is('name')) { 453 | $this->croak('Name expected'); 454 | } 455 | $name = $this->token->value; 456 | $this->next(); 457 | $this->expect(')'); 458 | $bcatch = array($name, $this->block_()); 459 | } 460 | if ($this->is('keyword', 'finally')) { 461 | $this->next(); 462 | $bfinally = $this->block_(); 463 | } 464 | if (! $bcatch && ! $bfinally) { 465 | $this->croak('Missing catch/finally blocks'); 466 | } 467 | return array('try', $body, $bcatch, $bfinally); 468 | } 469 | 470 | public function vardefs($no_in = null) { 471 | $arr = array(); 472 | for (;;) { 473 | if (! $this->is('name')) $this->unexpected(); 474 | $name = $this->token->value; 475 | $this->next(); 476 | if ($this->is('operator', '=')) { 477 | $this->next(); 478 | $arr[] = array($name, $this->expression(false, $no_in)); 479 | } else { 480 | $arr[] = array($name); 481 | } 482 | if (! $this->is('punc', ',')) break; 483 | $this->next(); 484 | } 485 | return $arr; 486 | } 487 | 488 | public function var_($no_in = null) { 489 | return array('var', $this->vardefs($no_in)); 490 | } 491 | 492 | public function const_() { 493 | return array('const', $this->vardefs()); 494 | } 495 | 496 | public function new_() { 497 | $newexp = $this->expr_atom(false); 498 | if ($this->is('punc', '(')) { 499 | $this->next(); 500 | $args = $this->expr_list(')'); 501 | } else { 502 | $args = array(); 503 | } 504 | return $this->subscripts(array('new', $newexp, $args), true); 505 | } 506 | 507 | public function expr_atom($allow_calls = null) { 508 | if ($this->is('operator', 'new')) { 509 | $this->next(); 510 | return $this->new_(); 511 | } 512 | if ($this->is('operator') && in_array($this->token->value, ParseJS::$UNARY_PREFIX)) { 513 | $token_value = $this->token->value; 514 | $this->next(); 515 | return $this->make_unary('unary-prefix', $token_value, $this->expr_atom($allow_calls)); 516 | } 517 | if ($this->is('punc')) { 518 | switch ($this->token->value) { 519 | case '(': 520 | $this->next(); 521 | $exp = $this->expression(); 522 | $this->expect(')'); 523 | return $this->subscripts($exp, $allow_calls); 524 | break; 525 | case '[': 526 | $this->next(); 527 | return $this->subscripts($this->array_(), $allow_calls); 528 | break; 529 | case '{': 530 | $this->next(); 531 | return $this->subscripts($this->object_(), $allow_calls); 532 | break; 533 | } 534 | $this->unexpected(); 535 | } 536 | if ($this->is('keyword', 'function')) { 537 | $this->next(); 538 | return $this->subscripts($this->function_(false), $allow_calls); 539 | } 540 | if (in_array($this->token->type, ParseJS::$ATOMIC_START_TOKEN)) { 541 | if ($this->token->type == 'regexp') { 542 | $atom = array('regexp', $this->token->value[0], $this->token->value[1]); 543 | } else { 544 | $atom = array($this->token->type, $this->token->value); 545 | } 546 | $this->next(); 547 | return $this->subscripts($atom, $allow_calls); 548 | } 549 | $this->unexpected(); 550 | } 551 | 552 | public function expr_list($closing, $allow_trailing_comma = null, $allow_empty = null) { 553 | $first = true; 554 | $arr = array(); 555 | while (! $this->is('punc', $closing)) { 556 | if ($first) { 557 | $first = false; 558 | } else { 559 | $this->expect(','); 560 | } 561 | if ($allow_trailing_comma && $this->is('punc', $closing)) break; 562 | if ($this->is('punc', ',') && $allow_empty) { 563 | $arr[] = array('atom', 'undefined'); 564 | } else { 565 | $arr[] = $this->expression(false); 566 | } 567 | } 568 | $this->next(); 569 | return $arr; 570 | } 571 | 572 | public function array_() { 573 | return array('array', $this->expr_list(']', ! $this->exigent_mode, true)); 574 | } 575 | 576 | public function object_() { 577 | $first = true; 578 | $arr = array(); 579 | while (! $this->is('punc', '}')) { 580 | if ($first) { 581 | $first = false; 582 | } else { 583 | $this->expect(','); 584 | } 585 | if (! $this->exigent_mode && $this->is('punc', '}')) { 586 | break; 587 | } 588 | $type = $this->token->type; 589 | $name = $this->as_property_name(); 590 | if ($type == 'name' && ($name == 'set' || $name == 'get') && ! $this->is('punc', ':')) { 591 | $arr[] = array($this->as_name(), $this->function_(false), $name); 592 | } else { 593 | $this->expect(':'); 594 | $arr[] = array($name, $this->expression(false)); 595 | } 596 | } 597 | $this->next(); 598 | return array('object', $arr); 599 | } 600 | 601 | public function as_property_name() { 602 | switch ($this->token->type) { 603 | case 'num': 604 | case 'string': 605 | $value = $this->token->value; 606 | $this->next(); 607 | return $value; 608 | break; 609 | } 610 | return $this->as_name(); 611 | } 612 | 613 | public function as_name() { 614 | switch ($this->token->type) { 615 | case 'name': 616 | case 'operator': 617 | case 'keyword': 618 | case 'atom': 619 | $value = $this->token->value; 620 | $this->next(); 621 | return $value; 622 | break; 623 | } 624 | $this->unexpected(); 625 | } 626 | 627 | public function subscripts($expr, $allow_calls = null) { 628 | if ($this->is('punc', '.')) { 629 | $this->next(); 630 | return $this->subscripts(array('dot', $expr, $this->as_name()), $allow_calls); 631 | } 632 | if ($this->is('punc', '[')) { 633 | $this->next(); 634 | $exp = $this->expression(); 635 | $this->expect(']'); 636 | return $this->subscripts(array('sub', $expr, $exp), $allow_calls); 637 | } 638 | if ($allow_calls) { 639 | if ($this->is('punc', '(')) { 640 | $this->next(); 641 | return $this->subscripts(array('call', $expr, $this->expr_list(')')), true); 642 | } 643 | if ($this->is('operator') && in_array($this->token->value, ParseJS::$UNARY_POSTFIX)) { 644 | $unary = $this->make_unary('unary-postfix', $this->token->value, $expr); 645 | $this->next(); 646 | return $unary; 647 | } 648 | } 649 | return $expr; 650 | } 651 | 652 | public function make_unary($tag, $op, $expr) { 653 | if (($op == '++' || $op == '--') && ! $this->is_assignable($expr)) { 654 | $this->croak('Invalid use of '.$op.' operator'); 655 | } 656 | return array($tag, $op, $expr); 657 | } 658 | 659 | public function expr_op($left, $min_prec, $no_in = null) { 660 | $op = ($this->is('operator')) ? $this->token->value : null; 661 | if ($op == 'in' && $no_in) { 662 | $op = null; 663 | } 664 | $prec = ($op !== null) ? ParseJS::$PRECEDENCE[$op] : null; 665 | if ($prec !== null && $prec > $min_prec) { 666 | $this->next(); 667 | $right = $this->expr_op($this->expr_atom(true), $prec, $no_in); 668 | return $this->expr_op(array('binary', $op, $left, $right), $min_prec, $no_in); 669 | } 670 | return $left; 671 | } 672 | 673 | public function expr_ops($no_in = null) { 674 | return $this->expr_op($this->expr_atom(true), 0, $no_in); 675 | } 676 | 677 | public function maybe_conditional($no_in = null) { 678 | $expr = $this->expr_ops($no_in); 679 | if ($this->is('operator', '?')) { 680 | $this->next(); 681 | $yes = $this->expression(false); 682 | $this->expect(':'); 683 | return array('conditional', $expr, $yes, $this->expression(false, $no_in)); 684 | } 685 | return $expr; 686 | } 687 | 688 | public function is_assignable($expr) { 689 | if (! $this->exigent_mode) return true; 690 | switch ($expr[0]) { 691 | case 'dot': 692 | case 'sub': 693 | case 'new': 694 | case 'call': 695 | return true; 696 | break; 697 | case 'name': 698 | return ($expr[1] != 'this'); 699 | break; 700 | } 701 | } 702 | 703 | public function maybe_assign($no_in = null) { 704 | $left = $this->maybe_conditional($no_in); 705 | $val = $this->token->value; 706 | if ($this->is('operator') && isset(ParseJS::$ASSIGNMENT[$val])) { 707 | if ($this->is_assignable($left)) { 708 | $this->next(); 709 | return array('assign', ParseJS::$ASSIGNMENT[$val], $left, $this->maybe_assign($no_in)); 710 | } 711 | $this->croak('Invalid assignment'); 712 | } 713 | return $left; 714 | } 715 | 716 | public function expression($commas = null, $no_in = null) { 717 | if ($commas === null) { 718 | $commas = true; 719 | } 720 | $expr = $this->maybe_assign($no_in); 721 | if ($commas && $this->is('punc', ',')) { 722 | $this->next(); 723 | return array('seq', $expr, $this->expression(true, $no_in)); 724 | } 725 | return $expr; 726 | } 727 | 728 | public function do_in_loop($cont) { 729 | try { 730 | $this->in_loop++; 731 | if (is_string($cont) && method_exists($this, $cont)) { 732 | $this->$cont(); 733 | } else { 734 | $cont(); 735 | } 736 | } catch (Exception $e) { } 737 | $this->in_loop--; 738 | } 739 | 740 | } 741 | 742 | /* End of file javascript-parser.php */ 743 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 James Brumond 2 | 3 | This software is dual licensed under the MIT and GNU General Public Licenses. 4 | This means that you may use this software under either of these licenses (at 5 | your option). 6 | 7 | -------------------------------------------------------------------------------- 8 | -------------------------------------------------------------------------------- 9 | 10 | MIT LICENSE 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in 20 | all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | THE SOFTWARE. 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- 33 | -------------------------------------------------------------------------------- 34 | 35 | GNU GENERAL PUBLIC LICENSE 36 | Version 3, 29 June 2007 37 | 38 | Copyright (C) 2007 Free Software Foundation, Inc. 39 | Everyone is permitted to copy and distribute verbatim copies 40 | of this license document, but changing it is not allowed. 41 | 42 | Preamble 43 | 44 | The GNU General Public License is a free, copyleft license for 45 | software and other kinds of works. 46 | 47 | The licenses for most software and other practical works are designed 48 | to take away your freedom to share and change the works. By contrast, 49 | the GNU General Public License is intended to guarantee your freedom to 50 | share and change all versions of a program--to make sure it remains free 51 | software for all its users. We, the Free Software Foundation, use the 52 | GNU General Public License for most of our software; it applies also to 53 | any other work released this way by its authors. You can apply it to 54 | your programs, too. 55 | 56 | When we speak of free software, we are referring to freedom, not 57 | price. Our General Public Licenses are designed to make sure that you 58 | have the freedom to distribute copies of free software (and charge for 59 | them if you wish), that you receive source code or can get it if you 60 | want it, that you can change the software or use pieces of it in new 61 | free programs, and that you know you can do these things. 62 | 63 | To protect your rights, we need to prevent others from denying you 64 | these rights or asking you to surrender the rights. Therefore, you have 65 | certain responsibilities if you distribute copies of the software, or if 66 | you modify it: responsibilities to respect the freedom of others. 67 | 68 | For example, if you distribute copies of such a program, whether 69 | gratis or for a fee, you must pass on to the recipients the same 70 | freedoms that you received. You must make sure that they, too, receive 71 | or can get the source code. And you must show them these terms so they 72 | know their rights. 73 | 74 | Developers that use the GNU GPL protect your rights with two steps: 75 | (1) assert copyright on the software, and (2) offer you this License 76 | giving you legal permission to copy, distribute and/or modify it. 77 | 78 | For the developers' and authors' protection, the GPL clearly explains 79 | that there is no warranty for this free software. For both users' and 80 | authors' sake, the GPL requires that modified versions be marked as 81 | changed, so that their problems will not be attributed erroneously to 82 | authors of previous versions. 83 | 84 | Some devices are designed to deny users access to install or run 85 | modified versions of the software inside them, although the manufacturer 86 | can do so. This is fundamentally incompatible with the aim of 87 | protecting users' freedom to change the software. The systematic 88 | pattern of such abuse occurs in the area of products for individuals to 89 | use, which is precisely where it is most unacceptable. Therefore, we 90 | have designed this version of the GPL to prohibit the practice for those 91 | products. If such problems arise substantially in other domains, we 92 | stand ready to extend this provision to those domains in future versions 93 | of the GPL, as needed to protect the freedom of users. 94 | 95 | Finally, every program is threatened constantly by software patents. 96 | States should not allow patents to restrict development and use of 97 | software on general-purpose computers, but in those that do, we wish to 98 | avoid the special danger that patents applied to a free program could 99 | make it effectively proprietary. To prevent this, the GPL assures that 100 | patents cannot be used to render the program non-free. 101 | 102 | The precise terms and conditions for copying, distribution and 103 | modification follow. 104 | 105 | TERMS AND CONDITIONS 106 | 107 | 0. Definitions. 108 | 109 | "This License" refers to version 3 of the GNU General Public License. 110 | 111 | "Copyright" also means copyright-like laws that apply to other kinds of 112 | works, such as semiconductor masks. 113 | 114 | "The Program" refers to any copyrightable work licensed under this 115 | License. Each licensee is addressed as "you". "Licensees" and 116 | "recipients" may be individuals or organizations. 117 | 118 | To "modify" a work means to copy from or adapt all or part of the work 119 | in a fashion requiring copyright permission, other than the making of an 120 | exact copy. The resulting work is called a "modified version" of the 121 | earlier work or a work "based on" the earlier work. 122 | 123 | A "covered work" means either the unmodified Program or a work based 124 | on the Program. 125 | 126 | To "propagate" a work means to do anything with it that, without 127 | permission, would make you directly or secondarily liable for 128 | infringement under applicable copyright law, except executing it on a 129 | computer or modifying a private copy. Propagation includes copying, 130 | distribution (with or without modification), making available to the 131 | public, and in some countries other activities as well. 132 | 133 | To "convey" a work means any kind of propagation that enables other 134 | parties to make or receive copies. Mere interaction with a user through 135 | a computer network, with no transfer of a copy, is not conveying. 136 | 137 | An interactive user interface displays "Appropriate Legal Notices" 138 | to the extent that it includes a convenient and prominently visible 139 | feature that (1) displays an appropriate copyright notice, and (2) 140 | tells the user that there is no warranty for the work (except to the 141 | extent that warranties are provided), that licensees may convey the 142 | work under this License, and how to view a copy of this License. If 143 | the interface presents a list of user commands or options, such as a 144 | menu, a prominent item in the list meets this criterion. 145 | 146 | 1. Source Code. 147 | 148 | The "source code" for a work means the preferred form of the work 149 | for making modifications to it. "Object code" means any non-source 150 | form of a work. 151 | 152 | A "Standard Interface" means an interface that either is an official 153 | standard defined by a recognized standards body, or, in the case of 154 | interfaces specified for a particular programming language, one that 155 | is widely used among developers working in that language. 156 | 157 | The "System Libraries" of an executable work include anything, other 158 | than the work as a whole, that (a) is included in the normal form of 159 | packaging a Major Component, but which is not part of that Major 160 | Component, and (b) serves only to enable use of the work with that 161 | Major Component, or to implement a Standard Interface for which an 162 | implementation is available to the public in source code form. A 163 | "Major Component", in this context, means a major essential component 164 | (kernel, window system, and so on) of the specific operating system 165 | (if any) on which the executable work runs, or a compiler used to 166 | produce the work, or an object code interpreter used to run it. 167 | 168 | The "Corresponding Source" for a work in object code form means all 169 | the source code needed to generate, install, and (for an executable 170 | work) run the object code and to modify the work, including scripts to 171 | control those activities. However, it does not include the work's 172 | System Libraries, or general-purpose tools or generally available free 173 | programs which are used unmodified in performing those activities but 174 | which are not part of the work. For example, Corresponding Source 175 | includes interface definition files associated with source files for 176 | the work, and the source code for shared libraries and dynamically 177 | linked subprograms that the work is specifically designed to require, 178 | such as by intimate data communication or control flow between those 179 | subprograms and other parts of the work. 180 | 181 | The Corresponding Source need not include anything that users 182 | can regenerate automatically from other parts of the Corresponding 183 | Source. 184 | 185 | The Corresponding Source for a work in source code form is that 186 | same work. 187 | 188 | 2. Basic Permissions. 189 | 190 | All rights granted under this License are granted for the term of 191 | copyright on the Program, and are irrevocable provided the stated 192 | conditions are met. This License explicitly affirms your unlimited 193 | permission to run the unmodified Program. The output from running a 194 | covered work is covered by this License only if the output, given its 195 | content, constitutes a covered work. This License acknowledges your 196 | rights of fair use or other equivalent, as provided by copyright law. 197 | 198 | You may make, run and propagate covered works that you do not 199 | convey, without conditions so long as your license otherwise remains 200 | in force. You may convey covered works to others for the sole purpose 201 | of having them make modifications exclusively for you, or provide you 202 | with facilities for running those works, provided that you comply with 203 | the terms of this License in conveying all material for which you do 204 | not control copyright. Those thus making or running the covered works 205 | for you must do so exclusively on your behalf, under your direction 206 | and control, on terms that prohibit them from making any copies of 207 | your copyrighted material outside their relationship with you. 208 | 209 | Conveying under any other circumstances is permitted solely under 210 | the conditions stated below. Sublicensing is not allowed; section 10 211 | makes it unnecessary. 212 | 213 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 214 | 215 | No covered work shall be deemed part of an effective technological 216 | measure under any applicable law fulfilling obligations under article 217 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 218 | similar laws prohibiting or restricting circumvention of such 219 | measures. 220 | 221 | When you convey a covered work, you waive any legal power to forbid 222 | circumvention of technological measures to the extent such circumvention 223 | is effected by exercising rights under this License with respect to 224 | the covered work, and you disclaim any intention to limit operation or 225 | modification of the work as a means of enforcing, against the work's 226 | users, your or third parties' legal rights to forbid circumvention of 227 | technological measures. 228 | 229 | 4. Conveying Verbatim Copies. 230 | 231 | You may convey verbatim copies of the Program's source code as you 232 | receive it, in any medium, provided that you conspicuously and 233 | appropriately publish on each copy an appropriate copyright notice; 234 | keep intact all notices stating that this License and any 235 | non-permissive terms added in accord with section 7 apply to the code; 236 | keep intact all notices of the absence of any warranty; and give all 237 | recipients a copy of this License along with the Program. 238 | 239 | You may charge any price or no price for each copy that you convey, 240 | and you may offer support or warranty protection for a fee. 241 | 242 | 5. Conveying Modified Source Versions. 243 | 244 | You may convey a work based on the Program, or the modifications to 245 | produce it from the Program, in the form of source code under the 246 | terms of section 4, provided that you also meet all of these conditions: 247 | 248 | a) The work must carry prominent notices stating that you modified 249 | it, and giving a relevant date. 250 | 251 | b) The work must carry prominent notices stating that it is 252 | released under this License and any conditions added under section 253 | 7. This requirement modifies the requirement in section 4 to 254 | "keep intact all notices". 255 | 256 | c) You must license the entire work, as a whole, under this 257 | License to anyone who comes into possession of a copy. This 258 | License will therefore apply, along with any applicable section 7 259 | additional terms, to the whole of the work, and all its parts, 260 | regardless of how they are packaged. This License gives no 261 | permission to license the work in any other way, but it does not 262 | invalidate such permission if you have separately received it. 263 | 264 | d) If the work has interactive user interfaces, each must display 265 | Appropriate Legal Notices; however, if the Program has interactive 266 | interfaces that do not display Appropriate Legal Notices, your 267 | work need not make them do so. 268 | 269 | A compilation of a covered work with other separate and independent 270 | works, which are not by their nature extensions of the covered work, 271 | and which are not combined with it such as to form a larger program, 272 | in or on a volume of a storage or distribution medium, is called an 273 | "aggregate" if the compilation and its resulting copyright are not 274 | used to limit the access or legal rights of the compilation's users 275 | beyond what the individual works permit. Inclusion of a covered work 276 | in an aggregate does not cause this License to apply to the other 277 | parts of the aggregate. 278 | 279 | 6. Conveying Non-Source Forms. 280 | 281 | You may convey a covered work in object code form under the terms 282 | of sections 4 and 5, provided that you also convey the 283 | machine-readable Corresponding Source under the terms of this License, 284 | in one of these ways: 285 | 286 | a) Convey the object code in, or embodied in, a physical product 287 | (including a physical distribution medium), accompanied by the 288 | Corresponding Source fixed on a durable physical medium 289 | customarily used for software interchange. 290 | 291 | b) Convey the object code in, or embodied in, a physical product 292 | (including a physical distribution medium), accompanied by a 293 | written offer, valid for at least three years and valid for as 294 | long as you offer spare parts or customer support for that product 295 | model, to give anyone who possesses the object code either (1) a 296 | copy of the Corresponding Source for all the software in the 297 | product that is covered by this License, on a durable physical 298 | medium customarily used for software interchange, for a price no 299 | more than your reasonable cost of physically performing this 300 | conveying of source, or (2) access to copy the 301 | Corresponding Source from a network server at no charge. 302 | 303 | c) Convey individual copies of the object code with a copy of the 304 | written offer to provide the Corresponding Source. This 305 | alternative is allowed only occasionally and noncommercially, and 306 | only if you received the object code with such an offer, in accord 307 | with subsection 6b. 308 | 309 | d) Convey the object code by offering access from a designated 310 | place (gratis or for a charge), and offer equivalent access to the 311 | Corresponding Source in the same way through the same place at no 312 | further charge. You need not require recipients to copy the 313 | Corresponding Source along with the object code. If the place to 314 | copy the object code is a network server, the Corresponding Source 315 | may be on a different server (operated by you or a third party) 316 | that supports equivalent copying facilities, provided you maintain 317 | clear directions next to the object code saying where to find the 318 | Corresponding Source. Regardless of what server hosts the 319 | Corresponding Source, you remain obligated to ensure that it is 320 | available for as long as needed to satisfy these requirements. 321 | 322 | e) Convey the object code using peer-to-peer transmission, provided 323 | you inform other peers where the object code and Corresponding 324 | Source of the work are being offered to the general public at no 325 | charge under subsection 6d. 326 | 327 | A separable portion of the object code, whose source code is excluded 328 | from the Corresponding Source as a System Library, need not be 329 | included in conveying the object code work. 330 | 331 | A "User Product" is either (1) a "consumer product", which means any 332 | tangible personal property which is normally used for personal, family, 333 | or household purposes, or (2) anything designed or sold for incorporation 334 | into a dwelling. In determining whether a product is a consumer product, 335 | doubtful cases shall be resolved in favor of coverage. For a particular 336 | product received by a particular user, "normally used" refers to a 337 | typical or common use of that class of product, regardless of the status 338 | of the particular user or of the way in which the particular user 339 | actually uses, or expects or is expected to use, the product. A product 340 | is a consumer product regardless of whether the product has substantial 341 | commercial, industrial or non-consumer uses, unless such uses represent 342 | the only significant mode of use of the product. 343 | 344 | "Installation Information" for a User Product means any methods, 345 | procedures, authorization keys, or other information required to install 346 | and execute modified versions of a covered work in that User Product from 347 | a modified version of its Corresponding Source. The information must 348 | suffice to ensure that the continued functioning of the modified object 349 | code is in no case prevented or interfered with solely because 350 | modification has been made. 351 | 352 | If you convey an object code work under this section in, or with, or 353 | specifically for use in, a User Product, and the conveying occurs as 354 | part of a transaction in which the right of possession and use of the 355 | User Product is transferred to the recipient in perpetuity or for a 356 | fixed term (regardless of how the transaction is characterized), the 357 | Corresponding Source conveyed under this section must be accompanied 358 | by the Installation Information. But this requirement does not apply 359 | if neither you nor any third party retains the ability to install 360 | modified object code on the User Product (for example, the work has 361 | been installed in ROM). 362 | 363 | The requirement to provide Installation Information does not include a 364 | requirement to continue to provide support service, warranty, or updates 365 | for a work that has been modified or installed by the recipient, or for 366 | the User Product in which it has been modified or installed. Access to a 367 | network may be denied when the modification itself materially and 368 | adversely affects the operation of the network or violates the rules and 369 | protocols for communication across the network. 370 | 371 | Corresponding Source conveyed, and Installation Information provided, 372 | in accord with this section must be in a format that is publicly 373 | documented (and with an implementation available to the public in 374 | source code form), and must require no special password or key for 375 | unpacking, reading or copying. 376 | 377 | 7. Additional Terms. 378 | 379 | "Additional permissions" are terms that supplement the terms of this 380 | License by making exceptions from one or more of its conditions. 381 | Additional permissions that are applicable to the entire Program shall 382 | be treated as though they were included in this License, to the extent 383 | that they are valid under applicable law. If additional permissions 384 | apply only to part of the Program, that part may be used separately 385 | under those permissions, but the entire Program remains governed by 386 | this License without regard to the additional permissions. 387 | 388 | When you convey a copy of a covered work, you may at your option 389 | remove any additional permissions from that copy, or from any part of 390 | it. (Additional permissions may be written to require their own 391 | removal in certain cases when you modify the work.) You may place 392 | additional permissions on material, added by you to a covered work, 393 | for which you have or can give appropriate copyright permission. 394 | 395 | Notwithstanding any other provision of this License, for material you 396 | add to a covered work, you may (if authorized by the copyright holders of 397 | that material) supplement the terms of this License with terms: 398 | 399 | a) Disclaiming warranty or limiting liability differently from the 400 | terms of sections 15 and 16 of this License; or 401 | 402 | b) Requiring preservation of specified reasonable legal notices or 403 | author attributions in that material or in the Appropriate Legal 404 | Notices displayed by works containing it; or 405 | 406 | c) Prohibiting misrepresentation of the origin of that material, or 407 | requiring that modified versions of such material be marked in 408 | reasonable ways as different from the original version; or 409 | 410 | d) Limiting the use for publicity purposes of names of licensors or 411 | authors of the material; or 412 | 413 | e) Declining to grant rights under trademark law for use of some 414 | trade names, trademarks, or service marks; or 415 | 416 | f) Requiring indemnification of licensors and authors of that 417 | material by anyone who conveys the material (or modified versions of 418 | it) with contractual assumptions of liability to the recipient, for 419 | any liability that these contractual assumptions directly impose on 420 | those licensors and authors. 421 | 422 | All other non-permissive additional terms are considered "further 423 | restrictions" within the meaning of section 10. If the Program as you 424 | received it, or any part of it, contains a notice stating that it is 425 | governed by this License along with a term that is a further 426 | restriction, you may remove that term. If a license document contains 427 | a further restriction but permits relicensing or conveying under this 428 | License, you may add to a covered work material governed by the terms 429 | of that license document, provided that the further restriction does 430 | not survive such relicensing or conveying. 431 | 432 | If you add terms to a covered work in accord with this section, you 433 | must place, in the relevant source files, a statement of the 434 | additional terms that apply to those files, or a notice indicating 435 | where to find the applicable terms. 436 | 437 | Additional terms, permissive or non-permissive, may be stated in the 438 | form of a separately written license, or stated as exceptions; 439 | the above requirements apply either way. 440 | 441 | 8. Termination. 442 | 443 | You may not propagate or modify a covered work except as expressly 444 | provided under this License. Any attempt otherwise to propagate or 445 | modify it is void, and will automatically terminate your rights under 446 | this License (including any patent licenses granted under the third 447 | paragraph of section 11). 448 | 449 | However, if you cease all violation of this License, then your 450 | license from a particular copyright holder is reinstated (a) 451 | provisionally, unless and until the copyright holder explicitly and 452 | finally terminates your license, and (b) permanently, if the copyright 453 | holder fails to notify you of the violation by some reasonable means 454 | prior to 60 days after the cessation. 455 | 456 | Moreover, your license from a particular copyright holder is 457 | reinstated permanently if the copyright holder notifies you of the 458 | violation by some reasonable means, this is the first time you have 459 | received notice of violation of this License (for any work) from that 460 | copyright holder, and you cure the violation prior to 30 days after 461 | your receipt of the notice. 462 | 463 | Termination of your rights under this section does not terminate the 464 | licenses of parties who have received copies or rights from you under 465 | this License. If your rights have been terminated and not permanently 466 | reinstated, you do not qualify to receive new licenses for the same 467 | material under section 10. 468 | 469 | 9. Acceptance Not Required for Having Copies. 470 | 471 | You are not required to accept this License in order to receive or 472 | run a copy of the Program. Ancillary propagation of a covered work 473 | occurring solely as a consequence of using peer-to-peer transmission 474 | to receive a copy likewise does not require acceptance. However, 475 | nothing other than this License grants you permission to propagate or 476 | modify any covered work. These actions infringe copyright if you do 477 | not accept this License. Therefore, by modifying or propagating a 478 | covered work, you indicate your acceptance of this License to do so. 479 | 480 | 10. Automatic Licensing of Downstream Recipients. 481 | 482 | Each time you convey a covered work, the recipient automatically 483 | receives a license from the original licensors, to run, modify and 484 | propagate that work, subject to this License. You are not responsible 485 | for enforcing compliance by third parties with this License. 486 | 487 | An "entity transaction" is a transaction transferring control of an 488 | organization, or substantially all assets of one, or subdividing an 489 | organization, or merging organizations. If propagation of a covered 490 | work results from an entity transaction, each party to that 491 | transaction who receives a copy of the work also receives whatever 492 | licenses to the work the party's predecessor in interest had or could 493 | give under the previous paragraph, plus a right to possession of the 494 | Corresponding Source of the work from the predecessor in interest, if 495 | the predecessor has it or can get it with reasonable efforts. 496 | 497 | You may not impose any further restrictions on the exercise of the 498 | rights granted or affirmed under this License. For example, you may 499 | not impose a license fee, royalty, or other charge for exercise of 500 | rights granted under this License, and you may not initiate litigation 501 | (including a cross-claim or counterclaim in a lawsuit) alleging that 502 | any patent claim is infringed by making, using, selling, offering for 503 | sale, or importing the Program or any portion of it. 504 | 505 | 11. Patents. 506 | 507 | A "contributor" is a copyright holder who authorizes use under this 508 | License of the Program or a work on which the Program is based. The 509 | work thus licensed is called the contributor's "contributor version". 510 | 511 | A contributor's "essential patent claims" are all patent claims 512 | owned or controlled by the contributor, whether already acquired or 513 | hereafter acquired, that would be infringed by some manner, permitted 514 | by this License, of making, using, or selling its contributor version, 515 | but do not include claims that would be infringed only as a 516 | consequence of further modification of the contributor version. For 517 | purposes of this definition, "control" includes the right to grant 518 | patent sublicenses in a manner consistent with the requirements of 519 | this License. 520 | 521 | Each contributor grants you a non-exclusive, worldwide, royalty-free 522 | patent license under the contributor's essential patent claims, to 523 | make, use, sell, offer for sale, import and otherwise run, modify and 524 | propagate the contents of its contributor version. 525 | 526 | In the following three paragraphs, a "patent license" is any express 527 | agreement or commitment, however denominated, not to enforce a patent 528 | (such as an express permission to practice a patent or covenant not to 529 | sue for patent infringement). To "grant" such a patent license to a 530 | party means to make such an agreement or commitment not to enforce a 531 | patent against the party. 532 | 533 | If you convey a covered work, knowingly relying on a patent license, 534 | and the Corresponding Source of the work is not available for anyone 535 | to copy, free of charge and under the terms of this License, through a 536 | publicly available network server or other readily accessible means, 537 | then you must either (1) cause the Corresponding Source to be so 538 | available, or (2) arrange to deprive yourself of the benefit of the 539 | patent license for this particular work, or (3) arrange, in a manner 540 | consistent with the requirements of this License, to extend the patent 541 | license to downstream recipients. "Knowingly relying" means you have 542 | actual knowledge that, but for the patent license, your conveying the 543 | covered work in a country, or your recipient's use of the covered work 544 | in a country, would infringe one or more identifiable patents in that 545 | country that you have reason to believe are valid. 546 | 547 | If, pursuant to or in connection with a single transaction or 548 | arrangement, you convey, or propagate by procuring conveyance of, a 549 | covered work, and grant a patent license to some of the parties 550 | receiving the covered work authorizing them to use, propagate, modify 551 | or convey a specific copy of the covered work, then the patent license 552 | you grant is automatically extended to all recipients of the covered 553 | work and works based on it. 554 | 555 | A patent license is "discriminatory" if it does not include within 556 | the scope of its coverage, prohibits the exercise of, or is 557 | conditioned on the non-exercise of one or more of the rights that are 558 | specifically granted under this License. You may not convey a covered 559 | work if you are a party to an arrangement with a third party that is 560 | in the business of distributing software, under which you make payment 561 | to the third party based on the extent of your activity of conveying 562 | the work, and under which the third party grants, to any of the 563 | parties who would receive the covered work from you, a discriminatory 564 | patent license (a) in connection with copies of the covered work 565 | conveyed by you (or copies made from those copies), or (b) primarily 566 | for and in connection with specific products or compilations that 567 | contain the covered work, unless you entered into that arrangement, 568 | or that patent license was granted, prior to 28 March 2007. 569 | 570 | Nothing in this License shall be construed as excluding or limiting 571 | any implied license or other defenses to infringement that may 572 | otherwise be available to you under applicable patent law. 573 | 574 | 12. No Surrender of Others' Freedom. 575 | 576 | If conditions are imposed on you (whether by court order, agreement or 577 | otherwise) that contradict the conditions of this License, they do not 578 | excuse you from the conditions of this License. If you cannot convey a 579 | covered work so as to satisfy simultaneously your obligations under this 580 | License and any other pertinent obligations, then as a consequence you may 581 | not convey it at all. For example, if you agree to terms that obligate you 582 | to collect a royalty for further conveying from those to whom you convey 583 | the Program, the only way you could satisfy both those terms and this 584 | License would be to refrain entirely from conveying the Program. 585 | 586 | 13. Use with the GNU Affero General Public License. 587 | 588 | Notwithstanding any other provision of this License, you have 589 | permission to link or combine any covered work with a work licensed 590 | under version 3 of the GNU Affero General Public License into a single 591 | combined work, and to convey the resulting work. The terms of this 592 | License will continue to apply to the part which is the covered work, 593 | but the special requirements of the GNU Affero General Public License, 594 | section 13, concerning interaction through a network will apply to the 595 | combination as such. 596 | 597 | 14. Revised Versions of this License. 598 | 599 | The Free Software Foundation may publish revised and/or new versions of 600 | the GNU General Public License from time to time. Such new versions will 601 | be similar in spirit to the present version, but may differ in detail to 602 | address new problems or concerns. 603 | 604 | Each version is given a distinguishing version number. If the 605 | Program specifies that a certain numbered version of the GNU General 606 | Public License "or any later version" applies to it, you have the 607 | option of following the terms and conditions either of that numbered 608 | version or of any later version published by the Free Software 609 | Foundation. If the Program does not specify a version number of the 610 | GNU General Public License, you may choose any version ever published 611 | by the Free Software Foundation. 612 | 613 | If the Program specifies that a proxy can decide which future 614 | versions of the GNU General Public License can be used, that proxy's 615 | public statement of acceptance of a version permanently authorizes you 616 | to choose that version for the Program. 617 | 618 | Later license versions may give you additional or different 619 | permissions. However, no additional obligations are imposed on any 620 | author or copyright holder as a result of your choosing to follow a 621 | later version. 622 | 623 | 15. Disclaimer of Warranty. 624 | 625 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 626 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 627 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 628 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 629 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 630 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 631 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 632 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 633 | 634 | 16. Limitation of Liability. 635 | 636 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 637 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 638 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 639 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 640 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 641 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 642 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 643 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 644 | SUCH DAMAGES. 645 | 646 | 17. Interpretation of Sections 15 and 16. 647 | 648 | If the disclaimer of warranty and limitation of liability provided 649 | above cannot be given local legal effect according to their terms, 650 | reviewing courts shall apply local law that most closely approximates 651 | an absolute waiver of all civil liability in connection with the 652 | Program, unless a warranty or assumption of liability accompanies a 653 | copy of the Program in return for a fee. 654 | 655 | END OF TERMS AND CONDITIONS 656 | 657 | How to Apply These Terms to Your New Programs 658 | 659 | If you develop a new program, and you want it to be of the greatest 660 | possible use to the public, the best way to achieve this is to make it 661 | free software which everyone can redistribute and change under these terms. 662 | 663 | To do so, attach the following notices to the program. It is safest 664 | to attach them to the start of each source file to most effectively 665 | state the exclusion of warranty; and each file should have at least 666 | the "copyright" line and a pointer to where the full notice is found. 667 | 668 | 669 | Copyright (C) 670 | 671 | This program is free software: you can redistribute it and/or modify 672 | it under the terms of the GNU General Public License as published by 673 | the Free Software Foundation, either version 3 of the License, or 674 | (at your option) any later version. 675 | 676 | This program is distributed in the hope that it will be useful, 677 | but WITHOUT ANY WARRANTY; without even the implied warranty of 678 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 679 | GNU General Public License for more details. 680 | 681 | You should have received a copy of the GNU General Public License 682 | along with this program. If not, see . 683 | 684 | Also add information on how to contact you by electronic and paper mail. 685 | 686 | If the program does terminal interaction, make it output a short 687 | notice like this when it starts in an interactive mode: 688 | 689 | Copyright (C) 690 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 691 | This is free software, and you are welcome to redistribute it 692 | under certain conditions; type `show c' for details. 693 | 694 | The hypothetical commands `show w' and `show c' should show the appropriate 695 | parts of the General Public License. Of course, your program's commands 696 | might be different; for a GUI interface, you would use an "about box". 697 | 698 | You should also get your employer (if you work as a programmer) or school, 699 | if any, to sign a "copyright disclaimer" for the program, if necessary. 700 | For more information on this, and how to apply and follow the GNU GPL, see 701 | . 702 | 703 | The GNU General Public License does not permit incorporating your program 704 | into proprietary programs. If your program is a subroutine library, you 705 | may consider it more useful to permit linking proprietary applications with 706 | the library. If this is what you want to do, use the GNU Lesser General 707 | Public License instead of this License. But first, please read 708 | . 709 | 710 | 711 | -------------------------------------------------------------------------------- /UglifyJS/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 | var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t\u200b")); 193 | 194 | var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); 195 | 196 | var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); 197 | 198 | var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); 199 | 200 | /* -----[ Tokenizer ]----- */ 201 | 202 | // regexps adapted from http://xregexp.com/plugins/#unicode 203 | var UNICODE = { 204 | 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]"), 205 | 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]"), 206 | 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]"), 207 | connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]") 208 | }; 209 | 210 | function is_letter(ch) { 211 | return UNICODE.letter.test(ch); 212 | }; 213 | 214 | function is_digit(ch) { 215 | ch = ch.charCodeAt(0); 216 | return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9 217 | }; 218 | 219 | function is_alphanumeric_char(ch) { 220 | return is_digit(ch) || is_letter(ch); 221 | }; 222 | 223 | function is_unicode_combining_mark(ch) { 224 | return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch); 225 | }; 226 | 227 | function is_unicode_connector_punctuation(ch) { 228 | return UNICODE.connector_punctuation.test(ch); 229 | }; 230 | 231 | function is_identifier_start(ch) { 232 | return ch == "$" || ch == "_" || is_letter(ch); 233 | }; 234 | 235 | function is_identifier_char(ch) { 236 | return is_identifier_start(ch) 237 | || is_unicode_combining_mark(ch) 238 | || is_digit(ch) 239 | || is_unicode_connector_punctuation(ch) 240 | || ch == "\u200c" // zero-width non-joiner 241 | || ch == "\u200d" // zero-width joiner (in my ECMA-262 PDF, this is also 200c) 242 | ; 243 | }; 244 | 245 | function parse_js_number(num) { 246 | if (RE_HEX_NUMBER.test(num)) { 247 | return parseInt(num.substr(2), 16); 248 | } else if (RE_OCT_NUMBER.test(num)) { 249 | return parseInt(num.substr(1), 8); 250 | } else if (RE_DEC_NUMBER.test(num)) { 251 | return parseFloat(num); 252 | } 253 | }; 254 | 255 | function JS_Parse_Error(message, line, col, pos) { 256 | this.message = message; 257 | this.line = line; 258 | this.col = col; 259 | this.pos = pos; 260 | try { 261 | ({})(); 262 | } catch(ex) { 263 | this.stack = ex.stack; 264 | }; 265 | }; 266 | 267 | JS_Parse_Error.prototype.toString = function() { 268 | return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; 269 | }; 270 | 271 | function js_error(message, line, col, pos) { 272 | throw new JS_Parse_Error(message, line, col, pos); 273 | }; 274 | 275 | function is_token(token, type, val) { 276 | return token.type == type && (val == null || token.value == val); 277 | }; 278 | 279 | var EX_EOF = {}; 280 | 281 | function tokenizer($TEXT) { 282 | 283 | var S = { 284 | text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), 285 | pos : 0, 286 | tokpos : 0, 287 | line : 0, 288 | tokline : 0, 289 | col : 0, 290 | tokcol : 0, 291 | newline_before : false, 292 | regex_allowed : false, 293 | comments_before : [] 294 | }; 295 | 296 | function peek() { return S.text.charAt(S.pos); }; 297 | 298 | function next(signal_eof) { 299 | var ch = S.text.charAt(S.pos++); 300 | if (signal_eof && !ch) 301 | throw EX_EOF; 302 | if (ch == "\n") { 303 | S.newline_before = true; 304 | ++S.line; 305 | S.col = 0; 306 | } else { 307 | ++S.col; 308 | } 309 | return ch; 310 | }; 311 | 312 | function eof() { 313 | return !S.peek(); 314 | }; 315 | 316 | function find(what, signal_eof) { 317 | var pos = S.text.indexOf(what, S.pos); 318 | if (signal_eof && pos == -1) throw EX_EOF; 319 | return pos; 320 | }; 321 | 322 | function start_token() { 323 | S.tokline = S.line; 324 | S.tokcol = S.col; 325 | S.tokpos = S.pos; 326 | }; 327 | 328 | function token(type, value, is_comment) { 329 | S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) || 330 | (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) || 331 | (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value))); 332 | var ret = { 333 | type : type, 334 | value : value, 335 | line : S.tokline, 336 | col : S.tokcol, 337 | pos : S.tokpos, 338 | nlb : S.newline_before 339 | }; 340 | if (!is_comment) { 341 | ret.comments_before = S.comments_before; 342 | S.comments_before = []; 343 | } 344 | S.newline_before = false; 345 | return ret; 346 | }; 347 | 348 | function skip_whitespace() { 349 | while (HOP(WHITESPACE_CHARS, peek())) 350 | next(); 351 | }; 352 | 353 | function read_while(pred) { 354 | var ret = "", ch = peek(), i = 0; 355 | while (ch && pred(ch, i++)) { 356 | ret += next(); 357 | ch = peek(); 358 | } 359 | return ret; 360 | }; 361 | 362 | function parse_error(err) { 363 | js_error(err, S.tokline, S.tokcol, S.tokpos); 364 | }; 365 | 366 | function read_num(prefix) { 367 | var has_e = false, after_e = false, has_x = false, has_dot = prefix == "."; 368 | var num = read_while(function(ch, i){ 369 | if (ch == "x" || ch == "X") { 370 | if (has_x) return false; 371 | return has_x = true; 372 | } 373 | if (!has_x && (ch == "E" || ch == "e")) { 374 | if (has_e) return false; 375 | return has_e = after_e = true; 376 | } 377 | if (ch == "-") { 378 | if (after_e || (i == 0 && !prefix)) return true; 379 | return false; 380 | } 381 | if (ch == "+") return after_e; 382 | after_e = false; 383 | if (ch == ".") { 384 | if (!has_dot && !has_x) 385 | return has_dot = true; 386 | return false; 387 | } 388 | return is_alphanumeric_char(ch); 389 | }); 390 | if (prefix) 391 | num = prefix + num; 392 | var valid = parse_js_number(num); 393 | if (!isNaN(valid)) { 394 | return token("num", valid); 395 | } else { 396 | parse_error("Invalid syntax: " + num); 397 | } 398 | }; 399 | 400 | function read_escaped_char() { 401 | var ch = next(true); 402 | switch (ch) { 403 | case "n" : return "\n"; 404 | case "r" : return "\r"; 405 | case "t" : return "\t"; 406 | case "b" : return "\b"; 407 | case "v" : return "\v"; 408 | case "f" : return "\f"; 409 | case "0" : return "\0"; 410 | case "x" : return String.fromCharCode(hex_bytes(2)); 411 | case "u" : return String.fromCharCode(hex_bytes(4)); 412 | default : return ch; 413 | } 414 | }; 415 | 416 | function hex_bytes(n) { 417 | var num = 0; 418 | for (; n > 0; --n) { 419 | var digit = parseInt(next(true), 16); 420 | if (isNaN(digit)) 421 | parse_error("Invalid hex-character pattern in string"); 422 | num = (num << 4) | digit; 423 | } 424 | return num; 425 | }; 426 | 427 | function read_string() { 428 | return with_eof_error("Unterminated string constant", function(){ 429 | var quote = next(), ret = ""; 430 | for (;;) { 431 | var ch = next(true); 432 | if (ch == "\\") ch = read_escaped_char(); 433 | else if (ch == quote) break; 434 | ret += ch; 435 | } 436 | return token("string", ret); 437 | }); 438 | }; 439 | 440 | function read_line_comment() { 441 | next(); 442 | var i = find("\n"), ret; 443 | if (i == -1) { 444 | ret = S.text.substr(S.pos); 445 | S.pos = S.text.length; 446 | } else { 447 | ret = S.text.substring(S.pos, i); 448 | S.pos = i; 449 | } 450 | return token("comment1", ret, true); 451 | }; 452 | 453 | function read_multiline_comment() { 454 | next(); 455 | return with_eof_error("Unterminated multiline comment", function(){ 456 | var i = find("*/", true), 457 | text = S.text.substring(S.pos, i), 458 | tok = token("comment2", text, true); 459 | S.pos = i + 2; 460 | S.line += text.split("\n").length - 1; 461 | S.newline_before = text.indexOf("\n") >= 0; 462 | 463 | // https://github.com/mishoo/UglifyJS/issues/#issue/100 464 | if (/^@cc_on/i.test(text)) { 465 | warn("WARNING: at line " + S.line); 466 | warn("*** Found \"conditional comment\": " + text); 467 | warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer."); 468 | } 469 | 470 | return tok; 471 | }); 472 | }; 473 | 474 | function read_name() { 475 | var backslash = false, name = "", ch; 476 | while ((ch = peek()) != null) { 477 | if (!backslash) { 478 | if (ch == "\\") backslash = true, next(); 479 | else if (is_identifier_char(ch)) name += next(); 480 | else break; 481 | } 482 | else { 483 | if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); 484 | ch = read_escaped_char(); 485 | if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); 486 | name += ch; 487 | backslash = false; 488 | } 489 | } 490 | return name; 491 | }; 492 | 493 | function read_regexp() { 494 | return with_eof_error("Unterminated regular expression", function(){ 495 | var prev_backslash = false, regexp = "", ch, in_class = false; 496 | while ((ch = next(true))) if (prev_backslash) { 497 | regexp += "\\" + ch; 498 | prev_backslash = false; 499 | } else if (ch == "[") { 500 | in_class = true; 501 | regexp += ch; 502 | } else if (ch == "]" && in_class) { 503 | in_class = false; 504 | regexp += ch; 505 | } else if (ch == "/" && !in_class) { 506 | break; 507 | } else if (ch == "\\") { 508 | prev_backslash = true; 509 | } else { 510 | regexp += ch; 511 | } 512 | var mods = read_name(); 513 | return token("regexp", [ regexp, mods ]); 514 | }); 515 | }; 516 | 517 | function read_operator(prefix) { 518 | function grow(op) { 519 | if (!peek()) return op; 520 | var bigger = op + peek(); 521 | if (HOP(OPERATORS, bigger)) { 522 | next(); 523 | return grow(bigger); 524 | } else { 525 | return op; 526 | } 527 | }; 528 | return token("operator", grow(prefix || next())); 529 | }; 530 | 531 | function handle_slash() { 532 | next(); 533 | var regex_allowed = S.regex_allowed; 534 | switch (peek()) { 535 | case "/": 536 | S.comments_before.push(read_line_comment()); 537 | S.regex_allowed = regex_allowed; 538 | return next_token(); 539 | case "*": 540 | S.comments_before.push(read_multiline_comment()); 541 | S.regex_allowed = regex_allowed; 542 | return next_token(); 543 | } 544 | return S.regex_allowed ? read_regexp() : read_operator("/"); 545 | }; 546 | 547 | function handle_dot() { 548 | next(); 549 | return is_digit(peek()) 550 | ? read_num(".") 551 | : token("punc", "."); 552 | }; 553 | 554 | function read_word() { 555 | var word = read_name(); 556 | return !HOP(KEYWORDS, word) 557 | ? token("name", word) 558 | : HOP(OPERATORS, word) 559 | ? token("operator", word) 560 | : HOP(KEYWORDS_ATOM, word) 561 | ? token("atom", word) 562 | : token("keyword", word); 563 | }; 564 | 565 | function with_eof_error(eof_error, cont) { 566 | try { 567 | return cont(); 568 | } catch(ex) { 569 | if (ex === EX_EOF) parse_error(eof_error); 570 | else throw ex; 571 | } 572 | }; 573 | 574 | function next_token(force_regexp) { 575 | if (force_regexp) 576 | return read_regexp(); 577 | skip_whitespace(); 578 | start_token(); 579 | var ch = peek(); 580 | if (!ch) return token("eof"); 581 | if (is_digit(ch)) return read_num(); 582 | if (ch == '"' || ch == "'") return read_string(); 583 | if (HOP(PUNC_CHARS, ch)) return token("punc", next()); 584 | if (ch == ".") return handle_dot(); 585 | if (ch == "/") return handle_slash(); 586 | if (HOP(OPERATOR_CHARS, ch)) return read_operator(); 587 | if (ch == "\\" || is_identifier_start(ch)) return read_word(); 588 | parse_error("Unexpected character '" + ch + "'"); 589 | }; 590 | 591 | next_token.context = function(nc) { 592 | if (nc) S = nc; 593 | return S; 594 | }; 595 | 596 | return next_token; 597 | 598 | }; 599 | 600 | /* -----[ Parser (constants) ]----- */ 601 | 602 | var UNARY_PREFIX = array_to_hash([ 603 | "typeof", 604 | "void", 605 | "delete", 606 | "--", 607 | "++", 608 | "!", 609 | "~", 610 | "-", 611 | "+" 612 | ]); 613 | 614 | var UNARY_POSTFIX = array_to_hash([ "--", "++" ]); 615 | 616 | var ASSIGNMENT = (function(a, ret, i){ 617 | while (i < a.length) { 618 | ret[a[i]] = a[i].substr(0, a[i].length - 1); 619 | i++; 620 | } 621 | return ret; 622 | })( 623 | ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="], 624 | { "=": true }, 625 | 0 626 | ); 627 | 628 | var PRECEDENCE = (function(a, ret){ 629 | for (var i = 0, n = 1; i < a.length; ++i, ++n) { 630 | var b = a[i]; 631 | for (var j = 0; j < b.length; ++j) { 632 | ret[b[j]] = n; 633 | } 634 | } 635 | return ret; 636 | })( 637 | [ 638 | ["||"], 639 | ["&&"], 640 | ["|"], 641 | ["^"], 642 | ["&"], 643 | ["==", "===", "!=", "!=="], 644 | ["<", ">", "<=", ">=", "in", "instanceof"], 645 | [">>", "<<", ">>>"], 646 | ["+", "-"], 647 | ["*", "/", "%"] 648 | ], 649 | {} 650 | ); 651 | 652 | var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); 653 | 654 | var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); 655 | 656 | /* -----[ Parser ]----- */ 657 | 658 | function NodeWithToken(str, start, end) { 659 | this.name = str; 660 | this.start = start; 661 | this.end = end; 662 | }; 663 | 664 | NodeWithToken.prototype.toString = function() { return this.name; }; 665 | 666 | function parse($TEXT, exigent_mode, embed_tokens) { 667 | 668 | var S = { 669 | input : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT, 670 | token : null, 671 | prev : null, 672 | peeked : null, 673 | in_function : 0, 674 | in_loop : 0, 675 | labels : [] 676 | }; 677 | 678 | S.token = next(); 679 | 680 | function is(type, value) { 681 | return is_token(S.token, type, value); 682 | }; 683 | 684 | function peek() { return S.peeked || (S.peeked = S.input()); }; 685 | 686 | function next() { 687 | S.prev = S.token; 688 | if (S.peeked) { 689 | S.token = S.peeked; 690 | S.peeked = null; 691 | } else { 692 | S.token = S.input(); 693 | } 694 | return S.token; 695 | }; 696 | 697 | function prev() { 698 | return S.prev; 699 | }; 700 | 701 | function croak(msg, line, col, pos) { 702 | var ctx = S.input.context(); 703 | js_error(msg, 704 | line != null ? line : ctx.tokline, 705 | col != null ? col : ctx.tokcol, 706 | pos != null ? pos : ctx.tokpos); 707 | }; 708 | 709 | function token_error(token, msg) { 710 | croak(msg, token.line, token.col); 711 | }; 712 | 713 | function unexpected(token) { 714 | if (token == null) 715 | token = S.token; 716 | token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")"); 717 | }; 718 | 719 | function expect_token(type, val) { 720 | if (is(type, val)) { 721 | return next(); 722 | } 723 | token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type); 724 | }; 725 | 726 | function expect(punc) { return expect_token("punc", punc); }; 727 | 728 | function can_insert_semicolon() { 729 | return !exigent_mode && ( 730 | S.token.nlb || is("eof") || is("punc", "}") 731 | ); 732 | }; 733 | 734 | function semicolon() { 735 | if (is("punc", ";")) next(); 736 | else if (!can_insert_semicolon()) unexpected(); 737 | }; 738 | 739 | function as() { 740 | return slice(arguments); 741 | }; 742 | 743 | function parenthesised() { 744 | expect("("); 745 | var ex = expression(); 746 | expect(")"); 747 | return ex; 748 | }; 749 | 750 | function add_tokens(str, start, end) { 751 | return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end); 752 | }; 753 | 754 | var statement = embed_tokens ? function() { 755 | var start = S.token; 756 | var ast = $statement.apply(this, arguments); 757 | ast[0] = add_tokens(ast[0], start, prev()); 758 | return ast; 759 | } : $statement; 760 | 761 | function $statement() { 762 | if (is("operator", "/")) { 763 | S.peeked = null; 764 | S.token = S.input(true); // force regexp 765 | } 766 | switch (S.token.type) { 767 | case "num": 768 | case "string": 769 | case "regexp": 770 | case "operator": 771 | case "atom": 772 | return simple_statement(); 773 | 774 | case "name": 775 | return is_token(peek(), "punc", ":") 776 | ? labeled_statement(prog1(S.token.value, next, next)) 777 | : simple_statement(); 778 | 779 | case "punc": 780 | switch (S.token.value) { 781 | case "{": 782 | return as("block", block_()); 783 | case "[": 784 | case "(": 785 | return simple_statement(); 786 | case ";": 787 | next(); 788 | return as("block"); 789 | default: 790 | unexpected(); 791 | } 792 | 793 | case "keyword": 794 | switch (prog1(S.token.value, next)) { 795 | case "break": 796 | return break_cont("break"); 797 | 798 | case "continue": 799 | return break_cont("continue"); 800 | 801 | case "debugger": 802 | semicolon(); 803 | return as("debugger"); 804 | 805 | case "do": 806 | return (function(body){ 807 | expect_token("keyword", "while"); 808 | return as("do", prog1(parenthesised, semicolon), body); 809 | })(in_loop(statement)); 810 | 811 | case "for": 812 | return for_(); 813 | 814 | case "function": 815 | return function_(true); 816 | 817 | case "if": 818 | return if_(); 819 | 820 | case "return": 821 | if (S.in_function == 0) 822 | croak("'return' outside of function"); 823 | return as("return", 824 | is("punc", ";") 825 | ? (next(), null) 826 | : can_insert_semicolon() 827 | ? null 828 | : prog1(expression, semicolon)); 829 | 830 | case "switch": 831 | return as("switch", parenthesised(), switch_block_()); 832 | 833 | case "throw": 834 | return as("throw", prog1(expression, semicolon)); 835 | 836 | case "try": 837 | return try_(); 838 | 839 | case "var": 840 | return prog1(var_, semicolon); 841 | 842 | case "const": 843 | return prog1(const_, semicolon); 844 | 845 | case "while": 846 | return as("while", parenthesised(), in_loop(statement)); 847 | 848 | case "with": 849 | return as("with", parenthesised(), statement()); 850 | 851 | default: 852 | unexpected(); 853 | } 854 | } 855 | }; 856 | 857 | function labeled_statement(label) { 858 | S.labels.push(label); 859 | var start = S.token, stat = statement(); 860 | if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0])) 861 | unexpected(start); 862 | S.labels.pop(); 863 | return as("label", label, stat); 864 | }; 865 | 866 | function simple_statement() { 867 | return as("stat", prog1(expression, semicolon)); 868 | }; 869 | 870 | function break_cont(type) { 871 | var name = is("name") ? S.token.value : null; 872 | if (name != null) { 873 | next(); 874 | if (!member(name, S.labels)) 875 | croak("Label " + name + " without matching loop or statement"); 876 | } 877 | else if (S.in_loop == 0) 878 | croak(type + " not inside a loop or switch"); 879 | semicolon(); 880 | return as(type, name); 881 | }; 882 | 883 | function for_() { 884 | expect("("); 885 | var init = null; 886 | if (!is("punc", ";")) { 887 | init = is("keyword", "var") 888 | ? (next(), var_(true)) 889 | : expression(true, true); 890 | if (is("operator", "in")) 891 | return for_in(init); 892 | } 893 | return regular_for(init); 894 | }; 895 | 896 | function regular_for(init) { 897 | expect(";"); 898 | var test = is("punc", ";") ? null : expression(); 899 | expect(";"); 900 | var step = is("punc", ")") ? null : expression(); 901 | expect(")"); 902 | return as("for", init, test, step, in_loop(statement)); 903 | }; 904 | 905 | function for_in(init) { 906 | var lhs = init[0] == "var" ? as("name", init[1][0]) : init; 907 | next(); 908 | var obj = expression(); 909 | expect(")"); 910 | return as("for-in", init, lhs, obj, in_loop(statement)); 911 | }; 912 | 913 | var function_ = embed_tokens ? function() { 914 | var start = prev(); 915 | var ast = $function_.apply(this, arguments); 916 | ast[0] = add_tokens(ast[0], start, prev()); 917 | return ast; 918 | } : $function_; 919 | 920 | function $function_(in_statement) { 921 | var name = is("name") ? prog1(S.token.value, next) : null; 922 | if (in_statement && !name) 923 | unexpected(); 924 | expect("("); 925 | return as(in_statement ? "defun" : "function", 926 | name, 927 | // arguments 928 | (function(first, a){ 929 | while (!is("punc", ")")) { 930 | if (first) first = false; else expect(","); 931 | if (!is("name")) unexpected(); 932 | a.push(S.token.value); 933 | next(); 934 | } 935 | next(); 936 | return a; 937 | })(true, []), 938 | // body 939 | (function(){ 940 | ++S.in_function; 941 | var loop = S.in_loop; 942 | S.in_loop = 0; 943 | var a = block_(); 944 | --S.in_function; 945 | S.in_loop = loop; 946 | return a; 947 | })()); 948 | }; 949 | 950 | function if_() { 951 | var cond = parenthesised(), body = statement(), belse; 952 | if (is("keyword", "else")) { 953 | next(); 954 | belse = statement(); 955 | } 956 | return as("if", cond, body, belse); 957 | }; 958 | 959 | function block_() { 960 | expect("{"); 961 | var a = []; 962 | while (!is("punc", "}")) { 963 | if (is("eof")) unexpected(); 964 | a.push(statement()); 965 | } 966 | next(); 967 | return a; 968 | }; 969 | 970 | var switch_block_ = curry(in_loop, function(){ 971 | expect("{"); 972 | var a = [], cur = null; 973 | while (!is("punc", "}")) { 974 | if (is("eof")) unexpected(); 975 | if (is("keyword", "case")) { 976 | next(); 977 | cur = []; 978 | a.push([ expression(), cur ]); 979 | expect(":"); 980 | } 981 | else if (is("keyword", "default")) { 982 | next(); 983 | expect(":"); 984 | cur = []; 985 | a.push([ null, cur ]); 986 | } 987 | else { 988 | if (!cur) unexpected(); 989 | cur.push(statement()); 990 | } 991 | } 992 | next(); 993 | return a; 994 | }); 995 | 996 | function try_() { 997 | var body = block_(), bcatch, bfinally; 998 | if (is("keyword", "catch")) { 999 | next(); 1000 | expect("("); 1001 | if (!is("name")) 1002 | croak("Name expected"); 1003 | var name = S.token.value; 1004 | next(); 1005 | expect(")"); 1006 | bcatch = [ name, block_() ]; 1007 | } 1008 | if (is("keyword", "finally")) { 1009 | next(); 1010 | bfinally = block_(); 1011 | } 1012 | if (!bcatch && !bfinally) 1013 | croak("Missing catch/finally blocks"); 1014 | return as("try", body, bcatch, bfinally); 1015 | }; 1016 | 1017 | function vardefs(no_in) { 1018 | var a = []; 1019 | for (;;) { 1020 | if (!is("name")) 1021 | unexpected(); 1022 | var name = S.token.value; 1023 | next(); 1024 | if (is("operator", "=")) { 1025 | next(); 1026 | a.push([ name, expression(false, no_in) ]); 1027 | } else { 1028 | a.push([ name ]); 1029 | } 1030 | if (!is("punc", ",")) 1031 | break; 1032 | next(); 1033 | } 1034 | return a; 1035 | }; 1036 | 1037 | function var_(no_in) { 1038 | return as("var", vardefs(no_in)); 1039 | }; 1040 | 1041 | function const_() { 1042 | return as("const", vardefs()); 1043 | }; 1044 | 1045 | function new_() { 1046 | var newexp = expr_atom(false), args; 1047 | if (is("punc", "(")) { 1048 | next(); 1049 | args = expr_list(")"); 1050 | } else { 1051 | args = []; 1052 | } 1053 | return subscripts(as("new", newexp, args), true); 1054 | }; 1055 | 1056 | function expr_atom(allow_calls) { 1057 | if (is("operator", "new")) { 1058 | next(); 1059 | return new_(); 1060 | } 1061 | if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) { 1062 | return make_unary("unary-prefix", 1063 | prog1(S.token.value, next), 1064 | expr_atom(allow_calls)); 1065 | } 1066 | if (is("punc")) { 1067 | switch (S.token.value) { 1068 | case "(": 1069 | next(); 1070 | return subscripts(prog1(expression, curry(expect, ")")), allow_calls); 1071 | case "[": 1072 | next(); 1073 | return subscripts(array_(), allow_calls); 1074 | case "{": 1075 | next(); 1076 | return subscripts(object_(), allow_calls); 1077 | } 1078 | unexpected(); 1079 | } 1080 | if (is("keyword", "function")) { 1081 | next(); 1082 | return subscripts(function_(false), allow_calls); 1083 | } 1084 | if (HOP(ATOMIC_START_TOKEN, S.token.type)) { 1085 | var atom = S.token.type == "regexp" 1086 | ? as("regexp", S.token.value[0], S.token.value[1]) 1087 | : as(S.token.type, S.token.value); 1088 | return subscripts(prog1(atom, next), allow_calls); 1089 | } 1090 | unexpected(); 1091 | }; 1092 | 1093 | function expr_list(closing, allow_trailing_comma, allow_empty) { 1094 | var first = true, a = []; 1095 | while (!is("punc", closing)) { 1096 | if (first) first = false; else expect(","); 1097 | if (allow_trailing_comma && is("punc", closing)) break; 1098 | if (is("punc", ",") && allow_empty) { 1099 | a.push([ "atom", "undefined" ]); 1100 | } else { 1101 | a.push(expression(false)); 1102 | } 1103 | } 1104 | next(); 1105 | return a; 1106 | }; 1107 | 1108 | function array_() { 1109 | return as("array", expr_list("]", !exigent_mode, true)); 1110 | }; 1111 | 1112 | function object_() { 1113 | var first = true, a = []; 1114 | while (!is("punc", "}")) { 1115 | if (first) first = false; else expect(","); 1116 | if (!exigent_mode && is("punc", "}")) 1117 | // allow trailing comma 1118 | break; 1119 | var type = S.token.type; 1120 | var name = as_property_name(); 1121 | if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) { 1122 | a.push([ as_name(), function_(false), name ]); 1123 | } else { 1124 | expect(":"); 1125 | a.push([ name, expression(false) ]); 1126 | } 1127 | } 1128 | next(); 1129 | return as("object", a); 1130 | }; 1131 | 1132 | function as_property_name() { 1133 | switch (S.token.type) { 1134 | case "num": 1135 | case "string": 1136 | return prog1(S.token.value, next); 1137 | } 1138 | return as_name(); 1139 | }; 1140 | 1141 | function as_name() { 1142 | switch (S.token.type) { 1143 | case "name": 1144 | case "operator": 1145 | case "keyword": 1146 | case "atom": 1147 | return prog1(S.token.value, next); 1148 | default: 1149 | unexpected(); 1150 | } 1151 | }; 1152 | 1153 | function subscripts(expr, allow_calls) { 1154 | if (is("punc", ".")) { 1155 | next(); 1156 | return subscripts(as("dot", expr, as_name()), allow_calls); 1157 | } 1158 | if (is("punc", "[")) { 1159 | next(); 1160 | return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls); 1161 | } 1162 | if (allow_calls && is("punc", "(")) { 1163 | next(); 1164 | return subscripts(as("call", expr, expr_list(")")), true); 1165 | } 1166 | if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) { 1167 | return prog1(curry(make_unary, "unary-postfix", S.token.value, expr), 1168 | next); 1169 | } 1170 | return expr; 1171 | }; 1172 | 1173 | function make_unary(tag, op, expr) { 1174 | if ((op == "++" || op == "--") && !is_assignable(expr)) 1175 | croak("Invalid use of " + op + " operator"); 1176 | return as(tag, op, expr); 1177 | }; 1178 | 1179 | function expr_op(left, min_prec, no_in) { 1180 | var op = is("operator") ? S.token.value : null; 1181 | if (op && op == "in" && no_in) op = null; 1182 | var prec = op != null ? PRECEDENCE[op] : null; 1183 | if (prec != null && prec > min_prec) { 1184 | next(); 1185 | var right = expr_op(expr_atom(true), prec, no_in); 1186 | return expr_op(as("binary", op, left, right), min_prec, no_in); 1187 | } 1188 | return left; 1189 | }; 1190 | 1191 | function expr_ops(no_in) { 1192 | return expr_op(expr_atom(true), 0, no_in); 1193 | }; 1194 | 1195 | function maybe_conditional(no_in) { 1196 | var expr = expr_ops(no_in); 1197 | if (is("operator", "?")) { 1198 | next(); 1199 | var yes = expression(false); 1200 | expect(":"); 1201 | return as("conditional", expr, yes, expression(false, no_in)); 1202 | } 1203 | return expr; 1204 | }; 1205 | 1206 | function is_assignable(expr) { 1207 | if (!exigent_mode) return true; 1208 | switch (expr[0]) { 1209 | case "dot": 1210 | case "sub": 1211 | case "new": 1212 | case "call": 1213 | return true; 1214 | case "name": 1215 | return expr[1] != "this"; 1216 | } 1217 | }; 1218 | 1219 | function maybe_assign(no_in) { 1220 | var left = maybe_conditional(no_in), val = S.token.value; 1221 | if (is("operator") && HOP(ASSIGNMENT, val)) { 1222 | if (is_assignable(left)) { 1223 | next(); 1224 | return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in)); 1225 | } 1226 | croak("Invalid assignment"); 1227 | } 1228 | return left; 1229 | }; 1230 | 1231 | function expression(commas, no_in) { 1232 | if (arguments.length == 0) 1233 | commas = true; 1234 | var expr = maybe_assign(no_in); 1235 | if (commas && is("punc", ",")) { 1236 | next(); 1237 | return as("seq", expr, expression(true, no_in)); 1238 | } 1239 | return expr; 1240 | }; 1241 | 1242 | function in_loop(cont) { 1243 | try { 1244 | ++S.in_loop; 1245 | return cont(); 1246 | } finally { 1247 | --S.in_loop; 1248 | } 1249 | }; 1250 | 1251 | return as("toplevel", (function(a){ 1252 | while (!is("eof")) 1253 | a.push(statement()); 1254 | return a; 1255 | })([])); 1256 | 1257 | }; 1258 | 1259 | /* -----[ Utilities ]----- */ 1260 | 1261 | function curry(f) { 1262 | var args = slice(arguments, 1); 1263 | return function() { return f.apply(this, args.concat(slice(arguments))); }; 1264 | }; 1265 | 1266 | function prog1(ret) { 1267 | if (ret instanceof Function) 1268 | ret = ret(); 1269 | for (var i = 1, n = arguments.length; --n > 0; ++i) 1270 | arguments[i](); 1271 | return ret; 1272 | }; 1273 | 1274 | function array_to_hash(a) { 1275 | var ret = {}; 1276 | for (var i = 0; i < a.length; ++i) 1277 | ret[a[i]] = true; 1278 | return ret; 1279 | }; 1280 | 1281 | function slice(a, start) { 1282 | return Array.prototype.slice.call(a, start == null ? 0 : start); 1283 | }; 1284 | 1285 | function characters(str) { 1286 | return str.split(""); 1287 | }; 1288 | 1289 | function member(name, array) { 1290 | for (var i = array.length; --i >= 0;) 1291 | if (array[i] === name) 1292 | return true; 1293 | return false; 1294 | }; 1295 | 1296 | function HOP(obj, prop) { 1297 | return Object.prototype.hasOwnProperty.call(obj, prop); 1298 | }; 1299 | 1300 | var warn = function() {}; 1301 | 1302 | /* -----[ Exports ]----- */ 1303 | 1304 | exports.tokenizer = tokenizer; 1305 | exports.parse = parse; 1306 | exports.slice = slice; 1307 | exports.curry = curry; 1308 | exports.member = member; 1309 | exports.array_to_hash = array_to_hash; 1310 | exports.PRECEDENCE = PRECEDENCE; 1311 | exports.KEYWORDS_ATOM = KEYWORDS_ATOM; 1312 | exports.RESERVED_WORDS = RESERVED_WORDS; 1313 | exports.KEYWORDS = KEYWORDS; 1314 | exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; 1315 | exports.OPERATORS = OPERATORS; 1316 | exports.is_alphanumeric_char = is_alphanumeric_char; 1317 | exports.set_logger = function(logger) { 1318 | warn = logger; 1319 | }; 1320 | -------------------------------------------------------------------------------- /UglifyJS/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 jsp = require("./parse-js"), 62 | slice = jsp.slice, 63 | member = jsp.member, 64 | PRECEDENCE = jsp.PRECEDENCE, 65 | OPERATORS = jsp.OPERATORS; 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 | if (stat[0] == "block") { 769 | if (stat[1]) { 770 | a.push.apply(a, stat[1]); 771 | } 772 | } else { 773 | a.push(stat); 774 | } 775 | return a; 776 | }, []); 777 | 778 | statements = (function(a, prev){ 779 | statements.forEach(function(cur){ 780 | if (prev && ((cur[0] == "var" && prev[0] == "var") || 781 | (cur[0] == "const" && prev[0] == "const"))) { 782 | prev[1] = prev[1].concat(cur[1]); 783 | } else { 784 | a.push(cur); 785 | prev = cur; 786 | } 787 | }); 788 | return a; 789 | })([]); 790 | 791 | if (options.dead_code) statements = (function(a, has_quit){ 792 | statements.forEach(function(st){ 793 | if (has_quit) { 794 | if (member(st[0], [ "function", "defun" , "var", "const" ])) { 795 | a.push(st); 796 | } 797 | else if (!options.no_warnings) 798 | warn_unreachable(st); 799 | } 800 | else { 801 | a.push(st); 802 | if (member(st[0], [ "return", "throw", "break", "continue" ])) 803 | has_quit = true; 804 | } 805 | }); 806 | return a; 807 | })([]); 808 | 809 | if (options.make_seqs) statements = (function(a, prev) { 810 | statements.forEach(function(cur){ 811 | if (prev && prev[0] == "stat" && cur[0] == "stat") { 812 | prev[1] = [ "seq", prev[1], cur[1] ]; 813 | } else { 814 | a.push(cur); 815 | prev = cur; 816 | } 817 | }); 818 | return a; 819 | })([]); 820 | 821 | if (block_type == "lambda") statements = (function(i, a, stat){ 822 | while (i < statements.length) { 823 | stat = statements[i++]; 824 | if (stat[0] == "if" && !stat[3]) { 825 | if (stat[2][0] == "return" && stat[2][1] == null) { 826 | a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ])); 827 | break; 828 | } 829 | var last = last_stat(stat[2]); 830 | if (last[0] == "return" && last[1] == null) { 831 | a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ])); 832 | break; 833 | } 834 | } 835 | a.push(stat); 836 | } 837 | return a; 838 | })(0, []); 839 | 840 | return statements; 841 | }; 842 | 843 | function make_if(c, t, e) { 844 | return when_constant(c, function(ast, val){ 845 | if (val) { 846 | warn_unreachable(e); 847 | return t; 848 | } else { 849 | warn_unreachable(t); 850 | return e; 851 | } 852 | }, function() { 853 | return make_real_if(c, t, e); 854 | }); 855 | }; 856 | 857 | function make_real_if(c, t, e) { 858 | c = walk(c); 859 | t = walk(t); 860 | e = walk(e); 861 | 862 | if (empty(t)) { 863 | c = negate(c); 864 | t = e; 865 | e = null; 866 | } else if (empty(e)) { 867 | e = null; 868 | } else { 869 | // if we have both else and then, maybe it makes sense to switch them? 870 | (function(){ 871 | var a = gen_code(c); 872 | var n = negate(c); 873 | var b = gen_code(n); 874 | if (b.length < a.length) { 875 | var tmp = t; 876 | t = e; 877 | e = tmp; 878 | c = n; 879 | } 880 | })(); 881 | } 882 | if (empty(e) && empty(t)) 883 | return [ "stat", c ]; 884 | var ret = [ "if", c, t, e ]; 885 | if (t[0] == "if" && empty(t[3]) && empty(e)) { 886 | ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ])); 887 | } 888 | else if (t[0] == "stat") { 889 | if (e) { 890 | if (e[0] == "stat") { 891 | ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]); 892 | } 893 | } 894 | else { 895 | ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]); 896 | } 897 | } 898 | else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) { 899 | ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]); 900 | } 901 | else if (e && aborts(t)) { 902 | ret = [ [ "if", c, t ] ]; 903 | if (e[0] == "block") { 904 | if (e[1]) ret = ret.concat(e[1]); 905 | } 906 | else { 907 | ret.push(e); 908 | } 909 | ret = walk([ "block", ret ]); 910 | } 911 | else if (t && aborts(e)) { 912 | ret = [ [ "if", negate(c), e ] ]; 913 | if (t[0] == "block") { 914 | if (t[1]) ret = ret.concat(t[1]); 915 | } else { 916 | ret.push(t); 917 | } 918 | ret = walk([ "block", ret ]); 919 | } 920 | return ret; 921 | }; 922 | 923 | function _do_while(cond, body) { 924 | return when_constant(cond, function(cond, val){ 925 | if (!val) { 926 | warn_unreachable(body); 927 | return [ "block" ]; 928 | } else { 929 | return [ "for", null, null, null, walk(body) ]; 930 | } 931 | }); 932 | }; 933 | 934 | return w.with_walkers({ 935 | "sub": function(expr, subscript) { 936 | if (subscript[0] == "string") { 937 | var name = subscript[1]; 938 | if (is_identifier(name)) 939 | return [ "dot", walk(expr), name ]; 940 | else if (/^[1-9][0-9]*$/.test(name) || name === "0") 941 | return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ]; 942 | } 943 | }, 944 | "if": make_if, 945 | "toplevel": function(body) { 946 | return [ "toplevel", with_scope(this.scope, function(){ 947 | return tighten(MAP(body, walk)); 948 | }) ]; 949 | }, 950 | "switch": function(expr, body) { 951 | var last = body.length - 1; 952 | return [ "switch", walk(expr), MAP(body, function(branch, i){ 953 | var block = tighten(MAP(branch[1], walk)); 954 | if (i == last && block.length > 0) { 955 | var node = block[block.length - 1]; 956 | if (node[0] == "break" && !node[1]) 957 | block.pop(); 958 | } 959 | return [ branch[0] ? walk(branch[0]) : null, block ]; 960 | }) ]; 961 | }, 962 | "function": function() { 963 | var ret = _lambda.apply(this, arguments); 964 | if (ret[1] && !HOP(scope.refs, ret[1])) { 965 | ret[1] = null; 966 | } 967 | return ret; 968 | }, 969 | "defun": _lambda, 970 | "block": function(body) { 971 | if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]); 972 | }, 973 | "binary": function(op, left, right) { 974 | return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){ 975 | return best_of(walk(c), this); 976 | }, function no() { 977 | return this; 978 | }); 979 | }, 980 | "conditional": function(c, t, e) { 981 | return make_conditional(walk(c), walk(t), walk(e)); 982 | }, 983 | "try": function(t, c, f) { 984 | return [ 985 | "try", 986 | tighten(MAP(t, walk)), 987 | c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null, 988 | f != null ? tighten(MAP(f, walk)) : null 989 | ]; 990 | }, 991 | "unary-prefix": function(op, expr) { 992 | expr = walk(expr); 993 | var ret = [ "unary-prefix", op, expr ]; 994 | if (op == "!") 995 | ret = best_of(ret, negate(expr)); 996 | return when_constant(ret, function(ast, val){ 997 | return walk(ast); // it's either true or false, so minifies to !0 or !1 998 | }, function() { return ret }); 999 | }, 1000 | "name": function(name) { 1001 | switch (name) { 1002 | case "true": return [ "unary-prefix", "!", [ "num", 0 ]]; 1003 | case "false": return [ "unary-prefix", "!", [ "num", 1 ]]; 1004 | } 1005 | }, 1006 | "new": function(ctor, args) { 1007 | if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) { 1008 | if (args.length != 1) { 1009 | return [ "array", args ]; 1010 | } else { 1011 | return [ "call", [ "name", "Array" ], args ]; 1012 | } 1013 | } 1014 | }, 1015 | "call": function(expr, args) { 1016 | if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) { 1017 | return [ "array", args ]; 1018 | } 1019 | }, 1020 | "while": _do_while, 1021 | "do": _do_while 1022 | }, function() { 1023 | return walk(ast_add_scope(ast)); 1024 | }); 1025 | }; 1026 | 1027 | /* -----[ re-generate code from the AST ]----- */ 1028 | 1029 | var DOT_CALL_NO_PARENS = jsp.array_to_hash([ 1030 | "name", 1031 | "array", 1032 | "object", 1033 | "string", 1034 | "dot", 1035 | "sub", 1036 | "call", 1037 | "regexp" 1038 | ]); 1039 | 1040 | function make_string(str, ascii_only) { 1041 | var dq = 0, sq = 0; 1042 | str = str.replace(/[\\\b\f\n\r\t\x22\x27]/g, function(s){ 1043 | switch (s) { 1044 | case "\\": return "\\\\"; 1045 | case "\b": return "\\b"; 1046 | case "\f": return "\\f"; 1047 | case "\n": return "\\n"; 1048 | case "\r": return "\\r"; 1049 | case "\t": return "\\t"; 1050 | case '"': ++dq; return '"'; 1051 | case "'": ++sq; return "'"; 1052 | } 1053 | return s; 1054 | }); 1055 | if (ascii_only) str = to_ascii(str); 1056 | if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'"; 1057 | else return '"' + str.replace(/\x22/g, '\\"') + '"'; 1058 | }; 1059 | 1060 | function to_ascii(str) { 1061 | return str.replace(/[\u0080-\uffff]/g, function(ch) { 1062 | var code = ch.charCodeAt(0).toString(16); 1063 | while (code.length < 4) code = "0" + code; 1064 | return "\\u" + code; 1065 | }); 1066 | }; 1067 | 1068 | function gen_code(ast, options) { 1069 | options = defaults(options, { 1070 | indent_start : 0, 1071 | indent_level : 4, 1072 | quote_keys : false, 1073 | space_colon : false, 1074 | beautify : false, 1075 | ascii_only : false 1076 | }); 1077 | var beautify = !!options.beautify; 1078 | var indentation = 0, 1079 | newline = beautify ? "\n" : "", 1080 | space = beautify ? " " : ""; 1081 | 1082 | function encode_string(str) { 1083 | return make_string(str, options.ascii_only); 1084 | }; 1085 | 1086 | function make_name(name) { 1087 | name = name.toString(); 1088 | if (options.ascii_only) 1089 | name = to_ascii(name); 1090 | return name; 1091 | }; 1092 | 1093 | function indent(line) { 1094 | if (line == null) 1095 | line = ""; 1096 | if (beautify) 1097 | line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line; 1098 | return line; 1099 | }; 1100 | 1101 | function with_indent(cont, incr) { 1102 | if (incr == null) incr = 1; 1103 | indentation += incr; 1104 | try { return cont.apply(null, slice(arguments, 1)); } 1105 | finally { indentation -= incr; } 1106 | }; 1107 | 1108 | function add_spaces(a) { 1109 | if (beautify) 1110 | return a.join(" "); 1111 | var b = []; 1112 | for (var i = 0; i < a.length; ++i) { 1113 | var next = a[i + 1]; 1114 | b.push(a[i]); 1115 | if (next && 1116 | ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) || 1117 | (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) { 1118 | b.push(" "); 1119 | } 1120 | } 1121 | return b.join(""); 1122 | }; 1123 | 1124 | function add_commas(a) { 1125 | return a.join("," + space); 1126 | }; 1127 | 1128 | function parenthesize(expr) { 1129 | var gen = make(expr); 1130 | for (var i = 1; i < arguments.length; ++i) { 1131 | var el = arguments[i]; 1132 | if ((el instanceof Function && el(expr)) || expr[0] == el) 1133 | return "(" + gen + ")"; 1134 | } 1135 | return gen; 1136 | }; 1137 | 1138 | function best_of(a) { 1139 | if (a.length == 1) { 1140 | return a[0]; 1141 | } 1142 | if (a.length == 2) { 1143 | var b = a[1]; 1144 | a = a[0]; 1145 | return a.length <= b.length ? a : b; 1146 | } 1147 | return best_of([ a[0], best_of(a.slice(1)) ]); 1148 | }; 1149 | 1150 | function needs_parens(expr) { 1151 | if (expr[0] == "function" || expr[0] == "object") { 1152 | // dot/call on a literal function requires the 1153 | // function literal itself to be parenthesized 1154 | // only if it's the first "thing" in a 1155 | // statement. This means that the parent is 1156 | // "stat", but it could also be a "seq" and 1157 | // we're the first in this "seq" and the 1158 | // parent is "stat", and so on. Messy stuff, 1159 | // but it worths the trouble. 1160 | var a = slice($stack), self = a.pop(), p = a.pop(); 1161 | while (p) { 1162 | if (p[0] == "stat") return true; 1163 | if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) || 1164 | ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) { 1165 | self = p; 1166 | p = a.pop(); 1167 | } else { 1168 | return false; 1169 | } 1170 | } 1171 | } 1172 | return !HOP(DOT_CALL_NO_PARENS, expr[0]); 1173 | }; 1174 | 1175 | function make_num(num) { 1176 | var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m; 1177 | if (Math.floor(num) === num) { 1178 | a.push("0x" + num.toString(16).toLowerCase(), // probably pointless 1179 | "0" + num.toString(8)); // same. 1180 | if ((m = /^(.*?)(0+)$/.exec(num))) { 1181 | a.push(m[1] + "e" + m[2].length); 1182 | } 1183 | } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) { 1184 | a.push(m[2] + "e-" + (m[1].length + m[2].length), 1185 | str.substr(str.indexOf("."))); 1186 | } 1187 | return best_of(a); 1188 | }; 1189 | 1190 | var generators = { 1191 | "string": encode_string, 1192 | "num": make_num, 1193 | "name": make_name, 1194 | "toplevel": function(statements) { 1195 | return make_block_statements(statements) 1196 | .join(newline + newline); 1197 | }, 1198 | "block": make_block, 1199 | "var": function(defs) { 1200 | return "var " + add_commas(MAP(defs, make_1vardef)) + ";"; 1201 | }, 1202 | "const": function(defs) { 1203 | return "const " + add_commas(MAP(defs, make_1vardef)) + ";"; 1204 | }, 1205 | "try": function(tr, ca, fi) { 1206 | var out = [ "try", make_block(tr) ]; 1207 | if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1])); 1208 | if (fi) out.push("finally", make_block(fi)); 1209 | return add_spaces(out); 1210 | }, 1211 | "throw": function(expr) { 1212 | return add_spaces([ "throw", make(expr) ]) + ";"; 1213 | }, 1214 | "new": function(ctor, args) { 1215 | args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : ""; 1216 | return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){ 1217 | var w = ast_walker(), has_call = {}; 1218 | try { 1219 | w.with_walkers({ 1220 | "call": function() { throw has_call }, 1221 | "function": function() { return this } 1222 | }, function(){ 1223 | w.walk(expr); 1224 | }); 1225 | } catch(ex) { 1226 | if (ex === has_call) 1227 | return true; 1228 | throw ex; 1229 | } 1230 | }) + args ]); 1231 | }, 1232 | "switch": function(expr, body) { 1233 | return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]); 1234 | }, 1235 | "break": function(label) { 1236 | var out = "break"; 1237 | if (label != null) 1238 | out += " " + make_name(label); 1239 | return out + ";"; 1240 | }, 1241 | "continue": function(label) { 1242 | var out = "continue"; 1243 | if (label != null) 1244 | out += " " + make_name(label); 1245 | return out + ";"; 1246 | }, 1247 | "conditional": function(co, th, el) { 1248 | return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?", 1249 | parenthesize(th, "seq"), ":", 1250 | parenthesize(el, "seq") ]); 1251 | }, 1252 | "assign": function(op, lvalue, rvalue) { 1253 | if (op && op !== true) op += "="; 1254 | else op = "="; 1255 | return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]); 1256 | }, 1257 | "dot": function(expr) { 1258 | var out = make(expr), i = 1; 1259 | if (expr[0] == "num") { 1260 | if (!/\./.test(expr[1])) 1261 | out += "."; 1262 | } else if (needs_parens(expr)) 1263 | out = "(" + out + ")"; 1264 | while (i < arguments.length) 1265 | out += "." + make_name(arguments[i++]); 1266 | return out; 1267 | }, 1268 | "call": function(func, args) { 1269 | var f = make(func); 1270 | if (needs_parens(func)) 1271 | f = "(" + f + ")"; 1272 | return f + "(" + add_commas(MAP(args, function(expr){ 1273 | return parenthesize(expr, "seq"); 1274 | })) + ")"; 1275 | }, 1276 | "function": make_function, 1277 | "defun": make_function, 1278 | "if": function(co, th, el) { 1279 | var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ]; 1280 | if (el) { 1281 | out.push("else", make(el)); 1282 | } 1283 | return add_spaces(out); 1284 | }, 1285 | "for": function(init, cond, step, block) { 1286 | var out = [ "for" ]; 1287 | init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space); 1288 | cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space); 1289 | step = (step != null ? make(step) : "").replace(/;*\s*$/, ""); 1290 | var args = init + cond + step; 1291 | if (args == "; ; ") args = ";;"; 1292 | out.push("(" + args + ")", make(block)); 1293 | return add_spaces(out); 1294 | }, 1295 | "for-in": function(vvar, key, hash, block) { 1296 | return add_spaces([ "for", "(" + 1297 | (vvar ? make(vvar).replace(/;+$/, "") : make(key)), 1298 | "in", 1299 | make(hash) + ")", make(block) ]); 1300 | }, 1301 | "while": function(condition, block) { 1302 | return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]); 1303 | }, 1304 | "do": function(condition, block) { 1305 | return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";"; 1306 | }, 1307 | "return": function(expr) { 1308 | var out = [ "return" ]; 1309 | if (expr != null) out.push(make(expr)); 1310 | return add_spaces(out) + ";"; 1311 | }, 1312 | "binary": function(operator, lvalue, rvalue) { 1313 | var left = make(lvalue), right = make(rvalue); 1314 | // XXX: I'm pretty sure other cases will bite here. 1315 | // we need to be smarter. 1316 | // adding parens all the time is the safest bet. 1317 | if (member(lvalue[0], [ "assign", "conditional", "seq" ]) || 1318 | lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) { 1319 | left = "(" + left + ")"; 1320 | } 1321 | if (member(rvalue[0], [ "assign", "conditional", "seq" ]) || 1322 | rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] && 1323 | !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) { 1324 | right = "(" + right + ")"; 1325 | } 1326 | return add_spaces([ left, operator, right ]); 1327 | }, 1328 | "unary-prefix": function(operator, expr) { 1329 | var val = make(expr); 1330 | if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr))) 1331 | val = "(" + val + ")"; 1332 | return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val; 1333 | }, 1334 | "unary-postfix": function(operator, expr) { 1335 | var val = make(expr); 1336 | if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr))) 1337 | val = "(" + val + ")"; 1338 | return val + operator; 1339 | }, 1340 | "sub": function(expr, subscript) { 1341 | var hash = make(expr); 1342 | if (needs_parens(expr)) 1343 | hash = "(" + hash + ")"; 1344 | return hash + "[" + make(subscript) + "]"; 1345 | }, 1346 | "object": function(props) { 1347 | if (props.length == 0) 1348 | return "{}"; 1349 | return "{" + newline + with_indent(function(){ 1350 | return MAP(props, function(p){ 1351 | if (p.length == 3) { 1352 | // getter/setter. The name is in p[0], the arg.list in p[1][2], the 1353 | // body in p[1][3] and type ("get" / "set") in p[2]. 1354 | return indent(make_function(p[0], p[1][2], p[1][3], p[2])); 1355 | } 1356 | var key = p[0], val = make(p[1]); 1357 | if (options.quote_keys) { 1358 | key = encode_string(key); 1359 | } else if ((typeof key == "number" || !beautify && +key + "" == key) 1360 | && parseFloat(key) >= 0) { 1361 | key = make_num(+key); 1362 | } else if (!is_identifier(key)) { 1363 | key = encode_string(key); 1364 | } 1365 | return indent(add_spaces(beautify && options.space_colon 1366 | ? [ key, ":", val ] 1367 | : [ key + ":", val ])); 1368 | }).join("," + newline); 1369 | }) + newline + indent("}"); 1370 | }, 1371 | "regexp": function(rx, mods) { 1372 | return "/" + rx + "/" + mods; 1373 | }, 1374 | "array": function(elements) { 1375 | if (elements.length == 0) return "[]"; 1376 | return add_spaces([ "[", add_commas(MAP(elements, function(el){ 1377 | if (!beautify && el[0] == "atom" && el[1] == "undefined") return ""; 1378 | return parenthesize(el, "seq"); 1379 | })), "]" ]); 1380 | }, 1381 | "stat": function(stmt) { 1382 | return make(stmt).replace(/;*\s*$/, ";"); 1383 | }, 1384 | "seq": function() { 1385 | return add_commas(MAP(slice(arguments), make)); 1386 | }, 1387 | "label": function(name, block) { 1388 | return add_spaces([ make_name(name), ":", make(block) ]); 1389 | }, 1390 | "with": function(expr, block) { 1391 | return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]); 1392 | }, 1393 | "atom": function(name) { 1394 | return make_name(name); 1395 | } 1396 | }; 1397 | 1398 | // The squeezer replaces "block"-s that contain only a single 1399 | // statement with the statement itself; technically, the AST 1400 | // is correct, but this can create problems when we output an 1401 | // IF having an ELSE clause where the THEN clause ends in an 1402 | // IF *without* an ELSE block (then the outer ELSE would refer 1403 | // to the inner IF). This function checks for this case and 1404 | // adds the block brackets if needed. 1405 | function make_then(th) { 1406 | if (th[0] == "do") { 1407 | // https://github.com/mishoo/UglifyJS/issues/#issue/57 1408 | // IE croaks with "syntax error" on code like this: 1409 | // if (foo) do ... while(cond); else ... 1410 | // we need block brackets around do/while 1411 | return make([ "block", [ th ]]); 1412 | } 1413 | var b = th; 1414 | while (true) { 1415 | var type = b[0]; 1416 | if (type == "if") { 1417 | if (!b[3]) 1418 | // no else, we must add the block 1419 | return make([ "block", [ th ]]); 1420 | b = b[3]; 1421 | } 1422 | else if (type == "while" || type == "do") b = b[2]; 1423 | else if (type == "for" || type == "for-in") b = b[4]; 1424 | else break; 1425 | } 1426 | return make(th); 1427 | }; 1428 | 1429 | function make_function(name, args, body, keyword) { 1430 | var out = keyword || "function"; 1431 | if (name) { 1432 | out += " " + make_name(name); 1433 | } 1434 | out += "(" + add_commas(MAP(args, make_name)) + ")"; 1435 | return add_spaces([ out, make_block(body) ]); 1436 | }; 1437 | 1438 | function make_block_statements(statements) { 1439 | for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) { 1440 | var stat = statements[i]; 1441 | var code = make(stat); 1442 | if (code != ";") { 1443 | if (!beautify && i == last) { 1444 | if ((stat[0] == "while" && empty(stat[2])) || 1445 | (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) || 1446 | (stat[0] == "if" && empty(stat[2]) && !stat[3]) || 1447 | (stat[0] == "if" && stat[3] && empty(stat[3]))) { 1448 | code = code.replace(/;*\s*$/, ";"); 1449 | } else { 1450 | code = code.replace(/;+\s*$/, ""); 1451 | } 1452 | } 1453 | a.push(code); 1454 | } 1455 | } 1456 | return MAP(a, indent); 1457 | }; 1458 | 1459 | function make_switch_block(body) { 1460 | var n = body.length; 1461 | if (n == 0) return "{}"; 1462 | return "{" + newline + MAP(body, function(branch, i){ 1463 | var has_body = branch[1].length > 0, code = with_indent(function(){ 1464 | return indent(branch[0] 1465 | ? add_spaces([ "case", make(branch[0]) + ":" ]) 1466 | : "default:"); 1467 | }, 0.5) + (has_body ? newline + with_indent(function(){ 1468 | return make_block_statements(branch[1]).join(newline); 1469 | }) : ""); 1470 | if (!beautify && has_body && i < n - 1) 1471 | code += ";"; 1472 | return code; 1473 | }).join(newline) + newline + indent("}"); 1474 | }; 1475 | 1476 | function make_block(statements) { 1477 | if (!statements) return ";"; 1478 | if (statements.length == 0) return "{}"; 1479 | return "{" + newline + with_indent(function(){ 1480 | return make_block_statements(statements).join(newline); 1481 | }) + newline + indent("}"); 1482 | }; 1483 | 1484 | function make_1vardef(def) { 1485 | var name = def[0], val = def[1]; 1486 | if (val != null) 1487 | name = add_spaces([ make_name(name), "=", make(val) ]); 1488 | return name; 1489 | }; 1490 | 1491 | var $stack = []; 1492 | 1493 | function make(node) { 1494 | var type = node[0]; 1495 | var gen = generators[type]; 1496 | if (!gen) 1497 | throw new Error("Can't find generator for \"" + type + "\""); 1498 | $stack.push(node); 1499 | var ret = gen.apply(type, node.slice(1)); 1500 | $stack.pop(); 1501 | return ret; 1502 | }; 1503 | 1504 | return make(ast); 1505 | }; 1506 | 1507 | function split_lines(code, max_line_length) { 1508 | var splits = [ 0 ]; 1509 | jsp.parse(function(){ 1510 | var next_token = jsp.tokenizer(code); 1511 | var last_split = 0; 1512 | var prev_token; 1513 | function current_length(tok) { 1514 | return tok.pos - last_split; 1515 | }; 1516 | function split_here(tok) { 1517 | last_split = tok.pos; 1518 | splits.push(last_split); 1519 | }; 1520 | function custom(){ 1521 | var tok = next_token.apply(this, arguments); 1522 | out: { 1523 | if (prev_token) { 1524 | if (prev_token.type == "keyword") break out; 1525 | } 1526 | if (current_length(tok) > max_line_length) { 1527 | switch (tok.type) { 1528 | case "keyword": 1529 | case "atom": 1530 | case "name": 1531 | case "punc": 1532 | split_here(tok); 1533 | break out; 1534 | } 1535 | } 1536 | } 1537 | prev_token = tok; 1538 | return tok; 1539 | }; 1540 | custom.context = function() { 1541 | return next_token.context.apply(this, arguments); 1542 | }; 1543 | return custom; 1544 | }()); 1545 | return splits.map(function(pos, i){ 1546 | return code.substring(pos, splits[i + 1] || code.length); 1547 | }).join("\n"); 1548 | }; 1549 | 1550 | /* -----[ Utilities ]----- */ 1551 | 1552 | function repeat_string(str, i) { 1553 | if (i <= 0) return ""; 1554 | if (i == 1) return str; 1555 | var d = repeat_string(str, i >> 1); 1556 | d += d; 1557 | if (i & 1) d += str; 1558 | return d; 1559 | }; 1560 | 1561 | function defaults(args, defs) { 1562 | var ret = {}; 1563 | if (args === true) 1564 | args = {}; 1565 | for (var i in defs) if (HOP(defs, i)) { 1566 | ret[i] = (args && HOP(args, i)) ? args[i] : defs[i]; 1567 | } 1568 | return ret; 1569 | }; 1570 | 1571 | function is_identifier(name) { 1572 | return /^[a-z_$][a-z0-9_$]*$/i.test(name) 1573 | && name != "this" 1574 | && !HOP(jsp.KEYWORDS_ATOM, name) 1575 | && !HOP(jsp.RESERVED_WORDS, name) 1576 | && !HOP(jsp.KEYWORDS, name); 1577 | }; 1578 | 1579 | function HOP(obj, prop) { 1580 | return Object.prototype.hasOwnProperty.call(obj, prop); 1581 | }; 1582 | 1583 | // some utilities 1584 | 1585 | var MAP; 1586 | 1587 | (function(){ 1588 | MAP = function(a, f, o) { 1589 | var ret = []; 1590 | for (var i = 0; i < a.length; ++i) { 1591 | var val = f.call(o, a[i], i); 1592 | if (val instanceof AtTop) ret.unshift(val.v); 1593 | else ret.push(val); 1594 | } 1595 | return ret; 1596 | }; 1597 | MAP.at_top = function(val) { return new AtTop(val) }; 1598 | function AtTop(val) { this.v = val }; 1599 | })(); 1600 | 1601 | /* -----[ Exports ]----- */ 1602 | 1603 | exports.ast_walker = ast_walker; 1604 | exports.ast_mangle = ast_mangle; 1605 | exports.ast_squeeze = ast_squeeze; 1606 | exports.gen_code = gen_code; 1607 | exports.ast_add_scope = ast_add_scope; 1608 | exports.set_logger = function(logger) { warn = logger }; 1609 | exports.make_string = make_string; 1610 | exports.split_lines = split_lines; 1611 | exports.MAP = MAP; 1612 | 1613 | // keep this last! 1614 | exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more; --------------------------------------------------------------------------------