Source code for jasy.script.output.Compressor

#
# Jasy - Web Tooling Framework
# Copyright 2010-2012 Zynga Inc.
# Copyright 2013-2014 Sebastian Werner
#

import re
import sys
import json

from jasy.script.tokenize.Lang import keywords
from jasy.script.parse.Lang import expressions, futureReserved

high_unicode = re.compile(r"\\u[2-9A-Fa-f][0-9A-Fa-f]{3}")
ascii_encoder = json.JSONEncoder(ensure_ascii=True)
unicode_encoder = json.JSONEncoder(ensure_ascii=False)

#
# Class
#


[docs]class Compressor: __semicolonSymbol = ";" __commaSymbol = "," def __init__(self, format=None): if format: if format.has("semicolon"): self.__semicolonSymbol = ";\n" if format.has("comma"): self.__commaSymbol = ",\n" self.__forcedSemicolon = False # # Main #
[docs] def compress(self, node): type = node.type result = None if type in self.__simple: result = type elif type in self.__prefixes: if getattr(node, "postfix", False): result = self.compress(node[0]) + self.__prefixes[node.type] else: result = self.__prefixes[node.type] + self.compress(node[0]) elif type in self.__dividers: first = self.compress(node[0]) second = self.compress(node[1]) divider = self.__dividers[node.type] # Fast path if node.type not in ("plus", "minus"): result = "%s%s%s" % (first, divider, second) # Special code for dealing with situations like x + ++y and y-- - x else: result = first if first.endswith(divider): result += " " result += divider if second.startswith(divider): result += " " result += second else: try: result = getattr(self, "type_%s" % type)(node) except AttributeError: raise Exception("Script compressor does not support type '%s' from line %s in file %s" % (type, node.line, node.getFileName())) if getattr(node, "parenthesized", None): return "(%s)" % result else: return result
# # Helpers # def __statements(self, node): result = [] for child in node: result.append(self.compress(child)) return "".join(result) def __handleForcedSemicolon(self, node): if node.type == "semicolon" and not hasattr(node, "expression"): self.__forcedSemicolon = True def __addSemicolon(self, result): if not result.endswith(self.__semicolonSymbol): if self.__forcedSemicolon: self.__forcedSemicolon = False return result + self.__semicolonSymbol else: return result def __removeSemicolon(self, result): if self.__forcedSemicolon: self.__forcedSemicolon = False return result if result.endswith(self.__semicolonSymbol): return result[:-len(self.__semicolonSymbol)] else: return result # # Data # __simple_property = re.compile(r"^[a-zA-Z_$][a-zA-Z0-9_$]*$") __number_property = re.compile(r"^[0-9]+$") __simple = ["true", "false", "null", "this", "debugger"] __dividers = { "plus" : '+', "minus" : '-', "mul" : '*', "div" : '/', "mod" : '%', "dot" : '.', "or" : "||", "and" : "&&", "strict_eq" : '===', "eq" : '==', "strict_ne" : '!==', "ne" : '!=', "lsh" : '<<', "le" : '<=', "lt" : '<', "ursh" : '>>>', "rsh" : '>>', "ge" : '>=', "gt" : '>', "bitwise_or" : '|', "bitwise_xor" : '^', "bitwise_and" : '&' } __prefixes = { "increment" : "++", "decrement" : "--", "bitwise_not" : '~', "not" : "!", "unary_plus" : "+", "unary_minus" : "-", "delete" : "delete ", "new" : "new ", "typeof" : "typeof ", "void" : "void " } # # Script Scope #
[docs] def type_script(self, node): return self.__statements(node)
# # Expressions #
[docs] def type_comma(self, node): return self.__commaSymbol.join(map(self.compress, node))
[docs] def type_object_init(self, node): return "{%s}" % self.__commaSymbol.join(map(self.compress, node))
[docs] def type_property_init(self, node): key = self.compress(node[0]) value = self.compress(node[1]) if type(key) in (int, float): pass elif self.__number_property.match(key): pass # Protect keywords and special characters elif key in keywords or key in futureReserved or not self.__simple_property.match(key): key = self.type_string(node[0]) return "%s:%s" % (key, value)
[docs] def type_array_init(self, node): def helper(child): return self.compress(child) if child is not None else "" return "[%s]" % ",".join(map(helper, node))
[docs] def type_array_comp(self, node): return "[%s %s]" % (self.compress(node.expression), self.compress(node.tail))
[docs] def type_string(self, node): # Omit writing real high unicode character which are not supported well by browsers ascii = ascii_encoder.encode(node.value) if high_unicode.search(ascii): return ascii else: return unicode_encoder.encode(node.value)
[docs] def type_number(self, node): value = node.value # Special handling for protected float/exponential if isinstance(value, str): # Convert zero-prefix if value.startswith("0.") and len(value) > 2: value = value[1:] # Convert zero postfix elif value.endswith(".0"): value = value[:-2] elif int(value) == value and node.parent.type != "dot": value = int(value) return "%s" % value
[docs] def type_regexp(self, node): return node.value
[docs] def type_identifier(self, node): return node.value
[docs] def type_list(self, node): return ",".join(map(self.compress, node))
[docs] def type_index(self, node): return "%s[%s]" % (self.compress(node[0]), self.compress(node[1]))
[docs] def type_declaration(self, node): names = getattr(node, "names", None) if names: result = self.compress(names) else: result = node.name initializer = getattr(node, "initializer", None) if initializer: result += "=%s" % self.compress(node.initializer) return result
[docs] def type_assign(self, node): assignOp = getattr(node, "assignOp", None) operator = "=" if not assignOp else self.__dividers[assignOp] + "=" return self.compress(node[0]) + operator + self.compress(node[1])
[docs] def type_call(self, node): return "%s(%s)" % (self.compress(node[0]), self.compress(node[1]))
[docs] def type_new_with_args(self, node): result = "new %s" % self.compress(node[0]) # Compress new Object(); => new Object; if len(node[1]) > 0: result += "(%s)" % self.compress(node[1]) else: parent = getattr(node, "parent", None) if parent and parent.type is "dot": result += "()" return result
[docs] def type_exception(self, node): return node.value
[docs] def type_generator(self, node): """Generator Expression.""" result = self.compress(getattr(node, "expression")) tail = getattr(node, "tail", None) if tail: result += " %s" % self.compress(tail) return result
[docs] def type_comp_tail(self, node): """Comprehensions Tails.""" result = self.compress(getattr(node, "for")) guard = getattr(node, "guard", None) if guard: result += "if(%s)" % self.compress(guard) return result
[docs] def type_in(self, node): first = self.compress(node[0]) second = self.compress(node[1]) if first.endswith("'") or first.endswith('"'): pattern = "%sin %s" else: pattern = "%s in %s" return pattern % (first, second)
[docs] def type_instanceof(self, node): first = self.compress(node[0]) second = self.compress(node[1]) return "%s instanceof %s" % (first, second)
# # Statements :: Core #
[docs] def type_block(self, node): return "{%s}" % self.__removeSemicolon(self.__statements(node))
[docs] def type_let_block(self, node): begin = "let(%s)" % ",".join(map(self.compress, node.variables)) if hasattr(node, "block"): end = self.compress(node.block) elif hasattr(node, "expression"): end = self.compress(node.expression) return begin + end
[docs] def type_const(self, node): return self.__addSemicolon("const %s" % self.type_list(node))
[docs] def type_var(self, node): return self.__addSemicolon("var %s" % self.type_list(node))
[docs] def type_let(self, node): return self.__addSemicolon("let %s" % self.type_list(node))
[docs] def type_semicolon(self, node): expression = getattr(node, "expression", None) return self.__addSemicolon(self.compress(expression) if expression else "")
[docs] def type_label(self, node): return self.__addSemicolon("%s:%s" % (node.label, self.compress(node.statement)))
[docs] def type_break(self, node): return self.__addSemicolon("break" if not hasattr(node, "label") else "break %s" % node.label)
[docs] def type_continue(self, node): return self.__addSemicolon("continue" if not hasattr(node, "label") else "continue %s" % node.label)
# # Statements :: Functions #
[docs] def type_function(self, node): if node.type == "setter": result = "set" elif node.type == "getter": result = "get" else: result = "function" name = getattr(node, "name", None) if name: result += " %s" % name params = getattr(node, "params", None) result += "(%s)" % self.compress(params) if params else "()" # keep expression closure format (may be micro-optimized for other code, too) if getattr(node, "expressionClosure", False): result += self.compress(node.body) else: result += "{%s}" % self.__removeSemicolon(self.compress(node.body)) return result
[docs] def type_getter(self, node): return self.type_function(node)
[docs] def type_setter(self, node): return self.type_function(node)
[docs] def type_return(self, node): result = "return" if hasattr(node, "value"): valueCode = self.compress(node.value) # Micro optimization: Don't need a space when a block/map/array/group/strings are returned if not valueCode.startswith(("(", "[", "{", "'", '"', "!", "-", "/")): result += " " result += valueCode return self.__addSemicolon(result)
# # Statements :: Exception Handling #
[docs] def type_throw(self, node): return self.__addSemicolon("throw %s" % self.compress(node.exception))
[docs] def type_try(self, node): result = "try%s" % self.compress(node.tryBlock) for catch in node: if catch.type == "catch": if hasattr(catch, "guard"): result += "catch(%s if %s)%s" % (self.compress(catch.exception), self.compress(catch.guard), self.compress(catch.block)) else: result += "catch(%s)%s" % (self.compress(catch.exception), self.compress(catch.block)) if hasattr(node, "finallyBlock"): result += "finally%s" % self.compress(node.finallyBlock) return result
# # Statements :: Loops #
[docs] def type_while(self, node): result = "while(%s)%s" % (self.compress(node.condition), self.compress(node.body)) self.__handleForcedSemicolon(node.body) return result
[docs] def type_do(self, node): # block unwrapping don't help to reduce size on this loop type # but if it happens (don't like to modify a global function to fix a local issue), we # need to fix the body and re-add braces around the statement body = self.compress(node.body) if not body.startswith("{"): body = "{%s}" % body return self.__addSemicolon("do%swhile(%s)" % (body, self.compress(node.condition)))
[docs] def type_for_in(self, node): # Optional variable declarations varDecl = getattr(node, "varDecl", None) # Body is optional - at least in comprehensions tails body = getattr(node, "body", None) if body: body = self.compress(body) else: body = "" result = "for" if node.isEach: result += " each" result += "(%s in %s)%s" % (self.__removeSemicolon(self.compress(node.iterator)), self.compress(node.object), body) if body: self.__handleForcedSemicolon(node.body) return result
[docs] def type_for(self, node): setup = getattr(node, "setup", None) condition = getattr(node, "condition", None) update = getattr(node, "update", None) result = "for(" result += self.__addSemicolon(self.compress(setup) if setup else "") result += self.__addSemicolon(self.compress(condition) if condition else "") result += self.compress(update) if update else "" result += ")%s" % self.compress(node.body) self.__handleForcedSemicolon(node.body) return result
# # Statements :: Conditionals #
[docs] def type_hook(self, node): """aka ternary operator.""" condition = node.condition thenPart = node.thenPart elsePart = node.elsePart if condition.type == "not": [thenPart, elsePart] = [elsePart, thenPart] condition = condition[0] return "%s?%s:%s" % (self.compress(condition), self.compress(thenPart), self.compress(elsePart))
[docs] def type_if(self, node): result = "if(%s)%s" % (self.compress(node.condition), self.compress(node.thenPart)) elsePart = getattr(node, "elsePart", None) if elsePart: result += "else" elseCode = self.compress(elsePart) # Micro optimization: Don't need a space when the child is a block # At this time the brace could not be part of a map declaration (would be a syntax error) if not elseCode.startswith(("{", "(", ";")): result += " " result += elseCode self.__handleForcedSemicolon(elsePart) return result
[docs] def type_switch(self, node): result = "switch(%s){" % self.compress(node.discriminant) for case in node: if case.type == "case": labelCode = self.compress(case.label) if labelCode.startswith('"'): result += "case%s:" % labelCode else: result += "case %s:" % labelCode elif case.type == "default": result += "default:" else: continue for statement in case.statements: temp = self.compress(statement) if len(temp) > 0: result += self.__addSemicolon(temp) return "%s}" % self.__removeSemicolon(result)