|
1 # |
|
2 # Jasy - Web Tooling Framework |
|
3 # Copyright 2010-2012 Zynga Inc. |
|
4 # Copyright 2013-2014 Sebastian Werner |
|
5 # |
|
6 |
|
7 from __future__ import unicode_literals |
|
8 |
|
9 import re, sys, json |
|
10 |
|
11 from jasy.script.tokenize.Lang import keywords |
|
12 from jasy.script.parse.Lang import expressions, futureReserved |
|
13 |
|
14 high_unicode = re.compile(r"\\u[2-9A-Fa-f][0-9A-Fa-f]{3}") |
|
15 ascii_encoder = json.JSONEncoder(ensure_ascii=True) |
|
16 unicode_encoder = json.JSONEncoder(ensure_ascii=False) |
|
17 |
|
18 # |
|
19 # Class |
|
20 # |
|
21 |
|
22 class Compressor: |
|
23 __semicolonSymbol = ";" |
|
24 __commaSymbol = "," |
|
25 |
|
26 |
|
27 def __init__(self, format=None): |
|
28 if format: |
|
29 if format.has("semicolon"): |
|
30 self.__semicolonSymbol = ";\n" |
|
31 |
|
32 if format.has("comma"): |
|
33 self.__commaSymbol = ",\n" |
|
34 |
|
35 self.__forcedSemicolon = False |
|
36 |
|
37 |
|
38 |
|
39 # |
|
40 # Main |
|
41 # |
|
42 |
|
43 def compress(self, node): |
|
44 type = node.type |
|
45 result = None |
|
46 |
|
47 if type in self.__simple: |
|
48 result = type |
|
49 elif type in self.__prefixes: |
|
50 if getattr(node, "postfix", False): |
|
51 result = self.compress(node[0]) + self.__prefixes[node.type] |
|
52 else: |
|
53 result = self.__prefixes[node.type] + self.compress(node[0]) |
|
54 |
|
55 elif type in self.__dividers: |
|
56 first = self.compress(node[0]) |
|
57 second = self.compress(node[1]) |
|
58 divider = self.__dividers[node.type] |
|
59 |
|
60 # Fast path |
|
61 if node.type not in ("plus", "minus"): |
|
62 result = "%s%s%s" % (first, divider, second) |
|
63 |
|
64 # Special code for dealing with situations like x + ++y and y-- - x |
|
65 else: |
|
66 result = first |
|
67 if first.endswith(divider): |
|
68 result += " " |
|
69 |
|
70 result += divider |
|
71 |
|
72 if second.startswith(divider): |
|
73 result += " " |
|
74 |
|
75 result += second |
|
76 |
|
77 else: |
|
78 try: |
|
79 result = getattr(self, "type_%s" % type)(node) |
|
80 except AttributeError: |
|
81 raise Exception("Script compressor does not support type '%s' from line %s in file %s" % (type, node.line, node.getFileName())) |
|
82 |
|
83 if getattr(node, "parenthesized", None): |
|
84 return "(%s)" % result |
|
85 else: |
|
86 return result |
|
87 |
|
88 |
|
89 |
|
90 # |
|
91 # Helpers |
|
92 # |
|
93 |
|
94 def __statements(self, node): |
|
95 result = [] |
|
96 for child in node: |
|
97 result.append(self.compress(child)) |
|
98 |
|
99 return "".join(result) |
|
100 |
|
101 def __handleForcedSemicolon(self, node): |
|
102 if node.type == "semicolon" and not hasattr(node, "expression"): |
|
103 self.__forcedSemicolon = True |
|
104 |
|
105 def __addSemicolon(self, result): |
|
106 if not result.endswith(self.__semicolonSymbol): |
|
107 if self.__forcedSemicolon: |
|
108 self.__forcedSemicolon = False |
|
109 |
|
110 return result + self.__semicolonSymbol |
|
111 |
|
112 else: |
|
113 return result |
|
114 |
|
115 def __removeSemicolon(self, result): |
|
116 if self.__forcedSemicolon: |
|
117 self.__forcedSemicolon = False |
|
118 return result |
|
119 |
|
120 if result.endswith(self.__semicolonSymbol): |
|
121 return result[:-len(self.__semicolonSymbol)] |
|
122 else: |
|
123 return result |
|
124 |
|
125 |
|
126 # |
|
127 # Data |
|
128 # |
|
129 |
|
130 __simple_property = re.compile(r"^[a-zA-Z_$][a-zA-Z0-9_$]*$") |
|
131 __number_property = re.compile(r"^[0-9]+$") |
|
132 |
|
133 __simple = ["true", "false", "null", "this", "debugger"] |
|
134 |
|
135 __dividers = { |
|
136 "plus" : '+', |
|
137 "minus" : '-', |
|
138 "mul" : '*', |
|
139 "div" : '/', |
|
140 "mod" : '%', |
|
141 "dot" : '.', |
|
142 "or" : "||", |
|
143 "and" : "&&", |
|
144 "strict_eq" : '===', |
|
145 "eq" : '==', |
|
146 "strict_ne" : '!==', |
|
147 "ne" : '!=', |
|
148 "lsh" : '<<', |
|
149 "le" : '<=', |
|
150 "lt" : '<', |
|
151 "ursh" : '>>>', |
|
152 "rsh" : '>>', |
|
153 "ge" : '>=', |
|
154 "gt" : '>', |
|
155 "bitwise_or" : '|', |
|
156 "bitwise_xor" : '^', |
|
157 "bitwise_and" : '&' |
|
158 } |
|
159 |
|
160 __prefixes = { |
|
161 "increment" : "++", |
|
162 "decrement" : "--", |
|
163 "bitwise_not" : '~', |
|
164 "not" : "!", |
|
165 "unary_plus" : "+", |
|
166 "unary_minus" : "-", |
|
167 "delete" : "delete ", |
|
168 "new" : "new ", |
|
169 "typeof" : "typeof ", |
|
170 "void" : "void " |
|
171 } |
|
172 |
|
173 |
|
174 |
|
175 # |
|
176 # Script Scope |
|
177 # |
|
178 |
|
179 def type_script(self, node): |
|
180 return self.__statements(node) |
|
181 |
|
182 |
|
183 |
|
184 # |
|
185 # Expressions |
|
186 # |
|
187 |
|
188 def type_comma(self, node): |
|
189 return self.__commaSymbol.join(map(self.compress, node)) |
|
190 |
|
191 def type_object_init(self, node): |
|
192 return "{%s}" % self.__commaSymbol.join(map(self.compress, node)) |
|
193 |
|
194 def type_property_init(self, node): |
|
195 key = self.compress(node[0]) |
|
196 value = self.compress(node[1]) |
|
197 |
|
198 if type(key) in (int, float): |
|
199 pass |
|
200 |
|
201 elif self.__number_property.match(key): |
|
202 pass |
|
203 |
|
204 # Protect keywords and special characters |
|
205 elif key in keywords or key in futureReserved or not self.__simple_property.match(key): |
|
206 key = self.type_string(node[0]) |
|
207 |
|
208 return "%s:%s" % (key, value) |
|
209 |
|
210 def type_array_init(self, node): |
|
211 def helper(child): |
|
212 return self.compress(child) if child != None else "" |
|
213 |
|
214 return "[%s]" % ",".join(map(helper, node)) |
|
215 |
|
216 def type_array_comp(self, node): |
|
217 return "[%s %s]" % (self.compress(node.expression), self.compress(node.tail)) |
|
218 |
|
219 def type_string(self, node): |
|
220 # Omit writing real high unicode character which are not supported well by browsers |
|
221 ascii = ascii_encoder.encode(node.value) |
|
222 |
|
223 if high_unicode.search(ascii): |
|
224 return ascii |
|
225 else: |
|
226 return unicode_encoder.encode(node.value) |
|
227 |
|
228 def type_number(self, node): |
|
229 value = node.value |
|
230 |
|
231 # Special handling for protected float/exponential |
|
232 if type(value) == str: |
|
233 # Convert zero-prefix |
|
234 if value.startswith("0.") and len(value) > 2: |
|
235 value = value[1:] |
|
236 |
|
237 # Convert zero postfix |
|
238 elif value.endswith(".0"): |
|
239 value = value[:-2] |
|
240 |
|
241 elif int(value) == value and node.parent.type != "dot": |
|
242 value = int(value) |
|
243 |
|
244 return "%s" % value |
|
245 |
|
246 def type_regexp(self, node): |
|
247 return node.value |
|
248 |
|
249 def type_identifier(self, node): |
|
250 return node.value |
|
251 |
|
252 def type_list(self, node): |
|
253 return ",".join(map(self.compress, node)) |
|
254 |
|
255 def type_index(self, node): |
|
256 return "%s[%s]" % (self.compress(node[0]), self.compress(node[1])) |
|
257 |
|
258 def type_declaration(self, node): |
|
259 names = getattr(node, "names", None) |
|
260 if names: |
|
261 result = self.compress(names) |
|
262 else: |
|
263 result = node.name |
|
264 |
|
265 initializer = getattr(node, "initializer", None) |
|
266 if initializer: |
|
267 result += "=%s" % self.compress(node.initializer) |
|
268 |
|
269 return result |
|
270 |
|
271 def type_assign(self, node): |
|
272 assignOp = getattr(node, "assignOp", None) |
|
273 operator = "=" if not assignOp else self.__dividers[assignOp] + "=" |
|
274 |
|
275 return self.compress(node[0]) + operator + self.compress(node[1]) |
|
276 |
|
277 def type_call(self, node): |
|
278 return "%s(%s)" % (self.compress(node[0]), self.compress(node[1])) |
|
279 |
|
280 def type_new_with_args(self, node): |
|
281 result = "new %s" % self.compress(node[0]) |
|
282 |
|
283 # Compress new Object(); => new Object; |
|
284 if len(node[1]) > 0: |
|
285 result += "(%s)" % self.compress(node[1]) |
|
286 else: |
|
287 parent = getattr(node, "parent", None) |
|
288 if parent and parent.type is "dot": |
|
289 result += "()" |
|
290 |
|
291 return result |
|
292 |
|
293 def type_exception(self, node): |
|
294 return node.value |
|
295 |
|
296 def type_generator(self, node): |
|
297 """ Generator Expression """ |
|
298 result = self.compress(getattr(node, "expression")) |
|
299 tail = getattr(node, "tail", None) |
|
300 if tail: |
|
301 result += " %s" % self.compress(tail) |
|
302 |
|
303 return result |
|
304 |
|
305 def type_comp_tail(self, node): |
|
306 """ Comprehensions Tails """ |
|
307 result = self.compress(getattr(node, "for")) |
|
308 guard = getattr(node, "guard", None) |
|
309 if guard: |
|
310 result += "if(%s)" % self.compress(guard) |
|
311 |
|
312 return result |
|
313 |
|
314 def type_in(self, node): |
|
315 first = self.compress(node[0]) |
|
316 second = self.compress(node[1]) |
|
317 |
|
318 if first.endswith("'") or first.endswith('"'): |
|
319 pattern = "%sin %s" |
|
320 else: |
|
321 pattern = "%s in %s" |
|
322 |
|
323 return pattern % (first, second) |
|
324 |
|
325 def type_instanceof(self, node): |
|
326 first = self.compress(node[0]) |
|
327 second = self.compress(node[1]) |
|
328 |
|
329 return "%s instanceof %s" % (first, second) |
|
330 |
|
331 |
|
332 |
|
333 # |
|
334 # Statements :: Core |
|
335 # |
|
336 |
|
337 def type_block(self, node): |
|
338 return "{%s}" % self.__removeSemicolon(self.__statements(node)) |
|
339 |
|
340 def type_let_block(self, node): |
|
341 begin = "let(%s)" % ",".join(map(self.compress, node.variables)) |
|
342 if hasattr(node, "block"): |
|
343 end = self.compress(node.block) |
|
344 elif hasattr(node, "expression"): |
|
345 end = self.compress(node.expression) |
|
346 |
|
347 return begin + end |
|
348 |
|
349 def type_const(self, node): |
|
350 return self.__addSemicolon("const %s" % self.type_list(node)) |
|
351 |
|
352 def type_var(self, node): |
|
353 return self.__addSemicolon("var %s" % self.type_list(node)) |
|
354 |
|
355 def type_let(self, node): |
|
356 return self.__addSemicolon("let %s" % self.type_list(node)) |
|
357 |
|
358 def type_semicolon(self, node): |
|
359 expression = getattr(node, "expression", None) |
|
360 return self.__addSemicolon(self.compress(expression) if expression else "") |
|
361 |
|
362 def type_label(self, node): |
|
363 return self.__addSemicolon("%s:%s" % (node.label, self.compress(node.statement))) |
|
364 |
|
365 def type_break(self, node): |
|
366 return self.__addSemicolon("break" if not hasattr(node, "label") else "break %s" % node.label) |
|
367 |
|
368 def type_continue(self, node): |
|
369 return self.__addSemicolon("continue" if not hasattr(node, "label") else "continue %s" % node.label) |
|
370 |
|
371 |
|
372 # |
|
373 # Statements :: Functions |
|
374 # |
|
375 |
|
376 def type_function(self, node): |
|
377 if node.type == "setter": |
|
378 result = "set" |
|
379 elif node.type == "getter": |
|
380 result = "get" |
|
381 else: |
|
382 result = "function" |
|
383 |
|
384 name = getattr(node, "name", None) |
|
385 if name: |
|
386 result += " %s" % name |
|
387 |
|
388 params = getattr(node, "params", None) |
|
389 result += "(%s)" % self.compress(params) if params else "()" |
|
390 |
|
391 # keep expression closure format (may be micro-optimized for other code, too) |
|
392 if getattr(node, "expressionClosure", False): |
|
393 result += self.compress(node.body) |
|
394 else: |
|
395 result += "{%s}" % self.__removeSemicolon(self.compress(node.body)) |
|
396 |
|
397 return result |
|
398 |
|
399 def type_getter(self, node): |
|
400 return self.type_function(node) |
|
401 |
|
402 def type_setter(self, node): |
|
403 return self.type_function(node) |
|
404 |
|
405 def type_return(self, node): |
|
406 result = "return" |
|
407 if hasattr(node, "value"): |
|
408 valueCode = self.compress(node.value) |
|
409 |
|
410 # Micro optimization: Don't need a space when a block/map/array/group/strings are returned |
|
411 if not valueCode.startswith(("(","[","{","'",'"',"!","-","/")): |
|
412 result += " " |
|
413 |
|
414 result += valueCode |
|
415 |
|
416 return self.__addSemicolon(result) |
|
417 |
|
418 |
|
419 |
|
420 # |
|
421 # Statements :: Exception Handling |
|
422 # |
|
423 |
|
424 def type_throw(self, node): |
|
425 return self.__addSemicolon("throw %s" % self.compress(node.exception)) |
|
426 |
|
427 def type_try(self, node): |
|
428 result = "try%s" % self.compress(node.tryBlock) |
|
429 |
|
430 for catch in node: |
|
431 if catch.type == "catch": |
|
432 if hasattr(catch, "guard"): |
|
433 result += "catch(%s if %s)%s" % (self.compress(catch.exception), self.compress(catch.guard), self.compress(catch.block)) |
|
434 else: |
|
435 result += "catch(%s)%s" % (self.compress(catch.exception), self.compress(catch.block)) |
|
436 |
|
437 if hasattr(node, "finallyBlock"): |
|
438 result += "finally%s" % self.compress(node.finallyBlock) |
|
439 |
|
440 return result |
|
441 |
|
442 |
|
443 |
|
444 # |
|
445 # Statements :: Loops |
|
446 # |
|
447 |
|
448 def type_while(self, node): |
|
449 result = "while(%s)%s" % (self.compress(node.condition), self.compress(node.body)) |
|
450 self.__handleForcedSemicolon(node.body) |
|
451 return result |
|
452 |
|
453 |
|
454 def type_do(self, node): |
|
455 # block unwrapping don't help to reduce size on this loop type |
|
456 # but if it happens (don't like to modify a global function to fix a local issue), we |
|
457 # need to fix the body and re-add braces around the statement |
|
458 body = self.compress(node.body) |
|
459 if not body.startswith("{"): |
|
460 body = "{%s}" % body |
|
461 |
|
462 return self.__addSemicolon("do%swhile(%s)" % (body, self.compress(node.condition))) |
|
463 |
|
464 |
|
465 def type_for_in(self, node): |
|
466 # Optional variable declarations |
|
467 varDecl = getattr(node, "varDecl", None) |
|
468 |
|
469 # Body is optional - at least in comprehensions tails |
|
470 body = getattr(node, "body", None) |
|
471 if body: |
|
472 body = self.compress(body) |
|
473 else: |
|
474 body = "" |
|
475 |
|
476 result = "for" |
|
477 if node.isEach: |
|
478 result += " each" |
|
479 |
|
480 result += "(%s in %s)%s" % (self.__removeSemicolon(self.compress(node.iterator)), self.compress(node.object), body) |
|
481 |
|
482 if body: |
|
483 self.__handleForcedSemicolon(node.body) |
|
484 |
|
485 return result |
|
486 |
|
487 |
|
488 def type_for(self, node): |
|
489 setup = getattr(node, "setup", None) |
|
490 condition = getattr(node, "condition", None) |
|
491 update = getattr(node, "update", None) |
|
492 |
|
493 result = "for(" |
|
494 result += self.__addSemicolon(self.compress(setup) if setup else "") |
|
495 result += self.__addSemicolon(self.compress(condition) if condition else "") |
|
496 result += self.compress(update) if update else "" |
|
497 result += ")%s" % self.compress(node.body) |
|
498 |
|
499 self.__handleForcedSemicolon(node.body) |
|
500 return result |
|
501 |
|
502 |
|
503 |
|
504 # |
|
505 # Statements :: Conditionals |
|
506 # |
|
507 |
|
508 def type_hook(self, node): |
|
509 """aka ternary operator""" |
|
510 condition = node.condition |
|
511 thenPart = node.thenPart |
|
512 elsePart = node.elsePart |
|
513 |
|
514 if condition.type == "not": |
|
515 [thenPart,elsePart] = [elsePart,thenPart] |
|
516 condition = condition[0] |
|
517 |
|
518 return "%s?%s:%s" % (self.compress(condition), self.compress(thenPart), self.compress(elsePart)) |
|
519 |
|
520 |
|
521 def type_if(self, node): |
|
522 result = "if(%s)%s" % (self.compress(node.condition), self.compress(node.thenPart)) |
|
523 |
|
524 elsePart = getattr(node, "elsePart", None) |
|
525 if elsePart: |
|
526 result += "else" |
|
527 |
|
528 elseCode = self.compress(elsePart) |
|
529 |
|
530 # Micro optimization: Don't need a space when the child is a block |
|
531 # At this time the brace could not be part of a map declaration (would be a syntax error) |
|
532 if not elseCode.startswith(("{", "(", ";")): |
|
533 result += " " |
|
534 |
|
535 result += elseCode |
|
536 |
|
537 self.__handleForcedSemicolon(elsePart) |
|
538 |
|
539 return result |
|
540 |
|
541 |
|
542 def type_switch(self, node): |
|
543 result = "switch(%s){" % self.compress(node.discriminant) |
|
544 for case in node: |
|
545 if case.type == "case": |
|
546 labelCode = self.compress(case.label) |
|
547 if labelCode.startswith('"'): |
|
548 result += "case%s:" % labelCode |
|
549 else: |
|
550 result += "case %s:" % labelCode |
|
551 elif case.type == "default": |
|
552 result += "default:" |
|
553 else: |
|
554 continue |
|
555 |
|
556 for statement in case.statements: |
|
557 temp = self.compress(statement) |
|
558 if len(temp) > 0: |
|
559 result += self.__addSemicolon(temp) |
|
560 |
|
561 return "%s}" % self.__removeSemicolon(result) |
|
562 |
|
563 |
|
564 |