Updated Packer - the current version was working incorrectly.
[jquery.git] / build / js / Packer.js
1 /*\r
2         Packer version 3.0 (beta 8) - copyright 2004-2007, Dean Edwards\r
3         http://www.opensource.org/licenses/mit-license\r
4 */\r
5 \r
6 eval(base2.namespace);\r
7 \r
8 var IGNORE = RegGrp.IGNORE;\r
9 var REMOVE = "";\r
10 var SPACE = " ";\r
11 var WORDS = /\w+/g;\r
12 \r
13 var Packer = Base.extend({\r
14         minify: function(script) {\r
15                 script = script.replace(Packer.CONTINUE, "");\r
16                 script = Packer.data.exec(script);\r
17                 script = Packer.whitespace.exec(script);\r
18                 script = Packer.clean.exec(script);\r
19                 return script;\r
20         },\r
21         \r
22         pack: function(script, base62, shrink) {\r
23                 script = this.minify(script + "\n");\r
24                 if (shrink) script = this._shrinkVariables(script);\r
25                 if (base62) script = this._base62Encode(script);        \r
26                 return script;\r
27         },\r
28         \r
29         _base62Encode: function(script) {\r
30                 var words = new Words(script);\r
31                 var encode = function(word) {\r
32                         return words.fetch(word).encoded;\r
33                 };\r
34                 \r
35                 /* build the packed script */\r
36                 \r
37                 var p = this._escape(script.replace(WORDS, encode));            \r
38                 var a = Math.min(Math.max(words.count(), 2), 62);               \r
39                 var c = words.count();          \r
40                 var k = words;\r
41                 var e = Packer["ENCODE" + (a > 10 ? a > 36 ? 62 : 36 : 10)];\r
42                 var r = a > 10 ? "e(c)" : "c";\r
43                 \r
44                 // the whole thing\r
45                 return format(Packer.UNPACK, p,a,c,k,e,r);\r
46         },\r
47         \r
48         _escape: function(script) {\r
49                 // single quotes wrap the final string so escape them\r
50                 // also escape new lines required by conditional comments\r
51                 return script.replace(/([\\'])/g, "\\$1").replace(/[\r\n]+/g, "\\n");\r
52         },\r
53         \r
54         _shrinkVariables: function(script) {\r
55                 // Windows Scripting Host cannot do regexp.test() on global regexps.\r
56                 var global = function(regexp) {\r
57                         // This function creates a global version of the passed regexp.\r
58                         return new RegExp(regexp.source, "g");\r
59                 };\r
60                 \r
61                 var data = []; // encoded strings and regular expressions\r
62                 var REGEXP = /^[^'"]\//;\r
63                 var store = function(string) {\r
64                         var replacement = "#" + data.length;\r
65                         if (REGEXP.test(string)) {\r
66                                 replacement = string.charAt(0) + replacement;\r
67                                 string = string.slice(1);\r
68                         }\r
69                         data.push(string);\r
70                         return replacement;\r
71                 };\r
72                 \r
73                 // Base52 encoding (a-Z)\r
74                 var encode52 = function(c) {\r
75                         return (c < 52 ? '' : arguments.callee(parseInt(c / 52))) +\r
76                                 ((c = c % 52) > 25 ? String.fromCharCode(c + 39) : String.fromCharCode(c + 97));\r
77                 };\r
78                                 \r
79                 // identify blocks, particularly identify function blocks (which define scope)\r
80                 var BLOCK = /(function\s*[\w$]*\s*\(\s*([^\)]*)\s*\)\s*)?(\{([^{}]*)\})/;\r
81                 var VAR_ = /var\s+/g;\r
82                 var VAR_NAME = /var\s+[\w$]+/g;\r
83                 var COMMA = /\s*,\s*/;\r
84                 var blocks = []; // store program blocks (anything between braces {})\r
85                 // encoder for program blocks\r
86                 var encode = function(block, func, args) {\r
87                         if (func) { // the block is a function block\r
88                         \r
89                                 // decode the function block (THIS IS THE IMPORTANT BIT)\r
90                                 // We are retrieving all sub-blocks and will re-parse them in light\r
91                                 // of newly shrunk variables\r
92                                 block = decode(block);\r
93                                 \r
94                                 // create the list of variable and argument names \r
95                                 var vars = match(block, VAR_NAME).join(",").replace(VAR_, "");\r
96                                 var ids = Array2.combine(args.split(COMMA).concat(vars.split(COMMA)));\r
97                                 \r
98                                 // process each identifier\r
99                                 var count = 0, shortId;\r
100                                 forEach (ids, function(id) {\r
101                                         id = trim(id);\r
102                                         if (id && id.length > 1) { // > 1 char\r
103                                                 id = rescape(id);\r
104                                                 // find the next free short name (check everything in the current scope)\r
105                                                 do shortId = encode52(count++);\r
106                                                 while (new RegExp("[^\\w$.]" + shortId + "[^\\w$:]").test(block));\r
107                                                 // replace the long name with the short name\r
108                                                 var reg = new RegExp("([^\\w$.])" + id + "([^\\w$:])");\r
109                                                 while (reg.test(block)) block = block.replace(global(reg), "$1" + shortId + "$2");\r
110                                                 var reg = new RegExp("([^{,])" + id + ":", "g");\r
111                                                 block = block.replace(reg, "$1" + shortId + ":");\r
112                                         }\r
113                                 });\r
114                         }\r
115                         var replacement = "~" + blocks.length + "~";\r
116                         blocks.push(block);\r
117                         return replacement;\r
118                 };\r
119                 \r
120                 // decoder for program blocks\r
121                 var ENCODED = /~(\d+)~/;\r
122                 var decode = function(script) {\r
123                         while (ENCODED.test(script)) {\r
124                                 script = script.replace(global(ENCODED), function(match, index) {\r
125                                         return blocks[index];\r
126                                 });\r
127                         }\r
128                         return script;\r
129                 };\r
130                 \r
131                 // encode strings and regular expressions\r
132                 script = Packer.data.exec(script, store);\r
133                 \r
134                 // remove closures (this is for base2 namespaces only)\r
135                 script = script.replace(/new function\(_\)\s*\{/g, "{;#;");\r
136                 \r
137                 // encode blocks, as we encode we replace variable and argument names\r
138                 while (BLOCK.test(script)) {\r
139                         script = script.replace(global(BLOCK), encode);\r
140                 }\r
141                 \r
142                 // put the blocks back\r
143                 script = decode(script);\r
144                 \r
145                 // put back the closure (for base2 namespaces only)\r
146                 script = script.replace(/\{;#;/g, "new function(_){");\r
147                 \r
148                 // put strings and regular expressions back\r
149                 script = script.replace(/#(\d+)/g, function(match, index) {             \r
150                         return data[index];\r
151                 });\r
152                 \r
153                 return script;\r
154         }\r
155 }, {\r
156         CONTINUE: /\\\r?\n/g,\r
157         \r
158         ENCODE10: "String",\r
159         ENCODE36: "function(c){return c.toString(a)}",\r
160         ENCODE62: "function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}",\r
161         \r
162         UNPACK: "eval(function(p,a,c,k,e,r){e=%5;if(!''.replace(/^/,String)){while(c--)r[%6]=k[c]" +\r
163                 "||%6;k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p." +\r
164                         "replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('%1',%2,%3,'%4'.split('|'),0,{}))",\r
165         \r
166         init: function() {\r
167                 this.data = reduce(this.data, function(data, replacement, expression) {\r
168                         data.store(this.javascript.exec(expression), replacement);\r
169                         return data;\r
170                 }, new RegGrp, this);\r
171                 this.clean = this.data.union(this.clean);\r
172                 this.whitespace = this.data.union(this.whitespace);\r
173         },\r
174         \r
175         clean: {\r
176                 "\\(\\s*;\\s*;\\s*\\)": "(;;)", // for (;;) loops\r
177                 "throw[^};]+[};]": IGNORE, // a safari 1.3 bug\r
178                 ";+\\s*([};])": "$1"\r
179         },\r
180         \r
181         data: {\r
182                 // strings\r
183                 "STRING1": IGNORE,\r
184                 'STRING2': IGNORE,\r
185                 "CONDITIONAL": IGNORE, // conditional comments\r
186                 "(COMMENT1)\\n\\s*(REGEXP)?": "\n$3",\r
187                 "(COMMENT2)\\s*(REGEXP)?": " $3",\r
188                 "([\\[(\\^=,{}:;&|!*?])\\s*(REGEXP)": "$1$2"\r
189         },\r
190         \r
191         javascript: new RegGrp({\r
192                 COMMENT1:    /(\/\/|;;;)[^\n]*/.source,\r
193                 COMMENT2:    /\/\*[^*]*\*+([^\/][^*]*\*+)*\//.source,\r
194                 CONDITIONAL: /\/\*@|@\*\/|\/\/@[^\n]*\n/.source,\r
195                 REGEXP:      /\/(\\[\/\\]|[^*\/])(\\.|[^\/\n\\])*\/[gim]*/.source,\r
196                 STRING1:     /'(\\.|[^'\\])*'/.source,\r
197                 STRING2:     /"(\\.|[^"\\])*"/.source\r
198         }),\r
199         \r
200         whitespace: {\r
201                 "(\\d)\\s+(\\.\\s*[a-z\\$_\\[(])": "$1 $2", // http://dean.edwards.name/weblog/2007/04/packer3/#comment84066\r
202                 "([+-])\\s+([+-])": "$1 $2", // c = a++ +b;\r
203                 "\\b\\s+\\$\\s+\\b": " $ ", // var $ in\r
204                 "\\$\\s+\\b": "$ ", // object$ in\r
205                 "\\b\\s+\\$": " $", // return $object\r
206                 "\\b\\s+\\b": SPACE,\r
207                 "\\s+": REMOVE\r
208         }\r
209 });\r