FIX suppression des /;;.*$/ (définit étant un commentaire) lors de la minification...
[euphorik.git] / tools / jsmin.js
1 /* jsmin.js - 2006-08-31
2 Author: Franck Marcia
3 This work is an adaptation of jsminc.c published by Douglas Crockford.
4 Permission is hereby granted to use the Javascript version under the same
5 conditions as the jsmin.c on which it is based.
6
7 jsmin.c
8 2006-05-04
9
10 Copyright (c) 2002 Douglas Crockford (www.crockford.com)
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy of
13 this software and associated documentation files (the "Software"), to deal in
14 the Software without restriction, including without limitation the rights to
15 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
16 of the Software, and to permit persons to whom the Software is furnished to do
17 so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 The Software shall be used for Good, not Evil.
23
24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 SOFTWARE.
31
32 Update:
33 add level:
34 1: minimal, keep linefeeds if single
35 2: normal, the standard algorithm
36 3: agressive, remove any linefeed and doesn't take care of potential
37 missing semicolons (can be regressive)
38 store stats
39 jsmin.oldSize
40 jsmin.newSize
41 */
42
43 String.prototype.has = function(c) {
44 return this.indexOf(c) > -1;
45 };
46
47 function jsmin(comment, input, level) {
48
49 if (input === undefined) {
50 input = comment;
51 comment = '';
52 level = 2;
53 } else if (level === undefined || level < 1 || level > 3) {
54 level = 2;
55 }
56
57 if (comment.length > 0) {
58 comment += '\n';
59 }
60
61 var a = '',
62 b = '',
63 EOF = -1,
64 LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
65 DIGITS = '0123456789',
66 ALNUM = LETTERS + DIGITS + '_$\\',
67 theLookahead = EOF;
68
69
70 /* isAlphanum -- return true if the character is a letter, digit, underscore,
71 dollar sign, or non-ASCII character.
72 */
73
74 function isAlphanum(c) {
75 return c != EOF && (ALNUM.has(c) || c.charCodeAt(0) > 126);
76 }
77
78
79 /* get -- return the next character. Watch out for lookahead. If the
80 character is a control character, translate it to a space or
81 linefeed.
82 */
83
84 function get() {
85
86 var c = theLookahead;
87 if (get.i == get.l) {
88 return EOF;
89 }
90 theLookahead = EOF;
91 if (c == EOF) {
92 c = input.charAt(get.i);
93 ++get.i;
94 }
95 if (c >= ' ' || c == '\n') {
96 return c;
97 }
98 if (c == '\r') {
99 return '\n';
100 }
101 return ' ';
102 }
103
104 get.i = 0;
105 get.l = input.length;
106
107
108 /* peek -- get the next character without getting it.
109 */
110
111 function peek() {
112 theLookahead = get();
113 return theLookahead;
114 }
115
116
117 /* next -- get the next character, excluding comments. peek() is used to see
118 if a '/' is followed by a '/' or '*'.
119 */
120
121 function next() {
122
123 var c = get();
124
125 if (c === ";" && peek() === ";") {
126 for (;;) {
127 c = get();
128 if (c <= '\n') {
129 return c;
130 }
131 }
132 }
133
134 if (c == '/') {
135 switch (peek()) {
136 case '/':
137 for (;;) {
138 c = get();
139 if (c <= '\n') {
140 return c;
141 }
142 }
143 break;
144 case '*':
145 get();
146 for (;;) {
147 switch (get()) {
148 case '*':
149 if (peek() == '/') {
150 get();
151 return ' ';
152 }
153 break;
154 case EOF:
155 throw 'Error: Unterminated comment.';
156 }
157 }
158 break;
159 default:
160 return c;
161 }
162 }
163 return c;
164 }
165
166
167 /* action -- do something! What you do is determined by the argument:
168 1 Output A. Copy B to A. Get the next B.
169 2 Copy B to A. Get the next B. (Delete A).
170 3 Get the next B. (Delete B).
171 action treats a string as a single character. Wow!
172 action recognizes a regular expression if it is preceded by ( or , or =.
173 */
174
175 function action(d) {
176
177 var r = [];
178
179 if (d == 1) {
180 r.push(a);
181 }
182
183 if (d < 3) {
184 a = b;
185 if (a == '\'' || a == '"') {
186 for (;;) {
187 r.push(a);
188 a = get();
189 if (a == b) {
190 break;
191 }
192 if (a <= '\n') {
193 throw 'Error: unterminated string literal: ' + a;
194 }
195 if (a == '\\') {
196 r.push(a);
197 a = get();
198 }
199 }
200 }
201 }
202
203 b = next();
204
205 if (b == '/' && '(,=:[!&|'.has(a)) {
206 r.push(a);
207 r.push(b);
208 for (;;) {
209 a = get();
210 if (a == '/') {
211 break;
212 } else if (a =='\\') {
213 r.push(a);
214 a = get();
215 } else if (a <= '\n') {
216 throw 'Error: unterminated Regular Expression literal';
217 }
218 r.push(a);
219 }
220 b = next();
221 }
222
223 return r.join('');
224 }
225
226
227 /* m -- Copy the input to the output, deleting the characters which are
228 insignificant to JavaScript. Comments will be removed. Tabs will be
229 replaced with spaces. Carriage returns will be replaced with
230 linefeeds.
231 Most spaces and linefeeds will be removed.
232 */
233
234 function m() {
235
236 var r = [];
237 a = '\n';
238
239 r.push(action(3));
240
241 while (a != EOF) {
242 switch (a) {
243 case ' ':
244 if (isAlphanum(b)) {
245 r.push(action(1));
246 } else {
247 r.push(action(2));
248 }
249 break;
250 case '\n':
251 switch (b) {
252 case '{':
253 case '[':
254 case '(':
255 case '+':
256 case '-':
257 r.push(action(1));
258 break;
259 case ' ':
260 r.push(action(3));
261 break;
262 default:
263 if (isAlphanum(b)) {
264 r.push(action(1));
265 } else {
266 if (level == 1 && b != '\n') {
267 r.push(action(1));
268 } else {
269 r.push(action(2));
270 }
271 }
272 }
273 break;
274 default:
275 switch (b) {
276 case ' ':
277 if (isAlphanum(a)) {
278 r.push(action(1));
279 break;
280 }
281 r.push(action(3));
282 break;
283 case '\n':
284 if (level == 1 && a != '\n') {
285 r.push(action(1));
286 } else {
287 switch (a) {
288 case '}':
289 case ']':
290 case ')':
291 case '+':
292 case '-':
293 case '"':
294 case '\'':
295 if (level == 3) {
296 r.push(action(3));
297 } else {
298 r.push(action(1));
299 }
300 break;
301 default:
302 if (isAlphanum(a)) {
303 r.push(action(1));
304 } else {
305 r.push(action(3));
306 }
307 }
308 }
309 break;
310 default:
311 r.push(action(1));
312 break;
313 }
314 }
315 }
316
317 return r.join('');
318 }
319
320 jsmin.oldSize = input.length;
321 ret = m(input);
322 jsmin.newSize = ret.length;
323
324 return comment + ret;
325
326 }
327
328 (function (a) {
329 if (!a[0]) {
330 print("Usage: jsmin.js file.js");
331 quit(1);
332 }
333 var input = readFile(a[0]);
334 if (!input) {
335 print("jslint: Couldn't open file '" + a[0] + "'.");
336 quit(1);
337 }
338 print(jsmin(input));
339 })(arguments);