def object_path_cmp(path1, path2): """ Compare two object paths. :param path1: The first ObjectPath instance :param path2: The second ObjectPath instance :return: <0, 0, or >0 depending on whether the first arg is less, equal or greater than the second """ if path1.object_type_name < path2.object_type_name: result = -1 elif path1.object_type_name > path2.object_type_name: result = 1 else: # I always thought of key and index path steps as separate. The AST # lumps indices in with the previous key as a single path component. # The following splits the path components into individual comparable # values again. Maybe I should not do this... path_vals1 = object_path_to_raw_values(path1) path_vals2 = object_path_to_raw_values(path2) result = iter_lex_cmp( path_vals1, path_vals2, object_path_component_cmp, ) return result
def __transform(self, ast): sorted_children = sorted( ast.operands, key=functools.cmp_to_key(observation_expression_cmp), ) # Deduping only applies to ORs if ast.operator == "OR": deduped_children = [ key.obj for key, _ in itertools.groupby( sorted_children, key=functools.cmp_to_key(observation_expression_cmp, ), ) ] else: deduped_children = sorted_children changed = iter_lex_cmp( ast.operands, deduped_children, observation_expression_cmp, ) != 0 ast.operands = deduped_children return ast, changed
def __transform(self, ast): """ Sort/dedupe children. AND and OR can be treated identically. :param ast: The comparison expression AST :return: The same AST node, but with sorted children """ sorted_children = sorted( ast.operands, key=functools.cmp_to_key(comparison_expression_cmp), ) deduped_children = [ # Apparently when using a key function, groupby()'s "keys" are the # key wrappers, not actual sequence values. Obviously we don't # need key wrappers in our ASTs! k.obj for k, _ in itertools.groupby( sorted_children, key=functools.cmp_to_key(comparison_expression_cmp, ), ) ] changed = iter_lex_cmp( ast.operands, deduped_children, comparison_expression_cmp, ) != 0 ast.operands = deduped_children return ast, changed
def list_cmp(value1, value2): """ Compare lists order-insensitively. Args: value1: The first ListConstant value2: The second ListConstant Returns: <0, 0, or >0 depending on whether the first arg is less, equal or greater than the second """ # Achieve order-independence by sorting the lists first. sorted_value1 = sorted( value1.value, key=functools.cmp_to_key(constant_cmp), ) sorted_value2 = sorted( value2.value, key=functools.cmp_to_key(constant_cmp), ) result = iter_lex_cmp(sorted_value1, sorted_value2, constant_cmp) return result
def startstop_cmp(qual1, qual2): """ Compare START/STOP qualifiers. This lexicographically orders by start time, then stop time. """ return iter_lex_cmp( (qual1.start_time, qual1.stop_time), (qual2.start_time, qual2.stop_time), generic_constant_cmp, )
def comparison_expression_cmp(expr1, expr2): """ Compare two comparison expressions. This is sensitive to the order of the expressions' sub-components. To achieve an order-insensitive comparison, the sub-component ASTs must be ordered first. Args: expr1: The first comparison expression expr2: The second comparison expression Returns: <0, 0, or >0 depending on whether the first arg is less, equal or greater than the second """ if isinstance(expr1, _ComparisonExpression) \ and isinstance(expr2, _ComparisonExpression): result = simple_comparison_expression_cmp(expr1, expr2) # One is simple, one is compound. Let's say... simple ones come first? elif isinstance(expr1, _ComparisonExpression): result = -1 elif isinstance(expr2, _ComparisonExpression): result = 1 # Both are compound: AND's before OR's? elif isinstance(expr1, AndBooleanExpression) \ and isinstance(expr2, OrBooleanExpression): result = -1 elif isinstance(expr1, OrBooleanExpression) \ and isinstance(expr2, AndBooleanExpression): result = 1 else: # Both compound, same boolean operator: sort according to contents. # This will order according to recursive invocations of this comparator, # on sub-expressions. result = iter_lex_cmp( expr1.operands, expr2.operands, comparison_expression_cmp, ) return result
def observation_expression_cmp(expr1, expr2): """ Compare two observation expression ASTs. This is sensitive to the order of the expressions' sub-components. To achieve an order-insensitive comparison, the ASTs must be canonically ordered first. Args: expr1: The first observation expression expr2: The second observation expression Returns: <0, 0, or >0 depending on whether the first arg is less, equal or greater than the second """ type1 = type(expr1) type2 = type(expr2) type1_idx = _OBSERVATION_EXPRESSION_TYPE_ORDER.index(type1) type2_idx = _OBSERVATION_EXPRESSION_TYPE_ORDER.index(type2) if type1_idx != type2_idx: result = generic_cmp(type1_idx, type2_idx) # else, both exprs are of same type. # If they're simple, use contained comparison expression order elif type1 is ObservationExpression: result = comparison_expression_cmp( expr1.operand, expr2.operand, ) elif isinstance(expr1, _CompoundObservationExpression): # Both compound, and of same type (and/or/followedby): sort according # to contents. result = iter_lex_cmp( expr1.operands, expr2.operands, observation_expression_cmp, ) else: # QualifiedObservationExpression # Both qualified. Check qualifiers first; if they are the same, # use order of the qualified expressions. qual1_type = type(expr1.qualifier) qual2_type = type(expr2.qualifier) qual1_type_idx = _QUALIFIER_TYPE_ORDER.index(qual1_type) qual2_type_idx = _QUALIFIER_TYPE_ORDER.index(qual2_type) result = generic_cmp(qual1_type_idx, qual2_type_idx) if result == 0: # Same qualifier type; compare qualifier details qual_cmp = _QUALIFIER_COMPARATORS.get(qual1_type) if qual_cmp: result = qual_cmp(expr1.qualifier, expr2.qualifier) else: raise TypeError( "Can't compare qualifier type: " + qual1_type.__name__, ) if result == 0: # Same qualifier type and details; use qualified expression order result = observation_expression_cmp( expr1.observation_expression, expr2.observation_expression, ) return result