def __recurser(node, permutation, inCondition=False): # Support block commands # These come with their own node.type if node.type == "if": # Pre-process condition # We manually process each child in for if-types __recurser(node.condition, permutation, True) # Cast condition to Python boolean type resultValue = None try: resultValue = Operation.castToBool(node.condition) except Operation.OperationError as ex: Console.debug("Walked into unprocessed condition. Waiting for actual execution. Message: %s", ex) if not resultValue is None: # Process relevant part of the sub tree if resultValue is True: # Fix missing processing of result node __recurser(node.thenPart, permutation) # Finally replace if-node with result node node.parent.replace(node, node.thenPart) elif resultValue is False and hasattr(node, "elsePart"): # Fix missing processing of result node __recurser(node.elsePart, permutation) # Finally replace if-node with result node node.parent.replace(node, node.elsePart) else: # Cleanup original if-node node.parent.remove(node) # All done including child nodes return # Process children first (resolve logic is inner-out) for child in list(node): if child is not None: __recurser(child, permutation, inCondition) # Inside of conditions replace identifiers with their actual value (from current permutation) if inCondition and node.type == "identifier": if not permutation.has(node.value): raise ResolverError("Could not find field %s" % node.value, node) repl = Util.castNativeToNode(permutation.get(node.value)) node.parent.replace(node, repl) # Support inline @field() commands elif node.type == "command" and node.name == "field": if len(node.params) == 0: raise ResolverError("Missing parameter to insert field via @field.", node) identifierNode = node.params[0] if identifierNode.type != "identifier": raise ResolverError("Invalid parameter to @field call: %s" % identifierNode.type, identifierNode) identifier = identifierNode.value if not permutation.has(identifier): raise ResolverError("Could not find field with the name %s" % identifier, identifierNode) repl = Util.castNativeToNode(permutation.get(identifier)) node.parent.replace(node, repl) # Support typical operators elif node.type in Util.ALL_OPERATORS: repl = Operation.compute(node) if repl is not None: node.parent.replace(node, repl)
def compute(node, first=None, second=None, operator=None, session=None): """ Recursively processes given operation node. Consumes optional hints for the first/second child of an operation as well as the operator itself (in cases where it could not be figured out automatically). The session is useful for supporting commands inside of operations. """ # Fill gaps in empty arguments if operator is None: operator = node.type # Fill missing first/second param if first is None and len(node) >= 1: first = node[0] if second is None and len(node) >= 2: second = node[1] # Error handling if node is None or operator is None: raise OperationError("Missing arguments for operation compute()", node) # Solve inner operations first if first is not None: if first.type in Util.ALL_OPERATORS: first = compute(first, session=session) elif first.type == "command": first = Util.executeCommand(first, session) if second is not None: if second.type in Util.ALL_OPERATORS: second = compute(second, session=session) elif second.type == "command": second = Util.executeCommand(second, session) # Support for not-/and-/or-operator if operator == "not": return Util.castNativeToNode(not castToBool(first)) elif operator == "and": return Util.castNativeToNode(castToBool(first) and castToBool(second)) elif operator == "or": return Util.castNativeToNode(castToBool(first) or castToBool(second)) # Support for default set operator "?=" when variable was not defined before elif operator == "questionmark" and first is None: return second # Ignore when not yet processed if first.type in ("command", "variable") or second.type in ("command", "variable"): return # Compare operation types elif first.type == second.type: if first.type in ("true", "false", "null"): if operator in ("eq", "ge", "le"): return Util.castNativeToNode(True) else: return Util.castNativeToNode(False) elif first.type == "number": firstUnit = getattr(first, "unit", None) secondUnit = getattr(second, "unit", None) if operator in Util.COMPARE_OPERATORS: if firstUnit == secondUnit or firstUnit is None or secondUnit is None: if operator == "eq": return Util.castNativeToNode(first.value == second.value) elif operator == "ne": return Util.castNativeToNode(first.value != second.value) elif operator == "gt": return Util.castNativeToNode(first.value > second.value) elif operator == "lt": return Util.castNativeToNode(first.value < second.value) elif operator == "ge": return Util.castNativeToNode(first.value >= second.value) elif operator == "le": return Util.castNativeToNode(first.value <= second.value) else: raise OperationError("Unsupported unit combination for number comparison", node) elif firstUnit == secondUnit or firstUnit is None or secondUnit is None: if operator in Util.MATH_OPERATORS: repl = Node.Node(type="number") if firstUnit is not None: repl.unit = firstUnit elif secondUnit is not None: repl.unit = secondUnit if operator == "plus": repl.value = first.value + second.value elif operator == "minus": repl.value = first.value - second.value elif operator == "mul": repl.value = first.value * second.value elif operator == "div": repl.value = first.value / second.value elif operator == "mod": repl.value = first.value % second.value return repl elif operator == "questionmark": return first else: raise OperationError("Unsupported number operation", node) elif firstUnit == "%" or secondUnit == "%": if operator in ("mul", "div"): repl = Node.Node(type="number") if operator == "mul": repl.value = first.value * second.value / 100 elif operator == "mul": repl.value = first.value / second.value / 100 if firstUnit == "%": repl.unit = secondUnit else: repl.unit = firstUnit return repl else: raise OperationError("Could not compute mixed percent operations for operators other than \"*\" and \"/\"", node) else: raise OperationError("Could not compute result from numbers of different units: %s vs %s" % (first.unit, second.unit), node) elif first.type == "string": if operator == "plus": repl = Node.Node(type="string") repl.value = first.value + second.value return repl elif operator == "eq": return Util.castNativeToNode(first.value == second.value) elif operator == "ne": return Util.castNativeToNode(first.value != second.value) else: raise OperationError("Unsupported string operation", node) elif first.type == "list": if len(first) == len(second): repl = Node.Node(type="list") for pos, child in enumerate(first): childRepl = compute(node, first=child, second=second[pos], session=session) if childRepl is not None: repl.append(childRepl) return repl else: raise OperationError("For list operations both lists have to have the same length!", node) else: raise OperationError("Unsupported operation on %s" % first.type, node) elif first.type == "true" and second.type == "false": return Util.castNativeToNode(False) elif first.type == "false" and second.type == "true": return Util.castNativeToNode(False) elif first.type == "list" and second.type != "list": repl = Node.Node(type="list") for child in first: childRepl = compute(node, first=child, second=second, session=session) if childRepl is not None: repl.append(childRepl) return repl elif first.type != "list" and second.type == "list": repl = Node.Node(type="list") for child in second: childRepl = compute(node, first=first, second=child, session=session) if childRepl is not None: repl.append(childRepl) return repl elif first.type == "string" or second.type == "string": repl = Node.Node(type="string") if first.type == "identifier" or second.type == "identifier": if operator == "plus": repl.value = first.value + second.value return repl elif operator == "eq": return Util.castNativeToNode(first.value == second.value) elif operator == "ne": return Util.castNativeToNode(first.value != second.value) else: raise OperationError("Unsupported string/identifier operation", node) else: if operator == "plus": repl.value = str(first.value) + str(second.value) return repl else: raise OperationError("Unsupported string operation", node) # Just handle when not both are null - equal condition is already done before elif first.type == "null" or second.type == "null": if operator == "eq": return Util.castNativeToNode(False) elif operator == "ne": return Util.castNativeToNode(True) elif operator in Util.MATH_OPERATORS: return Util.castNativeToNode(None) else: raise OperationError("Unsupported operation on null type", node) else: raise OperationError("Different types in operation: %s vs %s" % (first.type, second.type), node)