#
# 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)