ADD version JavaScript de jsmin (à la place de jsmin.rb)
[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 if (c == '/') {
125 switch (peek()) {
126 case '/':
127 for (;;) {
128 c = get();
129 if (c <= '\n') {
130 return c;
131 }
132 }
133 break;
134 case '*':
135 get();
136 for (;;) {
137 switch (get()) {
138 case '*':
139 if (peek() == '/') {
140 get();
141 return ' ';
142 }
143 break;
144 case EOF:
145 throw 'Error: Unterminated comment.';
146 }
147 }
148 break;
149 default:
150 return c;
151 }
152 }
153 return c;
154 }
155
156
157 /* action -- do something! What you do is determined by the argument:
158 1 Output A. Copy B to A. Get the next B.
159 2 Copy B to A. Get the next B. (Delete A).
160 3 Get the next B. (Delete B).
161 action treats a string as a single character. Wow!
162 action recognizes a regular expression if it is preceded by ( or , or =.
163 */
164
165 function action(d) {
166
167 var r = [];
168
169 if (d == 1) {
170 r.push(a);
171 }
172
173 if (d < 3) {
174 a = b;
175 if (a == '\'' || a == '"') {
176 for (;;) {
177 r.push(a);
178 a = get();
179 if (a == b) {
180 break;
181 }
182 if (a <= '\n') {
183 throw 'Error: unterminated string literal: ' + a;
184 }
185 if (a == '\\') {
186 r.push(a);
187 a = get();
188 }
189 }
190 }
191 }
192
193 b = next();
194
195 if (b == '/' && '(,=:[!&|'.has(a)) {
196 r.push(a);
197 r.push(b);
198 for (;;) {
199 a = get();
200 if (a == '/') {
201 break;
202 } else if (a =='\\') {
203 r.push(a);
204 a = get();
205 } else if (a <= '\n') {
206 throw 'Error: unterminated Regular Expression literal';
207 }
208 r.push(a);
209 }
210 b = next();
211 }
212
213 return r.join('');
214 }
215
216
217 /* m -- Copy the input to the output, deleting the characters which are
218 insignificant to JavaScript. Comments will be removed. Tabs will be
219 replaced with spaces. Carriage returns will be replaced with
220 linefeeds.
221 Most spaces and linefeeds will be removed.
222 */
223
224 function m() {
225
226 var r = [];
227 a = '\n';
228
229 r.push(action(3));
230
231 while (a != EOF) {
232 switch (a) {
233 case ' ':
234 if (isAlphanum(b)) {
235 r.push(action(1));
236 } else {
237 r.push(action(2));
238 }
239 break;
240 case '\n':
241 switch (b) {
242 case '{':
243 case '[':
244 case '(':
245 case '+':
246 case '-':
247 r.push(action(1));
248 break;
249 case ' ':
250 r.push(action(3));
251 break;
252 default:
253 if (isAlphanum(b)) {
254 r.push(action(1));
255 } else {
256 if (level == 1 && b != '\n') {
257 r.push(action(1));
258 } else {
259 r.push(action(2));
260 }
261 }
262 }
263 break;
264 default:
265 switch (b) {
266 case ' ':
267 if (isAlphanum(a)) {
268 r.push(action(1));
269 break;
270 }
271 r.push(action(3));
272 break;
273 case '\n':
274 if (level == 1 && a != '\n') {
275 r.push(action(1));
276 } else {
277 switch (a) {
278 case '}':
279 case ']':
280 case ')':
281 case '+':
282 case '-':
283 case '"':
284 case '\'':
285 if (level == 3) {
286 r.push(action(3));
287 } else {
288 r.push(action(1));
289 }
290 break;
291 default:
292 if (isAlphanum(a)) {
293 r.push(action(1));
294 } else {
295 r.push(action(3));
296 }
297 }
298 }
299 break;
300 default:
301 r.push(action(1));
302 break;
303 }
304 }
305 }
306
307 return r.join('');
308 }
309
310 jsmin.oldSize = input.length;
311 ret = m(input);
312 jsmin.newSize = ret.length;
313
314 return comment + ret;
315
316 }
317
318 (function (a) {
319 if (!a[0]) {
320 print("Usage: jsmin.js file.js");
321 quit(1);
322 }
323 var input = readFile(a[0]);
324 if (!input) {
325 print("jslint: Couldn't open file '" + a[0] + "'.");
326 quit(1);
327 }
328 print(jsmin(input));
329 })(arguments);