def compactIf(node, thenPart, condition): """ Reduces the size of a if statement (without elsePart) using boolean operators instead of the typical keywords e.g. "if(something)make()" is translated to "something&&make()" which is two characters shorter. This however only works when the thenPart is only based on expressions and does not contain other statements. """ thenExpression = getattr(thenPart, "expression", None) if not thenExpression: # Empty semicolon statement => translate if into semicolon statement node.remove(condition) node.remove(node.thenPart) node.append(condition, "expression") node.type = "semicolon" else: # Has expression => Translate IF using a AND or OR operator if condition.type == "not": replacement = Node(thenPart.tokenizer, "or") condition = condition[0] else: replacement = Node(thenPart.tokenizer, "and") replacement.append(condition) replacement.append(thenExpression) thenPart.append(replacement, "expression") fixParens(thenExpression) fixParens(condition) node.parent.replace(node, thenPart)
def combineToCommaExpression(node): """ This method tries to combine a block with multiple statements into one semicolon statement with a comma expression containing all expressions from the previous block. This only works when the block exclusively consists of expressions as this do not work with other statements. Still this conversion reduces the size impact of many blocks and leads to the removal of a lot of curly braces in the result code. Example: {x++;y+=3} => x++,x+=3 """ if node == None or node.type != "block": return node for child in node: if child.type != "semicolon": return node comma = Node(node.tokenizer, "comma") for child in list(node): # Ignore empty semicolons if hasattr(child, "expression"): comma.append(child.expression) semicolon = Node(node.tokenizer, "semicolon") semicolon.append(comma, "expression") parent = node.parent parent.replace(node, semicolon) return semicolon
def __createSimpleAssignment(identifier, valueNode): assignNode = Node(None, "assign") identNode = Node(None, "identifier") identNode.value = identifier assignNode.append(identNode) assignNode.append(valueNode) return assignNode
def createHook(condition, thenPart, elsePart): """ Creates a hook expression with the given then/else parts """ hook = Node(condition.tokenizer, "hook") hook.append(condition, "condition") hook.append(thenPart, "thenPart") hook.append(elsePart, "elsePart") return hook
def combineExpressions(condition, thenExpression, elseExpression): """ Combines then and else expression using a hook statement. """ hook = createHook(condition, thenExpression, elseExpression) semicolon = Node(condition.tokenizer, "semicolon") semicolon.append(hook, "expression") fixParens(condition) fixParens(thenExpression) fixParens(elseExpression) return semicolon
def reworkElse(node, elsePart): """ If an if ends with a return/throw we are able to inline the content of the else to the same parent as the if resides into. This method deals with all the nasty details of this operation. """ target = node.parent targetIndex = target.index(node)+1 # A workaround for compact if-else blocks # We are a elsePart of the if where we want to move our # content to. This cannot work. So we need to wrap ourself # into a block and move the else statements to this newly # established block if not target.type in ("block","script"): newBlock = Node(None, "block") newBlock.wrapped = True # Replace node with newly created block and put ourself into it node.parent.replace(node, newBlock) newBlock.append(node) # Update the target and the index target = newBlock targetIndex = 1 if not target.type in ("block","script"): print("No possible target found/created") return elsePart if elsePart.type == "block": for child in reversed(elsePart): target.insert(targetIndex, child) # Remove else block from if statement node.remove(elsePart) else: target.insert(targetIndex, elsePart) return
def __rebuildAsAssignment(node, firstVarStatement): """Rebuilds the items of a var statement into a assignment list and moves declarations to the given var statement""" assignment = Node(node.tokenizer, "semicolon") assignmentList = Node(node.tokenizer, "comma") assignment.append(assignmentList, "expression") # Casting to list() creates a copy during the process (keeps loop stable) for child in list(node): if hasattr(child, "name"): # Cleanup initializer and move to assignment if hasattr(child, "initializer"): assign = __createSimpleAssignment(child.name, child.initializer) assignmentList.append(assign) firstVarStatement.append(child) else: # JS 1.7 Destructing Expression for identifier in child.names: firstVarStatement.append(__createDeclaration(identifier.value)) if hasattr(child, "initializer"): assign = __createMultiAssignment(child.names, child.initializer) assignmentList.append(assign) node.remove(child) # Patch parent node to contain assignment instead of declaration if len(assignmentList) > 0: node.parent.replace(node, assignment) # Special process for "for-in" loops # It is OK to be second because of assignments are not allowed at # all in for-in loops and so the first if basically does nothing # for these kind of statements. elif getattr(node, "rel", None) == "iterator": if hasattr(child, "name"): node.parent.replace(node, __createIdentifier(child.name)) else: # JS 1.7 Destructing Expressions node.parent.replace(node, child.names) # Edge case. Not yet found if this happen realistically else: if hasattr(node, "rel"): logging.warn("Remove related node (%s) from parent: %s" % (node.rel, node)) node.parent.remove(node) # Minor post-cleanup. Remove useless comma statement when only one expression is the result if len(assignmentList) == 1: assignment.replace(assignmentList, assignmentList[0])
def __rebuildAsSplitted(self, value, mapper): """ The real splitter engine. Creates plus Node instances and cascade them automatically """ result = [] splits = self.__replacer.split(value) if len(splits) == 1: return None pair = Node(None, "plus") for entry in splits: if entry == "": continue if len(pair) == 2: newPair = Node(None, "plus") newPair.append(pair) pair = newPair if self.__replacer.match(entry): pos = int(entry[1]) - 1 # Items might be added multiple times. Copy to protect original. try: repl = mapper[pos] except KeyError: raise TranslationError( "Invalid positional value: %s in %s" % (entry, value)) copied = copy.deepcopy(mapper[pos]) if copied.type not in ("identifier", "call"): copied.parenthesized = True pair.append(copied) else: child = Node(None, "string") child.value = entry pair.append(child) return pair
def __rebuildAsSplitted(self, value, mapper): """ The real splitter engine. Creates plus Node instances and cascade them automatically """ result = [] splits = self.__replacer.split(value) if len(splits) == 1: return None pair = Node(None, "plus") for entry in splits: if entry == "": continue if len(pair) == 2: newPair = Node(None, "plus") newPair.append(pair) pair = newPair if self.__replacer.match(entry): pos = int(entry[1]) - 1 # Items might be added multiple times. Copy to protect original. try: repl = mapper[pos] except KeyError: raise TranslationError("Invalid positional value: %s in %s" % (entry, value)) copied = copy.deepcopy(mapper[pos]) if copied.type not in ("identifier", "call"): copied.parenthesized = True pair.append(copied) else: child = Node(None, "string") child.value = entry pair.append(child) return pair
def __createMultiAssignment(names, valueNode): assignNode = Node(None, "assign") assignNode.append(names) assignNode.append(valueNode) return assignNode
def __recurser(self, node): if node.type == "call": funcName = None if node[0].type == "identifier": funcName = node[0].value elif node[0].type == "dot" and node[0][1].type == "identifier": funcName = node[0][1].value if funcName in methods: params = node[1] table = self.__table # Verify param types if params[0].type != "string": logging.warn( "Expecting translation string to be type string: %s at line %s" % (params[0].type, params[0].line)) if (funcName == "trn" or funcName == "trc") and params[1].type != "string": logging.warn( "Expecting translation string to be type string: %s at line %s" % (params[1].type, params[1].line)) # Signature tr(msg, arg1, arg2, ...) if funcName == "tr": key = params[0].value if key in table: params[0].value = table[key] if len(params) == 1: node.parent.replace(node, params[0]) else: self.__splitTemplate(node, params[0], params[1:]) # Signature trc(hint, msg, arg1, arg2, ...) elif funcName == "trc": key = params[0].value if key in table: params[1].value = table[key] if len(params) == 2: node.parent.replace(node, params[1]) else: self.__splitTemplate(node, params[1], params[2:]) # Signature trn(msg, msg2, [...], int, arg1, arg2, ...) elif funcName == "trn": keySingular = params[0].value if keySingular in table: params[0].value = table[keySingular] keyPlural = params[1].value if keyPlural in table: params[1].value = table[keyPlural] # TODO: Multi plural support # Patch strings with dynamic values if len(params) >= 3: self.__splitTemplate(params[0], params[0], params[3:]) self.__splitTemplate(params[1], params[1], params[3:]) # Replace the whole call with: int < 2 ? singularMessage : pluralMessage hook = Node(None, "hook") hook.parenthesized = True condition = Node(None, "le") condition.append(params[2]) number = Node(None, "number") number.value = 1 condition.append(number) hook.append(condition, "condition") hook.append(params[1], "elsePart") hook.append(params[0], "thenPart") node.parent.replace(node, hook) # Process children for child in node: if child != None: self.__recurser(child)
def __recurser(self, node): if node.type == "call": funcName = None if node[0].type == "identifier": funcName = node[0].value elif node[0].type == "dot" and node[0][1].type == "identifier": funcName = node[0][1].value if funcName in methods: params = node[1] table = self.__table # Verify param types if params[0].type != "string": logging.warn("Expecting translation string to be type string: %s at line %s" % (params[0].type, params[0].line)) if (funcName == "trn" or funcName == "trc") and params[1].type != "string": logging.warn("Expecting translation string to be type string: %s at line %s" % (params[1].type, params[1].line)) # Signature tr(msg, arg1, arg2, ...) if funcName == "tr": key = params[0].value if key in table: params[0].value = table[key] if len(params) == 1: node.parent.replace(node, params[0]) else: self.__splitTemplate(node, params[0], params[1:]) # Signature trc(hint, msg, arg1, arg2, ...) elif funcName == "trc": key = params[0].value if key in table: params[1].value = table[key] if len(params) == 2: node.parent.replace(node, params[1]) else: self.__splitTemplate(node, params[1], params[2:]) # Signature trn(msg, msg2, [...], int, arg1, arg2, ...) elif funcName == "trn": keySingular = params[0].value if keySingular in table: params[0].value = table[keySingular] keyPlural = params[1].value if keyPlural in table: params[1].value = table[keyPlural] # TODO: Multi plural support # Patch strings with dynamic values if len(params) >= 3: self.__splitTemplate(params[0], params[0], params[3:]) self.__splitTemplate(params[1], params[1], params[3:]) # Replace the whole call with: int < 2 ? singularMessage : pluralMessage hook = Node(None, "hook") hook.parenthesized = True condition = Node(None, "le") condition.append(params[2]) number = Node(None, "number") number.value = 1 condition.append(number) hook.append(condition, "condition") hook.append(params[1], "elsePart") hook.append(params[0], "thenPart") node.parent.replace(node, hook) # Process children for child in node: if child != None: self.__recurser(child)
def createReturn(value): """ Creates a return statement with the given value """ ret = Node(value.tokenizer, "return") ret.append(value, "value") return ret
def optimize(node): # Process from inside to outside for child in list(node): # None children are allowed sometimes e.g. during array_init like [1,2,,,7,8] if child != None: optimize(child) # Cleans up empty semicolon statements (or pseudo-empty) if node.type == "semicolon" and node.parent.type in ("block", "script"): expr = getattr(node, "expression", None) if not expr or expr.type in ("null", "this", "true", "false", "identifier", "number", "string", "regexp"): node.parent.remove(node) return # Remove unneeded parens if getattr(node, "parenthesized", False): cleanParens(node) # Pre-compute numeric expressions where it makes sense if node.type in ("plus","minus","mul","div","mod") and node[0].type == "number" and node[1].type == "number": firstNumber = node[0] secondNumber = node[1] operator = node.type # Only do for real numeric values and not for protected strings (float differences between Python and JS) if type(firstNumber.value) == str or type(secondNumber.value) == str: pass elif operator == "plus": firstNumber.value += secondNumber.value node.parent.replace(node, firstNumber) elif operator == "minus": firstNumber.value -= secondNumber.value node.parent.replace(node, firstNumber) else: if operator == "mul": result = firstNumber.value * secondNumber.value elif operator == "div" and secondNumber.value is not 0: result = firstNumber.value / secondNumber.value elif operator == "mod": result = firstNumber.value % secondNumber.value else: result = None if result is not None and len(str(result)) < len(compress(node)): firstNumber.value = result node.parent.replace(node, firstNumber) # Pre-combine strings (even supports mixed string + number concats) elif node.type == "plus" and node[0].type in ("number", "string") and node[1].type in ("number", "string"): node[0].value = "%s%s" % (node[0].value, node[1].value) node[0].type = "string" node.parent.replace(node, node[0]) # Unwrap blocks if node.type == "block": if node.parent.type in ("try", "catch", "finally"): pass elif len(node) == 0: repl =Node(node.tokenizer, "semicolon") node.parent.replace(node, repl) node = repl elif len(node) == 1: if node.parent.type == "if" and node.rel == "thenPart" and hasattr(node.parent, "elsePart") and containsIf(node): # if with else where the thenBlock contains another if pass elif node.parent.type == "if" and node.rel == "thenPart" and containsIfElse(node): # if without else where the thenBlock contains a if-else pass elif node.parent.type in ("case", "default"): # virtual blocks inside case/default statements pass else: node.parent.replace(node, node[0]) node = node[0] else: node = combineToCommaExpression(node) # Remove "empty" semicolons who are inside a block/script parent if node.type == "semicolon": if not hasattr(node, "expression"): if node.parent.type in ("block", "script"): node.parent.remove(node) elif node.parent.type == "if": rel = getattr(node, "rel", None) if rel == "elsePart": node.parent.remove(node) # Process all if-statements if node.type == "if": condition = node.condition thenPart = node.thenPart elsePart = getattr(node, "elsePart", None) # Optimize for empty thenPart if elsePart is available if thenPart.type == "semicolon" and not hasattr(thenPart, "expression") and elsePart: if condition.type == "not": node.replace(condition, condition[0]) condition = condition[0] else: repl = Node(None, "not") node.replace(condition, repl) repl.append(condition) condition = repl node.replace(thenPart, elsePart) thenPart = elsePart elsePart = None # Optimize using hook operator if elsePart and thenPart.type == "return" and elsePart.type == "return": # Combine return statement replacement = createReturn(createHook(condition, thenPart.value, elsePart.value)) node.parent.replace(node, replacement) return # Check whether if-part ends with a return statement. Then # We do not need a else statement here and just can wrap the whole content # of the else block inside the parent if elsePart and endsWithReturnOrThrow(thenPart): reworkElse(node, elsePart) elsePart = None # Optimize using "AND" or "OR" operators # Combine multiple semicolon statements into one semicolon statement using an "comma" expression thenPart = combineToCommaExpression(thenPart) elsePart = combineToCommaExpression(elsePart) # Optimize remaining if or if-else constructs if elsePart: mergeParts(node, thenPart, elsePart, condition) elif thenPart.type == "semicolon": compactIf(node, thenPart, condition)